From f10e0c89b87e34e8a9e617e1769082513fd090e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 1 Aug 2026 16:25:04 +0000 Subject: [PATCH] docs: add CLAUDE.md with codebase architecture and workflow guide Documents the monorepo structure, schema/protocol/server/core dependency layering, per-package conventions, Effect-TS patterns, test/build/lint commands, and git/PR workflow for AI assistants working in this repo. --- CLAUDE.md | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000000..df6642f4b98e --- /dev/null +++ b/CLAUDE.md @@ -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 # 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`, ``, 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-/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()("@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`).