LiuHe
Code tools for LLMs to read, understand and modify code, using a tree-sitter parser, SQLite index and transactional writes.
Installation
Les étapes d'installation sont dans la documentation ci-dessous. Documentation
Documentation
Tirée du README du projet sur GitHub, actualisée chaque semaine. Les liens ouvrent GitHub.
Sommaire
Malong LiuHe(码龙·六合工具)
LLM-Native Code Operations Toolkit — Code tooling reinvented for the LLM that has no hands, no eyes, and no memory.
English | 简体中文 | Docs | GitHub
-65%25-blue)
Malong LiuHe is a toolkit built for LLMs rather than humans. It ships two components:
malong/— an MCP toolset whose default tier is the Six-He line (13 tools:code_task → code_read → code_patch / code_batch → code_check → code_control, with task state, evidence and structurednext_actionhand-back built in); the classic full catalog (44) and the diagnostic tier (18) are one env away —MALONG_INTERFACE=classic/MALONG_INTERFACE=diagnostic. The catalog spans symbol read/write, indexing, impact analysis, reference tracing, call graphs, dead-code detection, code review, security scanning, atomic batch editing, test orchestration, pipeline verification, plus capability discovery (code_querymode=capabilities/capability) and the ADR-018 full-call outlet (invoke_capability) — served to the LLM over MCP (JSON-RPC over stdio). Since 0.5.24 the Six-He line is the default; classic remains the full catalog.malong-parse/— a Rust parsing service: a tree-sitter-based multi-language symbol extraction engine (JavaScript/TypeScript/TSX/Python/Go/Rust/C/C++/Java/Bash), talking to the toolset over a Unix socket, with an LRU tree cache and batched parallel extraction.
Core Idea
Traditional tools (git, sed, IDE) assume a user with hands, eyes, and memory. An LLM has none of these. Malong LiuHe is redesigned around that fact:
| Missing | Design Consequence |
|---|---|
| No hands | Operations must be atomic, undoable, retryable (edit_transaction with rollback and undo journal) |
| No eyes | Output must be structured and self-explanatory (JSON for direct consumption; errors carry suggestion/next_action) |
| No memory | Every call is self-contained (takes workspace_dir); optimistic concurrency with version anchors (read_symbol → write_symbol(base_version) conflict state machine) |
Performance (Measured)
| Metric | Value | Note |
|---|---|---|
| Small-file read P95 | 1ms | 50 runs, warmed |
| Small-file write P95 | 7ms | 30 runs, incl. post-write re-index |
repo_map | 98ms | down from tens of seconds (SQLite index + Rust parsing) |
| Memory | RSS 134MB / 26% | under a real docker --memory=512m cgroup limit |
| Concurrency | 128 concurrent / 256 in-flight requests, zero OOM | 32-way mixed read+write on one hot file, no tearing, DB integrity_check PASS |
| Throughput | ~588 calls/s | single-threaded ceiling — 60–600× real LLM demand |
| Token estimate (fixed-path) | ↓65.3% | scripted measure-trail.js: 6-step legacy chain → 3-call primitives, 7673 → 2662 chars/4 est. Fixed editor path only — not a host-vs-malong nor an agentic-conversation result. |
| Call reduction (fixed-path) | ↓50.0% | 6 calls (legacy chain) → 3 calls (primitives); same scripted path |
| Tool-description compression | 44 classic tools ≈ 1.33k tokens | tiered: core kept, low-freq ≤70 chars, verbose ≤230; detail flows via next_step hints |
| Default-tier catalog tax (per turn) | 13 tools = 16.5 KB ≈ 4.86k tokens | descriptions 2.0 KB (≈0.60k) + inputSchema 14.5 KB (≈4.26k) — the schema is 88% of it; to shrink the catalog, cut the parameter surface, not the prose |
| Index throughput | 538 files / 7s | scoped reindex, measured |
| dry_run fidelity | 47/47 | golden-hash comparison across 3 languages, 100% |
| Concurrent writes to same symbol | 16/16 | exactly one winner + one conflict, zero silent overwrites |
Language Support
| Language | Symbol Extraction | Reference Extraction |
|---|---|---|
| JavaScript / TypeScript / TSX / JSX / MTS / CTS | ✓ | ✓ |
| Python | ✓ | ✓ |
| Go | ✓ | ✓ |
| Rust (impl blocks / trait / enum semantics) | ✓ | ✓ |
| C / C++ (incl. headers) | ✓ | ✓ |
| Java | ✓ | ✓ |
| Bash | ✓ | ✓ |
Tool Overview
| Category | Tools |
|---|---|
| Six-He line (graph + stateful — recommended daily entry) | code_task (resolve + plan) → code_read (materials + read_ref) → code_patch / code_batch / code_maintain (journaled writes) → code_check (evidence) → code_control (status / resume / expand / reopen / job); plus code_status, code_query, code_audit, code_worktree, invoke_capability, edit_transaction — 13 tools, env-gated |
| I/O primitives | read_symbol (symbol read + version), write_symbol / write_symbols (safe write: conflict state machine + atomic write + undo) |
| Index & search | reindex, symbol_search, code_search (keyword + regex intent patterns — NOT semantic search), repo_map (98ms file map; skeleton pagination: page/page_size/prefix — truncated output always carries the full top-level skeleton with page numbers) |
| Analysis | impact_analysis (blast radius + risk), call_chain, references, dep_graph, inspect (outline + refs + call chain in one), trace_symbol (constant tracing + hardcoded copies) |
| Editing | edit_batch, edit_transaction (atomic txn + rollback + cross-session lock + dry-run preview + create), edit_collision_guard (write conflict detection), sandbox_validate (pre-write validation), diff_facts (symbol changes + test sync), rename_symbol (atomic cross-file rename), git_worktree (isolated branch + verify + merge) |
| Quality gates | code_review, security_review (regex security patterns), code_quality (5-dim probe), style_sniffer, guard_patterns, naming_consistency, exception_guard, config_drift, dependency_gatekeeper, fix_imports, sweep_dead_code, mock_sync — all pure regex/AST, zero LLM calls, deterministic |
| Engineering | test_bridge (run/suggest/discover), find_tests, verify_pipeline (lint/test/typecheck stages), debug_runner (error analysis), patch_parser (SEARCH/REPLACE), tsc_check, spec_gen, active_todos |
| System | health, gc, feedback |
Long test suites must run in the background. The MCP request has a hard 120s ceiling and a foreground stage is capped at 110s, so a suite that takes minutes is reported as
failed— that is a request-budget truncation, not a code defect. Run it as a background job instead:code_checkwithrun: true, async: true(600s cap), then poll withcode_control(action="job").
Architecture
┌─────────────────────────┐ ┌──────────────────────────────┐
│ LLM Client (MCP) │ │ malong-parse (Rust daemon) │
│ malong/ 13 six-he │◄───►│ tree-sitter, 10 languages │
│ (+44/18 gated) │ │ LRU tree cache 50MB / batch │
│ Unix socket client │ │ catch_unwind crash recovery │
└─────────────────────────┘ └──────────────────────────────┘
- All parsing is done by the Rust service (zero tree-sitter bindings on the Node side)
- Index is SQLite (WAL mode, multi-process safe), isolated per workspace
- LRU tree cache (50MB / 5min TTL), batched extraction 50 files/batch
- Daemon multi-session safety: circuit breaker + liveness probe + cross-session coordinated restart (O_EXCL lock) — a slow request in one session never stalls others
Self-Hosting (Dogfooding)
30+ rounds of "Malong reviews Malong": the toolset audits and fixes its own code, with dozens of real bugs fixed and locked by tests — including directory-scope filtering gaps, false dead-code reports for registration patterns, lost constant read-sites, and SQL parameterization cleanup. Every round grew the suite (now 166 JS test files, full chain 0 failures). Latest round (r5): cross-file reference resolution is now import-aware — member calls such as path.join no longer bind to same-named local symbols (216 false bindings in this repo alone), cross-language (Python↔JS) links are blocked by a language-family filter, and aliases resolve through the import graph (ESM specifier scanning added on the JS side) — locked by test-crossfile-resolve (14 assertions, red→green); the DSH bridge also now pins MALONG_STATE_DIR when spawning the server, locked by test-dsh-bridge.
Scan Boundaries (limits acknowledged; committed within)
Deterministic scanners only — regex pattern matching and reference graphs. No control-flow, data-flow, or cross-module semantic coverage. 0 findings ≠ clean; high scores ≠ healthy.
security_reviewscans: injection (eval/Function-ctor, exec/spawn string building, template${},$()substitution, SQL concat), XSS, hardcoded secrets, CORS *, timing/insecure compare, dotenv. Not scanned: SSRF, XXE, deserialization, auth/logic flaws, control-flow defects.sweep_dead_codereference scope: import graph + fixed text-ref fallback (.sh/.json/.md etc.); CLI-string references count as alive (miss-over-delete). Not scanned: dynamic/reflection wiring.- Commitment: issues in scope get fixed; covered patterns are test-locked; new rules must stay within committed categories.
Quick Start
Standard install (default — recommended)
npm ci installs better-sqlite3 (the full SQLite backend); this is the default recommended path:
# 1) Get the parsing daemon — download a prebuilt binary from releases/, or build:
cd malong-parse && cargo build --release && cp target/release/malong-parse ~/.local/bin
malong-parse & # start the daemon (Unix socket)
# 2) Install the toolset (npm ci installs the full better-sqlite3 backend)
cd ../malong && npm ci
# 3) Register the MCP server (works with any MCP client, e.g. opencode / Claude Desktop)
# opencode (project-root opencode.json; Windows paths use double backslashes, Linux/macOS forward slashes):
# {
# "mcp": { # opencode V2 native: servers grouping (V1 used direct mcp.<name>)
# "servers": {
# "malong": {
# "type": "local",
# "command": ["node", "--max-old-space-size=512", "N:\\repo\\liuhe\\malong\\mcp-server.js", "--workspace", "N:\\repo\\liuhe"],
# # default since 0.5.24 = Six-He line (13 tools); this block is optional now — delete it for the classic 44 catalog
# "environment": { "MALONG_INTERFACE": "graph", "MALONG_ENTRY_STATE": "1" }
# }
# }
# }
# }
# Claude Desktop (verified on Windows: %APPDATA%\Claude\claude_desktop_config.json):
# {
# "mcpServers": {
# "malong": { "command": "node", "args": ["/path/to/malong/mcp-server.js"], "env": { "MALONG_INTERFACE": "graph", "MALONG_ENTRY_STATE": "1" } }
# }
# }
# 4) Ask your LLM: "search for the symbol 'handle' in my workspace"
# DeepSeek Harness (dsh web) — auto session-workspace convenience (extra, optional):
# Run once on the dsh host (Linux/macOS):
# Option A (npm, one line — full backend + platform binary included):
# dsh plugin --profile web add @jieai/dsh-malong-bridge
# pkill -f "dsh web"; dsh web --port 3456 --host 0.0.0.0 --trusted-host <LAN IP>
# Option B (script, points at a checkout of this repo):
# bash malong/dsh/install-dsh.sh # idempotent; edits ~/.dsh/profiles/web/cordis.patch.yml with backup
# The bridge exposes the Six-He line (13 tools: code_task/code_read/code_patch/...) as malong__* and auto-fills workspace_dir from the
# current conversation's workspace (no per-call path needed; explicit paths still win).
# Need the classic full catalog (44) or the diagnostic tier (18)? export MALONG_INTERFACE=classic|diagnostic on the dsh host.
# Full guide incl. index rules: malong/dsh/DSH-INTEGRATION.md
# Troubleshooting: if EVERY malong__* call hangs until timeout, the bridge's
# mcp-server subprocess is gone. The bridge now auto-restarts it (exponential
# backoff, pending calls fail fast with "restarting — retry shortly"); if that
# keeps failing, restart dsh web: kill <dsh web pid> && dsh web --port 3456 ...
SQL backend note: the default is the full
better-sqlite3backend. Ifnpm installfails (offline / no build toolchain / Node < 20), the server automatically degrades to the vendored sql.js WASM sandbox backend (malong/vendor/, zero-dependency, no network) — the startup log shows which backend is active and the upgrade command. Data files are fully compatible between backends (both are SQLite).
Zero-build deployment (degraded path: sandbox / offline environments)
For environments with Node >= 20 and no npm access (no npm ci, no native
compilation), everything runs from the repo files as-is (Linux/Windows — the
prebuilt binaries are linux-x86_64 and windows-x86_64). On this path SQLite
runs on the sql.js sandbox backend (degraded):
# 0) Clone, then enter the toolset directory:
git clone <repo-url> liuhe && cd liuhe/malong
# 1) Parse daemon — prebuilt binary from releases/ (no cargo needed):
mkdir -p ~/.local/bin
tar -xzf ../releases/malong-liuhe-0.5.27-linux-x86_64.tar.gz
cp malong-parse/target/release/malong-parse ~/.local/bin
malong-parse & # start the daemon (socket: /tmp/malong-parse-$UID.sock)
# 2) Run the toolset — zero npm install:
node --max-old-space-size=4096 mcp-server.js --workspace /path/to/project
# vendored SQLite (sql.js WASM) auto-enabled when better-sqlite3 is unavailable
# 3) Self-check (30s, optional):
node tests/test-db-adapter.js # 22 assertions: sql.js backend + persistence
node tests/test-mcp-server.js # 37 assertions: MCP stdio + daemon round-trip
Windows users: use
malong-liuhe-0.5.27-windows-x86_64.tar.gzfrom GitHub Releases (containsmalong-parse.exe), extract and put it on PATH; the MCP server auto-starts the daemon, so step 1'smalong-parse &is not required. Themkdir -p/tar -xzfabove are Unix syntax — on Windows use any extractor (tar -xzfworks on Win10+).
Note: if the daemon is not running, parse-dependent tools (symbol extraction etc.) degrade; the SQLite-backed tools (repo-map / code-index / health-check) still work.
- See
THIRD_PARTY_NOTICES.mdfor the vendored component.
What the LLM sees back:
{
"results": [
{ "name": "handle", "type": "function", "start_line": 6, "end_line": 12, "file": "app.js" },
{ "name": "handle_login", "type": "function", "start_line": 5, "end_line": 9, "file": "src/auth.py" }
],
"count": 2,
"next_step": "Before modifying found symbols, check blast radius: impact_analysis(...)"
}
Build the Rust parsing service
cd malong-parse
cargo build --release
cp target/release/malong-parse ~/.local/bin/ # or add to PATH
malong-parse & # daemon (Unix socket: /tmp/malong-parse-$(id -u).sock)
Prebuilt binaries for Linux x86_64 are committed under
releases/ (with .sha256 checksums) — pick one up instead of building if you prefer.
Platforms: Linux and macOS use the Unix socket; on Windows the daemon listens on TCP 127.0.0.1:31001 (set MALONG_PORT to override) — the MCP server probes and auto-starts malong-parse.exe on startup, so no manual launch is needed (if you use the parse-client API directly outside MCP, start it manually). Set MALONG_SOCKET to override the Unix socket path.
Install the toolset
cd malong
npm ci
npm test # 166 test files (daemon must be running)
Start the MCP server
cd malong
node --max-old-space-size=512 mcp-server.js --workspace /path/to/project
Register the MCP server with any MCP-capable LLM client and the tools become available. For opencode, register in the project root opencode.json (the environment block is optional since 0.5.24 — the default tier already is the Six-He line, 13 tools; set MALONG_INTERFACE=classic for the classic 44-tool catalog):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"servers": {
"malong": {
"type": "local",
"command": ["node", "--max-old-space-size=512", "malong/mcp-server.js", "--workspace", "."],
"environment": { "MALONG_INTERFACE": "graph", "MALONG_ENTRY_STATE": "1" }
}
}
}
}
Windows note: relative paths in
--workspaceandmalong/mcp-server.jsresolve against the directory where opencode is launched; pass absolute paths (escape backslashes as\\in JSON) if you start it elsewhere. The daemon is auto-started by the MCP server — no need to launchmalong-parse.exemanually.
For opencode V2 details — config shape, tool visibility (codemode), and troubleshooting — see malong/opencode/OPENCODE-V2.md.
Configuration (environment variables)
All optional — sensible defaults apply when unset.
| Variable | Default | Purpose |
|---|---|---|
MALONG_STATE_DIR | ~/.config/malong | Root of MCP state since 0.5.0: workspace indexes (workspaces/<hash>/code-index.db) plus usage / feedback / edit-stats. Legacy per-repo data/malong-mcp (seeded via --workspace) is migrated on first launch in an isolated child process (copy→verify→delete; fail-soft, source kept on failure); pre-0.3.37 ~/.config/opencode/ reads still fall back. |
MALONG_SKIP_STATE_MIGRATION | unset | Set to 1 to skip legacy-state migration at startup (emergency escape; old directory left untouched). |
MALONG_SOCKET | /tmp/malong-parse-$(id -u).sock | Unix socket path to the parse daemon (Linux / macOS). |
MALONG_PORT | 31001 | TCP port for the parse daemon (Windows). |
MALONG_PARSE_BIN | npm platform pkg / ~/.local/bin | Binary used when the client auto-starts the daemon. Resolution order: this env → @jieai/malong-parse-<os>-<arch> npm platform package (installed as an optional dependency of @jieai/dsh-malong-bridge) → ~/.local/bin/malong-parse → malong-parse/target/release (dev tree). |
MALONG_PARSE_MODE | rust-service | Parse transport. Only rust-service is supported (builtin / shadow are rejected). |
MALONG_WS_GC_DAYS | 14 | Days a workspace index cache may sit untouched before health cleanup prunes it; 0 disables. |
MALONG_INTERFACE | graph | Tool tier. Default (unset) = the Six-He line (13 tools: code_task/code_read/code_status/code_query + 8 stateful entries + edit_transaction). classic = the full 44-tool catalog; diagnostic = 18 (Six-He line + 5 analyzers). |
MALONG_ENTRY_STATE | on | The state layer (S1–S5 task state machine + ADR-018 invoke_capability) is allowed by default; set 0 for the read-only 5-tool tier. |
MALONG_PYTHON | unset | Explicit interpreter for Python test runs (test_bridge / verify_pipeline); overrides the auto-detected project venv (.venv/venv/env next to the project root). |
Suppressing security false positives
security_review is deliberately conservative, so benign patterns (a hash used as a cache key, process.exit in a CLI entry) can trip it. Two explicit, auditable suppression mechanisms exist — neither ever auto-suppresses injection rules.
Inline — append malong-ignore to a line to suppress all findings on it, or malong-ignore[eval,exec-cmd] for specific rules (a reason after : is encouraged):
const id = crypto.createHash('md5').update(p).digest('hex') // malong-ignore: cache-dir name, not security
Config — add a securityIgnore array to .ai-patterns.json (the same file guard_patterns reads). Each entry takes files (* / ** globs) and/or rules; omit one side to match all:
{ "securityIgnore": [ { "files": ["**/mcp-server.js"], "rules": ["process-exit"] } ] }
Suppressed findings are dropped from the score but counted in the summary's suppressed field, so nothing is hidden silently. Injection rules (eval / exec / SQL / spawn) are only ever suppressed by an explicit marker or entry you authored — never heuristically.
Journal auto-cleanup
Every safe write (write_symbol / write_symbols / edit_batch) leaves an undo journal under .malong/journal/. Terminal transactions (committed / rolled_back / abandoned / failed) older than a TTL (default 24h, configurable) are pruned automatically — throttled to at most one scan per hour per workspace, so it costs nothing. In-flight (created / staged) and needs_review (external change awaiting human review) journals are never auto-deleted. This only ever touches the tool's own rollback backups — never your source files.
Using with Claude Code
Verified end-to-end with Claude Code 2.x. The MCP layer is standard JSON-RPC over stdio, so any MCP client (Claude Code, codex, opencode, Claude Desktop…) connects the same way.
1) Prerequisites — Node >= 20 and the parsing daemon running (see the Zero-build deployment or Build the Rust parsing service sections above).
2) Install Claude Code
npm install -g @anthropic-ai/claude-code
3) Point Claude Code at an Anthropic-compatible provider
Claude Code needs an LLM backend. Any Anthropic-compatible endpoint works — set the
variables below (or manage them with cc-switch):
export ANTHROPIC_BASE_URL="https://your-provider/anthropic" # any Anthropic-compatible endpoint
export ANTHROPIC_AUTH_TOKEN="your-api-key" # never commit this
export ANTHROPIC_MODEL="your-model" # a model your provider serves
With
cc-switch:npm i -g @cc-switch/cli,cc-switch new my-provider(fill in the sameANTHROPIC_*keys underenv),cc-switch switch my-provider, theneval $(cc-switch export).
4) Register the MCP server
claude mcp add liuhe -- node /path/to/malong/mcp-server.js --workspace /path/to/project
claude mcp list # → "liuhe … ✔ Connected"
5) Use it — start claude in your project and ask, or run headless:
claude -p "index the workspace with reindex, then find createDb with symbol_search" \
--allowedTools "mcp__liuhe__reindex" "mcp__liuhe__symbol_search"
Tools are exposed as mcp__liuhe__<tool> (e.g. mcp__liuhe__health,
mcp__liuhe__symbol_search, mcp__liuhe__repo_map). Run reindex (optionally
blocking=true) once per workspace first, so symbol search / impact analysis have
an index to query.
Using with codex
Verified end-to-end with codex (same JSON-RPC-over-stdio MCP layer).
Version note: recent codex releases force the OpenAI Responses API (
wire_api = "responses"). Use a version that still supportswire_api = "chat"together with MCP — verified with 0.50.0:npm install -g @openai/codex@0.50.0.
1) Prerequisites — Node >= 20 and the parsing daemon running (see above).
2) Configure ~/.codex/config.toml — point codex at any OpenAI-compatible
provider (example below: OpenCode Zen) and register the MCP
server:
model = "deepseek-v4-flash"
model_provider = "opencode-zen"
[model_providers.opencode-zen]
name = "OpenCode Zen"
base_url = "https://opencode.ai/zen/go/v1" # any OpenAI-compatible endpoint
wire_api = "chat"
env_key = "OPENCODE_ZEN_API_KEY" # codex reads the API key from this env var
[mcp_servers.liuhe]
command = "node"
args = ["/path/to/malong/mcp-server.js", "--workspace", "/path/to/project"]
3) Run — export the API key, then start codex:
export OPENCODE_ZEN_API_KEY="your-api-key" # never commit this
codex exec "index the workspace with reindex, then find createDb with symbol_search"
Tools are exposed by the malong-mcp server; the prefix follows the name you
register it under — opencode malong_<tool> (e.g. malong_health,
malong_symbol_search), Claude Code mcp__liuhe__<tool>, the DSH bridge
malong__<tool>. Run reindex once per workspace first so symbol search has an
index to query.
Testing
- Rust:
cargo test(97 assertions: per-language extraction + protocol framing/decoding + cache LFU + server dispatch/priority queue + batch_extract + deep-nesting guards + hello handshake) - JS: 166 test files (authoritative list:
scripts.testinpackage.json; includes primitives / tool-registry / manifest-quality / write-runtime / edit-transaction-ext / verify-pipeline, the dogfood r12–r30 end-to-end series, plus G0/G1 capability-contract / capability-catalog / invoke-capability / invoke-stdio suites) - Total: 166 JS test files, full chain 0 failures (plus cargo test: 97 assertions)
- One-shot:
./scripts/ci.sh(self-contained: cargo test + npm test + dogfood; reuses or auto-starts the daemon)
License
Changelog
See CHANGELOG.md for the full release history.
Questions fréquentes
Qu'est-ce que LiuHe ?
Code tools for LLMs to read, understand and modify code, using a tree-sitter parser, SQLite index and transactional writes.
Comment installer LiuHe ?
Suivez les étapes d'installation de sa documentation, affichée sur cette page et dans son dépôt.
LiuHe est-il open source ?
Oui. Son code est sur GitHub sous licence MIT.
Qui maintient LiuHe ?
Il est publié par wulun811. AI.tn le référence sans le maintenir ni le recommander.
Issu des listes communautaires awesome-mcp-servers et du dépôt GitHub du projet. Être listé n'est pas une recommandation : vérifiez le code et les permissions d'un serveur avant de lui donner accès à vos données.