@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.
Files changed (180) hide show
  1. package/.ast-grep/rules/no-as-casting.yml +13 -0
  2. package/.claude-plugin/plugin.json +22 -0
  3. package/.github/workflows/ci-simple.yml +53 -0
  4. package/.github/workflows/ci.yml +171 -0
  5. package/.github/workflows/claude-code-review.yml +83 -0
  6. package/.github/workflows/claude.yml +50 -0
  7. package/.github/workflows/dependency-update.yml +84 -0
  8. package/.github/workflows/release.yml +166 -0
  9. package/.github/workflows/security-scan.yml +113 -0
  10. package/.github/workflows/security.yml +96 -0
  11. package/.husky/pre-commit +16 -0
  12. package/.husky/pre-push +25 -0
  13. package/.lintstagedrc.json +6 -0
  14. package/.tool-versions +1 -0
  15. package/CLAUDE.md +105 -0
  16. package/DEVELOPMENT.md +361 -0
  17. package/EXAMPLES.md +457 -0
  18. package/README.md +831 -16
  19. package/bin/ger +3 -18
  20. package/biome.json +36 -0
  21. package/bun.lock +678 -0
  22. package/bunfig.toml +8 -0
  23. package/docs/adr/0001-use-effect-for-side-effects.md +65 -0
  24. package/docs/adr/0002-use-bun-runtime.md +64 -0
  25. package/docs/adr/0003-store-credentials-in-home-directory.md +75 -0
  26. package/docs/adr/0004-use-commander-for-cli.md +76 -0
  27. package/docs/adr/0005-use-effect-schema-for-validation.md +93 -0
  28. package/docs/adr/0006-use-msw-for-api-mocking.md +89 -0
  29. package/docs/adr/0007-git-hooks-for-quality.md +94 -0
  30. package/docs/adr/0008-no-as-typecasting.md +83 -0
  31. package/docs/adr/0009-file-size-limits.md +82 -0
  32. package/docs/adr/0010-llm-friendly-xml-output.md +93 -0
  33. package/docs/adr/0011-ai-tool-strategy-pattern.md +102 -0
  34. package/docs/adr/0012-build-status-message-parsing.md +94 -0
  35. package/docs/adr/0013-git-subprocess-integration.md +98 -0
  36. package/docs/adr/0014-group-management-support.md +95 -0
  37. package/docs/adr/0015-batch-comment-processing.md +111 -0
  38. package/docs/adr/0016-flexible-change-identifiers.md +94 -0
  39. package/docs/adr/0017-git-worktree-support.md +102 -0
  40. package/docs/adr/0018-auto-install-commit-hook.md +103 -0
  41. package/docs/adr/0019-sdk-package-exports.md +95 -0
  42. package/docs/adr/0020-code-coverage-enforcement.md +105 -0
  43. package/docs/adr/0021-typescript-isolated-declarations.md +83 -0
  44. package/docs/adr/0022-biome-oxlint-tooling.md +124 -0
  45. package/docs/adr/README.md +30 -0
  46. package/docs/prd/README.md +12 -0
  47. package/docs/prd/architecture.md +325 -0
  48. package/docs/prd/commands.md +425 -0
  49. package/docs/prd/data-model.md +349 -0
  50. package/docs/prd/overview.md +124 -0
  51. package/index.ts +219 -0
  52. package/oxlint.json +24 -0
  53. package/package.json +82 -15
  54. package/scripts/check-coverage.ts +69 -0
  55. package/scripts/check-file-size.ts +38 -0
  56. package/scripts/fix-test-mocks.ts +55 -0
  57. package/skills/gerrit-workflow/SKILL.md +247 -0
  58. package/skills/gerrit-workflow/examples.md +572 -0
  59. package/skills/gerrit-workflow/reference.md +728 -0
  60. package/src/api/gerrit.ts +696 -0
  61. package/src/cli/commands/abandon.ts +65 -0
  62. package/src/cli/commands/add-reviewer.ts +156 -0
  63. package/src/cli/commands/build-status.ts +282 -0
  64. package/src/cli/commands/checkout.ts +422 -0
  65. package/src/cli/commands/comment.ts +460 -0
  66. package/src/cli/commands/comments.ts +85 -0
  67. package/src/cli/commands/diff.ts +71 -0
  68. package/src/cli/commands/extract-url.ts +266 -0
  69. package/src/cli/commands/groups-members.ts +104 -0
  70. package/src/cli/commands/groups-show.ts +169 -0
  71. package/src/cli/commands/groups.ts +137 -0
  72. package/src/cli/commands/incoming.ts +226 -0
  73. package/src/cli/commands/init.ts +164 -0
  74. package/src/cli/commands/mine.ts +115 -0
  75. package/src/cli/commands/open.ts +57 -0
  76. package/src/cli/commands/projects.ts +68 -0
  77. package/src/cli/commands/push.ts +430 -0
  78. package/src/cli/commands/rebase.ts +52 -0
  79. package/src/cli/commands/remove-reviewer.ts +123 -0
  80. package/src/cli/commands/restore.ts +50 -0
  81. package/src/cli/commands/review.ts +486 -0
  82. package/src/cli/commands/search.ts +162 -0
  83. package/src/cli/commands/setup.ts +286 -0
  84. package/src/cli/commands/show.ts +491 -0
  85. package/src/cli/commands/status.ts +35 -0
  86. package/src/cli/commands/submit.ts +108 -0
  87. package/src/cli/commands/vote.ts +119 -0
  88. package/src/cli/commands/workspace.ts +200 -0
  89. package/src/cli/index.ts +53 -0
  90. package/src/cli/register-commands.ts +659 -0
  91. package/src/cli/register-group-commands.ts +88 -0
  92. package/src/cli/register-reviewer-commands.ts +97 -0
  93. package/src/prompts/default-review.md +86 -0
  94. package/src/prompts/system-inline-review.md +135 -0
  95. package/src/prompts/system-overall-review.md +206 -0
  96. package/src/schemas/config.test.ts +245 -0
  97. package/src/schemas/config.ts +84 -0
  98. package/src/schemas/gerrit.ts +681 -0
  99. package/src/services/commit-hook.ts +314 -0
  100. package/src/services/config.test.ts +150 -0
  101. package/src/services/config.ts +250 -0
  102. package/src/services/git-worktree.ts +342 -0
  103. package/src/services/review-strategy.ts +292 -0
  104. package/src/test-utils/mock-generator.ts +138 -0
  105. package/src/utils/change-id.test.ts +98 -0
  106. package/src/utils/change-id.ts +63 -0
  107. package/src/utils/comment-formatters.ts +153 -0
  108. package/src/utils/diff-context.ts +103 -0
  109. package/src/utils/diff-formatters.ts +141 -0
  110. package/src/utils/formatters.ts +85 -0
  111. package/src/utils/git-commit.test.ts +277 -0
  112. package/src/utils/git-commit.ts +122 -0
  113. package/src/utils/index.ts +55 -0
  114. package/src/utils/message-filters.ts +26 -0
  115. package/src/utils/review-formatters.ts +89 -0
  116. package/src/utils/review-prompt-builder.ts +110 -0
  117. package/src/utils/shell-safety.ts +117 -0
  118. package/src/utils/status-indicators.ts +100 -0
  119. package/src/utils/url-parser.test.ts +271 -0
  120. package/src/utils/url-parser.ts +118 -0
  121. package/tests/abandon.test.ts +230 -0
  122. package/tests/add-reviewer.test.ts +579 -0
  123. package/tests/build-status-watch.test.ts +344 -0
  124. package/tests/build-status.test.ts +789 -0
  125. package/tests/change-id-formats.test.ts +268 -0
  126. package/tests/checkout/integration.test.ts +653 -0
  127. package/tests/checkout/parse-input.test.ts +55 -0
  128. package/tests/checkout/validation.test.ts +178 -0
  129. package/tests/comment-batch-advanced.test.ts +431 -0
  130. package/tests/comment-gerrit-api-compliance.test.ts +414 -0
  131. package/tests/comment.test.ts +708 -0
  132. package/tests/comments.test.ts +323 -0
  133. package/tests/config-service-simple.test.ts +100 -0
  134. package/tests/diff.test.ts +419 -0
  135. package/tests/extract-url.test.ts +517 -0
  136. package/tests/groups-members.test.ts +256 -0
  137. package/tests/groups-show.test.ts +323 -0
  138. package/tests/groups.test.ts +334 -0
  139. package/tests/helpers/build-status-test-setup.ts +83 -0
  140. package/tests/helpers/config-mock.ts +27 -0
  141. package/tests/incoming.test.ts +357 -0
  142. package/tests/init.test.ts +70 -0
  143. package/tests/integration/commit-hook.test.ts +246 -0
  144. package/tests/interactive-incoming.test.ts +173 -0
  145. package/tests/mine.test.ts +285 -0
  146. package/tests/mocks/msw-handlers.ts +80 -0
  147. package/tests/open.test.ts +233 -0
  148. package/tests/projects.test.ts +259 -0
  149. package/tests/rebase.test.ts +271 -0
  150. package/tests/remove-reviewer.test.ts +357 -0
  151. package/tests/restore.test.ts +237 -0
  152. package/tests/review.test.ts +135 -0
  153. package/tests/search.test.ts +712 -0
  154. package/tests/setup.test.ts +63 -0
  155. package/tests/show-auto-detect.test.ts +324 -0
  156. package/tests/show.test.ts +813 -0
  157. package/tests/status.test.ts +145 -0
  158. package/tests/submit.test.ts +316 -0
  159. package/tests/unit/commands/push.test.ts +194 -0
  160. package/tests/unit/git-branch-detection.test.ts +82 -0
  161. package/tests/unit/git-worktree.test.ts +55 -0
  162. package/tests/unit/patterns/push-patterns.test.ts +148 -0
  163. package/tests/unit/schemas/gerrit.test.ts +85 -0
  164. package/tests/unit/services/commit-hook.test.ts +132 -0
  165. package/tests/unit/services/review-strategy.test.ts +349 -0
  166. package/tests/unit/test-utils/mock-generator.test.ts +154 -0
  167. package/tests/unit/utils/comment-formatters.test.ts +415 -0
  168. package/tests/unit/utils/diff-context.test.ts +171 -0
  169. package/tests/unit/utils/diff-formatters.test.ts +165 -0
  170. package/tests/unit/utils/formatters.test.ts +411 -0
  171. package/tests/unit/utils/message-filters.test.ts +227 -0
  172. package/tests/unit/utils/shell-safety.test.ts +230 -0
  173. package/tests/unit/utils/status-indicators.test.ts +137 -0
  174. package/tests/vote.test.ts +317 -0
  175. package/tests/workspace.test.ts +295 -0
  176. package/tsconfig.json +36 -5
  177. package/src/commands/branch.ts +0 -180
  178. package/src/ger.ts +0 -22
  179. package/src/types.d.ts +0 -35
  180. package/src/utils.ts +0 -130
@@ -0,0 +1,82 @@
1
+ # ADR 0009: Enforce File Size Limits
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Large files are harder to maintain, test, and understand. We need limits to encourage modular code.
10
+
11
+ ## Decision
12
+
13
+ Enforce file size limits in pre-commit hooks:
14
+ - **Warning** at 500 lines
15
+ - **Error** (block commit) at 700 lines
16
+
17
+ ## Rationale
18
+
19
+ - **Maintainability**: Smaller files are easier to understand
20
+ - **Single responsibility**: Large files often do too much
21
+ - **Code review**: Easier to review smaller, focused files
22
+ - **Testing**: Smaller modules are easier to test in isolation
23
+
24
+ ## Consequences
25
+
26
+ ### Positive
27
+ - Encourages modular architecture
28
+ - Easier code reviews
29
+ - Better test coverage (smaller units)
30
+ - Faster navigation in IDE
31
+
32
+ ### Negative
33
+ - May need refactoring for legitimate large files
34
+ - Arbitrary thresholds may not fit all cases
35
+ - Some complex modules genuinely need more code
36
+
37
+ ## Exclusions
38
+
39
+ The following are excluded from size checks:
40
+ - `node_modules/`
41
+ - `tmp/`
42
+ - Generated files
43
+ - Test fixture files
44
+
45
+ ## Implementation
46
+
47
+ ```typescript
48
+ // scripts/check-file-size.ts
49
+ import { glob } from 'glob'
50
+
51
+ const WARN_THRESHOLD = 500
52
+ const ERROR_THRESHOLD = 700
53
+
54
+ const files = await glob('src/**/*.ts')
55
+
56
+ for (const file of files) {
57
+ const content = await Bun.file(file).text()
58
+ const lines = content.split('\n').length
59
+
60
+ if (lines > ERROR_THRESHOLD) {
61
+ console.error(`ERROR: ${file} has ${lines} lines (max: ${ERROR_THRESHOLD})`)
62
+ process.exit(1)
63
+ } else if (lines > WARN_THRESHOLD) {
64
+ console.warn(`WARN: ${file} has ${lines} lines (consider refactoring)`)
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Current Large Files
70
+
71
+ Files approaching limits (as of v0.3.5):
72
+ - `src/cli/commands/show.ts` (~400 lines) - display formatting
73
+ - `src/cli/commands/comment.ts` (~300 lines) - batch processing
74
+ - `src/cli/commands/checkout.ts` (~300 lines) - patchset handling
75
+
76
+ ## Refactoring Strategies
77
+
78
+ When files approach limits:
79
+ 1. Extract utility functions to `src/utils/`
80
+ 2. Split formatters into separate modules
81
+ 3. Move constants to dedicated files
82
+ 4. Extract sub-commands to separate files
@@ -0,0 +1,93 @@
1
+ # ADR 0010: LLM-Friendly XML Output
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ We want to integrate with AI tools (Claude, GPT, etc.) for automated code review and analysis. LLMs work better with structured, parseable output.
10
+
11
+ ## Decision
12
+
13
+ Add `--xml` flag to all major commands that outputs structured XML with CDATA wrapping for special characters.
14
+
15
+ ## Rationale
16
+
17
+ - **LLM consumption**: XML is well-understood by language models
18
+ - **Structured data**: Clear field separation vs prose
19
+ - **CDATA safety**: Handles special characters without escaping issues
20
+ - **Composability**: Pipe output to AI tools directly
21
+ - **Human readable**: XML is also readable by humans when needed
22
+
23
+ ## Consequences
24
+
25
+ ### Positive
26
+ - AI tools can parse output reliably
27
+ - Pipe directly to `llm`, `claude`, etc.
28
+ - Clear data boundaries
29
+ - No escaping ambiguity with CDATA
30
+
31
+ ### Negative
32
+ - Verbose output
33
+ - Two code paths (text and XML)
34
+ - CDATA has its own edge cases
35
+
36
+ ## Implementation
37
+
38
+ ```typescript
39
+ // --xml flag on commands
40
+ program
41
+ .command('show [change-id]')
42
+ .option('--xml', 'Output as XML for LLM consumption')
43
+ .action((changeId, options) => {
44
+ if (options.xml) {
45
+ outputXml(change)
46
+ } else {
47
+ outputPretty(change)
48
+ }
49
+ })
50
+ ```
51
+
52
+ ## CDATA Sanitization
53
+
54
+ ```typescript
55
+ // src/utils/shell-safety.ts
56
+ export function sanitizeCDATA(text: string): string {
57
+ // CDATA cannot contain "]]>" - split and rejoin
58
+ return text.replace(/]]>/g, ']]]]><![CDATA[>')
59
+ }
60
+
61
+ export function wrapCDATA(text: string): string {
62
+ return `<![CDATA[${sanitizeCDATA(text)}]]>`
63
+ }
64
+ ```
65
+
66
+ ## Example Output
67
+
68
+ ```xml
69
+ <change>
70
+ <number>12345</number>
71
+ <project>canvas-lms</project>
72
+ <subject><![CDATA[Fix login bug with special chars <>&]]></subject>
73
+ <diff><![CDATA[
74
+ --- a/file.ts
75
+ +++ b/file.ts
76
+ @@ -1,3 +1,4 @@
77
+ +import { something } from 'somewhere'
78
+ ]]></diff>
79
+ </change>
80
+ ```
81
+
82
+ ## Integration Examples
83
+
84
+ ```bash
85
+ # Pipe to Claude for review
86
+ ger show 12345 --xml | claude "Review this change"
87
+
88
+ # Pipe to llm tool
89
+ ger diff 12345 --xml | llm "Summarize changes"
90
+
91
+ # Batch review with comment posting
92
+ llm "Review this diff" < <(ger diff 12345 --xml) | ger comment 12345
93
+ ```
@@ -0,0 +1,102 @@
1
+ # ADR 0011: AI Tool Strategy Pattern
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ We want to support AI-powered code review, but don't want to hard-code a single AI tool. Users may have different tools installed.
10
+
11
+ ## Decision
12
+
13
+ Implement a strategy pattern for AI tools with auto-detection.
14
+
15
+ ## Rationale
16
+
17
+ - **No vendor lock-in**: Support multiple AI tools
18
+ - **User choice**: Users can specify preferred tool
19
+ - **Future-proof**: Easy to add new tools
20
+ - **Graceful fallback**: Try tools in priority order
21
+
22
+ ## Supported Tools
23
+
24
+ | Tool | Command | Priority |
25
+ |------|---------|----------|
26
+ | Claude CLI | `claude` | 1 (highest) |
27
+ | llm | `llm` | 2 |
28
+ | opencode | `opencode` | 3 |
29
+ | Gemini | `gemini` | 4 |
30
+
31
+ ## Consequences
32
+
33
+ ### Positive
34
+ - Works with whatever AI tool user has
35
+ - Easy to add new strategies
36
+ - Configurable default via config file
37
+ - Graceful degradation
38
+
39
+ ### Negative
40
+ - Must maintain multiple integrations
41
+ - Tool-specific output parsing
42
+ - Version compatibility concerns
43
+
44
+ ## Implementation
45
+
46
+ ```typescript
47
+ // src/services/review-strategy.ts
48
+ interface ReviewStrategy {
49
+ name: string
50
+ isAvailable(): Promise<boolean>
51
+ executeReview(prompt: string, diff: string): Promise<string>
52
+ }
53
+
54
+ const strategies: ReviewStrategy[] = [
55
+ claudeStrategy,
56
+ llmStrategy,
57
+ opencodeStrategy,
58
+ geminiStrategy,
59
+ ]
60
+
61
+ export const findAvailableStrategy = async (): Promise<ReviewStrategy | null> => {
62
+ for (const strategy of strategies) {
63
+ if (await strategy.isAvailable()) {
64
+ return strategy
65
+ }
66
+ }
67
+ return null
68
+ }
69
+ ```
70
+
71
+ ## Configuration
72
+
73
+ ```json
74
+ // ~/.ger/config.json
75
+ {
76
+ "host": "https://gerrit.example.com",
77
+ "username": "user",
78
+ "password": "token",
79
+ "aiTool": "claude", // explicit tool choice
80
+ "aiAutoDetect": true // or auto-detect
81
+ }
82
+ ```
83
+
84
+ ## Response Extraction
85
+
86
+ AI tools may wrap responses in tags:
87
+
88
+ ```typescript
89
+ const extractResponse = (output: string): string => {
90
+ // Try to extract from <response> tags
91
+ const match = output.match(/<response>([\s\S]*?)<\/response>/)
92
+ return match ? match[1].trim() : output.trim()
93
+ }
94
+ ```
95
+
96
+ ## Multi-Stage Review
97
+
98
+ The `review` command uses two stages:
99
+ 1. **Inline comments**: Line-specific feedback
100
+ 2. **Overall review**: High-level assessment
101
+
102
+ Each stage uses the same strategy but different prompts from `src/prompts/`.
@@ -0,0 +1,94 @@
1
+ # ADR 0012: Build Status via Message Parsing
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ We need to report CI/CD build status for changes. Gerrit doesn't have a standard build status API - different instances configure CI differently.
10
+
11
+ ## Decision
12
+
13
+ Parse change messages for build status patterns rather than relying on specific labels or plugins.
14
+
15
+ ## Rationale
16
+
17
+ - **Universal**: Works with any Gerrit instance
18
+ - **No plugin dependency**: Doesn't require specific CI integration
19
+ - **Flexible**: Patterns can be adjusted per instance
20
+ - **Observable**: Same info visible in Gerrit UI
21
+
22
+ ## Detected States
23
+
24
+ | State | Detection Pattern |
25
+ |-------|-------------------|
26
+ | `pending` | No build-related messages yet |
27
+ | `running` | "Build Started" message found |
28
+ | `success` | "Verified +1" after build messages |
29
+ | `failure` | "Verified -1" after build messages |
30
+ | `not_found` | Change doesn't exist |
31
+
32
+ ## Consequences
33
+
34
+ ### Positive
35
+ - Works out of box with most Gerrit setups
36
+ - No additional configuration needed
37
+ - Same logic users apply mentally
38
+
39
+ ### Negative
40
+ - Pattern matching can have false positives
41
+ - Doesn't work with non-standard CI messages
42
+ - Can't get detailed build logs
43
+
44
+ ## Implementation
45
+
46
+ ```typescript
47
+ // src/cli/commands/build-status.ts
48
+ const detectBuildState = (messages: ChangeMessage[]): BuildState => {
49
+ const sorted = [...messages].sort((a, b) =>
50
+ new Date(a.date).getTime() - new Date(b.date).getTime()
51
+ )
52
+
53
+ let buildStarted = false
54
+ let lastVerified: number | null = null
55
+
56
+ for (const msg of sorted) {
57
+ if (msg.message.includes('Build Started')) {
58
+ buildStarted = true
59
+ }
60
+ const verifiedMatch = msg.message.match(/Verified([+-]\d)/)
61
+ if (verifiedMatch) {
62
+ lastVerified = parseInt(verifiedMatch[1])
63
+ }
64
+ }
65
+
66
+ if (lastVerified === 1) return 'success'
67
+ if (lastVerified === -1) return 'failure'
68
+ if (buildStarted) return 'running'
69
+ return 'pending'
70
+ }
71
+ ```
72
+
73
+ ## Watch Mode
74
+
75
+ ```bash
76
+ # Poll until terminal state
77
+ ger build-status 12345 --watch --interval 30 --timeout 1800
78
+
79
+ # Exit codes for CI pipelines
80
+ # 0: completed (any state, like gh run watch)
81
+ # 1: failure (only with --exit-status)
82
+ # 2: timeout
83
+ # 3: API error
84
+ ```
85
+
86
+ ## JSON Output
87
+
88
+ ```json
89
+ {
90
+ "changeId": "12345",
91
+ "state": "running",
92
+ "lastMessage": "Build Started: https://jenkins.example.com/job/123"
93
+ }
94
+ ```
@@ -0,0 +1,98 @@
1
+ # ADR 0013: Git Subprocess Integration
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ We need to interact with Git for checkout, push, commit-msg hooks, and detecting Change-IDs. Options considered:
10
+
11
+ 1. **isomorphic-git** - Pure JS Git implementation
12
+ 2. **simple-git** - Node.js Git wrapper
13
+ 3. **nodegit** - libgit2 bindings
14
+ 4. **Subprocess spawning** - Shell out to git
15
+
16
+ ## Decision
17
+
18
+ Shell out to the `git` command via `Bun.spawn()` rather than using a library.
19
+
20
+ ## Rationale
21
+
22
+ - **No dependency**: Git is already installed on dev machines
23
+ - **Full feature support**: All git features available
24
+ - **Worktree support**: Libraries often struggle with worktrees
25
+ - **Familiar output**: Same output as manual git commands
26
+ - **No native bindings**: Avoid node-gyp/native module issues
27
+
28
+ ## Consequences
29
+
30
+ ### Positive
31
+ - Zero additional dependencies for Git
32
+ - Works with any Git version
33
+ - Full worktree support out of box
34
+ - Same behavior as command line
35
+
36
+ ### Negative
37
+ - Error handling is string parsing
38
+ - Platform-specific edge cases
39
+ - Subprocess overhead per call
40
+ - Security: must sanitize inputs
41
+
42
+ ## Implementation
43
+
44
+ ```typescript
45
+ // src/utils/git-commit.ts
46
+ export const runGit = async (args: string[]): Promise<{ stdout: string; stderr: string }> => {
47
+ const proc = Bun.spawn(['git', ...args], {
48
+ stdout: 'pipe',
49
+ stderr: 'pipe',
50
+ })
51
+
52
+ const stdout = await new Response(proc.stdout).text()
53
+ const stderr = await new Response(proc.stderr).text()
54
+ const exitCode = await proc.exited
55
+
56
+ if (exitCode !== 0) {
57
+ throw new GitError({ message: stderr.trim(), exitCode })
58
+ }
59
+
60
+ return { stdout: stdout.trim(), stderr: stderr.trim() }
61
+ }
62
+ ```
63
+
64
+ ## Security Considerations
65
+
66
+ ```typescript
67
+ // Validate ref names to prevent injection
68
+ const isValidRefName = (ref: string): boolean => {
69
+ // Git ref naming rules
70
+ return /^[a-zA-Z0-9_\-/.]+$/.test(ref) &&
71
+ !ref.includes('..') &&
72
+ !ref.startsWith('-')
73
+ }
74
+
75
+ // Use array args, not shell string
76
+ // GOOD: spawn(['git', 'checkout', branchName])
77
+ // BAD: spawn(`git checkout ${branchName}`, { shell: true })
78
+ ```
79
+
80
+ ## Worktree Detection
81
+
82
+ ```typescript
83
+ // Handles both regular repos and worktrees
84
+ export const getGitDir = async (): Promise<string> => {
85
+ const { stdout } = await runGit(['rev-parse', '--git-dir'])
86
+ return path.resolve(stdout)
87
+ }
88
+ ```
89
+
90
+ ## Change-ID Extraction
91
+
92
+ ```typescript
93
+ export const extractChangeIdFromHead = async (): Promise<string | null> => {
94
+ const { stdout } = await runGit(['log', '-1', '--format=%b'])
95
+ const match = stdout.match(/Change-Id: (I[0-9a-f]{40})/)
96
+ return match ? match[1] : null
97
+ }
98
+ ```
@@ -0,0 +1,95 @@
1
+ # ADR 0014: Group Management Support
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Gerrit uses groups for access control and reviewer assignment. Large teams need to manage groups and add them as reviewers efficiently.
10
+
11
+ ## Decision
12
+
13
+ Implement full group management commands: `groups`, `groups-show`, `groups-members`.
14
+
15
+ ## Rationale
16
+
17
+ - **Team workflows**: Add entire teams as reviewers at once
18
+ - **Discovery**: Find groups by name, owner, project
19
+ - **Visibility**: See group membership without Gerrit UI
20
+ - **Automation**: Script group-based reviewer assignment
21
+
22
+ ## Commands
23
+
24
+ | Command | Purpose |
25
+ |---------|---------|
26
+ | `groups` | List groups with filtering |
27
+ | `groups-show <id>` | Detailed group information |
28
+ | `groups-members <id>` | List group members |
29
+ | `add-reviewer --group` | Add group as reviewer |
30
+
31
+ ## Consequences
32
+
33
+ ### Positive
34
+ - Efficient team reviewer management
35
+ - Discoverable group information
36
+ - Scriptable group operations
37
+ - Consistent with other ger commands
38
+
39
+ ### Negative
40
+ - Additional API endpoints to maintain
41
+ - Group permissions can be complex
42
+ - LDAP groups may have sync delays
43
+
44
+ ## Implementation
45
+
46
+ ```typescript
47
+ // List groups with filters
48
+ export const listGroups = (options: GroupListOptions) =>
49
+ Effect.gen(function* () {
50
+ const api = yield* GerritApiService
51
+ const params = new URLSearchParams()
52
+
53
+ if (options.pattern) params.set('m', options.pattern)
54
+ if (options.owned) params.set('owned', '')
55
+ if (options.project) params.set('p', options.project)
56
+ if (options.user) params.set('user', options.user)
57
+
58
+ return yield* api.listGroups(params)
59
+ })
60
+ ```
61
+
62
+ ## Filtering Options
63
+
64
+ ```bash
65
+ # Filter by name pattern
66
+ ger groups --pattern "team-*"
67
+
68
+ # Groups I own
69
+ ger groups --owned
70
+
71
+ # Groups with access to project
72
+ ger groups --project canvas-lms
73
+
74
+ # Groups a user belongs to
75
+ ger groups --user john.doe
76
+ ```
77
+
78
+ ## Add Group as Reviewer
79
+
80
+ ```bash
81
+ # Add group to change
82
+ ger add-reviewer 12345 --group frontend-team
83
+
84
+ # Add as CC instead of reviewer
85
+ ger add-reviewer 12345 --group frontend-team --cc
86
+ ```
87
+
88
+ ## API Endpoints
89
+
90
+ ```
91
+ GET /a/groups/ # List groups
92
+ GET /a/groups/{id} # Group info
93
+ GET /a/groups/{id}/detail # Detailed info with members
94
+ GET /a/groups/{id}/members # Member list
95
+ ```
@@ -0,0 +1,111 @@
1
+ # ADR 0015: Batch Comment Processing
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ AI tools generate multiple inline comments at once. We need to post them efficiently rather than one-by-one.
10
+
11
+ ## Decision
12
+
13
+ Accept JSON array input for bulk inline comments with schema validation.
14
+
15
+ ## Rationale
16
+
17
+ - **AI integration**: AI tools output structured comment lists
18
+ - **Efficiency**: Single API call for multiple comments
19
+ - **Validation**: Schema ensures correct format before posting
20
+ - **Features**: Support ranges, sides, resolution state
21
+
22
+ ## Comment Schema
23
+
24
+ ```typescript
25
+ interface InlineComment {
26
+ file: string // File path
27
+ line?: number // Single line
28
+ range?: { // Or line range
29
+ start_line: number
30
+ end_line: number
31
+ start_character?: number
32
+ end_character?: number
33
+ }
34
+ message: string // Comment text
35
+ side?: 'PARENT' | 'REVISION' // Which side of diff
36
+ unresolved?: boolean // Mark as unresolved
37
+ }
38
+ ```
39
+
40
+ ## Consequences
41
+
42
+ ### Positive
43
+ - One API call for many comments
44
+ - Full Gerrit comment features
45
+ - Piped input from AI tools
46
+ - Schema validation catches errors early
47
+
48
+ ### Negative
49
+ - JSON format is verbose
50
+ - Must handle malformed input gracefully
51
+ - Range calculations can be complex
52
+
53
+ ## Implementation
54
+
55
+ ```typescript
56
+ // src/cli/commands/comment.ts
57
+ export const postBatchComments = (changeId: string, comments: InlineComment[]) =>
58
+ Effect.gen(function* () {
59
+ const api = yield* GerritApiService
60
+
61
+ // Group by file
62
+ const byFile: Record<string, CommentInput[]> = {}
63
+ for (const comment of comments) {
64
+ const input: CommentInput = {
65
+ message: comment.message,
66
+ line: comment.line,
67
+ range: comment.range,
68
+ side: comment.side,
69
+ unresolved: comment.unresolved ?? true,
70
+ }
71
+ byFile[comment.file] ??= []
72
+ byFile[comment.file].push(input)
73
+ }
74
+
75
+ // Post all at once
76
+ yield* api.postReview(changeId, { comments: byFile })
77
+ })
78
+ ```
79
+
80
+ ## Usage Examples
81
+
82
+ ```bash
83
+ # From file
84
+ cat comments.json | ger comment 12345
85
+
86
+ # From AI tool
87
+ llm "Review this diff, output JSON comments" < diff.txt | ger comment 12345
88
+
89
+ # Inline JSON
90
+ echo '[{"file":"src/index.ts","line":42,"message":"Consider null check"}]' | ger comment 12345
91
+ ```
92
+
93
+ ## Validation
94
+
95
+ ```typescript
96
+ const InlineCommentSchema = Schema.Struct({
97
+ file: Schema.String,
98
+ line: Schema.optional(Schema.Number),
99
+ range: Schema.optional(Schema.Struct({
100
+ start_line: Schema.Number,
101
+ end_line: Schema.Number,
102
+ start_character: Schema.optional(Schema.Number),
103
+ end_character: Schema.optional(Schema.Number),
104
+ })),
105
+ message: Schema.String,
106
+ side: Schema.optional(Schema.Literal('PARENT', 'REVISION')),
107
+ unresolved: Schema.optional(Schema.Boolean),
108
+ })
109
+
110
+ const CommentsArraySchema = Schema.Array(InlineCommentSchema)
111
+ ```