{
  "markdown": "```\n         _                _\n      __| | ___  __ _  __| |_______  _ __   ___\n     / _` |/ _ \\/ _` |/ _` |_  / _ \\| '_ \\ / _ \\\n    | (_| |  __/ (_| | (_| |/ / (_) | | | |  __/\n     \\__,_|\\___|\\__,_|\\__,_/___\\___/|_| |_|\\___|\n\n    > semantic doc search. local file. no cloud. no key.\n    > you ask in english. it answers in snippets.\n```\n\n> **Status.** Vector search wired end-to-end. MCP over stdio. One binary, Linux + macOS, zero telemetry. See [releases](https://github.com/laradji/deadzone/releases) for the latest tag and the [roadmap](https://github.com/laradji/deadzone/milestones) for in-flight work.\n> The scraper is still the messy half — [#64](https://github.com/laradji/deadzone/issues/64) is honest about it.\n\n---\n\n## The pitch, in one paragraph\n\nYour AI client says `\"how do I register a tool?\"`. The doc says `AddTool`. A grep-based index shrugs; a vector index doesn't. Deadzone is the vector index — `nomic-embed-text-v1.5` over Turso's native cosine distance, wrapped in a Go binary that speaks MCP over stdio and keeps every byte on your laptop. It is, roughly, [Context7](https://github.com/upstash/context7) with the internet turned off.\n\n---\n\n## Rules of the deadzone\n\n1. **One binary.** `deadzone`. Subcommands for everything. No `pip install`, no `npm i`, no `docker compose up`.\n2. **The index never leaves.** Local Turso file. No account. No API key. No egress on the hot path.\n3. **Natural language first.** Embeddings over cosine. `FTS5` is not invited.\n4. **The binary is the version.** The DB is pinned to the binary. Upgrade the binary, the DB follows; don't, and it won't.\n5. **Fail loudly or not at all.** `DEADZONE_DB_OFFLINE=1` refuses to guess. Verification failures in the scraper drop the doc, not the run.\n\n---\n\n## Install (pick one; they all converge on the same binary)\n\n```sh\n# macOS Apple Silicon — the one-liner\nbrew install laradji/deadzone/deadzone\n\n# Linux — resolve the latest tag once, then pick a flavor\nVERSION=$(curl -fsSL https://api.github.com/repos/laradji/deadzone/releases/latest | grep '\"tag_name\"' | cut -d'\"' -f4)\nARCH=amd64    # or arm64\n\n# self-mounting AppImage\ncurl -L -O \"https://github.com/laradji/deadzone/releases/download/${VERSION}/deadzone_${VERSION}_linux_${ARCH}.AppImage\"\nchmod +x \"deadzone_${VERSION}_linux_${ARCH}.AppImage\"\nmv \"deadzone_${VERSION}_linux_${ARCH}.AppImage\" deadzone\n\n# or plain tarball (no FUSE needed)\ncurl -L \"https://github.com/laradji/deadzone/releases/download/${VERSION}/deadzone_${VERSION}_linux_${ARCH}.tar.gz\" \\\n  | tar xz --strip-components=1\n```\n\nBoth flavors land a `deadzone` executable in your current directory, so the `./deadzone server` snippet below works as-is.\n\n```sh\n# Container — multi-arch (linux/amd64 + linux/arm64), ships with the DB baked, runs offline by default\ndocker pull ghcr.io/laradji/deadzone:latest\ndocker run --rm -i ghcr.io/laradji/deadzone:latest server\n```\n\nThe image bakes the binary, `libonnxruntime`, `deadzone.db`, and the `nomic-embed-text-v1.5` ONNX weights (~230 MB total), and runs as a non-root user out of [distroless](https://github.com/GoogleContainerTools/distroless) (no shell, no package manager). `DEADZONE_DB_OFFLINE=1` is set in the image so first launch is instant — no download, no volume mount, no `--network` access required. To refresh the index, pull a newer tag.\n\nWindows is blocked upstream — no `libtokenizers.a`. Use WSL.\n\n**Verify checksums** (optional but cheap):\n\n```sh\ncurl -L -O \"https://github.com/laradji/deadzone/releases/download/${VERSION}/deadzone_${VERSION}_checksums.txt\"\nsha256sum  --ignore-missing -c \"deadzone_${VERSION}_checksums.txt\"   # Linux\nshasum -a 256 --ignore-missing -c \"deadzone_${VERSION}_checksums.txt\"   # macOS\n```\n\n**AppImage needs FUSE v2.** Most desktops ship it; minimal servers don't. If you get `dlopen(): libfuse.so.2`, either `apt-get install libfuse2` (or `dnf install fuse-libs`) or pass `--appimage-extract-and-run` to bypass FUSE entirely.\n\n---\n\n## Run\n\n```sh\n./deadzone server\n```\n\nThat's the quick-start. On first launch it fetches `deadzone.db` matched to this binary's version, SHA256-verifies, caches it under the platform data dir, and serves. Second launch onwards: zero network. Upgrade the binary and the DB re-fetches on next launch; don't, and the cache is served forever.\n\nMCP client wire-up — native binary (Brew tap, tarball, or AppImage):\n\n```json\n{\n  \"mcpServers\": {\n    \"deadzone\": {\n      \"type\": \"stdio\",\n      \"command\": \"/path/to/deadzone\",\n      \"args\": [\"server\"]\n    }\n  }\n}\n```\n\nMCP client wire-up — container (multi-arch on `ghcr.io`). The image ships with `deadzone.db` baked, so no volume mount is needed and every container start is offline-instant:\n\n```json\n{\n  \"mcpServers\": {\n    \"deadzone\": {\n      \"type\": \"stdio\",\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"--rm\", \"-i\", \"ghcr.io/laradji/deadzone:latest\", \"server\"]\n    }\n  }\n}\n```\n\nThen, from the client:\n\n```\nsearch_libraries(\"terraform aws\")                 → ranked (lib_id, version) pairs\nsearch_docs(\"creating an s3 bucket\", lib_id=...)  → snippets, token-budgeted\n```\n\n---\n\n## The two tools\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│  search_libraries(name, limit?) → []LibraryHit                      │\n│  ─────────────────────────────────────────────                      │\n│  free text   ──►  vector match against the `libs` table             │\n│                   ──► [{lib_id, version, doc_count, match_score}]   │\n├─────────────────────────────────────────────────────────────────────┤\n│  search_docs(query, lib_id?, version?, tokens?) → []Snippet         │\n│  ──────────────────────────────────────────────────                 │\n│  natural  ──► 768-dim embed ──► cosine over docs                    │\n│  language                      ──► token-budgeted snippets back     │\n└─────────────────────────────────────────────────────────────────────┘\n```\n\n| Arg         | Shape                   | Notes                                                                 |\n|-------------|-------------------------|-----------------------------------------------------------------------|\n| `query`     | string                  | Matched semantically. Don't write keywords; write what you want.      |\n| `lib_id`    | `/org/project`          | Optional filter. Grab one from `search_libraries`.                    |\n| `version`   | `\"1.14\"` or similar     | Optional pin; **requires** `lib_id`. `version` alone is rejected.     |\n| `tokens`    | int                     | Response budget. Default `5000`, min `1000`, ≈ 4 chars/token.         |\n| `limit`     | int                     | On `search_libraries` — max results. Default `10`, max `50`.          |\n| `name`      | string                  | Free text on `search_libraries`. Empty returns the most-indexed libs. |\n\n---\n\n## Under the hood\n\n```\n  deadzone server\n       │\n       ▼\n  ┌──────────────┐   stdio JSON-RPC       ┌───────────────┐\n  │  MCP client  │ ─────────────────────► │   handler     │\n  └──────────────┘                        └──────┬────────┘\n                                                 │\n                              ┌──────────────────┴──────────────────┐\n                              ▼                                     ▼\n                     ┌────────────────┐                   ┌──────────────────┐\n                     │   embedder     │                   │   Turso (local)  │\n                     │  hugot + ORT   │                   │  F32_BLOB(768)   │\n                     │  nomic v1.5    │                   │  vector_distance │\n                     └────────┬───────┘                   └──────────────────┘\n                              │  768-dim                             ▲\n                              └──────────────  query vector  ────────┘\n```\n\n| Layer      | Choice                                                                        |\n|------------|-------------------------------------------------------------------------------|\n| Language   | Go 1.26.2, pinned via [`mise`](https://mise.jdx.dev)                           |\n| Storage    | [Turso](https://turso.tech) local file — native `F32_BLOB(N)` + `vector_distance_cos` |\n| Driver     | [`tursogo`](https://pkg.go.dev/turso.tech/database/tursogo) — **CGO-free** via [`purego`](https://github.com/ebitengine/purego) |\n| Embedder   | [`hugot`](https://github.com/knights-analytics/hugot) → `nomic-ai/nomic-embed-text-v1.5`, 768-dim, 8192-token ctx (int8 quantized) |\n| Runtime    | ONNX Runtime — binary CGO-linked at build time; `libonnxruntime` auto-fetched + SHA256-verified on first launch |\n| Protocol   | [`modelcontextprotocol/go-sdk`](https://github.com/modelcontextprotocol/go-sdk) over stdio |\n\nThe binary itself is CGO-linked (hugot ORT backend + static `libtokenizers.a`). At **runtime** the only native surface is `libonnxruntime`, loaded via `dlopen` after a SHA256-verified auto-download. Everything else — Go stdlib, `tursogo`, the model weights — is either statically linked or fetched on first launch against a pinned hash. No system installs. No `sudo`. If a download drifts from its pinned hash, the run aborts; there is no fallback to an unverified fetch.\n\nEscape hatches for air-gapped boxes:\n\n| Env var                   | Effect                                                                                  |\n|---------------------------|-----------------------------------------------------------------------------------------|\n| `DEADZONE_ORT_LIB_PATH`   | Hand-positioned `libonnxruntime` path. Skips the download.                              |\n| `DEADZONE_ORT_CACHE`      | Override the ORT library cache dir.                                                     |\n| `DEADZONE_HUGOT_CACHE`    | Override the model-weights cache dir.                                                   |\n| `DEADZONE_DB_CACHE`       | Override the `deadzone.db` cache dir.                                                   |\n| `DEADZONE_DB_OFFLINE=1`   | Refuse any network call. Fails loudly if nothing is cached. Set by default in the container image (which ships `deadzone.db` baked). |\n| `DEADZONE_DB_AUTOUPDATE=0` | Disable the boot-time DB freshness probe (the probe runs by default; `fetch-db` always probes regardless of this flag). |\n\nDefault cache paths per platform:\n\n| Platform | `deadzone.db` lives at                                           |\n|----------|------------------------------------------------------------------|\n| macOS    | `~/Library/Application Support/deadzone/deadzone.db`              |\n| Linux    | `$XDG_DATA_HOME/deadzone/deadzone.db` (else `~/.local/share/...`) |\n| Windows  | `%LOCALAPPDATA%\\deadzone\\deadzone.db`                             |\n\nA sibling `deadzone.db.release` JSON manifest records `{tag, sha256, fetched_at}`. Startup compares the cached tag against the binary's compiled-in version: match → fire a 3-second freshness probe against `deadzone.db.sha256` on the matching GitHub Release, atomic-swap if the remote sha differs (soft-fail to the cache on any network error); differs → fetch the new tag's release and atomic-swap; dev build → fall back to `/releases/latest` with a `server.db_version_dev_fallback` WARN. Pre-#197 binaries wrote a single-line tag-only sidecar; the JSON reader still accepts that format and rewrites it to v1 on first probe.\n\n---\n\n## Add a library\n\nContributor path. End users don't touch this — they just get what ships in `deadzone.db`.\n\n**Not editing YAML yourself?** Open an issue via the [New issue](https://github.com/laradji/deadzone/issues/new/choose) page and pick **Add a library** or **Refresh a library**. The template collects exactly what a registry entry needs.\n\n**Editing YAML yourself?** Append to [`libraries_sources.yaml`](libraries_sources.yaml):\n\n```yaml\nlibraries:\n  # Single-version lib — no `versions` key, urls used as-is.\n  - lib_id: /modelcontextprotocol/go-sdk\n    kind: github-md\n    urls:\n      - https://raw.githubusercontent.com/modelcontextprotocol/go-sdk/main/README.md\n      - https://raw.githubusercontent.com/modelcontextprotocol/go-sdk/main/docs/quick_start.md\n\n  # Multi-version lib — `versions` expands into one effective lib_id\n  # per version (/org/project/1.4, /org/project/1.5, …). {ref} is\n  # substituted from each version's ref: field.\n  - lib_id: /modelcontextprotocol/go-sdk\n    kind: github-md\n    versions:\n      \"1.4\": { ref: v1.4.1 }\n      \"1.5\": { ref: v1.5.0 }\n    urls:\n      - https://raw.githubusercontent.com/modelcontextprotocol/go-sdk/{ref}/README.md\n      - https://raw.githubusercontent.com/modelcontextprotocol/go-sdk/{ref}/docs/getting-started.md\n```\n\n| Field                | Req | Purpose                                                                                                          |\n|----------------------|-----|------------------------------------------------------------------------------------------------------------------|\n| `lib_id`             | yes | Canonical `/org/project` identifier (matches `db.docs.lib_id`).                                                  |\n| `kind`               | yes | `github-md` (raw markdown), `github-rst` (raw reStructuredText), or `scrape-via-agent` (HTML/text via LLM).      |\n| `urls`               | yes | Doc URL list with an optional `{ref}` placeholder.                                                               |\n| `versions`           | no  | `{\"1.4\": {ref: v1.4.1, urls: [...]}, \"1.5\": {ref: v1.5.0}, …}` — user-facing identifiers prefer `major.minor`.   |\n| `ref`                | no  | Git tag or commit SHA substituted into `{ref}`. Per-version `ref:` overrides top-level.                          |\n| `versions[v].urls`   | no  | Per-version URL list — replaces baseline wholesale. Use for structurally divergent versions.                     |\n\nPre-1.0: no Go editing, no recompile. Just edit YAML and re-scrape.\n\n---\n\n## Scrape-via-agent (experimental)\n\n> ⚠️ **The messy half.** Works today for non-markdown sources (Terraform providers, mkdocs, GitBook, …), but the LLM→verifier loop is sensitive to input truncation (48 KiB cap), HTML→markdown skill, and verbatim-code matching. Real-world hit rate on dense doc sites ≈ 50%/URL — see [#64](https://github.com/laradji/deadzone/issues/64). **Prefer `github-md` whenever the project ships committed markdown.**\n\nBring your own LLM runtime — [Ollama](https://ollama.ai), [llama.cpp](https://github.com/ggerganov/llama.cpp/tree/master/examples/server), [vLLM](https://github.com/vllm-project/vllm), LocalAI, LM Studio, Groq, OpenAI, anything that speaks `POST /v1/chat/completions`:\n\n```sh\nexport DEADZONE_AGENT_ENDPOINT=http://localhost:11434/v1\nexport DEADZONE_AGENT_ENDPOINT_MODEL=qwen2.5:7b\nexport DEADZONE_AGENT_ENDPOINT_API_KEY=sk-...   # optional\n```\n\nThen add a `kind: scrape-via-agent` entry to `libraries_sources.yaml` with a list of page URLs. The downstream pipeline (parse → chunk → embed → store) is **identical** to `github-md`; only the markdown source changes.\n\n**Guardrails.** Every fenced code block in the LLM output is verified verbatim against the source — invented examples drop the doc (`scraper.agent_verification_failed`), not the run. Missing/unreachable endpoint aborts at startup; no silent fallback.\n\n---\n\n## Local pipeline (contributors)\n\nTwo-step bootstrap: toolchain first, then the CGO native dep — kept separate so air-gapped / CI runners with vendored `libtokenizers.a` can skip step 2 by overriding `DEADZONE_TOKENIZERS_LIB`.\n\n```sh\njust bootstrap            # Go 1.26.2 + just toolchain (mise install)\njust fetch-tokenizers     # libtokenizers.a — one-shot CGO setup\njust build                # CGO + ORT, all packages\njust scrape                       # all libs — one artifact folder per lib\njust scrape /hashicorp/terraform  # one base lib, every version\njust scrape /hashicorp/terraform/1.14   # one exact version\njust consolidate                  # merge artifacts/*/artifact.db → deadzone.db\njust serve                        # MCP server against deadzone.db\n```\n\n`just` with no args lists every recipe. Each scrape rewrites `artifacts/<slug>/artifact.db` + `state.yaml` in place; `consolidate` merges all artifact DBs atomically under `deadzone.db`. Per-lib folders are gitignored; the committed [`artifacts/manifest.yaml`](artifacts/manifest.yaml) records release history only.\n\n**Full registry via CI.** `gh workflow run scrape-pack.yml -f tag=vX.Y.Z` fans out the matrix, consolidates, and uploads `deadzone.db` to the tagged release. Omit `-f tag=…` to stop at a consolidated-db cache.\n\n---\n\n## Release flow\n\nTwo-phase as of [#101](https://github.com/laradji/deadzone/issues/101) — CI ships binaries, operator ships the DB.\n\n```sh\n# 1. Regenerate deadzone.db from the committed scraper config.\njust scrape && just consolidate\n\n# 2. Tag + push. CI's release.yml builds tarballs + AppImages, creates the release,\n#    and auto-bumps the Homebrew tap on release.published.\ngit tag v0.X.0 && git push --tags\n\n# 3. Ship deadzone.db + deadzone.db.sha256 to the same release.\njust dbrelease v0.X.0\n\n# 4. Commit artifacts/manifest.yaml so the release-history trace lands in git.\ngit add artifacts/manifest.yaml && git commit -m \"release v0.X.0\" && git push\n```\n\nA stable-tag push fans out fully through CI: `release.yml` -> `chain-release.yml` dispatches `scrape-pack.yml` -> `chain-image.yml` dispatches `docker-publish.yml` (one workflow per concern, chained via `workflow_run`).\n\n**Manual Homebrew fallback.** The tap auto-bump fires on `release.published` ([#148](https://github.com/laradji/deadzone/pull/148)). If `RELEASE_PUBLISH_TOKEN` expires and the chain breaks, run it by hand:\n\n```sh\ngh workflow run update-package-channels.yml -f tag=v0.X.0\n```\n\n---\n\n## Logs\n\nStructured JSON on **stderr** via `log/slog`. Stdout is reserved for MCP JSON-RPC on `deadzone server`.\n\n| Subcommand      | Key events                                                                                                                      |\n|-----------------|--------------------------------------------------------------------------------------------------------------------------------|\n| `scrape`        | `scraper.start`, `scraper.lib_start`, `scraper.fetch` (per URL), `scraper.indexed`, `scraper.lib_done`, `scraper.done`. Errors: `scraper.fetch_failed`, `scraper.insert_failed`. Agent path adds `scraper.agent_configured`, `scraper.agent_ping_ok`, `scraper.agent_verification_failed`, `agent.input_truncated`. |\n| `consolidate`   | `consolidate.start`, `consolidate.done` with `artifacts`, `docs_merged`, `libs_merged`, `duration_ms`.                          |\n| `dbrelease`     | `dbrelease.start`, `packs.dbrelease.uploaded` (per asset), `dbrelease.done` with `sha256`, `size`, `lib_count`, `doc_count`.    |\n| `server`        | `server.start` (embedder + `doc_count`), one `search_docs` per call (`lib_id`, `tokens`, `results`, `latency_ms`). Boot may emit `server.db_upgraded`, `server.db_version_dev_fallback` WARN, `server.db_tag_sidecar_write_failed` WARN. |\n\n`--verbose` on any subcommand adds debug-level detail. On `server` it logs the raw `query` (off by default — queries may carry user data). On `scrape` it adds per-doc `scraper.doc_indexed`.\n\nMCP client log paths: Claude Code on macOS writes to `~/Library/Logs/Claude/mcp-server-deadzone.log`; other clients vary.\n\n---\n\n## Roadmap & contributing\n\nIssues: [`laradji/deadzone/issues`](https://github.com/laradji/deadzone/issues). Scope via [milestones](https://github.com/laradji/deadzone/milestones). Category via `feature` / `research` labels; priority via `P1` / `P2` / `P3`.\n\nNew library or refresh: use the [New issue](https://github.com/laradji/deadzone/issues/new/choose) page and pick the matching form.\n\n---\n\n## Why bother with vectors\n\nBecause `\"how to register a tool\"` should find the doc that says `AddTool`, and no FTS5 query will get you there without the human already knowing the answer. Embeddings-first retrieval is the point; everything else is plumbing.\n\nLong-form: [`docs/research/context7-analysis.md`](docs/research/context7-analysis.md).\n\n---\n\n## License\n\n[Apache License, Version 2.0](./LICENSE). Third-party attributions in [`NOTICE`](./NOTICE).\n\n**One important asterisk.** Apache 2.0 covers the Deadzone source code, and only that. It does **not** cover the third-party documentation the scraper indexes — those docs belong to their original authors under their own licenses. Running `deadzone scrape` is subject to each source's ToS. A pre-built pack is bound by the original content's license, not Apache 2.0. Personal local indexing: fine. Public redistribution: do the homework first.\n",
  "bytes": 20601,
  "sha": "08c15c12f2f86e0f514461e9e48460278a98ad6b832dc788b74bb23966aefd36",
  "repo_slug": "laradji/deadzone",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_laradji_deadzone_f0338f2a/readme"
}