@aaronshaf/ger 1.2.11 → 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 -196
  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,94 @@
1
+ # ADR 0016: Flexible Change Identifiers
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Gerrit changes can be identified by numeric ID (12345) or Change-ID (I1234567890abcdef...). Users may have either and shouldn't need to convert.
10
+
11
+ ## Decision
12
+
13
+ Accept both numeric change IDs and full Change-IDs in all commands.
14
+
15
+ ## Rationale
16
+
17
+ - **User convenience**: No mental mapping required
18
+ - **Copy-paste friendly**: Works with whatever user has
19
+ - **Gerrit API compatible**: API accepts both formats
20
+ - **Auto-detection**: Can detect from HEAD commit
21
+
22
+ ## Identifier Formats
23
+
24
+ | Format | Example | Regex |
25
+ |--------|---------|-------|
26
+ | Numeric | `12345` | `/^\d+$/` |
27
+ | Change-ID | `If5a3ae8cb5a107e187447802358417f311d0c4b1` | `/^I[0-9a-f]{40}$/` |
28
+ | Full triplet | `project~branch~Change-Id` | Complex |
29
+
30
+ ## Consequences
31
+
32
+ ### Positive
33
+ - Works with URLs, commits, or manual entry
34
+ - No conversion needed
35
+ - Matches Gerrit web UI behavior
36
+
37
+ ### Negative
38
+ - Validation logic in every command
39
+ - API may need to handle both
40
+ - Error messages need to cover both formats
41
+
42
+ ## Implementation
43
+
44
+ ```typescript
45
+ // src/utils/change-id.ts
46
+ export const isChangeNumber = (id: string): boolean =>
47
+ /^\d+$/.test(id)
48
+
49
+ export const isChangeId = (id: string): boolean =>
50
+ /^I[0-9a-f]{40}$/i.test(id)
51
+
52
+ export const normalizeChangeIdentifier = (input: string): string => {
53
+ // Already valid
54
+ if (isChangeNumber(input) || isChangeId(input)) {
55
+ return input
56
+ }
57
+
58
+ // Try to extract from URL
59
+ const urlMatch = input.match(/\/c\/[^/]+\/\+\/(\d+)/)
60
+ if (urlMatch) return urlMatch[1]
61
+
62
+ // Try to extract Change-ID from text
63
+ const cidMatch = input.match(/(I[0-9a-f]{40})/i)
64
+ if (cidMatch) return cidMatch[1]
65
+
66
+ throw new Error(`Invalid change identifier: ${input}`)
67
+ }
68
+ ```
69
+
70
+ ## Auto-Detection from Git
71
+
72
+ ```typescript
73
+ export const detectChangeFromHead = async (): Promise<string | null> => {
74
+ try {
75
+ const { stdout } = await runGit(['log', '-1', '--format=%b'])
76
+ const match = stdout.match(/Change-Id: (I[0-9a-f]{40})/i)
77
+ return match ? match[1] : null
78
+ } catch {
79
+ return null
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Command Usage
85
+
86
+ ```bash
87
+ # All equivalent
88
+ ger show 12345
89
+ ger show If5a3ae8cb5a107e187447802358417f311d0c4b1
90
+ ger show https://gerrit.example.com/c/project/+/12345
91
+
92
+ # Auto-detect from current commit
93
+ ger show # Uses Change-ID from HEAD
94
+ ```
@@ -0,0 +1,102 @@
1
+ # ADR 0017: Git Worktree Support
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Git worktrees allow multiple working directories for a single repository. Many developers use worktrees for parallel work on multiple changes.
10
+
11
+ ## Decision
12
+
13
+ Fully support git worktrees in all git-related operations.
14
+
15
+ ## Rationale
16
+
17
+ - **Common workflow**: Worktrees are popular for code review
18
+ - **Parallel development**: Work on multiple changes simultaneously
19
+ - **Clean separation**: Each worktree is isolated
20
+
21
+ ## Worktree Challenges
22
+
23
+ 1. `.git` is a file pointing to main repo, not a directory
24
+ 2. Hooks live in main repo's `.git/hooks`, not worktree
25
+ 3. `git rev-parse` behaves differently in worktrees
26
+ 4. Branch tracking differs between worktrees
27
+
28
+ ## Consequences
29
+
30
+ ### Positive
31
+ - Works for developers using worktrees
32
+ - No special configuration needed
33
+ - Automatic detection of worktree context
34
+
35
+ ### Negative
36
+ - More complex git detection logic
37
+ - Hook installation must find main repo
38
+ - Path resolution is more complex
39
+
40
+ ## Implementation
41
+
42
+ ```typescript
43
+ // src/services/git-worktree.ts
44
+ export const isWorktree = async (): Promise<boolean> => {
45
+ const gitDir = await runGit(['rev-parse', '--git-dir'])
46
+ const gitFile = Bun.file(path.join(process.cwd(), '.git'))
47
+
48
+ // In worktree, .git is a file, not directory
49
+ return await gitFile.exists() && !(await gitFile.stat()).isDirectory()
50
+ }
51
+
52
+ export const getMainGitDir = async (): Promise<string> => {
53
+ // Returns the main repo's .git directory, even in worktree
54
+ const { stdout } = await runGit(['rev-parse', '--git-common-dir'])
55
+ return path.resolve(stdout.trim())
56
+ }
57
+
58
+ export const getHooksDir = async (): Promise<string> => {
59
+ const mainGitDir = await getMainGitDir()
60
+ return path.join(mainGitDir, 'hooks')
61
+ }
62
+ ```
63
+
64
+ ## Hook Installation
65
+
66
+ ```typescript
67
+ // Install hook in main repo, not worktree
68
+ export const installCommitHook = async (): Promise<void> => {
69
+ const hooksDir = await getHooksDir()
70
+ const hookPath = path.join(hooksDir, 'commit-msg')
71
+
72
+ // Check if already installed
73
+ if (await Bun.file(hookPath).exists()) {
74
+ return
75
+ }
76
+
77
+ // Download and install Gerrit hook
78
+ const hookContent = await fetchGerritHook()
79
+ await Bun.write(hookPath, hookContent, { mode: 0o755 })
80
+ }
81
+ ```
82
+
83
+ ## Worktree Creation
84
+
85
+ ```typescript
86
+ // ger checkout creates worktree for change review
87
+ export const createReviewWorktree = async (changeId: string): Promise<string> => {
88
+ const worktreePath = `../review-${changeId}`
89
+ await runGit(['worktree', 'add', worktreePath])
90
+ return path.resolve(worktreePath)
91
+ }
92
+ ```
93
+
94
+ ## Path Resolution
95
+
96
+ ```typescript
97
+ // Always resolve to absolute paths
98
+ export const resolveGitPath = async (relativePath: string): Promise<string> => {
99
+ const toplevel = await runGit(['rev-parse', '--show-toplevel'])
100
+ return path.resolve(toplevel.trim(), relativePath)
101
+ }
102
+ ```
@@ -0,0 +1,103 @@
1
+ # ADR 0018: Auto-Install Gerrit Commit-msg Hook
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Gerrit requires a Change-Id footer in commit messages. This is typically added by a commit-msg hook that must be installed manually.
10
+
11
+ ## Decision
12
+
13
+ Auto-install the Gerrit commit-msg hook when pushing if not present.
14
+
15
+ ## Rationale
16
+
17
+ - **Convenience**: Users don't need manual setup
18
+ - **Error prevention**: Avoid "missing Change-Id" rejections
19
+ - **Standard practice**: Same hook Gerrit provides
20
+ - **Idempotent**: Safe to run multiple times
21
+
22
+ ## Consequences
23
+
24
+ ### Positive
25
+ - Just works out of the box
26
+ - No manual hook installation
27
+ - Consistent Change-Id format
28
+ - Reduces onboarding friction
29
+
30
+ ### Negative
31
+ - Modifies user's git hooks
32
+ - Hook download requires network
33
+ - Must handle worktrees correctly
34
+
35
+ ## Implementation
36
+
37
+ ```typescript
38
+ // src/services/commit-hook.ts
39
+ export const ensureCommitHook = async (gerritHost: string): Promise<void> => {
40
+ const hooksDir = await getHooksDir()
41
+ const hookPath = path.join(hooksDir, 'commit-msg')
42
+
43
+ // Check if hook exists and is executable
44
+ try {
45
+ const stat = await Bun.file(hookPath).stat()
46
+ if (stat.mode & 0o111) {
47
+ return // Already installed and executable
48
+ }
49
+ } catch {
50
+ // Hook doesn't exist, continue to install
51
+ }
52
+
53
+ // Download from Gerrit
54
+ const hookUrl = `${gerritHost}/tools/hooks/commit-msg`
55
+ const response = await fetch(hookUrl)
56
+
57
+ if (!response.ok) {
58
+ throw new Error(`Failed to download commit-msg hook: ${response.status}`)
59
+ }
60
+
61
+ const hookContent = await response.text()
62
+
63
+ // Write with executable permissions
64
+ await Bun.write(hookPath, hookContent, { mode: 0o755 })
65
+
66
+ console.log(`Installed commit-msg hook to ${hookPath}`)
67
+ }
68
+ ```
69
+
70
+ ## Hook Behavior
71
+
72
+ The Gerrit commit-msg hook:
73
+ 1. Checks if Change-Id already exists in message
74
+ 2. If not, generates a unique Change-Id
75
+ 3. Appends `Change-Id: I...` to commit message
76
+ 4. Change-Id is SHA-1 based on tree, parent, author, committer
77
+
78
+ ## When to Install
79
+
80
+ ```typescript
81
+ // Install before push operations
82
+ export const pushCommand = (options: PushOptions) =>
83
+ Effect.gen(function* () {
84
+ const config = yield* ConfigService
85
+
86
+ // Ensure hook is installed before push
87
+ yield* Effect.tryPromise(() => ensureCommitHook(config.host))
88
+
89
+ // Proceed with push
90
+ yield* performPush(options)
91
+ })
92
+ ```
93
+
94
+ ## Manual Override
95
+
96
+ Users can skip auto-install:
97
+ ```bash
98
+ # Environment variable
99
+ GERRIT_SKIP_HOOK=1 ger push
100
+
101
+ # Or install their own hook
102
+ # (ger won't overwrite existing executable hooks)
103
+ ```
@@ -0,0 +1,95 @@
1
+ # ADR 0019: SDK Package Exports
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Beyond CLI usage, developers may want to use ger programmatically in their own tools, scripts, or automation.
10
+
11
+ ## Decision
12
+
13
+ Export SDK modules for programmatic usage via package.json exports.
14
+
15
+ ## Rationale
16
+
17
+ - **Reusability**: Build custom tools on top of ger
18
+ - **Testing**: Import specific modules in tests
19
+ - **Automation**: Script Gerrit operations
20
+ - **Type safety**: Full TypeScript types included
21
+
22
+ ## Export Structure
23
+
24
+ ```json
25
+ // package.json
26
+ {
27
+ "name": "@aaronshaf/ger",
28
+ "exports": {
29
+ ".": "./src/index.ts",
30
+ "./api/gerrit": "./src/api/gerrit.ts",
31
+ "./services/config": "./src/services/config.ts",
32
+ "./schemas/gerrit": "./src/schemas/gerrit.ts",
33
+ "./utils": "./src/utils/index.ts"
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Consequences
39
+
40
+ ### Positive
41
+ - Build custom integrations
42
+ - Reuse validated schemas
43
+ - Access type-safe API client
44
+ - Import only what you need
45
+
46
+ ### Negative
47
+ - More surface area to maintain
48
+ - Breaking changes affect SDK users
49
+ - Documentation burden
50
+
51
+ ## Usage Examples
52
+
53
+ ```typescript
54
+ // Import API service
55
+ import { GerritApiService, GerritApiServiceLive } from '@aaronshaf/ger'
56
+ import { Effect } from 'effect'
57
+
58
+ // Custom automation
59
+ const myScript = Effect.gen(function* () {
60
+ const api = yield* GerritApiService
61
+ const changes = yield* api.listChanges('owner:self status:open')
62
+
63
+ for (const change of changes) {
64
+ console.log(`${change._number}: ${change.subject}`)
65
+ }
66
+ })
67
+
68
+ Effect.runPromise(
69
+ myScript.pipe(Effect.provide(GerritApiServiceLive))
70
+ )
71
+ ```
72
+
73
+ ```typescript
74
+ // Import schemas for validation
75
+ import { ChangeInfo } from '@aaronshaf/ger/schemas/gerrit'
76
+ import { Schema } from '@effect/schema'
77
+
78
+ const validateChange = (data: unknown) =>
79
+ Schema.decodeUnknownSync(ChangeInfo)(data)
80
+ ```
81
+
82
+ ```typescript
83
+ // Import utilities
84
+ import { normalizeChangeIdentifier, extractChangeIdFromCommitMessage } from '@aaronshaf/ger/utils'
85
+
86
+ const changeId = normalizeChangeIdentifier('https://gerrit.example.com/c/project/+/12345')
87
+ ```
88
+
89
+ ## API Documentation
90
+
91
+ See EXAMPLES.md for detailed SDK usage patterns:
92
+ - Effect-based API calls
93
+ - Config service setup
94
+ - Custom change processing
95
+ - Batch operations
@@ -0,0 +1,105 @@
1
+ # ADR 0020: Code Coverage Enforcement
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ We need to ensure adequate test coverage without slowing down development. Options:
10
+
11
+ 1. **CI-only enforcement** - Check in pipeline, local commits unrestricted
12
+ 2. **Pre-commit enforcement** - Block commits below threshold
13
+ 3. **Pre-push enforcement** - Block push if coverage drops
14
+ 4. **Advisory only** - Report but don't enforce
15
+
16
+ ## Decision
17
+
18
+ Enforce 80% code coverage threshold in pre-commit and pre-push hooks.
19
+
20
+ ## Rationale
21
+
22
+ - **Quality gate**: Prevents untested code from entering codebase
23
+ - **Fast feedback**: Know immediately if coverage dropped
24
+ - **Prevents drift**: Coverage doesn't slowly degrade
25
+ - **Meaningful threshold**: 80% balances coverage with practicality
26
+
27
+ ## Thresholds
28
+
29
+ | Metric | Threshold |
30
+ |--------|-----------|
31
+ | Lines | 80% |
32
+ | Functions | 80% |
33
+ | Statements | 80% |
34
+ | Branches | 80% |
35
+
36
+ ## Consequences
37
+
38
+ ### Positive
39
+ - Consistent test coverage across codebase
40
+ - Bugs caught before production
41
+ - Documentation of behavior via tests
42
+ - Confidence in refactoring
43
+
44
+ ### Negative
45
+ - May slow commits when adding new code
46
+ - Some code is hard to test (UI, edge cases)
47
+ - Can encourage gaming metrics vs quality tests
48
+
49
+ ## Exclusions
50
+
51
+ Excluded from coverage calculations:
52
+ - `tmp/` - Temporary files
53
+ - `scripts/` - Build scripts
54
+ - `*.d.ts` - Type declarations
55
+ - Test files themselves
56
+
57
+ ## Configuration
58
+
59
+ ```toml
60
+ # bunfig.toml
61
+ [test]
62
+ coverage = true
63
+ coverageThreshold = { line = 80, function = 80, statement = 80, branch = 80 }
64
+ coverageSkipTestFiles = true
65
+ ```
66
+
67
+ ## Enforcement Script
68
+
69
+ ```typescript
70
+ // scripts/check-coverage.ts
71
+ import { $ } from 'bun'
72
+
73
+ const result = await $`bun test --coverage`.json()
74
+
75
+ const thresholds = {
76
+ line: 80,
77
+ function: 80,
78
+ statement: 80,
79
+ branch: 80,
80
+ }
81
+
82
+ let failed = false
83
+
84
+ for (const [metric, threshold] of Object.entries(thresholds)) {
85
+ const actual = result.coverage[metric]
86
+ if (actual < threshold) {
87
+ console.error(`${metric} coverage ${actual}% < ${threshold}%`)
88
+ failed = true
89
+ }
90
+ }
91
+
92
+ if (failed) {
93
+ process.exit(1)
94
+ }
95
+ ```
96
+
97
+ ## Bypassing (Emergency Only)
98
+
99
+ ```bash
100
+ # Skip hooks entirely (discouraged)
101
+ git commit --no-verify -m "Emergency fix"
102
+
103
+ # Add to exclusion list temporarily
104
+ # (requires PR review)
105
+ ```
@@ -0,0 +1,83 @@
1
+ # ADR 0021: TypeScript Isolated Declarations
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ TypeScript 5.5+ introduced `isolatedDeclarations` which requires explicit return type annotations on exported functions. This improves declaration file generation.
10
+
11
+ ## Decision
12
+
13
+ Enable `isolatedDeclarations: true` in tsconfig.json.
14
+
15
+ ## Rationale
16
+
17
+ - **Faster builds**: Declaration files generated without full type-check
18
+ - **Explicit types**: Forces documentation of public API
19
+ - **Parallel builds**: Enables parallel .d.ts generation
20
+ - **Better tooling**: IDEs understand types without inference
21
+
22
+ ## Consequences
23
+
24
+ ### Positive
25
+ - Declaration files are accurate and fast to generate
26
+ - Public API is self-documenting
27
+ - Catches missing type annotations
28
+ - Better for SDK consumers
29
+
30
+ ### Negative
31
+ - More verbose code (explicit return types)
32
+ - Learning curve for developers
33
+ - Some Effect patterns require workarounds
34
+
35
+ ## Configuration
36
+
37
+ ```json
38
+ // tsconfig.json
39
+ {
40
+ "compilerOptions": {
41
+ "isolatedDeclarations": true,
42
+ "declaration": true,
43
+ "strict": true,
44
+ "noEmit": true
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Code Changes Required
50
+
51
+ ```typescript
52
+ // Before (inferred return type)
53
+ export const getChange = (id: string) =>
54
+ Effect.gen(function* () {
55
+ const api = yield* GerritApiService
56
+ return yield* api.getChange(id)
57
+ })
58
+
59
+ // After (explicit return type)
60
+ export const getChange = (id: string): Effect.Effect<ChangeInfo, ApiError, GerritApiService> =>
61
+ Effect.gen(function* () {
62
+ const api = yield* GerritApiService
63
+ return yield* api.getChange(id)
64
+ })
65
+ ```
66
+
67
+ ## Common Patterns
68
+
69
+ ```typescript
70
+ // Simple functions - explicit return
71
+ export const add = (a: number, b: number): number => a + b
72
+
73
+ // Effect functions - full type signature
74
+ export const fetchData = (): Effect.Effect<Data, ApiError, ApiService> =>
75
+ Effect.gen(function* () { ... })
76
+
77
+ // Constants - as const for literal types
78
+ export const STATUS_OPTIONS = ['NEW', 'MERGED', 'ABANDONED'] as const
79
+ ```
80
+
81
+ ## Enforcement
82
+
83
+ TypeScript compiler enforces this automatically when enabled. Build fails if any exported function lacks explicit return type annotation.
@@ -0,0 +1,124 @@
1
+ # ADR 0022: Biome Formatter + oxlint Linter
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ We need code formatting and linting tools. Options considered:
10
+
11
+ 1. **ESLint + Prettier** - Standard combo, highly configurable
12
+ 2. **Biome** - Fast, Rust-based formatter and linter
13
+ 3. **oxlint** - Ultra-fast Rust-based linter
14
+ 4. **deno fmt/lint** - Built into Deno, not Bun
15
+
16
+ ## Decision
17
+
18
+ Use Biome for formatting and oxlint for linting.
19
+
20
+ ## Rationale
21
+
22
+ - **Speed**: Both are Rust-based, extremely fast
23
+ - **Compatibility**: Work well with Bun
24
+ - **Simple config**: Less configuration than ESLint
25
+ - **Modern defaults**: Sensible rules out of the box
26
+
27
+ ## Tool Responsibilities
28
+
29
+ | Tool | Purpose |
30
+ |------|---------|
31
+ | Biome | Code formatting (spacing, line breaks, quotes) |
32
+ | oxlint | Static analysis (errors, best practices) |
33
+
34
+ ## Consequences
35
+
36
+ ### Positive
37
+ - Sub-second formatting on entire codebase
38
+ - Near-instant linting
39
+ - Fewer dependencies than ESLint ecosystem
40
+ - Works in pre-commit without slowdown
41
+
42
+ ### Negative
43
+ - Less mature than ESLint (fewer rules)
44
+ - Smaller community
45
+ - May miss some ESLint-specific rules
46
+
47
+ ## Biome Configuration
48
+
49
+ ```json
50
+ // biome.json
51
+ {
52
+ "formatter": {
53
+ "enabled": true,
54
+ "indentStyle": "tab",
55
+ "indentWidth": 2,
56
+ "lineWidth": 100
57
+ },
58
+ "linter": {
59
+ "enabled": false
60
+ },
61
+ "javascript": {
62
+ "formatter": {
63
+ "quoteStyle": "single",
64
+ "trailingComma": "all",
65
+ "semicolons": "asNeeded"
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## oxlint Configuration
72
+
73
+ ```json
74
+ // .oxlintrc.json
75
+ {
76
+ "rules": {
77
+ "no-unused-vars": "error",
78
+ "no-console": "warn",
79
+ "prefer-const": "error"
80
+ },
81
+ "ignorePatterns": [
82
+ "node_modules",
83
+ "tmp",
84
+ "*.d.ts"
85
+ ]
86
+ }
87
+ ```
88
+
89
+ ## lint-staged Integration
90
+
91
+ ```json
92
+ // package.json
93
+ {
94
+ "lint-staged": {
95
+ "*.ts": [
96
+ "biome format --write",
97
+ "oxlint"
98
+ ]
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Scripts
104
+
105
+ ```json
106
+ // package.json
107
+ {
108
+ "scripts": {
109
+ "format": "biome format --write .",
110
+ "format:check": "biome format .",
111
+ "lint": "oxlint .",
112
+ "check:all": "bun run format:check && bun run lint && bun run typecheck"
113
+ }
114
+ }
115
+ ```
116
+
117
+ ## Performance Comparison
118
+
119
+ | Tool | Time (100 files) |
120
+ |------|------------------|
121
+ | Prettier | ~2s |
122
+ | Biome | ~50ms |
123
+ | ESLint | ~5s |
124
+ | oxlint | ~100ms |