@aaronshaf/ger 1.2.10 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ast-grep/rules/no-as-casting.yml +13 -0
- package/.claude-plugin/plugin.json +22 -0
- package/.github/workflows/ci-simple.yml +53 -0
- package/.github/workflows/ci.yml +171 -0
- package/.github/workflows/claude-code-review.yml +83 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/dependency-update.yml +84 -0
- package/.github/workflows/release.yml +166 -0
- package/.github/workflows/security-scan.yml +113 -0
- package/.github/workflows/security.yml +96 -0
- package/.husky/pre-commit +16 -0
- package/.husky/pre-push +25 -0
- package/.lintstagedrc.json +6 -0
- package/.tool-versions +1 -0
- package/CLAUDE.md +105 -0
- package/DEVELOPMENT.md +361 -0
- package/EXAMPLES.md +457 -0
- package/README.md +831 -16
- package/bin/ger +3 -18
- package/biome.json +36 -0
- package/bun.lock +678 -0
- package/bunfig.toml +8 -0
- package/docs/adr/0001-use-effect-for-side-effects.md +65 -0
- package/docs/adr/0002-use-bun-runtime.md +64 -0
- package/docs/adr/0003-store-credentials-in-home-directory.md +75 -0
- package/docs/adr/0004-use-commander-for-cli.md +76 -0
- package/docs/adr/0005-use-effect-schema-for-validation.md +93 -0
- package/docs/adr/0006-use-msw-for-api-mocking.md +89 -0
- package/docs/adr/0007-git-hooks-for-quality.md +94 -0
- package/docs/adr/0008-no-as-typecasting.md +83 -0
- package/docs/adr/0009-file-size-limits.md +82 -0
- package/docs/adr/0010-llm-friendly-xml-output.md +93 -0
- package/docs/adr/0011-ai-tool-strategy-pattern.md +102 -0
- package/docs/adr/0012-build-status-message-parsing.md +94 -0
- package/docs/adr/0013-git-subprocess-integration.md +98 -0
- package/docs/adr/0014-group-management-support.md +95 -0
- package/docs/adr/0015-batch-comment-processing.md +111 -0
- package/docs/adr/0016-flexible-change-identifiers.md +94 -0
- package/docs/adr/0017-git-worktree-support.md +102 -0
- package/docs/adr/0018-auto-install-commit-hook.md +103 -0
- package/docs/adr/0019-sdk-package-exports.md +95 -0
- package/docs/adr/0020-code-coverage-enforcement.md +105 -0
- package/docs/adr/0021-typescript-isolated-declarations.md +83 -0
- package/docs/adr/0022-biome-oxlint-tooling.md +124 -0
- package/docs/adr/README.md +30 -0
- package/docs/prd/README.md +12 -0
- package/docs/prd/architecture.md +325 -0
- package/docs/prd/commands.md +425 -0
- package/docs/prd/data-model.md +349 -0
- package/docs/prd/overview.md +124 -0
- package/index.ts +219 -0
- package/oxlint.json +24 -0
- package/package.json +82 -15
- package/scripts/check-coverage.ts +69 -0
- package/scripts/check-file-size.ts +38 -0
- package/scripts/fix-test-mocks.ts +55 -0
- package/skills/gerrit-workflow/SKILL.md +247 -0
- package/skills/gerrit-workflow/examples.md +572 -0
- package/skills/gerrit-workflow/reference.md +728 -0
- package/src/api/gerrit.ts +696 -0
- package/src/cli/commands/abandon.ts +65 -0
- package/src/cli/commands/add-reviewer.ts +156 -0
- package/src/cli/commands/build-status.ts +282 -0
- package/src/cli/commands/checkout.ts +422 -0
- package/src/cli/commands/comment.ts +460 -0
- package/src/cli/commands/comments.ts +85 -0
- package/src/cli/commands/diff.ts +71 -0
- package/src/cli/commands/extract-url.ts +266 -0
- package/src/cli/commands/groups-members.ts +104 -0
- package/src/cli/commands/groups-show.ts +169 -0
- package/src/cli/commands/groups.ts +137 -0
- package/src/cli/commands/incoming.ts +226 -0
- package/src/cli/commands/init.ts +164 -0
- package/src/cli/commands/mine.ts +115 -0
- package/src/cli/commands/open.ts +57 -0
- package/src/cli/commands/projects.ts +68 -0
- package/src/cli/commands/push.ts +430 -0
- package/src/cli/commands/rebase.ts +52 -0
- package/src/cli/commands/remove-reviewer.ts +123 -0
- package/src/cli/commands/restore.ts +50 -0
- package/src/cli/commands/review.ts +486 -0
- package/src/cli/commands/search.ts +162 -0
- package/src/cli/commands/setup.ts +286 -0
- package/src/cli/commands/show.ts +491 -0
- package/src/cli/commands/status.ts +35 -0
- package/src/cli/commands/submit.ts +108 -0
- package/src/cli/commands/vote.ts +119 -0
- package/src/cli/commands/workspace.ts +200 -0
- package/src/cli/index.ts +53 -0
- package/src/cli/register-commands.ts +659 -0
- package/src/cli/register-group-commands.ts +88 -0
- package/src/cli/register-reviewer-commands.ts +97 -0
- package/src/prompts/default-review.md +86 -0
- package/src/prompts/system-inline-review.md +135 -0
- package/src/prompts/system-overall-review.md +206 -0
- package/src/schemas/config.test.ts +245 -0
- package/src/schemas/config.ts +84 -0
- package/src/schemas/gerrit.ts +681 -0
- package/src/services/commit-hook.ts +314 -0
- package/src/services/config.test.ts +150 -0
- package/src/services/config.ts +250 -0
- package/src/services/git-worktree.ts +342 -0
- package/src/services/review-strategy.ts +292 -0
- package/src/test-utils/mock-generator.ts +138 -0
- package/src/utils/change-id.test.ts +98 -0
- package/src/utils/change-id.ts +63 -0
- package/src/utils/comment-formatters.ts +153 -0
- package/src/utils/diff-context.ts +103 -0
- package/src/utils/diff-formatters.ts +141 -0
- package/src/utils/formatters.ts +85 -0
- package/src/utils/git-commit.test.ts +277 -0
- package/src/utils/git-commit.ts +122 -0
- package/src/utils/index.ts +55 -0
- package/src/utils/message-filters.ts +26 -0
- package/src/utils/review-formatters.ts +89 -0
- package/src/utils/review-prompt-builder.ts +110 -0
- package/src/utils/shell-safety.ts +117 -0
- package/src/utils/status-indicators.ts +100 -0
- package/src/utils/url-parser.test.ts +271 -0
- package/src/utils/url-parser.ts +118 -0
- package/tests/abandon.test.ts +230 -0
- package/tests/add-reviewer.test.ts +579 -0
- package/tests/build-status-watch.test.ts +344 -0
- package/tests/build-status.test.ts +789 -0
- package/tests/change-id-formats.test.ts +268 -0
- package/tests/checkout/integration.test.ts +653 -0
- package/tests/checkout/parse-input.test.ts +55 -0
- package/tests/checkout/validation.test.ts +178 -0
- package/tests/comment-batch-advanced.test.ts +431 -0
- package/tests/comment-gerrit-api-compliance.test.ts +414 -0
- package/tests/comment.test.ts +708 -0
- package/tests/comments.test.ts +323 -0
- package/tests/config-service-simple.test.ts +100 -0
- package/tests/diff.test.ts +419 -0
- package/tests/extract-url.test.ts +517 -0
- package/tests/groups-members.test.ts +256 -0
- package/tests/groups-show.test.ts +323 -0
- package/tests/groups.test.ts +334 -0
- package/tests/helpers/build-status-test-setup.ts +83 -0
- package/tests/helpers/config-mock.ts +27 -0
- package/tests/incoming.test.ts +357 -0
- package/tests/init.test.ts +70 -0
- package/tests/integration/commit-hook.test.ts +246 -0
- package/tests/interactive-incoming.test.ts +173 -0
- package/tests/mine.test.ts +285 -0
- package/tests/mocks/msw-handlers.ts +80 -0
- package/tests/open.test.ts +233 -0
- package/tests/projects.test.ts +259 -0
- package/tests/rebase.test.ts +271 -0
- package/tests/remove-reviewer.test.ts +357 -0
- package/tests/restore.test.ts +237 -0
- package/tests/review.test.ts +135 -0
- package/tests/search.test.ts +712 -0
- package/tests/setup.test.ts +63 -0
- package/tests/show-auto-detect.test.ts +324 -0
- package/tests/show.test.ts +813 -0
- package/tests/status.test.ts +145 -0
- package/tests/submit.test.ts +316 -0
- package/tests/unit/commands/push.test.ts +194 -0
- package/tests/unit/git-branch-detection.test.ts +82 -0
- package/tests/unit/git-worktree.test.ts +55 -0
- package/tests/unit/patterns/push-patterns.test.ts +148 -0
- package/tests/unit/schemas/gerrit.test.ts +85 -0
- package/tests/unit/services/commit-hook.test.ts +132 -0
- package/tests/unit/services/review-strategy.test.ts +349 -0
- package/tests/unit/test-utils/mock-generator.test.ts +154 -0
- package/tests/unit/utils/comment-formatters.test.ts +415 -0
- package/tests/unit/utils/diff-context.test.ts +171 -0
- package/tests/unit/utils/diff-formatters.test.ts +165 -0
- package/tests/unit/utils/formatters.test.ts +411 -0
- package/tests/unit/utils/message-filters.test.ts +227 -0
- package/tests/unit/utils/shell-safety.test.ts +230 -0
- package/tests/unit/utils/status-indicators.test.ts +137 -0
- package/tests/vote.test.ts +317 -0
- package/tests/workspace.test.ts +295 -0
- package/tsconfig.json +36 -5
- package/src/commands/branch.ts +0 -180
- package/src/ger.ts +0 -22
- package/src/types.d.ts +0 -35
- package/src/utils.ts +0 -130
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Data Model
|
|
2
|
+
|
|
3
|
+
Schemas and data structures used throughout the application.
|
|
4
|
+
|
|
5
|
+
## Gerrit API Types
|
|
6
|
+
|
|
7
|
+
### ChangeInfo
|
|
8
|
+
|
|
9
|
+
Primary change representation from Gerrit API.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const ChangeInfo = Schema.Struct({
|
|
13
|
+
id: Schema.String, // "project~branch~Change-Id"
|
|
14
|
+
_number: Schema.Number, // Numeric change ID
|
|
15
|
+
project: Schema.String, // Project name
|
|
16
|
+
branch: Schema.String, // Target branch
|
|
17
|
+
topic: Schema.optional(Schema.String),
|
|
18
|
+
subject: Schema.String, // First line of commit message
|
|
19
|
+
status: Schema.Literal('NEW', 'MERGED', 'ABANDONED', 'DRAFT'),
|
|
20
|
+
created: Schema.String, // ISO timestamp
|
|
21
|
+
updated: Schema.String, // ISO timestamp
|
|
22
|
+
submitted: Schema.optional(Schema.String),
|
|
23
|
+
submitter: Schema.optional(AccountInfo),
|
|
24
|
+
owner: AccountInfo,
|
|
25
|
+
current_revision: Schema.optional(Schema.String),
|
|
26
|
+
revisions: Schema.optional(Schema.Record(Schema.String, RevisionInfo)),
|
|
27
|
+
labels: Schema.optional(Schema.Record(Schema.String, LabelInfo)),
|
|
28
|
+
reviewers: Schema.optional(ReviewerMap),
|
|
29
|
+
messages: Schema.optional(Schema.Array(ChangeMessage)),
|
|
30
|
+
mergeable: Schema.optional(Schema.Boolean),
|
|
31
|
+
insertions: Schema.optional(Schema.Number),
|
|
32
|
+
deletions: Schema.optional(Schema.Number),
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### AccountInfo
|
|
37
|
+
|
|
38
|
+
User account information.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const AccountInfo = Schema.Struct({
|
|
42
|
+
_account_id: Schema.Number,
|
|
43
|
+
name: Schema.optional(Schema.String),
|
|
44
|
+
email: Schema.optional(Schema.String),
|
|
45
|
+
username: Schema.optional(Schema.String),
|
|
46
|
+
display_name: Schema.optional(Schema.String),
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### RevisionInfo
|
|
51
|
+
|
|
52
|
+
Patchset revision details.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const RevisionInfo = Schema.Struct({
|
|
56
|
+
_number: Schema.Number, // Patchset number
|
|
57
|
+
kind: Schema.Literal(
|
|
58
|
+
'REWORK', 'TRIVIAL_REBASE', 'MERGE_FIRST_PARENT_UPDATE',
|
|
59
|
+
'NO_CODE_CHANGE', 'NO_CHANGE'
|
|
60
|
+
),
|
|
61
|
+
created: Schema.String,
|
|
62
|
+
uploader: AccountInfo,
|
|
63
|
+
ref: Schema.String, // Git ref (refs/changes/xx/xxxxx/x)
|
|
64
|
+
fetch: Schema.optional(Schema.Record(Schema.String, FetchInfo)),
|
|
65
|
+
commit: Schema.optional(CommitInfo),
|
|
66
|
+
files: Schema.optional(Schema.Record(Schema.String, FileInfo)),
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### CommentInfo
|
|
71
|
+
|
|
72
|
+
Comment on a change.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const CommentInfo = Schema.Struct({
|
|
76
|
+
id: Schema.String,
|
|
77
|
+
path: Schema.optional(Schema.String),
|
|
78
|
+
line: Schema.optional(Schema.Number),
|
|
79
|
+
range: Schema.optional(CommentRange),
|
|
80
|
+
message: Schema.String,
|
|
81
|
+
author: AccountInfo,
|
|
82
|
+
updated: Schema.String,
|
|
83
|
+
side: Schema.optional(Schema.Literal('PARENT', 'REVISION')),
|
|
84
|
+
unresolved: Schema.optional(Schema.Boolean),
|
|
85
|
+
in_reply_to: Schema.optional(Schema.String),
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### CommentRange
|
|
90
|
+
|
|
91
|
+
Line range for inline comments.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const CommentRange = Schema.Struct({
|
|
95
|
+
start_line: Schema.Number,
|
|
96
|
+
start_character: Schema.Number,
|
|
97
|
+
end_line: Schema.Number,
|
|
98
|
+
end_character: Schema.Number,
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### ChangeMessage
|
|
103
|
+
|
|
104
|
+
Message/event on a change.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const ChangeMessage = Schema.Struct({
|
|
108
|
+
id: Schema.String,
|
|
109
|
+
author: Schema.optional(AccountInfo),
|
|
110
|
+
date: Schema.String,
|
|
111
|
+
message: Schema.String,
|
|
112
|
+
tag: Schema.optional(Schema.String),
|
|
113
|
+
_revision_number: Schema.optional(Schema.Number),
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### LabelInfo
|
|
118
|
+
|
|
119
|
+
Voting label information.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const LabelInfo = Schema.Struct({
|
|
123
|
+
approved: Schema.optional(AccountInfo),
|
|
124
|
+
rejected: Schema.optional(AccountInfo),
|
|
125
|
+
recommended: Schema.optional(AccountInfo),
|
|
126
|
+
disliked: Schema.optional(AccountInfo),
|
|
127
|
+
value: Schema.optional(Schema.Number),
|
|
128
|
+
default_value: Schema.optional(Schema.Number),
|
|
129
|
+
all: Schema.optional(Schema.Array(ApprovalInfo)),
|
|
130
|
+
values: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### GroupInfo
|
|
135
|
+
|
|
136
|
+
Gerrit group information.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const GroupInfo = Schema.Struct({
|
|
140
|
+
id: Schema.String,
|
|
141
|
+
name: Schema.String,
|
|
142
|
+
url: Schema.optional(Schema.String),
|
|
143
|
+
options: Schema.optional(GroupOptions),
|
|
144
|
+
description: Schema.optional(Schema.String),
|
|
145
|
+
group_id: Schema.optional(Schema.Number),
|
|
146
|
+
owner: Schema.optional(Schema.String),
|
|
147
|
+
owner_id: Schema.optional(Schema.String),
|
|
148
|
+
created_on: Schema.optional(Schema.String),
|
|
149
|
+
members: Schema.optional(Schema.Array(AccountInfo)),
|
|
150
|
+
includes: Schema.optional(Schema.Array(GroupInfo)),
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Configuration Schema
|
|
155
|
+
|
|
156
|
+
### AppConfig
|
|
157
|
+
|
|
158
|
+
Application configuration stored in `~/.ger/config.json`.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const AppConfig = Schema.Struct({
|
|
162
|
+
host: Schema.String, // Gerrit server URL
|
|
163
|
+
username: Schema.String, // Gerrit username
|
|
164
|
+
password: Schema.String, // HTTP password/token
|
|
165
|
+
aiTool: Schema.optional(Schema.String), // Preferred AI tool
|
|
166
|
+
aiAutoDetect: Schema.optional(Schema.Boolean), // Auto-detect AI tool
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**File permissions:** 0600 (owner read/write only)
|
|
171
|
+
|
|
172
|
+
## API Input Types
|
|
173
|
+
|
|
174
|
+
### ReviewInput
|
|
175
|
+
|
|
176
|
+
Input for posting reviews/comments.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const ReviewInput = Schema.Struct({
|
|
180
|
+
message: Schema.optional(Schema.String), // Overall comment
|
|
181
|
+
labels: Schema.optional(Schema.Record(Schema.String, Schema.Number)),
|
|
182
|
+
comments: Schema.optional(Schema.Record(
|
|
183
|
+
Schema.String, // file path
|
|
184
|
+
Schema.Array(CommentInput)
|
|
185
|
+
)),
|
|
186
|
+
tag: Schema.optional(Schema.String),
|
|
187
|
+
notify: Schema.optional(Schema.Literal('NONE', 'OWNER', 'OWNER_REVIEWERS', 'ALL')),
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### CommentInput
|
|
192
|
+
|
|
193
|
+
Input for a single inline comment.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const CommentInput = Schema.Struct({
|
|
197
|
+
line: Schema.optional(Schema.Number),
|
|
198
|
+
range: Schema.optional(CommentRange),
|
|
199
|
+
message: Schema.String,
|
|
200
|
+
side: Schema.optional(Schema.Literal('PARENT', 'REVISION')),
|
|
201
|
+
unresolved: Schema.optional(Schema.Boolean),
|
|
202
|
+
in_reply_to: Schema.optional(Schema.String),
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### ReviewerInput
|
|
207
|
+
|
|
208
|
+
Input for adding reviewers.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const ReviewerInput = Schema.Struct({
|
|
212
|
+
reviewer: Schema.String, // Username, email, or group
|
|
213
|
+
state: Schema.optional(Schema.Literal('REVIEWER', 'CC')),
|
|
214
|
+
notify: Schema.optional(Schema.Literal('NONE', 'OWNER', 'OWNER_REVIEWERS', 'ALL')),
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Internal Types
|
|
219
|
+
|
|
220
|
+
### BuildState
|
|
221
|
+
|
|
222
|
+
CI build status.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
type BuildState = 'pending' | 'running' | 'success' | 'failure' | 'not_found'
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### InlineComment
|
|
229
|
+
|
|
230
|
+
CLI input format for batch comments.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const InlineComment = Schema.Struct({
|
|
234
|
+
file: Schema.String,
|
|
235
|
+
line: Schema.optional(Schema.Number),
|
|
236
|
+
range: Schema.optional(CommentRange),
|
|
237
|
+
message: Schema.String,
|
|
238
|
+
side: Schema.optional(Schema.Literal('PARENT', 'REVISION')),
|
|
239
|
+
unresolved: Schema.optional(Schema.Boolean),
|
|
240
|
+
})
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### ChangeIdentifier
|
|
244
|
+
|
|
245
|
+
Normalized change identifier.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Accepts:
|
|
249
|
+
// - Numeric: "12345"
|
|
250
|
+
// - Change-ID: "If5a3ae8cb5a107e187447802358417f311d0c4b1"
|
|
251
|
+
// - Full triplet: "project~branch~Change-Id"
|
|
252
|
+
// - URL: "https://gerrit.example.com/c/project/+/12345"
|
|
253
|
+
|
|
254
|
+
const isChangeNumber = (id: string): boolean => /^\d+$/.test(id)
|
|
255
|
+
const isChangeId = (id: string): boolean => /^I[0-9a-f]{40}$/i.test(id)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Error Types
|
|
259
|
+
|
|
260
|
+
### ApiError
|
|
261
|
+
|
|
262
|
+
API call failures.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
class ApiError extends Schema.TaggedError<ApiError>()('ApiError', {
|
|
266
|
+
message: Schema.String,
|
|
267
|
+
statusCode: Schema.Number,
|
|
268
|
+
url: Schema.String,
|
|
269
|
+
}) {}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### ConfigError
|
|
273
|
+
|
|
274
|
+
Configuration issues.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
class ConfigError extends Schema.TaggedError<ConfigError>()('ConfigError', {
|
|
278
|
+
message: Schema.String,
|
|
279
|
+
}) {}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### GitError
|
|
283
|
+
|
|
284
|
+
Git operation failures.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
class GitError extends Schema.TaggedError<GitError>()('GitError', {
|
|
288
|
+
message: Schema.String,
|
|
289
|
+
exitCode: Schema.Number,
|
|
290
|
+
}) {}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### NoChangeIdError
|
|
294
|
+
|
|
295
|
+
Missing Change-ID in commit.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
class NoChangeIdError extends Schema.TaggedError<NoChangeIdError>()('NoChangeIdError', {
|
|
299
|
+
message: Schema.String,
|
|
300
|
+
}) {}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Output Formats
|
|
304
|
+
|
|
305
|
+
### Text (Default)
|
|
306
|
+
|
|
307
|
+
Human-readable colored output:
|
|
308
|
+
```
|
|
309
|
+
Change 12345: Fix login bug
|
|
310
|
+
Project: canvas-lms
|
|
311
|
+
Branch: main
|
|
312
|
+
Status: NEW
|
|
313
|
+
Owner: alice@example.com
|
|
314
|
+
|
|
315
|
+
Files:
|
|
316
|
+
M src/login.ts
|
|
317
|
+
A src/auth.ts
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### XML (--xml flag)
|
|
321
|
+
|
|
322
|
+
LLM-friendly structured output:
|
|
323
|
+
```xml
|
|
324
|
+
<change>
|
|
325
|
+
<number>12345</number>
|
|
326
|
+
<subject><![CDATA[Fix login bug]]></subject>
|
|
327
|
+
<project>canvas-lms</project>
|
|
328
|
+
<branch>main</branch>
|
|
329
|
+
<status>NEW</status>
|
|
330
|
+
<owner>alice@example.com</owner>
|
|
331
|
+
<files>
|
|
332
|
+
<file action="M">src/login.ts</file>
|
|
333
|
+
<file action="A">src/auth.ts</file>
|
|
334
|
+
</files>
|
|
335
|
+
</change>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### JSON
|
|
339
|
+
|
|
340
|
+
Programmatic consumption:
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"_number": 12345,
|
|
344
|
+
"subject": "Fix login bug",
|
|
345
|
+
"project": "canvas-lms",
|
|
346
|
+
"branch": "main",
|
|
347
|
+
"status": "NEW"
|
|
348
|
+
}
|
|
349
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# ger - Gerrit CLI Tool
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`ger` is a modern, LLM-friendly CLI tool and SDK for Gerrit Code Review. It provides comprehensive Gerrit workflow automation with AI integration, published as both a CLI tool and npm package.
|
|
6
|
+
|
|
7
|
+
## Goals
|
|
8
|
+
|
|
9
|
+
1. **Fast Gerrit operations** - Quick access to changes, diffs, comments without browser
|
|
10
|
+
2. **AI integration** - LLM-friendly output and automated code review
|
|
11
|
+
3. **Developer ergonomics** - Auto-detection, smart defaults, minimal configuration
|
|
12
|
+
4. **Programmatic access** - SDK for building custom tools and automation
|
|
13
|
+
5. **Type safety** - Full TypeScript with Effect for reliable operations
|
|
14
|
+
|
|
15
|
+
## Non-Goals
|
|
16
|
+
|
|
17
|
+
- Replace Gerrit web UI entirely (complex dashboards, admin)
|
|
18
|
+
- Support Gerrit versions older than 3.0
|
|
19
|
+
- Implement bidirectional sync (changes are created via git push)
|
|
20
|
+
- Provide real-time notifications (use Gerrit's native features)
|
|
21
|
+
|
|
22
|
+
## Design Decisions
|
|
23
|
+
|
|
24
|
+
| Decision | Choice | Rationale |
|
|
25
|
+
|----------|--------|-----------|
|
|
26
|
+
| Language | TypeScript/Bun | Fast runtime, native TS, matches ji/cn projects |
|
|
27
|
+
| Error handling | Effect library | Type-safe errors, composable operations |
|
|
28
|
+
| CLI framework | Commander.js | Stable, simple, well-documented |
|
|
29
|
+
| Output formats | Text, XML, JSON | Human + machine readable |
|
|
30
|
+
| Credentials | `~/.ger/config.json` | Secure, standard pattern |
|
|
31
|
+
| Git integration | Subprocess | No deps, full features, worktree support |
|
|
32
|
+
| Validation | Effect Schema | Single source of truth, type inference |
|
|
33
|
+
| Testing | MSW + Bun test | Fast, realistic API mocking |
|
|
34
|
+
| AI tools | Strategy pattern | Vendor-agnostic, auto-detection |
|
|
35
|
+
|
|
36
|
+
## User Personas
|
|
37
|
+
|
|
38
|
+
### Individual Developer
|
|
39
|
+
- Works on personal changes
|
|
40
|
+
- Needs quick status checks
|
|
41
|
+
- Wants CLI convenience over browser
|
|
42
|
+
|
|
43
|
+
### Code Reviewer
|
|
44
|
+
- Reviews multiple changes daily
|
|
45
|
+
- Needs efficient navigation
|
|
46
|
+
- Benefits from AI-assisted review
|
|
47
|
+
|
|
48
|
+
### Automation Engineer
|
|
49
|
+
- Builds CI/CD pipelines
|
|
50
|
+
- Needs programmatic access
|
|
51
|
+
- Requires JSON/XML output
|
|
52
|
+
|
|
53
|
+
### Team Lead
|
|
54
|
+
- Manages reviewers and groups
|
|
55
|
+
- Tracks team's open changes
|
|
56
|
+
- Assigns reviewers efficiently
|
|
57
|
+
|
|
58
|
+
## Commands Overview
|
|
59
|
+
|
|
60
|
+
| Category | Commands |
|
|
61
|
+
|----------|----------|
|
|
62
|
+
| **View** | `show`, `diff`, `comments`, `search` |
|
|
63
|
+
| **Review** | `comment`, `vote`, `review`, `add-reviewer` |
|
|
64
|
+
| **Manage** | `mine`, `incoming`, `abandon`, `restore` |
|
|
65
|
+
| **Operations** | `checkout`, `push`, `rebase`, `submit` |
|
|
66
|
+
| **Groups** | `groups`, `groups-show`, `groups-members` |
|
|
67
|
+
| **Utilities** | `status`, `setup`, `open`, `extract-url`, `build-status` |
|
|
68
|
+
|
|
69
|
+
## User Flows
|
|
70
|
+
|
|
71
|
+
### First-time Setup
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
$ ger setup
|
|
75
|
+
? Gerrit URL: https://gerrit.example.com
|
|
76
|
+
? Username: john.doe
|
|
77
|
+
? HTTP Password: ****
|
|
78
|
+
✓ Configuration saved to ~/.ger/config.json
|
|
79
|
+
✓ Connection verified
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Daily Code Review
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
$ ger incoming
|
|
86
|
+
PROJECT: canvas-lms
|
|
87
|
+
12345 Fix login bug alice CR: +1
|
|
88
|
+
12346 Add dark mode bob CR: 0
|
|
89
|
+
|
|
90
|
+
$ ger show 12345
|
|
91
|
+
Change 12345: Fix login bug
|
|
92
|
+
Author: alice@example.com
|
|
93
|
+
Status: NEW
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
$ ger review 12345
|
|
97
|
+
✓ AI review posted (3 inline comments, 1 overall comment)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Submit a Change
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
$ git commit -m "Fix typo in README"
|
|
104
|
+
$ ger push
|
|
105
|
+
✓ Pushed to refs/for/main
|
|
106
|
+
✓ Change 12347 created
|
|
107
|
+
|
|
108
|
+
$ ger add-reviewer 12347 bob carol
|
|
109
|
+
✓ Added 2 reviewers
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Success Metrics
|
|
113
|
+
|
|
114
|
+
- Handle repositories with 10,000+ changes
|
|
115
|
+
- Complete common operations in < 2 seconds
|
|
116
|
+
- AI review completes in < 30 seconds
|
|
117
|
+
- 80%+ test coverage maintained
|
|
118
|
+
|
|
119
|
+
## References
|
|
120
|
+
|
|
121
|
+
- [Gerrit REST API](https://gerrit-review.googlesource.com/Documentation/rest-api.html)
|
|
122
|
+
- [Effect library](https://effect.website/)
|
|
123
|
+
- [ji project](https://github.com/aaronshaf/ji) - JIRA CLI (same author)
|
|
124
|
+
- [cn project](https://github.com/aaronshaf/cn) - Confluence CLI (same author)
|
package/index.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aaronshaf/ger - Gerrit CLI and SDK
|
|
3
|
+
*
|
|
4
|
+
* This package provides both a CLI tool and a programmatic API for interacting with Gerrit Code Review.
|
|
5
|
+
* Built with Effect-TS for type-safe, composable operations.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*
|
|
9
|
+
* @example Basic usage with Effect
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { Effect, pipe } from 'effect'
|
|
12
|
+
* import {
|
|
13
|
+
* GerritApiService,
|
|
14
|
+
* GerritApiServiceLive,
|
|
15
|
+
* ConfigServiceLive,
|
|
16
|
+
* } from '@aaronshaf/ger'
|
|
17
|
+
*
|
|
18
|
+
* const program = Effect.gen(function* () {
|
|
19
|
+
* const api = yield* GerritApiService
|
|
20
|
+
* const change = yield* api.getChange('12345')
|
|
21
|
+
* console.log(change.subject)
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* const runnable = pipe(
|
|
25
|
+
* program,
|
|
26
|
+
* Effect.provide(GerritApiServiceLive),
|
|
27
|
+
* Effect.provide(ConfigServiceLive)
|
|
28
|
+
* )
|
|
29
|
+
*
|
|
30
|
+
* Effect.runPromise(runnable)
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Core API Service
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
// Service tag and implementation
|
|
40
|
+
GerritApiService,
|
|
41
|
+
GerritApiServiceLive,
|
|
42
|
+
// Types
|
|
43
|
+
type GerritApiServiceImpl,
|
|
44
|
+
// Errors
|
|
45
|
+
ApiError,
|
|
46
|
+
type ApiErrorFields,
|
|
47
|
+
} from './src/api/gerrit'
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Configuration Service
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
// Service tag and implementation
|
|
55
|
+
ConfigService,
|
|
56
|
+
ConfigServiceLive,
|
|
57
|
+
// Types
|
|
58
|
+
type ConfigServiceImpl,
|
|
59
|
+
// Errors
|
|
60
|
+
ConfigError,
|
|
61
|
+
type ConfigErrorFields,
|
|
62
|
+
} from './src/services/config'
|
|
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
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Git Worktree Service
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
// Service tag and implementation
|
|
90
|
+
GitWorktreeService,
|
|
91
|
+
GitWorktreeServiceLive,
|
|
92
|
+
type GitWorktreeServiceImpl,
|
|
93
|
+
// Types
|
|
94
|
+
type WorktreeInfo,
|
|
95
|
+
// Errors
|
|
96
|
+
WorktreeCreationError,
|
|
97
|
+
type WorktreeCreationErrorFields,
|
|
98
|
+
PatchsetFetchError,
|
|
99
|
+
type PatchsetFetchErrorFields,
|
|
100
|
+
DirtyRepoError,
|
|
101
|
+
type DirtyRepoErrorFields,
|
|
102
|
+
NotGitRepoError,
|
|
103
|
+
type NotGitRepoErrorFields,
|
|
104
|
+
type GitWorktreeError,
|
|
105
|
+
} from './src/services/git-worktree'
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Schemas and Types
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
// Authentication
|
|
113
|
+
GerritCredentials,
|
|
114
|
+
type GerritCredentials as GerritCredentialsType,
|
|
115
|
+
// Changes
|
|
116
|
+
ChangeInfo,
|
|
117
|
+
type ChangeInfo as ChangeInfoType,
|
|
118
|
+
// Comments
|
|
119
|
+
CommentInput,
|
|
120
|
+
type CommentInput as CommentInputType,
|
|
121
|
+
CommentInfo,
|
|
122
|
+
type CommentInfo as CommentInfoType,
|
|
123
|
+
// Messages
|
|
124
|
+
MessageInfo,
|
|
125
|
+
type MessageInfo as MessageInfoType,
|
|
126
|
+
// Reviews
|
|
127
|
+
ReviewInput,
|
|
128
|
+
type ReviewInput as ReviewInputType,
|
|
129
|
+
// Files and Diffs
|
|
130
|
+
FileInfo,
|
|
131
|
+
type FileInfo as FileInfoType,
|
|
132
|
+
FileDiffContent,
|
|
133
|
+
type FileDiffContent as FileDiffContentType,
|
|
134
|
+
RevisionInfo,
|
|
135
|
+
type RevisionInfo as RevisionInfoType,
|
|
136
|
+
// Diff Options
|
|
137
|
+
DiffFormat,
|
|
138
|
+
type DiffFormat as DiffFormatType,
|
|
139
|
+
DiffOptions,
|
|
140
|
+
type DiffOptions as DiffOptionsType,
|
|
141
|
+
DiffCommandOptions,
|
|
142
|
+
type DiffCommandOptions as DiffCommandOptionsType,
|
|
143
|
+
// Errors
|
|
144
|
+
GerritError,
|
|
145
|
+
type GerritError as GerritErrorType,
|
|
146
|
+
} from './src/schemas/gerrit'
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
// Config schemas
|
|
150
|
+
AppConfig,
|
|
151
|
+
type AppConfig as AppConfigType,
|
|
152
|
+
AiConfig,
|
|
153
|
+
type AiConfig as AiConfigType,
|
|
154
|
+
// Utilities
|
|
155
|
+
aiConfigFromFlat,
|
|
156
|
+
migrateFromNestedConfig,
|
|
157
|
+
} from './src/schemas/config'
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Utilities
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
// Change ID handling
|
|
165
|
+
normalizeChangeIdentifier,
|
|
166
|
+
isChangeId,
|
|
167
|
+
isChangeNumber,
|
|
168
|
+
isValidChangeIdentifier,
|
|
169
|
+
getIdentifierType,
|
|
170
|
+
} from './src/utils/change-id'
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
// Git commit utilities
|
|
174
|
+
extractChangeIdFromCommitMessage,
|
|
175
|
+
getLastCommitMessage,
|
|
176
|
+
getChangeIdFromHead,
|
|
177
|
+
GitError,
|
|
178
|
+
NoChangeIdError,
|
|
179
|
+
} from './src/utils/git-commit'
|
|
180
|
+
|
|
181
|
+
export {
|
|
182
|
+
// URL parsing
|
|
183
|
+
extractChangeNumber,
|
|
184
|
+
normalizeGerritHost,
|
|
185
|
+
isValidChangeId,
|
|
186
|
+
} from './src/utils/url-parser'
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
// Message filtering
|
|
190
|
+
filterMeaningfulMessages,
|
|
191
|
+
sortMessagesByDate,
|
|
192
|
+
} from './src/utils/message-filters'
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
// Shell safety
|
|
196
|
+
sanitizeCDATA,
|
|
197
|
+
} from './src/utils/shell-safety'
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
// Formatters
|
|
201
|
+
formatDate,
|
|
202
|
+
getStatusIndicator,
|
|
203
|
+
colors,
|
|
204
|
+
} from './src/utils/formatters'
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
// Comment formatters
|
|
208
|
+
formatCommentsPretty,
|
|
209
|
+
formatCommentsXml,
|
|
210
|
+
type CommentWithContext,
|
|
211
|
+
} from './src/utils/comment-formatters'
|
|
212
|
+
|
|
213
|
+
export {
|
|
214
|
+
// Diff formatters
|
|
215
|
+
formatDiffPretty,
|
|
216
|
+
formatDiffSummary,
|
|
217
|
+
formatFilesList,
|
|
218
|
+
extractDiffStats,
|
|
219
|
+
} from './src/utils/diff-formatters'
|
package/oxlint.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": {
|
|
3
|
+
"no-implicit-any": "error",
|
|
4
|
+
"no-explicit-any": "error",
|
|
5
|
+
"no-unused-vars": "error",
|
|
6
|
+
"no-console": "warn",
|
|
7
|
+
"no-debugger": "error",
|
|
8
|
+
"prefer-const": "error",
|
|
9
|
+
"no-var": "error",
|
|
10
|
+
"eqeqeq": "error",
|
|
11
|
+
"no-eval": "error"
|
|
12
|
+
},
|
|
13
|
+
"ignorePatterns": [
|
|
14
|
+
"node_modules",
|
|
15
|
+
"dist",
|
|
16
|
+
"tmp",
|
|
17
|
+
"**/*.js",
|
|
18
|
+
"**/*.jsx",
|
|
19
|
+
"**/*.tsx",
|
|
20
|
+
"coverage",
|
|
21
|
+
"*.config.js",
|
|
22
|
+
"*.config.ts"
|
|
23
|
+
]
|
|
24
|
+
}
|