Guidance for Claude Code (and any other agent) working in this repository.
Pooprusteek is a Rust TUI coding agent that talks to DeepSeek's reverse-engineered
web API (no paid API key — cookie/token auth, local SHA-3 proof-of-work) instead
of an official LLM API, with optional extra providers (OpenAI-compatible /
Anthropic Messages / Gemini endpoints via /providers). Built on tokio +
ratatui, it runs a single event-driven terminal UI supporting parallel
conversations, sub-agents, an iterative GOAL worker/evaluator loop, MCP
(stdio/HTTP/SSE tool servers with OAuth), markdown skills, and a fully local
semantic (RAG) layer — multilingual-e5-small ONNX embeddings + stemmed TF-IDF
- RRF over three corpora: skills, MCP tools, and persistent message history — a free, terminal-native alternative to Claude Code.
This repo keeps a curated knowledge base in .memories/ that this file bridges
to. Read .memories/INDEX.md first — it gives the full read order
(QUICKSTART.md → STATE.md → MAP.md → ARCHITECTURE.md → GLOSSARY.md →
BUGS.md → PLANS.md → LEARNINGS.md → CONVENTIONS.md → JOURNAL/), plus a
reference/ folder looked up on demand (commands, provider, tools, MCP, config,
prompts). .memories/reference/AUDIT-2026-07-02.md holds the latest full-codebase
audit — check it before assuming a subsystem is clean.
Note: Claude Code auto-loads this file, but the app itself does not auto-load
.memories/ — an agent only benefits from that folder if told to read it.
cargo build
cargo test --bin pooprusteek
cargo clippy --bin pooprusteek
MSRV 1.91 (edition 2024). CI (.github/workflows/ci.yml) is one sequential
pipeline: test (build+test, Windows+Linux) and lint (cargo fmt --check
cargo clippy -D warnings, blocking) gate everything else — the rollingdev-buildrelease (release-build+publishjobs, seescripts/dev-release.template.md) only builds and publishes once both pass, and only for a push todevelop. A local pre-commit hook (.githooks/pre-commit, opt in viagit config core.hooksPath .githooks) runs the same three checks before a commit is even made; bypass withgit commit --no-verify..gitlab-ci.ymlmirrors the samecheck → build → releaseshape for a future GitLab project (unverified, no GitLab remote exists yet).
Semantic/retrieval changes have their own quality gate — an #[ignore]d MRR
eval harness that needs the ~120 MB embedding model on disk (downloaded once,
shared with the app):
cargo test --bin pooprusteek semantic::eval -- --ignored --nocapture
Run it whenever you touch src/semantic/ ranking (thresholds, RRF, corpus
text composition) and record the numbers in the journal. Current baselines:
skills MRR 0.927, MCP tools MRR 0.836.
Build flake to know about: parallel rustc + ort linking can exhaust the
Windows pagefile (STATUS_STACK_BUFFER_OVERRUN / os error 1455, "paging file
too small"). It is NOT a code error — retry, or cargo test -j 1.
A single tokio::select! event loop multiplexes ticks, terminal input, and an
internal AppEvent channel. App is a thin coordinator, not a god-object —
behavior lives in cohesive sub-state structs and controllers it owns.
Conversations is the multi-chat store: each Conversation owns its own
messages, a forked provider/session, and its agent task, so concurrent
turns never collide. AgentRuntime::spawn(TurnSpec) is the only place any
turn (normal, sidechat, sub-agent) launches — it runs run_agent_loop in a
spawned task. Every emitted event is tagged with a ConversationId so
background turns route into the right buffer instead of the focused one. The
TUI render path reads the focused conversation's state and never mutates it.
Key subsystems added on top of that core:
src/semantic/— local RAG.SemanticServiceis a shared handle over one embedder + three corpora (skills, MCP tools, message history). Init is background (spawn_initonspawn_blocking; first run downloads the model, then backfills history from saved session files). Each turn gets an ephemeral hint (match_promptinrun_agent_loop) suggesting skills and MCP tools; thetool_search/history_searchbuiltins expose the same index to the model on demand. Deferred MCP schemas: above 12 tools ([semantic] mcp_schemas = auto), the system prompt carries only a server-level summary (<server> (N tools)) — individual tools are not enumerated; full definitions come from hints ortool_search. The history index (data_dir/semantic/history.json) is a rebuildable cache over session files (per-session watermarks, model-stamp wipe);/search(View::Search) and/ragare the user-facing surfaces.src/provider/— multi-protocol. The built-in DeepSeek web client (PoW + SSE) plus/providers-managed entries speaking OpenAI Chat Completions, Anthropic Messages, or Gemini. All implementLLMProviderwithfork()for per-conversation session isolation.src/mcp/— servers + OAuth. 8-source config discovery,/mcp add(JSON / wizard / one-liner), RFC 9728/8414/7591 + PKCE authorization with OS-keyring token storage.
- Never block or
.awaitnetwork/LLM/file I/O insidehandle_event— it runs on the mainselect!loop; spawn a task and send anAppEventback instead. - Never hold the
MCPManagerlock (or any sharedMutex) across an I/O.await— a slow call under the lock freezes every other consumer of that lock, including the UI. - All user-data persistence goes through
util::atomic_write, neverstd::fs::write— direct writes truncate-in-place and corrupt on crash mid-write. - Never byte-slice text — use
util::truncate_at_char_boundary; raw byte indices can split a multi-byte UTF-8 char or emoji. - A slash command is one file in
src/commands/defs/+ registration incommands/mod.rs—name()must be returned without a leading slash (dispatch strips the/before lookup; a registry test enforces this). - The tools layer must never reach into the app layer —
tools/andapp/communicate exclusively throughAppEvents, never direct calls or shared state upward. - A new tool = implement the
Tooltrait + one registration line intools/registry.rs. - Update
.memories(STATE.md,BUGS.md,JOURNAL/) as part of "done" for any non-trivial change. Anchor documentation to function/struct names, not line numbers — line numbers drift on the next edit. - CPU-heavy work (ONNX embedding, PoW solving) runs on
tokio::task::spawn_blockingonly — never on the event loop, never bare on an async worker. TheSemanticServiceinner mutex is held across synchronous work only, never across an.await. - Never print to stdout/stderr while the app runs — the TUI owns the terminal and
--acpowns stdout for JSON-RPC.tracinggoes to the data-dir log file (set up inmain.rs); ad-hoc debugging goes throughdebug_log. This includes dependencies: fastembed's download progress bar is explicitly disabled for this reason. - The semantic layer must degrade, never block features — every entry point returns empty/falls back to lexical when the embedder is disabled, still initializing, or failed. Deferred MCP schemas are only allowed when semantic matching is enabled (
App::effective_mcp_schema_modeforcesFullotherwise); session files remain the source of truth for the history index (it is a wipeable cache).
Full style guide: .memories/CONVENTIONS.md (error handling, async/state
patterns, module decomposition, naming, TUI rules, test conventions).
Commits are conventional commits with a scope + gitmoji, e.g.
refactor(app): 🧹 …, fix(deepseek): 🐛 …, feat(provider, app): ✨ … — see
git log for the live pattern. Commits are the user's job; don't run
git commit/git push unless explicitly asked.