Browse Source

fix(plugin): fix hooks and add missing skills for reliable skill invocation (#227)

* fix: overhaul Claude Code plugin for correctness and spec compliance

- Fix critical bugs: coder-agent name (CoderAgent→coder-agent), manifest
  path in session-start.sh, /oac:setup ghost command replaced with /install-context
- Fix hooks.json: add timeout/async, add description, restore matcher
- Fix session-start.sh: dual-format output (Claude Code + Cursor/OpenCode),
  remove invalid JSON escapes for $ and backtick
- Fix agent spec compliance: disallowedTools on read-only agents, WebFetch
  on external-scout, haiku model on context-scout, example blocks on 3 agents
- Fix skill descriptions: triggering conditions only (no workflow summaries)
  per superpowers empirical testing — prevents Claude shortcutting skill content
- Add Red Flags + Rationalization tables to 4 discipline skills
- Add install-context skill + command replacing oac-setup/oac-plan/oac-add-context
- Add cleanup-tmp.sh (was missing, broke /oac:cleanup)
- Add TypeScript installer (install-context.ts/js) with profile-based download
- Update plugin.json: keywords, homepage for marketplace discoverability
- Update README: correct skill count, fix structure diagram, remove stale refs
- Remove obsolete files: FIRST-TIME-SETUP.md, INSTALL.md, QUICK-START.md,
  download-context.sh, verify-context.sh, load-config.sh, context-manager skill,
  oac-setup/oac-plan/oac-add-context commands

* add TypeScript and OpenCode context standards files

Adds three new context files covering universal TypeScript patterns,
OpenCode-specific patterns (fn() wrapper, namespaces, Instance.state()),
and context-system-specific TypeScript coding standards.

* fix(plugin): fix hooks and add missing skills for reliable skill invocation

- Rewrite using-oac skill with always-on skill-check rule (was conditional on dev task detection)
- Update session-start.sh to dynamically inject full skill catalogue at session start
- Add brainstorming skill: intercepts creative/build work before any implementation
- Add systematic-debugging skill: 4-phase root cause process before any fix
- Add verification-before-completion skill: evidence-before-claims gate
- Add /brainstorm and /debug slash commands as pure skill shortcuts
- Fix oac-help to use disable-model-invocation:true (pure shortcut, not self-rendering)
- Fix oac-status to be simpler bash-based status check
Darren Hinde 5 months ago
parent
commit
9e5e70a12d

+ 998 - 0
.opencode/context/core/context-system/standards/typescript-coding.md

@@ -0,0 +1,998 @@
+# Context System TypeScript Coding Standards
+
+**Purpose**: TypeScript coding standards for context system implementation  
+**Scope**: Context resolver, CLI tools, and context management utilities  
+**Last Updated**: 2026-02-16
+
+---
+
+## Overview
+
+This document defines TypeScript coding standards specifically for the context system. These standards are extracted from the main OpenCode TypeScript standards and tailored for context-related code.
+
+**Relationship to TypeScript Standards**:
+- **Universal TypeScript** (`core/standards/typescript.md`) - Universal TypeScript patterns applicable to any project
+- **OpenCode TypeScript** (`openagents-repo/standards/opencode-typescript.md`) - OpenCode-specific patterns (fn() wrapper, namespaces, etc.)
+- **This file** (`typescript-coding.md`) - Context system-specific subset focused on context resolver, CLI tools, and context management utilities
+
+**When to use which**:
+- Writing TypeScript code anywhere → Load universal typescript.md
+- Writing OpenCode-specific code → Load opencode-typescript.md
+- Writing context system code specifically → Load universal typescript.md, opencode-typescript.md, AND this file
+- This file extends the base standards with context-specific patterns, not replaces them
+
+**Key Principles**:
+1. **Type Safety First** - Use Zod schemas for runtime validation
+2. **Functional Patterns** - Prefer pure functions over classes
+3. **Error Handling** - Explicit error types with context
+4. **Async Operations** - Proper handling of file I/O and git operations
+5. **Testing** - Comprehensive test coverage for all resolver logic
+
+---
+
+## Implementation Status
+
+This document describes both **current implementation** and **recommended patterns** for the context system.
+
+**Legend**:
+- ✅ **Current Implementation** - Patterns currently used in the codebase
+- 🎯 **Recommended** - Patterns to adopt for improvements
+- ⚠️ **Note** - Important clarifications or caveats
+
+**Priority Improvements**:
+1. Add Zod schemas for runtime validation (Section 1.1)
+2. Implement typed error classes (Section 3.1)
+3. Convert to async file operations (Section 4.1)
+4. Add branded types for type safety (Section 1.3)
+
+---
+
+## 1. Type Definitions
+
+### 1.1 Configuration Types
+
+**Current Implementation: TypeScript interfaces**  
+**Recommended: Zod schemas for runtime validation**
+
+```typescript
+// ✅ CURRENT IMPLEMENTATION - TypeScript interface
+// packages/core/src/context/resolver.ts:11-28
+export interface OACConfig {
+  version: string;
+  project?: {
+    name: string;
+    type: string;
+    version: string;
+  };
+  context: {
+    root: string;
+    locations?: Record<string, string>;
+    update?: {
+      check_on_start?: boolean;
+      auto_update?: boolean;
+      interval?: string;
+      strategy?: 'auto' | 'manual' | 'notify';
+    };
+  };
+}
+
+// Usage (current)
+function loadConfig(path: string): OACConfig {
+  const content = fs.readFileSync(path, 'utf-8');
+  return JSON.parse(content); // No runtime validation
+}
+
+// 🎯 RECOMMENDED - Zod schema with runtime validation
+import { z } from 'zod';
+
+export const OACConfigSchema = z.object({
+  version: z.string(),
+  project: z.object({
+    name: z.string(),
+    type: z.string(),
+    version: z.string(),
+  }).optional(),
+  context: z.object({
+    root: z.string(),
+    locations: z.record(z.string()).optional(),
+    update: z.object({
+      check_on_start: z.boolean().optional(),
+      auto_update: z.boolean().optional(),
+      interval: z.string().optional(),
+      strategy: z.enum(['auto', 'manual', 'notify']).optional(),
+    }).optional(),
+  }),
+});
+
+export type OACConfig = z.infer<typeof OACConfigSchema>;
+
+// Usage (recommended)
+function loadConfig(path: string): OACConfig {
+  const content = fs.readFileSync(path, 'utf-8');
+  const parsed = JSON.parse(content);
+  return OACConfigSchema.parse(parsed); // Validates at runtime
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:11-28`
+
+### 1.2 Result Types
+
+**Rule: Use discriminated unions for operation results**
+
+```typescript
+// ✅ GOOD - Discriminated union for results
+export type ResolveResult = 
+  | { success: true; path: string }
+  | { success: false; error: string; category?: string };
+
+export type UpdateResult =
+  | { success: true; category: string; filesChanged: number }
+  | { success: false; category: string; error: string };
+
+// Usage
+async function resolve(reference: string): Promise<ResolveResult> {
+  try {
+    const path = await resolveReference(reference);
+    return { success: true, path };
+  } catch (error) {
+    return { 
+      success: false, 
+      error: error instanceof Error ? error.message : String(error) 
+    };
+  }
+}
+
+// Consuming code
+const result = await resolve('{context.root}/file.md');
+if (result.success) {
+  console.log(result.path); // TypeScript knows path exists
+} else {
+  console.error(result.error); // TypeScript knows error exists
+}
+
+// ❌ BAD - Throwing errors for expected failures
+async function resolve(reference: string): Promise<string> {
+  if (!isValid(reference)) {
+    throw new Error('Invalid reference'); // Forces try/catch everywhere
+  }
+  return resolveReference(reference);
+}
+```
+
+### 1.3 Branded Types for Identifiers
+
+**Recommended Pattern: Use branded types for category names and references**
+
+> ⚠️ **Note**: This pattern is not currently implemented in the context system, but is recommended for future type safety improvements.
+
+```typescript
+// 🎯 RECOMMENDED - Branded types for type safety
+export type CategoryName = string & { readonly __brand: 'CategoryName' };
+export type ContextReference = string & { readonly __brand: 'ContextReference' };
+
+function createCategoryName(value: string): CategoryName {
+  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
+    throw new Error('Invalid category name format');
+  }
+  return value as CategoryName;
+}
+
+function createContextReference(value: string): ContextReference {
+  if (!/^\{context\.\w+\}\/.+$/.test(value)) {
+    throw new Error('Invalid context reference format');
+  }
+  return value as ContextReference;
+}
+
+// Type-safe functions
+function resolveCategory(category: CategoryName): string {
+  // Only accepts validated category names
+}
+
+function parseReference(ref: ContextReference): { category: CategoryName; path: string } {
+  // Only accepts validated references
+}
+
+// ❌ BAD - Plain strings allow invalid values
+function resolveCategory(category: string): string {
+  // Accepts any string, no validation
+}
+```
+
+---
+
+## 2. Class Design
+
+### 2.1 Single Responsibility
+
+**Rule: Classes should have a single, well-defined responsibility**
+
+```typescript
+// ✅ GOOD - Single responsibility: context resolution
+export class ContextResolver {
+  private config: OACConfig;
+  private cacheDir = '.oac-cache/remote';
+  
+  constructor(configPath: string = '.oac') {
+    this.config = this.loadConfig(configPath);
+    this.ensureCacheDir();
+  }
+  
+  async resolve(reference: string): Promise<string> {
+    // Only handles resolution logic
+  }
+  
+  private loadConfig(path: string): OACConfig {
+    // Only handles config loading
+  }
+}
+
+// ✅ GOOD - Separate class for cache management
+export class CacheManager {
+  constructor(private cacheDir: string) {}
+  
+  async get(category: string): Promise<string | null> {
+    // Only handles cache operations
+  }
+  
+  async set(category: string, path: string): Promise<void> {
+    // Only handles cache operations
+  }
+  
+  clear(category: string): void {
+    // Only handles cache operations
+  }
+}
+
+// ❌ BAD - Multiple responsibilities
+export class ContextManager {
+  async resolve(reference: string): Promise<string> {}
+  async updateRemote(url: string): Promise<void> {}
+  async validateConfig(config: unknown): OACConfig {}
+  async formatOutput(data: unknown): string {}
+  async sendNotification(message: string): void {}
+  // Too many unrelated responsibilities!
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:38-472`
+
+### 2.2 Private Methods
+
+**Rule: Use private methods for internal logic, public for API**
+
+```typescript
+// ✅ GOOD - Clear public API, private implementation
+export class ContextResolver {
+  // Public API
+  async resolve(reference: string): Promise<string> {
+    const parsed = this.parseReference(reference);
+    if (!parsed) return reference;
+    
+    const location = this.getLocation(parsed.category);
+    
+    if (this.isRemote(location)) {
+      const localPath = await this.ensureRemote(location, parsed.category);
+      return path.join(localPath, parsed.filePath);
+    }
+    
+    return path.join(location, parsed.filePath);
+  }
+  
+  // Private implementation details
+  private parseReference(reference: string): { category: string; filePath: string } | null {
+    const match = reference.match(/\{context\.(\w+)\}\/(.+)/);
+    if (!match) return null;
+    const [_, category, filePath] = match;
+    return { category, filePath };
+  }
+  
+  private getLocation(category: string): string {
+    if (category === 'root') return this.config.context.root;
+    return this.config.context.locations?.[category] ?? path.join(this.config.context.root, category);
+  }
+  
+  private isRemote(location: string): boolean {
+    return location.startsWith('git@') || location.startsWith('https://');
+  }
+  
+  private async ensureRemote(url: string, category: string): Promise<string> {
+    // Implementation
+  }
+}
+
+// ❌ BAD - Everything public
+export class ContextResolver {
+  parseReference(reference: string) {} // Should be private
+  getLocation(category: string) {} // Should be private
+  isRemote(location: string) {} // Should be private
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:81-177`
+
+---
+
+## 3. Error Handling
+
+### 3.1 Typed Errors
+
+**Current Implementation: Generic Error**  
+**Recommended: Specific error types for different failure modes**
+
+```typescript
+// ✅ CURRENT IMPLEMENTATION - Generic errors
+// packages/core/src/context/resolver.ts:55
+if (!fs.existsSync(this.configPath)) {
+  throw new Error(`.oac config file not found at ${this.configPath}`);
+}
+
+// 🎯 RECOMMENDED - Typed error classes
+export class ConfigNotFoundError extends Error {
+  constructor(public readonly configPath: string) {
+    super(`Config file not found at ${configPath}`);
+    this.name = 'ConfigNotFoundError';
+  }
+}
+
+export class InvalidConfigError extends Error {
+  constructor(
+    public readonly configPath: string,
+    public readonly validationErrors: string[]
+  ) {
+    super(`Invalid config at ${configPath}: ${validationErrors.join(', ')}`);
+    this.name = 'InvalidConfigError';
+  }
+}
+
+export class CategoryNotFoundError extends Error {
+  constructor(public readonly category: string) {
+    super(`Unknown context category: ${category}`);
+    this.name = 'CategoryNotFoundError';
+  }
+}
+
+export class RemoteCloneError extends Error {
+  constructor(
+    public readonly url: string,
+    public readonly cause: Error
+  ) {
+    super(`Failed to clone ${url}: ${cause.message}`);
+    this.name = 'RemoteCloneError';
+  }
+}
+
+// Usage with type checking
+try {
+  const config = loadConfig('.oac');
+} catch (error) {
+  if (error instanceof ConfigNotFoundError) {
+    console.error(`Config not found: ${error.configPath}`);
+    // Suggest creating config
+  } else if (error instanceof InvalidConfigError) {
+    console.error(`Invalid config: ${error.validationErrors.join('\n')}`);
+    // Show validation errors
+  } else {
+    throw error; // Unexpected error
+  }
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:54-64`
+
+### 3.2 Error Context
+
+**Rule: Include relevant context in error messages**
+
+```typescript
+// ✅ GOOD - Rich error context
+private loadConfig(configPath: string): OACConfig {
+  if (!fs.existsSync(configPath)) {
+    throw new ConfigNotFoundError(configPath);
+  }
+  
+  try {
+    const content = fs.readFileSync(configPath, 'utf-8');
+    const parsed = JSON.parse(content);
+    return OACConfigSchema.parse(parsed);
+  } catch (error) {
+    if (error instanceof z.ZodError) {
+      throw new InvalidConfigError(
+        configPath,
+        error.errors.map(e => `${e.path.join('.')}: ${e.message}`)
+      );
+    }
+    throw new Error(
+      `Failed to parse config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`
+    );
+  }
+}
+
+// ❌ BAD - No context
+private loadConfig(configPath: string): OACConfig {
+  const content = fs.readFileSync(configPath, 'utf-8');
+  return JSON.parse(content); // Throws generic errors
+}
+```
+
+---
+
+## 4. Async Operations
+
+### 4.1 File I/O
+
+**Rule: Use async file operations, handle errors explicitly**
+
+```typescript
+// ✅ GOOD - Async with proper error handling
+import { promises as fs } from 'fs';
+
+async function loadConfig(configPath: string): Promise<OACConfig> {
+  try {
+    const content = await fs.readFile(configPath, 'utf-8');
+    const parsed = JSON.parse(content);
+    return OACConfigSchema.parse(parsed);
+  } catch (error) {
+    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
+      throw new ConfigNotFoundError(configPath);
+    }
+    throw error;
+  }
+}
+
+// ✅ GOOD - Check existence before reading
+async function readIfExists(filePath: string): Promise<string | null> {
+  try {
+    await fs.access(filePath);
+    return await fs.readFile(filePath, 'utf-8');
+  } catch (error) {
+    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
+      return null;
+    }
+    throw error;
+  }
+}
+
+// ❌ BAD - Synchronous file operations
+function loadConfig(configPath: string): OACConfig {
+  const content = fs.readFileSync(configPath, 'utf-8'); // Blocks event loop
+  return JSON.parse(content);
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:53-64`
+
+### 4.2 Git Operations
+
+**Rule: Use execSync for git commands, handle failures gracefully**
+
+```typescript
+// ✅ GOOD - Git operations with error handling
+import { execSync } from 'child_process';
+
+async function cloneRemote(url: string, localPath: string, category: string): Promise<void> {
+  console.log(`📦 Cloning ${category} context from ${url}...`);
+  
+  try {
+    await fs.mkdir(path.dirname(localPath), { recursive: true });
+    execSync(`git clone ${url} ${localPath}`, { stdio: 'inherit' });
+    
+    this.recordUpdate(category);
+    console.log(`✅ Cloned ${category} successfully`);
+  } catch (error) {
+    throw new RemoteCloneError(
+      url,
+      error instanceof Error ? error : new Error(String(error))
+    );
+  }
+}
+
+async function updateRemote(category: string, localPath: string): Promise<void> {
+  console.log(`🔄 Updating ${category} context...`);
+  
+  try {
+    execSync('git pull', { cwd: localPath, stdio: 'inherit' });
+    this.recordUpdate(category);
+    console.log(`✅ Updated ${category} successfully`);
+  } catch (error) {
+    console.warn(`⚠️  Failed to update ${category}: ${error instanceof Error ? error.message : String(error)}`);
+    // Don't throw - update failures are non-fatal
+  }
+}
+
+// ❌ BAD - No error handling
+async function cloneRemote(url: string, localPath: string): Promise<void> {
+  execSync(`git clone ${url} ${localPath}`); // Can fail silently
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:182-210`
+
+---
+
+## 5. Testing Standards
+
+### 5.1 Test Structure
+
+**Rule: Use describe/it blocks, setup/teardown for file operations**
+
+```typescript
+// ✅ GOOD - Comprehensive test structure
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import { ContextResolver } from '../resolver';
+import type { OACConfig } from '../resolver';
+
+describe('ContextResolver', () => {
+  let tempDir: string;
+  let configPath: string;
+  let resolver: ContextResolver;
+
+  beforeEach(() => {
+    // Create temporary directory for tests
+    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'oac-test-'));
+    configPath = path.join(tempDir, '.oac');
+    process.chdir(tempDir);
+  });
+
+  afterEach(() => {
+    // Clean up temporary directory
+    if (fs.existsSync(tempDir)) {
+      fs.rmSync(tempDir, { recursive: true, force: true });
+    }
+  });
+
+  describe('resolve', () => {
+    beforeEach(() => {
+      const config: OACConfig = {
+        version: '1.0.0',
+        context: {
+          root: '.opencode/context',
+          locations: {
+            tasks: 'tasks/subtasks',
+            docs: 'docs'
+          }
+        }
+      };
+      fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
+      resolver = new ContextResolver(configPath);
+    });
+
+    it('should resolve root context reference', async () => {
+      const result = await resolver.resolve('{context.root}/core/standards/code-quality.md');
+      expect(result).toBe('.opencode/context/core/standards/code-quality.md');
+    });
+
+    it('should resolve tasks context reference', async () => {
+      const result = await resolver.resolve('{context.tasks}/feature-name/01-task.md');
+      expect(result).toBe('tasks/subtasks/feature-name/01-task.md');
+    });
+
+    it('should return original path if no context reference', async () => {
+      const result = await resolver.resolve('regular/path/to/file.md');
+      expect(result).toBe('regular/path/to/file.md');
+    });
+  });
+});
+```
+
+**File Reference**: `packages/core/src/context/__tests__/resolver.test.ts:7-142`
+
+### 5.2 Test Coverage
+
+**Rule: Test happy path, error cases, and edge cases**
+
+```typescript
+// ✅ GOOD - Comprehensive test coverage
+describe('ContextResolver', () => {
+  describe('constructor', () => {
+    it('should create resolver with default config path', () => {
+      // Happy path
+    });
+
+    it('should create resolver with custom config path', () => {
+      // Happy path variant
+    });
+
+    it('should throw error if config file does not exist', () => {
+      // Error case
+    });
+
+    it('should throw error if config file is invalid JSON', () => {
+      // Error case
+    });
+  });
+
+  describe('resolve', () => {
+    it('should resolve root context reference', async () => {
+      // Happy path
+    });
+
+    it('should resolve custom location reference', async () => {
+      // Happy path variant
+    });
+
+    it('should return original path if no context reference', async () => {
+      // Edge case
+    });
+
+    it('should handle nested paths', async () => {
+      // Edge case
+    });
+
+    it('should handle paths with special characters', async () => {
+      // Edge case
+    });
+  });
+
+  describe('edge cases', () => {
+    it('should handle empty locations', () => {
+      // Edge case
+    });
+
+    it('should handle paths with spaces', async () => {
+      // Edge case
+    });
+  });
+});
+```
+
+**File Reference**: `packages/core/src/context/__tests__/resolver.test.ts:28-318`
+
+---
+
+## 6. CLI Design
+
+### 6.1 Command Structure
+
+**Rule: Use commander.js pattern with typed options**
+
+```typescript
+// ✅ GOOD - Typed CLI commands
+import { Command } from 'commander';
+import { ContextResolver } from '@openagents/core';
+
+const program = new Command();
+
+program
+  .name('oac')
+  .description('OpenAgents Context CLI')
+  .version('1.0.0');
+
+program
+  .command('resolve <reference>')
+  .description('Resolve a context reference to a file path')
+  .option('-c, --config <path>', 'Config file path', '.oac')
+  .action(async (reference: string, options: { config: string }) => {
+    try {
+      const resolver = new ContextResolver(options.config);
+      const resolved = await resolver.resolve(reference);
+      console.log(resolved);
+    } catch (error) {
+      console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
+      process.exit(1);
+    }
+  });
+
+program
+  .command('update [category]')
+  .description('Update remote context (all or specific category)')
+  .option('-c, --config <path>', 'Config file path', '.oac')
+  .action(async (category: string | undefined, options: { config: string }) => {
+    try {
+      const resolver = new ContextResolver(options.config);
+      
+      if (category) {
+        await resolver.update(category);
+      } else {
+        await resolver.updateAll();
+      }
+    } catch (error) {
+      console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
+      process.exit(1);
+    }
+  });
+
+program.parse();
+```
+
+**File Reference**: `packages/cli/src/commands/context.ts`
+
+### 6.2 Output Formatting
+
+**Rule: Use consistent output formatting with colors/emojis**
+
+```typescript
+// ✅ GOOD - Consistent output formatting
+import chalk from 'chalk';
+
+function logSuccess(message: string): void {
+  console.log(chalk.green(`✅ ${message}`));
+}
+
+function logError(message: string): void {
+  console.error(chalk.red(`❌ ${message}`));
+}
+
+function logWarning(message: string): void {
+  console.warn(chalk.yellow(`⚠️  ${message}`));
+}
+
+function logInfo(message: string): void {
+  console.log(chalk.blue(`ℹ️  ${message}`));
+}
+
+// Usage
+async function cloneRemote(url: string, category: string): Promise<void> {
+  logInfo(`Cloning ${category} context from ${url}...`);
+  
+  try {
+    execSync(`git clone ${url} ${localPath}`, { stdio: 'inherit' });
+    logSuccess(`Cloned ${category} successfully`);
+  } catch (error) {
+    logError(`Failed to clone ${category}: ${error.message}`);
+    throw error;
+  }
+}
+
+// ❌ BAD - Inconsistent output
+console.log('Cloning...');
+console.log('Success!');
+console.log('ERROR: Failed');
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:183-210`
+
+---
+
+## 7. Configuration Management
+
+### 7.1 Config Loading
+
+**Rule: Load config once, cache in instance**
+
+```typescript
+// ✅ GOOD - Load once, cache in instance
+export class ContextResolver {
+  private config: OACConfig;
+  private configPath: string;
+
+  constructor(configPath: string = '.oac') {
+    this.configPath = configPath;
+    this.config = this.loadConfig(); // Load once
+    this.ensureCacheDir();
+  }
+
+  private loadConfig(): OACConfig {
+    if (!fs.existsSync(this.configPath)) {
+      throw new ConfigNotFoundError(this.configPath);
+    }
+
+    try {
+      const content = fs.readFileSync(this.configPath, 'utf-8');
+      const parsed = JSON.parse(content);
+      return OACConfigSchema.parse(parsed);
+    } catch (error) {
+      throw new InvalidConfigError(this.configPath, [error.message]);
+    }
+  }
+
+  // Use cached config
+  async resolve(reference: string): Promise<string> {
+    const location = this.getLocation(category); // Uses this.config
+    // ...
+  }
+}
+
+// ❌ BAD - Load config on every operation
+export class ContextResolver {
+  async resolve(reference: string): Promise<string> {
+    const config = this.loadConfig(); // Loads every time!
+    // ...
+  }
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:44-64`
+
+### 7.2 Default Values
+
+**Rule: Provide sensible defaults, make config optional**
+
+```typescript
+// ✅ GOOD - Defaults with optional overrides
+export const OACConfigSchema = z.object({
+  version: z.string().default('1.0.0'),
+  project: z.object({
+    name: z.string(),
+    type: z.string(),
+    version: z.string(),
+  }).optional(),
+  context: z.object({
+    root: z.string().default('.opencode/context'),
+    locations: z.record(z.string()).optional().default({}),
+    update: z.object({
+      check_on_start: z.boolean().default(false),
+      auto_update: z.boolean().default(false),
+      interval: z.string().default('1h'),
+      strategy: z.enum(['auto', 'manual', 'notify']).default('manual'),
+    }).optional().default({}),
+  }),
+});
+
+// Minimal valid config
+const minimalConfig = {
+  context: {
+    root: '.opencode/context'
+  }
+};
+
+// Full config with overrides
+const fullConfig = {
+  version: '2.0.0',
+  project: {
+    name: 'my-project',
+    type: 'agent-framework',
+    version: '1.0.0'
+  },
+  context: {
+    root: '.context',
+    locations: {
+      tasks: 'tasks',
+      team: 'https://github.com/org/team-context.git'
+    },
+    update: {
+      auto_update: true,
+      interval: '30m',
+      strategy: 'auto'
+    }
+  }
+};
+```
+
+---
+
+## 8. Path Handling
+
+### 8.1 Path Resolution
+
+**Rule: Use path.join for cross-platform compatibility**
+
+```typescript
+// ✅ GOOD - Cross-platform path handling
+import * as path from 'path';
+
+function resolveContextPath(category: string, filePath: string): string {
+  const location = this.getLocation(category);
+  return path.join(location, filePath); // Works on Windows and Unix
+}
+
+function getCacheDir(category: string): string {
+  return path.join(this.cacheDir, category);
+}
+
+// ❌ BAD - Hardcoded path separators
+function resolveContextPath(category: string, filePath: string): string {
+  const location = this.getLocation(category);
+  return `${location}/${filePath}`; // Breaks on Windows
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:98-102`
+
+### 8.2 Path Validation
+
+**Rule: Validate paths to prevent directory traversal**
+
+```typescript
+// ✅ GOOD - Path validation
+import * as path from 'path';
+
+function validatePath(filePath: string, baseDir: string): string {
+  const normalized = path.normalize(filePath);
+  const absolute = path.resolve(baseDir, normalized);
+  
+  // Ensure path is within baseDir
+  if (!absolute.startsWith(path.resolve(baseDir))) {
+    throw new Error(`Path traversal detected: ${filePath}`);
+  }
+  
+  return absolute;
+}
+
+// Usage
+const safePath = validatePath(userInput, this.config.context.root);
+
+// ❌ BAD - No validation
+function resolvePath(filePath: string): string {
+  return path.join(this.config.context.root, filePath); // Vulnerable to ../../../etc/passwd
+}
+```
+
+---
+
+## 9. Caching Strategy
+
+### 9.1 Cache Directory Structure
+
+**Rule: Organize cache by category, track update times**
+
+```typescript
+// ✅ GOOD - Structured cache management
+export class ContextResolver {
+  private cacheDir = '.oac-cache/remote';
+  private lastUpdateFile = '.oac-cache/.last-update.json';
+
+  private ensureCacheDir(): void {
+    if (!fs.existsSync(this.cacheDir)) {
+      fs.mkdirSync(this.cacheDir, { recursive: true });
+    }
+  }
+
+  private recordUpdate(category: string): void {
+    let updates: Record<string, number> = {};
+
+    if (fs.existsSync(this.lastUpdateFile)) {
+      try {
+        updates = JSON.parse(fs.readFileSync(this.lastUpdateFile, 'utf-8'));
+      } catch {
+        // Ignore parse errors
+      }
+    }
+
+    updates[category] = Date.now();
+
+    fs.mkdirSync(path.dirname(this.lastUpdateFile), { recursive: true });
+    fs.writeFileSync(this.lastUpdateFile, JSON.stringify(updates, null, 2));
+  }
+
+  private getLastUpdate(category: string): number | null {
+    if (!fs.existsSync(this.lastUpdateFile)) {
+      return null;
+    }
+
+    try {
+      const updates = JSON.parse(fs.readFileSync(this.lastUpdateFile, 'utf-8'));
+      return updates[category] || null;
+    } catch {
+      return null;
+    }
+  }
+}
+```
+
+**File Reference**: `packages/core/src/context/resolver.ts:40-296`
+
+---
+
+## 10. Summary: Key Patterns
+
+### TypeScript Patterns for Context System
+
+1. **Zod Schemas** - Runtime validation for all config and input
+2. **Branded Types** - Type-safe identifiers (CategoryName, ContextReference)
+3. **Discriminated Unions** - Type-safe result types
+4. **Typed Errors** - Specific error classes with context
+5. **Async/Await** - Proper async handling for I/O operations
+6. **Path Safety** - Cross-platform path handling with validation
+7. **Cache Management** - Structured caching with update tracking
+8. **Single Responsibility** - Classes with focused responsibilities
+9. **Private Methods** - Clear public API, private implementation
+10. **Comprehensive Testing** - Happy path, errors, and edge cases
+
+### File References
+
+- **Core Implementation**: `packages/core/src/context/resolver.ts`
+- **Tests**: `packages/core/src/context/__tests__/resolver.test.ts`
+- **CLI**: `packages/cli/src/commands/context.ts`
+- **Types**: `packages/core/src/index.ts`
+
+---
+
+**Related Documents**:
+- Universal TypeScript standards: `core/standards/typescript.md`
+- OpenCode TypeScript standards: `openagents-repo/standards/opencode-typescript.md`
+- Context system guide: `.opencode/context/core/context-system/guides/creation.md`
+- Context structure: `.opencode/context/core/context-system/standards/structure.md`

+ 624 - 0
.opencode/context/core/standards/typescript.md

@@ -0,0 +1,624 @@
+<!-- Context: core/standards | Priority: critical | Version: 1.0 | Updated: 2026-02-16 -->
+
+# Universal TypeScript Standards
+
+**Purpose**: Universal TypeScript patterns applicable to any TypeScript project  
+**Scope**: Language-level patterns, not framework-specific  
+**Last Updated**: 2026-02-16
+
+---
+
+## Table of Contents
+
+1. [Function Patterns](#1-function-patterns)
+2. [Type Safety](#2-type-safety)
+3. [Array Operations](#3-array-operations)
+4. [Async Patterns](#4-async-patterns)
+5. [Control Flow](#5-control-flow)
+6. [Code Organization](#6-code-organization)
+7. [Testing Principles](#7-testing-principles)
+8. [Variable Naming](#8-variable-naming)
+
+---
+
+## 1. Function Patterns
+
+### 1.1 Naming Convention
+
+**Rule: Prefer single-word function names**
+
+```typescript
+// ✅ GOOD - Single-word names
+export function create() {...}
+export function fork() {...}
+export function touch() {...}
+export function get() {...}
+export async function stream(input: StreamInput) {...}
+
+// ✅ 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()
+```
+
+### 1.2 Pure Functions
+
+**Rule: Prefer pure functions when possible**
+
+```typescript
+// ✅ GOOD - Pure function
+function calculateTotal(items: Item[]): number {
+  return items.reduce((sum, item) => sum + item.price, 0)
+}
+
+// ❌ AVOID - Side effects
+let total = 0
+function addToTotal(item: Item) {
+  total += item.price  // Mutates external state
+}
+```
+
+### 1.3 Function Composition
+
+```typescript
+// ✅ GOOD - Functional composition with pipes
+const filtered = agents
+  .filter((a) => a.mode !== "primary")
+  .filter((a) => hasPermission(a, caller))
+  .map((a) => a.name)
+
+// ✅ GOOD - Higher-order functions
+export function withRetry<T>(fn: () => Promise<T>, maxRetries: number): Promise<T> {
+  return fn().catch((error) => {
+    if (maxRetries > 0) {
+      return withRetry(fn, maxRetries - 1)
+    }
+    throw error
+  })
+}
+```
+
+---
+
+## 2. Type Safety
+
+### 2.1 TypeScript Types
+
+**Rule: Use TypeScript's type system, avoid `any`**
+
+```typescript
+// ✅ GOOD - Explicit types
+interface User {
+  id: string
+  name: string
+  email: string
+}
+
+function getUser(id: string): User {
+  // Implementation
+}
+
+// ❌ AVOID - any type
+function getUser(id: any): any {
+  // Loses all type safety
+}
+```
+
+### 2.2 Type Inference
+
+**Rule: Let TypeScript infer when obvious**
+
+```typescript
+// ✅ GOOD - Inference works
+const count = 42  // TypeScript knows this is number
+const users = await fetchUsers()  // Type inferred from return type
+
+// ❌ AVOID - Redundant annotations
+const count: number = 42
+const users: User[] = await fetchUsers()
+```
+
+### 2.3 Type Guards
+
+**Rule: Use type guards for runtime type checking**
+
+```typescript
+// ✅ GOOD - Type guard
+function isUser(value: unknown): value is User {
+  return (
+    typeof value === "object" &&
+    value !== null &&
+    "id" in value &&
+    "name" in value
+  )
+}
+
+// Usage
+if (isUser(data)) {
+  console.log(data.name)  // TypeScript knows data is User
+}
+```
+
+### 2.4 Avoid Any
+
+**Rule: Use `unknown` instead of `any` when type is truly unknown**
+
+```typescript
+// ✅ GOOD - unknown requires type checking
+function processData(data: unknown) {
+  if (typeof data === "string") {
+    return data.toUpperCase()
+  }
+  throw new Error("Invalid data")
+}
+
+// ❌ AVOID - any bypasses type checking
+function processData(data: any) {
+  return data.toUpperCase()  // No compile-time safety
+}
+```
+
+---
+
+## 3. Array Operations
+
+### 3.1 Functional Methods (Preferred)
+
+**Rule: Prefer map/filter/reduce 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")
+  .flatMap((x) => x.files)
+  .map((x) => path.relative(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 - 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)
+```
+
+### 3.2 For-Loops (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
+
+```typescript
+// ✅ GOOD - Early exit
+const patches = []
+for (const msg of all) {
+  if (msg.info.id === targetID) break
+  for (const part of msg.parts) {
+    if (part.type === "patch") {
+      patches.push(part)
+    }
+  }
+}
+
+// ✅ GOOD - Sequential mutations
+for (const key of Object.keys(tools)) {
+  if (disabled.has(key)) {
+    delete tools[key]
+  }
+}
+```
+
+### 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
+```
+
+---
+
+## 4. Async Patterns
+
+### 4.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([
+  getLanguage(model),
+  getConfig(),
+  getProvider(model.providerID),
+  getAuth(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 getLanguage(model)
+const cfg = await getConfig()  // Could run in parallel!
+const provider = await getProvider(model.providerID)
+```
+
+### 4.2 Sequential Operations
+
+**Rule: Chain when operations depend on previous results**
+
+```typescript
+// ✅ GOOD - Sequential dependency chain
+const session = await createSession({ title: "New" })
+const message = await addMessage(session.id, { content: "Hello" })
+const response = await processMessage(message.id)
+
+// ✅ GOOD - Promise chain for clarity
+const result = await createSession({ title: "New" })
+  .then((session) => addMessage(session.id, { content: "Hello" }))
+  .then((message) => processMessage(message.id))
+```
+
+### 4.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) => {
+  console.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) => {
+      console.error("Item failed", { item, error })
+      return null
+    })
+  }),
+)
+
+// ✅ ACCEPTABLE - try/catch for multiple operations
+try {
+  const session = await createSession(input)
+  await addMessage(session.id, message)
+  await publishEvent({ session })
+  return session
+} catch (error) {
+  console.error("Session creation failed", error)
+  throw error
+}
+
+// ❌ AVOID - try/catch for single operation
+try {
+  const result = await operation()
+  return result
+} catch (error) {
+  console.error(error)
+  throw error
+}
+// Better:
+const result = await operation().catch((error) => {
+  console.error(error)
+  throw error
+})
+```
+
+---
+
+## 5. Control Flow
+
+### 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 getSession(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"
+      }
+    }
+  }
+}
+```
+
+### 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 getSession(id)
+  await update(id, data)
+  return session
+}
+```
+
+### 5.3 Switch Statements
+
+**Rule: Use exhaustive switch with default case**
+
+```typescript
+// ✅ GOOD - Exhaustive switch
+function handleEvent(event: Event) {
+  switch (event.type) {
+    case "start":
+      return handleStart(event)
+    
+    case "update":
+      return handleUpdate(event)
+    
+    case "complete":
+      return handleComplete(event)
+    
+    default:
+      const _exhaustive: never = event
+      throw new Error(`Unhandled event type: ${(event as any).type}`)
+  }
+}
+```
+
+---
+
+## 6. Code Organization
+
+### 6.1 Import Order
+
+**Rule: Organize imports by source**
+
+```typescript
+// ✅ GOOD - Organized imports
+// 1. Node built-ins
+import path from "path"
+import fs from "fs/promises"
+
+// 2. External packages
+import { z } from "zod"
+import express from "express"
+
+// 3. Internal modules
+import { User } from "./types"
+import { getConfig } from "./config"
+```
+
+### 6.2 Naming Conventions
+
+```typescript
+// ✅ GOOD - Clear naming
+const session = await getSession(id)
+const user = await getCurrentUser()
+const messages = await getMessages({ sessionID })
+
+// ❌ BAD - Unnecessary verbosity
+const currentSession = await getSession(id)
+const currentlyAuthenticatedUser = await getCurrentUser()
+const sessionMessagesList = await getMessages({ sessionID })
+
+// ✅ GOOD - Multi-word when single word is ambiguous
+const sessionID = params.id
+const userAgent = req.headers["user-agent"]
+const maxRetries = config.retries
+```
+
+### 6.3 File Structure
+
+**Rule: One primary export per file**
+
+```typescript
+// user.ts
+export interface User {
+  id: string
+  name: string
+}
+
+export async function getUser(id: string): Promise<User> {
+  // Implementation
+}
+
+export async function createUser(data: CreateUserInput): Promise<User> {
+  // Implementation
+}
+```
+
+---
+
+## 7. Testing Principles
+
+### 7.1 Test Structure
+
+**Rule: Follow Arrange-Act-Assert pattern**
+
+```typescript
+// ✅ GOOD - AAA pattern
+test("creates user with valid data", async () => {
+  // Arrange
+  const userData = { name: "Alice", email: "alice@example.com" }
+  
+  // Act
+  const user = await createUser(userData)
+  
+  // Assert
+  expect(user.name).toBe("Alice")
+  expect(user.email).toBe("alice@example.com")
+})
+```
+
+### 7.2 Coverage Goals
+
+**Rule: Test both success and failure cases**
+
+```typescript
+// ✅ GOOD - Both positive and negative tests
+describe("createUser", () => {
+  test("creates user with valid data", async () => {
+    const user = await createUser({ name: "Alice", email: "alice@example.com" })
+    expect(user).toBeDefined()
+  })
+  
+  test("throws error with invalid email", async () => {
+    await expect(
+      createUser({ name: "Alice", email: "invalid" })
+    ).rejects.toThrow("Invalid email")
+  })
+})
+```
+
+### 7.3 Mock External Dependencies
+
+**Rule: Mock all external dependencies**
+
+```typescript
+// ✅ GOOD - Mocked dependencies
+test("fetches user data", async () => {
+  const mockFetch = vi.fn().mockResolvedValue({
+    json: () => Promise.resolve({ id: "1", name: "Alice" })
+  })
+  
+  global.fetch = mockFetch
+  
+  const user = await fetchUser("1")
+  expect(user.name).toBe("Alice")
+})
+```
+
+---
+
+## 8. Variable Naming
+
+### 8.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
+}
+```
+
+### 8.2 Destructuring
+
+**Rule: Avoid unnecessary destructuring, preserve context with dot notation**
+
+```typescript
+// ✅ GOOD - Preserve context
+function process(session: Session) {
+  console.log("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
+  console.log("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([...])
+```
+
+---
+
+## Related Standards
+
+- **OpenCode TypeScript**: `.opencode/context/openagents-repo/standards/opencode-typescript.md` (framework-specific patterns)
+- **Code Quality**: `.opencode/context/core/standards/code-quality.md` (general quality standards)
+- **Test Coverage**: `.opencode/context/core/standards/test-coverage.md` (testing standards)
+
+---
+
+**Version**: 1.0.0  
+**Last Updated**: 2026-02-16  
+**Maintainer**: OpenAgents Control Team

+ 4616 - 0
.opencode/context/openagents-repo/standards/opencode-typescript.md

@@ -0,0 +1,4616 @@
+<!-- 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**

+ 6 - 0
plugins/claude-code/commands/brainstorm.md

@@ -0,0 +1,6 @@
+---
+description: "You MUST use this before any creative work — creating features, building components, adding functionality, or modifying behavior."
+disable-model-invocation: true
+---
+
+Invoke the oac:brainstorming skill and follow it exactly as presented to you

+ 6 - 0
plugins/claude-code/commands/debug.md

@@ -0,0 +1,6 @@
+---
+description: "Use when encountering any bug, test failure, or unexpected behavior — before proposing any fixes."
+disable-model-invocation: true
+---
+
+Invoke the oac:systematic-debugging skill and follow it exactly as presented to you

+ 3 - 431
plugins/claude-code/commands/oac-help.md

@@ -1,434 +1,6 @@
 ---
-name: oac:help
-description: Show usage guide for OpenAgents Control workflow, skills, and commands
-argument-hint: [skill-name]
+description: "Show OAC workflow overview and available skills"
+disable-model-invocation: true
 ---
 
-# OpenAgents Control - Help Guide
-
-$ARGUMENTS
-
-## 🎯 Overview
-
-OpenAgents Control (OAC) brings intelligent multi-agent orchestration to Claude Code with a 6-stage workflow for context-aware development.
-
-## 🏗️ How Everything Works Together
-
-### Architecture Flow
-
-```
-User Request
-    ↓
-┌─────────────────────────────────────────────────────────────┐
-│ using-oac skill (6-stage workflow)                          │
-│                                                              │
-│  Stage 1: Analyze & Discover                                │
-│      ↓                                                       │
-│      └─→ /context-discovery → context-scout subagent        │
-│                                                              │
-│  Stage 2: Plan & Approve                                    │
-│      ↓                                                       │
-│      └─→ Present plan → REQUEST USER APPROVAL               │
-│                                                              │
-│  Stage 3: LoadContext                                       │
-│      ↓                                                       │
-│      ├─→ Read discovered context files                      │
-│      └─→ /external-scout (if external packages needed)      │
-│                                                              │
-│  Stage 4: Execute                                           │
-│      ↓                                                       │
-│      ├─→ Simple: Direct implementation                      │
-│      └─→ Complex: /task-breakdown → task-manager subagent   │
-│           ↓                                                  │
-│           ├─→ /code-execution → coder-agent subagent        │
-│           ├─→ /test-generation → test-engineer subagent     │
-│           └─→ /code-review → code-reviewer subagent         │
-│                                                              │
-│  Stage 5: Validate                                          │
-│      ↓                                                       │
-│      └─→ Run tests, verify acceptance criteria              │
-│                                                              │
-│  Stage 6: Complete                                          │
-│      ↓                                                       │
-│      └─→ Update docs, summarize changes                     │
-└─────────────────────────────────────────────────────────────┘
-    ↓
-Deliverables returned to user
-```
-
-### Skill → Subagent Mapping
-
-| Skill | Invokes | Primary Tools | Purpose |
-|-------|---------|---------------|---------|
-| `/using-oac` | N/A (orchestrator) | All | Main workflow orchestration through 6 stages |
-| `/context-discovery` | `context-scout` | Read, Glob, Grep | Discover relevant context files and standards |
-| `/install-context` | N/A (script runner) | Bash | Download context files from GitHub repository |
-| `/task-breakdown` | `task-manager` | Read, Write, Bash | Break complex features into atomic subtasks |
-| `/code-execution` | `coder-agent` | Read, Write, Edit, Bash | Implement code following discovered standards |
-| `/test-generation` | `test-engineer` | Read, Write, Bash | Generate comprehensive tests using TDD |
-| `/code-review` | `code-reviewer` | Read, Grep, Bash | Perform security and quality code review |
-| `/external-scout` | N/A (direct API) | WebFetch, Context7 | Fetch live documentation for external packages |
-
-### Configuration Hierarchy
-
-OAC uses a layered configuration system with the following priority:
-
-```
-1. .oac (current directory)
-   ↓ Project-specific settings
-   ↓ Overrides global and built-in defaults
-   ↓
-2. ~/.oac (home directory)
-   ↓ Personal defaults across all projects
-   ↓ Overrides built-in defaults
-   ↓
-3. Built-in defaults (plugins/claude-code/.oac.example)
-   ↓ Fallback when no custom config exists
-```
-
-**Configuration sections**:
-- `context.*` - Context download and caching behavior
-- `cleanup.*` - Temporary file cleanup settings
-- `workflow.*` - Workflow automation preferences
-- `external_scout.*` - External documentation fetching
-
-**Example**: If you set `workflow.auto_approve: true` in `~/.oac`, it applies to all projects unless a specific project's `.oac` overrides it.
-
-## 📋 6-Stage Workflow
-
-The OAC workflow ensures context-aware, high-quality code delivery:
-
-### Stage 1: Analyze & Discover
-- Understand requirements and scope
-- Invoke `/context-discovery` to find relevant context files
-- Identify project standards, patterns, and conventions
-
-### Stage 2: Plan & Approve
-- Present implementation plan
-- **REQUEST APPROVAL** before proceeding
-- Confirm approach with user
-
-### Stage 3: LoadContext
-- Read all discovered context files
-- Load coding standards, security patterns, naming conventions
-- Pre-load context for execution stage
-
-### Stage 4: Execute
-- **Simple tasks**: Direct implementation
-- **Complex tasks**: Invoke `/task-breakdown` to decompose into subtasks
-- Follow loaded standards and patterns
-
-### Stage 5: Validate
-- Run tests and validation
-- **STOP on failure** - fix before proceeding
-- Verify acceptance criteria met
-
-### Stage 6: Complete
-- Update documentation
-- Summarize changes
-- Return results
-
-## 🤖 Available Subagents
-
-OAC provides specialized subagents for different tasks:
-
-### task-manager
-Break down complex features into atomic, verifiable subtasks with dependency tracking.
-
-**When to use**: Complex features requiring multiple steps, parallel execution, or dependency management.
-
-**Example**:
-```
-Use the task-manager subagent to break down this feature:
-"Add user authentication with JWT tokens"
-```
-
-### context-scout
-Discover relevant context files, standards, and patterns for your task.
-
-**When to use**: Before implementing any feature, to find coding standards, security patterns, and conventions.
-
-**Example**:
-```
-Use the context-scout subagent to find:
-- TypeScript coding standards
-- Security patterns for authentication
-- API design conventions
-```
-
-### coder-agent
-Execute coding subtasks with full context awareness and self-review.
-
-**When to use**: Implementing specific features or subtasks following discovered standards.
-
-**Example**:
-```
-Use the coder-agent subagent to implement:
-- JWT authentication service
-- Following security patterns from context
-```
-
-### test-engineer
-Generate comprehensive tests using TDD principles.
-
-**When to use**: Creating tests for new features or existing code.
-
-**Example**:
-```
-Use the test-engineer subagent to create tests for:
-- Authentication service
-- Following test standards from context
-```
-
-### code-reviewer
-Perform thorough code review with security and quality analysis.
-
-**When to use**: Reviewing code changes before committing.
-
-**Example**:
-```
-Use the code-reviewer subagent to review:
-- Recent authentication changes
-- Check security patterns and code quality
-```
-
-### context-manager
-Manage context files, discover context roots, validate structure, and organize project context.
-
-**When to use**: Adding context from GitHub/worktrees, validating context files, or organizing context structure.
-
-**Example**:
-```
-Use the context-manager subagent to:
-- Add context from GitHub: github:acme-corp/standards
-- Add context from worktree: worktree:../team-context
-- Validate existing context files
-- Update navigation for discoverability
-```
-
-## 🎨 Available Skills
-
-Skills guide the main agent through specific workflows:
-
-### /using-oac
-Main workflow orchestrator implementing the 6-stage process.
-
-**Auto-invoked**: When you start a development task.
-
-### /context-discovery
-Guide for discovering and loading relevant context files.
-
-**Usage**: `/context-discovery authentication feature`
-
-### /task-breakdown
-Guide for breaking down complex features into subtasks.
-
-**Usage**: `/task-breakdown user authentication system`
-
-### /code-execution
-Guide for executing coding tasks with context awareness.
-
-**Usage**: `/code-execution implement JWT service`
-
-### /test-generation
-Guide for generating comprehensive tests.
-
-**Usage**: `/test-generation authentication service`
-
-### /code-review
-Guide for performing thorough code reviews.
-
-**Usage**: `/code-review src/auth/`
-
-## 📝 Available Commands
-
-### /install-context
-Download context files from the OpenAgents Control registry with interactive profile selection.
-
-**Usage**: `/install-context [--profile=<profile>] [--force] [--dry-run]`
-
-**Profiles**: `essential`, `standard` (recommended), `extended`, `specialized`, `all`
-
-**What it does**:
-- Asks which profile to install
-- Downloads context files from GitHub registry
-- Creates `.context-manifest.json`
-
-### /oac:help
-Show this usage guide (you're reading it now!).
-
-**Usage**: 
-- `/oac:help` - Show general help
-- `/oac:help <skill-name>` - Show help for specific skill
-
-### /oac:status
-Show plugin status and installed context.
-
-**Usage**: `/oac:status`
-
-**What it shows**:
-- Plugin version
-- Installed context version
-- Available subagents and skills
-- Context file count
-
-### /oac:cleanup
-Clean up old temporary files with approval.
-
-**Usage**: `/oac:cleanup`
-
-**What it does**:
-- Finds old session files (>7 days)
-- Finds old task files (>30 days)
-- Finds old external cache (>7 days)
-- Requests approval before deletion
-
-## ⚙️ Configuration Setup
-
-### First-Time Setup
-
-1. **Download context files** (required):
-   ```
-   /install-context --profile=essential
-   ```
-   This downloads coding standards, security patterns, and conventions.
-
-2. **Create configuration** (optional):
-   ```bash
-   # For project-specific settings
-   cp plugins/claude-code/.oac.example .oac
-   
-   # For global settings
-   cp plugins/claude-code/.oac.example ~/.oac
-   ```
-
-3. **Customize settings**:
-   Edit `.oac` to configure:
-   - Auto-download context updates
-   - Cleanup schedules
-   - Workflow preferences
-   - External documentation sources
-
-### Configuration Options
-
-**Context settings**:
-- `context.auto_download: true/false` - Auto-download context on first use
-- `context.categories: core,openagents-repo` - Which context categories to load
-- `context.update_check: true/false` - Check for context updates on startup
-- `context.cache_days: 7` - How long to cache context before suggesting update
-
-**Cleanup settings**:
-- `cleanup.auto_prompt: true/false` - Prompt to clean old temporary files
-- `cleanup.session_days: 7` - Days before session files are considered old
-- `cleanup.task_days: 30` - Days before completed tasks are considered old
-- `cleanup.external_days: 7` - Days before external cache is considered old
-
-**Workflow settings**:
-- `workflow.auto_approve: false` - **WARNING**: Skips approval gates (not recommended)
-- `workflow.verbose: false` - Show detailed workflow progress
-
-**External scout settings**:
-- `external_scout.enabled: true/false` - Enable external documentation fetching
-- `external_scout.cache_enabled: true/false` - Cache external docs locally
-- `external_scout.sources: context7` - Documentation sources to use
-
-## 🚀 Quick Start Examples
-
-### Example 1: Simple Feature
-```
-User: "Add a login endpoint"
-
-Claude (using-oac skill):
-1. Analyze: Understand login requirements
-2. Plan: Present implementation approach → REQUEST APPROVAL
-3. LoadContext: Read API standards, security patterns
-4. Execute: Implement endpoint directly
-5. Validate: Run tests
-6. Complete: Update API docs
-```
-
-### Example 2: Complex Feature
-```
-User: "Build a complete authentication system"
-
-Claude (using-oac skill):
-1. Analyze: Understand auth requirements
-2. Plan: Present high-level approach → REQUEST APPROVAL
-3. LoadContext: Read security patterns, API standards
-4. Execute: Invoke /task-breakdown
-   - Subtask 1: JWT service
-   - Subtask 2: Auth middleware
-   - Subtask 3: Login endpoint
-   - Subtask 4: Refresh token logic
-5. Validate: Run integration tests
-6. Complete: Update docs, summarize
-```
-
-### Example 3: Using Subagents Directly
-```
-# Discover context first
-Use the context-scout subagent to find TypeScript and security patterns.
-
-# Break down complex task
-Use the task-manager subagent to break down the authentication system.
-
-# Implement subtask
-Use the coder-agent subagent to implement JWT service following discovered patterns.
-
-# Generate tests
-Use the test-engineer subagent to create tests for the JWT service.
-
-# Review code
-Use the code-reviewer subagent to review all authentication changes.
-```
-
-## 🔑 Key Principles
-
-### Context First, Code Second
-Always discover and load context before implementing. This ensures your code follows project standards.
-
-### Approval Gates
-OAC requests approval before execution. This prevents unwanted changes and ensures alignment.
-
-### Atomic Tasks
-Complex features are broken into 1-2 hour subtasks with clear acceptance criteria.
-
-### Self-Review
-Every deliverable passes validation before completion (types, imports, anti-patterns, acceptance criteria).
-
-### No Nested Calls
-In Claude Code, only the main agent can invoke subagents. Skills orchestrate the workflow, subagents execute specialized tasks.
-
-## 📚 Learn More
-
-- **Installation & Quick Start**: See `README.md` for full setup guide
-- **Architecture**: See `README.md` for system overview
-- **Context System**: Explore `context/` directory for standards and patterns
-
-## 🆘 Troubleshooting
-
-### "Context files not found"
-Run `/install-context` to download context from GitHub.
-
-### "Subagent not available"
-Verify plugin installation with `/oac:status`.
-
-### "Approval not requested"
-This is a bug - OAC should always request approval before execution. Please report.
-
-### "Nested subagent call error"
-Claude Code doesn't support nested calls. Use skills to orchestrate, not subagents calling subagents.
-
-## 💡 Tips
-
-1. **Start with /install-context** - Download context files first
-2. **Let the workflow guide you** - The using-oac skill handles orchestration
-3. **Use context-scout early** - Discover standards before coding
-4. **Break down complex tasks** - Use task-manager for multi-step features
-5. **Review before committing** - Use code-reviewer for quality checks
-
----
-
-**Version**: 1.0.0  
-**Plugin**: oac  
-**Last Updated**: 2026-02-16
+Invoke the oac:using-oac skill and follow it exactly as presented to you. Then provide a brief summary of available skills and commands.

+ 9 - 104
plugins/claude-code/commands/oac-status.md

@@ -1,110 +1,15 @@
 ---
 name: oac:status
-description: Show OpenAgents Control plugin installation status and context
+description: Show OAC plugin status, installed context, and available skills
 ---
 
-# OpenAgents Control Status
+Show the current OAC plugin status by running:
 
-## Plugin Information
+!ls -la .opencode/context/ 2>/dev/null && echo "Context installed" || echo "No context installed — run /install-context"
+!cat .context-manifest.json 2>/dev/null | head -20 || echo "No manifest found"
 
-**Plugin**: OpenAgents Control (OAC)
-**Version**: !`jq -r '.version' ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json 2>/dev/null || cat ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json | grep '"version"' | sed 's/.*: "\(.*\)".*/\1/'`
-**Repository**: https://github.com/darrenhinde/OpenAgentsControl
-
----
-
-## Context Installation Status
-
-!`PLUGIN_CONTEXT="${CLAUDE_PLUGIN_ROOT}/context"
-MANIFEST="${CLAUDE_PLUGIN_ROOT}/.context-manifest.json"
-if [ -f "$MANIFEST" ]; then
-  echo "✅ **Context Installed**"
-  echo ""
-  echo "### Manifest Details"
-  cat "$MANIFEST" | sed 's/^/    /'
-  echo ""
-  echo "### Installed Context Files"
-  if [ -d "$PLUGIN_CONTEXT" ]; then
-    echo ""
-    echo "**Core Context**:"
-    find "$PLUGIN_CONTEXT/core" -type f -name "*.md" 2>/dev/null | wc -l | xargs -I {} echo "  - {} files"
-    echo ""
-    echo "**Categories**:"
-    find "$PLUGIN_CONTEXT" -type d -mindepth 1 -maxdepth 1 2>/dev/null | xargs -I {} basename {} | sed 's/^/  - /'
-  fi
-else
-  echo "❌ **Context Not Installed**"
-  echo ""
-  echo "Run \`/install-context\` to download context files from GitHub."
-fi`
-
----
-
-## Active Sessions
-
-!`if [ -d ".tmp/sessions" ]; then
-  SESSION_COUNT=$(find .tmp/sessions -maxdepth 1 -type d ! -path .tmp/sessions | wc -l | tr -d ' ')
-  if [ "$SESSION_COUNT" -gt 0 ]; then
-    echo "**Active Sessions**: $SESSION_COUNT"
-    echo ""
-    find .tmp/sessions -maxdepth 1 -type d ! -path .tmp/sessions -exec basename {} \; | sed 's/^/  - /'
-  else
-    echo "**Active Sessions**: None"
-  fi
-else
-  echo "**Active Sessions**: None"
-fi`
-
----
-
-## Available Components
-
-### Custom Subagents
-- `task-manager` - Break down complex features into atomic subtasks
-- `context-scout` - Discover relevant context files for tasks
-- `coder-agent` - Execute coding subtasks with context awareness
-- `test-engineer` - Generate comprehensive test suites
-- `code-reviewer` - Review code for quality and standards
-
-### Skills
-- `/using-oac` - Main 6-stage workflow orchestrator
-- `/context-discovery` - Guide context discovery process
-- `/task-breakdown` - Guide task breakdown process
-- `/code-execution` - Guide code execution process
-- `/test-generation` - Guide test generation process
-- `/code-review` - Guide code review process
-
-### Commands
-- `/install-context` - Download context files from GitHub
-- `/oac:help` - Show usage guide and available skills
-- `/oac:status` - Show this status information
-- `/oac:cleanup` - Clean up old temporary files
-
----
-
-## Recommendations
-
-!`if [ ! -f "${CLAUDE_PLUGIN_ROOT}/.context-manifest.json" ]; then
-  echo "⚠️  **Action Required**: Run \`/install-context\` to install context files"
-elif [ -d ".tmp/sessions" ]; then
-  SESSION_COUNT=$(find .tmp/sessions -maxdepth 1 -type d ! -path .tmp/sessions | wc -l | tr -d ' ')
-  if [ "$SESSION_COUNT" -gt 5 ]; then
-    echo "💡 **Cleanup Suggested**: You have $SESSION_COUNT active sessions. Consider cleaning up old sessions in \`.tmp/sessions/\`"
-  else
-    echo "✅ **All Good**: Plugin is properly configured and ready to use"
-  fi
-else
-  echo "✅ **All Good**: Plugin is properly configured and ready to use"
-fi`
-
----
-
-## Quick Start
-
-To start using OpenAgents Control:
-
-1. **Ensure context is installed**: Run `/install-context` if not already done
-2. **Invoke the main workflow**: Use `/using-oac` or let Claude auto-invoke it
-3. **Get help**: Run `/oac:help` for detailed usage guide
-
-For more information, see the [README](../README.md) or visit the [repository](https://github.com/darrenhinde/OpenAgentsControl).
+Then report:
+- Plugin version: 1.0.0
+- Context status (installed/not installed)
+- Available skills: brainstorming, context-discovery, systematic-debugging, verification-before-completion, task-breakdown, code-execution, test-generation, code-review, external-scout, parallel-execution
+- Available subagents: context-scout, task-manager, coder-agent, test-engineer, code-reviewer, context-manager, external-scout

+ 24 - 16
plugins/claude-code/hooks/session-start.sh

@@ -12,18 +12,7 @@ SKILL_FILE="${PLUGIN_ROOT}/skills/using-oac/SKILL.md"
 using_oac_content=$(cat "${SKILL_FILE}" 2>&1 || echo "Error reading using-oac skill")
 
 # Escape string for JSON embedding
-# SECURITY: This function prevents command injection attacks from malicious SKILL.md files
-# Previous implementation was vulnerable to:
-# - $(command) injection via unescaped dollar signs
-# - `command` injection via unescaped backticks
-# - Control character injection
-#
-# We now escape ALL dangerous characters in the correct order:
-# 1. Backslashes FIRST (to avoid double-escaping)
-# 2. Double quotes (JSON string delimiter)
-# 3. Dollar signs (prevent variable expansion and $(cmd) injection)
-# 4. Backticks (prevent `cmd` command substitution)
-# 5. Newlines, carriage returns, tabs (JSON control characters)
+# SECURITY: Prevents command injection attacks from malicious SKILL.md files
 escape_for_json() {
     local s="$1"
     # Escape backslashes FIRST - order matters!
@@ -39,16 +28,35 @@ escape_for_json() {
 
 using_oac_escaped=$(escape_for_json "$using_oac_content")
 
+# Build skill catalogue from skills directory
+skill_catalogue=""
+if [ -d "${PLUGIN_ROOT}/skills" ]; then
+    for skill_dir in "${PLUGIN_ROOT}/skills"/*/; do
+        skill_name=$(basename "$skill_dir")
+        skill_file="${skill_dir}SKILL.md"
+        if [ -f "$skill_file" ]; then
+            # Extract description from frontmatter
+            description=$(grep -m1 '^description:' "$skill_file" 2>/dev/null | sed 's/^description: *//;s/^"//;s/"$//' || echo "")
+            if [ -n "$description" ]; then
+                skill_catalogue="${skill_catalogue}\n- oac:${skill_name} — ${description}"
+            else
+                skill_catalogue="${skill_catalogue}\n- oac:${skill_name}"
+            fi
+        fi
+    done
+fi
+
 # Build warning message for first-time users
 warning_message=""
-if [[ ! -f "${PLUGIN_ROOT}/.context-manifest.json" ]]; then
-    warning_message="\n\n<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:👋 **Welcome to OpenAgents Control!** To get started, run /install-context to download context files. Then use /oac:help to learn the 6-stage workflow.</important-reminder>"
+if [[ ! -f "${PLUGIN_ROOT}/.context-manifest.json" ]] && [[ ! -f "$(pwd)/.context-manifest.json" ]]; then
+    warning_message="\n\n<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:👋 **Welcome to OpenAgents Control!** To get started, run /install-context to download context files. Then use /brainstorm before building anything.</important-reminder>"
 fi
 
 warning_escaped=$(escape_for_json "$warning_message")
+skill_catalogue_escaped=$(escape_for_json "$skill_catalogue")
 
-# Build context string once, reuse in both output formats
-OAC_CONTEXT="<EXTREMELY_IMPORTANT>\nYou are using OpenAgents Control (OAC).\n\nIN YOUR VERY FIRST REPLY you MUST start with exactly this line (no exceptions):\n🤖 **OAC Active** — 6-stage workflow enabled. Type /oac:help for commands.\n\n**Below is the full content of your 'using-oac' skill - your guide to the 6-stage workflow. For all other skills, use the 'Skill' tool:**\n\n${using_oac_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
+# Build context string
+OAC_CONTEXT="<EXTREMELY_IMPORTANT>\nYou have OAC (OpenAgents Control) superpowers.\n\n**Below is the full content of your 'oac:using-oac' skill — your introduction to using OAC skills. For all other skills, use the 'Skill' tool:**\n\n${using_oac_escaped}\n\n## Available OAC Skills (invoke with the Skill tool):\n${skill_catalogue_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
 
 # Output dual-format JSON for cross-tool compatibility
 # - additionalContext: Claude Code (hookSpecificOutput)

+ 88 - 0
plugins/claude-code/skills/brainstorming/SKILL.md

@@ -0,0 +1,88 @@
+---
+name: brainstorming
+description: "You MUST use this before any creative work — creating features, building components, adding functionality, or modifying behavior. Explores requirements and design before implementation."
+---
+
+# Brainstorming Ideas Into Designs
+
+## Overview
+
+Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
+
+Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval.
+
+<HARD-GATE>
+Do NOT write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity.
+</HARD-GATE>
+
+## Anti-Pattern: "This Is Too Simple To Need A Design"
+
+Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval.
+
+## Checklist
+
+You MUST create a task for each of these items and complete them in order:
+
+1. **Explore project context** — check files, docs, recent commits
+2. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
+3. **Propose 2-3 approaches** — with trade-offs and your recommendation
+4. **Present design** — in sections scaled to their complexity, get user approval after each section
+5. **Transition to implementation** — invoke `oac:context-discovery` to find relevant standards, then proceed with the OAC 6-stage workflow
+
+## Process Flow
+
+```dot
+digraph brainstorming {
+    "Explore project context" [shape=box];
+    "Ask clarifying questions" [shape=box];
+    "Propose 2-3 approaches" [shape=box];
+    "Present design sections" [shape=box];
+    "User approves design?" [shape=diamond];
+    "Invoke oac:context-discovery" [shape=doublecircle];
+
+    "Explore project context" -> "Ask clarifying questions";
+    "Ask clarifying questions" -> "Propose 2-3 approaches";
+    "Propose 2-3 approaches" -> "Present design sections";
+    "Present design sections" -> "User approves design?";
+    "User approves design?" -> "Present design sections" [label="no, revise"];
+    "User approves design?" -> "Invoke oac:context-discovery" [label="yes"];
+}
+```
+
+**The terminal state is invoking context-discovery.** Do NOT write code until context is loaded and the OAC workflow is followed.
+
+## The Process
+
+**Understanding the idea:**
+- Check out the current project state first (files, docs, recent commits)
+- Ask questions one at a time to refine the idea
+- Prefer multiple choice questions when possible, but open-ended is fine too
+- Only one question per message — if a topic needs more exploration, break it into multiple questions
+- Focus on understanding: purpose, constraints, success criteria
+
+**Exploring approaches:**
+- Propose 2-3 different approaches with trade-offs
+- Present options conversationally with your recommendation and reasoning
+- Lead with your recommended option and explain why
+
+**Presenting the design:**
+- Once you believe you understand what you're building, present the design
+- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced
+- Ask after each section whether it looks right so far
+- Cover: architecture, components, data flow, error handling, testing
+- Be ready to go back and clarify if something doesn't make sense
+
+## After the Design
+
+**Implementation:**
+- Invoke `oac:context-discovery` to find relevant context files and standards
+- Then follow the OAC 6-stage workflow (Stage 1 is already done — you have the design)
+
+## Key Principles
+
+- **One question at a time** — Don't overwhelm with multiple questions
+- **Multiple choice preferred** — Easier to answer than open-ended when possible
+- **YAGNI ruthlessly** — Remove unnecessary features from all designs
+- **Explore alternatives** — Always propose 2-3 approaches before settling
+- **Incremental validation** — Present design, get approval before moving on
+- **Be flexible** — Go back and clarify when something doesn't make sense

+ 160 - 0
plugins/claude-code/skills/systematic-debugging/SKILL.md

@@ -0,0 +1,160 @@
+---
+name: systematic-debugging
+description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
+---
+
+# Systematic Debugging
+
+## Overview
+
+Random fixes waste time and create new bugs. Quick patches mask underlying issues.
+
+**Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
+
+**Violating the letter of this process is violating the spirit of debugging.**
+
+## The Iron Law
+
+```
+NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
+```
+
+If you haven't completed Phase 1, you cannot propose fixes.
+
+## When to Use
+
+Use for ANY technical issue:
+- Test failures
+- Bugs in production
+- Unexpected behavior
+- Performance problems
+- Build failures
+- Integration issues
+
+**Use this ESPECIALLY when:**
+- Under time pressure (emergencies make guessing tempting)
+- "Just one quick fix" seems obvious
+- You've already tried multiple fixes
+- Previous fix didn't work
+- You don't fully understand the issue
+
+**Don't skip when:**
+- Issue seems simple (simple bugs have root causes too)
+- You're in a hurry (rushing guarantees rework)
+
+## The Four Phases
+
+You MUST complete each phase before proceeding to the next.
+
+### Phase 1: Root Cause Investigation
+
+**BEFORE attempting ANY fix:**
+
+1. **Read Error Messages Carefully**
+   - Don't skip past errors or warnings
+   - They often contain the exact solution
+   - Read stack traces completely
+   - Note line numbers, file paths, error codes
+
+2. **Reproduce Consistently**
+   - Can you trigger it reliably?
+   - What are the exact steps?
+   - Does it happen every time?
+   - If not reproducible → gather more data, don't guess
+
+3. **Check Recent Changes**
+   - What changed that could cause this?
+   - Git diff, recent commits
+   - New dependencies, config changes
+   - Environmental differences
+
+4. **Gather Evidence in Multi-Component Systems**
+
+   **WHEN system has multiple components:**
+
+   **BEFORE proposing fixes, add diagnostic instrumentation:**
+   ```
+   For EACH component boundary:
+     - Log what data enters component
+     - Log what data exits component
+     - Verify environment/config propagation
+     - Check state at each layer
+
+   Run once to gather evidence showing WHERE it breaks
+   THEN analyze evidence to identify failing component
+   THEN investigate that specific component
+   ```
+
+5. **Trace Data Flow**
+   - Where does bad value originate?
+   - What called this with bad value?
+   - Keep tracing up until you find the source
+   - Fix at source, not at symptom
+
+### Phase 2: Pattern Analysis
+
+**Find the pattern before fixing:**
+
+1. **Find Working Examples** — Locate similar working code in same codebase
+2. **Compare Against References** — Read reference implementation COMPLETELY
+3. **Identify Differences** — List every difference, however small
+4. **Understand Dependencies** — What other components does this need?
+
+### Phase 3: Hypothesis and Testing
+
+**Scientific method:**
+
+1. **Form Single Hypothesis** — "I think X is the root cause because Y"
+2. **Test Minimally** — Make the SMALLEST possible change to test hypothesis
+3. **Verify Before Continuing** — Did it work? Yes → Phase 4. No → form NEW hypothesis
+4. **When You Don't Know** — Say "I don't understand X". Don't pretend to know.
+
+### Phase 4: Implementation
+
+**Fix the root cause, not the symptom:**
+
+1. **Create Failing Test Case** — Simplest possible reproduction. MUST have before fixing.
+2. **Implement Single Fix** — Address the root cause. ONE change at a time.
+3. **Verify Fix** — Test passes? No other tests broken? Issue actually resolved?
+4. **If Fix Doesn't Work** — STOP. Count fixes tried. If ≥ 3: question the architecture.
+
+5. **If 3+ Fixes Failed: Question Architecture**
+   - Is this pattern fundamentally sound?
+   - Should we refactor architecture vs. continue fixing symptoms?
+   - Discuss with user before attempting more fixes
+
+## Red Flags — STOP and Follow Process
+
+If you catch yourself thinking:
+- "Quick fix for now, investigate later"
+- "Just try changing X and see if it works"
+- "Add multiple changes, run tests"
+- "It's probably X, let me fix that"
+- "I don't fully understand but this might work"
+- "One more fix attempt" (when already tried 2+)
+
+**ALL of these mean: STOP. Return to Phase 1.**
+
+## Common Rationalizations
+
+| Excuse | Reality |
+|--------|---------|
+| "Issue is simple, don't need process" | Simple issues have root causes too. |
+| "Emergency, no time for process" | Systematic debugging is FASTER than guess-and-check. |
+| "Just try this first, then investigate" | First fix sets the pattern. Do it right from the start. |
+| "Multiple fixes at once saves time" | Can't isolate what worked. Causes new bugs. |
+| "I see the problem, let me fix it" | Seeing symptoms ≠ understanding root cause. |
+| "One more fix attempt" (after 2+ failures) | 3+ failures = architectural problem. |
+
+## Quick Reference
+
+| Phase | Key Activities | Success Criteria |
+|-------|---------------|------------------|
+| **1. Root Cause** | Read errors, reproduce, check changes, gather evidence | Understand WHAT and WHY |
+| **2. Pattern** | Find working examples, compare | Identify differences |
+| **3. Hypothesis** | Form theory, test minimally | Confirmed or new hypothesis |
+| **4. Implementation** | Create test, fix, verify | Bug resolved, tests pass |
+
+**Related skills:**
+- `oac:verification-before-completion` — Verify fix worked before claiming success
+- `oac:test-generation` — For creating failing test cases (Phase 4, Step 1)

+ 77 - 408
plugins/claude-code/skills/using-oac/SKILL.md

@@ -1,440 +1,109 @@
 ---
 name: using-oac
-description: "Use when starting any development task — building a feature, fixing a bug, refactoring, or making any code change."
+description: "Use when starting any conversation — establishes how to find and use OAC skills, requiring Skill tool invocation BEFORE ANY response including clarifying questions"
 ---
 
 <EXTREMELY-IMPORTANT>
-IF you are starting ANY development task, you MUST use this workflow.
+If you think there is even a 1% chance an OAC skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
 
-This is not negotiable. This is not optional. "Simple" tasks are where skipping stages causes the most wasted work.
+IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
 
-EVERY task follows all 6 stages. NO EXCEPTIONS.
+This is not negotiable. This is not optional. You cannot rationalize your way out of this.
 </EXTREMELY-IMPORTANT>
 
-# OpenAgents Control (OAC) Workflow
+## How to Access Skills
 
-## Overview
+**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
 
-Context-first parallel execution with 6 mandatory stages and 2 approval gates.
+# Using OAC Skills
 
-**Terminal state:** Stage 6 Complete (task documented and summarized)
+## The Rule
 
-**Core value:** **5x faster feature development through parallel multi-agent execution**
-
----
-
-## Anti-Pattern: "This Is Too Simple for the Full Workflow"
-
-Every task goes through all 6 stages. A "simple" email validation, a config change, a "quick fix" — all of them.
-
-"Simple" tasks are where unexamined assumptions cause the most wasted work:
-- No context → implement wrong pattern → code review feedback → rework (30+ min wasted)
-- No approval → build wrong thing → user rejects → start over (hours wasted)
-- No validation → tests fail in CI → debug → fix → re-deploy (hours wasted)
-
-The workflow is fast for simple tasks (5-10 minutes). Skipping it costs 30+ minutes in rework.
-
----
-
-## The Iron Law
-
-```
-CONTEXT FIRST, CODE SECOND
-
-Stage 1 → 2 → APPROVAL GATE → 3 → 4 → 5 → VALIDATION GATE → 6
-            ↑                              ↑
-         APPROVAL                    VALIDATION
-          GATE                          GATE
-```
-
-NO skipping stages. NO coding before approval. NO completion before validation.
-
----
-
-## Workflow Diagram
+**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
 
 ```dot
-digraph oac {
-    rankdir=LR;
-    node [shape=box, style=rounded];
-    
-    s1 [label="Stage 1\nDiscover"];
-    s2 [label="Stage 2\nPlan"];
-    approval [label="User\nApproval?", shape=diamond];
-    s3 [label="Stage 3\nLoad Context"];
-    s4 [label="Stage 4\nExecute"];
-    s5 [label="Stage 5\nValidate"];
-    validation [label="Validation\nPassed?", shape=diamond];
-    s6 [label="Stage 6\nComplete", shape=doublecircle];
-
-    s1 -> s2;
-    s2 -> approval;
-    approval -> s3 [label="yes"];
-    approval -> s2 [label="no\nrevise"];
-    s3 -> s4;
-    s4 -> s5;
-    s5 -> validation;
-    validation -> s6 [label="yes"];
-    validation -> s4 [label="no\nfix"];
+digraph skill_flow {
+    "User message received" [shape=doublecircle];
+    "About to build/create something?" [shape=doublecircle];
+    "Already brainstormed?" [shape=diamond];
+    "Invoke oac:brainstorming skill" [shape=box];
+    "Might any OAC skill apply?" [shape=diamond];
+    "Invoke Skill tool" [shape=box];
+    "Announce: 'Using [skill] to [purpose]'" [shape=box];
+    "Has checklist?" [shape=diamond];
+    "Create TodoWrite todo per item" [shape=box];
+    "Follow skill exactly" [shape=box];
+    "Respond (including clarifications)" [shape=doublecircle];
+
+    "About to build/create something?" -> "Already brainstormed?";
+    "Already brainstormed?" -> "Invoke oac:brainstorming skill" [label="no"];
+    "Already brainstormed?" -> "Might any OAC skill apply?" [label="yes"];
+    "Invoke oac:brainstorming skill" -> "Might any OAC skill apply?";
+
+    "User message received" -> "Might any OAC skill apply?";
+    "Might any OAC skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
+    "Might any OAC skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
+    "Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
+    "Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
+    "Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
+    "Has checklist?" -> "Follow skill exactly" [label="no"];
+    "Create TodoWrite todo per item" -> "Follow skill exactly";
 }
 ```
 
----
-
-## Checklist
-
-Create TodoWrite items for each stage:
-
-1. **Stage 1: Discover** — Invoke `/context-discovery`, get context file list
-2. **Stage 2: Plan** — Create plan (simple: approach, complex: use `/task-breakdown`), present to user
-3. **APPROVAL GATE** — Wait for user confirmation
-4. **Stage 3: LoadContext** — Read ALL discovered context files
-5. **Stage 4: Execute** — Implement (simple: direct, complex: parallel via BatchExecutor)
-6. **Stage 5: Validate** — Run tests, verify criteria, STOP if failed
-7. **VALIDATION GATE** — All tests pass, all criteria met
-8. **Stage 6: Complete** — Update docs, summarize, cleanup
-
----
-
-## The Stages
-
-### Stage 1: Discover
-
-**Goal:** Understand task + find context files
-
-**Actions:**
-1. Analyze request: What to build, scope, risks
-2. Invoke `/context-discovery [task description]`
-3. Capture returned context file list (Critical → High → Medium priority)
-
-**Output:** Context file list
-
----
-
-### Stage 2: Plan
-
-**Goal:** Create execution plan + get approval
-
-**Actions:**
-
-**Simple tasks (1-3 files, <30 min):**
-- Direct implementation approach
-- Files to create/modify
-- Key technical decisions
-
-**Complex tasks (4+ files, >30 min):**
-- High-level breakdown into phases
-- Dependencies between components
-- Use `/task-breakdown` for detailed subtasks
-- **Parallel execution**: TaskManager identifies which subtasks can run in parallel
-
-**Present plan:**
-- Summary of approach
-- Files to be created/modified
-- Context files to be loaded
-- Estimated complexity
-- **Parallel batches** (if complex task)
-
-<HARD-GATE>
-REQUEST APPROVAL. Wait for user confirmation.
-
-DO NOT proceed to Stage 3 without explicit user approval.
-
-This applies to EVERY task, regardless of:
-- Perceived simplicity
-- "Obvious" requirements
-- Time pressure
-- Previous similar approvals
-
-If you think "this is too simple to need approval", STOP. You are rationalizing.
-</HARD-GATE>
-
-**Output:** Approved plan
-
----
-
-### Stage 3: LoadContext
-
-**Goal:** Pre-load ALL discovered context files for parallel execution
-
-**Actions:**
-1. Read EVERY context file from Stage 1:
-   - Use Read tool for each file
-   - Load in priority order (Critical → High → Medium)
-   - DON'T skip any files
-2. Internalize context:
-   - Coding standards
-   - Security patterns
-   - Naming conventions
-   - Architecture constraints
-3. If external libraries involved, invoke `/external-scout [library] [topic]`
-
-**Why this matters for parallel execution:**
-
-Pre-loading prevents race conditions when multiple agents execute in parallel:
-
-```
-WITHOUT pre-loading (race condition):
-CoderAgent 1 (10:00:00) → calls ContextScout → gets standards v1
-CoderAgent 2 (10:00:05) → calls ContextScout → gets standards v2
-Result: Inconsistent implementations ❌
-
-WITH pre-loading (consistent):
-Stage 3 (10:00:00) → Load standards once → Store in session
-CoderAgent 1 (10:01:00) → Uses pre-loaded standards
-CoderAgent 2 (10:01:00) → Uses same pre-loaded standards
-Result: Consistent implementations ✓
-```
-
-**Output:** All context loaded and ready for parallel execution
-
----
-
-### Stage 4: Execute
-
-**Goal:** Implement following loaded context
-
-**Simple tasks (direct execution):**
-1. Implement directly
-2. Follow loaded standards
-3. Apply security patterns
-4. Create tests if required
-5. Self-review before completion
-
-**Complex tasks (parallel execution via BatchExecutor):**
-1. Invoke `/task-breakdown` → TaskManager creates subtasks with `parallel: true` flags
-2. BatchExecutor groups parallelizable subtasks into batches
-3. Execute batches in parallel:
-   ```
-   Batch 1: [Subtask 01, Subtask 02, Subtask 03] ──┐
-                                                     ├─ All run simultaneously
-   Batch 2: [Subtask 04, Subtask 05] ───────────────┘
-   ```
-4. Each parallel agent uses pre-loaded context from Stage 3
-5. Track progress through subtask completion
-
-**Time savings example:**
-- Sequential: 5 subtasks × 30 min = 150 minutes
-- Parallel: 5 subtasks in 2 batches = 60 minutes (2.5x faster)
-
-**Output:** Implementation complete
-
----
-
-### Stage 5: Validate
-
-**Goal:** Verify implementation works
-
-**Actions:**
-1. Run tests (if they exist)
-2. Validate against acceptance criteria
-3. **For parallel execution**: Verify consistency across parallel implementations
-
-<HARD-GATE>
-STOP if validation fails:
-- Tests fail → fix issues before proceeding
-- Criteria unmet → complete implementation
-- Standards violated → refactor to comply
-- **Parallel conflicts** → resolve inconsistencies
-
-DO NOT proceed to Stage 6 until validation passes.
-</HARD-GATE>
-
-**Output:** Validated, working implementation
-
----
-
-### Stage 6: Complete
-
-**Goal:** Finalize with docs and cleanup
-
-**Actions:**
-1. Update documentation (if needed)
-2. Summarize what was done:
-   - Files created/modified
-   - Key technical decisions
-   - **Parallel execution metrics** (if applicable): time saved, batches executed
-   - Follow-up tasks needed
-3. Cleanup (if applicable):
-   - Remove temporary files
-   - Archive session files
-4. Present completion summary to user
-
-**Output:** Task complete, documented, summarized
-
----
-
-## Red Flags - STOP and Follow Workflow
-
-If you catch yourself thinking:
-- "Quick fix, skip context discovery"
-- "Obvious what they want, skip approval"
-- "It's working, skip validation"
-- "Too simple for full workflow"
-- "Context will slow me down"
-- "I'll load context as needed"
-- "Validation is just a formality"
-- "Parallel execution is overkill"
-
-**All of these mean: STOP. Follow the workflow from Stage 1.**
-
-You are rationalizing. The workflow is non-negotiable.
-
----
-
-## Common Rationalizations
-
-| Excuse | Reality |
-|--------|---------|
-| "Too simple for full workflow" | Simple tasks are where skipping stages costs most (30+ min rework) |
-| "I know what they want" | Assumptions cause misalignment. Get approval. (Saves hours) |
-| "Context will slow me down" | Context load = 2 min. Rework = 30+ min. |
-| "Validation is formality" | Skipping validation = bugs in production = emergency fixes |
-| "I'll load context as needed" | Causes race conditions in parallel execution |
-| "Parallel is overkill" | 5x speedup for complex features is not overkill |
-
----
-
-## Key Principles
-
-### Flat Delegation Hierarchy
-
-**Rule**: Only the main agent can invoke subagents. Subagents never call other subagents.
-
-**Correct pattern:**
-```
-Main Agent → /context-discovery → ContextScout
-Main Agent → /task-breakdown → TaskManager
-Main Agent → /code-execution → CoderAgent (multiple in parallel)
-```
-
-**Incorrect pattern (NOT supported):**
-```
-Main Agent → TaskManager → CoderAgent → ContextScout ❌
-```
+## Available OAC Skills
 
-### Context Pre-Loading for Parallel Execution
+| Skill | When to invoke |
+|-------|---------------|
+| `oac:using-oac` | This skill — loaded at session start |
+| `oac:brainstorming` | BEFORE any creative work, building features, adding functionality |
+| `oac:context-discovery` | BEFORE implementing anything — find standards and patterns |
+| `oac:task-breakdown` | When breaking complex features into subtasks |
+| `oac:code-execution` | When implementing code subtasks |
+| `oac:test-generation` | When creating tests |
+| `oac:code-review` | When reviewing code changes |
+| `oac:external-scout` | When working with external libraries/packages |
+| `oac:parallel-execution` | When running multiple agents in parallel |
+| `oac:systematic-debugging` | BEFORE proposing any fix for a bug or test failure |
+| `oac:verification-before-completion` | BEFORE claiming any work is complete or tests pass |
 
-**Why**: Prevents race conditions when multiple agents execute simultaneously
+## Skill Priority
 
-**How**: Stage 3 loads ALL context once, stored in session file, shared across all parallel agents
+When multiple skills could apply, use this order:
 
-### Approval Gates
+1. **Process skills first** (brainstorming, debugging) — these determine HOW to approach the task
+2. **Implementation skills second** (context-discovery, task-breakdown, code-execution) — these guide execution
 
-**Critical checkpoints:**
-- **Stage 2 → 3**: User must approve plan
-- **Stage 5 → 6**: Validation must pass
+"Let's build X" → brainstorming first, then context-discovery, then implementation skills.
+"Fix this bug" → systematic-debugging first, then verification-before-completion.
 
-**Never skip approval** - prevents wasted work and ensures alignment
+## Red Flags
 
-### Parallel Execution (Complex Tasks)
+These thoughts mean STOP — you're rationalizing:
 
-**When**: Complex tasks (4+ files, >30 min) with parallelizable subtasks
+| Thought | Reality |
+|---------|---------|
+| "This is just a simple question" | Questions are tasks. Check for skills. |
+| "I need more context first" | Skill check comes BEFORE clarifying questions. |
+| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
+| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
+| "Let me gather information first" | Skills tell you HOW to gather information. |
+| "This doesn't need a formal skill" | If a skill exists, use it. |
+| "I remember this skill" | Skills evolve. Read current version. |
+| "This doesn't count as a task" | Action = task. Check for skills. |
+| "The skill is overkill" | Simple things become complex. Use it. |
+| "I'll just do this one thing first" | Check BEFORE doing anything. |
+| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
+| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
 
-**How**: 
-1. TaskManager identifies parallel subtasks (`parallel: true`)
-2. BatchExecutor groups into batches
-3. Multiple CoderAgents execute simultaneously
-4. All use same pre-loaded context (Stage 3)
+## Skill Types
 
-**Benefit**: 5x faster for complex features
+**Rigid** (systematic-debugging, verification-before-completion): Follow exactly. Don't adapt away discipline.
 
----
+**Flexible** (brainstorming, context-discovery): Adapt principles to context.
 
-## Skill Invocations
-
-| Skill | Stage | Purpose |
-|-------|-------|---------|
-| `/context-discovery` | 1 | Find context files |
-| `/external-scout` | 3 | Fetch external library docs |
-| `/task-breakdown` | 2 (complex) | Create detailed subtasks with parallel flags |
-| `/code-execution` | 4 | Implement code subtasks (multiple in parallel) |
-| `/test-generation` | 4 | Create test subtasks |
-| `/code-review` | 4 | Review code subtasks |
-
----
-
-## Examples
-
-### Simple Task: Add email validation
-
-**Stage 1:** Discover validation patterns, security standards  
-**Stage 2:** Plan "Add regex to endpoint", get approval  
-**Stage 3:** Load patterns, standards  
-**Stage 4:** Implement validation + tests, self-review  
-**Stage 5:** Run tests, verify criteria  
-**Stage 6:** Update API docs, summarize  
-
-**Time:** ~10 minutes
-
----
-
-### Complex Task: Build authentication system
-
-**Stage 1:** Discover auth patterns, security standards, architecture guides  
-**Stage 2:** Plan "Multi-phase: JWT service, middleware, endpoints, tests", get approval  
-**Stage 3:** Load all discovered files (shared across parallel agents)  
-**Stage 4:** Parallel execution via BatchExecutor:
-```
-Batch 1 (parallel):
-├─ CoderAgent 1 → JWT service
-├─ CoderAgent 2 → Auth middleware  
-└─ CoderAgent 3 → Login endpoint
-
-Batch 2 (parallel):
-├─ CoderAgent 4 → Password reset
-└─ TestEngineer → Test suite
-```
-**Stage 5:** Run full test suite, verify all criteria, check consistency  
-**Stage 6:** Update docs, summarize, show time saved (5x faster)  
-
-**Time:** ~60 minutes (vs 300 minutes sequential = 5x faster)
-
----
-
-## Session Management (Complex Tasks)
-
-**Location**: `.tmp/sessions/{session-id}/`
-
-**Files**:
-- `context.md` - Shared context for all parallel agents
-- `subtasks/` - Individual subtask definitions
-- `.manifest.json` - Parallel execution state tracking
-
-**Cleanup**: After Stage 6, ask user if session files should be deleted
-
----
-
-## OAC vs Sequential Workflows
-
-| Aspect | Sequential (Superpowers) | Parallel (OAC) |
-|--------|--------------------------|----------------|
-| **Model** | 1 agent, sequential | Multiple agents, parallel |
-| **Best for** | Simple tasks (< 1 hour) | Complex features (> 4 hours) |
-| **Speed** | Fast for simple | **5x faster for complex** |
-| **Use case** | "Fix typo" | "Build auth system" |
-
-**When to use OAC**: Multi-component features, complex refactors, large features (4+ hours)
-
----
-
-## Related Skills
-
-- `context-discovery` - Stage 1 context discovery
-- `external-scout` - Stage 3 external library documentation
-- `task-breakdown` - Stage 4 complex task delegation with parallel flags
-- `code-execution` - Stage 4 code implementation (parallel capable)
-- `test-generation` - Stage 4 test creation
-- `code-review` - Stage 4 code review
-
----
+The skill itself tells you which.
 
-## Success Criteria
+## User Instructions
 
-✅ Every task follows all 6 stages in order  
-✅ Context discovered before execution  
-✅ User approval obtained before implementation  
-✅ All context pre-loaded (prevents race conditions in parallel execution)  
-✅ Validation passes before completion  
-✅ Documentation updated and task summarized  
-✅ **Parallel execution used for complex tasks (5x speedup)**  
+Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

+ 112 - 0
plugins/claude-code/skills/verification-before-completion/SKILL.md

@@ -0,0 +1,112 @@
+---
+name: verification-before-completion
+description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs — requires running verification commands and confirming output before making any success claims; evidence before assertions always
+---
+
+# Verification Before Completion
+
+## Overview
+
+Claiming work is complete without verification is dishonesty, not efficiency.
+
+**Core principle:** Evidence before claims, always.
+
+**Violating the letter of this rule is violating the spirit of this rule.**
+
+## The Iron Law
+
+```
+NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE
+```
+
+If you haven't run the verification command in this message, you cannot claim it passes.
+
+## The Gate Function
+
+```
+BEFORE claiming any status or expressing satisfaction:
+
+1. IDENTIFY: What command proves this claim?
+2. RUN: Execute the FULL command (fresh, complete)
+3. READ: Full output, check exit code, count failures
+4. VERIFY: Does output confirm the claim?
+   - If NO: State actual status with evidence
+   - If YES: State claim WITH evidence
+5. ONLY THEN: Make the claim
+
+Skip any step = lying, not verifying
+```
+
+## Common Failures
+
+| Claim | Requires | Not Sufficient |
+|-------|----------|----------------|
+| Tests pass | Test command output: 0 failures | Previous run, "should pass" |
+| Linter clean | Linter output: 0 errors | Partial check, extrapolation |
+| Build succeeds | Build command: exit 0 | Linter passing, logs look good |
+| Bug fixed | Test original symptom: passes | Code changed, assumed fixed |
+| Agent completed | VCS diff shows changes | Agent reports "success" |
+| Requirements met | Line-by-line checklist | Tests passing |
+
+## Red Flags — STOP
+
+- Using "should", "probably", "seems to"
+- Expressing satisfaction before verification ("Great!", "Perfect!", "Done!", etc.)
+- About to commit/push/PR without verification
+- Trusting agent success reports
+- Relying on partial verification
+- **ANY wording implying success without having run verification**
+
+## Rationalization Prevention
+
+| Excuse | Reality |
+|--------|---------|
+| "Should work now" | RUN the verification |
+| "I'm confident" | Confidence ≠ evidence |
+| "Just this once" | No exceptions |
+| "Agent said success" | Verify independently |
+| "Partial check is enough" | Partial proves nothing |
+
+## Key Patterns
+
+**Tests:**
+```
+✅ [Run test command] [See: 34/34 pass] "All tests pass"
+❌ "Should pass now" / "Looks correct"
+```
+
+**Build:**
+```
+✅ [Run build] [See: exit 0] "Build passes"
+❌ "Linter passed" (linter doesn't check compilation)
+```
+
+**Requirements:**
+```
+✅ Re-read plan → Create checklist → Verify each → Report gaps or completion
+❌ "Tests pass, phase complete"
+```
+
+**Agent delegation:**
+```
+✅ Agent reports success → Check VCS diff → Verify changes → Report actual state
+❌ Trust agent report
+```
+
+## When To Apply
+
+**ALWAYS before:**
+- ANY variation of success/completion claims
+- ANY expression of satisfaction
+- ANY positive statement about work state
+- Committing, PR creation, task completion
+- Moving to next task
+- Delegating to agents
+
+## The Bottom Line
+
+**No shortcuts for verification.**
+
+Run the command. Read the output. THEN claim the result.
+
+This is non-negotiable.