@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.
- 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 -196
- package/src/ger.ts +0 -22
- package/src/types.d.ts +0 -35
- 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 |
|