<!-- Context: openagents-repo/standards | Priority: critical | Version: 1.0 | Updated: 2026-02-16 -->

# OpenCode TypeScript Standards

**OpenCode-Specific Patterns**  
_Last Updated: Feb 2026_  
_Analyzed: 206+ TypeScript files across `packages/opencode/src/`_

**Note**: This file contains OpenCode-specific patterns (fn() wrapper, namespaces, Instance.state(), etc.). For universal TypeScript patterns, see `core/standards/typescript.md`.

---

## Table of Contents

1. [Function Definition Standards](#1-function-definition-standards)
2. [Class Usage Standards](#2-class-usage-standards)
3. [Array Handling Standards](#3-array-handling-standards)
4. [Variable & Destructuring Standards](#4-variable--destructuring-standards)
5. [Control Flow Standards](#5-control-flow-standards)
6. [Async & Concurrency Standards](#6-async--concurrency-standards)
7. [Race Condition Prevention](#7-race-condition-prevention)
8. [AI System Integration Standards](#8-ai-system-integration-standards)
9. [Service Architecture Standards](#9-service-architecture-standards)
10. [State Management Standards](#10-state-management-standards)
11. [Event Bus Standards](#11-event-bus-standards)
12. [Configuration Management Standards](#12-configuration-management-standards)
13. [Storage & Persistence Standards](#13-storage--persistence-standards)
14. [Error Handling Standards](#14-error-handling-standards)
15. [Type System Standards](#15-type-system-standards)
16. [Import Organization Standards](#16-import-organization-standards)
17. [Naming Conventions](#17-naming-conventions)
18. [Testing Standards](#18-testing-standards)
19. [Documentation Standards](#19-documentation-standards)
20. [Schema Definition Standards](#20-schema-definition-standards)
21. [Dependency Management Standards](#21-dependency-management-standards)
22. [Build & Development Standards](#22-build--development-standards)
23. [Performance Standards](#23-performance-standards)
24. [Security Standards](#24-security-standards)
25. [Modern TypeScript Patterns](#25-modern-typescript-patterns)
26. [Code Quality Tools](#26-code-quality-tools)
27. [Monorepo Patterns](#27-monorepo-patterns)
28. [Context System Standards](#28-context-system-standards)

---

## 1. Function Definition Standards

### 1.1 Naming Convention

**Rule: Prefer single-word function names**

**Compliance: 95%+**

```typescript
// ✅ GOOD - Single-word names
export const create = fn(...)
export const fork = fn(...)
export const touch = fn(...)
export const get = fn(...)
export const share = fn(...)
export async function stream(input: StreamInput) {...}
export async function work<T>(concurrency, items, fn) {...}

// ✅ ACCEPTABLE - Multi-word only when necessary
export function isDefaultTitle(title: string) {...}      // Boolean predicate
export function assertNotBusy(sessionID: string) {...}   // Assertion pattern
export async function createNext(input) {...}            // Version disambiguation
export async function resolvePromptParts(template) {...} // Complex operation needs clarity

// ❌ AVOID - Unnecessary multi-word names
function prepareJournal(dir: string) {}  // Use: journal()
function getUserData(id: string) {}      // Use: user()
function processFileContent(path) {}     // Use: process()
```

**File References:**

- `packages/opencode/src/session/index.ts` - Lines 140, 200, 206
- `packages/opencode/src/session/llm.ts` - Line 59
- `packages/opencode/src/util/queue.ts` - Lines 1-32

### 1.2 Function Definition Patterns

#### Pattern A: Zod-Validated Functions (Primary Pattern)

```typescript
// packages/opencode/src/util/fn.ts - The fn() wrapper
export function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.infer<T>) => Result) {
  const result = (input: z.infer<T>) => {
    const parsed = schema.parse(input) // Validates input
    return cb(parsed)
  }
  result.force = (input: z.infer<T>) => cb(input) // Skip validation
  result.schema = schema // Expose schema
  return result
}

// Usage Example 1: Session creation
export const create = fn(
  z
    .object({
      parentID: Identifier.schema("session").optional(),
      title: z.string().optional(),
      permission: Info.shape.permission,
    })
    .optional(),
  async (input) => {
    return createNext({
      parentID: input?.parentID,
      title: input?.title,
      permission: input?.permission ?? "allow",
    })
  },
)

// Usage Example 2: Simple validation
export const touch = fn(Identifier.schema("session"), async (sessionID) => {
  await update(sessionID, (draft) => {
    draft.time.updated = Date.now()
  })
})

// Usage Example 3: Complex object validation
export const messages = fn(
  z.object({
    sessionID: Identifier.schema("session"),
    limit: z.number().optional(),
  }),
  async (input) => {
    const result = [] as MessageV2.WithParts[]
    for await (const msg of MessageV2.stream(input.sessionID)) {
      if (input.limit && result.length >= input.limit) break
      result.push(msg)
    }
    result.reverse()
    return result
  },
)
```

**File References:**

- `packages/opencode/src/util/fn.ts` - Lines 1-12
- `packages/opencode/src/session/index.ts` - Lines 140-202

#### Pattern B: Namespace Organization (Preferred over Classes)

```typescript
// packages/opencode/src/session/index.ts
export namespace Session {
  const log = Log.create({ service: "session" })

  // Exported functions with fn() wrapper
  export const create = fn(...)
  export const fork = fn(...)
  export const touch = fn(...)
  export const messages = fn(...)

  // Async functions without wrapper
  export async function createNext(input) {...}
  export async function update(id, editor, options) {...}

  // Type definitions
  export type Info = z.infer<typeof Info>
  export const Info = z.object({...})

  // Events
  export const Event = {
    Created: BusEvent.define("session.created", z.object({info: Info})),
    Updated: BusEvent.define("session.updated", z.object({info: Info})),
  }

  // Error classes (only exception to no-class rule)
  export class BusyError extends Error {
    constructor(public readonly sessionID: string) {
      super(`Session ${sessionID} is busy`)
    }
  }
}

// Usage
await Session.create({ title: "New Session" })
await Session.touch(sessionID)
const msgs = await Session.messages({ sessionID, limit: 10 })
```

**File References:**

- `packages/opencode/src/session/index.ts` - Lines 26-518
- `packages/opencode/src/tool/tool.ts` - Lines 1-90
- `packages/opencode/src/session/processor.ts` - Lines 19-416

#### Pattern C: Inline vs. Extraction Rule

**Rule: Inline when used once, extract when composable/reusable**

```typescript
// ✅ GOOD - Inline when value used once
const journal = await Bun.file(path.join(dir, "journal.json")).json()
const [language, cfg] = await Promise.all([Provider.getLanguage(input.model), Config.get()])

// ❌ BAD - Unnecessary intermediate variable
const journalPath = path.join(dir, "journal.json")
const journalFile = Bun.file(journalPath)
const journal = await journalFile.json()

// ✅ GOOD - Extract when reusable across multiple call sites
async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "user">) {
  const disabled = PermissionNext.disabled(Object.keys(input.tools), input.agent.permission)
  for (const tool of Object.keys(input.tools)) {
    if (input.user.tools?.[tool] === false || disabled.has(tool)) {
      delete input.tools[tool]
    }
  }
  return input.tools
}

// Called from multiple locations
const tools = await resolveTools({ tools: input.tools, agent, user })

// ✅ GOOD - Extract when logic is complex and improves readability
function shouldUseCopilotResponsesApi(modelID: string): boolean {
  const match = /^gpt-(\d+)/.exec(modelID)
  if (!match) return false
  return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
}
```

**File References:**

- `packages/opencode/src/session/llm.ts` - Lines 59, 268-276
- `packages/opencode/src/provider/provider.ts` - Lines 46-56

### 1.3 Function Composition Patterns

```typescript
// ✅ GOOD - Functional composition with pipes
const filtered = agents
  .filter((a) => a.mode !== "primary")
  .filter((a) => PermissionNext.evaluate("task", a.name, caller.permission).action !== "deny")
  .map((a) => a.name)

// ✅ GOOD - Async composition with Promise.all
const [results, metadata] = await Promise.all([processItems(items), fetchMetadata(id)])

// ✅ GOOD - Higher-order functions
export function withLock<T>(filepath: string, fn: () => Promise<T>): Promise<T> {
  return Lock.write(filepath).then(async (lock) => {
    try {
      return await fn()
    } finally {
      lock[Symbol.dispose]()
    }
  })
}
```

---

## 2. Class Usage Standards

### 2.1 Avoid Classes (Except Specific Patterns)

**Finding: Only 5 classes in 206 files**

**Rule: Use namespaces + functions instead of classes**

```typescript
// ❌ AVOID - Class-based organization
class SessionManager {
  private sessions: Map<string, Session> = new Map()

  create(input: CreateInput) {
    const session = { id: generateId(), ...input }
    this.sessions.set(session.id, session)
    return session
  }

  update(id: string, data: Partial<Session>) {
    const session = this.sessions.get(id)
    if (!session) throw new Error("Not found")
    Object.assign(session, data)
    return session
  }
}

// ✅ PREFER - Namespace-based organization
export namespace Session {
  const state = Instance.state(async () => ({
    sessions: new Map<string, Info>()
  }))

  export const create = fn(
    CreateInput.schema,
    async (input) => {
      const session = { id: Identifier.ascending("session"), ...input }
      state().sessions.set(session.id, session)
      return session
    }
  )

  export const update = fn(
    z.object({ id: z.string(), data: z.record(z.unknown()) }),
    async (input) => {
      const session = state().sessions.get(input.id)
      if (!session) throw NotFoundError.create({...})
      Object.assign(session, input.data)
      return session
    }
  )
}
```

### 2.2 Allowed Class Use Cases

#### Use Case 1: Error Types

```typescript
// ✅ GOOD - Custom error classes
export class BusyError extends Error {
  constructor(public readonly sessionID: string) {
    super(`Session ${sessionID} is busy`)
  }
}

// Usage
throw new BusyError(sessionID)

// Catching
try {
  await operation()
} catch (error) {
  if (error instanceof BusyError) {
    // Handle busy state
  }
}
```

**File Reference:** `packages/opencode/src/session/index.ts:494`

#### Use Case 2: Protocol Implementations

```typescript
// ✅ GOOD - Implementing external interfaces
export class OpenAICompatibleChatLanguageModel implements LanguageModelV2 {
  readonly specificationVersion = "v2"
  readonly provider: string
  readonly modelId: string

  constructor(modelId: string, settings: ModelSettings) {
    this.modelId = modelId
    this.provider = settings.provider
  }

  async doGenerate(options: GenerateOptions): Promise<GenerateResult> {
    // Implementation required by interface
  }

  async doStream(options: StreamOptions): AsyncIterableIterator<StreamPart> {
    // Implementation required by interface
  }
}

// ✅ GOOD - OAuth protocol implementation
export class McpOAuthProvider implements OAuthClientProvider {
  async getAccessToken(): Promise<string> {
    // OAuth flow implementation
  }
}
```

**File References:**

- `packages/opencode/src/provider/sdk/*` - Lines 53, 131
- `packages/opencode/src/mcp/oauth-provider.ts:26`

#### Use Case 3: Async Iterators

```typescript
// ✅ GOOD - AsyncIterable implementation
export class AsyncQueue<T> implements AsyncIterable<T> {
  private queue: T[] = []
  private resolvers: ((value: T) => void)[] = []

  push(item: T) {
    const resolve = this.resolvers.shift()
    if (resolve) {
      resolve(item)
    } else {
      this.queue.push(item)
    }
  }

  async next(): Promise<T> {
    if (this.queue.length > 0) {
      return this.queue.shift()!
    }
    return new Promise((resolve) => {
      this.resolvers.push(resolve)
    })
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      yield await this.next()
    }
  }
}

// Usage
const queue = new AsyncQueue<Message>()
queue.push(message)

for await (const msg of queue) {
  console.log(msg)
}
```

**File Reference:** `packages/opencode/src/util/queue.ts:1-19`

#### Use Case 4: Complex Stateful Managers

```typescript
// ✅ ACCEPTABLE - When managing complex state machines
export class ACPSessionManager {
  private sessions: Map<string, ACPSession>
  private connections: Map<string, WebSocket>

  // Complex lifecycle management
  async createSession(id: string) {...}
  async handleMessage(id: string, msg: Message) {...}
  async closeSession(id: string) {...}

  // State machine logic
  private transition(from: State, to: State) {...}
}
```

**File Reference:** `packages/opencode/src/acp/session.ts:8`

---

## 3. Array Handling Standards

### 3.1 Functional Methods (85% of operations)

**Rule: Prefer map/filter/flatMap over for-loops**

```typescript
// ✅ GOOD - Functional chain with type inference
const files = messages
  .flatMap((x) => x.parts)
  .filter((x): x is Patch => x.type === "patch") // Type guard maintains inference
  .flatMap((x) => x.files)
  .map((x) => path.relative(Instance.worktree, x))

// ✅ GOOD - Parallel async operations
const results = await Promise.all(
  toolCalls.map(async (call) => {
    return executeCall(call)
  }),
)

// ✅ GOOD - Reduce for aggregation
const totalAdditions = diffs.reduce((sum, x) => sum + x.additions, 0)

// ✅ GOOD - Filter with type guards
const agents = await Agent.list().then((x) => x.filter((a): a is Agent & { mode: "secondary" } => a.mode !== "primary"))

// ✅ GOOD - Unique values
const uniqueNames = Array.from(new Set(items.map((x) => x.name)))

// ✅ GOOD - Sorting
const sorted = items.toSorted((a, b) => a.timestamp - b.timestamp)
```

**File References:**

- `packages/opencode/src/tool/batch.ts` - Lines 41, 56, 80, 170
- `packages/opencode/src/session/prompt.ts` - Lines 192-200
- `packages/opencode/src/session/summary.ts` - Lines 96-109

### 3.2 For-Loops (15%, only when necessary)

**Rule: Use for-loops only for:**

1. Algorithm complexity (DP, graph traversal)
2. Early exit requirements
3. Sequential side effects
4. Performance-critical iteration

#### Pattern 1: Algorithm Complexity

```typescript
// ✅ GOOD - Dynamic programming (LCS algorithm)
// packages/opencode/src/tool/edit.ts:175-176
function lcs(a: string[], b: string[]): number[][] {
  const dp: number[][] = Array(a.length + 1)
    .fill(0)
    .map(() => Array(b.length + 1).fill(0))

  for (let i = 1; i <= a.length; i++) {
    for (let j = 1; j <= b.length; j++) {
      if (a[i - 1] === b[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
      }
    }
  }

  return dp
}
```

#### Pattern 2: Early Exit

```typescript
// ✅ GOOD - Break on condition
// packages/opencode/src/session/revert.ts:31-40
const patches = []
for (const msg of all) {
  if (msg.info.id === revert.messageID) break
  for (const part of msg.parts) {
    if (part.type === "patch") {
      patches.push(part)
    }
  }
}

// ✅ GOOD - Find first match
for (const file of files) {
  if (await Filesystem.exists(file)) {
    return file
  }
}
```

#### Pattern 3: Sequential Side Effects

```typescript
// ✅ GOOD - Sequential mutations
for (const key of Object.keys(input.tools)) {
  if (input.user.tools?.[key] === false || disabled.has(key)) {
    delete input.tools[key]
  }
}

// ✅ GOOD - Streaming output
for (const line of lines) {
  await stream.write(line)
}
```

**File Reference:** `packages/opencode/src/tool/grep.ts:77`

#### Pattern 4: Performance Critical

```typescript
// ✅ GOOD - Low-level iteration for performance
for (let i = 0; i < buffer.length; i++) {
  result += buffer[i] * multiplier
}

// Note: Profile before optimizing - functional methods are often fast enough
```

### 3.3 Type Guards on Filter

**Rule: Use type guards to maintain type inference downstream**

```typescript
// ✅ GOOD - Type guard preserves type information
const patches = messages.flatMap((msg) => msg.parts).filter((part): part is PatchPart => part.type === "patch")
// patches is now PatchPart[], not Part[]

// ❌ BAD - Loses type information
const patches = messages.flatMap((msg) => msg.parts).filter((part) => part.type === "patch")
// patches is still Part[], requires casting later

// ✅ GOOD - Multiple type guards
const validAgents = agents
  .filter((a): a is Agent => a !== undefined)
  .filter((a): a is Agent & { mode: "secondary" } => a.mode !== "primary")
```

**File Reference:** `packages/opencode/src/tool/task.ts:24,29`

---

## 4. Variable & Destructuring Standards

### 4.1 Variable Declaration

**Rule: Prefer `const` over `let`**

```typescript
// ✅ GOOD - Immutable with ternary
const foo = condition ? 1 : 2
const result = await (isValid ? processValid() : processInvalid())

// ❌ BAD - Reassignment
let foo
if (condition) {
  foo = 1
} else {
  foo = 2
}

// ✅ GOOD - Early return instead of reassignment
function getValue(condition: boolean) {
  if (condition) return 1
  return 2
}

// ✅ ACCEPTABLE - let when mutation is necessary
let accumulator = 0
for (const item of items) {
  accumulator += item.value
}
```

**File Reference:** `AGENTS.md:56-68`

### 4.2 Destructuring

**Rule: Avoid unnecessary destructuring, preserve context with dot notation**

```typescript
// ✅ GOOD - Preserve context
function process(session: Session) {
  log.info("processing", { id: session.id, title: session.title })
  return {
    id: session.id,
    status: session.status,
    owner: session.owner
  }
}

// ❌ BAD - Loses context, harder to read
function process(session: Session) {
  const { id, title, status, owner } = session
  log.info("processing", { id, title })
  return { id, status, owner }
}

// ✅ ACCEPTABLE - Destructuring when improving readability
function renderUser({ name, email, avatar }: User) {
  return `<div>${name} (${email})</div>`
}

// ✅ ACCEPTABLE - Destructuring array returns
const [language, cfg, provider] = await Promise.all([...])
```

**File Reference:** `AGENTS.md:43-54`

### 4.3 Variable Naming

**Rule: Concise single-word names when descriptive**

```typescript
// ✅ GOOD
const session = await Session.get(id)
const user = await Auth.current()
const messages = await Session.messages({ sessionID })

// ❌ BAD - Unnecessary verbosity
const currentSession = await Session.get(id)
const currentlyAuthenticatedUser = await Auth.current()
const sessionMessagesList = await Session.messages({ sessionID })

// ✅ GOOD - Multi-word when single word is ambiguous
const sessionID = params.id
const userAgent = req.headers["user-agent"]
const maxRetries = config.retries
```

---

## 5. Control Flow Standards

### 5.1 Early Returns

**Rule: Avoid `else` statements, use early returns**

```typescript
// ✅ GOOD - Early returns
function getStatus(session: Session) {
  if (!session) return "not_found"
  if (session.busy) return "busy"
  if (session.error) return "error"
  return "ready"
}

async function process(id: string) {
  const session = await Session.get(id)
  if (!session) return { error: "Not found" }

  const result = await execute(session)
  if (!result.success) return { error: result.message }

  return { data: result.data }
}

// ❌ BAD - Else statements
function getStatus(session: Session) {
  if (!session) {
    return "not_found"
  } else {
    if (session.busy) {
      return "busy"
    } else {
      if (session.error) {
        return "error"
      } else {
        return "ready"
      }
    }
  }
}
```

**File Reference:** `AGENTS.md:70-86`

### 5.2 Guard Clauses

```typescript
// ✅ GOOD - Guard clauses at function start
async function updateSession(id: string, data: UpdateData) {
  if (!id) throw new Error("ID required")
  if (!data) throw new Error("Data required")
  if (data.title && data.title.length > 100) throw new Error("Title too long")

  // Main logic here
  const session = await Session.get(id)
  await Session.update(id, data)
  return session
}
```

### 5.3 Switch Statements

**Rule: Use exhaustive switch with default case**

```typescript
// ✅ GOOD - Exhaustive switch for stream handling
for await (const value of stream.fullStream) {
  switch (value.type) {
    case "reasoning-start":
      reasoningMap[value.id] = { id: generateId(), type: "reasoning", text: "" }
      break

    case "reasoning-delta":
      if (value.id in reasoningMap) {
        reasoningMap[value.id].text += value.text
        await Session.updatePart({ delta: value.text })
      }
      break

    case "tool-call":
      await Session.updatePart({ state: { status: "running" } })
      break

    case "tool-result":
      await Session.updatePart({ state: { status: "completed" } })
      break

    default:
      const _exhaustive: never = value
      throw new Error(`Unhandled type: ${(value as any).type}`)
  }
}
```

**File Reference:** `packages/opencode/src/session/processor.ts:53-346`

---

## 6. Async & Concurrency Standards

### 6.1 Parallel Execution (Default Pattern)

**Rule: Use `Promise.all` for independent operations**

```typescript
// ✅ GOOD - Parallel independent operations
const [language, cfg, provider, auth] = await Promise.all([
  Provider.getLanguage(input.model),
  Config.get(),
  Provider.getProvider(input.model.providerID),
  Auth.get(input.model.providerID),
])

// ✅ GOOD - Parallel array processing
const results = await Promise.all(
  items.map(async (item) => {
    return processItem(item)
  }),
)

// ❌ BAD - Sequential when independent
const language = await Provider.getLanguage(input.model)
const cfg = await Config.get() // Could run in parallel!
const provider = await Provider.getProvider(input.model.providerID)
const auth = await Auth.get(input.model.providerID)
```

**File Reference:** `packages/opencode/src/session/llm.ts:59`

### 6.2 Sequential Operations

**Rule: Chain when operations depend on previous results**

```typescript
// ✅ GOOD - Sequential dependency chain
const session = await Session.create({ title: "New" })
const message = await Session.addMessage(session.id, { content: "Hello" })
const response = await LLM.stream({ sessionID: session.id, messageID: message.id })

// ✅ GOOD - Promise chain for clarity
const result = await Session.create({ title: "New" })
  .then((session) => Session.addMessage(session.id, { content: "Hello" }))
  .then((message) => LLM.stream({ messageID: message.id }))
```

### 6.3 Error Handling in Async

**Rule: Prefer `.catch()` over try/catch when possible**

```typescript
// ✅ GOOD - Catch at call site
const result = await operation().catch((error) => {
  log.error("Operation failed", { error })
  return defaultValue
})

// ✅ GOOD - Promise.all with error handling
const results = await Promise.all(
  items.map(async (item) => {
    return processItem(item).catch((error) => {
      log.error("Item failed", { item, error })
      return null
    })
  }),
)

// ✅ ACCEPTABLE - try/catch for multiple operations
try {
  const session = await Session.create(input)
  await Session.addMessage(session.id, message)
  await EventBus.publish(Session.Event.Created, { session })
  return session
} catch (error) {
  log.error("Session creation failed", { error })
  throw error
}

// ❌ AVOID - try/catch for single operation
try {
  const result = await operation()
  return result
} catch (error) {
  log.error(error)
  throw error
}
// Better:
const result = await operation().catch((error) => {
  log.error(error)
  throw error
})
```

### 6.4 Concurrent Worker Pattern

```typescript
// ✅ GOOD - Controlled concurrency
export async function work<T>(concurrency: number, items: T[], fn: (item: T) => Promise<void>) {
  const pending = [...items]

  await Promise.all(
    Array.from({ length: concurrency }, async () => {
      while (true) {
        const item = pending.pop()
        if (item === undefined) return
        await fn(item)
      }
    }),
  )
}

// Usage
await work(3, files, async (file) => {
  await processFile(file)
})
```

**File Reference:** `packages/opencode/src/util/queue.ts:21-32`

---

## 7. Race Condition Prevention

### 7.1 Reader-Writer Lock (Storage Operations)

```typescript
// packages/opencode/src/util/lock.ts
export namespace Lock {
  const locks = new Map<
    string,
    {
      readers: number
      writer: boolean
      waitingReaders: (() => void)[]
      waitingWriters: (() => void)[]
    }
  >()

  export async function read(key: string): Promise<Disposable> {
    const lock = get(key)
    return new Promise((resolve) => {
      // Writers get priority
      if (!lock.writer && lock.waitingWriters.length === 0) {
        lock.readers++
        resolve({
          [Symbol.dispose]: () => {
            lock.readers--
            process(key)
          },
        })
      } else {
        lock.waitingReaders.push(() => {
          lock.readers++
          resolve({
            [Symbol.dispose]: () => {
              lock.readers--
              process(key)
            },
          })
        })
      }
    })
  }

  export async function write(key: string): Promise<Disposable> {
    const lock = get(key)
    return new Promise((resolve) => {
      if (!lock.writer && lock.readers === 0) {
        lock.writer = true
        resolve({
          [Symbol.dispose]: () => {
            lock.writer = false
            process(key)
          },
        })
      } else {
        lock.waitingWriters.push(() => {
          lock.writer = true
          resolve({
            [Symbol.dispose]: () => {
              lock.writer = false
              process(key)
            },
          })
        })
      }
    })
  }
}

// Usage with disposable pattern
export async function read<T>(target: string): Promise<T> {
  using _ = await Lock.read(target) // Auto-releases on scope exit
  const file = Bun.file(target)
  if (!(await file.exists())) {
    throw NotFoundError.create({ message: `File not found: ${target}` })
  }
  return file.json()
}

export async function write(target: string, data: any): Promise<void> {
  using _ = await Lock.write(target) // Auto-releases on scope exit
  const dir = path.dirname(target)
  await fs.mkdir(dir, { recursive: true })
  await Bun.write(target, JSON.stringify(data, null, 2))
}
```

**File References:**

- `packages/opencode/src/util/lock.ts` - Lines 1-99
- `packages/opencode/src/storage/storage.ts` - Lines 173, 183, 195

### 7.2 File-Level Lock (Edit Operations)

```typescript
// packages/opencode/src/file/time.ts
export async function withLock<T>(filepath: string, fn: () => Promise<T>): Promise<T> {
  const current = state()
  const currentLock = current.locks.get(filepath) ?? Promise.resolve()

  let release: () => void = () => {}
  const nextLock = new Promise<void>((resolve) => {
    release = resolve
  })

  const chained = currentLock.then(() => nextLock)
  current.locks.set(filepath, chained)

  await currentLock // Wait for previous lock

  try {
    return await fn()
  } finally {
    release()
    if (current.locks.get(filepath) === chained) {
      current.locks.delete(filepath)
    }
  }
}

// Usage: Atomic read-modify-write
await FileTime.withLock(filePath, async () => {
  const contentOld = await Bun.file(filePath).text()
  const contentNew = replace(contentOld, params.oldString, params.newString)
  await Bun.write(filePath, contentNew)
  await FileTime.touch(filePath)
})
```

**File References:**

- `packages/opencode/src/file/time.ts` - Lines 35-53
- `packages/opencode/src/tool/edit.ts` - Line 50

### 7.3 Mutex Pattern

```typescript
// ✅ GOOD - Simple mutex for critical sections
const mutexes = new Map<string, Promise<void>>()

async function withMutex<T>(key: string, fn: () => Promise<T>): Promise<T> {
  const current = mutexes.get(key) ?? Promise.resolve()

  let release: () => void
  const next = new Promise<void>((resolve) => {
    release = resolve
  })
  mutexes.set(key, next)

  await current

  try {
    return await fn()
  } finally {
    release!()
    if (mutexes.get(key) === next) {
      mutexes.delete(key)
    }
  }
}

// Usage
await withMutex(`session:${sessionID}`, async () => {
  // Critical section - only one execution at a time
  const session = await Session.get(sessionID)
  session.busy = true
  await Session.save(session)
})
```

### 7.4 Debouncing & Throttling

```typescript
// ✅ GOOD - Debounce for file watching
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
  let timer: Timer | undefined

  return (...args: Parameters<T>) => {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn(...args)
      timer = undefined
    }, delay)
  }
}

// Usage in file watcher
const debouncedUpdate = debounce((file: string) => {
  Bus.publish(FileWatcher.Event.Updated, { file })
}, 100)

watcher.on("change", (file) => {
  debouncedUpdate(file)
})
```

---

## 8. AI System Integration Standards

### 8.1 Message Context Management

```typescript
// packages/opencode/src/session/message-v2.ts

export type Part = TextPart | FilePart | PatchPart | ToolCallPart | ToolResultPart | ReasoningPart

export interface WithParts {
  info: Info
  parts: Part[]
}

// Convert internal message format to LLM format
export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] {
  return input.flatMap((msg): ModelMessage[] => {
    if (msg.info.role === "user") {
      return [
        {
          role: "user",
          content: msg.parts.map((part) => {
            if (part.type === "text") {
              return { type: "text", text: part.text }
            }
            if (part.type === "file") {
              return {
                type: "file",
                data: part.data,
                mimeType: part.mimeType,
              }
            }
            if (part.type === "patch") {
              return {
                type: "text",
                text: formatPatch(part),
              }
            }
            // ... handle other types
          }),
        },
      ]
    }

    if (msg.info.role === "assistant") {
      return [
        {
          role: "assistant",
          content: msg.parts
            .filter((p) => p.type === "text" || p.type === "reasoning")
            .map((p) => ({ type: "text", text: p.text })),
        },
      ]
    }

    // Handle tool results...
    return []
  })
}
```

**File Reference:** `packages/opencode/src/session/message-v2.ts:438-500`

### 8.2 System Prompt Construction

```typescript
// packages/opencode/src/session/llm.ts

export async function stream(input: StreamInput) {
  const system: string[] = []

  // Part 1: Base system prompt (cached)
  const header = [
    // Agent prompt OR provider prompt
    ...(input.agent.prompt ? [input.agent.prompt] : isCodex ? [] : SystemPrompt.provider(input.model)),
    // Custom prompt from request
    ...input.system,
    // Custom prompt from user config
    ...(input.user.system ? [input.user.system] : []),
  ]
    .filter(Boolean)
    .join("\n")

  system.push(header)

  // Part 2: Dynamic context (not cached)
  const dynamicContext = [
    `Working directory: ${Instance.directory}`,
    `Current date: ${new Date().toISOString()}`,
    // ... other dynamic info
  ].join("\n")

  system.push(dynamicContext)

  // Allow plugins to transform system prompt
  const original = clone(system)
  await Plugin.trigger(
    "experimental.chat.system.transform",
    {
      model: input.model,
      agent: input.agent,
    },
    { system },
  )

  // Optimize for prompt caching: maintain 2-part structure if header unchanged
  if (system.length > 2 && system[0] === header) {
    const rest = system.slice(1)
    system.length = 0
    system.push(header, rest.join("\n"))
  }

  // Convert to LLM messages
  const messages = [
    ...system.map((text) => ({ role: "system", content: text })),
    ...toModelMessages(input.messages, input.model),
  ]

  return language.doStream({
    model: input.model.id,
    messages,
    tools: await resolveTools(input),
  })
}
```

**File Reference:** `packages/opencode/src/session/llm.ts:67-97`

### 8.3 Tool Definition & Execution

```typescript
// packages/opencode/src/tool/tool.ts

export namespace Tool {
  export interface Info<Parameters = any, Metadata = any> {
    id: string
    title: string
    description: string
    parameters: z.ZodType<Parameters>
    metadata?: Metadata

    execute(args: Parameters, context: Context): Promise<ExecuteResult>
  }

  export interface Context {
    sessionID: string
    messageID: string
    agent: string
    abort: AbortSignal
    messages: () => Promise<MessageV2.WithParts[]>
    metadata: <T>(key: string, value: T) => Promise<void>
    ask: <T>(question: Question<T>) => Promise<T>
  }

  export type ExecuteResult = {
    output?: string
    title?: string
    metadata?: Record<string, unknown>
  }

  export function define<Parameters, Result>(
    id: string,
    init: {
      title: string
      description: string
      parameters: z.ZodType<Parameters>
      execute: (args: Parameters, context: Context) => Promise<ExecuteResult>
    },
  ): Info<Parameters> {
    return {
      id,
      ...init,
    }
  }
}

// Usage: Define a tool
export const ReadTool = Tool.define("read", {
  title: "Read File",
  description: "Read contents of a file from the filesystem",
  parameters: z.object({
    filePath: z.string().describe("Absolute path to file"),
    offset: z.number().optional().describe("Line number to start reading"),
    limit: z.number().optional().describe("Number of lines to read"),
  }),

  async execute(args, context) {
    await context.ask({
      type: "permission",
      message: `Read file ${args.filePath}?`,
      actions: ["allow", "deny"],
    })

    const content = await Bun.file(args.filePath).text()
    const lines = content.split("\n")

    const start = args.offset ?? 0
    const end = args.limit ? start + args.limit : lines.length
    const selected = lines.slice(start, end)

    return {
      output: selected.join("\n"),
      title: `Read ${path.basename(args.filePath)}`,
      metadata: {
        lineCount: selected.length,
        filePath: args.filePath,
      },
    }
  },
})

// Register tool
await ToolRegistry.register(ReadTool)

// Tool execution in session
const tools = await ToolRegistry.tools({ agent: input.agent })

const toolMap = tools.reduce(
  (acc, tool) => {
    acc[tool.id] = vercelAiTool({
      description: tool.description,
      parameters: zodToJsonSchema(tool.parameters),
      execute: async (args) => {
        const result = await tool.execute(args, {
          sessionID: input.sessionID,
          messageID: input.messageID,
          agent: input.agent.name,
          abort: input.abort,
          messages: () => Session.messages({ sessionID: input.sessionID }),
          metadata: async (key, value) => {
            await Session.updatePart({
              messageID: input.messageID,
              metadata: { [key]: value },
            })
          },
          ask: async (question) => {
            return new Promise((resolve) => {
              Bus.publish(Session.Event.Question, {
                sessionID: input.sessionID,
                question,
                respond: resolve,
              })
            })
          },
        })

        return result.output
      },
    })
    return acc
  },
  {} as Record<string, any>,
)
```

**File References:**

- `packages/opencode/src/tool/tool.ts` - Lines 1-90
- `packages/opencode/src/session/prompt.ts` - Lines 711-748

### 8.4 Streaming Handling

```typescript
// packages/opencode/src/session/processor.ts

export function create(input: CreateInput) {
  const reasoningMap: Record<string, ReasoningPart> = {}
  const toolcalls: Record<string, ToolCallPart> = {}

  return {
    async process() {
      const stream = await LLM.stream({
        sessionID: input.sessionID,
        messageID: input.messageID,
        model: input.model,
        messages: input.messages,
        tools: input.tools,
        abort: input.abort,
      })

      // Handle stream events
      for await (const value of stream.fullStream) {
        input.abort.throwIfAborted()

        switch (value.type) {
          case "reasoning-start":
            reasoningMap[value.id] = {
              id: Identifier.ascending("part"),
              type: "reasoning",
              text: "",
              time: { start: Date.now() },
            }
            await Session.addPart({
              messageID: input.messageID,
              part: reasoningMap[value.id],
            })
            break

          case "reasoning-delta":
            if (value.id in reasoningMap) {
              const part = reasoningMap[value.id]
              part.text += value.text
              await Session.updatePart({
                messageID: input.messageID,
                partID: part.id,
                delta: value.text,
              })
            }
            break

          case "reasoning-finish":
            if (value.id in reasoningMap) {
              const part = reasoningMap[value.id]
              part.time.end = Date.now()
              await Session.updatePart({
                messageID: input.messageID,
                partID: part.id,
                part,
              })
            }
            break

          case "text-delta":
            // Handle text streaming...
            break

          case "tool-call":
            const toolPart: ToolCallPart = {
              id: Identifier.ascending("part"),
              type: "tool-call",
              toolCallID: value.toolCallId,
              toolName: value.toolName,
              state: {
                status: "running",
                input: value.args,
                time: { start: Date.now() },
              },
            }
            toolcalls[value.toolCallId] = toolPart
            await Session.addPart({
              messageID: input.messageID,
              part: toolPart,
            })
            break

          case "tool-result":
            const match = toolcalls[value.toolCallId]
            if (match) {
              match.state = {
                status: "completed",
                output: value.result,
                time: {
                  start: match.state.time.start,
                  end: Date.now(),
                },
              }
              await Session.updatePart({
                messageID: input.messageID,
                partID: match.id,
                part: match,
              })
            }
            break

          case "error":
            await Session.updateMessage({
              messageID: input.messageID,
              error: {
                message: value.error.message,
                code: value.error.code,
              },
            })
            throw value.error

          case "finish":
            await Session.updateMessage({
              messageID: input.messageID,
              tokens: value.usage,
              time: { end: Date.now() },
            })
            break

          default:
            // Exhaustiveness check
            const _exhaustive: never = value
            throw new Error(`Unhandled stream type: ${(value as any).type}`)
        }
      }
    },
  }
}
```

**File Reference:** `packages/opencode/src/session/processor.ts:53-346`

### 8.5 Context Compaction

```typescript
// packages/opencode/src/session/compaction.ts

export async function isOverflow(input: {
  tokens: MessageV2.Assistant["tokens"]
  model: Provider.Model
}): Promise<boolean> {
  const limit = input.model.limit.context

  const total = input.tokens.input + input.tokens.output + input.tokens.cache.read + input.tokens.cache.write

  const threshold = limit * 0.75 // Trigger at 75% capacity

  return total > threshold
}

export async function compact(sessionID: string) {
  const messages = await Session.messages({ sessionID })
  const model = await Session.getModel(sessionID)

  // Find oldest non-system messages
  const candidates = messages.filter((m) => m.info.role !== "system")

  if (candidates.length <= 2) {
    // Keep at least 2 messages for context
    return
  }

  // Remove oldest message
  const toRemove = candidates[0]
  await Session.deleteMessage(toRemove.info.id)

  // Add summary if necessary
  const summary = await generateSummary(toRemove)
  await Session.addSystemMessage(sessionID, {
    content: `Previous context (summarized): ${summary}`,
  })

  log.info("compacted session", {
    sessionID,
    removedMessageID: toRemove.info.id,
    remainingMessages: messages.length - 1,
  })
}

// Check after each assistant response
await Session.onMessageComplete(async (messageID) => {
  const message = await Session.getMessage(messageID)
  const model = await Session.getModel(message.sessionID)

  if (await isOverflow({ tokens: message.tokens, model })) {
    await compact(message.sessionID)
  }
})
```

**File References:**

- `packages/opencode/src/session/compaction.ts` - Lines 30-48
- `packages/opencode/src/session/processor.ts` - Lines 282-284

---

## 9. Service Architecture Standards

### 9.1 Core Services Overview

| Service                 | Location                   | Pattern                | Responsibility              |
| ----------------------- | -------------------------- | ---------------------- | --------------------------- |
| **Session Management**  | `src/session/`             | Namespace + State      | AI conversation lifecycle   |
| **File Watching**       | `src/file/watcher.ts`      | Chokidar + Debounce    | Filesystem change detection |
| **LSP Integration**     | `src/lsp/`                 | Multi-client manager   | Language server protocol    |
| **MCP**                 | `src/mcp/`                 | Multi-transport client | Model Context Protocol      |
| **Terminal/PTY**        | `src/server/routes/pty.ts` | WebSocket-based        | Shell session management    |
| **Event Bus**           | `src/bus/`                 | Pub/sub pattern        | Cross-service communication |
| **Storage**             | `src/storage/`             | Lock-based persistence | JSON file storage           |
| **Provider Management** | `src/provider/`            | Lazy-loaded SDKs       | LLM provider abstraction    |
| **Config Management**   | `src/config/`              | Layered merge          | Multi-level configuration   |
| **Scheduler**           | `src/scheduler/`           | Interval-based         | Background job execution    |

### 9.2 Service Implementation Template

```typescript
// Service template following codebase patterns

import { Log } from "../util/log"
import { Instance } from "../project/instance"
import { BusEvent } from "../bus/event"
import z from "zod"

export namespace MyService {
  const log = Log.create({ service: "my-service" })

  // 1. Type definitions
  export type Info = z.infer<typeof Info>
  export const Info = z.object({
    id: z.string(),
    status: z.enum(["idle", "active", "error"]),
    created: z.number(),
  })

  // 2. Per-instance state
  const state = Instance.state(
    async () => {
      log.info("initializing service")

      return {
        items: new Map<string, Info>(),
        timers: new Map<string, Timer>(),
        clients: [] as Client[],
      }
    },
    async (state) => {
      log.info("disposing service")

      // Cleanup resources
      await Promise.all([
        ...state.clients.map((c) => c.close()),
        ...Array.from(state.timers.values()).map((t) => clearInterval(t)),
      ])

      state.items.clear()
    },
  )

  // 3. Event definitions
  export const Event = {
    Created: BusEvent.define(
      "my-service.created",
      z.object({
        info: Info,
      }),
    ),
    Updated: BusEvent.define(
      "my-service.updated",
      z.object({
        info: Info,
      }),
    ),
    Deleted: BusEvent.define(
      "my-service.deleted",
      z.object({
        id: z.string(),
      }),
    ),
  }

  // 4. Public API with fn() wrapper
  export const create = fn(
    z
      .object({
        id: z.string().optional(),
        data: z.record(z.unknown()).optional(),
      })
      .optional(),
    async (input) => {
      const id = input?.id ?? Identifier.ascending("my-service")

      const info: Info = {
        id,
        status: "idle",
        created: Date.now(),
      }

      state().items.set(id, info)
      await Bus.publish(Event.Created, { info })

      log.info("created", { id })
      return info
    },
  )

  export const get = fn(z.string(), async (id) => {
    const item = state().items.get(id)
    if (!item) {
      throw NotFoundError.create({ message: `Item ${id} not found` })
    }
    return item
  })

  export const list = fn(
    z
      .object({
        status: z.enum(["idle", "active", "error"]).optional(),
      })
      .optional(),
    async (input) => {
      const items = Array.from(state().items.values())

      if (input?.status) {
        return items.filter((item) => item.status === input.status)
      }

      return items
    },
  )

  // 5. Internal functions (not exported)
  async function cleanup(id: string) {
    const timer = state().timers.get(id)
    if (timer) {
      clearInterval(timer)
      state().timers.delete(id)
    }
  }
}
```

**File References:**

- `packages/opencode/src/session/index.ts` - Complete example
- `packages/opencode/src/mcp/index.ts` - Multi-client manager
- `packages/opencode/src/lsp/index.ts` - Lazy spawning

---

## 10. State Management Standards

### 10.1 Per-Instance State Isolation

**Rule: All state must be isolated per working directory**

```typescript
// packages/opencode/src/project/instance.ts

export namespace Instance {
  // Current working directory
  export let directory = process.cwd()

  // Git worktree root
  export let worktree = directory

  // Project ID
  export let id = "unknown"

  // Create state scoped to current instance
  export function state<S>(
    init: () => S | Promise<S>,
    dispose?: (state: Awaited<S>) => Promise<void>,
  ): () => Awaited<S> {
    return State.create(
      () => Instance.directory, // Key by directory
      init,
      dispose,
    )
  }
}

// packages/opencode/src/project/state.ts

const recordsByKey = new Map<string, Map<Function, Entry>>()

interface Entry {
  state: any
  dispose?: (state: any) => Promise<void>
}

export function create<S>(
  root: () => string,
  init: () => S | Promise<S>,
  dispose?: (state: Awaited<S>) => Promise<void>,
): () => Awaited<S> {
  return () => {
    const key = root()

    // Get or create entries for this key
    let entries = recordsByKey.get(key)
    if (!entries) {
      entries = new Map<Function, Entry>()
      recordsByKey.set(key, entries)
    }

    // Get or initialize state
    const exists = entries.get(init)
    if (exists) {
      return exists.state as Awaited<S>
    }

    const state = init()
    entries.set(init, { state, dispose })

    return state as Awaited<S>
  }
}

// Dispose all state for a key
export async function dispose(key: string) {
  const entries = recordsByKey.get(key)
  if (!entries) return

  await Promise.all(
    Array.from(entries.values()).map(async (entry) => {
      if (entry.dispose) {
        const state = await entry.state
        await entry.dispose(state)
      }
    }),
  )

  recordsByKey.delete(key)
}
```

**File References:**

- `packages/opencode/src/project/instance.ts` - Lines 66-68
- `packages/opencode/src/project/state.ts` - Lines 12-29

### 10.2 State Initialization Pattern

```typescript
// ✅ GOOD - Lazy initialization with disposal
const state = Instance.state(
  async () => {
    log.info("initializing LSP clients")

    const cfg = await Config.get()
    const servers: Record<string, LSPServer.Info> = {}
    const clients: LSPClient.Info[] = []

    // Initialize servers
    for (const [id, server] of Object.entries(LSPServer)) {
      if (cfg.lsp?.[id]?.enabled !== false) {
        servers[id] = server
      }
    }

    return {
      servers,
      clients,
      broken: new Set<string>(),
      spawning: new Map<string, Promise<LSPClient.Info | undefined>>(),
    }
  },
  async (state) => {
    log.info("disposing LSP clients")

    // Cleanup all clients
    await Promise.all(
      state.clients.map(async (client) => {
        try {
          await client.shutdown()
        } catch (error) {
          log.error("failed to shutdown client", { client: client.id, error })
        }
      }),
    )
  },
)

// Access state
const lspState = state()
lspState.clients.push(newClient)
```

### 10.3 Global State (Rare)

**Rule: Avoid global state, use per-instance state instead**

```typescript
// ❌ AVOID - Global mutable state
const sessions = new Map<string, Session>()

export function addSession(session: Session) {
  sessions.set(session.id, session)
}

// ✅ PREFER - Per-instance state
const state = Instance.state(async () => ({
  sessions: new Map<string, Session>(),
}))

export function addSession(session: Session) {
  state().sessions.set(session.id, session)
}

// ✅ ACCEPTABLE - Truly global state (user config, auth tokens)
export namespace Global {
  export namespace Path {
    export const home = os.homedir()
    export const config = path.join(home, ".config", "opencode")
    export const data = path.join(home, ".local", "share", "opencode")
    export const cache = path.join(home, ".cache", "opencode")
  }
}
```

---

## 11. Event Bus Standards

### 11.1 Event Definition

```typescript
// packages/opencode/src/bus/event.ts

export namespace BusEvent {
  export interface Definition<Properties extends z.ZodType = any> {
    type: string
    properties: Properties
  }

  export function define<Properties extends z.ZodType>(type: string, properties: Properties): Definition<Properties> {
    return { type, properties }
  }
}

// Usage: Define events for a service
export namespace Session {
  export const Event = {
    Created: BusEvent.define("session.created", z.object({ info: Info })),

    Updated: BusEvent.define("session.updated", z.object({ info: Info, changes: z.record(z.unknown()) })),

    Deleted: BusEvent.define("session.deleted", z.object({ id: z.string() })),

    Diff: BusEvent.define(
      "session.diff",
      z.object({
        sessionID: z.string(),
        messageID: z.string(),
        diff: z.object({
          file: z.string(),
          additions: z.number(),
          deletions: z.number(),
        }),
      }),
    ),

    Error: BusEvent.define(
      "session.error",
      z.object({
        sessionID: z.string(),
        error: z.object({
          message: z.string(),
          code: z.string().optional(),
        }),
      }),
    ),

    Question: BusEvent.define(
      "session.question",
      z.object({
        sessionID: z.string(),
        question: z.object({
          type: z.string(),
          message: z.string(),
          options: z.array(z.unknown()),
        }),
        respond: z.function(),
      }),
    ),
  }
}
```

### 11.2 Publishing Events

```typescript
// packages/opencode/src/bus/index.ts

export namespace Bus {
  const state = Instance.state(async () => ({
    subscriptions: new Map<string, Set<Handler>>(),
  }))

  type Handler = (payload: EventPayload) => void | Promise<void>

  interface EventPayload {
    type: string
    properties: any
  }

  export async function publish<Definition extends BusEvent.Definition>(
    def: Definition,
    properties: z.output<Definition["properties"]>,
  ): Promise<void> {
    const payload: EventPayload = {
      type: def.type,
      properties,
    }

    const pending: Promise<void>[] = []

    // Notify specific subscribers
    const specific = state().subscriptions.get(def.type)
    if (specific) {
      for (const handler of specific) {
        pending.push(Promise.resolve(handler(payload)))
      }
    }

    // Notify wildcard subscribers
    const wildcard = state().subscriptions.get("*")
    if (wildcard) {
      for (const handler of wildcard) {
        pending.push(Promise.resolve(handler(payload)))
      }
    }

    // Also publish to global bus (for cross-instance communication)
    GlobalBus.emit("event", {
      directory: Instance.directory,
      payload,
    })

    await Promise.all(pending)
  }
}

// Usage: Publish an event
await Bus.publish(Session.Event.Created, {
  info: {
    id: "session-123",
    title: "New Session",
    created: Date.now(),
  },
})

await Bus.publish(Session.Event.Updated, {
  info: updatedSession,
  changes: { title: "Updated Title" },
})
```

**File Reference:** `packages/opencode/src/bus/index.ts:41-64`

### 11.3 Subscribing to Events

```typescript
// packages/opencode/src/bus/index.ts

export namespace Bus {
  export function subscribe<Definition extends BusEvent.Definition>(
    def: Definition | "*",
    handler: (payload: { type: string; properties: z.output<Definition["properties"]> }) => void | Promise<void>,
  ): Disposable {
    const key = typeof def === "string" ? def : def.type

    let subs = state().subscriptions.get(key)
    if (!subs) {
      subs = new Set()
      state().subscriptions.set(key, subs)
    }

    subs.add(handler)

    return {
      [Symbol.dispose]: () => {
        subs?.delete(handler)
        if (subs?.size === 0) {
          state().subscriptions.delete(key)
        }
      },
    }
  }
}

// Usage: Subscribe to specific events
const subscription = Bus.subscribe(Session.Event.Created, async (event) => {
  log.info("session created", { id: event.properties.info.id })
  await notifyUser(`New session: ${event.properties.info.title}`)
})

// Unsubscribe
subscription[Symbol.dispose]()

// Or use disposable pattern
{
  using sub = Bus.subscribe(Session.Event.Updated, handleUpdate)

  // Automatically unsubscribes when scope exits
}

// Subscribe to all events
Bus.subscribe("*", (event) => {
  log.debug("event", { type: event.type, properties: event.properties })
})
```

### 11.4 Event-Driven Communication Pattern

```typescript
// Service A: Publish events
export namespace FileWatcher {
  export const Event = {
    Updated: BusEvent.define(
      "file.updated",
      z.object({
        file: z.string(),
        event: z.enum(["add", "change", "unlink"]),
      }),
    ),
  }

  async function watch(directory: string) {
    const watcher = chokidar.watch(directory)

    watcher.on("change", async (file) => {
      await Bus.publish(Event.Updated, {
        file,
        event: "change",
      })
    })
  }
}

// Service B: React to events
export namespace LSP {
  export async function initialize() {
    // Subscribe to file changes
    Bus.subscribe(FileWatcher.Event.Updated, async (event) => {
      if (event.properties.event === "change") {
        await notifyClientsOfChange(event.properties.file)
      }
    })
  }

  async function notifyClientsOfChange(file: string) {
    const clients = state().clients.filter((c) => c.watchesFile(file))
    await Promise.all(clients.map((c) => c.didChangeTextDocument({ uri: file })))
  }
}

// Service C: Also react to same events
export namespace Session {
  export async function initialize() {
    Bus.subscribe(FileWatcher.Event.Updated, async (event) => {
      // Invalidate cached file contents
      await invalidateFileCache(event.properties.file)
    })
  }
}
```

---

## 12. Configuration Management Standards

### 12.1 Configuration Precedence

**Order (Low → High priority):**

1. **Remote `.well-known/opencode`** - Organization defaults
2. **Global config** - `~/.config/opencode/opencode.json{,c}`
3. **Custom config** - `OPENCODE_CONFIG` env var path
4. **Project config** - `opencode.json{,c}` in project
5. **`.opencode` directories** - `.opencode/opencode.json{,c}`
6. **Inline config** - `OPENCODE_CONFIG_CONTENT` env var
7. **Managed config** - Enterprise `/etc/opencode` (highest priority)

```typescript
// packages/opencode/src/config/config.ts

export namespace Config {
  export const state = Instance.state(async () => {
    let result: Info = {}

    // 1. Remote organization config
    const auth = await Auth.all()
    for (const [key, value] of Object.entries(auth)) {
      if (value.type === "wellknown") {
        const response = await fetch(`${key}/.well-known/opencode`)
        const wellknown = await response.json()
        result = mergeConfigConcatArrays(result, wellknown.config ?? {})
      }
    }

    // 2. Global user config
    result = mergeConfigConcatArrays(result, await global())

    // 3. Custom config path
    if (Flag.OPENCODE_CONFIG) {
      result = mergeConfigConcatArrays(result, await loadFile(Flag.OPENCODE_CONFIG))
    }

    // 4. Project config (if not disabled)
    if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
      for (const file of ["opencode.jsonc", "opencode.json"]) {
        const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
        for (const resolved of found.toReversed()) {
          result = mergeConfigConcatArrays(result, await loadFile(resolved))
        }
      }
    }

    // 5. .opencode directories
    const directories = [
      Global.Path.config,
      ...(!Flag.OPENCODE_DISABLE_PROJECT_CONFIG
        ? await Array.fromAsync(
            Filesystem.up({
              targets: [".opencode"],
              start: Instance.directory,
              stop: Instance.worktree,
            }),
          )
        : []),
      // Always scan user home ~/.opencode/
      ...(await Array.fromAsync(
        Filesystem.up({
          targets: [".opencode"],
          start: Global.Path.home,
          stop: Global.Path.home,
        }),
      )),
    ]

    for (const dir of unique(directories)) {
      for (const file of ["opencode.jsonc", "opencode.json"]) {
        const configPath = path.join(dir, file)
        if (await Filesystem.exists(configPath)) {
          result = mergeConfigConcatArrays(result, await loadFile(configPath))
        }
      }
    }

    // 6. Inline config
    if (Flag.OPENCODE_CONFIG_CONTENT) {
      result = mergeConfigConcatArrays(result, await load(Flag.OPENCODE_CONFIG_CONTENT, "inline"))
    }

    // 7. Managed config (enterprise, highest priority)
    const managedPath = path.join(managedConfigDir, "opencode.json")
    if (await Filesystem.exists(managedPath)) {
      result = mergeConfigConcatArrays(result, await loadFile(managedPath))
    }

    return result
  })

  export const get = fn(z.void(), async () => state())
}
```

**File Reference:** `packages/opencode/src/config/config.ts:62-150`

### 12.2 Configuration Merge Strategy

```typescript
// packages/opencode/src/config/config.ts

function mergeConfigConcatArrays(target: Info, source: Info): Info {
  // Deep merge objects
  const merged = mergeDeep(target, source)

  // Special handling: Concatenate arrays instead of replacing
  if (target.plugin && source.plugin) {
    merged.plugin = Array.from(new Set([...target.plugin, ...source.plugin]))
  }

  if (target.instructions && source.instructions) {
    merged.instructions = Array.from(new Set([...target.instructions, ...source.instructions]))
  }

  return merged
}

// Example:
// Base config:    { plugin: ["a", "b"], model: { id: "gpt-4" } }
// Override config: { plugin: ["b", "c"], model: { temperature: 0.7 } }
// Result:         { plugin: ["a", "b", "c"], model: { id: "gpt-4", temperature: 0.7 } }
```

**File Reference:** `packages/opencode/src/config/config.ts:51-60`

### 12.3 Configuration Schema

```typescript
export namespace Config {
  export type Info = z.infer<typeof Info>
  export const Info = z.object({
    // Model configuration
    model: z
      .object({
        id: z.string().optional(),
        providerID: z.string().optional(),
        temperature: z.number().optional(),
        maxTokens: z.number().optional(),
      })
      .optional(),

    // Providers
    providers: z
      .record(
        z.object({
          apiKey: z.string().optional(),
          baseURL: z.string().optional(),
          enabled: z.boolean().optional(),
        }),
      )
      .optional(),

    // Plugins
    plugin: z.array(z.string()).optional(),

    // Instructions (system prompts)
    instructions: z.array(z.string()).optional(),

    // Agents
    agent: z
      .record(
        z.object({
          mode: z.enum(["primary", "secondary"]).optional(),
          prompt: z.string().optional(),
          permission: z.string().optional(),
        }),
      )
      .optional(),

    // LSP servers
    lsp: z
      .record(
        z.object({
          enabled: z.boolean().optional(),
          command: z.string().optional(),
          args: z.array(z.string()).optional(),
        }),
      )
      .optional(),

    // MCP servers
    mcp: z
      .record(
        z.union([
          z.object({
            command: z.string(),
            args: z.array(z.string()).optional(),
            env: z.record(z.string()).optional(),
            enabled: z.boolean().optional(),
          }),
          z.object({
            url: z.string(),
            transport: z.literal("sse"),
            enabled: z.boolean().optional(),
          }),
        ]),
      )
      .optional(),

    // Formatters
    formatter: z
      .record(
        z.object({
          command: z.string(),
          args: z.array(z.string()).optional(),
        }),
      )
      .optional(),
  })
}
```

### 12.4 Configuration File Loading

```typescript
export namespace Config {
  async function loadFile(filepath: string): Promise<Info> {
    if (!(await Filesystem.exists(filepath))) {
      return {}
    }

    const content = await Bun.file(filepath).text()
    return load(content, filepath)
  }

  async function load(content: string, source: string): Promise<Info> {
    try {
      // Parse JSONC (JSON with comments)
      const parsed = parseJsonc(content)

      // Validate against schema
      const validated = Info.parse(parsed)

      return validated
    } catch (error) {
      if (error instanceof z.ZodError) {
        log.error("config validation failed", {
          source,
          errors: error.errors,
        })
        throw new Error(`Invalid config at ${source}: ${error.errors[0].message}`)
      }

      throw error
    }
  }
}
```

---

## 13. Storage & Persistence Standards

### 13.1 Storage Operations

```typescript
// packages/opencode/src/storage/storage.ts

export namespace Storage {
  const log = Log.create({ service: "storage" })

  export const NotFoundError = NamedError.create("NotFoundError", z.object({ message: z.string() }))

  // Read with lock
  export async function read<T>(target: string): Promise<T> {
    using _ = await Lock.read(target)

    const file = Bun.file(target)
    if (!(await file.exists())) {
      throw NotFoundError.create({
        message: `File not found: ${target}`,
      })
    }

    return file.json()
  }

  // Write with lock
  export async function write(target: string, data: any): Promise<void> {
    using _ = await Lock.write(target)

    const dir = path.dirname(target)
    await fs.mkdir(dir, { recursive: true })

    await Bun.write(target, JSON.stringify(data, null, 2))
  }

  // Update (read-modify-write)
  export async function update<T>(target: string, editor: (draft: T) => void | Promise<void>): Promise<T> {
    using _ = await Lock.write(target)

    const data = await read<T>(target)
    await editor(data)
    await write(target, data)

    return data
  }

  // Delete
  export async function remove(target: string): Promise<void> {
    using _ = await Lock.write(target)

    if (await Filesystem.exists(target)) {
      await fs.rm(target, { recursive: true })
    }
  }

  // List files matching pattern
  export async function list(directory: string, pattern: string): Promise<string[]> {
    using _ = await Lock.read(directory)

    const files: string[] = []
    for await (const file of new Bun.Glob(pattern).scan({ cwd: directory })) {
      files.push(path.join(directory, file))
    }

    return files
  }
}
```

**File Reference:** `packages/opencode/src/storage/storage.ts:1-200`

### 13.2 Storage Paths

```typescript
export namespace Storage {
  export function path(...segments: string[]): string {
    return path.join(Global.Path.data, ...segments)
  }

  // Session storage
  export namespace Session {
    export function info(sessionID: string): string {
      return Storage.path("session", sessionID, "info.json")
    }

    export function messages(sessionID: string): string {
      return Storage.path("session", sessionID, "messages")
    }

    export function message(sessionID: string, messageID: string): string {
      return Storage.path("session", sessionID, "messages", `${messageID}.json`)
    }
  }

  // Project storage
  export namespace Project {
    export function info(projectID: string): string {
      return Storage.path("project", `${projectID}.json`)
    }

    export function list(): Promise<string[]> {
      return Storage.list(Storage.path("project"), "*.json")
    }
  }

  // Cache storage
  export namespace Cache {
    export function file(key: string): string {
      return Storage.path("cache", `${key}.json`)
    }
  }
}
```

### 13.3 Migration Pattern

```typescript
export namespace Storage {
  type Migration = (dir: string) => Promise<void>

  const MIGRATIONS: Migration[] = [
    // Migration 1: Rename old directories
    async (dir) => {
      const oldPath = path.join(dir, "old-structure")
      const newPath = path.join(dir, "new-structure")

      if (await Filesystem.exists(oldPath)) {
        await fs.rename(oldPath, newPath)
        log.info("migrated directory structure")
      }
    },

    // Migration 2: Transform data format
    async (dir) => {
      const files = await Storage.list(path.join(dir, "sessions"), "*.json")

      for (const file of files) {
        const data = await Storage.read<any>(file)

        // Add new field
        if (!data.version) {
          data.version = 2
          data.migrated = Date.now()
          await Storage.write(file, data)
        }
      }

      log.info("migrated session data", { count: files.length })
    },

    // Migration 3: Consolidate files
    async (dir) => {
      const oldDir = path.join(dir, "old")
      const newFile = path.join(dir, "consolidated.json")

      if (await Filesystem.exists(oldDir)) {
        const files = await Storage.list(oldDir, "*.json")
        const consolidated = await Promise.all(files.map((f) => Storage.read(f)))

        await Storage.write(newFile, consolidated)
        await fs.rm(oldDir, { recursive: true })

        log.info("consolidated files", { count: files.length })
      }
    },
  ]

  export async function migrate(): Promise<void> {
    const dataDir = Global.Path.data
    const versionFile = path.join(dataDir, ".version")

    let currentVersion = 0
    if (await Filesystem.exists(versionFile)) {
      currentVersion = Number(await Bun.file(versionFile).text())
    }

    const targetVersion = MIGRATIONS.length

    if (currentVersion >= targetVersion) {
      log.debug("storage up to date", { version: currentVersion })
      return
    }

    log.info("migrating storage", {
      from: currentVersion,
      to: targetVersion,
    })

    // Run pending migrations
    for (let i = currentVersion; i < targetVersion; i++) {
      log.info(`running migration ${i + 1}/${targetVersion}`)
      await MIGRATIONS[i](dataDir)
    }

    // Update version
    await Bun.write(versionFile, String(targetVersion))

    log.info("migration complete", { version: targetVersion })
  }
}
```

**File Reference:** `packages/opencode/src/storage/storage.ts:24-100`

---

## 14. Error Handling Standards

### 14.1 Named Errors (Preferred)

```typescript
// packages/opencode/src/util/error.ts

import { NamedError } from "@opencode-ai/util/error"
import z from "zod"

// Define typed errors
export const NotFoundError = NamedError.create(
  "NotFoundError",
  z.object({
    message: z.string(),
    resource: z.string().optional(),
    id: z.string().optional(),
  }),
)

export const ValidationError = NamedError.create(
  "ValidationError",
  z.object({
    message: z.string(),
    field: z.string().optional(),
    expected: z.string().optional(),
    received: z.string().optional(),
  }),
)

export const PermissionError = NamedError.create(
  "PermissionError",
  z.object({
    message: z.string(),
    action: z.string(),
    resource: z.string(),
  }),
)

// Usage: Throw typed error
throw NotFoundError.create({
  message: "Session not found",
  resource: "session",
  id: sessionID,
})

// Usage: Catch and check type
try {
  await operation()
} catch (error) {
  if (NotFoundError.is(error)) {
    log.warn("resource not found", {
      resource: error.data.resource,
      id: error.data.id,
    })
    return null
  }

  if (PermissionError.is(error)) {
    log.error("permission denied", {
      action: error.data.action,
      resource: error.data.resource,
    })
    throw error
  }

  // Unknown error
  log.error("unexpected error", { error })
  throw error
}
```

**File Reference:** `packages/opencode/src/storage/storage.ts:17-22`

### 14.2 Custom Error Classes (When Needed)

```typescript
// ✅ GOOD - Error with additional context
export class BusyError extends Error {
  constructor(public readonly sessionID: string) {
    super(`Session ${sessionID} is busy`)
    this.name = "BusyError"
  }
}

// Usage
throw new BusyError(sessionID)

// Catching
try {
  await process(sessionID)
} catch (error) {
  if (error instanceof BusyError) {
    log.info("session busy, retrying", { sessionID: error.sessionID })
    await retry()
  }
}
```

**File Reference:** `packages/opencode/src/session/index.ts:494`

### 14.3 Result Pattern (for Tools)

```typescript
// ✅ GOOD - Never throw in tool execution
export const ReadTool = Tool.define("read", {
  async execute(args, context) {
    try {
      const content = await Bun.file(args.filePath).text()

      return {
        output: content,
        title: `Read ${path.basename(args.filePath)}`,
      }
    } catch (error) {
      return {
        output: `Error reading file: ${error.message}`,
        title: "Read Failed",
        metadata: {
          error: true,
          message: error.message,
        },
      }
    }
  },
})

// ✅ GOOD - Result type for fallible operations
export type Result<T, E = Error> = { success: true; value: T } | { success: false; error: E }

export async function operation(): Promise<Result<Data>> {
  try {
    const data = await fetchData()
    return { success: true, value: data }
  } catch (error) {
    return { success: false, error }
  }
}

// Usage
const result = await operation()
if (result.success) {
  console.log(result.value)
} else {
  log.error("operation failed", { error: result.error })
}
```

### 14.4 Error Context

```typescript
// ✅ GOOD - Rich error context
class OperationError extends Error {
  constructor(
    message: string,
    public readonly context: {
      operation: string
      input: unknown
      timestamp: number
      sessionID?: string
    }
  ) {
    super(message)
    this.name = "OperationError"
  }
}

// Usage
try {
  await processData(input)
} catch (error) {
  throw new OperationError(
    "Failed to process data",
    {
      operation: "processData",
      input,
      timestamp: Date.now(),
      sessionID: context.sessionID
    }
  )
}

// Logging with context
catch (error) {
  if (error instanceof OperationError) {
    log.error("operation failed", {
      operation: error.context.operation,
      sessionID: error.context.sessionID,
      error: error.message
    })
  }
}
```

---

## 15. Type System Standards

### 15.1 Zod Schemas (Primary)

**Rule: Use Zod for runtime validation, derive TypeScript types from schemas**

```typescript
// ✅ GOOD - Schema-first approach
export namespace Session {
  export const Info = z.object({
    id: z.string(),
    title: z.string(),
    projectID: z.string(),
    modelID: z.string().optional(),
    permission: z.enum(["allow", "ask", "deny"]),
    time: z.object({
      created: z.number(),
      updated: z.number(),
    }),
  })

  // Derive TypeScript type from schema
  export type Info = z.infer<typeof Info>

  // Use in functions
  export const create = fn(Info.pick({ title: true, projectID: true }).partial(), async (input): Promise<Info> => {
    // Implementation
  })
}

// ❌ BAD - Type-first approach (no runtime validation)
export interface SessionInfo {
  id: string
  title: string
  projectID: string
}

export function create(input: Partial<SessionInfo>): SessionInfo {
  // No validation! Runtime errors possible
}
```

### 15.2 Type Inference

**Rule: Rely on type inference, avoid explicit annotations**

```typescript
// ✅ GOOD - Let TypeScript infer
const session = await Session.create({ title: "New" })
const messages = session.messages.filter((m) => m.role === "user")
const ids = messages.map((m) => m.id)

// ❌ BAD - Unnecessary annotations
const session: Session.Info = await Session.create({ title: "New" })
const messages: Message[] = session.messages.filter((m) => m.role === "user")
const ids: string[] = messages.map((m) => m.id)

// ✅ ACCEPTABLE - Annotation for clarity in complex scenarios
const messages: Message[] = await fetchMessages().then((msgs): Message[] => {
  return msgs.filter((m) => m.deleted !== true)
})
```

**File Reference:** `AGENTS.md:15`

### 15.3 Avoid `any`

```typescript
// ❌ BAD
function process(data: any) {
  return data.value.toString()
}

// ✅ GOOD - Use unknown and validate
function process(data: unknown) {
  if (typeof data !== "object" || data === null) {
    throw new Error("Expected object")
  }

  if (!("value" in data)) {
    throw new Error("Missing value property")
  }

  return String(data.value)
}

// ✅ BEST - Use Zod schema
const DataSchema = z.object({
  value: z.union([z.string(), z.number()]),
})

function process(data: unknown) {
  const validated = DataSchema.parse(data)
  return String(validated.value)
}
```

**File Reference:** `AGENTS.md:12`

### 15.4 Type Guards

```typescript
// ✅ GOOD - Type guards for narrowing
function isMessage(value: unknown): value is Message {
  return typeof value === "object" && value !== null && "id" in value && "role" in value && typeof value.id === "string"
}

// Usage
if (isMessage(data)) {
  console.log(data.id) // TypeScript knows data is Message
}

// ✅ GOOD - Type guards in filter
const messages = items.filter((item): item is Message => isMessage(item)).map((msg) => msg.id)

// ✅ GOOD - Discriminated unions
type Part =
  | { type: "text"; text: string }
  | { type: "file"; data: Buffer; mimeType: string }
  | { type: "patch"; files: string[]; diff: string }

function handle(part: Part) {
  switch (part.type) {
    case "text":
      console.log(part.text) // TypeScript knows: text part
      break
    case "file":
      console.log(part.mimeType) // TypeScript knows: file part
      break
    case "patch":
      console.log(part.files) // TypeScript knows: patch part
      break
    default:
      const _exhaustive: never = part
      throw new Error("Unhandled part type")
  }
}
```

---

## 16. Import Organization Standards

### 16.1 Import Order

```typescript
// 1. Zod and validation libraries
import z from "zod"

// 2. Node.js built-ins
import path from "path"
import fs from "fs/promises"
import os from "os"

// 3. External packages (alphabetical)
import { mergeDeep, sortBy, unique } from "remeda"
import chokidar from "chokidar"
import fuzzysort from "fuzzysort"

// 4. Internal packages (@opencode-ai/*)
import { NamedError } from "@opencode-ai/util/error"

// 5. Project utilities (../util/*)
import { Log } from "../util/log"
import { Lock } from "../util/lock"
import { fn } from "../util/fn"

// 6. Project services (../<service>/*)
import { Config } from "../config/config"
import { Storage } from "../storage/storage"
import { Bus } from "../bus"

// 7. Relative imports (same directory)
import { Session } from "./session"
import { MessageV2 } from "./message-v2"
```

### 16.2 Import Style

```typescript
// ✅ GOOD - Named imports
import { create, update, get } from "./session"
import { Log, type LogOptions } from "./util/log"

// ❌ AVOID - Default imports (except for external packages)
import session from "./session"

// ✅ GOOD - Namespace imports for large APIs
import * as Session from "./session"

// ✅ GOOD - Type-only imports
import type { Session } from "./session"
import { type Config, loadConfig } from "./config"
```

### 16.3 Circular Dependency Prevention

```typescript
// ❌ BAD - Circular dependency
// file-a.ts
import { funcB } from "./file-b"
export function funcA() {
  return funcB()
}

// file-b.ts
import { funcA } from "./file-a"
export function funcB() {
  return funcA()
}

// ✅ GOOD - Extract shared code
// shared.ts
export function shared() {
  return 42
}

// file-a.ts
import { shared } from "./shared"
export function funcA() {
  return shared()
}

// file-b.ts
import { shared } from "./shared"
export function funcB() {
  return shared()
}
```

---

## 17. Naming Conventions

### 17.1 Functions & Variables

```typescript
// ✅ camelCase for functions and variables
const sessionID = "abc123"
const userAgent = "opencode/1.0"
const maxRetries = 3

async function processMessage(messageID: string) {}
async function createSession() {}
```

### 17.2 Types & Interfaces

```typescript
// ✅ PascalCase for types, interfaces, and classes
type SessionInfo = {
  id: string
  title: string
}

interface Message {
  id: string
  content: string
}

class BusyError extends Error {}

namespace Session {
  export type Info = z.infer<typeof Info>
}
```

### 17.3 Constants

```typescript
// ✅ SCREAMING_SNAKE_CASE for true constants
const MAX_RETRY_ATTEMPTS = 3
const DEFAULT_TIMEOUT = 5000
const API_BASE_URL = "https://api.example.com"

// ✅ camelCase for configuration values
const maxRetries = config.retries ?? 3
const defaultModel = config.model?.id ?? "gpt-4"
```

### 17.4 Namespaces

```typescript
// ✅ PascalCase for namespaces
export namespace Session {}
export namespace Storage {}
export namespace FileWatcher {}
```

### 17.5 Files

```typescript
// ✅ kebab-case for files
// session-manager.ts
// message-processor.ts
// file-watcher.ts

// ✅ Single word when possible
// session.ts
// storage.ts
// config.ts
```

---

## 18. Testing Standards

### 18.1 Principles

1. **Avoid mocks** - Test actual implementation
2. **No logic duplication** - Don't reimplement logic in tests
3. **Use Bun test runner** - `bun test`

**File Reference:** `AGENTS.md:108-111`

### 18.2 Test Structure

```typescript
// packages/opencode/src/addons/serialize.test.ts

import { expect, test, describe } from "bun:test"
import { serialize, deserialize } from "./serialize"

describe("serialize", () => {
  test("preserves Date objects", () => {
    const input = { date: new Date("2024-01-01") }
    const serialized = serialize(input)
    const deserialized = deserialize(serialized)

    expect(deserialized.date).toBeInstanceOf(Date)
    expect(deserialized.date.toISOString()).toBe("2024-01-01T00:00:00.000Z")
  })

  test("preserves Set objects", () => {
    const input = { set: new Set([1, 2, 3]) }
    const serialized = serialize(input)
    const deserialized = deserialize(serialized)

    expect(deserialized.set).toBeInstanceOf(Set)
    expect(Array.from(deserialized.set)).toEqual([1, 2, 3])
  })

  test("handles nested structures", () => {
    const input = {
      map: new Map([["key", { date: new Date("2024-01-01") }]]),
      array: [new Set([1, 2])],
    }
    const serialized = serialize(input)
    const deserialized = deserialize(serialized)

    expect(deserialized.map).toBeInstanceOf(Map)
    expect(deserialized.map.get("key")?.date).toBeInstanceOf(Date)
    expect(deserialized.array[0]).toBeInstanceOf(Set)
  })
})
```

**File Reference:** `packages/opencode/src/addons/serialize.test.ts`

### 18.3 Test Commands

```bash
# Run all tests
bun test

# Run specific test file
bun test src/util/queue.test.ts

# Run tests matching pattern
bun test --test-name-pattern "serialize"

# Watch mode
bun test --watch

# Coverage
bun test --coverage
```

**File Reference:** `packages/opencode/AGENTS.md` - Build/Test Commands

### 18.4 Integration Tests

```typescript
import { test, expect, beforeAll, afterAll } from "bun:test"
import { Session } from "./session"
import { Storage } from "./storage"

let sessionID: string

beforeAll(async () => {
  // Setup: Create test session
  const session = await Session.create({ title: "Test Session" })
  sessionID = session.id
})

afterAll(async () => {
  // Cleanup: Delete test session
  await Session.delete(sessionID)
})

test("create and retrieve session", async () => {
  const session = await Session.get(sessionID)

  expect(session).toBeDefined()
  expect(session.id).toBe(sessionID)
  expect(session.title).toBe("Test Session")
})

test("update session", async () => {
  await Session.update(sessionID, { title: "Updated" })

  const session = await Session.get(sessionID)
  expect(session.title).toBe("Updated")
})
```

---

## 19. Documentation Standards

### 19.1 Code Comments

**Rule: Comment WHY, not WHAT**

```typescript
// ✅ GOOD - Explains why
// Cache header separately to enable 2-part prompt caching
if (system.length > 2 && system[0] === header) {
  system.length = 0
  system.push(header, rest.join("\n"))
}

// Prevent race condition: lock before checking existence
using _ = await Lock.write(filepath)
if (await Filesystem.exists(filepath)) {
  await fs.rm(filepath)
}

// ❌ BAD - States the obvious
// Push the item to the array
items.push(item)

// Set the title property
session.title = "New Title"
```

### 19.2 JSDoc (For Exported APIs)

````typescript
/**
 * Execute a function with a file lock to prevent concurrent access.
 *
 * The lock is automatically released when the function completes or throws.
 * Locks are queued and processed in order.
 *
 * @param filepath - Absolute path to the file to lock
 * @param fn - Function to execute while holding the lock
 * @returns The result of the function
 *
 * @example
 * ```typescript
 * await withLock("/path/to/file", async () => {
 *   const content = await Bun.file("/path/to/file").text()
 *   await Bun.write("/path/to/file", content + "\n")
 * })
 * ```
 */
export async function withLock<T>(filepath: string, fn: () => Promise<T>): Promise<T>
````

### 19.3 README Structure

```markdown
# Package Name

Brief description of what this package does.

## Installation

\`\`\`bash
bun install @opencode-ai/package-name
\`\`\`

## Usage

\`\`\`typescript
import { feature } from "@opencode-ai/package-name"

await feature()
\`\`\`

## API

### `function(param: Type): ReturnType`

Description of function.

**Parameters:**

- `param` - Description of parameter

**Returns:** Description of return value

**Example:**
\`\`\`typescript
const result = await function(param)
\`\`\`

## Development

\`\`\`bash
bun install
bun test
\`\`\`
```

---

## 20. Schema Definition Standards

### 20.1 Drizzle Schema (Database)

**Rule: Use snake_case for field names to match SQL conventions**

```typescript
// ✅ GOOD - snake_case fields
const sessionTable = sqliteTable("session", {
  id: text().primaryKey(),
  project_id: text().notNull(),
  created_at: integer().notNull(),
  updated_at: integer().notNull(),
})

// ❌ BAD - camelCase requires explicit column names
const sessionTable = sqliteTable("session", {
  id: text("id").primaryKey(),
  projectID: text("project_id").notNull(),
  createdAt: integer("created_at").notNull(),
  updatedAt: integer("updated_at").notNull(),
})
```

**File Reference:** `AGENTS.md:88-106`

### 20.2 Zod Schema

```typescript
// ✅ GOOD - Consistent field naming
export const SessionInfo = z.object({
  id: z.string(),
  projectID: z.string(), // camelCase for TypeScript
  title: z.string(),
  permission: z.enum(["allow", "ask", "deny"]),
  time: z.object({
    created: z.number(),
    updated: z.number(),
  }),
})

// ✅ GOOD - Schema composition
export const CreateSessionInput = SessionInfo.pick({
  title: true,
  projectID: true,
}).partial()

export const UpdateSessionInput = SessionInfo.partial().required({
  id: true,
})

// ✅ GOOD - Schema extension
export const SessionWithMessages = SessionInfo.extend({
  messages: z.array(MessageInfo),
})
```

---

## 21. Dependency Management Standards

### 21.1 Bun Runtime Preference

**Rule: Use Bun APIs when available**

```typescript
// ✅ GOOD - Bun APIs
const content = await Bun.file(filepath).text()
await Bun.write(filepath, content)
const json = await Bun.file(filepath).json()

const proc = Bun.spawn(["ls", "-la"], {
  cwd: directory,
  stdout: "pipe",
})

// ❌ AVOID - Node.js fs when Bun alternative exists
import fs from "fs/promises"
const content = await fs.readFile(filepath, "utf-8")
await fs.writeFile(filepath, content)
```

**File Reference:** `AGENTS.md:14`

### 21.2 Package Installation

```bash
# Add dependency
bun add package-name

# Add dev dependency
bun add -d package-name

# Add workspace dependency
bun add @opencode-ai/other-package
```

### 21.3 Version Management

```json
// package.json
{
  "dependencies": {
    "zod": "^3.22.4", // Allow patch and minor updates
    "remeda": "1.30.0", // Pin exact version for critical deps
    "@ai-sdk/anthropic": "^0.0.39"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "typescript": "^5.3.3"
  }
}
```

---

## 22. Build & Development Standards

### 22.1 Development Commands

```bash
# Install dependencies
bun install

# Run in development (TUI)
bun dev                       # Run in packages/opencode
bun dev <directory>           # Run in specific directory
bun dev .                     # Run in repo root

# Run API server
bun dev serve                 # Start on port 4096
bun dev serve --port 8080     # Custom port

# Run web UI (requires server running)
bun dev web                   # Start server + open web UI
bun run --cwd packages/app dev  # Just web UI

# Run desktop app
bun run --cwd packages/desktop tauri dev

# Type checking
bun run typecheck

# Run tests
bun test
bun test path/to/test.ts

# Build standalone executable
./packages/opencode/script/build.ts --single

# Regenerate SDK (after server changes)
./script/generate.ts
```

**File Reference:** `CONTRIBUTING.md:30-150`

### 22.2 Project Structure

```
opencode/
├── packages/
│   ├── opencode/           # Core business logic & CLI
│   │   ├── src/
│   │   │   ├── session/    # Session management
│   │   │   ├── tool/       # Tool implementations
│   │   │   ├── agent/      # Agent system
│   │   │   ├── mcp/        # Model Context Protocol
│   │   │   ├── lsp/        # Language Server Protocol
│   │   │   ├── file/       # File operations
│   │   │   ├── config/     # Configuration
│   │   │   ├── provider/   # LLM providers
│   │   │   ├── server/     # HTTP/WebSocket server
│   │   │   └── cli/cmd/tui/  # Terminal UI (SolidJS)
│   │   └── script/
│   │       └── build.ts    # Build script
│   ├── app/                # Web UI (SolidJS)
│   ├── desktop/            # Native app (Tauri)
│   ├── sdk/js/             # TypeScript SDK
│   ├── plugin/             # Plugin system
│   └── console/            # Web console
├── script/
│   ├── generate.ts         # Generate SDK
│   └── format.ts           # Format code
├── AGENTS.md               # Agent guidelines
├── CONTRIBUTING.md         # Contribution guide
└── opencode-typescript.md  # This document (OpenCode-specific patterns)
```

### 22.3 Build Output

```bash
# Single build creates:
packages/opencode/dist/opencode-darwin-arm64/
├── bin/
│   └── opencode           # Executable
├── node_modules/          # Bundled dependencies
└── package.json

# Run built executable:
./packages/opencode/dist/opencode-darwin-arm64/bin/opencode
```

---

## 23. Performance Standards

### 23.1 Lazy Initialization

```typescript
// ✅ GOOD - Lazy state initialization
const state = Instance.state(async () => {
  // Only initialize when first accessed
  const config = await Config.get()
  return { config, clients: [] }
})

// Access only when needed
if (needsClient) {
  const client = state().clients[0]
}

// ✅ GOOD - Lazy loading of heavy dependencies
const lazyModule = lazy(async () => {
  return import("./heavy-module")
})

// Load only when needed
const module = await lazyModule()
```

### 23.2 Caching

```typescript
// ✅ GOOD - Cache expensive operations
const cache = new Map<string, Data>()

async function getData(id: string): Promise<Data> {
  const cached = cache.get(id)
  if (cached) return cached

  const data = await fetchData(id)
  cache.set(id, data)
  return data
}

// ✅ GOOD - Time-based cache invalidation
const cache = new Map<string, { data: Data; expires: number }>()

async function getData(id: string): Promise<Data> {
  const cached = cache.get(id)
  if (cached && Date.now() < cached.expires) {
    return cached.data
  }

  const data = await fetchData(id)
  cache.set(id, {
    data,
    expires: Date.now() + 60_000, // 1 minute
  })
  return data
}
```

### 23.3 Debouncing

```typescript
// ✅ GOOD - Debounce high-frequency events
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
  let timer: Timer | undefined

  return (...args: Parameters<T>) => {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn(...args)
      timer = undefined
    }, delay)
  }
}

// Usage
const debouncedSave = debounce(async (content: string) => {
  await save(content)
}, 500)

// Only saves after 500ms of inactivity
input.on("change", (content) => {
  debouncedSave(content)
})
```

### 23.4 Streaming

```typescript
// ✅ GOOD - Stream large responses
export async function* streamMessages(sessionID: string) {
  const files = await Storage.list(Storage.path("messages", sessionID), "*.json")

  for (const file of files) {
    const message = await Storage.read<Message>(file)
    yield message
  }
}

// Usage
for await (const message of streamMessages(sessionID)) {
  process(message)
}
```

---

## 24. Security Standards

### 24.1 Input Validation

```typescript
// ✅ GOOD - Validate all external input
export const handleRequest = fn(
  z.object({
    sessionID: z.string().regex(/^session-[a-z0-9]{10}$/),
    content: z.string().max(100_000),
    metadata: z.record(z.unknown()).optional(),
  }),
  async (input) => {
    // Input is validated, safe to use
    return process(input)
  },
)

// ✅ GOOD - Validate file paths
function validatePath(filepath: string) {
  const normalized = path.normalize(filepath)
  const absolute = path.resolve(normalized)

  // Prevent directory traversal
  if (!absolute.startsWith(Instance.worktree)) {
    throw new Error("Path outside worktree")
  }

  return absolute
}
```

### 24.2 Permission Checks

```typescript
// ✅ GOOD - Check permissions before sensitive operations
async function readFile(filepath: string, context: Context) {
  // Validate path
  const safe = validatePath(filepath)

  // Check permission
  const permission = await PermissionNext.evaluate("read", safe, context.agent.permission)

  if (permission.action === "deny") {
    throw PermissionError.create({
      message: "Permission denied",
      action: "read",
      resource: safe,
    })
  }

  if (permission.action === "ask") {
    const allowed = await context.ask({
      type: "permission",
      message: `Read file ${path.basename(safe)}?`,
      actions: ["allow", "deny"],
    })

    if (!allowed) {
      throw PermissionError.create({
        message: "User denied permission",
        action: "read",
        resource: safe,
      })
    }
  }

  // Permission granted, perform operation
  return Bun.file(safe).text()
}
```

### 24.3 Secrets Management

```typescript
// ✅ GOOD - Load secrets from environment
export namespace Auth {
  export async function get(providerID: string): Promise<string | undefined> {
    const key = `OPENCODE_${providerID.toUpperCase()}_API_KEY`
    return process.env[key]
  }
}

// ❌ AVOID - Hardcoded secrets
const API_KEY = "sk-abc123..."

// ❌ AVOID - Logging secrets
log.info("api key", { key: apiKey })

// ✅ GOOD - Redact secrets in logs
log.info("api request", { key: apiKey.slice(0, 8) + "..." })
```

### 24.4 Command Injection Prevention

```typescript
// ❌ BAD - Command injection vulnerability
const output = await $`ls ${userInput}`.text()

// ✅ GOOD - Use array syntax (prevents injection)
const proc = Bun.spawn(["ls", userInput], {
  stdout: "pipe",
})
const output = await new Response(proc.stdout).text()

// ✅ GOOD - Validate input
const sanitized = userInput.replace(/[^a-zA-Z0-9_-]/g, "")
const proc = Bun.spawn(["ls", sanitized])
```

---

## Summary: Key Principles

1. **Single-word function names** unless multi-word necessary (95%+ adherence)
2. **Namespaces over classes** for code organization (only 5 classes in 206 files)
3. **Functional array methods** over for-loops (85/15 split)
4. **Parallel execution by default** via `Promise.all`
5. **Lock mechanisms** prevent race conditions (reader-writer locks, file locks)
6. **Per-instance state isolation** for multi-project support
7. **Event-driven architecture** via pub/sub bus
8. **Zod validation** for all inputs via `fn()` wrapper
9. **Disposable resources** with `using` keyword for automatic cleanup
10. **Bun runtime preference** for file I/O and process spawning
11. **Inline values once, extract when reusable**
12. **Named errors** over throw strings
13. **Avoid mocks in tests**, test actual implementation
14. **Layered config** with clear precedence (7 levels)
15. **Graceful disposal** for all stateful services
16. **Prefer `const` over `let`** - use ternaries instead of reassignment
17. **Avoid `else` statements** - use early returns
18. **Avoid destructuring** - preserve context with dot notation
19. **Type inference** - avoid explicit annotations unless necessary
20. **Avoid `any` type** - use `unknown` with validation
21. **Comment WHY, not WHAT** - explain reasoning, not obvious operations
22. **Schema-first approach** - define Zod schemas, derive TS types
23. **Reader-writer locks** for concurrent access
24. **System prompt caching** - maintain 2-part structure for efficiency
25. **Context compaction** at 75% token threshold

---

## File References Quick Index

### Core Patterns

- **Function wrapper**: `packages/opencode/src/util/fn.ts`
- **State management**: `packages/opencode/src/project/state.ts`
- **Instance isolation**: `packages/opencode/src/project/instance.ts`
- **Event bus**: `packages/opencode/src/bus/index.ts`
- **Locks**: `packages/opencode/src/util/lock.ts`
- **Async queue**: `packages/opencode/src/util/queue.ts`

### Services

- **Session**: `packages/opencode/src/session/index.ts`
- **LLM streaming**: `packages/opencode/src/session/llm.ts`
- **Processor**: `packages/opencode/src/session/processor.ts`
- **Messages**: `packages/opencode/src/session/message-v2.ts`
- **LSP**: `packages/opencode/src/lsp/index.ts`
- **MCP**: `packages/opencode/src/mcp/index.ts`
- **Storage**: `packages/opencode/src/storage/storage.ts`
- **Config**: `packages/opencode/src/config/config.ts`
- **Provider**: `packages/opencode/src/provider/provider.ts`

### Tools

- **Tool definition**: `packages/opencode/src/tool/tool.ts`
- **Edit**: `packages/opencode/src/tool/edit.ts`
- **Batch**: `packages/opencode/src/tool/batch.ts`
- **Grep**: `packages/opencode/src/tool/grep.ts`

### Documentation

- **Style guide**: `AGENTS.md`
- **Contributing**: `CONTRIBUTING.md`
- **This document**: `openagents-repo/standards/opencode-typescript.md`
- **Universal TypeScript**: `core/standards/typescript.md`

---

## 25. Modern TypeScript Patterns

### 25.1 Satisfies Operator

**Rule: Use `satisfies` to validate types without widening**

```typescript
// ✅ GOOD - satisfies preserves literal types
const config = {
  development: { port: 3000, host: "localhost" },
  production: { port: 8080, host: "0.0.0.0" },
} satisfies Record<string, { port: number; host: string }>

// Type is preserved: { development: { port: 3000, ... }, production: { ... } }
config.development.port // Type: 3000 (literal)

// ❌ BAD - Type annotation widens types
const config: Record<string, { port: number; host: string }> = {
  development: { port: 3000, host: "localhost" },
  production: { port: 8080, host: "0.0.0.0" },
}

config.development.port // Type: number (widened)

// ✅ GOOD - Event definitions with satisfies
export const Event = {
  Created: BusEvent.define("session.created", z.object({ info: Info })),
  Updated: BusEvent.define("session.updated", z.object({ info: Info })),
  Deleted: BusEvent.define("session.deleted", z.object({ id: z.string() })),
} satisfies Record<string, BusEvent.Definition>

// ✅ GOOD - Tool registry with satisfies
const tools = {
  read: ReadTool,
  write: WriteTool,
  edit: EditTool,
} satisfies Record<string, Tool.Info>
```

### 25.2 Const Assertions

**Rule: Use `as const` for immutable literal types**

```typescript
// ✅ GOOD - Const assertion for literal types
const ROLES = ["user", "assistant", "system"] as const
type Role = (typeof ROLES)[number] // "user" | "assistant" | "system"

// ✅ GOOD - Readonly object with const assertion
const STATUS = {
  IDLE: "idle",
  RUNNING: "running",
  COMPLETED: "completed",
  ERROR: "error",
} as const

type Status = (typeof STATUS)[keyof typeof STATUS]

// ✅ GOOD - Tuple types with const assertion
const point = [10, 20] as const // Type: readonly [10, 20]

// ❌ BAD - Without const assertion
const point = [10, 20] // Type: number[]

// ✅ GOOD - Configuration with const assertion
const PROVIDERS = {
  openai: {
    name: "OpenAI",
    baseURL: "https://api.openai.com/v1",
    models: ["gpt-4", "gpt-3.5-turbo"],
  },
  anthropic: {
    name: "Anthropic",
    baseURL: "https://api.anthropic.com/v1",
    models: ["claude-3-opus", "claude-3-sonnet"],
  },
} as const

type ProviderID = keyof typeof PROVIDERS // "openai" | "anthropic"
```

### 25.3 Template Literal Types

**Rule: Use template literals for type-safe string patterns**

```typescript
// ✅ GOOD - Template literal types for identifiers
type Prefix = "session" | "message" | "part"
type Identifier = `${Prefix}-${string}`

function parseIdentifier(id: Identifier): { prefix: Prefix; value: string } {
  const [prefix, ...rest] = id.split("-")
  return { prefix: prefix as Prefix, value: rest.join("-") }
}

// ✅ GOOD - Event names with template literals
type EventPrefix = "session" | "file" | "tool"
type EventAction = "created" | "updated" | "deleted"
type EventName = `${EventPrefix}.${EventAction}`

const event: EventName = "session.created" // ✅ Valid
const invalid: EventName = "session.invalid" // ❌ Type error

// ✅ GOOD - HTTP methods with template literals
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE"
type Endpoint = `/api/${string}`
type Route = `${HTTPMethod} ${Endpoint}`

const route: Route = "GET /api/sessions" // ✅ Valid

// ✅ GOOD - CSS properties with template literals
type CSSUnit = "px" | "rem" | "em" | "%"
type CSSValue = `${number}${CSSUnit}`

const width: CSSValue = "100px" // ✅ Valid
const height: CSSValue = "50rem" // ✅ Valid
```

### 25.4 Branded Types

**Rule: Use branded types for type-safe primitives**

```typescript
// ✅ GOOD - Branded types for IDs
type SessionID = string & { readonly __brand: "SessionID" }
type MessageID = string & { readonly __brand: "MessageID" }
type PartID = string & { readonly __brand: "PartID" }

function createSessionID(value: string): SessionID {
  // Validate format
  if (!/^session-[a-z0-9]{10}$/.test(value)) {
    throw new Error("Invalid session ID format")
  }
  return value as SessionID
}

function getSession(id: SessionID) {
  // Type-safe: only SessionID accepted
}

const sessionID = createSessionID("session-abc123def4")
getSession(sessionID) // ✅ Valid

const messageID = "message-xyz789" as MessageID
getSession(messageID) // ❌ Type error: MessageID not assignable to SessionID

// ✅ GOOD - Branded types for validated data
type Email = string & { readonly __brand: "Email" }
type URL = string & { readonly __brand: "URL" }

function validateEmail(value: string): Email {
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
    throw new Error("Invalid email")
  }
  return value as Email
}

function sendEmail(to: Email, subject: string) {
  // Type-safe: only validated emails accepted
}

// ✅ GOOD - Branded types for units
type Milliseconds = number & { readonly __brand: "Milliseconds" }
type Seconds = number & { readonly __brand: "Seconds" }

function toMilliseconds(seconds: Seconds): Milliseconds {
  return (seconds * 1000) as Milliseconds
}

function delay(ms: Milliseconds): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms))
}
```

### 25.5 Utility Types

**Rule: Leverage built-in utility types for type transformations**

```typescript
// ✅ GOOD - Pick for selecting properties
type SessionInfo = {
  id: string
  title: string
  projectID: string
  modelID: string
  permission: "allow" | "ask" | "deny"
  time: { created: number; updated: number }
}

type SessionSummary = Pick<SessionInfo, "id" | "title" | "time">

// ✅ GOOD - Omit for excluding properties
type PublicSessionInfo = Omit<SessionInfo, "permission">

// ✅ GOOD - Partial for optional properties
type UpdateSessionInput = Partial<SessionInfo> & { id: string }

// ✅ GOOD - Required for required properties
type CompleteSessionInfo = Required<SessionInfo>

// ✅ GOOD - Readonly for immutability
type ImmutableSession = Readonly<SessionInfo>

// ✅ GOOD - Record for key-value maps
type SessionMap = Record<string, SessionInfo>
type ProviderConfig = Record<string, { apiKey: string; baseURL: string }>

// ✅ GOOD - ReturnType for function return types
async function createSession(input: CreateInput) {
  return { id: "session-123", title: "New" }
}

type SessionResult = Awaited<ReturnType<typeof createSession>>
// Type: { id: string; title: string }

// ✅ GOOD - Parameters for function parameter types
function processMessage(sessionID: string, messageID: string, options: { limit?: number }) {}

type ProcessMessageParams = Parameters<typeof processMessage>
// Type: [string, string, { limit?: number }]

// ✅ GOOD - Exclude for filtering union types
type Status = "idle" | "running" | "completed" | "error"
type ActiveStatus = Exclude<Status, "completed" | "error">
// Type: "idle" | "running"

// ✅ GOOD - Extract for selecting union types
type Event = 
  | { type: "created"; data: SessionInfo }
  | { type: "updated"; data: Partial<SessionInfo> }
  | { type: "deleted"; id: string }

type CreatedEvent = Extract<Event, { type: "created" }>
// Type: { type: "created"; data: SessionInfo }

// ✅ GOOD - NonNullable for removing null/undefined
type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString>
// Type: string
```

### 25.6 Conditional Types

**Rule: Use conditional types for advanced type logic**

```typescript
// ✅ GOOD - Conditional types for unwrapping
type Unwrap<T> = T extends Promise<infer U> ? U : T

type A = Unwrap<Promise<string>> // string
type B = Unwrap<number> // number

// ✅ GOOD - Conditional types for filtering
type FilterByType<T, U> = T extends U ? T : never

type Events = "session.created" | "session.updated" | "file.changed" | "file.deleted"
type SessionEvents = FilterByType<Events, `session.${string}`>
// Type: "session.created" | "session.updated"

// ✅ GOOD - Conditional types for function overloads
type MessagePart = 
  | { type: "text"; text: string }
  | { type: "file"; data: Buffer }
  | { type: "patch"; diff: string }

type PartContent<T extends MessagePart["type"]> = 
  T extends "text" ? string :
  T extends "file" ? Buffer :
  T extends "patch" ? string :
  never

function getContent<T extends MessagePart["type"]>(
  part: MessagePart,
  type: T
): PartContent<T> | undefined {
  if (part.type === type) {
    return part.type === "text" ? part.text :
           part.type === "file" ? part.data :
           part.diff as PartContent<T>
  }
  return undefined
}

// ✅ GOOD - Conditional types for mapped types
type Nullable<T> = {
  [K in keyof T]: T[K] | null
}

type NullableSession = Nullable<SessionInfo>
// All properties can be null
```

---

## 26. Code Quality Tools

### 26.1 TypeScript Configuration

**Rule: Strict TypeScript configuration for maximum type safety**

```json
// tsconfig.json
{
  "compilerOptions": {
    // Type Checking
    "strict": true,                           // Enable all strict type checks
    "noUncheckedIndexedAccess": true,         // Add undefined to index signatures
    "noImplicitOverride": true,               // Require override keyword
    "noPropertyAccessFromIndexSignature": true, // Enforce bracket notation for index access
    
    // Modules
    "module": "ESNext",                       // Use latest module syntax
    "moduleResolution": "bundler",            // Bun-compatible resolution
    "resolveJsonModule": true,                // Import JSON files
    "allowImportingTsExtensions": true,       // Import .ts files directly
    
    // Emit
    "noEmit": true,                           // Bun handles compilation
    "declaration": true,                      // Generate .d.ts files
    "declarationMap": true,                   // Source maps for declarations
    
    // Interop
    "esModuleInterop": true,                  // CommonJS/ESM interop
    "allowSyntheticDefaultImports": true,     // Allow default imports
    "forceConsistentCasingInFileNames": true, // Case-sensitive imports
    
    // Language Features
    "target": "ESNext",                       // Latest JavaScript features
    "lib": ["ESNext"],                        // Latest standard library
    "jsx": "react-jsx",                       // JSX for SolidJS
    "jsxImportSource": "solid-js",            // SolidJS JSX runtime
    
    // Advanced
    "skipLibCheck": true,                     // Skip type checking of .d.ts files
    "isolatedModules": true,                  // Each file is a module
    "verbatimModuleSyntax": true              // Preserve import/export syntax
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}
```

**File Reference:** `packages/opencode/tsconfig.json`

### 26.2 ESLint Configuration

**Rule: Minimal linting, rely on TypeScript for type safety**

```json
// .eslintrc.json
{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module",
    "project": "./tsconfig.json"
  },
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    // Disable rules that conflict with codebase style
    "@typescript-eslint/no-explicit-any": "off",           // Allow any when necessary
    "@typescript-eslint/no-unused-vars": ["warn", {        // Warn on unused vars
      "argsIgnorePattern": "^_",                           // Ignore _prefixed args
      "varsIgnorePattern": "^_"
    }],
    "@typescript-eslint/no-non-null-assertion": "off",     // Allow ! operator
    
    // Enforce codebase patterns
    "prefer-const": "error",                               // Require const
    "no-var": "error",                                     // Disallow var
    "no-else-return": "error",                             // Enforce early returns
    
    // Async patterns
    "require-await": "error",                              // Async functions must await
    "no-return-await": "off",                              // Allow return await
    "@typescript-eslint/return-await": ["error", "always"], // Require return await
    
    // Import organization
    "sort-imports": ["error", {
      "ignoreCase": true,
      "ignoreDeclarationSort": true                        // Use import sorter plugin
    }]
  }
}
```

### 26.3 Prettier Configuration

**Rule: Minimal formatting, focus on consistency**

```json
// .prettierrc
{
  "semi": false,                    // No semicolons (matches codebase)
  "singleQuote": false,             // Double quotes
  "trailingComma": "all",           // Trailing commas everywhere
  "printWidth": 120,                // 120 character line width
  "tabWidth": 2,                    // 2 space indentation
  "useTabs": false,                 // Spaces, not tabs
  "arrowParens": "always",          // Always parentheses around arrow function params
  "endOfLine": "lf",                // Unix line endings
  "bracketSpacing": true,           // Spaces in object literals
  "jsxSingleQuote": false,          // Double quotes in JSX
  "quoteProps": "as-needed"         // Quote object keys only when needed
}
```

**File Reference:** `packages/opencode/.prettierrc`

### 26.4 Pre-commit Hooks

**Rule: Automated quality checks before commit**

```json
// package.json
{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint src --ext .ts,.tsx",
    "format": "prettier --write src",
    "format:check": "prettier --check src",
    "test": "bun test",
    "pre-commit": "bun run typecheck && bun run lint && bun run format:check && bun test"
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "bun test --related"
    ]
  }
}
```

```bash
# Install husky for git hooks
bun add -d husky lint-staged

# Setup pre-commit hook
npx husky install
npx husky add .husky/pre-commit "bunx lint-staged"
```

### 26.5 CI/CD Quality Gates

**Rule: Enforce quality in continuous integration**

```yaml
# .github/workflows/quality.yml
name: Quality Checks

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: oven-sh/setup-bun@v1
        with:
          bun-version: latest
      
      - name: Install dependencies
        run: bun install
      
      - name: Type check
        run: bun run typecheck
      
      - name: Lint
        run: bun run lint
      
      - name: Format check
        run: bun run format:check
      
      - name: Test
        run: bun test
      
      - name: Build
        run: bun run build
```

### 26.6 Code Coverage

**Rule: Track test coverage, aim for 80%+ on critical paths**

```bash
# Run tests with coverage
bun test --coverage

# Generate coverage report
bun test --coverage --coverage-reporter=html

# View coverage report
open coverage/index.html
```

```json
// package.json - Coverage thresholds
{
  "bun": {
    "test": {
      "coverage": {
        "enabled": true,
        "thresholds": {
          "line": 80,
          "function": 80,
          "branch": 75,
          "statement": 80
        },
        "exclude": [
          "**/*.test.ts",
          "**/*.spec.ts",
          "**/test/**",
          "**/dist/**"
        ]
      }
    }
  }
}
```

---

## 27. Monorepo Patterns

### 27.1 Workspace Structure

**Rule: Organize packages by function, share common code**

```
opencode/
├── packages/
│   ├── opencode/              # Core business logic & CLI
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── app/                   # Web UI (SolidJS)
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── desktop/               # Native app (Tauri)
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── sdk/                   # TypeScript SDK
│   │   ├── js/
│   │   │   ├── src/
│   │   │   ├── package.json
│   │   │   └── tsconfig.json
│   │   └── python/
│   │
│   ├── plugin/                # Plugin system
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   └── shared/                # Shared utilities
│       ├── util/
│       ├── types/
│       ├── package.json
│       └── tsconfig.json
│
├── package.json               # Root workspace config
├── tsconfig.json              # Base TypeScript config
└── bun.lockb                  # Lockfile
```

### 27.2 Workspace Dependencies

**Rule: Use workspace protocol for internal dependencies**

```json
// packages/app/package.json
{
  "name": "@opencode-ai/app",
  "version": "1.0.0",
  "dependencies": {
    // ✅ GOOD - Workspace dependency
    "@opencode-ai/sdk": "workspace:*",
    "@opencode-ai/shared": "workspace:*",
    
    // External dependencies
    "solid-js": "^1.8.0",
    "zod": "^3.22.4"
  }
}

// packages/sdk/js/package.json
{
  "name": "@opencode-ai/sdk",
  "version": "1.0.0",
  "dependencies": {
    "@opencode-ai/shared": "workspace:*"
  }
}
```

**Root package.json:**

```json
{
  "name": "opencode-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "packages/sdk/*"
  ],
  "scripts": {
    "dev": "bun run --cwd packages/opencode dev",
    "build": "bun run build:packages && bun run build:app",
    "build:packages": "bun run --filter '@opencode-ai/*' build",
    "test": "bun test",
    "typecheck": "bun run --filter '@opencode-ai/*' typecheck"
  }
}
```

### 27.3 Shared Configuration

**Rule: Extend base configs, override per package**

```json
// tsconfig.json (root)
{
  "compilerOptions": {
    "strict": true,
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

// packages/opencode/tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "types": ["bun-types"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

// packages/app/tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "solid-js",
    "types": ["vite/client"]
  },
  "include": ["src/**/*"]
}
```

### 27.4 Build Orchestration

**Rule: Build packages in dependency order**

```json
// package.json - Build scripts
{
  "scripts": {
    // Build all packages
    "build": "bun run build:shared && bun run build:sdk && bun run build:core && bun run build:app",
    
    // Build individual packages
    "build:shared": "bun run --cwd packages/shared build",
    "build:sdk": "bun run --cwd packages/sdk/js build",
    "build:core": "bun run --cwd packages/opencode build",
    "build:app": "bun run --cwd packages/app build",
    
    // Parallel builds (when no dependencies)
    "build:parallel": "bun run --filter '@opencode-ai/*' build",
    
    // Watch mode for development
    "dev:all": "bun run --filter '@opencode-ai/*' --parallel dev"
  }
}
```

### 27.5 Version Management

**Rule: Synchronized versioning for related packages**

```json
// package.json - Version management
{
  "version": "1.0.0",
  "scripts": {
    "version": "bun run version:sync",
    "version:sync": "node scripts/sync-versions.js"
  }
}
```

```typescript
// scripts/sync-versions.ts
import { readdir } from "fs/promises"
import { join } from "path"

const rootVersion = require("../package.json").version

async function syncVersions() {
  const packagesDir = join(__dirname, "..", "packages")
  const packages = await readdir(packagesDir)

  for (const pkg of packages) {
    const pkgPath = join(packagesDir, pkg, "package.json")
    const pkgJson = require(pkgPath)

    if (pkgJson.name?.startsWith("@opencode-ai/")) {
      pkgJson.version = rootVersion
      await Bun.write(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n")
      console.log(`Updated ${pkgJson.name} to ${rootVersion}`)
    }
  }
}

syncVersions()
```

### 27.6 Shared Types

**Rule: Export shared types from dedicated package**

```typescript
// packages/shared/types/src/index.ts

// ✅ GOOD - Shared types
export type SessionID = string & { readonly __brand: "SessionID" }
export type MessageID = string & { readonly __brand: "MessageID" }

export interface SessionInfo {
  id: SessionID
  title: string
  projectID: string
  time: { created: number; updated: number }
}

export interface MessageInfo {
  id: MessageID
  sessionID: SessionID
  role: "user" | "assistant" | "system"
  content: string
}

// ✅ GOOD - Shared Zod schemas
export const SessionInfoSchema = z.object({
  id: z.string(),
  title: z.string(),
  projectID: z.string(),
  time: z.object({
    created: z.number(),
    updated: z.number(),
  }),
})

// Usage in other packages
// packages/opencode/src/session/index.ts
import type { SessionInfo } from "@opencode-ai/shared/types"
import { SessionInfoSchema } from "@opencode-ai/shared/types"
```

### 27.7 Package Exports

**Rule: Define explicit exports for better tree-shaking**

```json
// packages/shared/package.json
{
  "name": "@opencode-ai/shared",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./types": {
      "types": "./dist/types/index.d.ts",
      "import": "./dist/types/index.js"
    },
    "./util": {
      "types": "./dist/util/index.d.ts",
      "import": "./dist/util/index.js"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsc && bun build src/index.ts --outdir dist"
  }
}
```

---

**End of Standards Document**
