فهرست منبع

Merge branch 'main' into feature/oac-package-refactor-v2

darrenhinde 4 ماه پیش
والد
کامیت
3f0d032b0f
31فایلهای تغییر یافته به همراه7873 افزوده شده و 1506 حذف شده
  1. 998 0
      .opencode/context/core/context-system/standards/typescript-coding.md
  2. 624 0
      .opencode/context/core/standards/typescript.md
  3. 4616 0
      .opencode/context/openagents-repo/standards/opencode-typescript.md
  4. 2 1
      .opencode/skill/project-orchestration/router.sh
  5. 23 15
      README.md
  6. 2 2
      plugins/claude-code/.claude-plugin/plugin.json
  7. 7 6
      plugins/claude-code/.context-manifest.json
  8. 6 0
      plugins/claude-code/.oac.json.example
  9. 7 34
      plugins/claude-code/agents/context-scout.md
  10. 6 0
      plugins/claude-code/commands/brainstorm.md
  11. 6 0
      plugins/claude-code/commands/debug.md
  12. 3 104
      plugins/claude-code/commands/install-context.md
  13. 3 431
      plugins/claude-code/commands/oac-help.md
  14. 22 107
      plugins/claude-code/commands/oac-status.md
  15. 2 5
      plugins/claude-code/hooks/hooks.json
  16. 41 16
      plugins/claude-code/hooks/session-start.sh
  17. 40 0
      plugins/claude-code/scripts/.context-manifest.json
  18. 158 205
      plugins/claude-code/scripts/install-context.js
  19. 252 0
      plugins/claude-code/scripts/install-context.sh
  20. 14 1
      plugins/claude-code/scripts/install-context.ts
  21. 11 0
      plugins/claude-code/settings.local.json
  22. 7 7
      plugins/claude-code/skills/context-discovery/SKILL.md
  23. 128 0
      plugins/claude-code/skills/context-discovery/context-discovery-protocol.md
  24. 128 0
      plugins/claude-code/skills/context-setup/SKILL.md
  25. 160 0
      plugins/claude-code/skills/debugger/SKILL.md
  26. 1 1
      plugins/claude-code/skills/external-research/SKILL.md
  27. 0 122
      plugins/claude-code/skills/install-context/SKILL.md
  28. 95 0
      plugins/claude-code/skills/oac-approach/SKILL.md
  29. 93 404
      plugins/claude-code/skills/using-oac/SKILL.md
  30. 112 0
      plugins/claude-code/skills/verification-before-completion/SKILL.md
  31. 306 45
      update.sh

+ 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**

+ 2 - 1
.opencode/skill/project-orchestration/router.sh

@@ -51,7 +51,8 @@ fi
 
 # Find project root
 find_project_root() {
-    local dir="$(pwd)"
+    local dir
+    dir="$(pwd)"
     while [ "$dir" != "/" ]; do
         if [ -d "$dir/.git" ] || [ -f "$dir/package.json" ]; then
             echo "$dir"

+ 23 - 15
README.md

@@ -60,7 +60,7 @@ export async function POST(request: Request) {
 
 **The result:** Production-ready code that ships without heavy rework.
 
-### What Makes AOC Different
+### What Makes OAC Different
 
 **🎯 Context-Aware (Your Secret Weapon)**  
 Agents load YOUR patterns before generating code. Code matches your project from the start. No refactoring needed.
@@ -80,7 +80,7 @@ Store YOUR coding patterns once. Entire team uses same standards. Commit context
 **🔄 Model Agnostic**  
 Use any AI model (Claude, GPT, Gemini, local). No vendor lock-in.
 
-**Full-stack development:** AOC handles both frontend and backend work. The agents coordinate to build complete features from UI to database.
+**Full-stack development:** OAC handles both frontend and backend work. The agents coordinate to build complete features from UI to database.
 
 ---
 
@@ -98,7 +98,7 @@ Use any AI model (Claude, GPT, Gemini, local). No vendor lock-in.
 | **Error Recovery** | ✅ Human-guided validation | ⚠️ Auto-retry (can loop) | ⚠️ Auto-retry | ✅ Self-correcting |
 | **Best For** | Production code, teams | Quick prototypes | Solo developers | Power users, complex projects |
 
-**Use AOC when:**
+**Use OAC when:**
 - ✅ You have established coding patterns
 - ✅ You want code that ships without refactoring
 - ✅ You need approval gates for quality control
@@ -133,6 +133,14 @@ curl -fsSL https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/main/
 bash install.sh
 ```
 
+### Keep Updated
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/main/update.sh | bash
+```
+
+> Use `--install-dir PATH` if you installed to a custom location (e.g. `~/.config/opencode`).
+
 ### Step 2: Start Building
 
 ```bash
@@ -201,7 +209,7 @@ Add a login endpoint
 
 **The problem with AI code:** It doesn't match your patterns. You spend hours refactoring.
 
-**The AOC solution:** Teach your patterns once. Agents load them automatically. Code matches from the start.
+**The OAC solution:** Teach your patterns once. Agents load them automatically. Code matches from the start.
 
 ### How It Works
 
@@ -242,7 +250,7 @@ Ships without refactoring ✅
 - Large token overhead per request
 - Slow responses, high costs
 
-**AOC approach:**
+**OAC approach:**
 - Loads only relevant patterns
 - Context files <200 lines (quick to load)
 - Lazy loading (agents load what they need)
@@ -257,7 +265,7 @@ Ships without refactoring ✅
 
 **The team problem:** Every developer writes code differently. Inconsistent patterns. Hard to maintain.
 
-**The AOC solution:** Store team patterns in `.opencode/context/project/`. Commit to repo. Everyone uses same standards.
+**The OAC solution:** Store team patterns in `.opencode/context/project/`. Commit to repo. Everyone uses same standards.
 
 **Example workflow:**
 ```bash
@@ -326,7 +334,7 @@ Agents are markdown files you can edit. Change workflows, add constraints, custo
 Before generating code, ContextScout discovers relevant patterns from your context files. Ranks by priority (Critical → High → Medium). Prevents wasted work.
 
 **2. Editable Agents - Full Control**  
-Unlike Cursor/Copilot where behavior is baked into plugins, AOC agents are markdown files. Edit them directly:
+Unlike Cursor/Copilot where behavior is baked into plugins, OAC agents are markdown files. Edit them directly:
 ```bash
 nano .opencode/agent/core/opencoder.md  # local project install
 # Or: nano ~/.config/opencode/agent/core/opencoder.md  # global install
@@ -619,7 +627,7 @@ Agents automatically use updated patterns.
 
 ## 🎯 Is This For You?
 
-### ✅ Use AOC if you:
+### ✅ Use OAC if you:
 - Build production code that ships without heavy rework
 - Work in a team with established coding standards
 - Want control over agent behavior (not black-box plugins)
@@ -628,7 +636,7 @@ Agents automatically use updated patterns.
 - Want repeatable, consistent results
 - Use multiple AI models (no vendor lock-in)
 
-### ⚠️ Skip AOC if you:
+### ⚠️ Skip OAC if you:
 - Want fully autonomous execution without approval gates
 - Prefer "just do it" mode over human-guided workflows
 - Don't have established coding patterns yet
@@ -640,14 +648,14 @@ Agents automatically use updated patterns.
 **Try this test:**
 1. Ask your current AI tool to generate an API endpoint
 2. Count how many minutes you spend refactoring it to match your patterns
-3. If you're spending time on refactoring, AOC will save you that time
+3. If you're spending time on refactoring, OAC will save you that time
 
 **Or ask yourself:**
 - Do you have coding standards your team follows?
 - Do you spend time refactoring AI-generated code?
 - Do you want AI to follow YOUR patterns, not generic ones?
 
-If you answered "yes" to any of these, AOC is for you.
+If you answered "yes" to any of these, OAC is for you.
 
 ---
 
@@ -718,15 +726,15 @@ A: Run `/add-context --update` anytime your patterns change. Agents automaticall
 ### Comparison
 
 **Q: How is this different from Cursor/Copilot?**  
-A: AOC has editable agents (not baked-in), approval gates (not auto-execute), context system (YOUR patterns), and MVI token efficiency.
+A: OAC has editable agents (not baked-in), approval gates (not auto-execute), context system (YOUR patterns), and MVI token efficiency.
 
 **Q: How is this different from Aider?**  
-A: AOC has team patterns, context system, approval workflow, and smart pattern discovery. Aider is file-based only.
+A: OAC has team patterns, context system, approval workflow, and smart pattern discovery. Aider is file-based only.
 
 **Q: How does this compare to Oh My OpenCode?**  
-A: Both are built on OpenCode. AOC focuses on **control & repeatability** (approval gates, pattern control, team standards). Oh My OpenCode focuses on **autonomy & speed** (parallel agents, auto-execution). [Read detailed comparison →](https://github.com/darrenhinde/OpenAgentsControl/discussions/116)
+A: Both are built on OpenCode. OAC focuses on **control & repeatability** (approval gates, pattern control, team standards). Oh My OpenCode focuses on **autonomy & speed** (parallel agents, auto-execution). [Read detailed comparison →](https://github.com/darrenhinde/OpenAgentsControl/discussions/116)
 
-**Q: When should I NOT use AOC?**  
+**Q: When should I NOT use OAC?**  
 A: If you want fully autonomous execution without approval gates, or if you don't have established coding patterns yet.
 
 ### Setup

+ 2 - 2
plugins/claude-code/.claude-plugin/plugin.json

@@ -1,7 +1,7 @@
 {
   "name": "oac",
-  "description": "OpenAgents Control - Intelligent code review, TDD test generation, automated documentation, and smart task breakdown with context-aware AI agents",
-  "version": "1.0.0",
+  "description": "OpenAgentsControl — multi-agent orchestration for Claude Code. Context-aware development with skills, subagents, parallel execution, and automated code review.",
+  "version": "1.0.1",
   "author": {
     "name": "darrenhinde",
     "url": "https://github.com/darrenhinde"

+ 7 - 6
plugins/claude-code/.context-manifest.json

@@ -1,11 +1,12 @@
 {
   "version": "1.0.0",
+  "profile": "standard",
   "source": {
-    "repository": "github:darrenhinde/OpenAgentsControl#main",
-    "branch": null,
-    "commit": null,
-    "downloaded_at": null
+    "repository": "darrenhinde/OpenAgentsControl",
+    "branch": "main",
+    "commit": "1442740b4a776a790754c9dbde64ab7f1aa9d4e8",
+    "downloaded_at": "2026-02-23T11:37:10Z"
   },
-  "categories": [],
-  "files": {}
+  "categories": ["core","openagents-repo"],
+  "files": {"core": 86,"openagents-repo": 107}
 }

+ 6 - 0
plugins/claude-code/.oac.json.example

@@ -0,0 +1,6 @@
+{
+  "version": "1",
+  "context": {
+    "root": ".claude/context"
+  }
+}

+ 7 - 34
plugins/claude-code/agents/context-scout.md

@@ -61,7 +61,7 @@ model: haiku
 **3 steps. That's it.**
 
 1. **Understand intent** — What is the user trying to do? What context do they need?
-2. **Follow navigation** — Read `navigation.md` files from `.opencode/context/` downward. They are the map.
+2. **Follow navigation** — Read `navigation.md` files from the resolved `{context_root}` downward. They are the map.
 3. **Return ranked files** — Priority order: Critical → High → Medium. Brief summary per file.
 
 ---
@@ -70,44 +70,17 @@ model: haiku
 
 ### Step 0: Discover Context Root
 
-**Before discovering context files, find where context is stored:**
+**Follow the OAC Context Discovery Protocol exactly.**
 
-**Discovery Order**:
-1. **Check .oac config** - Try reading `.oac` file for `context.root` setting
-2. **Check .claude/context** - Claude Code default location
-3. **Check context** - Simple root-level directory
-4. **Check .opencode/context** - OpenCode/OAC default location
-5. **Check plugin context** - `plugins/claude-code/context/` (installed via /install-context)
-6. **Fallback** - If none found, report error (don't assume location)
+Read the protocol file — its path is in your session context under **OAC System Paths**:
 
-**Process**:
 ```
-# Try reading .oac config
-Read: .oac
-  → If exists, parse JSON and extract context.root
-  → If context.root is set and directory exists, use it
-
-# Try .claude/context
-Glob: .claude/context/navigation.md
-  → If exists, use .claude/context
-
-# Try context
-Glob: context/navigation.md
-  → If exists, use context
-
-# Try .opencode/context
-Glob: .opencode/context/navigation.md
-  → If exists, use .opencode/context
-
-# Try plugin context (installed via /install-context)
-Glob: plugins/claude-code/context/navigation.md
-  → If exists, use plugins/claude-code/context
-
-# If none found
-  → Return error: "No context root found. Run /install-context to download context files."
+Read: {PLUGIN_ROOT}/skills/context-discovery/context-discovery-protocol.md
 ```
 
-**Output**: Context root path (e.g., `.claude/context`, `context`, or `.opencode/context`)
+Execute the protocol (Steps 1–4) and return the resolved `context_root`, `source`, and `write_oac_json` flag to the main agent.
+
+**You cannot write `.oac.json` yourself (read-only agent).** If the protocol says `write_oac_json: true`, include that signal in your response so the main agent can create the file.
 
 ---
 

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

@@ -0,0 +1,6 @@
+---
+description: "Use before implementing anything — discovers context and proposes a plan for approval before writing code."
+disable-model-invocation: true
+---
+
+Invoke the oac:approach 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:debugger skill and follow it exactly as presented to you

+ 3 - 104
plugins/claude-code/commands/install-context.md

@@ -1,110 +1,9 @@
 ---
 name: install-context
-description: Install OpenAgents Control context files from registry with interactive profile selection
-argument-hint: [--profile=<profile>] [--force] [--dry-run] [--verbose]
+description: Install OpenAgents Control context files
+argument-hint: [--profile=<profile>] [--force] [--dry-run]
 ---
 
-# Install Context Command
-
 $ARGUMENTS
 
-## Overview
-
-Install context files from the OpenAgents Control registry with interactive profile selection.
-
-**Available profiles**: essential, standard, extended, specialized, all
-
-**Options**:
-- `--profile=<profile>` - Install specific profile (skips interactive selection)
-- `--force` - Force reinstall even if already installed
-- `--dry-run` - Preview what would be installed without installing
-- `--verbose` - Show detailed installation progress
-- `--categories=<ids>` - Install specific context components (comma-separated)
-
----
-
-## Usage
-
-### Interactive Mode (Recommended)
-
-```
-/install-context
-```
-
-This will:
-1. Check current installation status
-2. Ask which profile to install
-3. Confirm before proceeding
-4. Run installation
-5. Verify files
-6. Offer cleanup options
-
-### Direct Mode (With Options)
-
-```
-# Install essential profile
-/install-context --profile=essential
-
-# Force reinstall standard profile
-/install-context --profile=standard --force
-
-# Preview extended profile
-/install-context --profile=extended --dry-run
-
-# Install with verbose output
-/install-context --profile=standard --verbose
-
-# Install specific components
-/install-context --categories=core-standards,openagents-repo
-```
-
----
-
-## How It Works
-
-This command invokes the `install-context` skill which:
-
-1. **Checks current installation** - Shows what's already installed (if any)
-2. **Asks for profile** - Interactive selection or uses `--profile` option
-3. **Confirms installation** - Shows summary and asks for confirmation
-4. **Runs TypeScript installer** - Executes `scripts/install-context.ts`
-5. **Verifies installation** - Checks files exist and manifest is created
-6. **Offers cleanup** - Asks about cleaning up temporary files
-
----
-
-## Profile Descriptions
-
-**essential** (Recommended for getting started)
-- Minimal components for basic functionality
-- ~4 components, ~30 seconds download
-- Includes: core patterns, project context, navigation
-
-**standard** (Recommended for most users)
-- Standard components for typical use
-- ~12 components, ~2 minutes download
-- Includes: essential + development workflows + common patterns
-
-**extended** (For advanced features)
-- Extended components for advanced features
-- ~30 components, ~5 minutes download
-- Includes: standard + specialized domains + advanced patterns
-
-**specialized** (For specific domains)
-- Specialized components for specific domains
-- ~50 components, ~10 minutes download
-- Includes: extended + domain-specific contexts (UI, data, product, etc.)
-
-**all** (Complete installation)
-- All available context components
-- ~191 components, ~20 minutes download
-- Includes: Everything in the registry
-
----
-
-## Examples
-
-### Example 1: First-time Installation
-
-```
-User: /install-context
+Use the `context-setup` skill to guide through installing context files for this project.

+ 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.

+ 22 - 107
plugins/claude-code/commands/oac-status.md

@@ -1,110 +1,25 @@
 ---
-name: oac:status
-description: Show OpenAgents Control plugin installation status and context
+name: oac-status
+description: Show OAC plugin status, installed context, and available skills
 ---
 
-# OpenAgents Control Status
-
-## Plugin Information
-
-**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).
+Show the current OAC plugin status.
+
+Run:
+```bash
+echo "=== Context Installation ===" && \
+  cat "${CLAUDE_PLUGIN_ROOT}/.context-manifest.json" 2>/dev/null | grep -E '"profile"|"downloaded_at"|"version"' || echo "Not installed — run /install-context" && \
+  echo "" && \
+  echo "=== Context Root ===" && \
+  cat .oac.json 2>/dev/null || echo "No .oac.json — context root not pinned" && \
+  echo "" && \
+  echo "=== Context Files ===" && \
+  ls "${CLAUDE_PLUGIN_ROOT}/context/" 2>/dev/null | wc -l | xargs -I{} echo "{} component directories installed"
+```
+
+Then report in plain language:
+- **Plugin version:** 1.0.0
+- **Context status:** installed profile + component count, or "not installed"
+- **Context root:** path from `.oac.json`, or "not pinned (run /install-context)"
+- **Available skills:** install-context, context-discovery, approach, debugger, verification-before-completion, task-breakdown, code-execution, test-generation, code-review, external-research, parallel-execution
+- **Available subagents:** context-scout, task-manager, coder-agent, test-engineer, code-reviewer, external-scout

+ 2 - 5
plugins/claude-code/hooks/hooks.json

@@ -1,15 +1,12 @@
 {
-  "description": "OAC session initialization — injects workflow context on session start",
   "hooks": {
     "SessionStart": [
       {
-        "matcher": "startup|resume|clear|compact",
         "hooks": [
           {
             "type": "command",
-            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh",
-            "timeout": 30,
-            "async": false
+            "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh\"",
+            "timeout": 30
           }
         ]
       }

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

@@ -11,19 +11,9 @@ SKILL_FILE="${PLUGIN_ROOT}/skills/using-oac/SKILL.md"
 # Read using-oac content
 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 +29,51 @@ escape_for_json() {
 
 using_oac_escaped=$(escape_for_json "$using_oac_content")
 
+# Build skill catalogue from skills directory
+# Use real newlines (not literal \n) so escape_for_json encodes them correctly as \n in JSON
+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
+# Context can be installed project-local (.claude/.context-manifest.json)
+# or globally (~/.claude/.context-manifest.json)
+PROJECT_MANIFEST="$(pwd)/.claude/.context-manifest.json"
+GLOBAL_MANIFEST="${HOME}/.claude/.context-manifest.json"
 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 "${PROJECT_MANIFEST}" ]] && [[ ! -f "${GLOBAL_MANIFEST}" ]]; then
+    warning_message="\n\n<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER: **No context files found.** Invoke the context-setup skill now to set up context for this project — it will check your project type and recommend the right profile. Say: \"I notice you don't have context files set up. Let me get that sorted for you.\" Then immediately run the context-setup skill.</important-reminder>"
 fi
 
 warning_escaped=$(escape_for_json "$warning_message")
+skill_catalogue_escaped=$(escape_for_json "$skill_catalogue")
+
+# Build OAC system paths block
+PROTOCOL_PATH="${PLUGIN_ROOT}/skills/context-discovery/context-discovery-protocol.md"
+plugin_root_escaped=$(escape_for_json "$PLUGIN_ROOT")
+protocol_path_escaped=$(escape_for_json "$PROTOCOL_PATH")
+OAC_SYSTEM_PATHS="## OAC System Paths\n- Plugin Root: ${plugin_root_escaped}\n- Context Discovery Protocol: ${protocol_path_escaped}"
+
+# Context scout instruction — run proactively at session start when context is available
+CONTEXT_SCOUT_INSTRUCTION="## Context Discovery\nBefore responding to any coding or implementation request this session, use the 'oac:context-discovery' skill to locate the project's coding standards. This runs once per session — do not repeat it if already done."
+
+context_scout_escaped=$(escape_for_json "$CONTEXT_SCOUT_INSTRUCTION")
 
-# 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).\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${OAC_SYSTEM_PATHS}\n\n${context_scout_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
 
 # Output dual-format JSON for cross-tool compatibility
 # - additionalContext: Claude Code (hookSpecificOutput)

+ 40 - 0
plugins/claude-code/scripts/.context-manifest.json

@@ -0,0 +1,40 @@
+{
+  "version": "1.0.0",
+  "profile": "essential",
+  "source": {
+    "repository": "darrenhinde/OpenAgentsControl",
+    "branch": "main",
+    "commit": "5f4991633f5349385859a08da76c8b33f3a033e1",
+    "downloaded_at": "2026-02-16T12:20:32.201Z"
+  },
+  "context": [
+    {
+      "id": "essential-patterns",
+      "name": "Essential Patterns",
+      "path": ".opencode/context/core/essential-patterns.md",
+      "local_path": "/Users/darrenhinde/Documents/GitHub/MYBUSINESS/OpenAgentsControl/plugins/claude-code/scripts/context/core/essential-patterns.md",
+      "category": "essential"
+    },
+    {
+      "id": "project-context",
+      "name": "Project Context",
+      "path": ".opencode/context/project/project-context.md",
+      "local_path": "/Users/darrenhinde/Documents/GitHub/MYBUSINESS/OpenAgentsControl/plugins/claude-code/scripts/context/project/project-context.md",
+      "category": "essential"
+    },
+    {
+      "id": "context-paths-config",
+      "name": "Context Paths Configuration",
+      "path": ".opencode/context/core/config/paths.json",
+      "local_path": "/Users/darrenhinde/Documents/GitHub/MYBUSINESS/OpenAgentsControl/plugins/claude-code/scripts/context/core/config/paths.json",
+      "category": "essential"
+    },
+    {
+      "id": "root-navigation",
+      "name": "Root Navigation",
+      "path": ".opencode/context/navigation.md",
+      "local_path": "/Users/darrenhinde/Documents/GitHub/MYBUSINESS/OpenAgentsControl/plugins/claude-code/scripts/context/navigation.md",
+      "category": "essential"
+    }
+  ]
+}

+ 158 - 205
plugins/claude-code/scripts/install-context.js

@@ -1,34 +1,34 @@
 #!/usr/bin/env node
 /**
  * install-context.js
- * Simple context installer for OAC Claude Code Plugin
- * 
- * Downloads context files from OpenAgents Control repository
- * Supports profile-based installation (core, full, custom)
+ * Downloads OAC context files to .claude/context/ in the current project.
+ *
+ * Requirements: node, git (nothing else to install)
+ *
+ * Run from project root:
+ *   node install-context.js [--profile=standard] [--force] [--dry-run]
  */
 
 const { execSync } = require('child_process');
-const { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } = require('fs');
+const { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, mkdtempSync } = require('fs');
 const { join } = require('path');
+const os = require('os');
 
 // Configuration
 const GITHUB_REPO = 'darrenhinde/OpenAgentsControl';
 const GITHUB_BRANCH = 'main';
 const CONTEXT_SOURCE_PATH = '.opencode/context';
-const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT || process.cwd();
-const CONTEXT_DIR = join(PLUGIN_ROOT, 'context');
-const MANIFEST_FILE = join(PLUGIN_ROOT, '.context-manifest.json');
+
+// Roots resolved after parsing --global flag in main()
+const PROJECT_ROOT = process.cwd();
+const GLOBAL_ROOT = join(os.homedir(), '.claude');
 
 // Installation profiles
 const PROFILES = {
   core: {
-    name: 'Core',
-    description: 'Essential standards and workflows',
-    categories: ['core', 'openagents-repo']
+    categories: ['core', 'openagents-repo'],
   },
   full: {
-    name: 'Full',
-    description: 'All available context',
     categories: [
       'core',
       'openagents-repo',
@@ -39,225 +39,159 @@ const PROFILES = {
       'product',
       'learning',
       'project',
-      'project-intelligence'
-    ]
-  }
+      'project-intelligence',
+    ],
+  },
 };
 
-// Colors for output
-const colors = {
-  reset: '\x1b[0m',
-  red: '\x1b[31m',
-  green: '\x1b[32m',
-  yellow: '\x1b[33m',
-  blue: '\x1b[34m',
+// Map user-facing profile names → internal profile names
+const PROFILE_MAP = {
+  'essential':   'core',
+  'standard':    'core',
+  'extended':    'full',
+  'specialized': 'full',
+  'all':         'full',
+  'core':        'core',
+  'full':        'full',
 };
 
-// Logging helpers
+// Colors
+const c = {
+  reset:  '\x1b[0m',
+  red:    '\x1b[31m',
+  green:  '\x1b[32m',
+  yellow: '\x1b[33m',
+  blue:   '\x1b[34m',
+};
 const log = {
-  info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
-  success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
-  warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
-  error: (msg) => console.error(`${colors.red}✗${colors.reset} ${msg}`),
+  info:    (msg) => console.log(`${c.blue}ℹ${c.reset} ${msg}`),
+  success: (msg) => console.log(`${c.green}✓${c.reset} ${msg}`),
+  warning: (msg) => console.log(`${c.yellow}⚠${c.reset} ${msg}`),
+  error:   (msg) => console.error(`${c.red}✗${c.reset} ${msg}`),
 };
 
-/**
- * Check if required commands are available
- */
 function checkDependencies() {
-  const required = ['git'];
-  const missing = [];
-
-  for (const cmd of required) {
-    try {
-      execSync(`command -v ${cmd}`, { stdio: 'ignore' });
-    } catch {
-      missing.push(cmd);
-    }
-  }
-
-  if (missing.length > 0) {
-    log.error(`Missing required dependencies: ${missing.join(', ')}`);
-    log.info('Install with: brew install ' + missing.join(' '));
+  try {
+    execSync('command -v git', { stdio: 'ignore' });
+  } catch {
+    log.error('git is required but not found.');
+    log.info('Install: brew install git (Mac) or sudo apt install git (Linux)');
     process.exit(1);
   }
 }
 
 /**
- * Download context using git sparse-checkout
+ * Download context via git sparse-checkout.
+ * Returns the commit SHA of the downloaded content.
  */
-function downloadContext(categories) {
-  const tempDir = join(PLUGIN_ROOT, '.tmp-context-download');
+function downloadContext(categories, contextDir) {
+  const tempDir = mkdtempSync(join(os.tmpdir(), 'oac-context-'));
 
   try {
-    log.info(`Downloading context from ${GITHUB_REPO}...`);
-    log.info(`Categories: ${categories.join(', ')}`);
-
-    // Clean up temp directory if it exists
-    if (existsSync(tempDir)) {
-      rmSync(tempDir, { recursive: true, force: true });
-    }
+    log.info(`Downloading from ${GITHUB_REPO}...`);
 
-    // Clone with sparse checkout (no working tree files)
     log.info('Cloning repository...');
     execSync(
       `git clone --depth 1 --filter=blob:none --sparse https://github.com/${GITHUB_REPO}.git "${tempDir}"`,
       { stdio: 'pipe' }
     );
 
-    // Configure sparse checkout for requested categories
-    log.info('Configuring sparse checkout...');
-    const sparseCheckoutPaths = categories.map(cat => `${CONTEXT_SOURCE_PATH}/${cat}`);
-    
-    // Also include root navigation
-    sparseCheckoutPaths.push(`${CONTEXT_SOURCE_PATH}/navigation.md`);
+    const commitSha = execSync(`git -C "${tempDir}" rev-parse HEAD`, { encoding: 'utf-8' }).trim();
 
+    log.info('Configuring sparse checkout...');
+    const sparsePaths = [
+      ...categories.map(cat => `${CONTEXT_SOURCE_PATH}/${cat}`),
+      `${CONTEXT_SOURCE_PATH}/navigation.md`,
+    ];
     execSync(
-      `cd "${tempDir}" && git sparse-checkout set ${sparseCheckoutPaths.join(' ')}`,
+      `git -C "${tempDir}" sparse-checkout set --skip-checks ${sparsePaths.join(' ')}`,
       { stdio: 'pipe' }
     );
 
-    // Create context directory if it doesn't exist
-    if (!existsSync(CONTEXT_DIR)) {
-      mkdirSync(CONTEXT_DIR, { recursive: true });
-    }
-
-    // Copy files to context directory
     log.info('Copying context files...');
-    const sourceContextDir = join(tempDir, CONTEXT_SOURCE_PATH);
-    
-    if (existsSync(sourceContextDir)) {
-      execSync(`cp -r "${sourceContextDir}"/* "${CONTEXT_DIR}"/`, { stdio: 'pipe' });
-      log.success('Context files downloaded successfully');
-    } else {
+    mkdirSync(contextDir, { recursive: true });
+    const sourceDir = join(tempDir, CONTEXT_SOURCE_PATH);
+
+    if (!existsSync(sourceDir)) {
       throw new Error('Context directory not found in repository');
     }
+    execSync(`cp -r "${sourceDir}/"* "${contextDir}/"`, { stdio: 'pipe' });
 
-    // Count downloaded files
-    const fileCount = execSync(`find "${CONTEXT_DIR}" -type f | wc -l`, { encoding: 'utf-8' }).trim();
-    log.success(`Downloaded ${fileCount} files`);
+    const fileCount = execSync(`find "${contextDir}" -type f | wc -l`, { encoding: 'utf-8' }).trim();
+    log.success(`Downloaded ${fileCount.trim()} files`);
 
-    // Clean up temp directory
-    rmSync(tempDir, { recursive: true, force: true });
+    return commitSha;
 
   } catch (error) {
     log.error('Failed to download context');
-    if (error instanceof Error) {
-      log.error(error.message);
-    }
-    
-    // Clean up on error
-    if (existsSync(tempDir)) {
-      rmSync(tempDir, { recursive: true, force: true });
-    }
-    
+    if (error instanceof Error) log.error(error.message);
     process.exit(1);
+  } finally {
+    rmSync(tempDir, { recursive: true, force: true });
   }
 }
 
-/**
- * Create manifest file tracking installation
- */
-function createManifest(profile, categories) {
-  try {
-    // Get commit SHA
-    const tempDir = join(PLUGIN_ROOT, '.tmp-manifest-check');
-    
-    if (existsSync(tempDir)) {
-      rmSync(tempDir, { recursive: true, force: true });
-    }
-
-    execSync(
-      `git clone --depth 1 https://github.com/${GITHUB_REPO}.git "${tempDir}"`,
-      { stdio: 'pipe' }
-    );
-
-    const commitSha = execSync(`cd "${tempDir}" && git rev-parse HEAD`, { encoding: 'utf-8' }).trim();
-    
-    rmSync(tempDir, { recursive: true, force: true });
-
-    // Count files per category
-    const files = {};
-    for (const category of categories) {
-      const categoryPath = join(CONTEXT_DIR, category);
-      if (existsSync(categoryPath)) {
-        const count = parseInt(
-          execSync(`find "${categoryPath}" -type f | wc -l`, { encoding: 'utf-8' }).trim(),
-          10
-        );
-        files[category] = count;
-      }
+function createManifest(profile, categories, commitSha, contextDir, manifestFile) {
+  const files = {};
+  for (const category of categories) {
+    const categoryPath = join(contextDir, category);
+    if (existsSync(categoryPath)) {
+      files[category] = parseInt(
+        execSync(`find "${categoryPath}" -type f | wc -l`, { encoding: 'utf-8' }).trim(),
+        10
+      );
     }
-
-    // Create manifest
-    const manifest = {
-      version: '1.0.0',
-      profile,
-      source: {
-        repository: GITHUB_REPO,
-        branch: GITHUB_BRANCH,
-        commit: commitSha,
-        downloaded_at: new Date().toISOString(),
-      },
-      categories,
-      files,
-    };
-
-    writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
-    log.success(`Created manifest: ${MANIFEST_FILE}`);
-
-  } catch (error) {
-    log.warning('Failed to create manifest (non-fatal)');
   }
+
+  const manifest = {
+    version: '1.0.0',
+    profile,
+    source: {
+      repository: GITHUB_REPO,
+      branch: GITHUB_BRANCH,
+      commit: commitSha,
+      downloaded_at: new Date().toISOString(),
+    },
+    categories,
+    files,
+  };
+
+  mkdirSync(join(manifestFile, '..'), { recursive: true });
+  writeFileSync(manifestFile, JSON.stringify(manifest, null, 2));
+  log.success(`Manifest created: ${manifestFile}`);
 }
 
-/**
- * Show usage information
- */
 function showUsage() {
   console.log(`
-Usage: node install-context.js [PROFILE] [OPTIONS]
-
-PROFILES:
-  core              Download core context only (default)
-                    Categories: core, openagents-repo
-  
-  full              Download all available context
-                    Categories: all domains
+Usage: node install-context.js [OPTIONS]
 
-  custom            Specify custom categories
-                    Use with --category flags
+Downloads OAC context files. Requirements: node, git (nothing else to install)
+Works on Mac, Linux, and Windows.
 
 OPTIONS:
-  --category=NAME   Add specific category (use with 'custom' profile)
-  --force           Force re-download even if context exists
-  --help            Show this help message
+  --profile=NAME    Profile: essential, standard, extended, all (default: standard)
+  --global          Install to ~/.claude/context/ (all projects share it)
+  --force           Re-download even if already installed
+  --dry-run         Show what would be installed without downloading
+  --help            Show this help
 
-EXAMPLES:
-  # Install core context (default)
-  node install-context.js
-
-  # Install full context
-  node install-context.js full
-
-  # Install custom categories
-  node install-context.js custom --category=core --category=development
+PROFILES:
+  essential/standard  Core context (core + openagents-repo)
+  extended/all        Full context (all categories)
 
-  # Force reinstall
-  node install-context.js core --force
+SCOPE:
+  default (no flag)   Installs to .claude/context/ in the current project
+  --global            Installs to ~/.claude/context/ for all projects
 `);
 }
 
-/**
- * Main installation function
- */
 function main() {
   const args = process.argv.slice(2);
-
-  // Parse arguments
-  let profile = 'core';
+  let profileName = 'standard';
   let customCategories = [];
+  let isGlobal = false;
   let force = false;
+  let dryRun = false;
 
   for (const arg of args) {
     if (arg === '--help' || arg === '-h') {
@@ -265,10 +199,21 @@ function main() {
       process.exit(0);
     } else if (arg === '--force') {
       force = true;
+    } else if (arg === '--dry-run') {
+      dryRun = true;
+    } else if (arg === '--global') {
+      isGlobal = true;
+    } else if (arg.startsWith('--profile=')) {
+      profileName = arg.split('=')[1];
+      if (!PROFILE_MAP[profileName]) {
+        log.error(`Unknown profile: ${profileName}`);
+        log.info(`Valid profiles: ${Object.keys(PROFILE_MAP).join(', ')}`);
+        process.exit(1);
+      }
     } else if (arg.startsWith('--category=')) {
       customCategories.push(arg.split('=')[1]);
-    } else if (arg === 'core' || arg === 'full' || arg === 'custom') {
-      profile = arg;
+    } else if (PROFILE_MAP[arg]) {
+      profileName = arg;
     } else {
       log.error(`Unknown argument: ${arg}`);
       showUsage();
@@ -276,53 +221,61 @@ function main() {
     }
   }
 
-  // Determine categories to install
-  let categories;
-  if (profile === 'custom') {
-    if (customCategories.length === 0) {
-      log.error('Custom profile requires --category flags');
-      showUsage();
-      process.exit(1);
-    }
-    categories = customCategories;
-  } else {
-    categories = PROFILES[profile].categories;
-  }
+  // Resolve install targets based on scope
+  const installRoot = isGlobal ? GLOBAL_ROOT : join(PROJECT_ROOT, '.claude');
+  const CONTEXT_DIR = join(installRoot, 'context');
+  const MANIFEST_FILE = join(installRoot, '.context-manifest.json');
+  const scopeLabel = isGlobal ? 'global (~/.claude/context)' : 'project (.claude/context)';
 
-  // Check if context already exists
+  const categories = customCategories.length > 0
+    ? (profileName = 'custom', customCategories)
+    : PROFILES[PROFILE_MAP[profileName] || 'core'].categories;
+
+  // Already installed?
   if (existsSync(MANIFEST_FILE) && !force) {
-    log.warning('Context already installed. Use --force to reinstall.');
-    log.info('Current installation:');
+    log.warning(`Context already installed at ${scopeLabel}. Use --force to reinstall.`);
     try {
       const manifest = JSON.parse(readFileSync(MANIFEST_FILE, 'utf-8'));
-      console.log(JSON.stringify(manifest, null, 2));
-    } catch {
-      log.error('Failed to read manifest');
-    }
+      log.info(`Profile: ${manifest.profile}, installed: ${manifest.source?.downloaded_at?.slice(0, 10)}`);
+    } catch { /* ignore */ }
     process.exit(0);
   }
 
-  // Check dependencies
   checkDependencies();
 
-  // Download context
   console.log('');
-  log.info(`Installing ${profile} profile`);
-  log.info(`Repository: ${GITHUB_REPO}`);
-  log.info(`Branch: ${GITHUB_BRANCH}`);
+  log.info(`Scope:      ${scopeLabel}`);
+  log.info(`Profile:    ${profileName}`);
+  log.info(`Categories: ${categories.join(', ')}`);
+  log.info(`Target:     ${CONTEXT_DIR}`);
   console.log('');
 
-  downloadContext(categories);
+  if (dryRun) {
+    log.info('Dry run — no files downloaded');
+    return;
+  }
+
+  const commitSha = downloadContext(categories, CONTEXT_DIR);
+  createManifest(profileName, categories, commitSha, CONTEXT_DIR, MANIFEST_FILE);
 
-  // Create manifest
-  createManifest(profile, categories);
+  if (isGlobal) {
+    log.info('Global install — no .oac.json needed (discovery chain finds ~/.claude/context automatically)');
+  } else {
+    const oacJson = join(PROJECT_ROOT, '.oac.json');
+    if (!existsSync(oacJson)) {
+      writeFileSync(oacJson, JSON.stringify({ version: '1', context: { root: '.claude/context' } }, null, 2));
+      log.success('.oac.json created at project root');
+    } else {
+      log.info('.oac.json already exists — skipping');
+    }
+  }
 
   console.log('');
   log.success('Context installation complete!');
-  log.info(`Context location: ${CONTEXT_DIR}`);
+  log.info(`Scope:    ${scopeLabel}`);
+  log.info(`Context:  ${CONTEXT_DIR}`);
   log.info(`Manifest: ${MANIFEST_FILE}`);
   console.log('');
 }
 
-// Run main function
 main();

+ 252 - 0
plugins/claude-code/scripts/install-context.sh

@@ -0,0 +1,252 @@
+#!/usr/bin/env bash
+# install-context.sh — Download OAC context files to .claude/context/
+#
+# Requirements: bash, git (nothing else to install)
+#
+# Run from project root:
+#   bash install-context.sh [--profile=standard] [--force] [--dry-run]
+
+set -euo pipefail
+
+GITHUB_REPO="darrenhinde/OpenAgentsControl"
+GITHUB_BRANCH="main"
+CONTEXT_SOURCE_PATH=".opencode/context"
+
+# Paths are set in main() after parsing --global flag
+PROJECT_ROOT="$(pwd)"
+GLOBAL_ROOT="${HOME}/.claude"
+
+# Colors
+RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
+log_info()    { echo -e "${BLUE}ℹ${NC} $*"; }
+log_success() { echo -e "${GREEN}✓${NC} $*"; }
+log_warning() { echo -e "${YELLOW}⚠${NC} $*"; }
+log_error()   { echo -e "${RED}✗${NC} $*" >&2; }
+
+usage() {
+  cat <<EOF
+Usage: $(basename "$0") [OPTIONS]
+
+Download OAC context files. Requirements: git (nothing else to install)
+
+OPTIONS:
+  --profile=NAME    Profile: essential, standard, extended, all (default: standard)
+  --global          Install to ~/.claude/context/ (all projects share it)
+  --force           Re-download even if already installed
+  --dry-run         Show what would be installed without downloading
+  --help            Show this help
+
+PROFILES:
+  essential/standard  Core context (core + openagents-repo)
+  extended/all        Full context (all categories)
+
+SCOPE:
+  default (no flag)   Installs to .claude/context/ in the current project
+  --global            Installs to ~/.claude/context/ for all projects
+EOF
+  exit 0
+}
+
+get_categories() {
+  case "${1:-standard}" in
+    essential|standard|core)
+      echo "core openagents-repo"
+      ;;
+    extended|all|full)
+      echo "core openagents-repo development ui content-creation data product learning project project-intelligence"
+      ;;
+    *)
+      log_error "Unknown profile: $1"
+      log_info "Valid profiles: essential, standard, extended, all"
+      exit 1
+      ;;
+  esac
+}
+
+check_dependencies() {
+  if ! command -v git >/dev/null 2>&1; then
+    log_error "git is required but not installed."
+    log_info "Install: brew install git (Mac) or sudo apt install git (Linux)"
+    exit 1
+  fi
+}
+
+download_context() {
+  local categories=($1)
+  local temp_dir
+  temp_dir="$(mktemp -d)"
+  # shellcheck disable=SC2064
+  trap "rm -rf '${temp_dir}'" EXIT
+
+  log_info "Cloning repository (sparse)..."
+  git clone --depth 1 --filter=blob:none --sparse \
+    "https://github.com/${GITHUB_REPO}.git" "${temp_dir}" --quiet 2>&1 | grep -v "^$" || true
+
+  # Get commit SHA from the clone (no second clone needed)
+  COMMIT_SHA=$(git -C "${temp_dir}" rev-parse HEAD)
+
+  log_info "Configuring sparse checkout..."
+  local sparse_paths=""
+  for cat in "${categories[@]}"; do
+    sparse_paths="${sparse_paths} ${CONTEXT_SOURCE_PATH}/${cat}"
+  done
+  sparse_paths="${sparse_paths} ${CONTEXT_SOURCE_PATH}/navigation.md"
+
+  # shellcheck disable=SC2086
+  git -C "${temp_dir}" sparse-checkout set --skip-checks ${sparse_paths} 2>/dev/null
+
+  log_info "Copying context files..."
+  mkdir -p "${CONTEXT_DIR}"
+
+  local source_dir="${temp_dir}/${CONTEXT_SOURCE_PATH}"
+  if [ ! -d "${source_dir}" ]; then
+    log_error "Context directory not found in repository"
+    exit 1
+  fi
+
+  cp -r "${source_dir}/"* "${CONTEXT_DIR}/"
+
+  local file_count
+  file_count=$(find "${CONTEXT_DIR}" -type f | wc -l | tr -d ' ')
+  log_success "Downloaded ${file_count} files"
+}
+
+write_manifest() {
+  local profile="$1"
+  local categories=($2)
+  local commit="$3"
+  local timestamp
+  timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
+
+  mkdir -p "$(dirname "${MANIFEST_FILE}")"
+
+  # Build JSON without jq — use printf
+  local cats_json="["
+  local files_json="{"
+  local first=true
+
+  for cat in "${categories[@]}"; do
+    $first || { cats_json="${cats_json},"; files_json="${files_json},"; }
+    cats_json="${cats_json}\"${cat}\""
+    local count=0
+    [ -d "${CONTEXT_DIR}/${cat}" ] && count=$(find "${CONTEXT_DIR}/${cat}" -type f | wc -l | tr -d ' ')
+    files_json="${files_json}\"${cat}\": ${count}"
+    first=false
+  done
+
+  cats_json="${cats_json}]"
+  files_json="${files_json}}"
+
+  printf '{
+  "version": "1.0.0",
+  "profile": "%s",
+  "source": {
+    "repository": "%s",
+    "branch": "%s",
+    "commit": "%s",
+    "downloaded_at": "%s"
+  },
+  "categories": %s,
+  "files": %s
+}\n' \
+    "${profile}" \
+    "${GITHUB_REPO}" \
+    "${GITHUB_BRANCH}" \
+    "${commit}" \
+    "${timestamp}" \
+    "${cats_json}" \
+    "${files_json}" \
+    > "${MANIFEST_FILE}"
+
+  log_success "Manifest created: ${MANIFEST_FILE}"
+}
+
+main() {
+  local profile="standard"
+  local global=false
+  local force=false
+  local dry_run=false
+
+  for arg in "$@"; do
+    case "${arg}" in
+      --profile=*) profile="${arg#*=}" ;;
+      --global)    global=true ;;
+      --force)     force=true ;;
+      --dry-run)   dry_run=true ;;
+      --help|-h)   usage ;;
+      *)
+        log_error "Unknown option: ${arg}"
+        echo ""
+        usage
+        ;;
+    esac
+  done
+
+  # Set install targets based on scope
+  local context_dir manifest_file oac_json scope_label
+  if [ "${global}" = true ]; then
+    context_dir="${GLOBAL_ROOT}/context"
+    manifest_file="${GLOBAL_ROOT}/.context-manifest.json"
+    oac_json=""  # global install: no per-project .oac.json
+    scope_label="global (~/.claude/context)"
+  else
+    context_dir="${PROJECT_ROOT}/.claude/context"
+    manifest_file="${PROJECT_ROOT}/.claude/.context-manifest.json"
+    oac_json="${PROJECT_ROOT}/.oac.json"
+    scope_label="project (.claude/context)"
+  fi
+
+  # Export so sub-functions can use them
+  CONTEXT_DIR="${context_dir}"
+  MANIFEST_FILE="${manifest_file}"
+
+  local categories
+  categories=$(get_categories "${profile}")
+
+  # Already installed?
+  if [ -f "${MANIFEST_FILE}" ] && [ "${force}" = false ]; then
+    log_warning "Context already installed at ${scope_label}. Use --force to reinstall."
+    log_info "Manifest: ${MANIFEST_FILE}"
+    exit 0
+  fi
+
+  check_dependencies
+
+  echo ""
+  log_info "Scope:      ${scope_label}"
+  log_info "Profile:    ${profile}"
+  log_info "Categories: ${categories}"
+  log_info "Target:     ${CONTEXT_DIR}"
+  echo ""
+
+  if [ "${dry_run}" = true ]; then
+    log_info "Dry run — no files downloaded"
+    exit 0
+  fi
+
+  COMMIT_SHA=""
+  download_context "${categories}"
+
+  write_manifest "${profile}" "${categories}" "${COMMIT_SHA}"
+
+  # Write .oac.json for project installs so context-scout uses the fast path
+  if [ "${global}" = false ]; then
+    if [ ! -f "${oac_json}" ]; then
+      printf '{\n  "version": "1",\n  "context": {\n    "root": ".claude/context"\n  }\n}\n' > "${oac_json}"
+      log_success ".oac.json created at project root"
+    else
+      log_info ".oac.json already exists — skipping"
+    fi
+  else
+    log_info "Global install — no .oac.json needed (discovery chain finds ~/.claude/context automatically)"
+  fi
+
+  echo ""
+  log_success "Context installation complete!"
+  log_info "Scope:    ${scope_label}"
+  log_info "Context:  ${CONTEXT_DIR}"
+  log_info "Manifest: ${MANIFEST_FILE}"
+  echo ""
+}
+
+main "$@"

+ 14 - 1
plugins/claude-code/scripts/install-context.ts

@@ -8,7 +8,7 @@
  */
 
 import { existsSync, writeFileSync, readFileSync } from 'fs'
-import { join } from 'path'
+import { join, relative, dirname } from 'path'
 import type { InstallOptions, InstallResult, Profile } from './types/registry'
 import type { Manifest, ManifestComponent } from './types/manifest'
 import { fetchRegistry, filterContextByProfile, filterContextByIds, getUniquePaths } from './utils/registry-fetcher'
@@ -196,6 +196,19 @@ export async function installContext(options: InstallOptions = {}): Promise<Inst
     log.success(`Manifest created: ${MANIFEST_FILE}`)
     console.log('')
 
+    // Write .oac.json at project root so context-scout uses fast path on next session
+    const projectRoot = dirname(PLUGIN_ROOT)
+    const oacJsonPath = join(projectRoot, '.oac.json')
+    const contextRelPath = relative(projectRoot, CONTEXT_DIR).replace(/\\/g, '/')
+    if (!existsSync(oacJsonPath)) {
+      const oacConfig = { version: '1', context: { root: contextRelPath } }
+      writeFileSync(oacJsonPath, JSON.stringify(oacConfig, null, 2))
+      log.success(`.oac.json created at project root → context.root = "${contextRelPath}"`)
+    } else {
+      log.info(`.oac.json already exists at project root — skipping (use --force to overwrite)`)
+    }
+    console.log('')
+
     // Clean up temp directory
     cleanup(tempDir, verbose)
 

+ 11 - 0
plugins/claude-code/settings.local.json

@@ -0,0 +1,11 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(node install-context.js:*)",
+      "Bash(bash .claude/hooks/session-start.sh)",
+      "Bash(node /Users/darrenhinde/Documents/GitHub/MYBUSINESS/claude-test/.claude/scripts/install-context.js:*)",
+      "Bash(node:*)",
+      "Bash(bash .claude/scripts/install-context.sh:*)"
+    ]
+  }
+}

+ 7 - 7
plugins/claude-code/skills/context-discovery/SKILL.md

@@ -8,7 +8,7 @@ agent: context-scout
 # Context Discovery
 
 ## Overview
-Discover project standards and patterns before implementing features. Context-scout finds and ranks relevant files from `.opencode/context/` based on your request.
+Discover project standards and patterns before implementing features. Context-scout resolves the context root using the [OAC Context Discovery Protocol](./context-discovery-protocol.md), then finds and ranks relevant files based on your request.
 
 **Announce at start:** "I'm using the context-discovery skill to find relevant standards for [feature/task]."
 
@@ -29,11 +29,11 @@ Run the skill with your implementation topic:
 
 ### Step 2: Load Critical Priority Files
 
-Read EVERY file marked **Critical Priority**:
+Read EVERY file marked **Critical Priority** (paths returned by context-scout, relative to the resolved context root):
 
 ```bash
-Read: .opencode/context/core/standards/code-quality.md
-Read: .opencode/context/core/standards/security-patterns.md
+Read: {context_root}/core/standards/code-quality.md
+Read: {context_root}/core/standards/security-patterns.md
 ```
 
 These are **mandatory**—proceed only after loading.
@@ -43,7 +43,7 @@ These are **mandatory**—proceed only after loading.
 Read files marked **High Priority**:
 
 ```bash
-Read: .opencode/context/core/workflows/approval-gates.md
+Read: {context_root}/core/workflows/approval-gates.md
 ```
 
 These are **strongly recommended** for your implementation.
@@ -53,7 +53,7 @@ These are **strongly recommended** for your implementation.
 Read **Medium Priority** files for additional context:
 
 ```bash
-Read: .opencode/context/project-intelligence/architecture.md
+Read: {context_root}/project-intelligence/architecture.md
 ```
 
 These are **optional but helpful**.
@@ -104,7 +104,7 @@ Instructions: Follow functional patterns and security best practices.
 
 - task-breakdown
 - code-execution
-- external-scout
+- external-research
 
 ---
 

+ 128 - 0
plugins/claude-code/skills/context-discovery/context-discovery-protocol.md

@@ -0,0 +1,128 @@
+# OAC Context Discovery Protocol
+
+> Single source of truth for all context root discovery.
+> Referenced by: context-scout agent, context-discovery skill, any agent needing project context.
+
+---
+
+## Summary
+
+```
+.oac.json exists?  YES → read context.root → done (fast path)
+                   NO  → run discovery chain
+                          Found?  YES → signal main agent to write .oac.json (if project-local)
+                                   NO  → return setup tips
+```
+
+---
+
+## Step 1: Check .oac.json (Fast Path)
+
+```
+Glob: .oac.json
+```
+
+If the file exists, read it and extract `context.root`:
+
+```json
+{
+  "version": "1",
+  "context": {
+    "root": ".claude/context"
+  }
+}
+```
+
+- Use `context.root` as the resolved context root. **Stop here.**
+- If `context.root` is set but that directory has no `navigation.md`: fall through to Step 2 and warn the caller.
+
+---
+
+## Step 2: Discovery Chain
+
+Only runs if Step 1 found no `.oac.json` (or the path in it was invalid).
+
+Check each path **in order**. Stop at the first that has a `navigation.md` file:
+
+```
+1. Glob: .claude/context/navigation.md          → root = .claude/context           (project-local)
+2. Glob: context/navigation.md                  → root = context                   (project-local)
+3. Glob: .opencode/context/navigation.md        → root = .opencode/context         (project-local)
+4. Check: ~/.claude/context/navigation.md       → root = ~/.claude/context         (global install)
+5. Glob: {PLUGIN_ROOT}/context/navigation.md    → root = {PLUGIN_ROOT}/context     (plugin fallback)
+```
+
+`{PLUGIN_ROOT}` is provided in your session context under **OAC System Paths**.
+`~` expands to the user's home directory (`$HOME` on Mac/Linux, `%USERPROFILE%` on Windows).
+
+**First match wins.**
+
+---
+
+## Step 3: Signal .oac.json Creation
+
+After a successful discovery chain match, signal the **main agent** (not context-scout — it is read-only) to write `.oac.json` at the project root **only if** the resolved root is project-local:
+
+| Resolved root           | Write .oac.json? |
+|-------------------------|------------------|
+| `.claude/context`       | ✅ yes           |
+| `context`               | ✅ yes           |
+| `.opencode/context`     | ✅ yes           |
+| `~/.claude/context`     | ❌ no (global install — applies to all projects, no per-project pointer needed) |
+| `{PLUGIN_ROOT}/context` | ❌ no (machine-specific path — don't commit this) |
+
+Content to write at project root `.oac.json`:
+
+```json
+{
+  "version": "1",
+  "context": {
+    "root": "{resolved_root}"
+  }
+}
+```
+
+---
+
+## Step 4: No Context Found
+
+If all steps fail, **do not error**. Return this message to the user:
+
+```
+No context found for this project.
+
+  Option 1 — Download standard context bundles (recommended):
+    Run: /install-context
+    Downloads coding standards, security patterns, workflows, and more.
+
+  Option 2 — Point to docs you already have:
+    Create .oac.json at your project root:
+    {
+      "version": "1",
+      "context": { "root": "path/to/your/docs" }
+    }
+
+  Option 3 — Proceed without context:
+    Standards won't be applied — code quality may be inconsistent.
+```
+
+---
+
+## Return Format
+
+Always return these three values:
+
+| Field            | Value                                                                                                              |
+|------------------|--------------------------------------------------------------------------------------------------------------------|
+| `context_root`   | Resolved path (e.g. `.claude/context`)                                                                            |
+| `source`         | `oac.json` \| `discovery:claude` \| `discovery:context` \| `discovery:opencode` \| `discovery:plugin` \| `none`  |
+| `write_oac_json` | `true` if main agent should create `.oac.json` with this root, else `false`                                       |
+
+---
+
+## Notes for Callers
+
+- **context-scout** is read-only — it signals `write_oac_json: true` but cannot write the file itself.
+- **Main agent** (or install-context) is responsible for writing `.oac.json`.
+- **install-context** always writes `.oac.json` after a successful install — no discovery needed.
+- Once `.oac.json` exists, discovery chain never runs again (fast path always wins).

+ 128 - 0
plugins/claude-code/skills/context-setup/SKILL.md

@@ -0,0 +1,128 @@
+---
+name: context-setup
+description: Install context files from registry. Use when user runs /install-context, says "install context", "setup context", or when context is missing and the user needs to get started.
+---
+
+# Install Context
+
+**Announce:** "Let me set up your context files."
+
+---
+
+## Why Context Files Matter
+
+Context files are project-specific standards that every AI agent loads before writing code. Without them:
+- Code won't follow your team's naming conventions or architecture patterns
+- Security and quality standards won't be applied automatically
+- Every agent starts from scratch instead of building on established patterns
+
+With context files, every agent — coder, reviewer, tester — follows the same standards without you repeating yourself.
+
+---
+
+## Quick Check
+
+Run this single command to see everything relevant at once:
+
+```bash
+echo "=== Environment ===" && \
+  { command -v git >/dev/null 2>&1 && echo "✓ git $(git --version | cut -d' ' -f3)" || echo "✗ git: not found — required"; } && \
+  { command -v node >/dev/null 2>&1 && echo "✓ node $(node --version)" || echo "  node: not found (bash installer works without it)"; } && \
+  echo "=== Context Status ===" && \
+  { [ -f .claude/.context-manifest.json ] \
+    && echo "Project: ✓ installed — $(grep -o '"profile": "[^"]*"' .claude/.context-manifest.json | head -1 | cut -d'"' -f4) profile" \
+    || echo "Project: not installed"; } && \
+  { [ -f ~/.claude/.context-manifest.json ] \
+    && echo "Global:  ✓ installed — $(grep -o '"profile": "[^"]*"' ~/.claude/.context-manifest.json | head -1 | cut -d'"' -f4) profile" \
+    || echo "Global:  not installed"; } && \
+  { [ -f .oac.json ] && echo ".oac.json: ✓ exists" || echo ".oac.json: not found"; }
+```
+
+**If already installed:** show the status, ask if they want to reinstall (`--force`) or switch profiles.
+
+**If git is missing:** stop and show the install instructions for git. Nothing else will work.
+
+---
+
+## Ask Two Questions (then install)
+
+Present both together as one message:
+
+```
+Where do you want to install context?
+
+  1. This project only  — .claude/context/ (just for this repo)
+  2. Globally           — ~/.claude/context/ (shared by all your projects)
+
+Which profile?
+
+  standard  — Core coding standards, security patterns, dev workflows  (recommended, ~30s)
+  extended  — Everything above + UI, data, content, product domains    (~1 min)
+  all       — The full library                                         (~2 min)
+```
+
+**Wait for answers before running anything.**
+
+---
+
+## Run the Installer
+
+Read the Plugin Root from **OAC System Paths** in your session context. Use that literal path.
+
+**On Mac / Linux** — prefer the bash installer (git only, no node needed):
+```bash
+bash "{PLUGIN_ROOT}/scripts/install-context.sh" --profile={profile} [--global]
+```
+
+**On Windows** (or if bash fails) — use the node installer:
+```bash
+node "{PLUGIN_ROOT}/scripts/install-context.js" --profile={profile} [--global]
+```
+
+Both accept `--force` to reinstall over an existing install.
+
+Show the installer output live as it runs.
+
+---
+
+## Verify
+
+```bash
+{ [ -f .claude/.context-manifest.json ] && echo "Project: ✓" || echo "Project: not found"; } && \
+{ [ -f ~/.claude/.context-manifest.json ] && echo "Global: ✓" || echo "Global: not found"; } && \
+{ [ -f .oac.json ] && echo ".oac.json: ✓" || echo ".oac.json: missing (project installs need this)"; }
+```
+
+For project installs: if `.oac.json` is missing, create it:
+```bash
+printf '{\n  "version": "1",\n  "context": {\n    "root": ".claude/context"\n  }\n}\n' > .oac.json
+```
+
+Confirm to the user: **"You're ready. Every agent will now load these standards automatically."**
+
+---
+
+## Error Handling
+
+**`git: command not found`**
+```
+✗ Git is required.
+  Mac:   brew install git
+  Linux: sudo apt install git
+  Windows: https://git-scm.com/download/win
+```
+
+**`Cannot find module '...install-context.js'` or script not found:**
+Check with `ls "{PLUGIN_ROOT}/scripts/"` — use whichever script is present.
+
+**Network failure:** Check internet connection and retry.
+
+---
+
+## Remember
+
+- Read Plugin Root from **OAC System Paths** in session context — never use `$CLAUDE_PLUGIN_ROOT` as a shell variable
+- Ask scope + profile together — don't ask them as separate conversations
+- Show the quick check output before asking questions
+- Wait for confirmation before running the installer
+- Global installs don't need `.oac.json` — discovery chain finds `~/.claude/context/` automatically

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

@@ -0,0 +1,160 @@
+---
+name: debugger
+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)

+ 1 - 1
plugins/claude-code/skills/external-scout/SKILL.md → plugins/claude-code/skills/external-research/SKILL.md

@@ -1,5 +1,5 @@
 ---
-name: external-scout
+name: external-research
 description: Use when the task involves an external library or package and current API docs are needed before writing code.
 context: fork
 agent: external-scout

+ 0 - 122
plugins/claude-code/skills/install-context/SKILL.md

@@ -1,122 +0,0 @@
----
-name: install-context
-description: Install context files from registry. Use when user mentions "install context", "download context", or context is missing.
-disable-model-invocation: true
----
-
-# Install Context
-
-## Overview
-
-Guide user through installing context files from OpenAgents Control registry with profile selection.
-
-**Announce at start:** "I'm using the install-context skill to set up your context files."
-
----
-
-## The Process
-
-### Step 1: Check Current Installation
-
-```bash
-cat ${CLAUDE_PLUGIN_ROOT}/.context-manifest.json 2>/dev/null
-```
-
-**If exists:** Show current profile and ask if they want to change/reinstall.
-
-**If missing:** Proceed to Step 2.
-
----
-
-### Step 2: Ask Which Profile
-
-Present options, ask user to choose:
-
-```
-Available profiles:
-
-essential   - Core patterns only (~4 components, 30s)
-standard    - Typical use (~12 components, 2min) [RECOMMENDED]
-extended    - Advanced features (~30 components, 5min)
-specialized - Domain-specific (~50 components, 10min)
-all         - Everything (~191 components, 20min)
-
-Which profile? [standard]
-```
-
-Wait for response.
-
----
-
-### Step 3: Run Installer
-
-```bash
-cd ${CLAUDE_PLUGIN_ROOT}/scripts
-bun run install-context.ts --profile={selected_profile}
-```
-
-**If bun is not available, fallback to:** `node install-context.js --profile={selected_profile}`
-
-**If user provided --force or --dry-run:** Pass those flags.
-
-Show output to user.
-
----
-
-### Step 4: Verify
-
-```bash
-cat ${CLAUDE_PLUGIN_ROOT}/.context-manifest.json
-ls ${CLAUDE_PLUGIN_ROOT}/context/
-```
-
-Report: "✓ Installed {count} components. Context ready."
-
----
-
-## CLI Options
-
-If user provides options, skip interactive prompts:
-
-```bash
-/install-context --profile=essential
-/install-context --force
-/install-context --dry-run
-/install-context --categories=core-standards,openagents-repo
-```
-
----
-
-## Error Handling
-
-**Git not installed:**
-```
-✗ Git required. Install: brew install git
-```
-
-**Network failure:**
-```
-✗ Can't reach GitHub. Check connection and retry.
-```
-
-**Already installed (without --force):**
-```
-⚠ Already installed. Use --force to reinstall.
-```
-
----
-
-## Remember
-
-- Keep it simple - just install and verify
-- Don't over-explain profiles
-- Show progress from installer
-- Verify manifest exists
-- Done
-
----
-
-## Related
-
-- `using-oac` - Uses installed context
-- `context-discovery` - Discovers from installed context

+ 95 - 0
plugins/claude-code/skills/oac-approach/SKILL.md

@@ -0,0 +1,95 @@
+---
+name: oac-approach
+description: "Use before any implementation — understands the request, discovers project context, and proposes a concise plan for user approval before writing any code."
+---
+
+# Plan Before You Code
+
+## Overview
+
+Understand the request, discover relevant context, propose a concise plan, get approval. Keep it fast — the goal is alignment, not a design workshop.
+
+<HARD-GATE>
+Do NOT write any code or make any file changes until the user has approved your proposed approach.
+</HARD-GATE>
+
+## The Process
+
+### Step 1: Understand the Request
+
+Read the user's message. You already have the "what" — don't ask a series of questions up front. Only ask clarifying questions if:
+- The request is genuinely ambiguous (two different valid interpretations)
+- You discover during context check that key details are missing (e.g. which framework, which database)
+
+In those cases, ask the specific questions you need — not a blanket "tell me more". Be explicit about what you need and why.
+
+### Step 2: Discover Context
+
+Invoke `oac:context-discovery` with the task topic to find relevant project standards and patterns.
+
+**If context-discovery reports no context installed:** proceed anyway — note it as "none" in the proposal and include the context hint (see Step 4). Do not pause or ask the user before proposing.
+
+**If context found:** note the key files returned — reference them in your proposal.
+
+### Step 3: Ask Clarifying Questions (if needed)
+
+After reviewing the context and the request, if there are still gaps that would significantly change the approach, ask them now — explicitly and concisely:
+
+```
+Before I propose an approach, I need a couple of details:
+
+1. {specific question} — because {why it matters to the approach}
+2. {specific question} — because {why it matters to the approach}
+```
+
+Keep it to the minimum needed. If you can make a reasonable assumption, state it in the proposal instead of asking.
+
+### Step 4: Propose
+
+Present a lightweight plan. Short — not a full spec:
+
+```
+## Proposed Approach
+
+**What**: {1-2 sentence description of what we're building/changing}
+**How**: {brief description of approach — key decisions only}
+**Assumptions**: {any assumptions made where questions weren't asked}
+**Files**: {list of files to create or modify}
+**Context loaded**: {key standards files found, or "none — using general best practices"}
+**External docs needed**: {any library docs to fetch first, or "none"}
+
+Approve or let me know what to adjust.
+```
+
+**If context was missing or minimal**, append this hint at the end of the proposal — do not replace or interrupt the proposal, just add it after:
+
+```
+💡 Tip: For better results tailored to your project's standards, run /install-context
+   to set up context files. This helps me follow your coding conventions automatically.
+```
+
+### Step 5: Get Approval
+
+Wait for the user to approve or adjust. If they adjust, update the proposal and ask again. Do not start implementation until explicitly approved.
+
+### Step 6: Hand Off to Implementation
+
+Once approved:
+- **Simple** (1–3 files, straightforward): implement directly
+- **Complex** (4+ files, multiple components): invoke `oac:task-breakdown` to create subtasks first
+
+## Key Principles
+
+- **Fast** — Understand from the message, don't interrogate the user up front
+- **Context first** — Always attempt discovery before proposing
+- **No context = hint, not block** — Show the install tip at the bottom of the proposal, never before it
+- **Explicit questions** — If you must ask, say what you need and why — not "tell me more"
+- **Assumptions over questions** — State assumptions in proposal rather than asking for every detail
+- **Concise proposal** — A paragraph, not a doc; enough for yes/no/adjust
+- **YAGNI** — Propose the minimum that satisfies the requirement
+
+## Related
+
+- `oac:context-discovery` — invoked in Step 2
+- `oac:context-setup` — when no context found
+- `oac:task-breakdown` — for complex features after approval

+ 93 - 404
plugins/claude-code/skills/using-oac/SKILL.md

@@ -1,440 +1,129 @@
 ---
 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, this is your secret weapon to best perform your tasks"
 ---
 
 <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:approach skill" [shape=box];
+    "Might any OAC skill apply?" [shape=diamond];
+    "Invoke Skill tool" [shape=box];
+    "Announce: 'OAC skill loaded: [friendly-skill-name] — [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:approach skill" [label="no"];
+    "Already brainstormed?" -> "Might any OAC skill apply?" [label="yes"];
+    "Invoke oac:approach 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: 'OAC skill loaded: [friendly-skill-name] — [purpose]'";
+    "Announce: 'OAC skill loaded: [friendly-skill-name] — [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 ❌
-```
-
-### Context Pre-Loading for Parallel Execution
-
-**Why**: Prevents race conditions when multiple agents execute simultaneously
-
-**How**: Stage 3 loads ALL context once, stored in session file, shared across all parallel agents
-
-### Approval Gates
-
-**Critical checkpoints:**
-- **Stage 2 → 3**: User must approve plan
-- **Stage 5 → 6**: Validation must pass
-
-**Never skip approval** - prevents wasted work and ensures alignment
+## Available OAC Skills
 
-### Parallel Execution (Complex Tasks)
+| Skill | When to invoke |
+|-------|---------------|
+| `oac:using-oac` | This skill — loaded at session start |
+| `oac:approach` | 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-research` | When working with external libraries/packages |
+| `oac:parallel-execution` | When running multiple agents in parallel |
+| `oac:debugger` | BEFORE proposing any fix for a bug or test failure |
+| `oac:verification-before-completion` | BEFORE claiming any work is complete or tests pass |
 
-**When**: Complex tasks (4+ files, >30 min) with parallelizable subtasks
+## Skill Priority
 
-**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)
+When multiple skills could apply, use this order:
 
-**Benefit**: 5x faster for complex features
+1. **Process skills first** (approach, debugger) — these determine HOW to approach the task
+2. **Implementation skills second** (context-discovery, task-breakdown, code-execution) — these guide execution
 
----
-
-## Skill Invocations
+"Let's build X" → approach first, then context-discovery, then implementation skills.
+"Fix this bug" → debugger first, then verification-before-completion.
 
-| 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 |
-
----
+## Red Flags
 
-## Examples
+These thoughts mean STOP — you're rationalizing:
 
-### Simple Task: Add email validation
+| 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. |
 
-**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  
+## Skill Types
 
-**Time:** ~10 minutes
+**Rigid** (debugger, verification-before-completion): Follow exactly. Don't adapt away discipline.
 
----
+**Flexible** (approach, context-discovery): Adapt principles to context.
 
-### Complex Task: Build authentication system
+The skill itself tells you which.
 
-**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
+## Skill Friendly Names
 
-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)  
+When announcing a skill, use the friendly name below (not the internal skill ID):
 
-**Time:** ~60 minutes (vs 300 minutes sequential = 5x faster)
+| Skill ID | Friendly Name |
+|----------|--------------|
+| `oac-approach` | OAC Approach |
+| `context-discovery` | OAC Context Discovery |
+| `task-breakdown` | OAC Task Breakdown |
+| `code-execution` | OAC Code Execution |
+| `test-generation` | OAC Test Generation |
+| `code-review` | OAC Code Review |
+| `external-research` | OAC External Research |
+| `parallel-execution` | OAC Parallel Execution |
+| `debugger` | OAC Debugger |
+| `verification-before-completion` | OAC Verification |
+| `context-setup` | OAC Context Setup |
 
----
-
-## 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
-
----
+Example announcement: `OAC skill loaded: OAC Approach — planning before implementation`
 
-## 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.

+ 306 - 45
update.sh

@@ -3,80 +3,341 @@
 #############################################################################
 # OpenAgents Control Updater
 # Updates existing OpenCode components to latest versions
+#
+# Compatible with:
+# - macOS (bash 3.2+)
+# - Linux (bash 3.2+)
+# - Windows (Git Bash, WSL)
+#
+# Usage:
+#   ./update.sh                          # Auto-detect install location
+#   ./update.sh --install-dir PATH       # Update a specific install path
+#
+# Environment variables:
+#   OPENCODE_INSTALL_DIR                 # Override default install directory
+#   OPENCODE_BRANCH                      # Branch to pull from (default: main)
 #############################################################################
 
 set -e
 
-# Colors
-GREEN='\033[0;32m'
-# YELLOW='\033[1;33m' # Unused
-BLUE='\033[0;34m'
-CYAN='\033[0;36m'
-BOLD='\033[1m'
-NC='\033[0m'
+# Detect platform
+PLATFORM="$(uname -s)"
+case "$PLATFORM" in
+    Linux*)     PLATFORM="Linux";;
+    Darwin*)    PLATFORM="macOS";;
+    CYGWIN*|MINGW*|MSYS*) PLATFORM="Windows";;
+    *)          PLATFORM="Unknown";;
+esac
 
-REPO_URL="https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/main"
-INSTALL_DIR=".opencode"
+# Colors (disable on Windows terminals without color support)
+if [ "$PLATFORM" = "Windows" ] && [ -z "$WT_SESSION" ] && [ -z "$ConEmuPID" ]; then
+    RED=''
+    GREEN=''
+    YELLOW=''
+    BLUE=''
+    CYAN=''
+    BOLD=''
+    NC=''
+else
+    RED='\033[0;31m'
+    GREEN='\033[0;32m'
+    YELLOW='\033[1;33m'
+    BLUE='\033[0;34m'
+    CYAN='\033[0;36m'
+    BOLD='\033[1m'
+    NC='\033[0m'
+fi
+
+BRANCH="${OPENCODE_BRANCH:-main}"
+REPO_URL="https://raw.githubusercontent.com/darrenhinde/OpenAgentsControl/${BRANCH}"
+
+# CLI argument for custom install dir (overrides env var)
+CUSTOM_INSTALL_DIR=""
+
+# Track backup files for cleanup on exit
+BACKUP_FILES=()
+
+# Clean up any leftover backup files on exit/interrupt
+cleanup_backups() {
+    for f in "${BACKUP_FILES[@]}"; do
+        [ -f "$f" ] && rm -f "$f"
+    done
+}
+trap cleanup_backups EXIT INT TERM
+
+#############################################################################
+# Utility Functions
+#############################################################################
 
 print_success() { echo -e "${GREEN}✓${NC} $1"; }
-print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
-print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
+print_info()    { echo -e "${BLUE}ℹ${NC} $1"; }
+print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
+print_error()   { echo -e "${RED}✗${NC} $1" >&2; }
+print_step()    { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
 
 print_header() {
     echo -e "${CYAN}${BOLD}"
     echo "╔════════════════════════════════════════════════════════════════╗"
     echo "║                                                                ║"
-    echo "║           OpenAgents Control Updater v1.0.0                   ║"
+    echo "║           OpenAgents Control Updater v1.1.0                   ║"
     echo "║                                                                ║"
     echo "╚════════════════════════════════════════════════════════════════╝"
     echo -e "${NC}"
 }
 
-update_component() {
-    local path=$1
-    local url="${REPO_URL}/${path}"
-    
-    if [ ! -f "$path" ]; then
-        print_info "Skipping $path (not installed)"
+print_usage() {
+    echo "Usage: $0 [--install-dir PATH]"
+    echo ""
+    echo "Options:"
+    echo "  --install-dir PATH   Update a specific installation directory"
+    echo "  --help               Show this help message"
+    echo ""
+    echo "Environment variables:"
+    echo "  OPENCODE_INSTALL_DIR   Override the default installation directory"
+    echo "  OPENCODE_BRANCH        Branch to pull updates from (default: main)"
+    echo ""
+    echo "Examples:"
+    echo "  # Auto-detect and update"
+    echo "  $0"
+    echo ""
+    echo "  # Update a global installation"
+    echo "  $0 --install-dir ~/.config/opencode"
+    echo ""
+    echo "  # Update via environment variable"
+    echo "  export OPENCODE_INSTALL_DIR=~/.config/opencode && $0"
+}
+
+#############################################################################
+# Path Resolution
+#############################################################################
+
+get_global_install_path() {
+    # Return platform-appropriate global installation path
+    case "$PLATFORM" in
+        macOS)
+            echo "${HOME}/.config/opencode"
+            ;;
+        Linux)
+            echo "${HOME}/.config/opencode"
+            ;;
+        Windows)
+            # Windows Git Bash/WSL: Use same as Linux
+            echo "${HOME}/.config/opencode"
+            ;;
+        *)
+            echo "${HOME}/.config/opencode"
+            ;;
+    esac
+}
+
+normalize_path() {
+    local input_path="$1"
+
+    # Handle empty path
+    if [ -z "$input_path" ]; then
+        echo ""
+        return 1
+    fi
+
+    local normalized_path
+
+    # Expand tilde to $HOME (works on Linux, macOS, Windows Git Bash)
+    if [[ $input_path == ~* ]]; then
+        normalized_path="${HOME}${input_path:1}"
+    else
+        normalized_path="$input_path"
+    fi
+
+    # Convert backslashes to forward slashes (Windows compatibility)
+    normalized_path="${normalized_path//\\//}"
+
+    # Remove trailing slashes
+    normalized_path="${normalized_path%/}"
+
+    # If path is relative, make it absolute based on current directory
+    if [[ ! "$normalized_path" = /* ]] && [[ ! "$normalized_path" =~ ^[A-Za-z]: ]]; then
+        normalized_path="$(pwd)/${normalized_path}"
+    fi
+
+    echo "$normalized_path"
+    return 0
+}
+
+resolve_install_dir() {
+    local custom_dir="$1"
+
+    # Priority: CLI arg → env var → auto-detect (local then global)
+    if [ -n "$custom_dir" ]; then
+        normalize_path "$custom_dir"
         return
     fi
-    
-    # Backup existing file
-    cp "$path" "${path}.backup"
-    
-    if curl -fsSL "$url" -o "$path"; then
+
+    if [ -n "$OPENCODE_INSTALL_DIR" ]; then
+        normalize_path "$OPENCODE_INSTALL_DIR"
+        return
+    fi
+
+    # Auto-detect: prefer local project install, fall back to global
+    local local_path
+    local_path="$(pwd)/.opencode"
+    local global_path
+    global_path=$(get_global_install_path)
+
+    if [ -d "$local_path" ]; then
+        echo "$local_path"
+    elif [ -d "$global_path" ]; then
+        echo "$global_path"
+    else
+        # Neither exists — return local path so main() gives a clear error
+        echo "$local_path"
+    fi
+}
+
+#############################################################################
+# Update Logic
+#############################################################################
+
+update_component() {
+    local path="$1"
+    local install_dir="$2"
+    local relative_path="${path#"$install_dir"/}"
+
+    # Guard: reject paths that escaped the install dir
+    if [[ "$relative_path" == /* ]] || [[ "$relative_path" == *..* ]]; then
+        print_warning "Skipping suspicious path: $path"
+        return 1
+    fi
+
+    local url="${REPO_URL}/.opencode/${relative_path}"
+    local backup="${path}.backup"
+
+    cp "$path" "$backup"
+    BACKUP_FILES+=("$backup")
+
+    if curl -fsSL "$url" -o "$path" 2>/dev/null; then
         print_success "Updated $path"
-        rm "${path}.backup"
+        rm -f "$backup"
+        # Remove from tracking array (bash 3.2 compatible)
+        local new_backups=()
+        for f in "${BACKUP_FILES[@]}"; do
+            [ "$f" != "$backup" ] && new_backups+=("$f")
+        done
+        BACKUP_FILES=("${new_backups[@]+"${new_backups[@]}"}")
     else
-        print_info "Failed to update $path, restoring backup"
-        mv "${path}.backup" "$path"
+        print_warning "Could not update $path — restoring backup"
+        mv "$backup" "$path"
+        return 1
     fi
 }
 
+update_all_components() {
+    local install_dir="$1"
+    local updated=0
+    local failed=0
+
+    # Update markdown files
+    while IFS= read -r -d '' file; do
+        if update_component "$file" "$install_dir"; then
+            updated=$((updated + 1))
+        else
+            failed=$((failed + 1))
+        fi
+    done < <(find "$install_dir" -name "*.md" -type f -print0)
+
+    # Update TypeScript files
+    while IFS= read -r -d '' file; do
+        if update_component "$file" "$install_dir"; then
+            updated=$((updated + 1))
+        else
+            failed=$((failed + 1))
+        fi
+    done < <(find "$install_dir" -name "*.ts" -type f -not -path "*/node_modules/*" -print0)
+
+    # Update shell scripts inside install dir
+    while IFS= read -r -d '' file; do
+        if update_component "$file" "$install_dir"; then
+            updated=$((updated + 1))
+        else
+            failed=$((failed + 1))
+        fi
+    done < <(find "$install_dir" -name "*.sh" -type f -print0)
+
+    print_info "Updated: $updated file(s), failed: $failed file(s)"
+}
+
+#############################################################################
+# Argument Parsing
+#############################################################################
+
+parse_args() {
+    while [[ $# -gt 0 ]]; do
+        case "$1" in
+            --install-dir=*)
+                CUSTOM_INSTALL_DIR="${1#*=}"
+                if [ -z "$CUSTOM_INSTALL_DIR" ]; then
+                    print_error "--install-dir requires a non-empty path"
+                    exit 1
+                fi
+                shift
+                ;;
+            --install-dir)
+                if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
+                    CUSTOM_INSTALL_DIR="$2"
+                    shift 2
+                else
+                    print_error "--install-dir requires a path argument"
+                    exit 1
+                fi
+                ;;
+            --help|-h)
+                print_usage
+                exit 0
+                ;;
+            *)
+                print_error "Unknown option: $1"
+                print_usage
+                exit 1
+                ;;
+        esac
+    done
+}
+
+#############################################################################
+# Main
+#############################################################################
+
 main() {
+    parse_args "$@"
+
     print_header
-    
-    if [ ! -d "$INSTALL_DIR" ]; then
-        echo "Error: $INSTALL_DIR directory not found"
-        echo "Run install.sh first to install components"
+
+    local install_dir
+    install_dir=$(resolve_install_dir "$CUSTOM_INSTALL_DIR")
+
+    if [ ! -d "$install_dir" ]; then
+        print_error "Installation directory not found: $install_dir"
+        echo ""
+        echo "Searched locations:"
+        echo "  1. --install-dir argument"
+        echo "  2. OPENCODE_INSTALL_DIR environment variable"
+        echo "  3. Local path:  $(pwd)/.opencode"
+        echo "  4. Global path: $(get_global_install_path)"
+        echo ""
+        echo "Run install.sh first to install components, or specify the correct"
+        echo "path with: $0 --install-dir PATH"
         exit 1
     fi
-    
+
+    if [ ! -w "$install_dir" ]; then
+        print_error "No write permission for: $install_dir"
+        exit 1
+    fi
+
+    print_info "Updating installation at: ${CYAN}${install_dir}${NC}"
     print_step "Updating components..."
-    
-    # Update all markdown files in .opencode
-    while IFS= read -r -d '' file; do
-        update_component "$file"
-    done < <(find "$INSTALL_DIR" -name "*.md" -type f -print0)
-    
-    # Update TypeScript files
-    while IFS= read -r -d '' file; do
-        update_component "$file"
-    done < <(find "$INSTALL_DIR" -name "*.ts" -type f -not -path "*/node_modules/*" -print0)
-    
-    # Update config files
-    [ -f "env.example" ] && update_component "env.example"
-    
+
+    update_all_components "$install_dir"
+
     print_success "Update complete!"
 }