OpenCode plugin that maintains a searchable architecture index at .arch-index/ARCH_INDEX.md.
- Tracks Python/TypeScript/JavaScript/Rust source changes and marks dirty files.
- Rebuilds index sections with deterministic ordering and stable line-range pointers.
- Enforces freshness on index reads (
readand common shell readers likecat). - Uses Python AST extraction by default, with optional LSP enrichment.
The plugin exposes manual control tools that can be invoked through OpenCode tool calling:
arch_index_reload_full: Force a full rebuild from current tracked files.arch_index_refresh_dirty: Run dirty-file incremental refresh immediately.arch_index_status: Show overview status (index existence, in-flight rebuild, dirty counts, sequence counters, preview of dirty files).
By default, these tools are not exposed to model tool-calling. Enable exposure via config when needed.
The plugin also supports slash commands (enabled by default):
/arch-index-reload(alias:/arch-index-reload-full)/arch-index-refresh(alias:/arch-index-refresh-dirty)/arch-index-status [--dirty-limit N]
These are useful when you want deterministic operator-driven refresh behavior instead of waiting for a read trigger.
- Bun 1.0+
- Python 3 available as
python3
npm install opencode-arch-index-pluginAdd the plugin in your OpenCode config (project or global):
{
"plugin": [
"opencode-arch-index-plugin"
]
}The plugin supports configuration via JSON files at the following locations (highest precedence first):
- Project Config:
.opencode/opencode-arch-index.json - User Config:
~/.config/opencode/opencode-arch-index.json(on Linux/macOS) - Defaults: Internal plugin defaults
You can start from the bundled default language profile and copy it into your project config path:
mkdir -p .opencode
cp examples/opencode-arch-index.default.json .opencode/opencode-arch-index.json{
"respect_gitignore": true,
"include": [],
"exclude": [],
"global_mode": "hybrid",
"modes": {
"rust": "off"
},
"logging": {
"level": "info",
"metrics_enabled": false,
"metrics_top_n": 10,
"metrics_history_limit": 20
},
"lsp": {
"references_batch_concurrency": 4,
"reference_cache_enabled": true,
"reference_cache_max_entries": 20000,
"circuit_breaker_failure_threshold": 5,
"circuit_breaker_cooldown_ms": 30000,
"servers": {
"python": {
"initialization_options": {
"python": {
"analysis": {
"diagnosticMode": "openFilesOnly"
}
}
}
}
}
},
"analysis": {
"max_file_size_bytes": 2097152,
"skip_binary_files": true,
"binary_sniff_bytes": 4096
},
"controls": {
"expose_tools_to_model": false,
"enable_slash_commands": true
}
}respect_gitignore: Whether to skip files matched by.gitignore(default:true).include: Optional allowlist of glob patterns. When non-empty, only matched files continue to analysis.exclude: Array of glob patterns to explicitly exclude.global_mode: Default mode for all supported languages (off,ast,lsp,hybrid).modes: Per-language mode overrides (python,typescript,javascript,rust).logging.level: Log verbosity (debug,info,warn,error,silent).logging.metrics_enabled: Emit rebuild/analyzer performance metrics in logs.logging.metrics_top_n: Number of slowest files to include in bottleneck summaries.logging.metrics_history_limit: Number of recent metric runs persisted to history.lsp.references_batch_concurrency: Max parallel references requests per file while a document stays open.lsp.reference_cache_enabled: Enable dirty-safe reference count cache.lsp.reference_cache_max_entries: Max persistent reference cache entries.lsp.circuit_breaker_failure_threshold: Consecutive failures before opening LSP circuit breaker.lsp.circuit_breaker_cooldown_ms: Cooldown time for a tripped LSP circuit breaker.lsp.servers.<language>.initialization_options: Pass-through LSP initialization options (python,typescript,rust).analysis.max_file_size_bytes: Skip files larger than this byte limit.analysis.skip_binary_files: Enable binary-content guard.analysis.binary_sniff_bytes: Bytes sampled for binary detection.controls.expose_tools_to_model: Expose manual tools to model tool-calling.controls.enable_slash_commands: Enable slash commands for manual control actions.
Layers are merged such that defaults < user < project. Atomic values like respect_gitignore and global_mode are overwritten, while objects like modes, logging, and lsp are shallow-merged.
The selector evaluates paths in this order:
- Exclude: If a file matches any
excludepattern, it is skipped. - Ignored Directories: Files under default ignored directories are skipped (
venv,.venv,__pycache__,.mypy_cache,.pytest_cache,node_modules,.git,.arch-index,dist,build). - Respect Gitignore: If
respect_gitignoreis true and it matches a.gitignorepattern, it is skipped. - Include Allowlist: If
includeis non-empty, the file must match one of these patterns. - Extension: File suffix must map to a language whose effective mode is not
off.
Language activation is automatic from file extensions in the tracked set; LSP servers are only touched when matching files exist and their effective mode requires LSP.
When logging.metrics_enabled is true, the plugin emits structured metrics for:
- full rebuild phases (
snapshot,baseline discovery/analyze,delta analyze,reconcile,render,write) - analyzer stage breakdown (
prepare,extract_primary,fallback,enrich,finalize) - per-language analyzer totals for the same breakdown stages
- per-file analyzer timings (slowest N files with
extract_primary_ms,fallback_ms,enrich_ms,finalize_ms,total_ms)
This is intended for bottleneck diagnosis on large multi-project worktrees.
Metrics history is appended at .arch-index/metrics-history.json and automatically trimmed to the last logging.metrics_history_limit runs.
off: No indexing for this language.ast: Pure AST extraction via language-specific adapters.lsp: LSP-only symbol extraction when a document-symbol provider is available.hybrid: AST extraction enriched with LSP data (e.g., reference counts).
Non-fatal Fallbacks:
- The fallback chain is
LSP -> AST -> skip. - If
lspmode cannot extract symbols from LSP, it falls back to AST parsing; if AST also fails, that file is skipped. - If
hybridenrichment fails, AST symbols are preserved and enrichment is skipped. - Fallback/degradation emits visible warnings (toast in TUI when available) and console warnings for auditability.
- If an adapter fails to parse a specific file, a warning is logged and the file is skipped, but the rest of the indexing continues.
- If no adapter is found for a file extension, it is skipped with a warning.
The generated index uses deterministic sections:
- metadata
0) Usage1) Tree2) Symbols
A real generated snapshot for the current repository is committed at:
examples/generated/opencode-arch-index.ARCH_INDEX.md
bun run build
bun testBuild output:
dist/index.jsdist/extract_symbols.py