--- url: /kuda/tutorials/first-run.md --- # Tutorial: your first index Goal: go from a checkout to a queryable, freshness-tracked memory of the repo — the artifact an agent loads before planning instead of re-exploring from scratch. ## 1. Init ```sh cd your-repo kuda init ``` This writes a commented `kuda.toml` and a `.kuda/` skeleton. Open the toml — the defaults are fully local and deterministic: ```toml [extract] kinds = ["manifest", "treesitter"] # adapter names, tried in order [summarize] kind = "template" # deterministic cards [store] kind = "sqlite" path = ".kuda/index.sqlite" ``` If `rust-analyzer` is on PATH you can add `"lsp"` to `kinds` — extraction quality tiers up automatically (see [extraction tiers](../explanation/architecture.md#extraction-tiers)). ## 2. Sync ```sh kuda sync ``` Output: ``` sync /home/you/your-repo indexed 19 module(s) at 3432d05 ``` `sync` walks manifests, runs the tree-sitter symbol queries from `data/langs/*`, summarizes each module into a card, and stores the symbol/edge graph in sqlite. It writes: | path | tier | commit? | | --------------------- | ---- | ------------------------ | | `.kuda/index.yaml` | L0 | yes — the always-load card | | `.kuda/modules/*.yaml`| L1 | yes — reviewable in diffs | | `.kuda/index.sqlite` | L2 | no — rebuildable graph | | `.kuda/meta.json` | meta | no — provenance record | ## 3. Look at what it learned ```sh kuda map ``` The L0 card: repo name, the module list, detected commands, and the provenance block (`commit_sha`, file hashes, generator). This is the \~1k-token blob an agent reads first. ```sh kuda card kuda-store ``` An L1 card: purpose (from the manifest `description`), public API symbols, dependency edges, `verify:` — the files to open before trusting the card — and its own provenance. ## 4. Query ```sh kuda find IndexStore ``` ``` crates/kuda-store/src/lib.rs:9 Trait kuda-store ``` ```sh kuda dependents serde ``` ``` kuda-cards --PackageDep--> serde kuda-core --Imports--> serde ``` And the doc-sweep query — everything that mentions a name, useful when a change must update code *and* docs and comments: ```sh kuda refs SqliteStore --docs ``` ## 5. Break it, then watch kuda notice Edit any `crates/*/Cargo.toml` — change a `description` — and: ```sh kuda status ``` ``` drifted kuda-store 19 fresh, 1 drifted (HEAD af26b8c) ``` `status` re-hashes each card's `verify:` files and compares with the recorded provenance. `kuda sync` regenerates only what drifted. ## You have built A committed, queryable, self-checking map of the repository. Point agents at it — via `kuda mcp` or `kuda acp` — and they plan against the map instead of grepping blindly. Next: [serve it over MCP](../how-to/mcp.md) or [index a multi-repo workspace](../how-to/multi-repo.md). --- --- url: /kuda/how-to/multi-repo.md --- # How-to: index a multi-repo workspace For sibling checkouts — an org of repos that import each other — kuda keeps one index per repo *and* answers cross-repo questions over all of them. ## 1. Declare the workspace Create `/.kuda/workspace.yaml` (the directory containing the sibling repos), or inside one repo's `.kuda/`: ```yaml roots: - kuda - mimus - ../cli - ../lsp ``` Paths resolve relative to the file's directory. `[workspace]` in `kuda.toml` documents the same intent: ```toml [workspace] roots = ["../cli", "../lsp"] resolve_git_deps = true ``` ## 2. Sync all roots ```sh kuda sync --workspace ``` ``` sync /home/you/GitHub/9aia/kuda indexed 19 module(s) at af26b8c sync /home/you/GitHub/9aia/mimus indexed 15 module(s) at 323421e ``` Each root gets its own `.kuda/` artifacts — the per-repo indexes stay independent and committable in their own repos. ## 3. Query across the set ```sh kuda dependents kuda-core --workspace ``` Edges found in *every* root's store are merged — so an import of `kuda-core` inside `mimus` shows up alongside internal users. That is the impact-analysis query that matters for a breaking change: "who calls this, anywhere in the org". ## 4. Git dependency edges `resolve_git_deps = true` records the intent for the extractor tier that maps `git = "…"` manifest dependencies onto sibling checkouts — turning "depends on github.com/x/y" into "depends on the sibling repo y". Until that lands, cross-repo edges come from module-name matching across the merged stores. ## Current limits * `find` and `refs` remain per-root; `dependents` is the merged query today. * Cards are generated per root — there is no aggregate workspace card yet. Compose one from `kuda map` output across roots. --- --- url: /kuda/how-to/mcp.md --- # How-to: serve the index over MCP `kuda mcp` exposes the index as tools to MCP-capable agents (Devin, Cursor, Claude Code, Zed, …). It is a stdio JSON-RPC server — no ports, no daemon. ## Run ```sh cd your-repo kuda mcp ``` ## Tools | tool | query | | ------------- | --------------------------------------- | | `repo_card` | L0 blob — the always-load summary | | `module_card` | L1 card + freshness for one module | | `find` | symbol -> file:line | | `dependents` | reverse dep edges (impact analysis) | | `refs` | code + docs/comments mentions | | `status` | provenance drift vs HEAD | ## Register with your agent stdio servers are declared in the client's MCP config — the shape varies, but it is always "command + args": ```json { "mcpServers": { "kuda": { "command": "kuda", "args": ["mcp"] } } } ``` Run it from a repo that has been `kuda sync`'d — the tools read `.kuda/` at call time, so a re-`sync` is picked up without restarting the server. ## Verify the handshake ```sh printf '%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"find","arguments":{"name":"IndexStore"}}}' \ | kuda mcp ``` Two replies: the `initialize` result, then a `content[0].text` body like `crates/kuda-store/src/lib.rs:9 Trait kuda-store`. ## Trust model Cards are hypotheses. Every `module_card` response includes the `verify:` file list — agents should open those files before acting on card content, and `status` tells them whether the whole index has drifted. See [architecture](../explanation/architecture.md#trust-model). See also: [ACP surface](acp.md) — same index, the other wire protocol. --- --- url: /kuda/how-to/acp.md --- # How-to: the ACP surface `kuda acp` speaks the Agent Client Protocol — the editor↔agent wire protocol used by Zed and supported by agent clients such as cline and Cursor. Where `kuda mcp` exposes *tools*, `kuda acp` exposes the index as an *agent* you can prompt. ## Run ```sh cd your-repo kuda acp # stdio JSON-RPC server ``` ## Prompts The session/prompt method accepts compact query strings: | prompt | returns | | ------------------- | ---------------------------------------- | | `find ` | `file:line kind module` hits | | `dependents ` | reverse dep edges | | `card ` | the module card YAML | | `map` / `status` | the L0 card (fallback for anything else) | Replies stream back as session/update notifications with `agent_message_chunk` content, then a `stopReason: end_turn` result. ## Verify ```sh printf '%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \ '{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"'"$PWD"'","mcpServers":[]}}' \ '{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"kuda-default","prompt":[{"type":"text","text":"find IndexStore"}]}}' \ | kuda acp ``` You get `agentInfo`, a `sessionId`, an `agent_message_chunk` carrying `crates/kuda-store/src/lib.rs:9 Trait kuda-store`, and `end_turn`. ## Which protocol when? * **MCP** — the agent already has tool-use wired and you want the index as callable tools. Most current clients. * **ACP** — you want kuda to appear as an *agent* inside an ACP-speaking client, sharing its own session model. Both read the same `.kuda/` artifacts — pick per client, or run both. --- --- url: /kuda/how-to/languages.md --- # How-to: add a language Per-language knowledge is data, not code. A new language means a new `data/langs/*.toml` descriptor — the extractors' vocabulary stays fixed. ## The descriptor `data/langs/rust.toml` is the template: ```toml name = "rust" extensions = [".rs"] manifests = ["Cargo.toml"] # module-boundary markers grammar = "rust" # adapter id — see below # Tree-sitter symbol query. Captures are @name. where # maps to SymbolKind: function|type|trait|const|module|macro. symbol_query = ''' (function_item name: (identifier) @name.function) (struct_item name: (type_identifier) @name.type) ... ''' ``` | field | used by | | -------------- | ---------------------------------------- | | `name` | reports, LSP `languageId`, dep normalization | | `extensions` | both extractors' file walking | | `manifests` | package graph + module attribution | | `grammar` | `kuda-treesitter` language registry | | `symbol_query` | optional; falls back to a builtin | | `dep_query` | optional; import edges for `dependents` | ## What the adapter id controls `grammar` is looked up in the adapter's registry — the match arm inside `kuda-treesitter::language()`. Today that registry has `rust`, `typescript`, `python`, and `go`. Adding a grammar needs the compiled `tree-sitter-` crate as a dependency plus one arm — an adapter change, not an engine change. Descriptors for languages without a compiled grammar still get `manifest` extraction (module graph) for free. ## Writing a symbol query 1. Find the grammar's `node-types.json` (in the grammar repo) for the node names your language uses. 2. Capture the *name* child of each public-ish item as `@name.` — the kind suffix must match `SymbolKind` names or it lands in `Other`. 3. Test locally: put the query in `symbol_query`, run `kuda sync`, then `kuda find `. ## Writing a dep query `dep_query` captures the *import specifier* as `@dep` — whatever syntax names the thing being imported (a `dotted_name`, a string literal, a use-path root). Other captures in the same match are ignored, so `#eq?` predicates can capture freely. Sync normalizes each capture per language (`python` takes the first dotted segment, typescript strips `./` relative specifiers and keeps `@scope/pkg`, go keeps the full import path) and rewrites it to a module name when it matches an indexed package under `-`/`_` folding — `import neuralmind_shared` produces an edge on `neuralmind-shared`. Unmatched specifiers stay external (`dependents serde` still lists importers). ## Deploying descriptors The CLI embeds `data/langs/*.toml` at build time. Adding a file there + rebuilding is the whole distribution path today — a config point for user-supplied descriptor dirs is a TODO item. --- --- url: /kuda/how-to/sturn.md --- # How-to: sturn WAL integration When a repo is [sturn](https://9aia.com/sturn/)-managed, kuda reads the write-ahead log and feeds its ops into sync's diff — a typed, evented change feed on top of the hash walk. ## What it does `sync` already diffs by hashing every file. On sturn repos it additionally walks `refs/sturn/wal/*` and marks modules touched by these ops: | op | feeds | |---|---| | `write` | `path` | | `rename` / `delete` | `from` / `to` / `path` | | `claim` / `release` | static prefix of `glob` | Each ref's consumed head is persisted in the index (`sturn_wal:*` meta keys), so ops are consumed exactly once. **No config.** No WAL refs = no work. It's purely additive — a write that bypassed sturn is still caught by the hash diff. ## Setup ```console $ sturn init # repo gets refs/sturn/wal/* $ sturn ws create main # a workspace to write through $ kuda sync # WAL ops now feed the diff ``` ## The agent contract (advisory) Agents should write *through* sturn when they can — claim before editing, record intent — but sturn is **metadata, not a write gate**. An agent that uses plain `write_file` doesn't break anything; its change still lands in the hash diff, only the coordination signal (claim/intent) is lost. Point agents at sturn's MCP surface in their rules: ```markdown ## Writes - Prefer `sturn` MCP tools for file writes: claim the path, write, then snap. Intent text is required. - If sturn is unavailable, write normally — the index stays correct either way. ``` ## Why additive, not exclusive The WAL is ground truth *only* for writes that flowed through it. The filesystem is ground truth for everything. kuda consumes both: sturn says *who touched what and why*; hashes say *what actually changed*. Neither is trusted alone. --- --- url: /kuda/how-to/llm-summarizer.md --- # How-to: summarizers `kuda-cards` turns extractions into the committed YAML artifacts. Two shipping adapters: `template` (deterministic) and `acp` (agent-written prose); `llm` is parked — ACP is the semantic path. ## `acp` — agent-written cards ```toml [summarize] kind = "acp" acp_cmd = "env CLINE_PROVIDER=cline-pass CLINE_MODEL=cline-free/mimo-v2.6-flash cline --acp" # NOTE: provider/model are env vars, not CLI flags — -P/-m are # ignored in ACP mode. ``` `sync` spawns the agent once per run (one session, reused across modules) and sends each module a session/prompt asking for `purpose:`/`invariants:`/`notes:` grounded in the file list. The agent's prose fills the card's `purpose` + `notes` regions — `api`/`deps`/`verify`/`provenance` stay mechanical. Protocol notes: * session/request_permission from the agent is auto-answered with the first option — required, or the prompt deadlocks * Any ACP-capable agent works: `cline --acp`, `zed agent`, `gemini --experimental-acp`, … * Auth lives in the agent — kuda holds no keys * Agent failure → silent `template` fallback per module ## `template` (default, shipping) Deterministic, zero-model. Each module card's `purpose` comes from the manifest `description` field (Cargo.toml `[package].description`, package.json `description`); `api` lists the extracted public symbols; `deps` lists the module's edges; `verify:` lists the files the card was built from. Because it is mechanical, `template` output is stable — the same inputs always produce the same card, so diffs mean real change. That is the property you want in committed, reviewed artifacts. ## `llm` (parked) A direct-provider adapter (`provider`/`model` + keyring) is in the backlog — ACP is preferred because it is agent-agnostic and key-free. Same grounding rules apply when it lands: cite files or stay silent, never clobber `verify:` or human `notes:`. ## Incremental sync `sync` compares each module's input-file hashes with the committed card's `provenance.hashes` before calling the summarizer — identical means the card is reused and the agent is never prompted. Change one file and only that module re-summarizes; the agent spend is per-drift, not per-sync. ## File-tier cards `file_cards = true` under `[summarize]` adds an L1.5 level: every source file gets a `.kuda/files//.yaml` card. Files are split into ~1.5k-char semantic chunks (tree-sitter aware via text-splitter), budgeted to ~14k chars per prompt, and each card carries its own blob hash — an unchanged file never re-prompts. --- --- url: /kuda/reference/cli.md --- # Command-Line Help for `kuda` This document contains the help content for the `kuda` command-line program. **Command Overview:** * [`kuda`↴](#kuda) * [`kuda init`↴](#kuda-init) * [`kuda sync`↴](#kuda-sync) * [`kuda status`↴](#kuda-status) * [`kuda map`↴](#kuda-map) * [`kuda card`↴](#kuda-card) * [`kuda find`↴](#kuda-find) * [`kuda dependents`↴](#kuda-dependents) * [`kuda refs`↴](#kuda-refs) * [`kuda watch`↴](#kuda-watch) * [`kuda mcp`↴](#kuda-mcp) * [`kuda acp`↴](#kuda-acp) * [`kuda doctor`↴](#kuda-doctor) ## `kuda` A hippocampus for your codebase — repo memory: index, query, verify **Usage:** `kuda [OPTIONS] ` ###### **Subcommands:** * `init` — Detect languages -> kuda.toml + .kuda/ scaffold * `sync` — Incremental index (hash -> extract -> summarize -> store) * `status` — Provenance vs HEAD: per-card drift report * `map` — Render the L0 repo card (the always-load blob) * `card` — Module card + freshness * `find` — Symbol -> file:line * `dependents` — Reverse dep lookup — impact analysis * `refs` — Code + docs/comments mentions — the doc-sweep query * `watch` — Watch the repo — fs events trigger incremental syncs (debounced; only changed modules re-summarize) * `mcp` — Serve the index over MCP (stdio) * `acp` — Speak ACP — Agent Client Protocol surface for any agent client (zed, cline, cursor) * `doctor` — Extractor/store/config health ###### **Options:** * `--config ` — Config path (else $KUDA_CONFIG, ./kuda.toml, ~/.config/kuda/kuda.toml) * `--workspace` — Workspace mode — all roots in .kuda/workspace.yaml ## `kuda init` Detect languages -> kuda.toml + .kuda/ scaffold **Usage:** `kuda init` ## `kuda sync` Incremental index (hash -> extract -> summarize -> store) **Usage:** `kuda sync [OPTIONS]` ###### **Options:** * `--full` — Rebuild everything, ignoring content-hash freshness ## `kuda status` Provenance vs HEAD: per-card drift report **Usage:** `kuda status` ## `kuda map` Render the L0 repo card (the always-load blob) **Usage:** `kuda map` ## `kuda card` Module card + freshness **Usage:** `kuda card ` ###### **Arguments:** * `` ## `kuda find` Symbol -> file:line **Usage:** `kuda find ` ###### **Arguments:** * `` ## `kuda dependents` Reverse dep lookup — impact analysis **Usage:** `kuda dependents ` ###### **Arguments:** * `` ## `kuda refs` Code + docs/comments mentions — the doc-sweep query **Usage:** `kuda refs [OPTIONS] ` ###### **Arguments:** * `` ###### **Options:** * `--docs` — Only docs/comments/markdown mentions ## `kuda watch` Watch the repo — fs events trigger incremental syncs (debounced; only changed modules re-summarize) **Usage:** `kuda watch` ## `kuda mcp` Serve the index over MCP (stdio) **Usage:** `kuda mcp` ## `kuda acp` Speak ACP — Agent Client Protocol surface for any agent client (zed, cline, cursor) **Usage:** `kuda acp` ## `kuda doctor` Extractor/store/config health **Usage:** `kuda doctor` This document was generated automatically by clap-markdown. --- --- url: /kuda/reference/config.md --- # Config — `kuda.toml` ## Top level | field | type | required | notes | |---|---|---|---| | `repo` | `RepoCfg` | optional | — | | `extract` | `ExtractCfg` | optional | — | | `summarize` | `SummarizeCfg` | optional | — | | `store` | `StoreCfg` | optional | — | | `cards` | `CardsCfg` | optional | — | | `workspace` | `WorkspaceCfg` | optional | — | ## `[repo]` | field | type | required | notes | |---|---|---|---| | `name` | `string` | optional | Defaults to the directory basename. | | `root` | `path` | optional | Repo root; default = the toml's dir. | ## `[extract]` | field | type | required | notes | |---|---|---|---| | `kinds` | `list` | optional | Impl kinds tried in order: "manifest" | "treesitter" | "lsp". | | `lsp_server` | `string` | optional | Server command for the lsp adapter (per-language later). | | `exclude` | `list` | optional | Extra dir/file names skipped during walks — beyond the built-in noise set (`default_excludes`). Keeps generated, vendored, and build output out of index, hashes, and agent prompts (agent cost is per-file fed in). | ## `[summarize]` | field | type | required | notes | |---|---|---|---| | `kind` | `string` | optional | "template" (deterministic, default) | "acp" (agent-spawned). "llm" is a backlog adapter — ACP is the semantic path. | | `acp_cmd` | `string` | optional | ACP agent command for kind="acp" — spawns per summarize. | | `file_cards` | `bool` | optional | L1.5 per-file cards — chunked agent summaries per source file. Opt-in: ~10-50x the prompt count of module cards, bounded by per-file hash-skip. | | `provider` | `string` | optional | Provider id for the llm adapter; key resolves via secrets chain. | | `model` | `string` | optional | — | ## `[store]` | field | type | required | notes | |---|---|---|---| | `kind` | `string` | optional | — | | `path` | `path` | optional | L2 index path — rebuildable, gitignored. | ## `[cards]` | field | type | required | notes | |---|---|---|---| | `dir` | `path` | optional | Committed artifacts dir: index.yaml + modules/\*.yaml. | ## `[workspace]` | field | type | required | notes | |---|---|---|---| | `roots` | `Vec` | optional | Sibling repo roots indexed together. | | `resolve_git_deps` | `bool` | optional | Resolve `git = ".../org/repo"` manifest deps to siblings. | ## Constraints * `deny_unknown_fields`: typos in field names fail the load. * `extract.kinds` resolves to adapter crates (`manifest`, `treesitter`, `lsp`); unknown kinds warn at `kuda sync`. * `summarize.kind` is `template` (default) or `acp`; `acp` requires `acp_cmd` and falls back to template on failure. --- --- url: /kuda/reference/cards.md --- # Reference: card schema Two artifact files, both committed YAML under `.kuda/` (location configurable via `[cards].dir`). ## `.kuda/index.yaml` — the L0 repo card ```yaml name: kuda # repo display name root: /abs/path/to/repo purpose: '' # manifest/tagline — human-editable modules: # sorted module names - kuda-core - kuda-store commands: # detected verify/build commands - cargo check - cargo test --workspace tree_digest: 93183a1c… # sha1 over sorted path:hash lines — # every walked file, one line files_total: 12401 # walked-file count (diagnostic) provenance: files: [Cargo.toml, crates/kuda-store/Cargo.toml, README.md] hashes: [sha1, ...] # card inputs only: manifests + README commit_sha: af26b8c… generated_at: '1712345678' # unix seconds generator: kuda/0.1.0 ``` `provenance.files` on the L0 card is deliberately bounded — manifests decide module identity, the README feeds `purpose`, and `tree_digest`/`files_total` fingerprint the rest. Embedding the full walked set put ~10k rows in the always-load card; per-file hash lists still live on the L1 module cards where `status` actually checks them. ## `.kuda/modules/.yaml` — an L1 module card ```yaml name: kuda-store path: crates/kuda-store/Cargo.toml purpose: Store port — IndexStore trait # manifest description api: # extracted public symbols — bounded: - IndexStore # `_`-privates dropped, sorted, capped - SqliteStore # at 40 entries api_omitted: 217 # symbols beyond the cap ("+N more") deps: # outgoing edges - from: kuda-store to: kuda-core kind: PackageDep notes: '' # HUMAN-OWNED — preserved on regen verify: # files to open before trusting — - crates/kuda-store/src/lib.rs # entry points first, capped at 10 provenance: files: [...] hashes: [...] commit_sha: … generated_at: '…' generator: kuda-template/0.1.0 ``` ## Provenance semantics `files` + `hashes` are git `hash-object` digests of the card's inputs at sync time. `kuda status` (and `mimus audit`) re-hash the same files and compare — a mismatch marks the card `drifted`. `commit_sha` records the index point in history; `generator` records which adapter produced the card so format drift is auditable. ## Ownership Machine fields (`api`, `deps`, `provenance`) are regenerated on every `sync`. `notes:` and `purpose:` (when hand-edited past the manifest default) are the human regions — a real merge story is a TODO; today regen overwrites `purpose` only when it still equals the old machine value. --- --- url: /kuda/reference/crate-api.md --- # Crate API Rustdoc for every workspace crate — public APIs are documented at source (doc comments). * [kuda-core](/kuda/api/kuda_core/index.html) — domain * [kuda-config](/kuda/api/kuda_config/index.html) — port * [kuda-secret](/kuda/api/kuda_secret/index.html) — port * [kuda-vcs](/kuda/api/kuda_vcs/index.html) — port * [kuda-acp](/kuda/api/kuda_acp/index.html) — impl * [kuda-cards](/kuda/api/kuda_cards/index.html) — impl * [kuda-config-toml](/kuda/api/kuda_config_toml/index.html) — impl * [kuda-dotenv](/kuda/api/kuda_dotenv/index.html) — impl * [kuda-extract](/kuda/api/kuda_extract/index.html) — impl * [kuda-git](/kuda/api/kuda_git/index.html) — impl * [kuda-keyring](/kuda/api/kuda_keyring/index.html) — impl * [kuda-lsp](/kuda/api/kuda_lsp/index.html) — impl * [kuda-manifest](/kuda/api/kuda_manifest/index.html) — impl * [kuda-query](/kuda/api/kuda_query/index.html) — impl * [kuda-store](/kuda/api/kuda_store/index.html) — impl * [kuda-store-sqlite](/kuda/api/kuda_store_sqlite/index.html) — impl * [kuda-summarize](/kuda/api/kuda_summarize/index.html) — impl * [kuda-template](/kuda/api/kuda_template/index.html) — impl * [kuda-treesitter](/kuda/api/kuda_treesitter/index.html) — impl * [kuda](/kuda/api/kuda/index.html) — binary --- --- url: /kuda/explanation/architecture.md --- # Explanation: architecture kuda answers one question — *"what does this project look like, right now, in a form an agent can load?"* — and treats a wrong answer as worse than none. ## The pipeline ``` repos ──► extract ──► summarize ──► index ──► store ──► serve │ │ │ │ │ adapters: adapters: graph + sqlite cli | manifest template cards + yaml mcp | acp treesitter (llm planned) provenance lsp ``` **Extract** is mechanical — no model, fully rerunnable. It pulls symbols, package structure, and import edges. **Summarize** turns each module's extraction into an L1 card — committed YAML humans review in diffs. **Index** merges everything into the symbol/dependency graph with per-node provenance. **Serve** is read-side only: `find`, `dependents`, `refs`, `card`, `map`, `status` — plus the MCP and ACP wire surfaces. ## The tiers of memory * **L0 repo card** (`.kuda/index.yaml`) — the ~1k-token always-load blob: purpose, module map, commands, provenance. * **L1 module cards** (`.kuda/modules/*.yaml`) — fetched on demand when planning touches a module. * **L1.5 file cards** (`.kuda/files//*.yaml`) — per-source-file `purpose` written by the semantic tier (opt-in, `[summarize] file_cards = true`). Files are chunked with a code-aware splitter (tree-sitter boundaries via text-splitter), fed to the agent, and hash-gated per file. * **L2 graph** (`.kuda/index.sqlite`) — symbols, edges both directions, per-file hashes; gitignored, rebuildable, the query substrate. * **L3** — the source itself. Memory is an index *into* code, never a replacement. Committed L0/L1/L1.5 + ignored L2 is deliberate: cards are reviewed prose, the graph is derived state. ## Incremental sync `sync` is diff-driven end to end: 1. One walk groups manifests + source files by module (manifest package names, not dir names — `kuda-docs`, not `docs`). 2. Every file gets an in-process git blob hash — no subprocess per file. 3. Hashes are compared against the store's `files` table — unchanged modules are never re-parsed; a changed file re-parses only its own module. 4. Cards whose provenance matches are reused verbatim — the summarizer (and the agent, in `acp` mode) is only invoked for drift. Steady-state on a warm index is sub-second; agent spend is per-drift, never per-sync. Deleted files and vanished modules are detected by the same diff — their cards and store rows are removed. ## Extraction tiers Best-available wins, configured per repo via `[extract].kinds`: * `manifest` — free, no parser: package graph + module boundaries from Cargo.toml/package.json/pyproject/go.mod. * `treesitter` — grammar+query data from `data/langs/*`: symbols and `use`/import edges in ~any language. * `lsp` — `documentSymbol` against a real server (`lsp_server = "rust-analyzer"`); the strongest signal where a server exists. Absent server = silent skip. See [adding a language](../how-to/languages.md) — knowledge lives in descriptor files; adding one is a data change. ## Language as data Per-language knowledge — grammar refs, symbol queries, manifest names — lives in `data/langs/*`, not in extractor code. The adapters' vocabulary (`language()` registries, `SymbolKind`, `EdgeKind`) is the only fixed part. ## Ports and adapters `kuda-core` holds domain types only (`Symbol`, `ModuleCard`, `Provenance`, `Claim`-free config). Every concern is a trait crate + adapter crates: * `kuda-extract` → `kuda-manifest`, `kuda-treesitter`, `kuda-lsp` * `kuda-store` → `kuda-store-sqlite` * `kuda-summarize` → `kuda-template`, (`kuda-llm` planned) * `kuda-vcs` → `kuda-git`; `kuda-config` → `kuda-config-toml`; `kuda-secret` → `kuda-keyring`, `kuda-dotenv` New capability = new adapter crate, not a core rewrite. ## Trust model Every artifact carries `provenance {files, hashes, commit_sha, generator}` — `kuda status` re-hashes and reports drift per card. Cards end with `verify:` — the files an agent should open before acting on the card. Memory is a hypothesis that narrows the search space; the code remains the only truth. `mimus audit` independently re-verifies card provenance — memory with an external falsifier. --- --- url: /kuda/explanation/release-model.md --- # The release model kuda releases itself — there is no hosted CI and no release tool in the loop. Two deliberate phases: ## Phase 1 — the explicit seed `kuda deploy release` runs once, by hand: it reads cargo metadata, topologically orders the workspace's path-dependency graph, `cargo publish`es every member, tags `{repo}-v{version}`, and pushes the tag. Why explicit? Publishing is the **only irreversible step** in the whole system. Queues, runs, and statuses are all idempotent and local; a crate version on crates.io is forever. Everything else in kuda fires on push — the seed doesn't. (`--dry-run` prints the order and tag without touching anything.) ## Phase 2 — push-driven forever after From then on a release is *just another kuda job*: a push to `main` hits the webhook, queues latest-wins, and runs `scripts/release.sh` in a detached worktree under the shell runner. The script: 1. diffs crate paths since the last `kuda-v*` tag 2. `cargo set-version --bump patch` on touched crates only (the cli republishes on any change — it's the install artifact) 3. `cargo publish`es them in dep order 4. tags `kuda-vX.Y.Z`, builds host-arch binaries with `cargo dist`, uploads to a GitHub Release via `gh` The 5-minute sleep at the top isn't a hack — it's the debounce window. Combined with `queue = "latest"` + `cancel_running`, rapid push bursts collapse to one release of the newest sha; killed runs get an honest "cancelled" status. ## Why this shape * **No release-plz / GHA**: the point of the project is that CI is a local loop — depending on hosted CI to ship it would be self-defeating. `scripts/release.sh` is deliberately boring bash so it can run as a plain `cmd` under `runner.kind = "shell"`. * **Latest-wins, not `all`**: a release that misses an intermediate push loses nothing — the tag lands on HEAD, which contains everything. `queue = "all"` is for pipelines where each sha's run is individually meaningful. * **Explicit over automatic**: a wrong tag or half-published workspace is far worse than a delayed release; the boundary sits exactly at irreversibility. Host prerequisites: `cargo-edit`, `cargo-dist`, `gh`, and `CARGO_REGISTRY_TOKEN` (env → keyring → env file). Multi-arch binaries arrive when the remote-runner pair lands — see TODO. --- --- url: /kuda/comparison.md --- # kuda vs the alternatives ## vs grep + re-read every session | | grep/ripgrep | kuda | | --------------------- | --------------------- | -------------------------- | | find a symbol | fast, per-query | fast — plus kind + card | | "what depends on X" | manual, cross-repo pain | one query | | overall comprehension | none | L0 card in ~1k tokens | | freshness | always current | provenance-checked | | doc/comment mentions | same grep | unified `refs --docs` | ## vs embeddings / RAG | | RAG store | kuda | | --------------------- | --------------------- | -------------------------- | | fuzzy semantic lookup | strong | (optional store adapter later)| | dep graph, provenance | weak | first-class | | staleness | opaque | explicit sha/hash diff | | determinism | model-dependent | `template` = deterministic | ## vs LSP alone | | LSP | kuda | | --------------------- | --------------------- | -------------------------- | | cross-file refs | strong (per language) | `lsp` adapter reuses them | | needs a running server| yes | only for the `lsp` tier | | prose/cards | none | L1/L0 artifacts | | persistence | session-scoped | committed + rebuildable | Trade-off: kuda adds an index step and committed artifacts — the payoff is that "overall comprehension" becomes a fixed ~1k-token load instead of a per-session expedition. --- --- url: /kuda/getting-started.md --- # Getting started Pick by what you need to do. * **Tutorials** (learn): [first-run](tutorials/first-run.md) * **How-to** (tasks): [multi-repo](how-to/multi-repo.md) · [mcp](how-to/mcp.md) · [llm summarizer](how-to/llm-summarizer.md) * **Reference** (lookup): [cli](reference/cli.md) · [config](reference/config.md) · [crate api](reference/crate-api.md) * **Explanation** (why): [architecture](explanation/architecture.md) · [release model](explanation/release-model.md) · [comparison](comparison.md) — vs grep / RAG Install: ```sh cargo install --git https://github.com/9aia/kuda kuda ``` First index: ```sh cd your-repo kuda init # detect languages -> kuda.toml + .kuda/ kuda sync # incremental index -> .kuda/ artifacts kuda status # provenance vs HEAD kuda map # the L0 card — what agents load first ``` --- --- url: /kuda/public/api/static.files/SourceSerif4-LICENSE-a2cfd9d5.md --- Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. Copyright 2014 - 2023 Adobe (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL *** ## SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1. Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2. Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3. No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5. The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.