[rynfar/meridian] CLAUDE.md
Claude
API Leak/Claude
6,925 characters
# CLAUDE.md
Project guidelines for AI agents working in this codebase.
## What This Is
A proxy that bridges OpenCode (Anthropic API format) to Claude Max (Agent SDK). See `ARCHITECTURE.md` for the full module map and dependency rules.
## Commands
```bash
npm test # Run all tests (bun test)
npm run build # Build with tsup
npm start # Start the proxy server
```
## Code Rules
### Module Boundaries
- **Do not add code to `server.ts` that belongs in a leaf module.** If it's pure logic (no HTTP, no Hono), extract it.
- **`session/lineage.ts` must stay pure.** No side effects, no I/O, no imports from cache or server.
- **Leaf modules (`errors.ts`, `models.ts`, `tools.ts`, `messages.ts`) must not import from `server.ts` or `session/`.** Dependencies flow downward only.
- **No circular dependencies.**
### Agent-Specific Logic
OpenCode-specific behavior is documented in `ARCHITECTURE.md` under "Agent-Specific Logic". When modifying these areas:
- Add a `NOTE:` comment marking the code as agent-specific
- Do not spread agent-specific logic into new modules
- Future work will use an adapter pattern — see `DEFERRED.md`
### Testing
- Every extracted module must have unit tests
- Pure functions get direct unit tests (no mocks)
- Integration tests go through the HTTP layer with mocked SDK
- **All tests must pass before any change is considered complete**
- New test files go in `src/__tests__/`
- **E2E tests** are documented in [`E2E.md`](./E2E.md) — run manually before releases or after major refactors (requires Claude Max subscription)
### Style
- No `as any`, `@ts-ignore`, or `@ts-expect-error`
- No empty catch blocks
- Match existing patterns — check neighboring code before writing
- Keep `server.ts` as thin as possible — it should orchestrate, not compute
## Architecture Quick Reference
```
server.ts → HTTP routes, SSE streaming, concurrency (orchestration only)
adapter.ts → AgentAdapter interface (extensibility point)
adapters/
opencode.ts → OpenCode-specific: headers, CWD, tool config
forgecode.ts → ForgeCode-specific: XML CWD, patch/shell tools, passthrough
query.ts → buildQueryOptions (shared stream/non-stream SDK call builder)
errors.ts → classifyError (pure)
models.ts → mapModelToClaudeModel, resolveClaudeExecutableAsync
tools.ts → BLOCKED_BUILTIN_TOOLS, CLAUDE_CODE_ONLY_TOOLS, MCP_SERVER_NAME
messages.ts → normalizeContent, getLastUserMessage (pure)
fileChanges.ts → PostToolUse hook: file write/edit tracking + summary formatting (pure)
session/
lineage.ts → Hashing, lineage verification (PURE — no I/O)
fingerprint.ts → extractClientCwd, getConversationFingerprint
cache.ts → LRU caches, lookupSession, storeSession (stateful)
```
## Stable API Contract
External plugins depend on these interfaces. **Do not change without project owner approval.**
| Interface | Location | Used by |
|-----------|----------|---------|
| `startProxyServer(config)` → `ProxyInstance` | `server.ts` | Plugins that spawn proxy instances |
| `ProxyInstance.close()` | `types.ts` | Plugins for graceful shutdown |
| `ProxyConfig` type | `types.ts` | Plugin configuration |
| `x-opencode-session` header | `adapters/opencode.ts` | Session tracking from agent plugins |
| `x-meridian-profile` header | `server.ts`, `profiles.ts` | Per-request profile selection |
| `GET /health` response shape | `server.ts` | Plugin health checks |
| `POST /v1/messages` request/response format | `server.ts` | All agents (Anthropic API contract) |
| `GET /profiles/list` response shape | `server.ts` | Profile management UI and CLI |
| `POST /profiles/active` request/response | `server.ts` | Profile switching from CLI and UI |
If you need to modify any of these, open an issue first — breaking changes affect downstream plugin authors.
## Git & Workflow
### Commit format
- Format: `type: brief description`
- Types: feat, fix, refactor, perf, test, docs, chore
- No AI attribution lines
### Development workflow — NEVER push directly to main
All changes go through this process, no exceptions:
1. **Create a feature branch** from `main`:
```bash
git checkout -b feat/my-feature main
```
2. **Make changes, commit, push the branch:**
```bash
git add -A && git commit -m "feat: my feature"
git push origin feat/my-feature
```
3. **Create a PR** targeting `main`:
```bash
gh pr create --title "feat: my feature" --base main
```
4. **Wait for CI** — the `test` job must pass before merging:
```bash
gh pr checks <PR_NUMBER>
```
5. **Merge the PR** (squash merge preferred):
```bash
gh pr merge <PR_NUMBER> --squash --delete-branch
```
6. **Never** run `git push origin main` directly — all code reaches `main` through merged PRs only.
## Releasing
**Do NOT run `npm version`, `git push --tags`, or `npm publish` manually.**
Releases are handled automatically by [Release Please](https://github.com/googleapis/release-please):
1. Merge PRs to `main` using [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, etc.)
2. Release Please auto-creates/updates a release PR that batches all unreleased changes
3. Review the release PR — the changelog should only show changes since the last release
4. When ready to ship, merge the release PR:
```bash
gh pr merge <RELEASE_PR_NUMBER> --merge
```
5. Merging the release PR automatically:
- Bumps `package.json` and `CHANGELOG.md`
- Creates a git tag (`meridian-v*`) and GitHub Release
- Runs tests, builds, and publishes to npm with provenance
Multiple PRs get batched into a single release. Never publish manually.
### Troubleshooting releases
- **Changelog shows entire history?** — Release Please can't find the previous release tag. Check that `meridian-v<version>` tags exist for recent releases: `git tag -l 'meridian-v*' | tail -5`
- **Release PR not updating?** — It only updates on `push` to `main`. If you closed it, push any commit to main to regenerate.
- **Publish failed with E403?** — The version was already published. This is safe to ignore; the release is already on npm.
- **`publish_only` workflow dispatch** — Emergency escape hatch to publish the current version without Release Please. Only use when the normal flow is broken.
### Release config files
- **`.release-please-manifest.json`** — tracks the current released version. Release Please updates this automatically when a release PR is merged. **Do not edit manually** unless resetting the version anchor.
- **`release-please-config.json`** — defines the release type (`node`), component name, and changelog section mapping.
- **`.github/workflows/release-please.yml`** — the workflow that runs on every push to `main`. It creates/updates the release PR and publishes to npm when merged.