@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
package/bunfig.toml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[test]
|
|
2
|
+
# Test configuration
|
|
3
|
+
coverage = true
|
|
4
|
+
coverageThreshold = { line = 80.0, function = 80.0, statement = 80.0, branch = 80.0 }
|
|
5
|
+
coverageReporter = ["text", "lcov"]
|
|
6
|
+
coverageSkipTestFiles = true
|
|
7
|
+
# Exclude patterns from coverage
|
|
8
|
+
coverageExclude = ["node_modules", "dist", "tmp", "tests/mocks", "**/*.d.ts"]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# ADR 0001: Use Effect for Side Effects
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need to decide on a strategy for handling side effects (API calls, file I/O, errors) in the `ger` CLI. Options considered:
|
|
10
|
+
|
|
11
|
+
1. **Traditional try/catch** - Simple but errors lose type information
|
|
12
|
+
2. **Result types (manual)** - Explicit but verbose
|
|
13
|
+
3. **Effect library** - Type-safe, composable, full dependency injection
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
Use the Effect library for all side effects and dependency injection.
|
|
18
|
+
|
|
19
|
+
## Rationale
|
|
20
|
+
|
|
21
|
+
- **Type-safe errors**: Effect tracks error types at compile time via tagged unions
|
|
22
|
+
- **Composability**: Operations compose naturally with `Effect.gen` and `pipe()`
|
|
23
|
+
- **Dependency injection**: Layers provide testable, swappable dependencies
|
|
24
|
+
- **Resource management**: `Effect.scoped` handles cleanup automatically
|
|
25
|
+
- **Concurrent operations**: Effect handles parallelism safely
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
|
|
29
|
+
### Positive
|
|
30
|
+
- Compile-time error tracking with `Effect.catchTag`
|
|
31
|
+
- Clear error handling paths without try/catch
|
|
32
|
+
- Testable services via Layer injection
|
|
33
|
+
- No runtime surprises from unhandled errors
|
|
34
|
+
|
|
35
|
+
### Negative
|
|
36
|
+
- Learning curve for Effect newcomers
|
|
37
|
+
- Additional dependency (~100KB)
|
|
38
|
+
- More verbose than simple async/await
|
|
39
|
+
- Requires understanding of functional programming patterns
|
|
40
|
+
|
|
41
|
+
## Example
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Tagged error types
|
|
45
|
+
export class ApiError extends Schema.TaggedError<ApiError>()('ApiError', {
|
|
46
|
+
message: Schema.String,
|
|
47
|
+
statusCode: Schema.Number,
|
|
48
|
+
}) {}
|
|
49
|
+
|
|
50
|
+
// Effect-based service
|
|
51
|
+
export const getChange = (changeId: string): Effect.Effect<ChangeInfo, ApiError, GerritApiService> =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
const api = yield* GerritApiService
|
|
54
|
+
const change = yield* api.getChange(changeId)
|
|
55
|
+
return change
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Provide dependencies via layers
|
|
59
|
+
Effect.runPromise(
|
|
60
|
+
getChange('12345').pipe(
|
|
61
|
+
Effect.provide(GerritApiServiceLive),
|
|
62
|
+
Effect.provide(ConfigServiceLive)
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ADR 0002: Use Bun Runtime
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need to choose a JavaScript/TypeScript runtime for the CLI tool. Options considered:
|
|
10
|
+
|
|
11
|
+
1. **Node.js** - Most common, widely supported
|
|
12
|
+
2. **Deno** - Modern, secure by default, built-in TypeScript
|
|
13
|
+
3. **Bun** - Fast, native TypeScript, all-in-one toolchain
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
Use Bun 1.2.0+ as the runtime.
|
|
18
|
+
|
|
19
|
+
## Rationale
|
|
20
|
+
|
|
21
|
+
- **Native TypeScript**: No compilation step needed, run `.ts` files directly
|
|
22
|
+
- **Speed**: Faster startup time than Node.js (~4x faster cold start)
|
|
23
|
+
- **All-in-one**: Package manager, test runner, bundler included
|
|
24
|
+
- **SQLite built-in**: Native SQLite support for future caching needs
|
|
25
|
+
- **Consistency**: Matches `ji` and `cn` projects (same author)
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
|
|
29
|
+
### Positive
|
|
30
|
+
- No separate build step for development
|
|
31
|
+
- Faster CLI startup time (important for frequently-run tool)
|
|
32
|
+
- Single tool for package management, testing, and running
|
|
33
|
+
- Built-in test runner with coverage support
|
|
34
|
+
|
|
35
|
+
### Negative
|
|
36
|
+
- Smaller ecosystem than Node.js
|
|
37
|
+
- Some npm packages may have compatibility issues
|
|
38
|
+
- Users must install Bun (not pre-installed like Node on many systems)
|
|
39
|
+
- Breaking changes between Bun versions possible
|
|
40
|
+
|
|
41
|
+
## Version Requirements
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// src/cli/index.ts
|
|
45
|
+
const [major, minor] = Bun.version.split('.').map(Number)
|
|
46
|
+
if (major < 1 || (major === 1 && minor < 2)) {
|
|
47
|
+
console.error('ger requires Bun 1.2.0 or later')
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Build Configuration
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
// package.json
|
|
56
|
+
{
|
|
57
|
+
"type": "module",
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc --noEmit && echo 'Build successful - CLI runs directly with bun'",
|
|
60
|
+
"dev": "bun run src/cli/index.ts",
|
|
61
|
+
"test": "bun test"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ADR 0003: Store Credentials in Home Directory
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need to store Gerrit authentication credentials (host, username, HTTP password/token). Options considered:
|
|
10
|
+
|
|
11
|
+
1. **Environment variables only** - Simple but inconvenient for interactive use
|
|
12
|
+
2. **Project-local config** - Risk of committing credentials
|
|
13
|
+
3. **System keychain** - Secure but platform-specific complexity
|
|
14
|
+
4. **Home directory file** - Portable, standard pattern
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Store credentials in `~/.ger/config.json` with environment variable fallback.
|
|
19
|
+
|
|
20
|
+
## Rationale
|
|
21
|
+
|
|
22
|
+
- **Standard pattern**: Matches Git, SSH, AWS CLI conventions
|
|
23
|
+
- **Persistent**: Survives terminal sessions
|
|
24
|
+
- **Secure**: File permissions set to 0600 (owner read/write only)
|
|
25
|
+
- **CI-friendly**: Environment variables work in pipelines
|
|
26
|
+
- **Portable**: Same approach across macOS, Linux, WSL
|
|
27
|
+
|
|
28
|
+
## Consequences
|
|
29
|
+
|
|
30
|
+
### Positive
|
|
31
|
+
- Single setup command configures everything
|
|
32
|
+
- Works offline once configured
|
|
33
|
+
- Environment variables for CI/CD pipelines
|
|
34
|
+
- Matches user expectations from other CLIs
|
|
35
|
+
|
|
36
|
+
### Negative
|
|
37
|
+
- File stored in plaintext (mitigated by permissions)
|
|
38
|
+
- Users must trust file system security
|
|
39
|
+
- Migration needed when format changes
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const CONFIG_DIR = path.join(os.homedir(), '.ger')
|
|
45
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
|
|
46
|
+
|
|
47
|
+
// Set secure permissions
|
|
48
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })
|
|
49
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config), { mode: 0o600 })
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Environment Variables
|
|
53
|
+
|
|
54
|
+
| Variable | Purpose |
|
|
55
|
+
|----------|---------|
|
|
56
|
+
| `GERRIT_HOST` | Gerrit server URL |
|
|
57
|
+
| `GERRIT_USERNAME` | Username for authentication |
|
|
58
|
+
| `GERRIT_PASSWORD` | HTTP password or API token |
|
|
59
|
+
|
|
60
|
+
## Priority Order
|
|
61
|
+
|
|
62
|
+
1. File configuration (`~/.ger/config.json`)
|
|
63
|
+
2. Environment variables
|
|
64
|
+
3. Error (no credentials found)
|
|
65
|
+
|
|
66
|
+
## Migration Support
|
|
67
|
+
|
|
68
|
+
Automatically migrates legacy nested format:
|
|
69
|
+
```json
|
|
70
|
+
// Old format
|
|
71
|
+
{ "credentials": { "host": "...", "username": "..." } }
|
|
72
|
+
|
|
73
|
+
// New flat format
|
|
74
|
+
{ "host": "...", "username": "..." }
|
|
75
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ADR 0004: Use Commander.js for CLI Framework
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need a CLI framework for argument parsing, command routing, and help generation. Options considered:
|
|
10
|
+
|
|
11
|
+
1. **Ink** - React-like components for terminal UIs
|
|
12
|
+
2. **Commander.js** - Classic, stable CLI framework
|
|
13
|
+
3. **Yargs** - Feature-rich, complex API
|
|
14
|
+
4. **Clipanion** - Type-safe, used by Yarn
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Use Commander.js for the CLI framework, with Inquirer for interactive prompts.
|
|
19
|
+
|
|
20
|
+
## Rationale
|
|
21
|
+
|
|
22
|
+
- **Stability**: Most downloaded CLI framework, battle-tested
|
|
23
|
+
- **Simplicity**: Straightforward API for defining commands
|
|
24
|
+
- **Features**: Built-in help, version, option parsing
|
|
25
|
+
- **Ecosystem**: Wide community support and documentation
|
|
26
|
+
- **Consistency**: Matches patterns from other tools
|
|
27
|
+
|
|
28
|
+
## Consequences
|
|
29
|
+
|
|
30
|
+
### Positive
|
|
31
|
+
- Simple command definition with `.command()`, `.option()`, `.action()`
|
|
32
|
+
- Automatic `--help` and `--version` generation
|
|
33
|
+
- Subcommand support for complex CLIs
|
|
34
|
+
- Well-documented with many examples
|
|
35
|
+
|
|
36
|
+
### Negative
|
|
37
|
+
- Not as type-safe as Clipanion
|
|
38
|
+
- Less suitable for complex interactive UIs (use Inquirer instead)
|
|
39
|
+
- Callback-based API (wrapped with Effect)
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// src/cli/index.ts
|
|
45
|
+
import { Command } from 'commander'
|
|
46
|
+
|
|
47
|
+
const program = new Command()
|
|
48
|
+
.name('ger')
|
|
49
|
+
.description('Gerrit CLI tool')
|
|
50
|
+
.version(version)
|
|
51
|
+
|
|
52
|
+
// Register commands
|
|
53
|
+
program
|
|
54
|
+
.command('show [change-id]')
|
|
55
|
+
.description('Show change details')
|
|
56
|
+
.option('--xml', 'Output as XML')
|
|
57
|
+
.action((changeId, options) => {
|
|
58
|
+
// Wrapped in Effect.runPromise
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Interactive Prompts
|
|
63
|
+
|
|
64
|
+
For interactive input, use `@inquirer/prompts`:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { input, select } from '@inquirer/prompts'
|
|
68
|
+
|
|
69
|
+
const changeId = await select({
|
|
70
|
+
message: 'Select a change to abandon:',
|
|
71
|
+
choices: changes.map(c => ({
|
|
72
|
+
name: `${c._number}: ${c.subject}`,
|
|
73
|
+
value: String(c._number)
|
|
74
|
+
}))
|
|
75
|
+
})
|
|
76
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# ADR 0005: Use Effect Schema for Data Validation
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need to validate data from external sources (Gerrit API, user input, config files). Options considered:
|
|
10
|
+
|
|
11
|
+
1. **Zod** - Popular, simple API, runtime validation
|
|
12
|
+
2. **io-ts** - Functional, integrates with fp-ts
|
|
13
|
+
3. **Effect Schema** - Part of Effect ecosystem, type inference
|
|
14
|
+
4. **Manual validation** - No dependencies, full control
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Use Effect Schema for all data validation.
|
|
19
|
+
|
|
20
|
+
## Rationale
|
|
21
|
+
|
|
22
|
+
- **Effect integration**: Seamless with Effect error handling
|
|
23
|
+
- **Single source of truth**: Schema defines type AND validation
|
|
24
|
+
- **Composable**: Schemas compose with `Schema.Struct`, `Schema.Array`, etc.
|
|
25
|
+
- **Branded types**: Support for refined types with constraints
|
|
26
|
+
- **Decode/Encode**: Bidirectional transformations supported
|
|
27
|
+
|
|
28
|
+
## Consequences
|
|
29
|
+
|
|
30
|
+
### Positive
|
|
31
|
+
- Types inferred from schemas automatically
|
|
32
|
+
- Validation errors are structured, not strings
|
|
33
|
+
- Reusable in tests with MSW mock validation
|
|
34
|
+
- Compile-time and runtime safety
|
|
35
|
+
|
|
36
|
+
### Negative
|
|
37
|
+
- Learning curve for Schema DSL
|
|
38
|
+
- Larger bundle than Zod
|
|
39
|
+
- Less documentation than mainstream libraries
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// src/schemas/gerrit.ts
|
|
45
|
+
import { Schema } from '@effect/schema'
|
|
46
|
+
|
|
47
|
+
export const ChangeInfo = Schema.Struct({
|
|
48
|
+
id: Schema.String,
|
|
49
|
+
_number: Schema.Number,
|
|
50
|
+
project: Schema.String,
|
|
51
|
+
branch: Schema.String,
|
|
52
|
+
subject: Schema.String,
|
|
53
|
+
status: Schema.Literal('NEW', 'MERGED', 'ABANDONED', 'DRAFT'),
|
|
54
|
+
owner: Schema.Struct({
|
|
55
|
+
_account_id: Schema.Number,
|
|
56
|
+
name: Schema.optional(Schema.String),
|
|
57
|
+
email: Schema.optional(Schema.String),
|
|
58
|
+
}),
|
|
59
|
+
created: Schema.String,
|
|
60
|
+
updated: Schema.String,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Type is inferred
|
|
64
|
+
export type ChangeInfo = Schema.Schema.Type<typeof ChangeInfo>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Tagged Errors
|
|
68
|
+
|
|
69
|
+
Effect Schema supports tagged errors for type-safe error handling:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
export class ApiError extends Schema.TaggedError<ApiError>()('ApiError', {
|
|
73
|
+
message: Schema.String,
|
|
74
|
+
statusCode: Schema.Number,
|
|
75
|
+
url: Schema.String,
|
|
76
|
+
}) {}
|
|
77
|
+
|
|
78
|
+
// Usage
|
|
79
|
+
Effect.catchTag('ApiError', (error) => {
|
|
80
|
+
console.error(`API failed: ${error.message} (${error.statusCode})`)
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Validation in API Layer
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const parseResponse = <T>(schema: Schema.Schema<T>, data: unknown): Effect.Effect<T, ApiError> =>
|
|
88
|
+
Schema.decodeUnknown(schema)(data).pipe(
|
|
89
|
+
Effect.mapError((parseError) =>
|
|
90
|
+
new ApiError({ message: 'Invalid response format', statusCode: 500, url: '' })
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# ADR 0006: Use MSW for API Mocking
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need to mock HTTP requests in tests without making real API calls. Options considered:
|
|
10
|
+
|
|
11
|
+
1. **nock** - Classic Node.js HTTP mocking
|
|
12
|
+
2. **MSW (Mock Service Worker)** - Request interception at network level
|
|
13
|
+
3. **fetch-mock** - Simple fetch mocking
|
|
14
|
+
4. **Manual mocks** - Jest/Vitest manual module mocks
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Use MSW (Mock Service Worker) for all HTTP request mocking in tests.
|
|
19
|
+
|
|
20
|
+
## Rationale
|
|
21
|
+
|
|
22
|
+
- **Network level**: Intercepts at fetch/HTTP level, not module level
|
|
23
|
+
- **Realistic**: Tests actual HTTP behavior, not mocked modules
|
|
24
|
+
- **Reusable handlers**: Define once, use across many tests
|
|
25
|
+
- **Schema validation**: Mock responses can validate against Effect Schemas
|
|
26
|
+
- **Framework agnostic**: Works with any test runner
|
|
27
|
+
|
|
28
|
+
## Consequences
|
|
29
|
+
|
|
30
|
+
### Positive
|
|
31
|
+
- Tests exercise real HTTP client code
|
|
32
|
+
- Handlers are portable and reusable
|
|
33
|
+
- Easy to simulate error conditions (timeouts, 500s)
|
|
34
|
+
- No module mocking complexity
|
|
35
|
+
|
|
36
|
+
### Negative
|
|
37
|
+
- Additional dependency
|
|
38
|
+
- Requires setup/teardown in tests
|
|
39
|
+
- Learning MSW handler syntax
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// tests/mocks/handlers.ts
|
|
45
|
+
import { http, HttpResponse } from 'msw'
|
|
46
|
+
|
|
47
|
+
export const handlers = [
|
|
48
|
+
http.get('*/a/changes/:changeId', ({ params }) => {
|
|
49
|
+
return HttpResponse.json({
|
|
50
|
+
id: 'project~main~I1234567890abcdef',
|
|
51
|
+
_number: parseInt(params.changeId as string),
|
|
52
|
+
project: 'test-project',
|
|
53
|
+
branch: 'main',
|
|
54
|
+
subject: 'Test change',
|
|
55
|
+
status: 'NEW',
|
|
56
|
+
// ... schema-compliant response
|
|
57
|
+
})
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
http.post('*/a/changes/:changeId/revisions/current/review', () => {
|
|
61
|
+
return new HttpResponse(null, { status: 200 })
|
|
62
|
+
}),
|
|
63
|
+
]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Test Setup
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// tests/setup.ts
|
|
70
|
+
import { setupServer } from 'msw/node'
|
|
71
|
+
import { handlers } from './mocks/handlers'
|
|
72
|
+
|
|
73
|
+
export const server = setupServer(...handlers)
|
|
74
|
+
|
|
75
|
+
beforeAll(() => server.listen())
|
|
76
|
+
afterEach(() => server.resetHandlers())
|
|
77
|
+
afterAll(() => server.close())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Error Simulation
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Simulate API errors in specific tests
|
|
84
|
+
server.use(
|
|
85
|
+
http.get('*/a/changes/:changeId', () => {
|
|
86
|
+
return new HttpResponse(null, { status: 404 })
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# ADR 0007: Git Hooks for Code Quality
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need to enforce code quality standards automatically. Options considered:
|
|
10
|
+
|
|
11
|
+
1. **CI-only checks** - Run in pipeline, no local enforcement
|
|
12
|
+
2. **Manual commands** - Developers run checks before committing
|
|
13
|
+
3. **Git hooks** - Automatic checks on commit/push
|
|
14
|
+
4. **IDE integration** - Real-time feedback in editor
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Use Husky + lint-staged for pre-commit hooks, with additional pre-push validation.
|
|
19
|
+
|
|
20
|
+
## Rationale
|
|
21
|
+
|
|
22
|
+
- **Automatic enforcement**: Can't forget to run checks
|
|
23
|
+
- **Fast feedback**: Catch issues before push, not in CI
|
|
24
|
+
- **Staged files only**: lint-staged only checks changed files (fast)
|
|
25
|
+
- **Consistent**: All developers run same checks
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
|
|
29
|
+
### Positive
|
|
30
|
+
- Consistent code quality across all commits
|
|
31
|
+
- Fast local feedback
|
|
32
|
+
- Prevents broken commits from reaching CI
|
|
33
|
+
- Staged-only linting is fast
|
|
34
|
+
|
|
35
|
+
### Negative
|
|
36
|
+
- Initial setup required (Husky install)
|
|
37
|
+
- Can slow down commits if checks are slow
|
|
38
|
+
- Developers may skip with `--no-verify` (discouraged)
|
|
39
|
+
|
|
40
|
+
## Pre-commit Checks
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# .husky/pre-commit
|
|
44
|
+
bun lint-staged
|
|
45
|
+
bun run scripts/check-file-size.ts
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## lint-staged Configuration
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
// package.json
|
|
52
|
+
{
|
|
53
|
+
"lint-staged": {
|
|
54
|
+
"*.ts": [
|
|
55
|
+
"biome format --write",
|
|
56
|
+
"oxlint"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Pre-push Checks
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# .husky/pre-push
|
|
66
|
+
bun run build
|
|
67
|
+
bun test
|
|
68
|
+
bun run test:coverage:check
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## File Size Enforcement
|
|
72
|
+
|
|
73
|
+
Custom script checks file lengths:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// scripts/check-file-size.ts
|
|
77
|
+
const WARN_THRESHOLD = 500 // lines
|
|
78
|
+
const ERROR_THRESHOLD = 700 // lines
|
|
79
|
+
|
|
80
|
+
// Warn at 500, error at 700 lines
|
|
81
|
+
// Excludes: generated files, tmp/, node_modules/
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Coverage Enforcement
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// scripts/check-coverage.ts
|
|
88
|
+
const THRESHOLDS = {
|
|
89
|
+
line: 80,
|
|
90
|
+
function: 80,
|
|
91
|
+
statement: 80,
|
|
92
|
+
branch: 80,
|
|
93
|
+
}
|
|
94
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# ADR 0008: Prohibit `as` Type Casting
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
TypeScript's `as` keyword allows bypassing type safety. This can hide bugs and defeat the purpose of type checking. We need a policy on type assertions.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Prohibit `as` type casting except for `as const` and `as unknown` (required for Effect Schema workarounds).
|
|
14
|
+
|
|
15
|
+
## Rationale
|
|
16
|
+
|
|
17
|
+
- **Type safety**: `as` bypasses TypeScript's guarantees
|
|
18
|
+
- **Hidden bugs**: Incorrect casts cause runtime errors
|
|
19
|
+
- **Better alternatives**: Type guards, generics, schema validation
|
|
20
|
+
- **Code review**: Hard to catch incorrect casts in review
|
|
21
|
+
|
|
22
|
+
## Exceptions
|
|
23
|
+
|
|
24
|
+
### Allowed: `as const`
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Allowed - creates literal types
|
|
28
|
+
const statuses = ['NEW', 'MERGED', 'ABANDONED'] as const
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Allowed: `as unknown` (Effect Schema workaround)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Required for Effect Schema TaggedError pattern
|
|
35
|
+
export class ApiError
|
|
36
|
+
extends (ApiErrorSchema as unknown as new (
|
|
37
|
+
args: ApiErrorFields,
|
|
38
|
+
) => ApiErrorFields & Error & { readonly _tag: 'ApiError' })
|
|
39
|
+
implements Error
|
|
40
|
+
{ ... }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Consequences
|
|
44
|
+
|
|
45
|
+
### Positive
|
|
46
|
+
- Compile-time errors instead of runtime crashes
|
|
47
|
+
- Forces proper type design
|
|
48
|
+
- More maintainable code
|
|
49
|
+
- Better IDE support
|
|
50
|
+
|
|
51
|
+
### Negative
|
|
52
|
+
- More verbose in some cases
|
|
53
|
+
- Effect Schema requires workarounds
|
|
54
|
+
- Learning curve for developers used to `as`
|
|
55
|
+
|
|
56
|
+
## Enforcement
|
|
57
|
+
|
|
58
|
+
ast-grep rules (in progress):
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
# sgconfig.yml
|
|
62
|
+
rules:
|
|
63
|
+
- id: no-as-casting
|
|
64
|
+
pattern: $EXPR as $TYPE
|
|
65
|
+
except:
|
|
66
|
+
- $EXPR as const
|
|
67
|
+
- $EXPR as unknown
|
|
68
|
+
message: "Avoid 'as' type casting. Use type guards or schema validation."
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Alternatives to `as`
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Instead of: const x = data as ChangeInfo
|
|
75
|
+
|
|
76
|
+
// Use schema validation
|
|
77
|
+
const x = Schema.decodeUnknownSync(ChangeInfo)(data)
|
|
78
|
+
|
|
79
|
+
// Or type guards
|
|
80
|
+
function isChangeInfo(data: unknown): data is ChangeInfo {
|
|
81
|
+
return typeof data === 'object' && data !== null && '_number' in data
|
|
82
|
+
}
|
|
83
|
+
```
|