|
| 1 | +# AGENTS.md - AI Agent Guidelines for opencode-codebase-index |
| 2 | + |
| 3 | +Semantic codebase indexing plugin for OpenCode. Hybrid TypeScript/Rust architecture: |
| 4 | +- **TypeScript** (`src/`): Plugin logic, embedding providers, tools |
| 5 | +- **Rust** (`native/`): Tree-sitter parsing, vector search (usearch), SQLite storage |
| 6 | + |
| 7 | +## Build/Test/Lint Commands |
| 8 | + |
| 9 | +```bash |
| 10 | +npm run build # Build both TypeScript and Rust native module |
| 11 | +npm run build:ts # Build TypeScript only (via tsup) |
| 12 | +npm run build:native # Build Rust native module only |
| 13 | + |
| 14 | +npm run test:run # Run all tests once (CI mode) |
| 15 | +npm test # Run tests in watch mode |
| 16 | + |
| 17 | +npm run lint # ESLint on src/ |
| 18 | +npm run typecheck # TypeScript type checking (tsc --noEmit) |
| 19 | +``` |
| 20 | + |
| 21 | +### Running a Single Test |
| 22 | + |
| 23 | +```bash |
| 24 | +npx vitest run tests/files.test.ts # Run specific test file |
| 25 | +npx vitest run -t "parseFile" # Run tests matching pattern |
| 26 | +npx vitest run tests/native.test.ts --reporter=verbose |
| 27 | +``` |
| 28 | + |
| 29 | +### Native Module Build (requires Rust) |
| 30 | + |
| 31 | +```bash |
| 32 | +cd native && cargo build --release && napi build --release --platform |
| 33 | +``` |
| 34 | + |
| 35 | +## Code Style Guidelines |
| 36 | + |
| 37 | +### Import Organization |
| 38 | + |
| 39 | +Group imports in this order, separated by blank lines: |
| 40 | +1. Type-only imports (`import type { ... }`) |
| 41 | +2. External packages and Node.js built-ins |
| 42 | +3. Internal modules |
| 43 | + |
| 44 | +**Always use `.js` extension** for internal imports (ESM requirement): |
| 45 | +```typescript |
| 46 | +import { Indexer } from "./indexer/index.js"; // Correct |
| 47 | +import { Indexer } from "./indexer/index"; // Wrong - runtime error |
| 48 | +``` |
| 49 | + |
| 50 | +**Use namespace imports** for Node.js built-ins: |
| 51 | +```typescript |
| 52 | +import * as path from "path"; |
| 53 | +import * as os from "os"; |
| 54 | +``` |
| 55 | + |
| 56 | +### Naming Conventions |
| 57 | + |
| 58 | +| Element | Convention | Example | |
| 59 | +|---------|------------|---------| |
| 60 | +| Files/Directories | kebab-case | `codebase-index.json`, `native/` | |
| 61 | +| Functions/Variables | camelCase | `loadJsonFile`, `projectRoot` | |
| 62 | +| Classes/Types/Interfaces | PascalCase | `Indexer`, `ChunkType` | |
| 63 | +| OpenCode tools | snake_case | `codebase_search`, `index_codebase` | |
| 64 | +| Constants | UPPER_SNAKE_CASE | `MAX_BATCH_TOKENS` | |
| 65 | + |
| 66 | +### Type Patterns |
| 67 | + |
| 68 | +- **Explicit return types** on all exported functions |
| 69 | +- **Strict TypeScript** enabled (`strict: true`) |
| 70 | +- **Prefix unused parameters** with `_` (ESLint enforced) |
| 71 | +- **Use `unknown`** for error handling, then narrow: |
| 72 | + |
| 73 | +```typescript |
| 74 | +function getErrorMessage(error: unknown): string { |
| 75 | + if (error instanceof Error) return error.message; |
| 76 | + return String(error); |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +### Error Handling |
| 81 | + |
| 82 | +```typescript |
| 83 | +// Non-critical: empty catch |
| 84 | +try { |
| 85 | + return JSON.parse(readFileSync(filePath, "utf-8")); |
| 86 | +} catch { /* ignore */ } |
| 87 | +return null; |
| 88 | + |
| 89 | +// Critical: descriptive error |
| 90 | +throw new Error("No embedding provider available. Configure GitHub, OpenAI, Google, or Ollama."); |
| 91 | +``` |
| 92 | + |
| 93 | +Use `p-retry` for network operations with rate limit handling. |
| 94 | + |
| 95 | +### OpenCode Tool Definitions |
| 96 | + |
| 97 | +```typescript |
| 98 | +import { tool, type ToolDefinition } from "@opencode-ai/plugin"; |
| 99 | +const z = tool.schema; |
| 100 | + |
| 101 | +export const my_tool: ToolDefinition = tool({ |
| 102 | + description: "Clear, concise description", |
| 103 | + args: { |
| 104 | + query: z.string().describe("What this argument is for"), |
| 105 | + limit: z.number().optional().default(10), |
| 106 | + }, |
| 107 | + async execute(args) { |
| 108 | + return "Result string"; |
| 109 | + }, |
| 110 | +}); |
| 111 | +``` |
| 112 | + |
| 113 | +## File Structure |
| 114 | + |
| 115 | +``` |
| 116 | +src/ |
| 117 | +├── index.ts # Plugin entry point |
| 118 | +├── config/ # Configuration schema and parsing |
| 119 | +├── embeddings/ # Provider detection and API clients |
| 120 | +├── indexer/ # Core indexing logic |
| 121 | +├── git/ # Git utilities (branch detection) |
| 122 | +├── tools/ # OpenCode tool definitions |
| 123 | +├── utils/ # File collection, cost estimation |
| 124 | +├── native/ # Rust native module wrapper |
| 125 | +└── watcher/ # File/git change watcher |
| 126 | +
|
| 127 | +native/ # Rust source (tree-sitter, usearch, SQLite) |
| 128 | +tests/ # Vitest test files (.test.ts suffix) |
| 129 | +``` |
| 130 | + |
| 131 | +## Testing Guidelines |
| 132 | + |
| 133 | +- Use Vitest globals (`describe`, `it`, `expect`, `beforeEach`, `afterEach`) |
| 134 | +- Test timeout is 30 seconds (native module operations can be slow) |
| 135 | +- Use temp directories for file-based tests: |
| 136 | + |
| 137 | +```typescript |
| 138 | +let tempDir: string; |
| 139 | +beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-")); }); |
| 140 | +afterEach(() => { fs.rmSync(tempDir, { recursive: true, force: true }); }); |
| 141 | +``` |
| 142 | + |
| 143 | +## Configuration Files |
| 144 | + |
| 145 | +| File | Purpose | |
| 146 | +|------|---------| |
| 147 | +| `tsconfig.json` | ES2022 target, ESNext modules, strict mode | |
| 148 | +| `tsup.config.ts` | ESM + CJS output, external node modules | |
| 149 | +| `eslint.config.js` | typescript-eslint recommended rules | |
| 150 | +| `vitest.config.ts` | Node environment, 30s timeout | |
| 151 | +| `native/Cargo.toml` | Rust dependencies and build config | |
| 152 | + |
| 153 | +## Common Pitfalls |
| 154 | + |
| 155 | +1. **Missing `.js` extension** in imports causes runtime errors |
| 156 | +2. **Native module not built**: Run `npm run build:native` after cloning |
| 157 | +3. **Type errors with tool schemas**: Use `z` from `tool.schema`, not direct zod import |
| 158 | + |
| 159 | +## PR Checklist |
| 160 | + |
| 161 | +```bash |
| 162 | +npm run build && npm run typecheck && npm run lint && npm run test:run |
| 163 | +``` |
0 commit comments