Contributing
April 26, 2026 ยท View on GitHub
Thanks for working on unch. This project is a local-first semantic code search CLI with optional remote indexing, MCP integration, and npm/Codex packaging. This guide is meant to make the codebase less mysterious before you start changing it.
Project Map
.
|-- cmd/
| |-- unch/ CLI entrypoint for the main binary
| `-- bench/ benchmark runner entrypoint
|-- internal/
| |-- bench/ benchmark suites, scoring, and CLI adapters
| |-- cli/ command parsing and command orchestration
| |-- embed/ embedding provider interface and shared formatting
| | |-- llama/ local llama.cpp / yzma GGUF embedder
| | `-- openrouter/ OpenRouter embedding provider
| |-- filehashdb/ file hash snapshots used for incremental indexing
| |-- indexdb/ SQLite/vector index storage and provider/model snapshots
| |-- indexing/ repository scanning and indexing service
| | `-- treesitter/ language-specific Tree-sitter symbol extraction
| |-- mcp/ stdio MCP protocol, tool schemas, and renderers
| |-- modelcatalog/ built-in model metadata
| |-- runtime/ model downloads, yzma runtime resolution, progress UI
| |-- search/ semantic/lexical search service
| |-- semsearch/ `.semsearch` paths, manifests, tokens, remote state
| `-- termui/ terminal progress/session helpers
|-- benchmarks/
| `-- suites/ checked-in benchmark suite definitions
|-- docs/ GitHub-facing docs and assets
|-- mintlify/ public docs site source
|-- npm/unch/ npm wrapper, native binary installer, Codex setup
|-- install/ PowerShell installer
|-- scripts/ release, benchmark, docs, and asset helper scripts
`-- .github/
|-- workflows/ CI, release, remote index, docs sync
`-- releases/ release note markdown files
Core Flows
CLI command flow
cmd/unch/main.go calls internal/cli.Run. Command-specific files in internal/cli parse flags, resolve .semsearch paths, prepare an embedder, and call the lower-level services.
Use internal/cli for command behavior and UX. Keep storage, indexing, search, and provider logic in their own packages.
Indexing flow
internal/cli/index.go prepares paths and an embedder, then calls internal/indexing.Service.
The indexing service:
- walks repository files with
internal/indexing.Scanner - extracts symbols through
internal/indexing/treesitter - embeds each indexed symbol through
internal/embed - writes vectors and metadata to
internal/indexdb - records file hashes in
internal/filehashdb - updates
.semsearch/manifest.jsonthroughinternal/semsearch
Add language support in internal/indexing/treesitter, not in the CLI.
Search flow
internal/cli/search.go opens the active provider/model snapshot from internal/indexdb, embeds the query, then calls internal/search.Service.
Search modes:
autocombines lexical and semantic behaviorsemanticuses embeddings and distance filteringlexicalis for exact names, identifiers, and strings
Embedding providers
The provider interface lives in internal/embed.
Current providers:
llama.cppviainternal/embed/llamaopenrouterviainternal/embed/openrouter
Provider/model identity matters. Index snapshots are isolated by provider, model, and vector dimension so different embedding backends can coexist in the same .semsearch directory.
MCP and Codex flow
internal/mcp owns the MCP protocol layer: framing, JSON-RPC methods, tool schemas, tool calls, and human-readable renderers.
internal/cli/start.go starts the MCP server. internal/cli/mcp_backend.go adapts MCP tool calls to the same indexing/search services used by the CLI.
The npm wrapper in npm/unch installs the native binary and provides:
unchfor normal CLI usageunch-mcpas a small launcher forunch start mcpunch codex installto register the MCP server and install the Codex skill
For Codex, users should run:
npm install -g @uchebnick/unch
unch codex install
Then they restart Codex. Codex starts the MCP server automatically.
Remote indexing flow
Remote indexing is optional. unch create ci generates .github/workflows/unch-index.yml, and unch bind ci stores the remote binding in .semsearch/manifest.json.
Remote sync logic lives mostly in internal/semsearch and internal/cli/remote.go.
Common Change Points
Add a CLI command
- Add command parsing/dispatch in
internal/cli/root.go. - Put behavior in a focused
internal/cli/<command>.gofile. - Add help text in
internal/cli/help.go. - Add CLI tests in
internal/cli. - Update README and Mintlify docs if the command is user-facing.
Add a language parser
- Add language-specific extraction in
internal/indexing/treesitter/<language>.go. - Register the language in
internal/indexing/treesitter/parser.go. - Add fixtures/tests in
internal/indexing/treesitter_test.go. - Update README, Mintlify compatibility docs, and release notes.
Add an embedding provider
- Implement
internal/embed.Embedder. - Add provider parsing/identity in
internal/embed/provider.go. - Wire provider construction through
internal/cli/embedding.go. - Ensure index snapshots are provider/model-specific.
- Add token/config docs if the provider needs credentials.
Change MCP behavior
- Update tool schemas in
internal/mcp/tools.go. - Update MCP params/results in
internal/mcp/types.go. - Update backend behavior in
internal/cli/mcp_backend.go. - Add protocol/tool tests in
internal/mcp. - Update
npm/unch/scripts/codex-install.jsif the Codex skill text changes. - Update README, npm README, and Mintlify MCP docs.
Local Setup
Build and test the current checkout:
go test ./...
go build -o unch ./cmd/unch
Run an end-to-end local smoke test:
go run ./cmd/unch index --root .
go run ./cmd/unch search --root . "command dispatch"
First local llama.cpp usage may download the default embedding model, fetch managed yzma runtime libraries, and create ./.semsearch/.
npm Wrapper Checks
When touching npm/unch, run:
cd npm/unch
npm test
If MCP launcher behavior changes, build a local binary first and run the smoke test:
go build -o npm/unch/vendor/unch ./cmd/unch
cd npm/unch
npm run test:mcp
Remove npm/unch/vendor/ before committing.
Documentation
Keep these in sync for user-facing changes:
README.mddocs/mintlify/npm/unch/README.md.github/releases/<tag>.mdfor release notes
For install, MCP, provider/model, or compatibility changes, update both GitHub docs and Mintlify docs.
Before Opening a PR
Run the narrowest relevant tests plus formatting checks:
go test ./...
git diff --check
For docs-only changes, git diff --check is usually enough.
Before opening a PR, check:
- the branch contains only the intended files
- generated or downloaded files are not staged
- docs match the actual CLI behavior
- release notes mention upgrade-impacting behavior
Reporting Issues
- Bugs: include OS, architecture,
unch --version, provider/model, repository language, command, and unexpected result. - Search quality reports: include the query, actual results, and the result you expected to rank higher.
- MCP/Codex issues: include whether
unch codex installwas run, whether Codex was restarted, and any MCP error text. - CI or remote issues: include the workflow URL or failing run URL when possible.