Skip to main content
Glama
N0moreher0
by N0moreher0
README.md
# venira-mcp-coding

> Fast, workspace-scoped **MCP server for coding agents** (OpenCode, Claude Desktop, any MCP client). 16 tools, structured JSON envelopes, validated read caches, async shell, and full SQLite audit trail — **pure Node, no external binary, no GUI, no network.**

```
┌──────────────┐   stdio (MCP)   ┌────────────────────────────────────────────┐
│ MCP client    │ ◄──────────────► │  venira-mcp-coding (Node, single process) │
│ (OpenCode,    │                  │  16 tools · read caches · async shell    │
│  Claude, ...) │                  │  SQLite audit (WAL) · p50/p95 metrics   │
└──────────────┘                  └────────────────────────────────────────────┘
```

- **Zero external dependencies.** Only Node ≥ 20 + `@modelcontextprotocol/sdk` + `zod` +
  `better-sqlite3`. No Rust binary, no database server, no network.
- **Workspace-locked by default.** Every path is validated inside the workspace; `../` escape
  is rejected with `path_escape`.
- **Validated read cache.** Reads are cached and re-validated by `mtime + size`, so external
  edits or deletes never return stale content.
- **Async shell.** `project_shell_run` returns a `run_id` in ~5 ms; poll with
  `project_shell_status` (5–30 s observation window, deliberately non-queued).
- **Full observability.** `project_health` reports capability state, queue depth, cache
  hit-rates, active shells, and p50/p95/max per operation.
- **Audit trail.** Every operation (including failures, shell runs, and cancellations) is
  written to a SQLite WAL table (`project_tool_audit`).

## 1. Table of contents

- [Quick start](#2-quick-start)
- [MCP client configuration (JSON)](#3-mcp-client-configuration-json)
- [Tools](#4-tools-16)
- [Architecture](#5-architecture)
- [Security model](#6-security-model)
- [Benchmark](#7-benchmark)
- [Envelopes, hints, and error codes](#8-envelopes-hints--error-codes)
- [Pros / cons (honest)](#9-what-this-project-does-well--what-it-does-not)
- [Interesting details](#10-interesting-details--things-you-should-know)
- [Development workflow](#11-development-workflow)
- [Troubleshooting](#12-troubleshooting)
- [Contributing](#13-contributing)
- [CI/CD](./CI-CD.md)
- [Docker](./DOCKER.md)

---

## 2. Quick start

### Prerequisites

| Requirement | Version | Notes |
|---|---|---|
| Node.js | ≥ 20.0 | Ships `node:sqlite` (audit inspection) and full ESM |
| Git | any | — |

That's it. There is no external binary to build or download.

### Run in 30 seconds (npm, recommended)

```bash
npm install -g venira-mcp-coding        # or: npx -y venira-mcp-coding (no install)
```

Once the package is published, `npx` fetches it to a temp dir and runs it —
no local clone or build needed:

```bash
npx -y venira-mcp-coding                # start the MCP server (stdio)
npx -y venira-mcp-coding --doctor       # run the preflight suite
npx -y venira-mcp-coding --verify       # run the behavioural regression suite
```

The `bin` entry in `package.json` resolves `dist/server.js` relative to the
installed location, so `npx` works without you having to clone anything.

### Run from source (developers)

```bash
git clone https://github.com/N0moreher0/Venira-mcp.git venira-mcp-coding
cd venira-mcp-coding
npm install
npm run build          # tsc → dist/
npm start              # stdio MCP server (attach via your MCP client, or pipe JSON manually)
```

### Run in Docker (no Node on the host)

```bash
# Build locally
docker build -t venira-mcp-coding .

# Run your project as the workspace
docker run --rm -it \
  -v /path/to/your/project:/workspace \
  -e VENIRA_WORKSPACE=/workspace \
  -e VENIRA_DATA_DIR=/workspace/data \
  venira-mcp-coding
```

See [`DOCKER.md`](./DOCKER.md) for multi-arch builds and MCP-client
integration over Docker.

### One-shot sanity checks (no MCP client required)

```bash
npm run smoke    # in-process: list a dir, read package.json, verify path-escape is blocked
npm run doctor   # 7 preflight checks: workspace, data dir, tool count, health, shell, version
npm run verify   # end-to-end MCP test suite (~30 checks: cache, batch, shell, audit, modes)
npm run bench    # in-process corehost-tier benchmark (read throughput, batch, parallel)
npm run bench:mcp# end-to-end MCP benchmark (first call, batch 8, search, health, …)
```

---

## 3. MCP client configuration (JSON)

### 3.1 OpenCode (`opencode.jsonc` or `.opencode/opencode.jsonc`)

The simplest form (uses `npx`, no local install needed):

```jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "venira": {
      "type": "local",
      "command": ["npx", "-y", "venira-mcp-coding"],
      "enabled": true,
      "environment": {
        "VENIRA_WORKSPACE": "C:\\path\\to\\your-project",
        "VENIRA_UNRESTRICTED": "0"
      }
    }
  }
}
```

Or point at a local build (after `git clone` + `npm install` + `npm run build`):

```jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "venira": {
      "type": "local",
      "command": ["node", "C:\\path\\to\\venira-mcp-coding\\dist\\server.js"],
      "enabled": true,
      "environment": {
        // Root of the workspace. All relative paths resolve against this.
        // Defaults to the server's process.cwd() when unset.
        "VENIRA_WORKSPACE": "C:\\path\\to\\your-project",
        // Where the audit DB (venira.db) lives. Defaults to <workspace>/data.
        "VENIRA_DATA_DIR": "C:\\path\\to\\data",
        // Lock every path to the workspace. KEEP this unset (or "0") in normal use.
        "VENIRA_UNRESTRICTED": "0",
        // Default shell for project_shell_run: "powershell" | "cmd" | "bash"
        "VENIRA_DEFAULT_SHELL": "powershell",
        // "full" | "compact" | "summary" (default "full")
        "VENIRA_OUTPUT_MODE": "full",
        // Set to "1" for pretty-printed JSON in tool results (costs bytes; leave off for agents).
        "VENIRA_PRETTY": "0"
      }
    }
  }
}
```

> This repo ships a ready-to-edit copy at [`opencode.example.json`](./opencode.example.json).

### 3.2 Claude Desktop / any MCP client (`claude_desktop_config.json` or `.mcp.json`)

```jsonc
{
  "mcpServers": {
    "venira": {
      "command": "npx",
      "args": ["-y", "venira-mcp-coding"],
      "env": {
        "VENIRA_WORKSPACE": "C:\\path\\to\\your-project",
        "VENIRA_DATA_DIR": "C:\\path\\to\\your-project\\data",
        "VENIRA_UNRESTRICTED": "0",
        "VENIRA_DEFAULT_SHELL": "powershell"
      }
    }
  }
}
```

> For a local build (not yet published to npm), use
> `"command": "node", "args": ["C:\\path\\to\\venira-mcp-coding\\dist\\server.js"]`
> instead of `npx`.

### 3.3 Environment variables (full reference)

| Variable | Default | Description |
|---|---|---|
| `VENIRA_WORKSPACE` | `process.cwd()` | Root of the workspace. All relative paths resolve against this; absolute paths outside it are rejected unless unrestricted. |
| `VENIRA_DATA_DIR` | `<workspace>/data` | Where the audit DB (`venira.db`) is created/updated. |
| `VENIRA_UNRESTRICTED` | `"0"` | `"1"` disables workspace locking (absolute paths anywhere work). **Do not set in production.** |
| `VENIRA_DEFAULT_SHELL` | platform default | `powershell` / `cmd` / `bash` used when `project_shell_run` receives no `shell` argument. |
| `VENIRA_OUTPUT_MODE` | `full` | `full` = complete payloads · `compact` = summary when >8 KB · `summary` = always trimmed previews. |
| `VENIRA_PRETTY` | `"0"` | `"1"` = 2-space pretty JSON in tool results. |
| `VENIRA_SESSION_ID` | auto (`opencode-<pid>-<rand>`) | Session tag; audit rows are grouped by it. Useful for verification / parallel test runs. |
| `VENIRA_READ_CACHE_BYTES` | `67108864` (64 MiB) | Budget for read + info caches. |
| `VENIRA_LIST_CACHE_MS` | `1500` | TTL for directory listing cache. |
| `VENIRA_SEARCH_CACHE_MS` | `3000` | TTL for search result cache. |
| `VENIRA_RECENT_CACHE_MS` | `2000` | TTL for recent-files cache. |
| `VENIRA_BATCH_ITEM_BYTES` | `524288` (512 KB) | Per-item cap inside `project_read_batch`. |
| `VENIRA_DEBUG_STARTUP` | `"0"` | `"1"` = startup errors go to stderr instead of the protocol-safe stdout event. |

---

## 4. Tools (16)

Every tool returns the standard envelope ([§8](#8-envelopes-hints--error-codes)):

```jsonc
{
  "ok": true,
  "status": "completed",
  "request_id": "8f3a…c2",
  "kind": "file_read",
  "data": { … },
  "truncated": false,        // optional
  "hint": "Next chunk: …",   // optional, self-explanatory next action
  "cached": true,            // optional, true when served from cache
  "elapsed_ms": 2.57
}
```

### 4.1 Read family (read-only, batch-safe, cached)

| Tool | Purpose | Notes |
|---|---|---|
| `project_list_files` | List files/dirs, depth 1–3, limit ≤ 200 | TTL cache 1.5 s, invalidated on any mutation |
| `project_read_file` | Read a text file, by line range (1-based) | mtime+size-validated cache; `next_start_line` when truncated; optional `line_numbers` prefix |
| `project_read_ranges` | Read **multiple disjoint line ranges of one file** in a single call (max 16) | Reuses the read cache per range |
| `project_get_file_info` | File/dir metadata: kind, size, mtime | Very fast; cached |
| `project_search` | Search by file name, dir name, literal content, regex, or symbol definition | TTL cache 3 s; respects `glob`, `exclude_glob`, `context_lines`, `count_only` |
| `project_recent_files` | Files modified recently (bounded filesystem scan, skips `node_modules`/`target`/`.git`) | TTL cache 2 s; includes `session_edits` — files you wrote in this session |
| `project_read_batch` | Run up to **8 read-only ops in one call** (`list_files`, `read_file`, `get_file_info`, `search`) | Sequential + cached; response budget enforced per item |
| `project_health` | Server diagnostics: capability, queue, cache hit-rates, active shells, p50/p95 per op, audit DB stats | `include_audit=false` skips the DB query (~2 ms) |

### 4.2 Mutate family (destructive, invalidates caches)

| Tool | Purpose | Notes |
|---|---|---|
| `project_write_file` | Create / overwrite / append a file | `mode: "create"` errors if file exists; `expected_sha256` guards against blind overwrites |
| `project_edit_file` | Replace exact text: one `old_text`/`new_text` pair **or** up to 64 `edits[]` | `replace_all` supported; `expected_sha256` available |
| `project_create_directory` | Create a directory tree | `create_parents` defaults to `true` |
| `project_create` | Create a **file or directory in one call** | `kind: "file"` requires `content`; `kind: "directory"` ignores `content` |
| `project_delete_path` | Delete a file or directory tree | `recursive: true` for directories. **Not undoable.** |

### 4.3 Shell family (async, non-blocking)

| Tool | Purpose | Notes |
|---|---|---|
| `project_shell_run` | Run a shell command; returns `run_id` in ~5 ms | `shell`: `powershell` / `cmd` / `bash`; `relative_cwd` optional. stdout/stderr tail captured in-process. |
| `project_shell_status` | Poll a running command | `wait_seconds` 0–30 is an *observation window*, not a timeout. Deliberately **not queued** behind other calls, not audit-logged (prevents orphan "running" rows). |
| `project_shell_cancel` | Kill a running command by `run_id` | Also used at MCP shutdown to avoid orphan processes. |

### 4.4 How the model is told to pick the right tool

At connect time the server advertises an `instructions` block (MCP SDK ≥ 1.32) containing a
**decision tree** the model can read. A condensed version:

```
1. Two+ reads or mixed read-only steps → project_read_batch (max 8, sequential + cached)
2. One file → project_read_file; large file → start_line/end_line or project_read_ranges;
   follow next_start_line when truncated
3. Find code → project_search (kind=literal_content or definition), then project_read_ranges around hits
4. Directory tree → project_list_files (depth 1..3, limit ≤200). "What changed lately?" → project_recent_files
5. Write whole file → project_write_file; small precise change → project_edit_file;
   delete → project_delete_path (destructive, not undoable)
6. Build/test/long command → project_shell_run (returns run_id immediately),
   then poll project_shell_status (wait_seconds 5..30); project_shell_cancel to stop
7. Debug latency/errors → project_health
```

Every tool also carries machine-readable metadata in its description (`Use when:` / `Avoid when:` /
`Speed:` / `Output:` / `Batch-safe:`) and a `_meta.venira` object with the same fields, so both
prompt-driven and tool-discovery-driven clients get the same signal.

---

## 5. Architecture

```
venira-mcp-coding/
├── server.ts                 # MCP entry point: McpServer + StdioTransport, warm-up, shutdown
├── tools/
│   └── project.ts            # 16 tool registrations, serial queue, caches, shell tracking,
│                             #   audit-DB inspection, project_health, project_create
├── lib/
│   ├── corehost.ts           # In-process backend: file I/O, search, async shell,
│   │                         #   scope capability, SQLite WAL audit (better-sqlite3)
│   ├── workspace.ts          # Workspace path resolution + escape validation
│   ├── output.ts             # Envelope builder, output modes, hint inference, error parser
│   ├── metrics.ts            # Ring-buffer p50/p95/max per operation (256 samples each)
│   ├── types.ts              # Shared host snapshot types
│   └── version.ts            # Exported VERSION constant
├── scripts/
│   ├── smoke.ts              # In-process backend sanity check
│   ├── bench.ts              # In-process backend benchmark
│   ├── bench-mcp.ts          # End-to-end MCP benchmark
│   ├── verify.ts             # ~30 end-to-end checks (cache, batch, shell, audit, modes)
│   └── doctor.ts             # 7 preflight checks (workspace, data dir, tools, health, shell)
├── opencode.example.json     # Ready-to-edit OpenCode MCP config
├── package.json / tsconfig.json
└── dist/                     # Build output (committed to the npm package, gitignored in repo)
```

**Key design decisions**

- **In-process backend.** The `CoreHost` class in `lib/corehost.ts` implements every
  `project_tool.*` operation in pure Node — no JSON-Lines IPC, no child process. The
  `tools/project.ts` layer still funnels calls through a **serial queue** because
  SQLite WAL audit writes are cheap but not free; keeping one writer at a time is
  simpler and deterministic than racing on `better-sqlite3` transactions.
- **Long-lived capability.** The workspace scope (capability token, TTL 2 h) is opened
  **once** at startup (in parallel with the MCP handshake) and reused for every request.
  A capability expiry triggers exactly one re-open-and-retry.
- **Cache strategy.** Reads and file-info are cached and validated by `mtime + size`
  (immune to external edits and deletions). List / search / recent use short TTLs
  (1.5 s / 3 s / 2 s) and are wiped on any mutation. Search results with 0 hits are
  *not* cached (prevents stale "no match"). Read + info caches are bounded by
  `VENIRA_READ_CACHE_BYTES` (default 64 MiB) with LRU eviction.
- **Audit, not just logging.** `better-sqlite3` opens `venira.db` in WAL mode.
  Every `execute` op gets a `running` row on start and a `succeeded`/`failed`
  row on completion. `shell_status` is the only op intentionally *not* audited —
  a long poll must never leave an orphan `running` row. `project_health`
  (with `include_audit`) reports orphan rows as a post-mortem signal.

---

## 6. Security model

This project is intentionally **conservative by default**:

| Control | Default | How it works |
|---|---|---|
| **Workspace lock** | ON | `lib/workspace.ts` resolves every path against `VENIRA_WORKSPACE` (or `process.cwd()`). A path that escapes (`../..`, absolute outside root) throws `path_escape`. |
| **Unrestricted mode** | OFF | `VENIRA_UNRESTRICTED=1` disables the lock (use only when you fully understand the consequences — the agent can read/write anywhere on the machine). |
| **Shell cwd** | workspace | `project_shell_run` with no `relative_cwd` runs in the workspace root; the command itself is still whatever the agent passes. |
| **Audit** | ON | Every operation (including failures and cancellations) is written to `project_tool_audit` in a SQLite WAL database. |
| **`sensitive_result`** | — | The envelope carries `retryable: false` and a "do not retry" hint when content is withheld by policy. |
| **`expected_sha256`** | opt-in | `project_write_file` / `project_edit_file` / `project_create` accept a content hash; a mismatch aborts with `conflict` — protects against blind overwrites when the agent re-applies an old edit. |
| **Destructive ops** | labelled | Every mutating tool advertises `destructiveHint: true` in its MCP annotations; `project_delete_path` additionally documents that it is not undoable. |

**Threats this does NOT protect against:** a malicious prompt can still instruct the agent to
run destructive shell commands (`rm -rf`, `format`…). The audit log and `project_health` make that
visible *after* the fact; the workspace lock only bounds filesystem access, not shell side-effects.
If you need hard boundaries, run the MCP server in a container / VM, or drop `VENIRA_DEFAULT_SHELL`
to a restricted shell profile.

---

## 7. Benchmark

Numbers below are from a Windows 11 / NVMe / Node 20 machine, standalone build, 2026-10-27.
See [`benchmarks.md`](./benchmarks.md) for the full table and historical comparison.

### 7.1 End-to-end MCP (`npm run bench:mcp`)

| Scenario | avg | p50 | p95 | max |
|---|---|---|---|---|
| **First call** (post warm-up) | — | **2.5 ms** | — | — |
| `project_read_file` ×50, same file (cached) | 1.93 ms | 1.01 ms | 2.65 ms | 3.07 ms |
| `project_read_file` ×8, distinct files | 2.37 ms | 1.92 ms | 5.78 ms | 5.78 ms |
| `project_read_batch` ×3 (8 items each) | **4.18 ms** | 3.71 ms | 6.08 ms | 6.08 ms |
| `project_get_file_info` ×30 | 1.11 ms | 1.02 ms | 2.63 ms | 2.97 ms |
| `project_search` ×10 (literal content) | 3.38 ms | 1.26 ms | 21.76 ms | 21.76 ms |
| `project_read_ranges` (2 ranges) | 3.6 ms | — | — | — |
| `project_recent_files` | 5.0 ms | — | — | — |
| `project_health` (no DB query) | **1.8 ms** | — | — | — |

> **Reading the batch row:** 8 distinct reads cost **4.2 ms total** as one batch call,
> vs. ~8 × 2.4 ms ≈ 19 ms issued individually. That is the round-trip saving the decision
> tree in [§4.4](#44-how-the-model-is-told-to-pick-the-right-tool) is designed to exploit.

### 7.2 In-process tier (`npm run bench`)

| Scenario | Value |
|---|---|
| Direct first read (cold, includes capability open) | 19.96 ms |
| `read_file` sequential ×50 | avg 0.72 ms · p50 0.59 ms · p95 1.88 ms |
| 8-file batch sequential | 11.0 ms total |
| Parallel ×8 | 2.9 ms total |
| `get_file_info` ×50 | avg 0.30 ms |
| `list_files` | 12.7 ms |
| `search` (literal, `*.ts`) | 9.8 ms |
| `shell_run` + `shell_status` round-trip | ~930 ms (2 s wait budget dominates) |

### 7.3 Rust-backend v0.3.0 → standalone v1.0.0

| Metric | v0.3.0 (Rust IPC) | v1.0.0 (standalone) | Why |
|---|---|---|---|
| First MCP call | 6.3 ms | **2.5 ms** | No subprocess spawn |
| Cached read p50 | 2.57 ms | **1.01 ms** | In-process file I/O |
| 8-file batch avg | 19.11 ms | **4.18 ms** | No IPC + no audit lock |
| `project_health` | 5.5 ms | **1.8 ms** | In-process |
| Install surface | Rust binary + Node | **Node only** | `npm install` is the whole setup |

---

## 8. Envelopes, hints, and error codes

Every success and every failure is a single JSON object. On failure, the MCP result has
`isError: true` and the envelope looks like:

```jsonc
{
  "ok": false,
  "status": "failed",
  "request_id": "…",
  "kind": "read_file",
  "error": {
    "code": "not_found",
    "message": "Path does not exist: .verify-tmp/missing.txt",
    "retryable": false
  },
  "hint": "Check the path with project_list_files or project_search first; do not retry the same path.",
  "elapsed_ms": 4.12
}
```

| `error.code` | `retryable` | What it means |
|---|---|---|
| `not_found` | ✗ | Path does not exist |
| `invalid_request` | ✗ | Bad argument names/types |
| `conflict` | ✓ | File changed since read, or edit ambiguous — re-read and retry |
| `path_escape` | ✗ | Path outside the workspace (use `VENIRA_UNRESTRICTED=1` to opt out) |
| `scope_denied` | ✗ | Capability / permission mismatch |
| `resource_limit` | ✗ | Batch item too large or response budget exhausted — narrow the request |
| `io_error` | ✓ | File locked or disk busy — retry once |
| `audit_unavailable` | ✗ | Audit row could not be written — stop retrying |
| `capability_expired` | ✓ | Scope re-opens automatically on next call — retry once |
| `host_timeout` | ✓ | Backend didn't answer in time — check `project_health`, retry once |
| `host_unavailable` | ✓ | Backend is unavailable; it recovers on the next call — retry once |
| `sensitive_result` | ✗ | Content withheld by policy — do not retry or work around it |
| `unsupported` | ✗ | Operation unavailable in current permission mode |
| `internal` | ✗ | Unexpected error — check `project_health` and stderr |

Hints are **actionable next steps**, not prose: "Next chunk: `project_read_file({ relative_path:
"src/foo.ts", start_line: 1000 })`", "Poll `project_shell_status({ run_id: "r123", wait_seconds: 5 })`",
etc. The model is expected to read `hint` first.

---

## 9. What this project does well & what it does not

**Strengths**

- **Speed at the MCP layer.** ~2.5 ms first call, ~1 ms p50 cached reads, ~4.2 ms for an
  8-file batch. The in-process backend removes the IPC hop and the audit lock contention
  that dominated the v0.3.0 (Rust) profile.
- **Zero install friction.** `npm install && npm run build` is the entire setup. No Rust
  toolchain, no prebuilt binary, no database server.
- **Correctness of reads.** mtime+size validation means the cache can never serve content
  that no longer exists on disk (deleted, renamed, or edited externally).
- **Self-diagnosis.** `project_health` + `doctor.ts` + `verify.ts` give three independent
  viewpoints: runtime state, preflight, and behavioural regression.
- **Audit-grade traceability.** Every op, including failures and cancellations, lands in a
  SQLite table tagged by session — useful for post-mortems on what an agent actually did.
- **Agent-friendly contracts.** Envelopes, hints, `_meta` annotations, and a decision-tree
  `instructions` block mean the model rarely has to guess.

**Limitations (be honest about these)**

- **macOS CI-only, not dev-tested.** CI runs on `macos-latest` (x64 + arm64) for both Node
  20 and 22, but no developer has day-to-day macOS. Path handling is POSIX-correct
  (verified on Linux), but Spotlight / Gatekeeper / SIP quirks are untested.
- **No git/diff tooling yet.** `project_diff_files`, `project_file_symbols`, and
  `project_symbol_references` are on the roadmap (see [`TODO.md`](./TODO.md)) but not implemented.
- **Search is a line scanner, not an index.** `project_search` reads every file in scope
  (bounded at 1 MiB/file) and matches line-by-line. It's fine for typical project sizes;
  for a monorepo with thousands of files, p95 will climb.
- **`project_recent_files` is a bounded scan**, not an index: it caps at 4 000 files / 300
  directories and skips `node_modules`, `target`, `dist`, `.git`, etc.
- **Shell is tail-only.** `project_shell_run` returns a bounded tail of stdout/stderr, not a
  live stream. Long outputs are truncated by design.
- **One workspace per server instance.** The workspace, data dir, and permission mode are
  fixed at process start. To serve multiple workspaces concurrently, run multiple instances.
- **`better-sqlite3` is a native module.** Prebuilt binaries cover the common Node ×
  platform × arch combos (Node 20/22 on Linux x64/arm64, macOS x64/arm64, Windows x64).
  Unusual platforms (musl, Alpine, ARM Windows, exotic archs) will compile from source
  on `npm install` and need `python3` + a C compiler. The Docker image and the
  standalone `.zip` in the release sidestep this entirely.

---

## 10. Interesting details & things you should know

- **Serial queue, by design.** The MCP layer funnels every `execute` through a promise
  queue so the SQLite audit writer is single-threaded. `shell_status` and `cancel` are
  deliberately *unqueued* so a long poll never blocks anything else.
- **Warm-up races the MCP handshake.** `warmUp()` opens the capability scope immediately
  and in parallel with `server.connect()`. The first tool call therefore costs ~2.5 ms
  instead of ~20 ms (capability open alone is ~17 ms).
- **Search cache is asymmetric on purpose.** A search that *found* hits is cached (TTL 3 s).
  A search that *found none* is not — because the absence of a hit is exactly what can
  become stale the instant a file is written. (`verify.ts` guards this with
  `search_cache_invalidated`.)
- **`project_recent_files` is pure Node.** No backend needed: bounded BFS with `readdir` +
  `stat` in chunks of 64, symlinks skipped, ignored dirs pruned, results sorted by mtime
  descending. The `session_edits` field additionally lists files *this* session wrote
  (up to 20), so the model knows what it already touched.
- **Audit DB is inspected read-only.** `project_health` opens `venira.db` with
  `readonly: true` via `better-sqlite3` and counts rows for the session, including
  `orphan_running` (rows stuck at `running` — a sign of an unclean shutdown).
- **Metrics are ring buffers, not histograms.** `lib/metrics.ts` keeps the last 256
  samples per operation (p50/p95/max/avg). Deliberately tiny: the goal is "what happened
  this session", not "90-day SLO tracking".
- **Output modes are cheap.** `summary` mode replaces payloads with 8 KB previews + a
  `summary` object; `compact` only adds a `summary` when the payload exceeds 8 KB. The
  model can request full data back via a follow-up call when it truly needs it.
- **Shutdown is defensive.** On `SIGINT`/`SIGTERM`/stdin close the server cancels all
  running shells, closes the scope, and marks the audit DB cleanly — no orphans, no stuck WAL.

---

## 11. Development workflow

```bash
npm run build        # type-check + emit dist/
npm run smoke        # in-process sanity (backend reachable, path lock works)
npm run doctor       # preflight: 7 checks incl. audit DB and shell smoke
npm run verify       # behavioural regression suite (~30 checks, ~60 s)
npm run bench        # in-process numbers
npm run bench:mcp    # end-to-end numbers
```

**Adding a new tool**

1. Register it in `tools/project.ts` with `registerProjectTools` — use the `config()` helper
   so `Use when` / `Avoid when` / `Speed` / `Output` / `Batch-safe` land in the description
   *and* in `_meta.venira`.
2. Read-only ops: route through `executeCached` so they automatically participate in
   cache invalidation (mutations wipe list/search/recent caches).
3. New backend ops: add the handler in `lib/corehost.ts` under `operations`, and add an
   audit row (or mark it read-only like `shell_status` if it's a long poll).
4. Add a case to `scripts/verify.ts` (the suite already checks instruction blocks,
   annotations, envelope shape, and audit rows for new tool names).
5. Update the decision tree in `server.ts` (`INSTRUCTIONS`) and the tool table in
   `README.md`.

**Testing conventions**

- `verify.ts` uses a fixed `VENIRA_SESSION_ID` so its audit rows are separable from real
  sessions.
- `bench.ts` uses a unique `request_id` tag per run — the audit table has a UNIQUE
  constraint on `request_id`, and reusing one across runs would produce
  `audit_unavailable`.
- Do not commit to `dist/`, `node_modules/`, `test/`, or `*.log` (see `.gitignore`).

---

## 12. Troubleshooting

| Symptom | First things to check |
|---|---|
| `not_found` on a path that looks fine | Run `npm run doctor`; confirm `VENIRA_WORKSPACE` points where you think. |
| `path_escape` on a path that looks fine | The path contains `../` that resolves outside the workspace. Use a workspace-relative path; set `VENIRA_UNRESTRICTED=1` only when you truly need it. |
| Read cache seems stale | It should not — reads are mtime+size validated. If you see it, check that the filesystem timestamp updates (some network drives delay `mtime`). |
| Shell command not finishing | `project_shell_status` with `wait_seconds: 30`; check `project_health` → `shells.active` to see which `run_id` is still running. |
| `better-sqlite3` build failure on install | You're on an unusual platform (musl, exotic arch). Run `npm rebuild better-sqlite3` with build tools (`python3`, `make`, `g++`), or use the Docker image / prebuilt standalone zip instead. |
| `npx venira-mcp-coding` not found | The package is not published yet. Use a local build: `node <repo>/dist/server.js`. |
| `venira.db` won't open in `project_health` | Another process has the WAL locked; make sure only one server instance writes to the same data dir. |
| Everything slow | Run `project_health` — `queue.depth`, `pending_requests`, and per-op p95 will tell you if it's a bottleneck or a stuck command. |

---

## 13. Contributing

1. Fork and clone; `npm install && npm run build && npm run doctor`.
2. Read [`TODO.md`](./TODO.md) — the next milestones are already scoped (cache toggles,
   request tracing, `project_read_audit`, shell UX, fuzzy edit, symbol tools).
3. Keep the invariants from [§10](#10-interesting-details--things-you-should-know):
   serial queue for `execute`, long-lived capability, asymmetric search cache, bounded scans.
4. Add a `verify.ts` check for every new tool; keep the suite green before opening a PR.
5. Run `npm run bench:mcp` and paste the numbers in the PR description when touching
   `tools/project.ts` or `lib/corehost.ts`.

---

## License

Dual-licensed: **MIT OR Apache-2.0** — see [`LICENSE`](./LICENSE).

- MIT license text: top of [`LICENSE`](./LICENSE)
- Apache-2.0 full text: [`LICENSE-APACHE2.0.txt`](./LICENSE-APACHE2.0.txt)

TDQS

A4.2/5.0

Scored across 16 tools

Disambiguation4/5

Most tools map cleanly to one resource+action, and the explicit 'Use when / Avoid when' guidance sharply separates read_file vs read_ranges vs read_batch and shell_run vs shell_status. The main overlap is project_create, which deliberately duplicates project_create_directory and project_write_file, leaving two valid paths for creating a file or directory.

Naming Consistency4/5

Nearly everything uses a project_ prefix with snake_case verb_noun (list_files, read_file, write_file, edit_file, create_directory, delete_path, shell_run). A few names drop the verb (project_search, project_recent_files, project_health, project_create), which is a minor deviation but still predictable.

Tool Count4/5

16 tools is on the heavy side but each cluster (4 read variants, 3 shell lifecycle, write/edit/create/delete, diagnostics) earns its place for a coding workspace. The one genuinely redundant entry is project_create, which re-bundles existing capabilities.

Completeness4/5

Coverage is strong: list, read, search, write, edit, mkdir, delete, async shell with status/cancel, plus self-diagnostics. The notable gaps are move/rename and copy operations, which are common in coding workflows and would otherwise force a read-write-delete workaround.

Maintenance

ActivityMaintained
ResponsivenessNo issues