An open API service indexing awesome lists of open source software.

https://github.com/whiskeyjimbo/veska


https://github.com/whiskeyjimbo/veska

ai-agents code-graph code-intelligence developer-tools embeddings golang llm local-first mcp model-context-protocol semantic-search static-analysis treesitter vector-search

Last synced: 16 days ago
JSON representation

Awesome Lists containing this project

README

          

# Veska

[![Quickstart](https://img.shields.io/badge/Quick_Start-blue)](https://whiskeyjimbo.github.io/veska/getting-started/quickstart/)
[![CI](https://github.com/whiskeyjimbo/veska/actions/workflows/ci.yml/badge.svg)](https://github.com/whiskeyjimbo/veska/actions/workflows/ci.yml)
[![Docs](https://github.com/whiskeyjimbo/veska/actions/workflows/docs.yml/badge.svg)](https://github.com/whiskeyjimbo/veska/actions/workflows/docs.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/whiskeyjimbo/veska)](https://goreportcard.com/report/github.com/whiskeyjimbo/veska)
[![Go Version](https://img.shields.io/github/go-mod/go-version/whiskeyjimbo/veska)](go.mod)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)

> **Give your AI coding agent real structural understanding of your codebase.**
> Veska parses your repo into a queryable code graph + semantic index and serves
> it to your agent (and you) over MCP - so answers come back as exact
> `file:line` spans, not grep-and-guess. Fully local, in-process, zero-setup.

![Veska - natural-language query in, exact file:line answer out](docs/manual/assets/demo.gif)

**Veska** is a local code-intelligence daemon. It runs on your laptop, parses
your repository into a code graph (nodes + edges), embeds that graph
semantically, and serves both to your editor and your AI agent over MCP - so
they reason from the same structural ground truth instead of guessing.

## What it gives you

- **Grounded structural answers.** Every function, type, file, and call traces
to a node, edge, or commit. Structural recall stays current within the
save → staging freshness budget.
- **Eventually-consistent semantic search.** `semantic_search` embeds the graph
with an in-process embedder (model2vec by default - no external service);
during the indexing lag window it falls back to a BM25 lexical index and
flags the response `degraded_reasons`.
- **Promotion checks.** On every commit, synchronous checks emit advisory
`Finding`s: dead code, contract drift, leaked secrets, and vulnerable
`go.mod` dependencies via the OSV.dev advisory database. All four ship on
by default: `veska init` writes an active `[vuln_source]` config block to
`~/.veska/config.toml` (see
[`docs/operations/CONFIG-SURFACE.md`](docs/operations/CONFIG-SURFACE.md))
unless you opt out with `veska init --no-vuln` (or answer "no" at the
interactive prompt). **Lifecycle:** the block is read at daemon start, so
it takes effect from the next `veska service start` (restart the service
after editing the block). New scans pick it up automatically; to scan
already-promoted repos retroactively, run `veska reindex `.
- **Duplicate & similar-code detection.** Find copy-paste and drifted clones
for de-dupe triage: `eng_find_clones` for one symbol-group mode at a time
(`exact` byte-identical, or `near` fuzzy via stored similarity), and
`eng_find_clusters` for a whole-repo (or cross-repo) sweep across `exact`,
`structural` (Type-2, same shape after renaming), and `near` tiers, ranked
tightest first. Exact/structural are deterministic hashes; near reads the
similarity scores auto-link already stored (no new embedding sweep).
- **Optional LLM features.** An off-by-default post-promotion review pipeline
and per-node summaries (Ollama-backed).
- **Mechanical wiki.** Hot-zones and entry-points computed from the graph,
no LLM in the path. The `eng_get_hot_zone` and `eng_get_entry_points`
MCP tools return data in-memory and write nothing; the `veska wiki`
CLI renders the same data into `docs/veska/{hot_zones,entry_points}.md`
inside the repo (re-runnable, idempotent - bracket markers in each
page preserve any hand edits outside the managed block). A context-pack
tool sits alongside.
- **Cross-actor attribution.** A single `actor_kind: human | agent | system`
enum distinguishes who changed what in the audit log.

## Process topology - one binary, three personalities

`make build` produces a single binary at `bin/veska`; `bin/veska-daemon` and
`bin/veska-mcp` are symlinks to it. The argv[0] dispatcher in
`cmd/veska/main.go` routes each invocation into its own package.

| Invocation | Role |
|---|---|
| `veska` | CLI - `init`, `repo`, `reindex`, `service`, `doctor`, `backup`, `wiki`, … Run `veska --help` for the full list. |
| `veska-daemon` (symlink) | Long-running process - owns the SQLite store, the fsnotify watcher, the embedder, and the post-promotion queue. Composition root: `internal/cli/daemon/wire.go`. |
| `veska-mcp` (symlink) | Thin stdio shim proxying an editor's MCP connection to the daemon's Unix socket. Routes into `internal/cli/mcp`. |

## Vector backends

Semantic search and auto-linking find the nearest embeddings to a query. Two
backends do that, chosen with `VESKA_VECTOR_BACKEND`: **`memory`** (`memvec`, the
default - exact linear scan, lowest RAM, no setup) and **`usearch`** (approximate
HNSW - flat query latency at scale, needs `libusearch_c.so`). Measured across Go
repos (memvec is the exact-recall oracle; regenerate with `make eval-backend-matrix`):

| repo | symbols | q p95 memvec | q p95 usearch | usearch recall | RAM memvec | RAM usearch |
|---------|--------:|-------------:|--------------:|---------------:|-----------:|------------:|
| go-git | 11,262 | 4.0 ms | 0.3 ms | 0.9990 | 34 MiB | 65 MiB |
| veska | 12,900 | 4.3 ms | 0.4 ms | 0.9994 | 39 MiB | 65 MiB |
| grpc-go | 19,520 | 6.2 ms | 0.3 ms | 0.9979 | 59 MiB | 129 MiB |
| consul | 37,272 | 11.6 ms | 0.4 ms | 0.9965 | 113 MiB | 129 MiB |

memvec's query latency grows with repo size while usearch stays flat (~0.3 ms);
usearch trades ~2x the RAM and a slower index build for that. Stick with the
default until linear-scan latency is noticeable. Full discussion:
**[Vector storage backends](docs/manual/concepts/vector-backends.md)**.

## Requirements

- **Go 1.26+**
- **Go repositories only, for now.** The tree-sitter parser ships a single
Go grammar, so the code graph is built from `.go` files. Other languages
are a deliberate future step, not a current capability.
- **No external services for core use.** SQLite, the vector index, and the
default embedder all run in-process. A fresh machine indexes and searches
with nothing else installed or running.

### Embedder

Semantic search needs an embedder. Veska **elects one at boot** in preference
order - it never mixes vector spaces, so exactly one embedder owns the index
at a time:

1. **model2vec** (`potion-code-16M`) - a fast, in-process static *code*
embedder. The default and recommended choice. Get it either way:
- **Fat binary** (`make build`, default) - the model is compiled into the
binary. Zero setup: nothing to install, no download, no network.
- **Thin binary** (`make build-small`) + `veska install model2vec` - a
one-time ~62 MB download into `~/.veska/`.
2. **static-v2** - an in-binary fallback that works with no model files at
all (lower quality). Used only when model2vec is unavailable.

No Ollama, no network, and no separate process is required for search.

### Optional: Ollama

Ollama is **only** for the optional **LLM features** - the post-promotion
**review pipeline** and per-node **summaries** (both off by default). It is
**not** used for embeddings in the default config. (Power users can force an
Ollama embedding model with `VESKA_EMBEDDER=ollama`, but model2vec is faster
and higher-quality on code, so this is rarely worthwhile.)

Install Ollama only if you want those LLM features:

```sh
# macOS: brew install ollama && ollama serve &
# Linux (snap): sudo snap install ollama && ollama serve &
# Linux (curl): curl -fsSL https://ollama.com/install.sh | sh && ollama serve &
```

## Build

`make build` is the fat binary by default - it embeds the
model2vec weights into the binary so the install is zero-setup: no separate
download, no network, no static-v2 fallback at boot.

```sh
make build # default: ~104 MB fat binary (model2vec ~62 MB embedded
# into a ~42 MB thin build). Zero setup at runtime.
make build-small # ~42 MB thin: veska, veska-daemon, veska-mcp (+ layercheck).
# Use this only when you want size-sensitive binaries
# (CI, containers); you must then run `veska install model2vec`
# to avoid booting on the low-quality static-v2 fallback.
make test # go test ./...
make all # build-small + test + vet + lint + layercheck
# (uses the thin build to keep the test loop fast)
```

Binaries land in `./bin/`. Either `export PATH="$PWD/bin:$PATH"` or use the
`./bin/` prefix in the Quick Start below.

### Install into your `PATH`

After a `make build`, drop the binaries into a user bin directory in one step:

```sh
make install # → ~/.local/bin (default)
VESKA_INSTALL_DIR=/usr/local/bin sudo make install # system-wide
```

For a self-contained tarball (the three fat binaries + `install.sh` + a
README), run `make release-archive`. The archive at
`dist/veska---.tar.gz` is the same shape a future
GitHub release will ship - `./install.sh` from inside the extracted
directory does the same thing as `make install` .

## Quick start

```sh
# 1. Build veska (default: fat, zero-setup embedder).
make build
# Size-sensitive builds can `make build-small` instead, then run
# `./bin/veska install model2vec` to avoid the low-quality static-v2 fallback.

# 2. Initialize veska's data directory at ~/.veska/.
./bin/veska init

# 3. Start the daemon.
#
# Pick one:
# - Just kicking the tires? Background it: ./bin/veska-daemon &
# - Want it on every boot, auto-restart on
# crash, logs under ~/.veska/logs? use the service form below.
#
# For a real install, run it as an OS service (systemd --user on Linux,
# launchd on macOS). Uninstall with `./bin/veska service uninstall`.
./bin/veska service install
./bin/veska service start

# 4. Register a repo. --wait blocks until the cold scan finishes (a few
# seconds for most repos) so the first search below is already hot.
# Without --wait the scan kicks off in the background; the next
# `eng_search_semantic` call may then return `[]` with
# `degraded_reasons=embeddings_pending` until indexing catches up.
# Tail ~/.veska/logs/daemon.log for the "cold scan: complete" line.
./bin/veska repo add /path/to/your/repo --wait

# 5. Sanity-check.
./bin/veska doctor status
```

The first `veska repo add` registers the repo, installs the git post-commit
hook with an absolute path to the `veska` binary, and dispatches a cold scan
through the daemon. Subsequent commits drive promotion via `eng_promote_repo`
on the daemon's MCP socket.

To force a re-scan of an already-registered repo (e.g. after a model swap):

```sh
./bin/veska reindex /path/to/your/repo
```

Safe to run while the daemon is up - the CLI dispatches the cold-scan
through the daemon's `eng_reindex_repo` MCP tool , so your
editor's MCP connection is not interrupted. With the daemon stopped, the
same command falls back to a direct in-process reparse.

### Next steps

Point your editor's MCP client at `bin/veska-mcp` (a stdio command), and seed a
per-agent instruction file with `veska init --agent `. The full
walkthroughs - editor configs (Claude Desktop, Cursor, Zed, Continue), the shell
JSON-RPC interface, and the first-call sanity check - live in the manual:
**[Quickstart](https://whiskeyjimbo.github.io/veska/getting-started/quickstart/)**
and **[Connecting your editor](https://whiskeyjimbo.github.io/veska/guides/editor-setup/)**.

### Configuration

State lives under `~/.veska/` (`VESKA_HOME`). Daemon config is
`~/.veska/config.toml` - see [`docs/operations/CONFIG-SURFACE.md`](docs/operations/CONFIG-SURFACE.md).
Key environment variables:

| Var | Purpose | Default |
|---|---|---|
| `VESKA_HOME` | Data root | `~/.veska` |
| `VESKA_EMBEDDER` | Embedder election: `auto` (model2vec→static-v2), or force `model2vec` / `static` / `ollama` | `auto` |
| `VESKA_VECTOR_BACKEND` | `memory` (in-process `memvec` linear scan) or `usearch` (HNSW) | `memory` |
| `VESKA_OLLAMA_URL` | Ollama endpoint - LLM review + summaries, and `VESKA_EMBEDDER=ollama` | `http://localhost:11434` |
| `VESKA_EMBED_MODEL` | Ollama embedding model - only when `VESKA_EMBEDDER=ollama` | `nomic-embed-text` |

The elected embedder is recorded in `~/.veska/embedder.locked`. Switching
embedders requires a re-index (`veska reindex`) since their vectors aren't
comparable.

## Architecture

```
cmd/veska/ single binary entry point; argv[0] dispatcher in main.go
internal/
core/
domain/ pure entities: Node, Edge, Graph, Task, Finding
ports/ interface contracts (GraphStorage, VectorStorage, VulnSource, …)
application/ use-case services: ingester, promoter, embedder, checks, review, wiki
cli/ composition roots: daemon/wire.go and the mcp stdio shim
infrastructure/ adapters: sqlite, vector, embedding/{model2vec,static,ollama,elect}, treesitter, mcp, git
repo/ repos-table registry
platform/ cross-cutting operational concerns (config, doctor, health, …)
docs/ user manual, architecture summary, and operational runbooks
```

## MCP tools

The daemon exposes 38 tools over a Unix-socket JSON-RPC server (forwarded to
editors by `veska-mcp`). Tool names follow `eng__`. Quick map:

| Family | Tools |
|---|---|
| Admin | `eng_get_status`, `eng_get_config`, `eng_get_current_repo`, `eng_get_repo`, `eng_list_repos` |
| Repo lifecycle | `eng_add_repo`, `eng_remove_repo`, `eng_promote_repo`, `eng_reindex_repo`, `eng_set_repo_alias`, `eng_remove_repo_alias` |
| Graph | `eng_find_symbol`, `eng_get_node`, `eng_get_file_nodes`, `eng_get_call_chain` |
| Search | `eng_search_semantic`, `eng_search_similar`, `eng_find_related` (semantic neighbors of the code at a `file_path`+`line`) |
| Duplicates | `eng_find_clones` (duplicate groups for one mode: `exact` byte-identical or `near` fuzzy), `eng_find_clusters` (whole-repo / cross-repo de-dupe triage across `exact`/`structural`/`near` tiers, tightest first) |
| Blast radius | `eng_get_blast_radius`, `eng_get_diff_blast_radius`, `eng_get_dirty_blast_radius` |
| Context | `eng_get_context_pack`, `eng_find_changed_symbols` (takes `ref_a`/`ref_b` or aliases `base`/`head`; defaults to `HEAD~1..HEAD`; chunks filtered, comment-only diffs surface `non_symbol_changes_only` in `degraded_reasons`) |
| Dependencies | `eng_list_dependencies` (external modules the repo CALLS into, ranked by call-site count) |
| Misc | `eng_find_owner`, `eng_find_todos` |
| Findings | `eng_list_findings`, `eng_get_finding`, `eng_close_finding`, `eng_reopen_finding` |
| Suppressions | `eng_list_suppressions`, `eng_get_suppression`, `eng_suppress_finding`, `eng_close_suppression` |
| Wiki | `eng_get_hot_zone`, `eng_get_entry_points` |

See the **[MCP tools reference](docs/manual/reference/mcp-tools.md)** for the
full response shape, `repo_id` aliasing, parameter aliases, cross-repo edges, and
the parked task-tool family.

## Testing

```sh
make test # go test ./... - unit + integration suites
make test-mcp # python pytest harness against a running daemon (fast)
make test-mcp-deep # add cross-validation against the live SQLite
```

`tests/mcp/` spawns `bin/veska-mcp` as a subprocess, drives every registered
tool with happy/bad/edge inputs, and pretty-prints each call's transcript so
the suite doubles as a human-readable smoke. Requires `VESKA_HOME` to point
at a running daemon's data dir and at least one `veska repo add`'d repo.

## Documentation

- [`docs/PRODUCT.md`](docs/PRODUCT.md) - What Veska is, in plain English.
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) - High-level system architecture and design.
- [`docs/manual/`](docs/manual/) - User and operator manual (MkDocs source).
- [`docs/operations/`](docs/operations/) - Config surface and runbooks.

## License

Veska is licensed under the **GNU Affero General Public License v3.0**
(`AGPL-3.0-only`) - see [`LICENSE`](LICENSE). You may use, study, modify, and
share it freely; if you run a modified version as a network service, the AGPL
requires you to offer that version's source to its users.

Third-party components (Go dependencies and, in `embed_model` builds, the
potion-code-16M model weights) are redistributed under their own permissive
licenses, reproduced in [`THIRD_PARTY_NOTICES`](THIRD_PARTY_NOTICES).
Regenerate that file with `make notices` after a dependency change.