git clone https://github.com/bulutmuf/opencode-failover.git
cd opencode-failover
bun install- Bun >= 1.0.0
- Node.js >= 18 (for type definitions)
bun test # Run all tests
bun run typecheck # Type-check without emitting
bun install # Install dependencies- No comments unless explaining complex business logic or algorithmic choices. Code should be self-documenting.
- Functional style: prefer
const,map,filter,reduceover mutable loops. - Table-driven tests: all new logic gets table-driven tests in
*.test.tsfiles. - No premature abstraction: inline logic at the call site unless it is reused or has a clear independent name.
- TypeScript strict mode: no
anytypes, explicit return types on exports.
Follow Conventional Commits:
type(scope): summary
| Type | Use for |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
test |
Adding or updating tests |
chore |
Maintenance (deps, CI, config) |
refactor |
Code change that neither fixes a bug nor adds a feature |
| Scope | Use for |
|---|---|
config |
Configuration parsing (src/lib/config.ts) |
state |
Key pool logic (src/lib/state.ts) |
classify |
Error classification (src/lib/classify.ts) |
hooks |
Plugin hook wiring (src/index.ts chat.headers, event) |
tool |
keychain-status tool |
ci |
GitHub Actions workflows |
| (none) | Cross-cutting changes |
feat(config): add env var fallback for provider keys
fix(classify): handle missing statusCode gracefully
test(state): add edge case for all-keys-disabled scenario
docs: update troubleshooting guide
chore: bump @opencode-ai/plugin to 1.17.14
All new logic gets a *.test.ts file adjacent to the source:
src/lib/state.ts → src/state.test.ts
src/lib/classify.ts → src/classify.test.ts
Tests are table-driven:
import { describe, it, expect } from "bun:test"
import { classify, ErrorAction } from "./classify.ts"
describe("classify", () => {
it.each([
{ name: "429 rotates", input: { statusCode: 429 }, expected: ErrorAction.Rotate },
{ name: "401 disables", input: { statusCode: 401 }, expected: ErrorAction.Disable },
])("$name", ({ input, expected }) => {
expect(classify(input).action).toBe(expected)
})
})bun test # All tests
bun test src/state.test.ts # Single file- Cover positive, negative, and edge cases.
- Use
bun:test(not Jest or Vitest). - No mocks — test actual implementation.
- 33+ expect() calls across the suite (current baseline).
- Fork or create a branch from
main. - Make changes with atomic commits (one logical change per commit).
- Run
bun testandbun run typecheck— both must pass. - Open a PR with a clear description of what changed and why.
- PR titles follow the same conventional commit format.
Documented in documents/. If your change affects a previously decided
architecture:
- Update the relevant ADR file.
- Add a note to the changelog if user-facing.
- Reference the ADR in your PR description.
By contributing, you agree that your contributions will be licensed under the MIT License.