- One function, one purpose: Each function should do exactly one thing well
- One class, one reason to change: Each class encapsulates a single concern
- Pure functions preferred: Minimize side effects, maximize testability
- Extract reusable code into functions: Reduce duplication immediately
- Shared utilities go in
/src/utils/: Common functionality in one place - Use composition over repetition: Build complex behavior from small pieces
- Imports: Alphabetical order, grouped by source (stdlib → packages → local)
- Export single responsibility items: Keep module boundaries clean
- Avoid circular dependencies: Maintain clear dependency flow
- No error suppression: Never use
@ts-ignore,# noqa,# type: ignore - Explicit error handling: Catch and handle errors at appropriate levels
- Meaningful error messages: Include context for debugging
- Precise type hints for public APIs: All exported functions/classes must be typed
- Avoid
anytype: Use specific types or generics instead - Type narrowing: Use typeof checks and discriminated unions
- GitHub issue exists with clearly defined requirements
- Work matches issue content exactly - no scope creep
- Issue includes quality checkboxes (tests, coverage, linting, type-check)
# 1. Create feature branch from issue description
git checkout -b feature/issue-description
# 2. Implement changes with tests
# 3. Run quality gates locally (see below)
# 4. Commit and pushBefore pushing to CI, run ALL quality gates locally:
# 1. Run tests
npm run test
# 2. Run type checking
npm run typecheck
# 3. Run linting
npm run lintAll must pass before pushing. No exceptions.
- Feature branch created
- Tests written (before implementation when possible)
- Code passes all quality gates locally
- Test coverage 80%+ for new code
- No type suppressions (
@ts-ignore, etc.) - Linting passes
- Type checking passes
- All issue checkboxes completed
<type>(<scope>): <subject>
<body>
<footer>
- feat: New feature (bumps minor version)
- fix: Bug fix (bumps patch version)
- docs: Documentation changes
- test: Test additions or improvements
- refactor: Code reorganization (no behavior change)
- perf: Performance improvements
- chore: Build, dependencies, tooling
feat(sync): add symlink version comparison logic
fix(cli): resolve permission issues in non-interactive mode
docs: update setup instructions for macOS
test: add filesystem mocking for symlink tests
Format: MAJOR.MINOR.PATCH
- MAJOR: Breaking changes (API modifications)
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
Example progression:
- v1.0.0 → v1.1.0 (new feature)
- v1.1.0 → v1.1.1 (bug fix)
- v1.1.1 → v2.0.0 (breaking change)
Before writing ANY code, file, or component, ask:
- ✅ Is this explicitly required by the GitHub issue?
- ✅ Can existing code/tools solve this instead?
- ✅ What's the SIMPLEST way to meet the requirement?
- ✅ Will removing this break core functionality?
- ✅ Am I building for hypothetical future needs?
If you cannot justify necessity, DO NOT CREATE IT.
200-PR Litmus Test: Will this file be useful in 200 PRs?
| File Type | Location | Status |
|---|---|---|
| Source code | src/ |
✅ Keep |
| Tests | tests/ |
✅ Keep |
| Configuration | root (tsconfig, .eslint, prettier) | ✅ Keep |
| CI/CD | .github/workflows/ |
✅ Keep |
| Debug scripts | Any filename with debug_ |
❌ Delete |
| Analysis docs | *_SUMMARY.md, NOTES.md |
❌ Delete |
| Throwaway investigation scripts | One-off bash files | ❌ Delete |
-
Imports: Group and alphabetize (stdlib → packages → local)
import type { Plugin } from "@opencode-ai/plugin" import { access, mkdir } from "fs/promises" import { join } from "path" import type { Config } from "./types" import { parseVersion } from "./utils"
-
Naming: Use descriptive names following camelCase
// Good async function findSkillsInCache(cacheDir: string): Promise<SkillInfo[]> // Avoid async function f(d): Promise<any>
-
Comments: Use docstrings for public APIs, inline comments sparingly
/** * Discovers and syncs skills from cache to target directory. * @param cacheDir - Path to plugin cache * @returns Array of found skills */
// Describe the behavior, not the implementation
describe('findSkillsInCache', () => {
it('should return empty array when cache directory does not exist', () => {})
it('should find skills with valid SKILL.md file', () => {})
it('should sort versions using semantic versioning', () => {})
})- Write test that fails (red)
- Implement minimum code to pass test (green)
- Refactor to clean up implementation (refactor)
- Minimum 80% coverage for all new code
- All edge cases tested: null, empty, error states
- Integration tests for multi-component flows
- Unit tests: Pure functions, no side effects
- Integration tests: Multiple components working together
- Mocking: Use vitest for filesystem, OpenCode client, async operations
npm run test:coverage
# Check report for untested branches/lines