@aaronshaf/ger 1.2.10 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/.ast-grep/rules/no-as-casting.yml +13 -0
  2. package/.claude-plugin/plugin.json +22 -0
  3. package/.github/workflows/ci-simple.yml +53 -0
  4. package/.github/workflows/ci.yml +171 -0
  5. package/.github/workflows/claude-code-review.yml +83 -0
  6. package/.github/workflows/claude.yml +50 -0
  7. package/.github/workflows/dependency-update.yml +84 -0
  8. package/.github/workflows/release.yml +166 -0
  9. package/.github/workflows/security-scan.yml +113 -0
  10. package/.github/workflows/security.yml +96 -0
  11. package/.husky/pre-commit +16 -0
  12. package/.husky/pre-push +25 -0
  13. package/.lintstagedrc.json +6 -0
  14. package/.tool-versions +1 -0
  15. package/CLAUDE.md +105 -0
  16. package/DEVELOPMENT.md +361 -0
  17. package/EXAMPLES.md +457 -0
  18. package/README.md +831 -16
  19. package/bin/ger +3 -18
  20. package/biome.json +36 -0
  21. package/bun.lock +678 -0
  22. package/bunfig.toml +8 -0
  23. package/docs/adr/0001-use-effect-for-side-effects.md +65 -0
  24. package/docs/adr/0002-use-bun-runtime.md +64 -0
  25. package/docs/adr/0003-store-credentials-in-home-directory.md +75 -0
  26. package/docs/adr/0004-use-commander-for-cli.md +76 -0
  27. package/docs/adr/0005-use-effect-schema-for-validation.md +93 -0
  28. package/docs/adr/0006-use-msw-for-api-mocking.md +89 -0
  29. package/docs/adr/0007-git-hooks-for-quality.md +94 -0
  30. package/docs/adr/0008-no-as-typecasting.md +83 -0
  31. package/docs/adr/0009-file-size-limits.md +82 -0
  32. package/docs/adr/0010-llm-friendly-xml-output.md +93 -0
  33. package/docs/adr/0011-ai-tool-strategy-pattern.md +102 -0
  34. package/docs/adr/0012-build-status-message-parsing.md +94 -0
  35. package/docs/adr/0013-git-subprocess-integration.md +98 -0
  36. package/docs/adr/0014-group-management-support.md +95 -0
  37. package/docs/adr/0015-batch-comment-processing.md +111 -0
  38. package/docs/adr/0016-flexible-change-identifiers.md +94 -0
  39. package/docs/adr/0017-git-worktree-support.md +102 -0
  40. package/docs/adr/0018-auto-install-commit-hook.md +103 -0
  41. package/docs/adr/0019-sdk-package-exports.md +95 -0
  42. package/docs/adr/0020-code-coverage-enforcement.md +105 -0
  43. package/docs/adr/0021-typescript-isolated-declarations.md +83 -0
  44. package/docs/adr/0022-biome-oxlint-tooling.md +124 -0
  45. package/docs/adr/README.md +30 -0
  46. package/docs/prd/README.md +12 -0
  47. package/docs/prd/architecture.md +325 -0
  48. package/docs/prd/commands.md +425 -0
  49. package/docs/prd/data-model.md +349 -0
  50. package/docs/prd/overview.md +124 -0
  51. package/index.ts +219 -0
  52. package/oxlint.json +24 -0
  53. package/package.json +82 -15
  54. package/scripts/check-coverage.ts +69 -0
  55. package/scripts/check-file-size.ts +38 -0
  56. package/scripts/fix-test-mocks.ts +55 -0
  57. package/skills/gerrit-workflow/SKILL.md +247 -0
  58. package/skills/gerrit-workflow/examples.md +572 -0
  59. package/skills/gerrit-workflow/reference.md +728 -0
  60. package/src/api/gerrit.ts +696 -0
  61. package/src/cli/commands/abandon.ts +65 -0
  62. package/src/cli/commands/add-reviewer.ts +156 -0
  63. package/src/cli/commands/build-status.ts +282 -0
  64. package/src/cli/commands/checkout.ts +422 -0
  65. package/src/cli/commands/comment.ts +460 -0
  66. package/src/cli/commands/comments.ts +85 -0
  67. package/src/cli/commands/diff.ts +71 -0
  68. package/src/cli/commands/extract-url.ts +266 -0
  69. package/src/cli/commands/groups-members.ts +104 -0
  70. package/src/cli/commands/groups-show.ts +169 -0
  71. package/src/cli/commands/groups.ts +137 -0
  72. package/src/cli/commands/incoming.ts +226 -0
  73. package/src/cli/commands/init.ts +164 -0
  74. package/src/cli/commands/mine.ts +115 -0
  75. package/src/cli/commands/open.ts +57 -0
  76. package/src/cli/commands/projects.ts +68 -0
  77. package/src/cli/commands/push.ts +430 -0
  78. package/src/cli/commands/rebase.ts +52 -0
  79. package/src/cli/commands/remove-reviewer.ts +123 -0
  80. package/src/cli/commands/restore.ts +50 -0
  81. package/src/cli/commands/review.ts +486 -0
  82. package/src/cli/commands/search.ts +162 -0
  83. package/src/cli/commands/setup.ts +286 -0
  84. package/src/cli/commands/show.ts +491 -0
  85. package/src/cli/commands/status.ts +35 -0
  86. package/src/cli/commands/submit.ts +108 -0
  87. package/src/cli/commands/vote.ts +119 -0
  88. package/src/cli/commands/workspace.ts +200 -0
  89. package/src/cli/index.ts +53 -0
  90. package/src/cli/register-commands.ts +659 -0
  91. package/src/cli/register-group-commands.ts +88 -0
  92. package/src/cli/register-reviewer-commands.ts +97 -0
  93. package/src/prompts/default-review.md +86 -0
  94. package/src/prompts/system-inline-review.md +135 -0
  95. package/src/prompts/system-overall-review.md +206 -0
  96. package/src/schemas/config.test.ts +245 -0
  97. package/src/schemas/config.ts +84 -0
  98. package/src/schemas/gerrit.ts +681 -0
  99. package/src/services/commit-hook.ts +314 -0
  100. package/src/services/config.test.ts +150 -0
  101. package/src/services/config.ts +250 -0
  102. package/src/services/git-worktree.ts +342 -0
  103. package/src/services/review-strategy.ts +292 -0
  104. package/src/test-utils/mock-generator.ts +138 -0
  105. package/src/utils/change-id.test.ts +98 -0
  106. package/src/utils/change-id.ts +63 -0
  107. package/src/utils/comment-formatters.ts +153 -0
  108. package/src/utils/diff-context.ts +103 -0
  109. package/src/utils/diff-formatters.ts +141 -0
  110. package/src/utils/formatters.ts +85 -0
  111. package/src/utils/git-commit.test.ts +277 -0
  112. package/src/utils/git-commit.ts +122 -0
  113. package/src/utils/index.ts +55 -0
  114. package/src/utils/message-filters.ts +26 -0
  115. package/src/utils/review-formatters.ts +89 -0
  116. package/src/utils/review-prompt-builder.ts +110 -0
  117. package/src/utils/shell-safety.ts +117 -0
  118. package/src/utils/status-indicators.ts +100 -0
  119. package/src/utils/url-parser.test.ts +271 -0
  120. package/src/utils/url-parser.ts +118 -0
  121. package/tests/abandon.test.ts +230 -0
  122. package/tests/add-reviewer.test.ts +579 -0
  123. package/tests/build-status-watch.test.ts +344 -0
  124. package/tests/build-status.test.ts +789 -0
  125. package/tests/change-id-formats.test.ts +268 -0
  126. package/tests/checkout/integration.test.ts +653 -0
  127. package/tests/checkout/parse-input.test.ts +55 -0
  128. package/tests/checkout/validation.test.ts +178 -0
  129. package/tests/comment-batch-advanced.test.ts +431 -0
  130. package/tests/comment-gerrit-api-compliance.test.ts +414 -0
  131. package/tests/comment.test.ts +708 -0
  132. package/tests/comments.test.ts +323 -0
  133. package/tests/config-service-simple.test.ts +100 -0
  134. package/tests/diff.test.ts +419 -0
  135. package/tests/extract-url.test.ts +517 -0
  136. package/tests/groups-members.test.ts +256 -0
  137. package/tests/groups-show.test.ts +323 -0
  138. package/tests/groups.test.ts +334 -0
  139. package/tests/helpers/build-status-test-setup.ts +83 -0
  140. package/tests/helpers/config-mock.ts +27 -0
  141. package/tests/incoming.test.ts +357 -0
  142. package/tests/init.test.ts +70 -0
  143. package/tests/integration/commit-hook.test.ts +246 -0
  144. package/tests/interactive-incoming.test.ts +173 -0
  145. package/tests/mine.test.ts +285 -0
  146. package/tests/mocks/msw-handlers.ts +80 -0
  147. package/tests/open.test.ts +233 -0
  148. package/tests/projects.test.ts +259 -0
  149. package/tests/rebase.test.ts +271 -0
  150. package/tests/remove-reviewer.test.ts +357 -0
  151. package/tests/restore.test.ts +237 -0
  152. package/tests/review.test.ts +135 -0
  153. package/tests/search.test.ts +712 -0
  154. package/tests/setup.test.ts +63 -0
  155. package/tests/show-auto-detect.test.ts +324 -0
  156. package/tests/show.test.ts +813 -0
  157. package/tests/status.test.ts +145 -0
  158. package/tests/submit.test.ts +316 -0
  159. package/tests/unit/commands/push.test.ts +194 -0
  160. package/tests/unit/git-branch-detection.test.ts +82 -0
  161. package/tests/unit/git-worktree.test.ts +55 -0
  162. package/tests/unit/patterns/push-patterns.test.ts +148 -0
  163. package/tests/unit/schemas/gerrit.test.ts +85 -0
  164. package/tests/unit/services/commit-hook.test.ts +132 -0
  165. package/tests/unit/services/review-strategy.test.ts +349 -0
  166. package/tests/unit/test-utils/mock-generator.test.ts +154 -0
  167. package/tests/unit/utils/comment-formatters.test.ts +415 -0
  168. package/tests/unit/utils/diff-context.test.ts +171 -0
  169. package/tests/unit/utils/diff-formatters.test.ts +165 -0
  170. package/tests/unit/utils/formatters.test.ts +411 -0
  171. package/tests/unit/utils/message-filters.test.ts +227 -0
  172. package/tests/unit/utils/shell-safety.test.ts +230 -0
  173. package/tests/unit/utils/status-indicators.test.ts +137 -0
  174. package/tests/vote.test.ts +317 -0
  175. package/tests/workspace.test.ts +295 -0
  176. package/tsconfig.json +36 -5
  177. package/src/commands/branch.ts +0 -180
  178. package/src/ger.ts +0 -22
  179. package/src/types.d.ts +0 -35
  180. package/src/utils.ts +0 -130
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
+ ```