Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

OpenCode is an open source AI coding agent: a terminal UI (TUI), headless API server, web app, and desktop app, all sharing one Bun/TypeScript/Effect core. It ships two built-in agents (`build` — full access, `plan` — read-only/ask-first) plus an internal `general` subagent for multistep search, and supports custom providers/plugins/MCP servers.

This is a Bun workspaces + Turborepo monorepo. Bun >= 1.3 is required (`packageManager` in `package.json` pins the exact version; `.husky/pre-push` enforces it).

**The default branch is `dev`, not `main`.** A local `main` ref may not exist — diff against `dev` / `origin/dev`.

## Common Commands

Run everything from the repo root unless noted.

```bash
bun install # install all workspace deps
bun dev # run the TUI against packages/opencode itself
bun dev <directory> # run the TUI against another directory/repo
bun dev serve [--port N] # headless API server only (default port 4096)
bun dev web # start server + open the web UI
bun dev:desktop # Electron desktop app (packages/desktop)
bun dev:web # web app dev server (packages/app)
bun dev:console # packages/console/app
bun dev:stats # packages/stats/app (via `sst shell --stage=production`)
bun dev:storybook # component storybook

bun lint # oxlint (type-aware)
bun typecheck # turbo typecheck across every package (uses tsgo, not tsc)
```

`bun dev` is the dev-mode equivalent of the built `opencode` binary — same subcommands (`serve`, `web`, `<directory>`, etc.).

### Tests

Tests **cannot run from the repo root** — the root `test` script deliberately exits 1 (guard: `do-not-run-tests-from-root`). Run them from the owning package directory instead:

```bash
cd packages/opencode && bun test # whole package
cd packages/opencode && bun test src/session/summary.test.ts # single file
cd packages/opencode && bun test -t "some test name" # filter by name
```

Most package `test` scripts pass `--only-failures`; test files live under each package's `test/` directory (not colocated with `src/`, e.g. `packages/opencode/test/**`, `packages/core/test/**`). `packages/opencode` also has `test:httpapi` (coverage/auth/effect HttpApi exhaustiveness checks), `bench:test`, and `profile:test`.

Type checking: always run `bun typecheck` from a package directory (or the root, which fans it out via turbo) — never invoke `tsc`/`tsgo` directly.

### Building

```bash
./packages/opencode/script/build.ts --single # standalone "localcode" binary
./packages/opencode/dist/opencode-<platform>/bin/opencode
```

### Regenerating generated code

- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Never hand-edit `src/generated` or `src/generated-effect`; `packages/client` has `check:generated` to verify they're up to date.
- To regenerate the legacy JS SDK: `./packages/sdk/js/script/build.ts`.

### Debugging

Bun's debugger is rough; run manually with `--inspect` and attach rather than using an IDE "launch" config (breakpoints can map incorrectly). If you need TUI + server breakpoints together, run `bun dev spawn` (plain `bun dev` runs the server in a worker thread where breakpoints may not hit). Full instructions, including split TUI/server debug invocations and `.vscode/*.example.json`, are in `CONTRIBUTING.md`.

## Architecture

### Package dependency layering

The wire/runtime layers form a strict one-way dependency chain (`AGENTS.md`):

```
@opencode-ai/schema → @opencode-ai/protocol → @opencode-ai/server
↑ ↑
└─────────────── @opencode-ai/client ──────────┘ (schema + protocol only, never core/server)

sdk-next composes client + core + server
```

- **`packages/schema`** — browser-safe Effect Schema wire/storage contracts only (no services, no side effects). Current (unversioned) contracts vs. legacy `V1` contracts (e.g. `Session` vs `SessionV1`) coexist during migration; new code must not depend on the `v1/` subtree.
- **`packages/protocol`** — the current `/api/...` `HttpApi` surface, built from schema.
- **`packages/server`** — the concrete `HttpApi` router/handlers, built from protocol + core.
- **`packages/core`** (`@opencode-ai/core`) — the domain/business logic: sessions, permissions, providers, plugins, config, database (Drizzle schema + migrations under `src/database`/`src/**/*.sql.ts`), filesystem, git, LSP, system-context, etc. Effect-first throughout.
- **`packages/client`** — the generated "OpenCode Client" (Promise + Effect SDKs) reflected once from the public `HttpApi` into an SDK Contract IR, then emitted by independent Promise/Effect emitters. "Embedded OpenCode" is the same client wired to an in-memory `HttpClient` transport against the same router (no network hop).
- **`packages/sdk`, `packages/sdk-next`** — published SDK packages built on top of `client`.

### Applications

- **`packages/opencode`** — the `opencode` CLI/binary: business logic entrypoint, headless server bootstrapping, and `src/cli/cmd/tui/` (the TUI, SolidJS + [opentui](https://github.com/sst/opentui)). This is also where session orchestration lives (`src/session/`), separate from `@opencode-ai/core`'s lower-level session domain code.
- **`packages/cli`** (`@opencode-ai/cli`, bin `lildax`) — thin composition of core/sdk/server/tui.
- **`packages/tui`** — reusable SolidJS/opentui TUI framework pieces (context, config, editor, runtime) consumed by `packages/opencode`'s TUI.
- **`packages/app`** — shared web UI (SolidJS), the UI layer behind both the web app and the desktop app.
- **`packages/desktop`** — Electron app wrapping `packages/app`. Renderer code must only reach native APIs via `window.api` (`src/preload`); IPC handlers are registered in `src/main/ipc.ts`.
- **`packages/web`** — marketing/docs site (Astro + Starlight).
- **`packages/console`, `packages/stats`** — internal apps (console UI, usage/stats dashboard, deployed via SST — see `sst.config.ts`).
- **`packages/session-ui`, `packages/ui`** — shared component libraries (session/message rendering, design system) consumed by app/console/storybook.
- **`packages/storybook`** — component storybook for `ui`/`session-ui`.

### Supporting packages

- **`packages/llm`** (`@opencode-ai/llm`) — standalone, Effect Schema-first, multi-provider LLM client, independent of session/product concerns. A **route** is a composition of `Protocol` (request/response shape + streaming state machine), `Endpoint` (URL), `Auth`, and `Framing` (SSE, etc.); provider facades (`OpenAI.configure(...)`, `Azure.configure(...)`, ...) are thin configured wrappers over routes. This decomposition is why many OpenAI-compatible providers (DeepSeek, TogetherAI, Cerebras, ...) are ~15-line route definitions reusing `OpenAIChat.protocol`. `packages/opencode/src/session/llm.ts` and `src/session/llm/*` are the integration point that decides AI-SDK vs. this package's native route runtime for a given request. See `packages/llm/AGENTS.md` for the full route/protocol/tool-dispatch model.
- **`packages/plugin`** (`@opencode-ai/plugin`) — SDK for writing OpenCode plugins (tools, TUI extensions, hooks).
- **`packages/codemode`** — confined sandboxed execution over explicit schema-described tools; host-agnostic (no session/channel/conversation awareness — the host supplies authorization and scope).
- **`packages/effect-drizzle-sqlite`, `packages/effect-sqlite-node`** — vendored, generic (non-opencode-specific) Effect + Drizzle + SQLite adapters, intended to be replaceable by an eventual upstream `drizzle-orm/effect-sqlite`.
- **`packages/httpapi-codegen`** — the SDK Contract IR / codegen machinery used to emit `packages/client`.
- **`packages/http-recorder`** — HTTP cassette recording/replay used by `llm` and other provider tests.
- **`packages/function`, `packages/identity`, `packages/enterprise`, `packages/containers`, `packages/slack`** — auxiliary services (Cloudflare Worker functions/GitHub App, auth, enterprise features, container runtime, Slack integration).
- **`script/`** (repo root) — release/changelog/publish automation, GitHub triage bots, translation scripts.

### Session runtime (V2) and System Context

OpenCode is mid-migration to a "V2" session core; both old and new session code paths currently coexist (see the `V1` schema split above). Key invariants (`AGENTS.md`):

- Durable prompt admission is separate from model execution: `SessionV2.prompt(...)` admits a durable `session_input` row, then schedules advisory `SessionExecution.wake(sessionID)`; a serialized runner promotes admitted input into visible messages only at safe boundaries.
- `SessionExecution` is process-global and Session-ID based; `SessionRunner`, model resolution, tools, permissions, and filesystem access are Location-scoped.
- Exactly one `llm.stream(request)` call happens per provider turn; legacy `SessionPrompt.loop(...)` must not be bridged into new code.
- Session drains are process-local coordination only — no durable identity — until clustering exists.

The precise domain vocabulary (System Context, Context Source, Context Epoch, Safe Provider-Turn Boundary, Session Drain, Admitted Prompt vs. Prompt Promotion, Mid-Conversation System Message, etc.) is formally defined in **`CONTEXT.md`** — read it before touching session/context-assembly code; don't infer these terms from context.

### Effect-TS conventions

Nearly all runtime code (core, server, protocol, opencode, llm) is written in Effect (v4 beta / "effect-smol"). Highlights from `packages/opencode/AGENTS.md` (read it in full before writing Effect code):

- `Effect.gen(function* () {...})` for composition; `Effect.fn("Domain.method")` for named/traced effects, `Effect.fnUntraced` for internal helpers; `yield* new MyError(...)` over `Effect.fail(new MyError(...))`.
- `makeRuntime` (`src/effect/run-service.ts`) for services in general; `InstanceState` (`src/effect/instance-state.ts`) specifically for per-directory/per-project state that needs its own lifecycle (backed by a directory-keyed `ScopedCache`).
- `EffectBridge` at native/external callback boundaries (`@parcel/watcher`, `node-pty`, `fs.watch`, plugin callbacks) that need to re-enter Effect services with instance context.
- Effect v4 beta has no `Effect.fork`/`Effect.forkDaemon` — use `Effect.forkIn(scope)`.
- Prefer Effect's own service wrappers (`FileSystem.FileSystem`, `HttpClient.HttpClient`, `ChildProcessSpawner`, `Path.Path`, `Clock`, `DateTime`) over raw Node/Bun/web APIs inside effectful code.

### Module shape

Do **not** use `export namespace Foo { ... }` (non-standard ESM, defeats tree-shaking, breaks Node's native TS runner). Instead use flat exports plus a self-reexport at the bottom of the file:

```ts
// src/foo/foo.ts
export interface Interface { ... }
export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
export const layer = Layer.effect(Service, ...)

export * as Foo from "./foo"
```

Consumers import the namespace: `import { Foo } from "@/foo/foo"`. For `foo/index.ts`, self-reexport from `"."` rather than `"./index"`. Directories with several independent sibling modules (e.g. `src/session/`, `src/config/`) keep each sibling as its own file with its own self-reexport and **no barrel `index.ts`** — a barrel forces every import to evaluate every sibling and defeats tree-shaking.

## Code Style

`AGENTS.md` (root) is the authoritative, example-backed style guide — read it before making non-trivial changes. It is also the canonical source for git conventions. The load-bearing rules it enforces:

- One function unless splitting adds real reuse/composition; don't preemptively extract single-use helpers.
- No `else` (early returns instead); no unnecessary destructuring (use dot notation); `const` over `let`; avoid `try`/`catch` where avoidable; avoid `any`.
- Never alias imports (`import { foo as bar }`) and never use star imports (`import * as Foo`) — import a module's own exported namespace by name instead (e.g. `import { Project } from "@opencode-ai/core/project"`).
- Prefer dynamic imports for heavy, conditionally-needed modules, especially in startup-sensitive entrypoints; destructure the binding near the top of the narrowest scope that needs it.
- Drizzle schema fields use `snake_case` names directly (e.g. `project_id: text().notNull()`) instead of remapping camelCase fields to string column names.
- Use Bun APIs (`Bun.file()`, etc.) where they fit.

Several packages layer additional, package-specific rules in their own `AGENTS.md` — check for one before working in these directories:

| Package | Notes |
|---|---|
| `packages/schema` | Contract-boundary rules: current vs. `V1` naming, event classification, module/naming/mutability conventions for Effect Schema contracts. |
| `packages/llm` | Route/protocol architecture, provider facade conventions, protocol file section ordering, recorded-test cassette workflow (`RECORD=true`, `RECORDED_*` filters). |
| `packages/opencode` | Database/migration locations, module-shape rules (above), full Effect rules, and a `tmux`-based workflow for running `bun dev` non-blockingly (`tmux new-session -d -s opencode-dev 'bun dev'`, then `tmux capture-pane -pt opencode-dev`). |
| `packages/app` | Stability > simplicity > performance; benchmark session/timeline changes before/after; never restart the app/server process while debugging; SolidJS `createStore` over multiple `createSignal`; local UI dev requires running backend and `packages/app` dev servers separately (`opencode dev web` proxies the hosted app, not local changes). |
| `packages/desktop` | Renderer → `window.api` only; IPC handlers in `src/main/ipc.ts`. |
| `packages/effect-drizzle-sqlite` | Keep generic (Drizzle + Effect + SQLite only) — no opencode-specific tables/paths/migrations here. |
| `packages/codemode` | No speculative permission/approval policy; host owns authorization; keep sandbox host-neutral. |
| `packages/stats` | `bun dev:stats` from repo root to run locally. |

## Testing conventions

- Avoid mocks; avoid `globalThis.*` unless it's genuinely the only option. Test the real implementation rather than duplicating its logic in the test.
- `packages/llm` provider tests are fixture-first: live calls are gated behind `RECORD=true` plus required API-key env vars, replayed from cassettes otherwise (see `packages/llm/AGENTS.md`).

## Config & tooling

- `.opencode/` is this repo's own OpenCode project config: `opencode.jsonc` (providers/permissions/MCP/tool toggles/reference docs), plus `agent/`, `command/`, `skills/`, `plugins/`, `themes/`.
- Linting is `oxlint` (type-aware; see `.oxlintrc.json` for suppressed rules and why). Formatting is Prettier (`semi: false`, `printWidth: 120`, configured in root `package.json`).
- `.husky/pre-push` verifies the running Bun version matches `package.json`'s `packageManager` and runs `bun typecheck`.

## Git / contribution workflow

(Full detail in `CONTRIBUTING.md`; branch/commit conventions also restated in `AGENTS.md`.)

- Branch names: short (≤3 words), hyphen-separated, no slashes or `type/` prefixes (e.g. `session-recovery`, `fix-scroll-state`).
- Commits and PR titles: conventional-commit style, `type(scope): summary`, types `feat|fix|docs|chore|refactor|test`, optional scope naming the affected package/area (`core`, `opencode`, `tui`, `app`, `desktop`, `sdk`, `plugin`, ...).
- **Issue-first policy**: PRs must reference an existing issue (`Fixes #123`); UI PRs need before/after screenshots or video; non-UI PRs must explain how the change was verified.
- Any UI or core product feature needs a design review with the core team before implementation — bug fixes, new provider/LSP/formatter support, and doc improvements don't.
- Keep PR/issue descriptions short and human-written; long AI-generated walls of text are explicitly discouraged and may be ignored or flag the contribution under the project's vouch/denounce trust system (`.github/VOUCHED.td`).