Files
yvgude--lean-ctx/CHANGELOG.md
T
wehub-resource-sync 26382a7ac6
CI / Test (macos-latest) (push) Waiting to run
CI / Test (windows-latest) (push) Waiting to run
CI / Build (no embeddings / no ORT) (push) Waiting to run
CI / Format (push) Waiting to run
CI / Cookbook (Node) (push) Waiting to run
CI / Pi Extension (Node) (push) Waiting to run
CI / Rust SDK (lean-ctx-client) (push) Waiting to run
CI / Embed SDK (lean-ctx-sdk) (push) Waiting to run
CI / Python SDK (leanctx) (push) Waiting to run
CI / Hermes Plugin (Python) (push) Waiting to run
CI / SDK Conformance Matrix (push) Waiting to run
CI / Coverage (push) Waiting to run
CI / cargo-deny (push) Waiting to run
CI / Adversarial Safety (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
JetBrains Plugin / Actionlint (push) Waiting to run
CI / Benchmarks (push) Waiting to run
CI / Output-Quality Gate (eval A/B) (push) Waiting to run
CI / Documentation (push) Waiting to run
CI / CI Green (push) Blocked by required conditions
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (rust) (push) Waiting to run
JetBrains Plugin / Validation (push) Waiting to run
JetBrains Plugin / Build (push) Waiting to run
JetBrains Plugin / Test (push) Blocked by required conditions
Security Check / Security Scan (push) Waiting to run
CI / Test (ubuntu-latest) (push) Has started running
CI / Clippy (push) Has started running
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

720 KiB
Raw Blame History

Changelog

All notable changes to lean-ctx are documented here. Format follows Keep a Changelog.

[3.9.8] — 2026-07-12

Added

  • Smart Read redirect for Cursor (auto mode). Native Read calls in Cursor are now transparently compressed via lean-ctx's auto mode — selecting the optimal compression (signatures, map, etc.) per file for 87-97% token savings. Windowed reads (offset/limit) remain verbatim (full-compact) to preserve line indexing. Validated by edit-probe PoC: StrReplace does NOT fire a Read PreToolUse, so the redirect is safe.
  • Replace mode hardening for all agents.
    • Claude Code / CodeBuddy: content-aware CLAUDE.md block updates; Replace mode block is now correctly installed even when block version matches.
    • Codex: mode-aware pretooluse handler — denies Bash in Replace mode, rewrites in Hybrid mode. Removed dead install_codex_deny_hook.
    • Pi: propagates replace mode to extension config + dedicated PI_AGENTS_REPLACE.md template.
    • Qoder: deny hooks for Read|Grep|Glob in Replace mode + replace rules.
    • Crush / Hermes: mode-aware rules installation via replace_rules_content().
    • CodeBuddy added to REPLACE_AGENTS.
  • lean-md addon integration (PR #721). External addon by @dasTholo — directive-driven Markdown for agent plans. Reverse-cut: renderer lives in dasTholo/lean-md, lean-ctx hosts only the thin surface (registry entry, LSP formatter routing, RenderTransform trait, .lmd.md raw read).
  • SessionStart nudge improvements. Explicit ctx_read cache statistics for shared-mode hosts (Cursor); fixed stale ctx_semantic_search reference.

Fixed

  • Cursor Read redirect regression (GH #1250 follow-up). install_cursor_deny_hook was re-adding the Read redirect that merge_cursor_hooks had removed, causing cli_full traffic with 0% compression (1.4M tokens wasted). Now correctly separated: Read → redirect (smart compression), Grep|Glob → deny.
  • Cursor Glob deny. Added Glob to the deny matcher alongside Grep — forces use of ctx_glob instead of native Glob.
  • Claude/CodeBuddy CLAUDE.md not updating in Replace mode. Block installer now checks content (not just version) to detect stale blocks.
  • lean-md registry integration: "mcp""none". Invalid enum value from PR #721 corrected; IntegrationKind::parse("mcp") silently returned None.
  • Codex integration test adjusted. agent_init_codex test updated from 3 to 2 PreToolUse entries after removing the separate deny hook.

Changed

  • redirect_read_args() now returns Vec<String> with dynamic mode selection instead of a fixed [&str; 4] array.
  • refresh_agent_hooks() is now fully mode-aware — determines the recommended HookMode per agent and installs the correct artifacts.
  • Removed PoC edit_probe handler (served its diagnostic purpose).

[3.9.7] — 2026-07-11

Added

  • Runtime tools/list_changed notifications (GH #1250). When the user changes tool_profile, tools_enabled, or disabled_tools via dashboard, CLI, or manual config edit, the MCP server now automatically sends a notifications/tools/list_changed to the IDE client on the next tool call. No more "restart IDE to pick up profile changes" — Cursor, Claude Code, and all MCP clients refresh their tool surface immediately. New module: server/tools_config_watch with hash-based change detection.
  • CLI profile-switch messaging updated. lean-ctx tools <profile> now prints "Changes take effect on the next tool call (auto-detected)" instead of "Restart your AI tool / IDE".

Fixed

  • lean-ctx index build memory explosion capped (GH #790). Six fixes that together prevent unbounded RAM growth during index builds:
    1. Memory Guardian activated for CLI buildsstart_guard() now runs before ensure_all_background(), so pressure/abort checks in graph and BM25 code actually fire (previously only started in daemon mode).
    2. graph_index_max_files default 0 → 15 000 — caps the graph scan; override with graph_index_max_files = 0 for unlimited.
    3. Graph content_cache capped at 256 MB — stops caching file contents once the budget is hit; the edge builder falls back to disk reads.
    4. Edge build batched (500 files/batch)par_iter(ALL) replaced with chunked parallel batches and memory-pressure checks between them.
    5. BM25 chunk content truncated to 10-line snippets during build — full text is tokenised for scoring, then the stored body is snipped immediately (no more holding every file's full content in the chunk vector).
    6. BM25 save streams through zstdpostcard::to_allocvec replaced with postcard::to_iozstd::Encoder → temp file; eliminates the intermediate uncompressed Vec<u8> allocation entirely.
    7. BM25 parallel path (prepare_chunk) now truncates — the parallel build path (add_prepared) previously bypassed the 10-line snippet truncation, holding full file bodies for all chunks simultaneously.
    8. Memory Guardian: immediate first RSS sample — closes the 3-second blind window where builders allocated freely with stale Normal pressure flags.
    9. CLI build evicts content_cache on Hard+ pressure — previously only called jemalloc_purge; now clears the 128 MB shared content cache.
    10. Graph + BM25 run sequentially under memory pressure — prevents peak allocations from compounding when the system is already low on RAM.
    11. Graph scan admission control — uses index_admission (same as BM25) before parallel fan-out; oversized corpora degrade to sequential.
    12. Graph scan batch size 2000 → 500 — matches BM25's MAX_BATCH_FILES; reduces per-batch peak from ~40 MB to ~10 MB of ScanFileResult content.
    13. Batch-0 pressure check — graph scan now checks abort_requested on the very first batch (previously batch 0 always ran unchecked).
    14. Tightened guardian pressure thresholds — Hard fires at 1.5× (was 2×), Critical at 2× (was 3×) of max_ram_percent. On a 64 GB machine at 10%: Hard = 9.6 GB (was 12.8 GB), Critical = 12.8 GB (was 19.2 GB).
    15. Edge parallel batches check is_under_pressure — previously only checked abort_requested (Hard+); Soft pressure now stops edge-building.
    16. build-full and build-semantic start the memory guardian — previously only build activated the guardian; full/semantic builds ran unprotected.
  • Cursor Read redirect removed — savings jump from 9.5 % to 73+ % (GH #1250). Cursor's StrReplace internally triggers a native Read that the redirect hook intercepted, producing verbatim cli_full output with ~0.5 % savings. This dominated the token stats (68 % of all input tokens!) and dragged the overall savings rate to single digits. The Read matcher is now removed from Cursor's preToolUse redirect hook — native Reads pass through unmodified (StrReplace works), and the agent uses ctx_read (MCP) for compressed reads, matching how Claude Code already works (read_redirect = auto). Grep redirect remains for compression.

[3.9.6] — 2026-07-10

Added

  • full-compact read mode. New ctx_read mode: headerless, trailing-whitespace-stripped verbatim content. Used by the Read redirect to produce temp files faithful to the original line structure while giving ~510 % compression without breaking the host's offset/limit windowed reads (fixes header-in-temp-file offset bug from the original full mode, #1021 follow-up).
  • Dashboard channel-breakdown API. New /api/stats field channel_breakdown classifies every recorded command into redirect (hook-intercepted native tools), rewrite (shell commands rewritten to lean-ctx), or mcp (direct ctx_* calls). Powers the Cockpit Proof/Trends chart showing which delivery channel contributes what savings.

Changed

  • Default memory_cleanup switched from aggressive to shared. Idle cache TTL rises from 5 minutes to 1 hour, matching real-world agent session durations (think pauses, context switches). Low-memory devices can opt back via memory_cleanup = "aggressive" or LEAN_CTX_MEMORY_CLEANUP=aggressive.
  • Default cache_max_tokens raised from 500k to 2M. Four times more headroom for the in-memory read cache — eliminates premature eviction in large codebases. Override via LEAN_CTX_CACHE_MAX_TOKENS.
  • Read redirect switched from full to full-compact mode. Redirect hook now produces headerless, whitespace-trimmed temp files. Fixes offset/limit correctness (#1021 follow-up) and saves ~510 % on every redirected native Read.
  • Grep redirect now host-aware (GH #398 follow-up). grep_content_mode() no longer requires an explicit output_mode=content parameter — when the mode is absent, it detects the host IDE and allows the redirect on Cursor (which defaults to content) while blocking it on Claude Code (which defaults to files_with_matches).
  • Rules enforcement strengthened across all IDEs (RULES_VERSION 8 → 9, CLAUDE.md v6 → v7). All 28 supported IDEs now receive MUST/NEVER/SELF-CORRECT language with quantitative evidence (~1 % hook redirect vs 1370 % direct ctx_* savings), replacing the previous "prefer" wording that let agents fall back to native tools silently.

Fixed

  • Compressed-output cache hits now properly counted. SessionCache::get_compressed() increments cache_hits and total_reads, so re-reads of auto/map/signatures outputs appear in CEP stats and dashboard.
  • read_dedup savings tracked in CEP stats. The PostToolUse read_dedup hook now calls stats::record("cli_read_dedup")
    • stats::flush(), surfacing dedup savings in the redirect channel on the dashboard.
  • Dashboard channel-breakdown empty state. cockpit-remaining.js now shows a helpful "No channel data yet" message instead of silently rendering nothing (which previously caused a SyntaxError on the Proof/Trends page).
  • cap_to_raw() inflation prevention tracked as metric. Events where framed output would exceed raw content are now counted in cache_telemetry and shown in ctx_cache status.
  • CLI ls tree-tracking fix. cmd_ls now passes the real original token count to cli_track_tree instead of 0, so tree-view savings appear correctly in CEP stats.
  • agent_wrapper: pwd - and unquoted eval arg (GH #745 follow-up). Two additional Claude Code sandbox variants now handled: (1) trailing pwd - (lone dash flag) not matched by has_trailing_bare_pwd; (2) unquoted eval arguments (eval pwd vs eval 'pwd').
  • Doctor text corrected: shared cleanup description updated from "30 min" to "1 hour" to match the new default TTL.

[3.9.5] — 2026-07-10

Fixed

  • Prompt-cache invalidation via additionalContext injection (GH #778). PostToolUse [CODE HEALTH] notices and PreToolUse shadow-mode nudges injected text into additionalContext on every qualifying event. On Anthropic models this retroactively mutates the cached prefix, causing 440520k tokens of cache re-bills per injection. Fix: default inject_context = false in [code_health] config — notices now route to ctx_knowledge and the ContextBus (dashboard). Opt-in via [code_health] inject_context = true or LEAN_CTX_INJECT_CONTEXT=1.
  • Marker contamination in source files — root-cause fix. The redirect-suffix (--- lean-ctx: ctx_compose ...) was appended directly to .lctx temp files. When agents copied temp-file content back into source, the marker leaked into .rs/.js/.sh files, breaking builds. Fix: the suffix is never written to file content; the nudge travels exclusively via additionalContext (gated by inject_context) or is suppressed entirely.
  • Release pipeline: rmcp crates.io compile bug. rmcp 2.2.0 on crates.io calls SseStream::from_bytes_stream but sse-stream 0.2.x only provides from_byte_stream. Restored [patch.crates-io] to upstream git rev 67a3085 which has the fix; cargo publish --no-verify bypasses broken verification.
  • Release pipeline: Homebrew SHA256 grep collision. The grep pattern x86_64-unknown-linux-gnu also matched the -cuda variant, producing multiple SHA256 values and breaking the GitHub Actions output format. Fixed with .tar.gz suffix anchoring.
  • agent_wrapper detection — pwd - and unquoted eval arg (GH #745 follow-up). Two additional Claude Code sandbox variants still hit the eval hard-block in v3.9.3: (1) trailing && pwd - (lone dash flag) was not matched by has_trailing_bare_pwd; (2) unquoted eval arguments (eval pwd vs eval 'pwd'). Fix: has_trailing_bare_pwd now accepts any pwd followed by flag-only tokens (no redirect operator), covering pwd, pwd -P, pwd -, and future variants.

[3.9.4] — 2026-07-09

Added

  • lean-ctx wrap <agent> — one-command setup (GH #premium-setup). Replaces the 5-step manual setup (install → PATH → onboard → shell reload → IDE restart) with a single command that orchestrates everything: config snapshot, shell hooks, MCP registration, agent hooks, daemon start, MCP connection probe, and a premium terminal summary. Undo with lean-ctx unwrap <agent>.
  • lean-ctx unwrap <agent> — byte-for-byte config restore. Reads the wrap snapshot and restores every modified file to its pre-wrap state, removes MCP registration, and cleans up the snapshot directory.
  • MCP verify probe. wrap spawns lean-ctx mcp, sends JSON-RPC initialize
    • tools/list, and confirms ctx_read is present — gives instant feedback that the MCP server works before the user opens their editor.
  • Agent launch detection. wrap checks whether Cursor/VS Code is already running and gives context-aware restart hints (process detection via pgrep on macOS/Linux, tasklist on Windows).
  • install.sh auto-PATH fix. The installer now adds ~/.local/bin to PATH automatically (appends to shell RC + exports in current session). Opt out with LEAN_CTX_NO_PATH_FIX=1.
  • install.sh auto-onboard. After binary installation, lean-ctx onboard runs automatically. Opt out with LEAN_CTX_NO_ONBOARD=1.
  • npm postinstall auto-onboard. npm install -g lean-ctx-bin now runs lean-ctx onboard after download (skipped in CI).

Changed

  • CLI help: wrap-first progressive disclosure. Quickstart, concise help, and full reference now lead with lean-ctx wrap <agent> as the primary getting-started path. onboard and setup remain available as alternatives.
  • README: 30-second setup. "Get started" section updated from 5 manual steps to lean-ctx wrap cursor.
  • Website: wrap-first flow. Landing page hero, getting-started prompt generator, and setup commands reference all updated to the wrap-first workflow.

[3.9.3] — 2026-07-08

Fixed

  • Bypass-hint gates — tool-drift prevention for all 34 editors (GH #748, #749, #750). Three gates silently suppressed the nudge that reminds models to use ctx_* tools: (1) bypass hints were gated behind minimal_overhead instead of their own bypass_hints config key — decoupled; (2) cold-start sessions (no ctx_* call yet) never triggered a hint because LAST_LCTX_CALL_TS was 0 — added SERVER_START_TS fallback; (3) Cursor's conversation_id UUID never matched lean-ctx's session_id, so filtered counts returned 0 — added unfiltered fallback. All three fixes ride MCP tool responses and reach every editor that connects via MCP.
  • ctx_read lock contention with parallel subagents — Two-Phase Read (GH #751). The slow path held the global SessionCache write-lock during disk I/O, compression, and graph-hint SQLite queries, serializing parallel subagents reading different files. Restructured: Phase 1 tries the [unchanged] stub under a shared read lock (~70% of calls complete here); Phase 2a reads the file under per-file lock without the cache lock; Phase 2b takes a brief write lock for handle_with_preread(). Graph hints (graph_related_hint()) are now computed after lock release. Documented in LOCK_ORDERING.md.
  • Dashboard "Evict" UX — missing await + no visual state (GH #744). _executeOverlay() now awaits loadData() so the table re-renders before the user can interact. Excluded rows render with line-through, reduced opacity, an "Excluded" badge, and a disabled evict button.
  • agent_wrapper detection — zsh sandbox variant (GH #745). unwrap_agent_wrapper() now detects the zsh sandbox shape (setopt NO_EXTENDED_GLOB + bare pwd) alongside the existing bash redirect path.
  • Gateway double-counts OpenRouter non-BYOK usage.cost (GH #746). absorb_openai() no longer sums usage.cost + upstream_inference_cost when they are identical (non-BYOK mirror).
  • Dashboard "Read Full" button risk warning persists (GH #747). risk.rs now checks SetView(Full) overlays when computing risk warnings, so files with an active "Read Full" override no longer show a stale compressed-read warning.

Added

  • Cursor SessionStart additionalContext reactivated (GH #752). Cursor fixed SessionStart additionalContext support circa Q1 2026; the prior exclusion (#1031) is removed. Shared-mode hosts now receive a short reinforcement nudge for exclusive tools (ctx_compose, ctx_semantic_search, ctx_callgraph, ctx_knowledge, ctx_session).
  • Redirect-suffix for drifting models (GH #753). When no ctx_* call has been seen in the last 5 minutes, Read-redirect .lctx temp files append a one-line separator: --- lean-ctx: ctx_compose bundles search+read+symbols in one call ---. Applies to Cursor, Claude Code (read_redirect=on), and Copilot CLI; not Windsurf.
  • HookCovered MUST_INVOKE wording (GH #753). Strengthened the HOOK_COVERED_HEADER to use CRITICAL/ALWAYS wording and "ACTUALLY EMIT the call — describing it is not calling it".

[3.9.2] — 2026-07-07

Added

  • Unified distribution, Phase 2 (GH #724/#726): self-service addon publishing + hosted installs. New lean-ctx addon publish --namespace <ns> wraps the authoring lean-ctx-addon.toml verbatim into a signed kind=addon context package and uploads it through the existing ctxpkg publish path — after local gates that mirror the hosted listing bar (schema, runnable [mcp] endpoint, audit verdict: blocking findings refuse, review publishes disclosed; --check runs everything without network I/O). lean-ctx addon add <ns>/<name>[@version] installs hosted packs: index-verified download, integrity hashes, mandatory ed25519 signature, kind ↔ payload coherence — then the embedded manifest walks the unchanged consent → preflight → health-probe pipeline; addon update re-resolves from the install source. The context registry refuses to import kind=addon packs (wrong trust chain, use addon add). The bundled registries are now generated snapshots: gen_registry validates, sorts and canonicalizes rust/data/{addon,grammar}_registry.json; CI + preflight fail on drift (deterministic, timestamp-free, #498).
  • Unified distribution, Phase 1 (GH #724/#725): managed addon binaries + the kind package taxonomy. .ctxpkg manifests gain an optional kind field (context | skills | addon | grammar; default context, omitted when serializing, so every existing package stays byte-identical — non-context kinds require schema v2). Addon manifests gain an [artifacts.<target-triple>] block: addon add downloads the prebuilt binary for the current platform into the managed bin dir (<data_dir>/addons/bin/<name>/<version>/, never PATH), verifies its SHA-256 before the atomic install, auto-pins that hash as the spawn-time binhash, and rewrites the gateway command to the absolute managed path — zero PATH interaction, tamper ⇒ spawn refused, addons.policy = locked blocks the fetch before any network I/O. New lean-ctx addon update <name> installs side-by-side, health-gates, then prunes; addon remove deletes the managed binaries; doctor verifies every receipt (exists + hash + not revoked). The grammar-dylib fetch (#690) now shares the same download→verify→install core (artifact_install), byte-identical behavior.
  • Universal cost coverage for every provider — LiteLLM catalog, gateway cost headers, operator price overrides (GL #1189). GL #1179 made OpenRouter turns exactly billed and OpenRouter-listed models live-priced; three gaps remained and are now closed: (1) second live catalog — the LiteLLM community price map (~2900 entries, no key) is fetched alongside the OpenRouter catalog in the same refresh/disk-cache/kill-switch cycle and merged gap-filling (OpenRouter wins on conflicts), covering azure/, bedrock/, vertex_ai/, groq/, mistral/, embedding models and niche hosts; either source failing is tolerated (fail-open, previous table kept); (2) measured cost from response headers — LiteLLM-style gateways report the billed USD per turn in x-litellm-response-cost, which the proxy now reads (plus an operator-defined header via [proxy] cost_response_header) and books as provider-measured cost; a body-reported figure (OpenRouter usage.cost) always beats the header, and junk header values never enter the ledger; (3) first-class negotiated prices[cost.prices."<model>"] in config.toml (input_per_m, output_per_m, optional cache rates) merges into the pricing table as exact entries, overriding embedded and live catalog rows for committed-use discounts, Azure PTU or zero-rated internal models; only a provider-measured bill ranks higher. Exact matching now runs against the full loaded table (custom override names price exactly), and the price-source ladder is uniform for every provider: measured bill > operator override > live catalogs > embedded list > family heuristic > blended fallback — each rung honestly labeled via cost_source.
  • Index-time include/exclude filters (GH #735). The retrieval corpus is now declared explicitly instead of abusing .gitignore for retrieval policy: new [index] config (exclude, include, respect_gitignore) plus per-run CLI overlays on every lean-ctx index build command — repeatable --exclude <glob> / --include <glob> (both --flag value and --flag=value forms) and --no-gitignore / --respect-gitignore. One shared filter layer (core::index_filter) drives the BM25 walk, the graph scan, the graph staleness check, and the watch snapshot; the semantic index chunks the BM25 corpus and inherits the same universe — excluded files produce no chunks, graph nodes, or embeddings. Globs match the root-relative path (forward slashes on every platform); exclude wins over include; a non-empty include list admits matching files only. CLI --exclude appends to config, --include replaces the config set for the run, and a run with overlay flags skips the daemon delegation so the one-off corpus can't be overwritten by a config-built index. index status (human + --json) reports the active filter summary; repo-local config extends global excludes and can only tighten gitignore handling. Empty filters preserve today's behavior byte-for-byte.
  • Personas now drive the whole pipeline (persona-spec-v1 runtime wiring, GL #1178). The five declared persona fields were spec + capability-reporting only; every one of them is now consumed at runtime: default_read_mode enters the ctx_read mode chain (explicit arg > policy pack > persona > profile/auto; "auto" = no opinion), compressor compacts ctx_url_read flowing-text modes through the extension registry (researchmarkdown, support/lead-genprose; extractive facts/quotes/links stay verbatim to protect citations), chunker makes token-budget truncation cut on chunk boundaries (paragraphs / line windows) instead of mid-sentence, intent_taxonomy lands as the contract-promised persona block (PERSONA: / INTENTS: / DEFAULTS:) in the MCP session instructions, and sensitivity_floor folds into [sensitivity] enforcement (Config::sensitivity_effective) — a floor above public enables enforcement out of the box and can only tighten an explicit config (LEAN_CTX_SENSITIVITY=off stays the kill switch). The coding default declares the historical defaults (auto/identity/public), so default installs remain byte-identical (#498 prompt-cache stability). Contract doc gains a "Runtime wiring" table (docs/contracts/persona-spec-v1.md).

Fixed

  • MCP stdio server processes leaked after client disconnect (GH #733). When the transport closed while an abandoned tool handler (#271 watchdog) still occupied a blocking thread, the implicit Tokio runtime drop blocked on that thread forever — the server process survived the disconnect invisibly. Clients that force-reconnect on tool timeout (e.g. the Pi extension's MCP bridge) spawn a fresh server per reconnect, so every abandoned handler leaked one ~26 MB lean-ctx process; on a 1.9 GB machine 50+ accumulated within a session and exhausted RAM + swap. The runtime now shuts down with a bounded 2 s grace period after the transport closes (telemetry is already flushed at that point); hung stragglers die with the process.
  • Gateway console showed ~15x the real OpenRouter bill for unknown models (GL #1179). An external gateway run (Claude Code → lean-ctx → OpenRouter, deepseek/deepseek-v4-flash) was billed $0.05 by OpenRouter while the console reported $0.74: the embedded price table had no V4 entry, so the heuristic matcher silently fell back to 2025-era deepseek-v3 list prices — and presented the estimate as COST. Cost accounting is now three-layered and honest: (1) measured provider cost — OpenRouter chat requests opt into usage: {include: true} (only when the effective upstream is openrouter.ai; api.openai.com never sees the non-standard field), and the response scanner absorbs usage.cost plus cost_details.upstream_inference_cost (BYOK) as the authoritative billed charge, which beats every table estimate in the meter, the policy gate and usage_events; (2) live prices for all models — a new core::gain::live_pricing module fetches the public OpenRouter models catalog (320 models) with a 24h disk cache, atomic swap, background refresh at proxy/gateway/dashboard startup, slug normalization (vendor prefixes, date suffixes, :free/:extended variants) and a LEAN_CTX_LIVE_PRICING=off kill switch, slotted between exact embedded matches and the heuristic in ModelPricing::quote; (3) cost provenance — every usage event now records cost_source (provider|live|list|heuristic|shadow), the admin console marks measured vs estimated spend (✓/ badges, KPI foot line, CSV columns measured_requests/estimated_requests), lean-ctx spend prints ✓/* markers per model, and /api/status + the health strip expose live-pricing freshness. Result: OpenRouter-billed turns show the exact invoice amount; everything else shows current list prices instead of stale hardcoded ones, and estimates are visibly labeled as such.
  • Cursor sessions ran with read compression silently disabled (GH #722). Cursor ≥ 3.7 exports CLAUDE_PROJECT_DIR to its hook child processes for Claude-compat — and lean-ctx's guard-host detection took that variable as proof the host is Claude Code, so read_redirect = auto switched the PreToolUse Read redirect off (the #637 protection) in every Cursor session: native Reads/Greps passed through uncompressed, with no warning (a 2-hour Cursor session showed 152 file reads, 0 redirected). Cursor has no read-before-write guard, so its own markers (CURSOR_VERSION, CURSOR_PROJECT_DIR, CURSOR_AGENT, …) now identify the host first and win over the compat variable; the PostToolUse read-dedup inherits the same corrected detection. Real Claude Code behavior is unchanged.
  • A stdin-reading command could wedge an agent's shell session forever (GH #723). The buffered lean-ctx -c path inherited the host's stdin, so a command that falls back to reading stdin — e.g. rg left without a path argument by an empty $(…) substitution — blocked on a pipe that never delivers EOF. Worse, the timeout kill only reaped the direct shell child: orphaned grandchildren kept the captured stdout pipe open, the reader threads never saw EOF, and the caller hung after the timeout had fired (observed: one orphaned rg kept a Cursor shell session dead for hours). In agent/pipe contexts (stdin not a TTY) the child now gets /dev/null stdin (immediate EOF) and runs in its own process group, which the timeout kill signals as a whole — grandchildren die, pipes close, the session stays alive. Interactive TTY usage (prompts, Ctrl+C routing) is unchanged.
  • Hook wrapper scripts killed every session on synced multi-machine setups (GH #719). Each session heal rewrote the ~/.claude/hooks/lean-ctx-* wrappers with the machine-absolute path of the local binary. On a peer machine sharing that home directory (Dropbox/Syncthing, different username), every hook then exec'd a non-existent path and each session died silently. Wrappers now keep their portable $HOME-based form: a heal no longer overwrites a wrapper whose binary resolves on this machine, the LEAN_CTX_HOOK_BINARY override is emitted verbatim, and all binary references are shell-quoted (installs under paths with spaces work). The self-rewrite guard accepts both quoted and legacy unquoted forms, and lean-ctx doctor now flags wrapper scripts whose binary no longer resolves. Thanks @tr3lane for the precise follow-up to #708.
  • ledger evict always reported "Evicted 0/1" (GH #715). Eviction matched targets by exact string equality, but the ledger stores absolute canonical paths while users (and lean-ctx's own eviction hints) pass project-relative paths or basenames — so nothing ever matched. Targets now resolve in three stages: exact → relative to the project root → unique suffix match, with ambiguous suffixes reported alongside their candidates instead of silently doing nothing. The same resolver backs remove and set_state, eviction hints print paths that actually resolve, and stored entries are lexically normalized on load (migrating old backslash entries). The dashboard's "Evict" button used to only add an exclude overlay — pressure never dropped; it now performs a real ledger eviction and applies the overlay to the resolved canonical path. normalize_dashboard_demo_path emits forward slashes on Windows. Thanks @ITFinesse for the report.
  • secret_detection redacted harmless identifiers (GH #718). Keyword alternations had no word boundaries, so camelCase identifiers (superuserPassword = "postgres", getStripeSecretKey(), GITHUB_FEEDBACK_TOKEN) and the "Generic long secret" rule (any 32+ char value, e.g. confirmRequiredEndpointKeySchema) triggered [REDACTED] in compressed reads of ordinary source code. Detection keywords now require word boundaries, right-hand sides that are code identifiers or property access (no quotes, no digits) are exempt, and placeholder values (change_me, <your-key>, xxx…, dummy) are skipped. A new subtractive secret_detection.exclude_patterns config lets teams whitelist project-specific false positives — applied in both redaction layers (compressed output and audit/tee). Real credentials (sk-ant-…, AWS keys, quoted high-entropy strings) redact exactly as before, with regression tests covering every reported repro. Thanks @jackkeller for the exceptionally precise report.
  • Dashboard showed "idle" while a session was actively working (GH #717). Three stacked causes: /api/workspaces deduplicated workspaces by exact string match, so Windows path variants of the same root (C:\proj, C:/proj, c:/proj) rendered duplicate cards where the stale twin sat on "idle"; unlike /api/session it never merged the proxy's live stats.last_use freshness; and sessions only flushed to disk every 5 changes, so slow-ticking sessions stayed invisible for the whole batch window. Workspaces now group under a lexical canonical key (freshest timestamp wins, counters take the max), the freshest workspace absorbs stats.last_use, and unsaved changes flush after 60 s (the first change of a fresh session immediately). /api/agents uses the same 10-minute activity threshold as the workspace panel, which now also shows tool calls per workspace. Thanks @ITFinesse for the report.

[3.9.1] — 2026-07-05

Fixed

  • gateway keys add corrupted the key file scaffolded by gateway init (GH #716). init writes the canonical empty set keys = []; keys add appended a [[keys]] table to that body, leaving BOTH representations in the file — invalid TOML (duplicate key), so the documented onboarding flow (initkeys add) failed on first use, and a full revoke re-armed the same trap. add now strips the empty-array form before appending, and all key-file writers (add/rotate/revoke) validate the assembled body before the atomic swap — a bad assembly can never replace a good file on disk. Regression tests cover init → add → revoke-to-empty → add and verify a poisoned file is refused byte-for-byte untouched.

[3.9.0] — 2026-07-05

Changed

  • The shell hook is now transparent in plain human terminals: default activation is agents-only (GH #699). With the old always default the hook aliased git/docker/kubectl in every interactive shell — so a human in a plain terminal (no agent anywhere) saw lean-ctx allowlist diagnostics for their own commands. lean-ctx exists to save agent tokens; the aliases now auto-activate only when an agent session is detected (LEAN_CTX_AGENT, CURSOR_AGENT — newly recognized across every guard — CLAUDECODE, CODEBUDDY, CODEX_CLI_SESSION, GEMINI_SESSION). Set shell_activation = "always" (or LEAN_CTX_SHELL_ACTIVATION=always) to keep the old behavior, e.g. to feed your own shell usage into lean-ctx wrapped; lean-ctx-on still opts a single session in manually. The "[CLI] Command would be blocked in MCP mode" allowlist diagnostic is also downgraded to debug level for interactive TTY callers — it's agent telemetry, not human feedback. Thanks @DerPate for the precise report.

Added

  • /v1/compress is wire-compatible with LiteLLM's prompt-compression guardrail (GH #700). LiteLLM ≥ v1.92 can call a compression sidecar during pre_call (guardrail: headroom); the response now carries the tokens_before / tokens_after / compression_ratio telemetry fields that guardrail logs, alongside the existing richer stats block. Point the guardrail's api_base at the lean-ctx daemon and every request through a LiteLLM gateway is compressed deterministically (prompt-cache-safe, #498) — no client change, including Claude Code via ANTHROPIC_BASE_URL. Cookbook: docs/guides/compress-sdk.md.
  • Provider-verified savings receipts (GH #701, opt-in proxy.counterfactual_metering). Wire savings were estimated (bytes/4 or local tokenizer). With metering on, every request the proxy rewrites also fires Anthropic's free count_tokens endpoint with the original, uncompressed body — concurrently with the real forward, spawned detached so it can never delay, mutate or fail the request — and pairs the provider-counted "would have billed N" with the same response's actually billed usage. Same request, same moment: no traffic-mix confound (methodology adopted from pxpipe's counterfactual metering). /status gains a verified_savings block and lean-ctx proxy status a Verified: line beside the estimate; per-model pairs persist across restarts in proxy_usage.json (pre-#701 files load unchanged). Net-negative results are reported signed, never clamped. Anthropic only (no free counting endpoint elsewhere); probe failures silently degrade the row to the estimate.
  • CCR round-trips through LiteLLM's agentic loop (GH #702). A lossy /v1/compress rewrite now advertises its retrieval hash in the guardrail's regex-locked hash=<24-hex> form, and the new GET /v1/retrieve/{hash} endpoint resolves it from the content-addressed tee store ({"original_content": …}). LiteLLM (BerriAI/litellm#31681) injects its retrieve tool on seeing the marker, validates the hash per call id, and replays the model with the verbatim original — compression behind a LiteLLM gateway is reversible end-to-end, with zero lean-ctx-specific client code. The marker shape is pinned by a contract test so drift fails CI; the hash is a pure function of the content, so stubs stay byte-stable (#498). The existing local handles (<lc_expand:…>, tee paths, /v1/references/{id}) are unchanged.
  • Persistent per-extension grammar telemetry (GH #690 Phase 2 groundwork). The tiering cut needs to know which of the ~27 static tree-sitter grammars actually earn their binary bytes, but the only signal was a pair of process-lifetime counters with no language dimension (flagged by @getappz). core/grammar_usage now records tree-sitter vs regex-fallback hits per file extension, persisted across sessions in grammar_usage.json (aggregate counters only — no paths or project data). ctx_metrics shows the all-time top extensions in its SIGNATURE BACKEND section.

Fixed

  • Multi-window MCP starts can no longer trip the crash-loop backoff (GH #694 follow-up — thanks @ITFinesse). The crash-loop guard counts server starts in a 60s window, but a healthy burst — N editor windows each spawning a server, plus the client's own retries while a slow host initializes — could cross the threshold with zero crashes. The resulting pre-handshake backoff sleep (up to 30s) then caused the very "Waiting for server to respond to initialize request" timeouts it exists to prevent, wedging the second window. A completed MCP handshake now clears the start history (a handshake proves binary + config are healthy; true crash loops die before it), so only genuinely crashing servers back off.
  • VS Code Insiders is now a first-class MCP target (GH #694 follow-up — thanks @ITFinesse). Insiders keeps a fully separate profile dir (Code - Insiders/User), so registering lean-ctx in stable's Code/User/mcp.json left Insiders with an empty MCP: Open User Configuration — exactly the "server missing in one window" confusion from the multi-window report. setup/init now detect and write the dedicated Insiders config on all platforms (agent key vscode-insiders), doctor lists it as its own MCP location, and uninstall cleans it up.
  • Grammar-addon dylibs refuse to load from world-writable dirs/files (GH #690 review point 3, PR #697 — thanks @getappz). A group/other- writable grammar dir would let any local account swap the dylib between hash check and dlopen; the loader now rejects that layout outright.
  • ctx_read gains repo param parity in multi-repo mode (GH #696, PR #698 — thanks @getappz). ctx_search/ctx_glob/ctx_tree could already target a registered root via repo=<alias>, but ctx_read could not — you could find a file in another root yet not read it. Read-only by design (ctx_edit/ctx_patch stay session-rooted until undo history is multi-repo-aware); unknown aliases error with the list of known ones, and jail + secret screening apply against the resolved repo root.
  • A corrupt stats.json is quarantined, never silently reset (GH #706 — thanks @getappz). A crash mid-write (or disk-full) could leave truncated JSON; the loader's unwrap_or_default() then wiped months of savings history without a trace on the next write. Unparseable stats now move to stats.json.corrupt (one warning log; the file is evidence and stays recoverable by hand), and doctor reports the quarantine with recovery guidance instead of everyone silently starting from zero.
  • Relative paths follow a mid-session worktree switch (GH #707 — thanks @getappz). project_root is captured once at MCP initialize; when the client later enters a git worktree (Claude Code EnterWorktree nests a full checkout under .claude/worktrees/<n>/), every relative path kept resolving into the stale root — silently, because the same layout exists in both trees. Resolution now walks both shell_cwd and project_root up to their nearest .git entry (dir or worktree file); when the boundaries differ, the live shell_cwd wins. A plain cd rust/ inside the same checkout shares the boundary and is untouched, and a shell_cwd with no git upward gives no signal — so the monorepo behavior stays exactly as before.
  • ctx_read raw mode no longer swallows markdown table delimiters (GH #709 — thanks @getappz). The output sanitizer's symbol-flood guard (meant for degenerate model output like @@@@@@…) also matched legitimate document structure — |----|----| delimiter rows, ====/---- setext underlines and HR lines vanished from raw reads, breaking the mode's byte-fidelity contract. Structural characters no longer count toward the flood check, and a removed flood line no longer eats the file's trailing newline.
  • ctx_shell's explicit cwd param now updates the live shell cwd (GH #707 follow-up). The worktree-divergence detection reads session.shell_cwd, but that field only tracked cd commands inside command text — clients that switch checkouts pass the new directory as the cwd argument of every call, so the switch was invisible to path resolution. A jail-accepted explicit cwd is now persisted, verified end-to-end over a real MCP session (read resolves into the worktree copy after ctx_shell cwd=<worktree>).
  • lean-ctx stop/dev-install no longer SIGTERM their own process tree (GH #714). Run under the lean-ctx shell wrapper (lean-ctx -c … → sh → lean-ctx dev-install), the process sweep matched the wrapper parent and killed the pipeline mid-install (exit 143) — after the binary swap but before autostart was re-enabled. The sweep now excludes the full ps ppid ancestor chain and every member of its own foreground process group — agent harnesses (Cursor's shell) reparent intermediaries to PID 1 mid-run, which broke the ancestor walk alone; the group covers the wrapper regardless of reparenting. Verified: dev-install under the Cursor agent shell now completes end-to-end, including autostart re-enable.
  • Unknown MCP tool names now suggest the nearest registered tool (GH #712 — thanks @getappz). ctx_serach returned a bare "Unknown tool" while the CLI has long offered "did you mean" for typos; the Levenshtein suggester is now shared (core::levenshtein) and the MCP dispatch error appends "— did you mean 'ctx_search'?" within a length-scaled edit budget, so agents self-correct in one turn instead of falling back to native tools.

Added

  • Portable hook binary for synced agent configs (GH #708, hook_binary / LEAN_CTX_HOOK_BINARY). Generated hook commands bake the machine-absolute binary path (#367: agent hosts run hooks without your PATH). If you sync ~/.claude/settings.json between machines with different usernames, that absolute path is wrong on every other machine — and re-running init/doctor --fix there rewrites the file, ping-ponging your sync forever. Setting hook_binary = "$HOME/.local/bin/lean-ctx" (config) or LEAN_CTX_HOOK_BINARY (env) emits that expression verbatim into every shell-executed hook command — the hook host's shell expands it at run time — and doctor accepts it as current, ending the rewrite cycle. MCP server registrations and launchd/systemd autostart units keep the real absolute path: nothing expands variables there.
  • The AI Gateway (team mode). The engine can now run as a shared org gateway — one deployment your whole team points its IDEs at, with per-person attribution, governance and audited savings. Compiled into the default binary (gateway-server feature), local-free invariant intact: nothing changes for solo use until you run it.
    • lean-ctx gateway serve — multi-provider reverse proxy (Anthropic / OpenAI / Gemini / Ollama / custom registry) with per-person bearer keys, usage metering to Postgres (usage_events), wire-shape translation (an Anthropic-speaking IDE can call an OpenAI-hosted model and vice versa) and a token-protected admin console on a separate port.
    • lean-ctx gateway init — plug-and-play scaffold: docker compose, .env, key file and a step-by-step README in one command; gateway doctor preflights config, secrets, DB and ports.
    • lean-ctx gateway keys add|list|rotate|revoke — key lifecycle without storing plaintext (SHA-256 hashes only, shown once). rotate (GL enterprise#67) replaces every key of a person in one atomic file swap — no window where the person has zero valid keys — and keeps team/project attribution.
    • GET /v1/models (GL enterprise#63) — the curated org model catalog from [proxy.routing.aliases], content-negotiated: OpenAI-shape and Anthropic-shape clients each get their native list format. IDEs discover org names like zuehlke/fast; the gateway resolves the alias, injects upstream credentials and stamps routed_from into the ledger.
    • /me personal usage view (GL enterprise#64/#65) — each person signs in with their own gateway key and sees exactly their spend, savings, trend, models and projects — never anyone else's. Dark/light, 24h90d windows, savings-share KPI.
    • Signed org-policy gates (GL enterprise#25/#66) — under a signed, pinned, enforced = true org policy the forward path refuses: models outside the [routing].allowed_models ceiling (403), spend above [budgets] caps per person/UTC-day or project/UTC-month (429), and — new — requests beyond [budgets].max_requests_per_minute_per_person (429 with an honest Retry-After of the seconds until the minute rolls). Errors arrive in the caller's wire shape; refusals are counted on leanctx_policy_blocked_total{reason="model_ceiling"|"budget"|"rate_limit"}. Without an enforced org policy every gate is a no-op.
    • Evidence & GDPR (GL enterprise#36/#39) — usage retention windows, Ed25519-signed evidence exports (gateway evidence / evidence verify), person-scoped gateway gdpr export|delete, and Blake3 pseudonymization for person identifiers at rest.
  • Multi-window visibility (GH #694). lean-ctx doctor no longer claims "no active session" when sessions exist for other workspaces: run from a directory that isn't an open project root it now reports none for this directory — recent: frontend (4m ago), backend (1h ago), naming every workspace with a live session. The dashboard overview gains a "Connected workspaces" panel (new /api/workspaces endpoint) listing each project with status (active <10 min, idle <24 h, stale), last activity, tokens saved and current task — shown as soon as two or more workspaces have sessions.

Added

  • Grammar addons: long-tail tree-sitter grammars as signed runtime dylibs (GH #690 Phase 1, PR #695 — thanks @getappz). Structural understanding no longer has to be compiled in: an extension not covered by the 27 built-in grammars can now resolve through a SHA-256-pinned, per-platform grammar dylib that is dlopen'd at runtime — manifest + curated registry (data/grammar_registry.json, user-overridable under the same signed- override policy as the addon registry), a loader that verifies the hash pin on every load plus the tree-sitter ABI version before handing the grammar to the parser, a five-platform CI build matrix, and a zero-config fetch on first use. Fully offline-safe: no addon installed (or no network, or addons.policy = locked, or the new addons.grammar_auto_fetch = false for strict-egress orgs) degrades to the regex-signature fallback exactly as before. Installed dylibs land read-only and ad-hoc-signed on macOS; every fetch is logged with its source URL. The registry ships empty — which of the 27 static grammars (if any) move to the addon tier is a separate, telemetry-gated Phase 2 decision.

Changed

  • The heredoc-to-interpreter refusal now hands the agent the recovery path (GL #1161). Policy review outcome: the block stays — inline code embedded in the command string never exists as an inspectable artifact, while a script file passes the write path's own guards and leaves an audit trail. But the old message ("Use a script file instead") left agents rediscovering the workaround by trial and error; the refusal now spells it out: write the code to a file (Write/ctx_edit), then python3 /tmp/snippet.

Fixed

  • A transient roots/list failure no longer disables project-root detection for the whole MCP session (GH #694). The first tool call resolves client roots exactly once; when that single attempt failed (e.g. the IDE window was still starting up — the VS Code second-window pattern), the server never asked again and fell back to cwd guessing for the session's lifetime. Failed attempts now re-arm resolution for up to 3 tries; a -32601 Method not found (client declares the capability but doesn't implement it — Cursor) still gives up immediately, and roots/list_changed restores the retry budget.
  • dev-install on Windows no longer hard-fails with ACCESS_DENIED while an IDE holds the old binary open (GH #691). The final swap did a bare replace-rename, which Windows refuses for as long as any process runs the old image — and dev-install deliberately never kills the IDE-owned MCP server (#1036), so no retry budget could ever succeed (measured: identical failure after 60 s). The install now uses the rustup-style sidecar swap: the running binary is renamed aside to lean-ctx.old.exe (allowed for mapped images), the fresh binary lands at the real path, and the sidecar is reclaimed on the next install once its holder exited. If even the rename-aside is blocked (AV/EDR-style zero-sharing lock), the error now explains the cause and the fix instead of a bare OS error code. Thanks @getappz for the measurement work in #691/#692.
  • ctx_share handovers with org agent ids (team:alice) are now pullable on Windows. The share filename embedded the agent id verbatim; NTFS interprets : as an Alternate Data Stream, so the write "succeeded" but the file never appeared in the store — the receiving agent saw "No shared contexts for you". Filenames now use a filesystem-safe slug ([A-Za-z0-9._-], everything else -); the true agent id still lives inside the JSON payload.
  • Background knowledge writers can no longer clobber facts a parallel remember just committed (lost-update, #326 class). The consolidation pipeline (apply_artifacts_to_stores) and the gateway memory adapter (addon_memory ingest) both did load → modify → blind save() from a background thread; a fact committed between their load and save was silently dropped — surfacing as flaky "no current fact exists" errors on ctx_knowledge relate right after a successful remember. Both writers now go through ProjectKnowledge::mutate_locked like every other writer.
  • CI: three timing/environment flakes hardened. The session_lock_timeout prompt-timeout bounds (400 ms) fired falsely on loaded Windows runners — the assertion only distinguishes "timed out" from "hung", so the bound is now 5 s; the lock-ordering check now skips #[cfg(test)]-gated statics (test-only locks need no production lock-ordering documentation); the two production gateway locks from enterprise#25 (SNAPSHOT, LEDGER) are documented in LOCK_ORDERING.md (L58/L59).
  • max_ram_percent is now actually enforced under Cursor/MCP load — no more 75 GB OOM-kill-respawn cycles (GH #685). Two compounding gaps, both closed: Uncontrolled build growth: the parallel BM25/graph index builds fanned the whole corpus across the rayon pool in one shot — on a 1M+-file multi-root setup the transient build state outran the 3 s memory guardian straight into the kernel OOM killer. Builds now run in 2000-file batches with a guardian check between batches (order-preserving, so indexes stay byte-identical — equivalence-tested), a new admission gate (index_admission) degrades corpora whose estimated peak exceeds the RSS headroom to the sequential build up front, and extra workspace roots are indexed one at a time on a single supervisor thread instead of up to 8 concurrent graph+BM25 pairs. Eviction blind spots: the eviction orchestrator reasoned over session-cache token utilization, which cannot see the HNSW/ANN graph, the resident trigram search indexes or the materialized graph indexes — under Hard/Critical RSS pressure it could conclude "nothing to do" while those structures dominated RSS. RSS pressure now enforces a floor action (Hard ⇒ unload indices, Critical ⇒ emergency drop), and UnloadIndices/EmergencyDrop additionally clear the ANN cache (new ann_cache::clear() + memory_usage_bytes()), the resident search indexes (search_index::clear_resident()) and the graph cache. All evicted structures rebuild transparently on next use.
  • sed/awk file dumps are verbatim output — no more dictionary-mangled source (GH #688). A range-print like sed -n '10,50p' file.ps1 fell into the generic terse pipeline, whose dictionary layer word-substitutes code identifiers with no code-awareness (functionfn, returnret, bare else lines dropped) — corrupting code read via sed/awk instead of cat. sed/awk/gawk/mawk/nawk now classify as file viewers like cat/head/tail. In-place edits are excluded via a token-based flag check (-i, -i.bak, -ni clusters, --in-place[=suffix], gawk -i inplace) — deliberately NOT a substring match, so filenames like my-input.txt or data-import.csv can't silently re-enter the terse pipeline. Byte-exact regression test with the original PowerShell repro. Thanks @getappz for the report and the PR the fix is based on (#689).
  • setup no longer panics when a client's MCP-instructions cap lands inside a multi-byte character (GH #680). The Claude Code / CodeBuddy 2048-char truncation used a raw byte slice; when the cut fell inside an em-dash the whole setup crashed ("end byte index 2048 is not a char boundary", live-reported at setup level 3, step 3/13). The cut now backs up to the previous char boundary (truncate_instructions, unit-tested with the exact crash shape).
  • doctor no longer false-flags a working OpenCode install (GH #686). Two gaps: has_lean_ctx_mcp_entry only walked mcp.servers.lean-ctx, but OpenCode's schema (opencode.ai/config.json) nests servers DIRECTLY under mcp — the direct-child form is now recognized too; and OpenCode was absent from the SKILL.md candidate list (checked: ~/.config/opencode/skills/ lean-ctx/SKILL.md) — it is now both checked by doctor AND installed by install_all_skills when OpenCode is detected, so check and installer can't drift apart.
  • Anchored line-1 edits of UTF-8-BOM files no longer conflict forever (GH #683 follow-up). With ctx_read stripping the BOM (output honesty #683), the anchor hash the model holds for line 1 is over the BOM-less text — but ctx_patch validated anchors against the raw preimage, so the hashes could never match and every retry conflicted again. The edit side now validates against the same BOM-less view and re-prepends the BOM on write (the BOM is an encoding artifact of the file, not of the edit).
  • Shell allowlist no longer splits commands at backslash-escaped operators (GL #1160). In restricted (allowlisted) mode, rg -n split\.label\|foo src/ was split at the escaped pipe, so the pattern fragment after it was validated — and blocked — as an unknown command (field report: rg dying with "not in the allowlist" on regex tokens, exit 126). The operator scanner, the subshell-paren walker and the substitution detector now honour bash backslash semantics outside single quotes: \|, \;, \&, \(, \) and \$( are data, never operators. Real (unescaped) pipes still split and every segment is still validated — over-blocking removed, deny-by-default unchanged. Also drops a dead pipe-index scanner from check_pipe_to_bare_interpreter.
  • Marked-block surgery no longer eats user content when a marker is quoted in prose (GL #1158). marked_block (and the Claude/CodeBuddy remove_block twin) located <!-- lean-ctx --> markers via substring search, so a documentation sentence like (see the `<!-- lean-ctx -->` block below) anchored the block replacement at the prose mention and silently deleted everything down to the real end marker — live-reproduced on this repo's own AGENTS.md, where a session-start heal wiped ~75 lines (Development Workflow, Session Continuity, Provider Pipeline, Quality Bar). Markers now match only as whole (trimmed) lines — the exact shape every writer emits — and the end marker is searched strictly after the start line, so stray end markers above the block can't create bogus spans. All upsert/replace/remove trigger checks (hooks/mod.rs, hooks/support.rs, rules_dedup) use the same line-based predicate; prose mentions are now invisible to the block machinery. Regression tests cover the exact live-repro shape.

Added

  • Anchored editing end-to-end — ctx_patch becomes the first-class edit path (#1008, "Edit Loop v1"). The anchored editor now closes the loop the rules already routed: read with ctx_read(mode="anchored") (or tag hits via ctx_search(anchored=true)), then patch by line + hash anchor — the agent never reproduces old text byte-for-byte, saving output tokens (~5x input cost) on every edit.
    • Advertised where it earns its tokens: ctx_patch joins the lazy core and the standard profile (now 16 tools). Client-aware quirks keep the default surface lean — clients with a reliable native editor (Cursor, Zed, Windsurf, Antigravity, OpenCode) skip it and pay zero extra schema tokens; Claude Code, CodeBuddy, pi/SDK and headless clients get it. Pinned profiles are client-agnostic and always include it.
    • Schema diet: the advertised ctx_patch schema shrank ~625 → ~263 tokens; rarely-used params (expected_md5, backup, validate_syntax, evidence) stay supported but are no longer advertised.
    • op=create: ctx_patch can create new files (strictly new — existing files are refused; not mixable with anchored ops in one batch), so MCP-only harnesses get the complete edit story from one tool.
    • Guidance coherence: Claude/CodeBuddy pointer blocks (v5/v3, keeping the MCP-aware guard semantics of v4/v2), agent templates, skills and per-editor guides now teach anchored-editing-first; ctx_edit (str_replace) is documented as the legacy power-profile fallback. New troubleshooting FAQ: "Where did ctx_edit go?".
    • Edit-efficiency metering (honest, #361-style): a separate metric channel measures the anchored-editing claim per applied op — tokens(replaced span) tokens(anchor args), i.e. output the model did not re-emit — plus stale-anchor CONFLICT retries, against the str_replace baseline (old_string tokens paid, old_string misses). Never estimated, never folded into the read-gain ledger, never printed in tool bodies (#498). Surfaced in ctx_metrics, /api/stats → edit_efficiency and a dashboard ROI "Edit Efficiency" card (~/.lean-ctx/edit_metering.json). Contract: docs/contracts/edit-metering-v1.md.
    • A/B benchmark, reliability + cost: the hermetic edit_reliability suite fixes identical mechanical bugs across 5 languages with both tools — anchored 10/10 vs minimal str_replace 5/10 (recovering to 10/10 only by paying extra recalled context), and ~41% fewer argument output tokens on identical successful fixes (tiny-span exceptions reported honestly).
  • Hook-aware Cursor guidance — the honest profile (GL #1153#1157). On hosts whose installed lean-ctx hooks already compress the native tools (Cursor: PreToolUse rewrite covers Shell, redirect covers Read/Grep), the injected ~/.cursor/rules/lean-ctx.mdc now carries a new HookCovered profile instead of the full mapping: it states that native Shell/Read/Grep are compressed transparently (using them is fine) and advertises only the capabilities with no native equivalent (ctx_compose, ctx_symbol/ctx_callgraph, ctx_semantic_search, ctx_knowledge/ctx_session, ctx_expand). Rationale: Cursor's harness makes native tools first-class, so a "NEVER use native" rule there is unenforceable and only produces instruction dissonance — the model follows neither rulebook consistently. The MCP initialize anchor for covered Cursor sessions is reworded the same way. Detection is conservative (both PreToolUse entries must be present; invalid/missing hooks.json falls back to the full Dedicated mapping), the byte-exact drift check re-syncs the profile when hooks are installed or removed later, and the Cursor hook installer now honours shadow_mode/compression_level instead of hardcoding them (GL #1156). ~55% smaller Cursor rules payload on hook-covered installs, billed every session.
  • Guard-safe re-read dedup for Claude Code / CodeBuddy (GL #1140, follow-up to #637). read_redirect = auto keeps the read-before-write guard intact by letting native Read run on the real path — which also forfeited the Read dedup savings on those hosts. A new PostToolUse hook (lean-ctx hook read-dedup, matcher Read only) wins them back without touching the guard: the result of a re-read of an unchanged, already-read file is replaced with a compact [unchanged] stub via the documented updatedToolOutput channel. First reads stay byte-identical (edit safety: old_string always comes from real content), the incoming response shape is mirrored with only the content field swapped (unknown shapes pass through), every failure path fails open, replacement happens only when strictly smaller, a host compaction (PreCompact) purges the session's records so post-compaction re-reads deliver full content again, and Cursor's double-fired hooks are recognised by tool_use_id so a duplicate first read is never mistaken for a re-read. Config read_dedup = auto | on | off (env LEAN_CTX_READ_DEDUP); auto (default) activates only on guard hosts, where the PreToolUse redirect is off. Verified end-to-end against headless claude -p 2.1.139: first read byte-identical, second read served as the ~40-token stub, native Edit of the same file still passes the read-before-write gate.
  • Hybrid multi-repo search (Context Hub, GL#1133). ctx_multi_repo action=search now runs the full hybrid stack per root — BM25 + dense embeddings + SPLADE boost + graph ranks, the same pipeline as single-root semantic search — and fuses the per-root rankings with RRF (identical key and score semantics as before, so fusion behavior is unchanged; only the per-root signal got stronger). A root with a cold dense index degrades to its BM25 ranking with a warning instead of failing or inline-embedding under the query (#512 semantics). mode="bm25" forces the legacy lexical-only path, byte-identical to the previous output.

Changed

  • Benchmark numbers refreshed & self-footprint made a headline metric (#659). BENCHMARKS.md regenerated with v3.8.18 (map 98.1% / signatures 96.7% on the 50-file corpus; cold start 2.69s → 0.67s). The README benchmarks section now also states lean-ctx's own fixed per-session cost (~2.1K tok, CI-gated via doctor overhead --gate) and links the deterministic dual-arm self-verify (digest f5ed145e61ce3689) with its methodology. The CGB self-assessment (C2 — Managed) is surfaced from the README security section and Journey 13.

Fixed

  • dev-install honours redirected cargo target dirs (GH #671). Both rust/dev-install.sh and the lean-ctx dev-install command located the built binary at a hardcoded target/release/…; with CARGO_TARGET_DIR or a ~/.cargo/config.toml [build] target-dir override (one shared build cache across worktrees) they silently symlinked/installed a stale or missing binary. The target dir is now resolved via cargo metadata (env, config files and workspace settings all honoured) with a ./target fallback, the shell script fails loudly when the binary is absent instead of planting a dead symlink on PATH, the Rust path gained the same resolution plus the Windows .exe suffix, and tests/pre_release_check.sh follows suit. Follow-up: install.sh's source-build path (served at leanctx.com/install.sh) had the same hardcode and could link a stale binary from an earlier default-layout build — it now resolves via cargo metadata identically and names the override in its error hint. Thanks @getappz for the report and the initial fix (#672)!
  • pi-lean-ctx ships with zero runtime npm dependencies (GH #670). pi installs every package into one shared npm prefix and re-reifies the whole tree on each pi install/pi remove; an interrupted rewrite (Windows AV/file locks) stranded zod/v3/locales/en.js and the extension failed to load — unrepairable by reinstalling, because npm never re-extracts a package whose version matches. The MCP SDK (incl. zod) is now vendored as one self-contained bundle (extensions/vendor/mcp-sdk.cjs, built at prepack), so no corruptible dependency tree exists in the first place. Verified by an isolation smoke: bundle in an empty dir, real initialize + tools/list roundtrip, plus a jiti-loaded co-install with pi-markdown-preview.
  • MCP server answers initialize before doing housekeeping (GH #669). Orphan-process sweep (one ps per running lean-ctx), proxy autostart (TCP probe + detached spawn) and the throttled savings-recap publish ran in front of the stdio transport bind — on a cold WSL2 / VS Code Server start this widened the window in which VS Code's start-on-demand first tool call races server readiness and dies with Cannot read properties of undefined (reading 'invoke') (upstream: microsoft/vscode#321150). That work is now deferred onto the blocking pool, concurrent with the handshake; a time_to_initialize_ms log line makes the span measurable, lean-ctx doctor surfaces the upstream race on WSL2 + VS Code setups, and a regression test drives the exact race pattern (tools/call immediately after the initialized notification) against the real binary.
  • Zero-config golden path: onboard --yes now leaves doctor fully green. Three healers that silently disagreed are aligned: the session-start heal installs the agent SKILL.md files alongside rules (previously doctor warned "run: lean-ctx setup" forever), doctor's shell-hook probe honours a relocated LEAN_CTX_CONFIG_DIR (no more false "pipe guard missing"), and setup/onboard detect Claude Code / CodeBuddy by their state dir (~/.claude/, ~/.codebuddy/) exactly like doctor and the rules injector do — killing the dead loop where doctor pointed at setup but setup skipped the client. A new integration gate (onboard_doctor_clean) runs the full journey in an isolated HOME and asserts doctor exits green.
  • ctx_knowledge remember never stalls on the embedding model again. The first remember on a fresh install used to block up to the 120s tool watchdog while the ~30MB embedding model downloaded. It now uses non-blocking engine access: the fact commits immediately, the engine warms up in the background.
  • Semantic recall self-heals missing vectors. Facts written by the consolidation/ETL writers (and by remember while the engine is still warming up) never got an embedding, and only a manual embeddings_reindex repaired that — on a live machine most projects sat at 0 vectors, invisible to mode=semantic recall. remember now backfills up to 32 missing vectors per call (one batched inference, most-valuable-first, under the per-project lock), so active projects converge to full coverage without any manual step.
  • minimal_overhead=true (the default) is now documented honestly: session continuity is delivered via the AUTO CONTEXT block on the first tool call (prompt-cache-friendly) instead of an ACTIVE SESSION block at initialize.
  • CLAUDE.md block v4: MCP-aware guidance (GL #1138, second half of #637). The injected CLAUDE.md/CODEBUDDY.md block recommended ctx_read-first and a ctx_edit fallback unconditionally — in sessions without a connected lean-ctx MCP server those tools do not exist, stranding agents on shell heredocs. The block (v4 / CodeBuddy v2, session-heal updates existing installs) now scopes every ctx_* recommendation to "when the ctx_* MCP tools are listed in this session", documents native ReadEdit as the primary editing path under the read-before-write gate, and says explicitly to use native tools throughout when no ctx_* tools are available. doctor gains an Instructions/MCP consistency check (GL #1139) that flags the hazardous combination — instructions advertising ctx_* while no lean-ctx entry is registered in the Claude MCP config — with a lean-ctx setup repair hint.

Security

  • ctx_call can no longer bypass egress DLP or permission inheritance. The guarded dispatch path unwraps ctx_call(name=…, args=…) and runs both checks against the inner tool and its arguments (nested ctx_call is already refused by the handler). Egress payload extraction is centralized in one helper shared by the MCP server and lean-ctx policy enforce, and now also covers ctx_patch write bodies (new_text, new_body, ops[].new_text). prefer_native_editor (#454) now hides/refuses ctx_patch alongside ctx_edit.
  • Bundled addons now spawn with a scrubbed environment (addon env isolation). Every runnable registry addon (Headroom, Sophon, Repomix, Serena, …) now declares a [capabilities] block. Its mere presence flips the single gateway spawn point from the legacy "inherit the full host environment" path to the scrubbed path (env_clear + base allowlist), so host API keys/tokens no longer reach an untrusted addon child process. Network/filesystem grants are declared honestly to match each tool's real needs (registry fetch, cache/index/vault writes) — the empty env allowlist is the isolation win. A regression test asserts every runnable bundled addon carries a capability block.

Added

  • Doc corpora as first-class retrieval sources (Context Hub, GL#1132). The artifact index now ingests PDF (panic-safe local text extraction; a scanned or malformed PDF becomes a warning, not a failed build), and the artifact registry (.lean-ctx-artifacts.json) accepts absolute/~ paths so external doc folders — an Obsidian vault, ~/notes — become searchable corpora. PathJail stays the gate: external entries resolve only when allow-listed (read_only_roots / extra_roots / LEAN_CTX_ALLOW_PATH); a leading slash that matches an existing project path keeps its legacy project-relative meaning. New CLI flag semantic-search --artifacts searches the doc corpus; new guide docs/guides/docs-sources.md. Determinism guard: re-indexing an unchanged corpus is byte-identical (#498).
  • pgvector dense backend (Context Hub, GL#1136). Teams that already operate PostgreSQL can point the dense half of hybrid retrieval at it: LEANCTX_PGVECTOR_URL=postgres://… (or LEANCTX_DENSE_BACKEND=pgvector) stores embeddings in per-project, per-dimension vector(N) tables — same namespacing, point-id scheme and delete-by-file incremental sync as the qdrant backend, so switching backends never mixes identities. Implemented through the psql CLI (zero new crate dependencies, mirrors the postgres provider); rows return as per-line JSON for robust parsing; identifiers and literals are strictly validated/escaped. The qdrant + pgvector features join the default feature set, so release binaries support all three backends out of the box; a live end-to-end test (pgvector_e2e_round_trip, --ignored) verifies table creation, cosine search, incremental replace and quote-escaping against a real pgvector container. Guide: docs/guides/dense-backends.md.
  • Addon registry: qmd + memgraph-ingester (Context Hub, GL#1134). Two community tools from the Discord retrieval thread are now 1-command installs: qmd (on-device Markdown/notes search — BM25 + vectors + reranking, via npx -y @tobilu/qmd@2.5.3 mcp) and memgraph-ingester (structure-aware RAG on a Memgraph code graph, via uvx memgraph-ingester-mcp==0.6.6; needs a running Memgraph). Both ship scrubbed-env capability blocks; the memgraph Bolt-URI/read-only toggles joined the reviewed env passthrough allowlist.
  • Docs: the context-infrastructure map (GL#1135). New docs/guides/context-infrastructure.md (sources → one pipeline → hybrid retrieval → OKF/ctxpkg portability → addons) and docs/guides/dense-backends.md documenting the previously undocumented Qdrant dense backend (LEANCTX_DENSE_BACKEND, LEANCTX_QDRANT_URL/_API_KEY /_TIMEOUT_SECS/_COLLECTION_PREFIX) next to the default in-process store.
  • Portable OKF knowledge export/import (knowledge export --format okf / knowledge import <dir>, ctx_knowledge). Renders facts, patterns and typed relations from one shared KnowledgeSnapshot to the vendor-neutral Open Knowledge Format (git-diffable Markdown + YAML, relations as Markdown links) or the signed .ctxpkg bundle. Round-trips byte-identically, accepts foreign OKF bundles, and never leaves dangling relations. Fully local and free.
  • Addon registry version-staleness check (scripts/check-addon-versions.py). Resolves every pinned upstream (PyPI / npm / NuGet / crates.io) against its registry and reports drift as GitHub annotations. Wired into a dedicated, non-blocking Addon Registry Freshness workflow (weekly + whenever the registry changes) so a curated pin is never silently stale — and an upstream release never breaks our own build.
  • Cognee is now 1-click installable (addon add cognee). It ships a published MCP package (cognee-mcp) and runs fully local by default (SQLite + LanceDB + Kuzu), so it fits the standard uv tool install bootstrap; the only runtime requirement is an LLM_API_KEY, which is passed through via a reviewed single-entry capability allowlist (all other host env stays scrubbed). The remaining memory/graph listings (mem0, graphiti, zep, letta, claude-context) stay directory-only because they need external infrastructure (a vector/graph DB, or a managed account) that a one-command install cannot provision.
  • session new aliases session reset (#653). lean-ctx session new now clears the active session just like session reset, matching the "start a new session" mental model; covered by a CLI characterization test.
  • Deterministic markdown compaction + progress-log folding in aggressive compression (#655). .md/.markdown/.mdown reads (and .txt files that carry a real ATX heading) are structurally compacted: every heading survives, fenced code blocks are atomic (kept verbatim or dropped whole, never split by an omission marker), and body lines are ranked by an IDF-style scorer over ordered token sets so the output is byte-stable (#498). Shell compression now folds repetitive cargo/pytest/package-manager progress runs into stable markers while still honoring the verbatim token cap — diagnostics stay verbatim, oversized logs keep safety-needle preservation. Thanks @ousatov-ua!

Changed

  • RMCP SDK upgraded 1.7 → 2.0 (MCP 2025-11-25 alignment, #656). The MCP server/client stack now builds on rmcp 2.0: Content is the spec-unified ContentBlock, prompt roles use the shared Role, resources are plain Resource structs, and progress notifications use the new constructor API. Pulls in rmcp 2.0's security fixes (OAuth resource-spoofing/metadata-SSRF hardening, streamable-HTTP session-leak fix) and unlocks 2025-11-25 protocol features (tool icons, URL-mode elicitation, tasks) for future releases. Protocol negotiation with older clients (2025-06-18 and earlier) is unchanged — verified end-to-end over stdio against the 1.7 baseline (identical tool surface, identical negotiated protocol). Client-facing roots-based project-root auto-detection stays in place (SEP-2577 deprecation acknowledged upstream, still fully functional).
  • Refreshed bundled addon pins to current upstream: Headroom 0.27.0 → 0.28.0, Repomix 1.15.0 → 1.16.0.

Fixed

  • Zero-config first-session frictions closed after a fresh-install E2E audit (#658). A scripted fresh journey (isolated $HOME, real MCP handshake like an editor) surfaced eight frictions; all are fixed with regression tests: auto-findings now parse the pre-decoration tool output, so the injected --- AUTO CONTEXT --- header can no longer become a junk Read --- finding polluting session memory and every wakeup briefing (F1); setup/onboard --help prints help instead of executing setup side effects (F2); ctx_call with misspelled keys (tool/args/params) fails with the exact fix instead of silently dispatching without arguments (F3); ctx_knowledge remember derives a deterministic key slug when key is omitted and accepts content= as value alias — matching what our own injected instructions document (F4); Rust call edges inside macro bodies (println!, assert_eq!, …) are extracted at the token level, so a fresh Rust project no longer reports 0 edges (F5); the project-overview header surfaces persisted call-graph edges instead of contradicting ctx_callgraph with 0 edges (F6); bare ctx_knowledge recall lists recent facts instead of erroring (F7); and ctx_session show is accepted as a synonym of status (F8).
  • MCP PathJail auto-corrects a stale markerless root instead of rejecting the workspace (#649). An MCP server launched by VS Code/WSL could adopt a markerless client cwd (e.g. /mnt/c/Users/<user>) as its jail root; the first absolute path into the real workspace on another mount was then rejected with path escapes project root, breaking ctx_compose / ctx_read / ctx_patch. resolve_path now reroots opt-in-free from such a markerless root to the marker-bearing project derived from the requested path — the same rationale as the agent-config-dir case (#580) — while a markerless target with no derivable project stays blocked, so PathJail enforcement is unchanged.
  • Local daemon IPC no longer 401s on tool calls (#651, #652). The daemon writes an auto-generated auth token, but the IPC client (Unix domain socket / Windows named pipe) sends no Authorization header, so /v1/tools/call failed with 401 while /health passed. Router construction is now split: TCP HTTP keeps Bearer auth, while local IPC serving disables the HTTP Bearer — the socket/pipe is already a user-local OS boundary (Unix 0o600, user-specific pipe name). TCP auth is unchanged, a regression test guards the IPC path, and a security review found no weakening of network auth.
  • Codex stops reconstructing compressed shell output in chunks (#625, #654). The SessionStart hint now states plainly that compressed output is not exact evidence and hard-requires re-running lean-ctx raw "<exact command>" for exact content, forbidding chunked reconstruction (cat/sed/head/tail) and quoting compressed output as exact — so Codex uses the reversible raw escape instead of re-reading the compressed view piecemeal.
  • Enterprise/OS TLS roots are honored by every HTTP client (#643). All ureq clients are now built through core::http_client, which injects RootCerts::PlatformVerifier so requests trust the system/enterprise trust store instead of only the bundled WebPKI roots — fixing UnknownIssuer failures behind TLS-intercepting corporate proxies (updates, version check, embeddings download, Qdrant, Datadog/FinOps export, LLM enhance, SSO/billing, web fetch, webhooks).
  • Shell hook is quiet by default (#646). The activation notice (lean-ctx: ON …) no longer prints on every new interactive terminal; mode-change notices now route through a _lean_ctx_notice helper that speaks only when LEAN_CTX_DEBUG=1 (and stdout is a TTY). lean-ctx-status still reports the current state on demand.
  • doctor recognizes its own running dashboard on port 3333 (#644). The dashboard port check reported a conflict whenever port 3333 was busy — even when the occupant was lean-ctx's own dashboard. It now probes /api/version on bind failure and reads the port as healthy only when the response is the dashboard's own version JSON; unrelated services still surface the conflict. Implemented by strengthening and reusing the dashboard's existing dashboard_responding probe, so the browser-open guard and doctor share one source of truth.
  • Native Read no longer breaks Claude Code's read-before-write guard (#637). The PreToolUse redirect hook rewrote a native Read to a temp .lctx copy, so Claude Code's Write/Edit read-before-write guard tracked the temp path and a follow-up native Write/Edit to the real file failed with "File has not been read yet" — worst in headless claude -p, with no supported off-switch (the hook self-healed back into settings.json). A new read_redirect = auto | on | off key (env LEAN_CTX_READ_REDIRECT) now governs the Read redirect and is evaluated per hook fire, so it also covers headless runs and never fights the self-heal. The default auto disables only the Read path-swap on hosts carrying that guard — Claude Code / CodeBuddy, detected inside the hook via the CLAUDE_PROJECT_DIR marker Claude Code exports to every hook subprocess (CLAUDECODE / CODEBUDDY are honored too) — so native Read → Write/Edit works out of the box; the ctx_read MCP tool and the Grep/Glob redirects keep compressing. on restores always-redirect; off disables the Read redirect everywhere.

[3.8.18] — 2026-06-30

Added

  • Compressed output is now visibly reversible — a first-class recovery layer on every surface (#625). Agents (Codex especially) were re-reading compressed views line-by-line because nothing taught them how to get the raw bytes back — the real cause behind "too compressed" reports. The escape hatch already existed; it is now discoverable and consistent, with the non-MCP path treated as first-class (many orgs forbid MCP):
    • Proactive (MCP rules v4). A new RECOVER rule states the invariant — compressed output is reversible, never re-read it line-by-line — and names the recovery grammar: read the shown file path with any tool (no MCP), or ctx_read(mode=full|raw=true); [Archived]/tee/firewall handles → ctx_expand(id=…).
    • Reactive (ctx_read footer). Every lossy ctx_read view ends with a recovery footer that leads with the native file path, then the MCP fallbacks; full/raw views carry no footer (deduped by mode). ctx_read gained an explicit raw parameter (verbatim bytes, unframed escape hatch).
    • CLI / shell-hook surface. lean-ctx raw "<command>" is now surfaced in help, help all and the cheatsheet, so the raw path is reachable without MCP.
    • Guaranteed raw on disk. The default tee_mode is now high-compression: whenever a view compresses heavily a verbatim copy is one ctx_expand/file read away. The new recovery_hints config (off|minimal|full) tunes footer verbosity. All recovery grammar (archive, firewall, spill, tee, ctx_shell) flows through one recovery module, so every channel speaks the same non-MCP-first sentence.

Fixed

  • Source reads are no longer silently stripped of decorative separator comments, which had broken follow-up edits (#628). A file read carries lossless-only intent — the model edits exactly what it sees — so the proxy never lossy- compresses a recognized file read. The safety net for reads routed through an unrecognized tool (the content heuristic) under-counted real source, though: a decorative separator comment (// ————, // ----) and the call-shaped scaffolding of a test file (describe(…) {, });) scored as non-code, so a genuine .test.ts could be compressed on the wire and lose those separator lines — after which ctx_edit failed on a whitespace mismatch against the on-disk file. The heuristic now treats comment lines as neutral (they never dilute the code ratio) and recognizes top-level call/closer shapes as code, so real source is protected regardless of the originating tool. Regression tests pin the verbatim ctx_read modes (full/raw/lines:N-M) to reproduce every source line, including decorative comments.
  • The Codex lean-ctx -c SessionStart hook is no longer a redundant nag (#625). Codex's PreToolUse hook already rewrites every rewritable Bash command to lean-ctx -c "<command>" transparently (permissionDecision: allow + updatedInput), so the old SessionStart line ("prefer lean-ctx -c") taught nothing actionable — and crucially said nothing about getting raw output, which is the one thing an agent cannot reach once a command is auto-compressed. The hint now teaches the raw escape (lean-ctx raw "<command>") and forbids the small-chunk re-read anti-pattern. The raw spellings (lean-ctx raw, lean-ctx -c --raw) are reentrance-safe: the rewrite hook leaves any command starting with lean-ctx untouched, so the agent always reaches verbatim bytes.
  • ctx_shell now surfaces when a requested cwd was rejected by the project-root jail (#629). A cwd resolving outside the session's project_root is rejected by the path jail and silently replaced with the project root — correct sandboxing (it stops MCP clients escaping the workspace), but the silent fallback made the parameter look ignored: a caller running pwd && ls in what they thought was dir A actually ran in the root with no indication why. The jail is untouched; effective_cwd_checked now returns the rejection reason and ctx_shell appends a one-line [cwd: …] hint naming the reason and the directory it ran in instead. Thanks to @mahmoudps for the report and the original patch (#630).
  • ctx_search no longer returns a false No matches for content edited after the index warmed (#624). The resident trigram index treated itself as fresh for a fixed 15 s TTL with no per-file change detection, so in hybrid setups where edits are applied natively a freshly edited or created file was invisible to trigram narrowing — for a word-literal query a missing trigram made the candidate set provably empty, yielding 0 matches even though the text was on disk (and get_fresh even served a stale index past the TTL, so results flipped between hits and misses depending on timing). Index freshness is now a function of corpus state, not the clock: the build records a cheap, order-independent signature over the eligible files' (path, mtime, size), and every lookup re-derives it via a stat-only walk that shares the build's exact filter path — the resident index is served only when the signature matches the live filesystem, otherwise lean-ctx walks accurately for that call and rebuilds the index in the background. The optional LEAN_CTX_SEARCH_INDEX_COALESCE_MS (default 0 = always verify) coalesces the stat-walk under bursty load on very large indexed trees. ctx_read was never affected (it is mtime + MD5 verified).
  • No-auth dashboard no longer 403s behind a port-remapped Docker publish (#623). In no-auth mode every /api/* request is gated on a Host allowlist built from the bound port. When a container binds 0.0.0.0:3333 but Docker publishes it on a different host port (-p 60000:3333), the browser reaches 127.0.0.1:60000 and sends Host: 127.0.0.1:60000 — the published port, which the bind-port allowlist (127.0.0.1:3333) rejected, so every API call returned 403 and the dashboard's cards failed to load. The gate now accepts any loopback host (127.0.0.0/8, localhost, ::1) on any port: a loopback Host can't be a DNS-rebinding target, so this is safe, and cross-origin/CSRF is still blocked by the Sec-Fetch-Site/Origin checks. Port-remapped loopback publishes now work out of the box without LEAN_CTX_DASHBOARD_ALLOWED_HOSTS.

[3.8.17] — 2026-06-30

Fixed

  • Codex Desktop remote-control pairing now works with the ChatGPT-subscription opt-in (#597). When [proxy] codex_chatgpt_proxy routes Codex through the proxy, chatgpt_base_url points every /backend-api/* call at lean-ctx — including Codex Desktop's remote-control pairing, which opens a WebSocket to chatgpt.com. The /backend-api handler only spoke HTTP/SSE (it even stripped the Upgrade/Connection headers), so the pairing handshake never completed and remote control stayed broken. The proxy now detects a WebSocket upgrade on /backend-api and tunnels it verbatim through to wss://chatgpt.com, replaying the client's auth plus the shared Cloudflare clearance and relaying every frame in both directions. Opening that wss:// socket needs a process-default rustls CryptoProvider; because the dependency tree pulls both aws-lc-rs (reqwest) and ring (lettre/ureq), tokio-tungstenite couldn't auto-pick one and the TLS handshake aborted — so the proxy now installs aws-lc-rs (reqwest's provider) at startup. Model-turn compression on /backend-api/codex/responses is unchanged, and the default native ChatGPT path (opt-in off) was never affected. Verified end-to-end against the live ChatGPT backend: the remote-control enroll
    • WebSocket now reach chatgpt.com identically whether Codex connects directly or through the proxy (the proxy is fully transparent).

[3.8.16] — 2026-06-30

Added

  • Agent navigation upgrades for coding agents (#607#611). A cohesive set of cross-turn navigation primitives that cut wasted re-discovery:
    • Stable symbol handles (#607). Symbols carry a resolvable, cross-turn handle (path#name@Lline); ctx_search action=symbol handle=… returns the exact body deterministically, so an agent can re-open a symbol next turn without re-searching.
    • Search hits tagged with their enclosing symbol (#608). Every ctx_search match now reports the function/class it lives in (plus that symbol's handle), turning a bare line hit into navigable context.
    • Agent-loop taxonomy + navigation-paradox guidance (#609). The canonical rules (now v3) name the act→observe→navigate loop and warn against the "more reads ≠ more understanding" paradox, surfaced as a compact per-turn one-liner.
    • signatures coverage for 5 more languages (#610). OCaml, Haskell, Julia, Solidity and Nix gain tree-sitter signature extraction (68 %…−90 % tokens vs. full source) via statically linked grammars (no dynamic WASM).
    • Off-vs-on answer-quality testbench (#611). lean-ctx eval testbench measures answer quality, tokens, turns and walltime across pinned real repos, with a deterministic recorded subset that gates CI and a FINDINGS.md + regressions report.
  • Codex ChatGPT-subscription proxy — durable, opt-in model-turn compression (#603/#616/#621). New [proxy] codex_chatgpt_proxy (env LEAN_CTX_CODEX_CHATGPT_PROXY, default false). A ChatGPT-subscription login is flat-rate, so the safe default leaves Codex talking directly to chatgpt.com — history visible, codex cloud/remote intact, no #597. When you opt in, lean-ctx setup pins the generated leanctx-chatgpt provider (model_provider + chatgpt_base_url + a [model_providers.leanctx-chatgpt] block) so model turns route through the proxy's /backend-api/codex/responses rail and get compressed; every other /backend-api/* call (auth, cloud/remote, MCP) is forwarded credential-preserving. Pinning a provider scopes Codex history to it (#597), so routing is opt-in — you accept that trade only when you ask for it. On that rail the proxy strips Codex's X-OpenAI-Internal-Codex-Responses-Lite marker so chatgpt.com serves the full Responses stream every model needs (gpt-5.5 was rejected in lite mode); single- and multi-turn previous_response_id continuation verified (#623). lean-ctx doctor is opt-in-aware — the sanctioned rail reads healthy, a half-written pair or an openai_base_url/backend-api override stays flagged as a stale artifact to heal. Turn it on/off durably with lean-ctx proxy codex-chatgpt on|off|status: it writes the opt-in straight to [proxy] codex_chatgpt_proxy — the single source of truth the env-less managed proxy, editor integrations and every later setup pass read (none inherit the shell env, #449/#590) — then re-applies Codex's provider config immediately, with clear feedback (and a heads-up when the proxy isn't running yet). Toggling back off strips the entries and restores native history + cloud/remote. Exporting LEAN_CTX_CODEX_CHATGPT_PROXY still works and is bridged to config on proxy enable/restart. This closes the trap where a shell env opt-in never reached the process that actually rewrote the Codex config. Builds on @ousatov-ua's PRs #616/#621, gated behind the opt-in so non-routing users keep full native history by default.
  • Managed Connectors — hosted continuous source sync (#281). The team server runs a scheduled, in-process sync of configured GitLab/GitHub sources into a workspace's BM25/graph/knowledge stores, so every seat's ctx_semantic_search / ctx_knowledge surfaces the source's issues/PRs/pipelines without per-call credentials or manual ctx_provider runs. Per-connector credentials live only in the private team.json (encrypted at rest by the control plane) and are never returned; GET /v1/connectors exposes a secret-free roster plus per-connector health, audit-scope gated. Ingestion honours the hosted storage quota as a non-destructive backstop (pauses, never deletes — #282), and connector activity is rolled into the /v1/usage snapshot (#283). The managed_connectors count remains entitlement-gated by the control plane at provisioning.
  • Context Time Machine — git-anchored, signed snapshots of the layer state (epic #1022). The state of the context layer (what the model saw, why, and at what token ROI) becomes a navigable, reproducible, shareable artifact — the temporal axis through everything lean-ctx does.
    • CONTEXT_SNAPSHOT_V1 (#1023). A distilled, typed, content-addressed (BLAKE3) and ed25519-signable projection of the live stores — git anchor, Context IR lineage, ledger Φ-scores, ROI, and the session slice — never raw transcripts. Deterministic per the output-determinism contract (contract).
    • Headless engine (#1024). lean-ctx snapshot create [--sign] | list | show | verify builds a snapshot from the live stores, anchors it to the current commit, and stores it on a crash-safe, append-only timeline (index.jsonl); verify proves both integrity (body hashes to its id) and the signature.
    • Replay in the dashboard (#1025). A new Time Machine tab scrubs the timeline and shows, per frame, the git anchor, ROI, lineage, ledger Φ, and the session behind it, over a JSON control-plane API.
    • Restore / resume (#1026). lean-ctx snapshot restore <id> [--git] merges the snapshot's session slice (task, decisions, files) into the live session so the next agent resumes where it left off, and with --git checks out the commit anchor — guarded so it never clobbers a dirty tree. Bare-CLI sessions now stamp their project root (as the MCP daemon already did), so a CLI-only session … ; snapshot create flow captures the session slice.
    • Share / import (#1027). lean-ctx snapshot publish <id> [--out <path>] writes a signed, portable *.ctxsnapshot.json; lean-ctx snapshot import <file> proves its integrity and signature and appends it to the local timeline (idempotent; tampered or wrongly-signed files are refused) — so a teammate can show, verify, and restore exactly the state you shared.
  • Lossless memory & one consolidation engine (#995). Project memory is now fully recoverable and managed by a single capacity manager. Builds on and supersedes the original capacity-reclaim proposal by @ousatov-ua (PR #588).
    • Nothing is hard-dropped. Every store — facts, history, procedures and patterns — evicts through one archive-backed path (core/memory_capacity::reclaim_store): the lowest-value tail is written to memory/archive/<store>/ before removal, so it stays restorable. This replaces the previous per-store hard drops (history drain, procedure/pattern truncate) that lost data on overflow.
    • One capacity formula with hysteresis. A reclaim triggers only when a store reaches its cap and then settles it at a working-room target (75% by default), instead of churning on every write near the cap. On by default; set [memory.lifecycle] reclaim_enabled = false to trim only the overflow. The target fraction is [memory.lifecycle] reclaim_headroom_pct (env LEAN_CTX_LIFECYCLE_RECLAIM_*).
    • One consolidation engine. The CLI/MCP consolidate, the scheduled post-dispatch pass, startup auto-consolidate and the cognition loop now share a single canonical engine and session-import core (ConsolidateOptions), so promotion budgets, fact keys and confidences are identical everywhere. Fixes a long-standing cwd bug (#2362) where background consolidation imported the wrong project's session.
    • Recover on demand. lean-ctx knowledge restore [--store …] [--query …] [--limit N] and ctx_knowledge action=restore bring archived items back into the live stores (idempotent; a live fact's key is never shadowed by an older archived value). The recall-miss rehydrate now reaches every retained archive (previously 16 kept but only 4 reachable).
    • Preview before you commit. lean-ctx knowledge consolidate --dry-run (and ctx_knowledge action=consolidate dry_run=true) reports exactly what a run would import and archive, writing nothing. The consolidation report now breaks down archived counts per store and points at the restore command.
  • Learn-loop enrichment (#980). The gotcha learn-loop now reaches further with three additions. (1) Multi-target write-back: lean-ctx learn --apply writes the distilled learnings to AGENTS.md (created if absent) and CLAUDE.local.md (updated only when the project already keeps one), each via an atomic tmp+rename so a crash can never truncate your memory file. (2) Zero-config transcript scan: lean-ctx learn --mine with no argument auto-discovers the agent-transcripts directory (~/.claude/projects, then ~/.cursor/agent-transcripts) instead of erroring. (3) Loop-weighting bridge: the cognition loop now promotes proven gotchas — high confidence, seen across ≥3 sessions, and shown to have prevented real errors — into durable project knowledge, so recall surfaces them like any other fact (idempotent, capped per pass).
  • Never-compress path globs (#1150). New proxy.compress_protect takes a list of file-path globs (*.snap, **/golden/**, tests/fixtures/*) whose reads are always returned verbatim (full), bypassing every lossy mode (auto, aggressive, signatures, density, …) — for files where exact bytes matter more than token savings: golden snapshots, byte-asserted fixtures, security-sensitive configs. Each glob is matched against both the path and its file name, so *.snap works anywhere while **/golden/** targets a directory; explicit raw reads and lines: slices are left as requested. Empty by default (the lossless crushers and beneficial gate already keep compression safe), so it is a precise escape hatch, not a tax on the default fast path.
  • Premium defaults: safe cache telemetry now ships on (#986). Everything that is pure measurement or a strict safety improvement is enabled by default, so every install and every update delivers the best lean-ctx without flipping a flag (config is written minimally, so code defaults reach existing users on upgrade). The cache-economics telemetry below (proxy.cache_policy) and the cache-aligner volatile-field telemetry (proxy.cache_aligner, #940) are now on by default — both are measurement-only / strictly cache-safe. Features that change provider-visible content or carry a real cost risk stay opt-in by design (a premium proxy never silently risks your bill): the cold-prefix repack (cold_prefix_repack, ~12× re-bill on a wrong cold guess), the active cache-aligner relocate (cache_align_relocate), breakpoint injection (cache_breakpoint), and output shaping (effort, verbosity_steer). All remain togglable via config or LEAN_CTX_PROXY_*=on|off.
  • Cache-economics: prompt-cache miss attribution + net-cost repack gate (#986). proxy.cache_policy (now on by default) answers the one cache question the proxy could not yet measure — why a turn misses the provider prompt-cache. proxy/cache_attribution classifies every anchored turn by comparing the cacheable prefix hash and idle time against the conversation's previous turn: cold start, warm reuse (stable prefix within TTL — should hit), TTL lapse (stable prefix, expired by time), or prefix change (the prefix mutated, so the provider re-writes regardless of timing). The four outcomes surface as cumulative gauges under /status cache_attribution, turning "I keep missing cache" into an actionable diagnosis (extend the TTL / repack vs stop mutating the prefix). It is strictly measurement-only — the request body is never touched. The same flag also adds proxy/cache_policy, a priced (model_pricing) net-cost gate folded into the cold-prefix repack (#480): a repack is skipped when the cacheable prefix is below the provider's ~1024-token minimum, so re-seeding it could never produce a cache the provider keeps. The gate is an extra AND-condition, so it can only make repacking more conservative — it can never bust a cache the default kept. On by default; the attribution never mutates the wire bytes, so the request the provider sees is byte-identical whether the policy is on or off. Opt out with proxy.cache_policy = false or LEAN_CTX_PROXY_CACHE_POLICY=off.
  • YAML crusher — kubectl -o yaml, manifests, CI configs (#985). A new core/yaml_crush maps a YAML document onto the JSON value model (yaml_serde) and compacts it through the shared json_crush core: the verbose YAML formatting is dropped and redundant items/list arrays are factored into _defaults, all behind a _lc_yaml_crush envelope that round-trips exactly to the parsed value. Wired into the aggressive read path (compressor, ctx_read) for .yaml/.yml and into shell-output compression (kubectl/helm -o yaml), with the same lossless-then-lossy ladder as the tabular crusher — the lossy stage drops high-entropy columns behind a CCR handle (yaml_ tee prefix). Fires only above the 25 % reduction gate (JSON quoting offsets YAML formatting for flat string maps, so those are correctly left alone) and never inflates. Deterministic (#498); a Condition::YamlCrush arm measures the win.
  • Columnar crusher for CSV/TSV — reads and shell output (#982). A new core/tabular_crush rewrites row-oriented delimited data into a columnar JSON shape: constant columns are hoisted once into _const, varying columns stay positional in _rows, so a table whose values repeat down a column stops paying for that column on every line. It is wired into the aggressive read path (compressor, ctx_read) for .csv/.tsv and into shell-output compression (shell/compress/engine), and only fires when it clears a 25 % size gate (columnar JSON quoting has overhead, so the bar is tuned below the JSON crusher's 50 %). The lossless mode round-trips exactly; an opt-in lossy mode drops high-entropy columns into _dropped with full CCR recovery (tbl_ tee-store prefix, <lc_expand:…> handle). Deterministic (#498) and never inflates. A Condition::TabularCrush arm in the A/B harness measures the win.
  • CCR robustness regression suite (#983). Nine focused tests (proxy/ccr_robustness_tests) lock down the content-addressed-recovery path against the failure classes seen in comparable context layers: a lossy rewrite that emits a handle it cannot back, retrieval after the tee file is gone past its TTL, an in-band splice on a streaming-shaped request, the tee store colliding with read-stub bookkeeping, path-traversal / non-tee / bad-hex handles, and the cold stub index resurrecting content across a restart. A change-aware preflight gate runs them on fast whenever the recovery surface changes and on every full run, so the guarantees stay green without slowing unrelated work.
  • See compression before it ships — compress diff + ctx_compare (#984). A read-only core/compress_preview renders original-vs-emitted side by side with byte and token accounting, the saved-token delta and ratio, and a unified diff, reusing the real read/shell pipelines (no separate code path to drift). Exposed as the CLI lean-ctx compress diff <file|-> [--shell "cmd"] [--json] and the read-only MCP tool ctx_compare (Debug category). Deterministic and self-describing, so an agent can decide whether a rewrite is worth it.
  • ctx_outline levels up — directory outline, deterministic JSON, name filter, verifiable AST backend (gitlab #981). A public review (the ast-grep author, comparing his new ast-grep outline) called our outline "fishy" — fair only as a first impression: ctx_outline has always been real tree-sitter with declarative per-language queries (core/signatures_ts, ~22 languages, real multi-line spans), but the prominent file in the repo is the 790-line regex fallback (core/signatures.rs) and the tool overclaimed "via tree-sitter" without disclosing it. This release closes the gap and turns it into an advantage:
    • Directory outline. ctx_outline <dir> now emits a deterministic, sorted, gitignore/vendor-aware per-file table of contents (matching ast-grep outline src); a directory used to be rejected. Bounded (≤ 600 files, ≤ 1.5 MB/file).
    • Stable JSON output. format=json produces byte-stable JSON (#498) — fixed field order, sorted files, no timestamps — for a file or a directory, each file labelled with the extraction backend (tree-sitter | regex) so the syntax-aware claim is verifiable, not asserted. (ast-grep lists stable JSON as an open TODO.)
    • Name filter. match=<substr> keeps only symbols whose name contains the case-insensitive substring, composing with kind= — across file / dir / JSON.
    • Navigable by default. The text outline now always carries @Lstart-end line spans (located renderers), so it actually serves "navigate before a full read", as advertised.
    • Honesty + correctness. core/signatures.rs gained a module-doc pointing at the tree-sitter primary path; Rust impl blocks render as impl …, not class … (both backends); the tool description now states "tree-sitter primary, regex fallback"; dead handle_via_read removed. New extract_signatures_with_backend powers the per-file backend label.
  • Active cache-aligner relocate — the opt-in tail-relocate the #940 detector was the precursor to (#974). With [proxy] cache_align_relocate (env LEAN_CTX_PROXY_CACHE_ALIGN_RELOCATE) enabled, the proxy rewrites an unanchored Anthropic system prompt into a stable block — every volatile value (ISO dates/datetimes, UUIDs, git SHAs) replaced by a constant [ctx#N] placeholder — carrying the cache_control breakpoint, plus an uncached trailing block that re-states the relocated values. The large, stable prefix then stays byte-identical turn-to-turn and finally caches at the provider, while only the small volatile tail is reprocessed. Anthropic-only, Treatment-arm, and gated on a client that anchored nothing of its own and on Anthropic's minimum cacheable size; deterministic (#498) and idempotent (a second pass sees only placeholders). Composes with the #939 breakpoint injection to exactly one anchor on the stable block. New /status cache_safety gauges (volatile_relocate_requests, volatile_fields_relocated) quantify the win. Default off — the request is byte-identical until you opt in.

Fixed

  • Cline/Roo rules are MCP-first — dropped the stale lean-ctx -c prefix guidance (GH #603). install_cline_rules hardcoded a .clinerules body telling the agent to prefix every shell command with lean-ctx -c, even though Cline/Roo get the lean-ctx MCP server installed and the shell hook already wraps real terminal commands — so the manual prefix re-wrapped an already-wrapped command, tripped the re-entry passthrough and returned uncompressed output. The body now derives from core::rules_canonical (the single source of truth) like every other dedicated rule file: MCP-first (ctx_*), no lean-ctx -c, wrapped in the canonical markers so uninstall can strip it (the old freeform header was not removable). Reported by @ousatov-ua.
  • lean-ctx no longer touches Codex under a ChatGPT subscription login (GH #597). GH #568 pinned model_provider = "leanctx-chatgpt" (plus a [model_providers.leanctx-chatgpt] block) and openai_base_url/chatgpt_base_url overrides in ~/.codex/config.toml to route ChatGPT turns through the proxy. That was the wrong trade for a subscription: a ChatGPT plan is flat-rate, so compression saves no money — while the pin hid every prior conversation (Codex scopes history by provider id by design, openai/codex#15494/#19318) from /resume, fork and the Desktop picker, the backend-api base-URL overrides funnelled Codex's cloud/remote + login traffic through a proxy built only for model turns (breaking codex cloud/remote), and they made Codex depend on a live local proxy. lean-ctx now writes nothing for ChatGPT auth — Codex talks directly to chatgpt.com, so history, codex cloud/remote and login all stay native (no data loss; rollouts + SQLite were always intact). API-key Codex is unchanged: it keeps the per-token /v1 proxy rail, where compression actually cuts cost. Upgrading auto-heals: the next lean-ctx proxy enable/setup strips the stale leanctx-chatgpt provider and the backend-api base-URL overrides, and lean-ctx doctor flags any lingering ChatGPT-proxy entries.
  • Shell hook no longer blocks Claude Code's Bash tool (GH #595). Claude Code wraps every Bash call in its own scaffolding (shopt -u extglob … && eval '<cmd>' < /dev/null && pwd -P >| /tmp/claude-XXXX-cwd) before the lean-ctx shell hook forwards the whole line to lean-ctx -c. The allowlist then hard-blocked the eval at command position (exit 126) on every command — the wrapper shape is identical each time, so the Bash tool became unusable.
    • Look through the wrapper: the new shell::agent_wrapper detects the host scaffold, extracts the real inner command and runs that through the normal allowlist + compression pipeline, so the inner git/cargo/… command is gated and compressed as usual instead of dying on the eval.
    • cwd tracking preserved: the trailing pwd -P >| …-cwd snapshot is rebuilt onto the unwrapped command, so the host keeps tracking the working directory.
    • Security unchanged: detection requires both an eval '<cmd>' and a host cwd-snapshot redirect, so a bare eval the model itself chose still hits the allowlist's hard block (regression-tested end to end).
  • Installer no longer fails on symlinked ~/.claude / ~/.codex (GH #596). Dotfiles users symlink their agent config (~/.claude.json, ~/.codex/config.toml, …) into a managed repo. The [Critical] symlink hijack protection previously added to config_io::write_atomic then hard-blocked every write through such a symlink (refusing to write through symlink), so setup/init could no longer register the MCP server or write agent config.
    • Write through the symlink: the new resolve_write_target follows a user-managed symlink to its real file and writes there atomically, leaving the symlink intact and the dotfile updated — the legitimate dotfiles pattern.
    • Hijack protection kept: following is allowed only when the resolved target stays within $HOME; a symlink whose target escapes $HOME is still refused, so a planted symlink can never redirect a config write to a system path.
    • Opt-in escape hatch for out-of-$HOME dotfiles: power users who keep their dotfiles repo outside $HOME (e.g. /opt/dotfiles) can now allow specific trusted roots via the new allow_symlink_roots config key (or the LEAN_CTX_ALLOW_SYMLINK_ROOTS env var). It is empty by default (strict $HOME-only stays the default) and security-sensitive — like extra_roots, an untrusted project-local config can never add a root. The refusal message now spells out all three ways forward (CLAUDE_CONFIG_DIR/CODEX_HOME, move under $HOME, or allow-list the root) instead of a bare "escapes $HOME".
    • Robust directory setup: a new ensure_dir tolerates a symlinked agent directory (and creates a dangling in-$HOME target), and the Claude/Codex setup steps now surface a clear error instead of silently swallowing a failed create_dir_all. The Claude skill also honors CLAUDE_CONFIG_DIR instead of a hardcoded ~/.claude.
    • Read-only / cross-FS fallback shared: config_io now reuses the edit tools' atomic-write mechanics (core::atomic_fs), gaining the read-only-directory in-place fallback (#459). Regression-tested end to end with symlinked claude/codex configs.
  • CLI and MCP now always read the same config.toml (GH #594). When an older release had baked LEAN_CTX_DATA_DIR into an editor's MCP env, the server ran in single-dir mode and read config from the data dir (~/.local/share/lean-ctx) while the terminal CLI read it from the config dir (~/.config/lean-ctx), so settings silently diverged.
    • Resolver: a LEAN_CTX_DATA_DIR equal to the standard $XDG_DATA_HOME/ lean-ctx is now a data-only pin and no longer collapses config/state/cache onto the data dir. Custom/legacy single-dir paths still collapse (unchanged back-compat). Deterministic, no filesystem access (#498).
    • Self-healing writers: setup / update strip a stale LEAN_CTX_DATA_DIR from the lean-ctx MCP entry across all formats (JSON, Codex TOML, Hermes YAML).
    • Lossless migration: a config.toml stranded in the data dir is relocated to the config dir — adopted when canonical is empty, otherwise the CLI-authored config wins and the stray copy is archived to config.toml.superseded (never deleted).
    • doctor: flags config location — stray config.toml in the data dir, and doctor --fix unifies it.

Changed

  • Frictionless updates. lean-ctx update — the prebuilt-binary self-updater everyone should use — now bounds its DNS/connect/response phases with timeouts, so a dead network or unresponsive mirror can no longer make it appear "stuck". dev-install prints an up-front notice that it builds from source (a contributor workflow that can take minutes) and points end-users at lean-ctx update. When the running binary is behind the latest release, the MCP session and lean-ctx doctor now surface a one-line "run lean-ctx update" nudge — notify-only, never an auto-install (on by default; opt out with update_check_disabled / LEAN_CTX_NO_UPDATE_CHECK).

[3.8.15] — 2026-06-27

Fixed

  • The embeddings-free build compiles again — unblocks FreeBSD and other non-tier-1 ports (#586). The default embeddings feature pulls ort (ONNX Runtime), whose load-dynamic dylib resolver only ships a default library name for windows/linux/android/macos/ios; on FreeBSD that match is non-exhaustive and the build fails inside ort itself. lean-ctx already intends to build without ORT (run_inference has a not(embeddings) stub), but the ort_environment / ort_execution_providers modules and two embeddings helpers (embed_batch's rayon import, run_inference_batch) were not feature-gated, so --no-default-features — the configuration such ports must use — also failed to compile. Those are now gated, and a build-minimal CI job (cargo check --no-default-features) guards the configuration so it can never silently regress. Semantic search / embeddings stay unavailable on targets ORT does not support, but the rest of lean-ctx builds and runs.

[3.8.14] — 2026-06-27

Added

  • Write-time memory admission — dedup-merge + salience floor (gitlab #969/#970). A capped knowledge store used to fill with paraphrases of facts it already held, forcing eviction to drop a good fact to make room for a near-duplicate. The agent-facing ctx_knowledge remember path now runs a server-side admission gate (ProjectKnowledge::remember_admitted) before committing: a new value that is ≥ auto_merge_similarity (word-Jaccard, default 0.9) to an existing same category fact under a different key is merged into it (a confirmation bump, no new row), and a value whose content salience falls below min_salience (default 0 = off, lossless) is rejected with a clear reason. Internal restorers (archive rehydrate, cognition auto-promotion) keep using the ungated remember, so admission only disciplines fresh agent writes. Same-key confirm/supersede (contradictions) is untouched. Tunable via [memory.admission] / LEAN_CTX_ADMISSION_{ENABLED,MERGE_SIMILARITY,MIN_SALIENCE}.
  • Cluster compaction — collapse low-value fact piles into recoverable digests (gitlab #969/#971). Decay + the cap kept a busy store churning at 100% but never actually shrank it. A new cognition-loop step (8c, hourly, lean-ctx-driven) collapses a same-category cluster of faded (< max_confidence), barely-confirmed (<= max_confirmations), never-frequently/recently-retrieved facts — at least min_cluster of them (default 4) — into a single content-addressed digest fact, archiving the originals so they rehydrate on recall. Digests and synthesized summaries are never re-compacted. The digest key/value are byte-stable functions of the cluster (#498). Surfaced as compacted= on the cognition-loop report. Tunable via [memory.compaction] / LEAN_CTX_COMPACTION_*; runs only in the background loop, never on the remember hot path.
  • Self-curating memory defaults + actionable capacity guidance (gitlab #969/#972). prune_unretrieved_after_days now defaults to a conservative, recoverable 90 days (was off), so genuinely cold single-confirmation facts are archived instead of accumulating. lean-ctx doctor capacity warnings are no longer a dead end: a store at its cap now prints that this is healthy by design (eviction holds it there) and which lever to pull, while an over-cap CRIT tells the operator to run the cognition loop or raise the cap.
  • Read-cache re-delivery telemetry (gitlab #953). Turns the subjective "re-reads feel unreliable" signal into data: every event that drops a fully-delivered cache entry — forcing the next read to re-send the whole file instead of the cheap [unchanged] stub — increments a process-global counter grouped by cause (compaction, idle, eviction, conversation), surfaced as a re-deliveries forced: line in ctx_cache status. The counters live only in that diagnostic, never in a cacheable tool-output body, so output determinism (#498) is preserved. Pure measurement — no behavioral change.
  • Persistent, conversation-scoped [unchanged] stub index — survives daemon restarts and idle clears (gitlab #955). The in-memory read cache is wiped on every daemon restart and emptied by the idle-TTL clear, so until now the first unchanged re-read afterwards re-delivered the whole file — the single biggest remaining source of the "re-reads aren't reliable" feeling. A new focused module core::read_stub_index persists the minimal bookkeeping needed to emit the ~13-token stub — {path, md5, mtime, line_count, file_ref, delivered_conversation}, never the content — to {data_dir}/read_cache/stub_index.json (atomic tmp+rename, LRU-capped at 1024 records). It is write-through on every full delivery, flushed on the batch/idle/shutdown save cadence, and rehydrated at startup, so a re-read of an unchanged file in the same conversation now collapses to the stub even across a restart. Correctness is gated harder than the warm path: a cold stub (no live entry) is served only when the file's mtime and md5 still match disk and the current conversation equals the delivering one (conversation::conversation_allows_cold_stub — no "no-context → legacy" escape, because across a process boundary an unknown conversation cannot prove the content is in context; this keeps #954's cross-chat hazard closed). A host compaction drops the whole index synchronously (the conversation's context was summarised away), mirroring SessionCache::reset_delivery_flags. Content is always re-read from disk — only delivery bookkeeping persists — so tool-output determinism (#498) is untouched. Side benefit: because the index outlives the idle clear, same-conversation re-reads after idle no longer re-deliver either. Kill-switch LEAN_CTX_STUB_PERSIST=0.
  • Deterministic JSON crusher core — core::json_crush (gitlab #934/#935, Headroom "Smart Crusher" port). Real JSON payloads (API responses, kubectl get -o json, DB dumps, RAG chunks) are dominated by arrays of objects that repeat the same keys and values on every row. The new single-source module factors that redundancy out: crush_lossless hoists every key present in all items of an array to its dominant value (a _defaults block) and keeps only per-item deviations, so it is exactly reconstructible via reconstruct; crush_lossy additionally records near-unique high-entropy columns (timestamps/UUIDs) in _dropped for out-of-band CCR recovery. Output is a pure function of the input Value — no timestamps, counters, randomness, or hash-map order leakage (candidate keys walk a BTreeSet, value frequencies a BTreeMap) — and it never inflates (a no-op returns None). This is the deterministic, byte-stable answer to Headroom's statistical crusher (#498).
  • Opt-in lossless JSON crushing for verbatim data commands (gitlab #936). A new crush_verbatim_json config key (env LEAN_CTX_CRUSH_VERBATIM_JSON, default off) lets the array-heavy JSON of otherwise byte-verbatim data commands (gh api, jq, kubectl get -o json, curl JSON) flow through the lossless crusher when it at least halves the payload. Off by default keeps those outputs verbatim; on, they are reshaped into a compact, fully reconstructible form and never lose a datum. The gate is a pure, unit-tested function and only ever touches Verbatim data commands — Passthrough (auth flows, dev servers, streaming) is never reshaped.
  • Active prompt-cache breakpoint injection for Anthropic (gitlab #939, Headroom "cache aligner" adjacent). A new opt-in cache_breakpoint proxy config key (env LEAN_CTX_PROXY_CACHE_BREAKPOINT, default off) makes the proxy add a single cache_control: {type:"ephemeral"} breakpoint to the system field of Anthropic requests only when the client set none of its own — so a raw API client's large, stable system prompt bills later turns at the cached rate instead of full price every turn (the cache win it left on the table). It is Anthropic-only by construction: OpenAI and Gemini cache prefixes automatically and ignore the marker, so those paths stay byte-unchanged. The injection is deterministic (a pure function of the body, so the prefix it creates is itself byte-stable, #498), never adds a second breakpoint (it defers to any client cache_control and to a client-cached message prefix), and is skipped below Anthropic's minimum cacheable size so it never churns bytes for no cache. It runs even on an otherwise meter-only/byte-passthrough proxy (the one sanctioned mutation), and every injection is counted on a dedicated breakpoints_injected gauge in /status cache_safety — a pure win signal, never against the cache-safe ratio.
  • Cache-aligner volatile-field telemetry (gitlab #940, Headroom "cache aligner" stage 1, telemetry-first). A single volatile token in an otherwise-stable system prompt — today's date, a fresh UUID, a git SHA — shifts the prefix bytes and busts the provider cache on every turn. A new opt-in cache_aligner proxy config key (env LEAN_CTX_PROXY_CACHE_ALIGNER, default off) makes the proxy scan each unanchored Anthropic system prompt for those fields and report how many it found on /status cache_safety (volatile_system_requests, volatile_fields_detected), so a user can quantify how much prompt-cache their prompt leaks. The scan is measurement only — the request body is never mutated, so it stays strictly cache-safe — and deterministic (matches are collected, sorted, and overlapping spans merged, so a full timestamp counts once). This is the honest precursor to an opt-in tail-relocate, which is deliberately deferred until the data shows it pays.
  • Retrieve-coupled CCR learning (gitlab #941, Headroom CCR "learning" port). When an agent keeps pulling back originals the inline compressed form dropped, that is direct evidence the compression was too aggressive. LoopDetector now tracks ctx_expand/ctx_retrieve re-fetches in a dedicated sliding-window counter (retrieve_count, alongside the existing correction counter), exposed as the ccr_retrieve_rate anomaly metric. The session auto-degrade now reacts to the stronger of the two pressures (correction loops and CCR retrieves) and recovers only when neither fires — so a session that over-retrieves dials compression down to Lite (>=3) then Off (>=5) for itself. The level is server state that feeds future CompressionLevel::effective() decisions, never part of any tool output body, so output determinism (#498) is preserved.
  • Model-free JSON-crush accuracy gate (gitlab #942). A new Condition::JsonCrush arm in the deterministic A/B eval harness (core::eval_ab) routes JSON/JSONL through json_crush instead of whitespace-only compaction, and a committed JSON-QA fixture (a redundant operator roster with one outlier field) plus the gate json_crush_condition_preserves_answer_and_beats_baseline prove — with no live model — that the crush keeps every gold answer while packing it in strictly fewer tokens than the raw baseline. This is the deterministic accuracy floor of the "crushed >= raw" claim, guarding against a future over-aggressive change.
  • Per-upstream proxy compression stats + ChatGPT Codex support (#582). The proxy /status and lean-ctx proxy status now break compression down per upstream — Anthropic, OpenAI, ChatGPT, Gemini — each with its own request / byte / token-saved counters, so you can see exactly where the savings come from. The split is purely additive: the existing top-level totals are unchanged, and an unknown label is still counted in the totals but never misattributed to a bucket. ChatGPT Codex traffic (/backend-api/codex/responses) is recorded under its own ChatGPT label while reusing the OpenAI Responses compression, usage, introspection and holdout paths, and JSON-encoded tool-result envelopes inside Responses output are now compressed/pruned without dropping items or breaking function_call / function_call_output pairing (shrink-only, respects should_protect). The research-prose squeeze cap is tunable via LEAN_CTX_RESEARCH_PROSE_CAP (default 20000). Thanks to community contributor @ousatov-ua.
  • Self-observability + self-curation tooling (gitlab #959#964). A cluster of measurement-first additions that let lean-ctx report on — and tune — its own context footprint: a doctor injected-context linter plus a budget-gated per-session overhead report (#960/#964); a health per-tool value signal that recommends disabling tools that never earn their tokens (#961); knowledge-decay pruning and an ACTIVE-SESSION token budget so the injected session block stays bounded (#962); a shadow-minimal rules block that trims re-teaching (#963); and a deterministic footprint delta-eval harness for injected context (#959). All are diagnostic/state-only — no tool-output body changes — so output determinism (#498) is preserved.

Changed

  • json_schema::compress is now crush-backed (gitlab #936). The generic JSON fallback (and the jq route) prefers the lossless json_crush form over the value-dropping schema outline whenever the array is redundant enough to at least halve the payload — keeping every datum reconstructible instead of collapsing it to a structure-only sketch. Heterogeneous or low-redundancy arrays still fall through to the compact schema outline (unchanged), so there is no regression for those. curl's top-level array-of-objects path now defers to the same shared core instead of its useless [object(NK); N] summary, converging the generic JSON handling on one implementation (docker inspect and the aws resource summarizers stay intentionally domain-specific). PATTERN_ENGINE_VERSION is bumped (1→2) so determinism consumers detect the new output shape.
  • ctx_read aggressive mode compacts JSON structurally (gitlab #936). Reading a .json file in aggressive mode (the auto-resolved mode for large non-code data files) now routes redundant array-of-object payloads through the lossless json_crush core instead of generic text pruning, which mangles JSON structure. It fires only when the crush at least halves the file and shrinks the token count; the exact bytes stay recoverable with a full/raw re-read. map mode stays a compact structural overview (unchanged). The "must at least halve" gate is centralized in json_crush::{crush_value_if_beneficial, crush_text_if_beneficial} (one KEEP_DATA_DIVISOR), so the shell (json_schema, curl) and read paths can never drift.
  • Unified, surgical CCR retrieve path across the whole tee store (gitlab #938). ctx_expand now resolves every content-addressed original through one resolver with a fixed precedence: proxy prune/live stubs (proxy_<hash>), the JSON crusher's lossy originals (json_<hash>), AND every compressed shell command's already-teed verbatim output (<slug>_<8hex>.log) — before the reference (ref_) and archive (hex) stores. So an agent can pull back just the slice it needs (head/tail/search/json_path/range) from any of them instead of re-reading the whole file; the high-compression shell footer now advertises the ctx_expand slice form. The resolver trusts only the file name and always rebuilds the path under {state}/tee/ (no traversal). Opt-in verbatim JSON crushing (crush_verbatim_json) gains a lossy stage 2: when the lossless reshape does not pay, it drops near-unique high-entropy columns (timestamps, UUIDs) and persists the verbatim original under json_<hash>, embedding a content-addressed ctx_expand handle so a dropped datum is never irrecoverable.
  • ctx_search absorbs ctx_semantic_search and ctx_symbol (#509). Search collapses to a single action-routed ctx_search: an action argument (regex default, semantic, symbol, reindex, find_related) routes to the same engines as before, and a missing action is inferred so existing calls keep working. The two former tools become deprecated aliases — hidden from tools/list but still callable for one release — which trims the advertised surface (Standard 17→15 tools, Minimal 6→5) so a model picks the right search on the first try. Underlying search behavior is unchanged; this is the final step of the #509 read/search consolidation begun in 3.8.12/3.8.13.
  • Parallel BM25 index build and incremental rebuild (gitlab #933, #581). The full index build now tokenizes across a rayon pool and merges deterministically (#933); the edit-loop incremental rebuild — changed/new/removed files on a warm index — does the same (#581). Both paths are byte-for-byte identical to the sequential result (covered by determinism tests and a CI build-time regression gate), so first-index and reindex-after-edit are faster with no change to what search returns. Credit to the #581 reference work by @ousatov-ua.
  • Generated dependency lockfiles are excluded from the index (#585). npm/pnpm lockfiles (package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml) carry ingestible .json/.yaml extensions and used to slip into the index, where a retrieval surface (ctx_compose, BM25 search) would inline a large auto-generated dependency pin — a pure token sink. They are now dropped at the ingestion front-door via a new non-ingestible IngestKind::Generated, joining the *.lock/*.lockb files already excluded there (the scattered "lock" extension check is removed so detection lives in one place). Detection is by file name, so it is depth-independent — a monorepo's frontend/package-lock.json is caught too, unlike a root-anchored ignore glob. An explicit ctx_read/ctx_tree/ctx_glob of a lockfile is unaffected.

Fixed

  • CI on main was red on all three Test jobs — a stale source-grep test (gitlab #957). scenario_server_degrade_thresholds asserted the dispatch source literally contains("correction_count >= 5") etc.; the #941 retrieve-coupled refactor renamed that to pressure = correction_count .max(retrieve_count), so the literals vanished and the assertion failed on every platform (the rest of CI stayed green). Replaced the brittle grep with a behavioral test backed by a new pure, total CompressionLevel::degrade_action (Set/Clear/Leave) extracted from the dispatch — runtime behavior is unchanged (5+ → Off, 3+ → Lite, 0 → clear, 12 → hold), but the threshold table is now unit-tested and immune to internal renames.
  • Subagents force-freshed every read, so re-reads were never cached inside a Task (gitlab #956, closes the #952 series). is_subagent_context() set effective_fresh = fresh || subagent, a blanket cold full read for the whole subagent run — safe (a subagent must not be served a stub for content only the parent received) but it threw away exactly the cheap [unchanged] re-read that #946/#954/#955 reclaimed. Now that the stub is conversation-scoped, the safety is enforced precisely instead of by bypass: a subagent runs under its own task:{CURSOR_TASK_ID} scope (conversation::current_conversation_id), so the stub gate withholds any stub the parent or a sibling delivered (distinct, non-None scope → never matches), while the subagent's own re-reads of an unchanged file collapse to the stub. The blanket force-fresh now applies only when scoping is off (LEAN_CTX_CONVERSATION_SCOPE=0); an explicit LEAN_CTX_FORCE_FRESH=1 still always forces fresh. Stubs stay double-gated (mtime+md5 vs disk and conversation match), so a subagent is only ever stubbed for a file it read itself, unchanged — never stale, never cross-agent.
  • auto-mode re-reads bypassed the [unchanged] cache stub and re-delivered the whole file (gitlab #946). The cheap ~13-token re-read stub (Fref=path [unchanged NL]) only fired for an explicit mode=full re-read; in the default auto mode a re-read of an unchanged, already fully-delivered file re-sent the entire body — the "re-reads aren't cached / reliability is worse than before" regression. Cause: ctx_read resolved auto with cache: None, so the resolver's unit-tested unchanged + full_delivered → ("full","cache_hit") short-circuit was dead code on the real read path (a silent divergence from ctx_smart_read, which threaded the cache correctly; introduced by the #683 deterministic cascade). resolve_auto_mode is now cache-aware, the warm path routes an autofull cache-hit through the same try_stub_hit_readonly stub as an explicit full re-read, and the registered read-lock fast path accepts auto too (self-guarded by the stub). Compressed- first files still serve their cached compressed output on re-read — no wrong escalation to full. Regression test auto_reread_of_fully_delivered_file_serves_unchanged_stub.
  • The [unchanged] re-read stub was not conversation-scoped — a file delivered in one chat could be stubbed for a re-read in another (gitlab #954). The read SessionCache is shared across every chat served by one daemon, but the stub asserts "you already have this in context" — true only within the conversation that received the full content. A re-read from a different chat on the same daemon could therefore receive Fref=path [unchanged NL] for content it never saw (the idle-TTL clear only incidentally masked it). Each entry now records the delivered_conversation (resolved from the live Cursor conversation_id that hooks write to active_transcript.json), and try_stub_hit_readonly serves the stub only when the current conversation matches; a mismatch re-delivers in full and is counted by the new re-delivery telemetry (#953). With no conversation context (hooks absent) it falls back to the legacy process-scoped behavior, so single-chat hit rates are unchanged and byte-stable (#498). The conversation gate is a pure, unit-tested function (conversation::conversation_allows_stub) injected into the stub path for deterministic, host-independent tests. Kill-switch LEAN_CTX_CONVERSATION_SCOPE=0.
  • ctx_impact missed Go and Kotlin same-package blast radius (#398 bug class). The C#/Java fix in 3.8.13 closed one instance of a general gap: any language with implicit same-package visibility references project types with no import, so import edges alone leave the consumed type a false-negative leaf. For Go the miss was total — same-package is same-directory and fully import-free, so changing a struct used by a sibling file reported "no impact". core::type_ref_edges now resolves Go usages directory-scoped and strict (a common name like Config/Server declared in many packages still resolves to the one true same-package definer, with no cross-package leak) and Kotlin usages by declared package, both durable through the graph_index mirror and emitted by the ctx_impact builder. The old coarse Go package heuristic — one arbitrary same-directory edge per file, silently parsed as a top-weight imports edge in the mirror — is removed: it both missed the real consumer and pulled non-consumers (e.g. an unrelated logger.go) into the blast radius. Precise type_ref edges replace it, and a genuinely unused file now falls to the standard low-weight sibling rescue like every other language. Per-language scope is centralized in one resolve_scope (previously the namespace logic was duplicated across three call sites). GRAPH_ENGINE_VERSION is bumped (3→4) so stale graphs self-heal. (gitlab #920#924)
  • Project-root resolution unified for search and the MCP path jail (#580, #948). An index built at the git root but searched from a sub-directory resolved to a different namespace hash and returned zero hits; separately, an MCP server launched from an agent-config directory (.copilot / .cursor / .windsurf / .gemini) adopted that directory as the project root and then rejected in-tree reads with "path escapes project root". A single git-promotion resolver is now the one source of truth for the root, an explicit sub-directory becomes a result filter rather than its own namespace, and an agent-config CWD auto-reroots to the real project. PathJail enforcement is unchanged — only root derivation is corrected. Adopted from reference PR #581 by @ousatov-ua.
  • lean-ctx call ctx_tools … panicked on the CLI call path (#583). Invoking the ctx_tools meta-tool from the CLI crashed with "there is no reactor running" because the runtime was resolved via Handle::current(), which only exists on the MCP path (handlers there run inside block_in_place). It now uses Handle::try_current(): the ambient handle is reused on the MCP path and a one-shot runtime is built on the CLI path. Pure control-flow fix — MCP behavior and output bytes are unchanged.
  • ctx_shell could silently drop output when a child held the pipe open (gitlab #945). A process that kept the write end of the pipe open past its own exit truncated the captured output; the reader now drains to EOF so the full output is compressed and returned.
  • lean-ctx update failed with UnknownIssuer behind TLS-inspecting proxies (#578). The updater now validates TLS against the OS trust store via ureq's PlatformVerifier, so corporate roots installed in the system keychain/store are honored.
  • gain --deep reported "Daemon: offline" on Windows while the daemon was running (#576). The footer's daemon-status probe used a Unix-only check; it now reports the daemon state correctly on Windows too.

[3.8.13] — 2026-06-26

Added

  • ctx_explore — delegated, deterministic repository exploration (gitlab #907). A new multi-turn explorer — MCP tool #78 plus a lean-ctx explore CLI — that answers "where does X live / how does Y work" in a single call instead of the agent's usual read→grep→read loop. It seeds with BM25 lexical retrieval, expands along a bounded graph BFS grounded in the hit set, then selects citations by coverage, returning byte-stable path:start-end ranges (with a citation-only mode for minimal token spend). Wired into the tool registry, the standard and read-only tool profiles, and the heavy-index warm-need; eval_harness gains a SearchArm::Explore (output-token metric plus new "exploration" queries in rust/eval/search-suite.ndjson) so A/B runs can compare explore vs hybrid vs bm25 on recall/MRR/tokens. Output is a deterministic function of repo content (#498).
  • Codex ChatGPT subscription auth now routes through the proxy (#568). Completes the #554 fix: instead of skipping config when a Codex ChatGPT login is detected (which left subscription users at 0 savings), install_codex_env now writes a mode-specific config. ChatGPT subscription auth is pointed at the proxy's Codex backend rail (model_provider = leanctx-chatgpt, openai_base_url = …/backend-api/codex, chatgpt_base_url = …/backend-api for aux calls such as the codex_apps streamable-HTTP MCP endpoint); API-key Codex keeps the /v1 path. The proxy gained /backend-api/codex/responses (compressed/metered via the OpenAI Responses path to chatgpt.com) plus credential-preserving passthrough for non-model /backend-api/* traffic. Header forwarding stays allowlist-based both ways; a dedicated cookie store persists only Cloudflare anti-bot cookies (cf_clearance/__cf_bm/cf_chl_*) and drops auth/session cookies; gzip/zstd request bodies are decoded under a bounded reader (zip-bomb safe) before compression and re-encoded. Thanks @ousatov-ua.
  • lean-ctx doctor warns when the MCP server is launched from a directory without a project root (#547). When an MCP client spawns lean-ctx from an IDE/agent config dir (.lmstudio, .claude, .codex, .codebuddy) or any marker-less CWD, every out-of-tree ctx_read fails with "path escapes project root". The new MCP server CWD doctor check (also surfaced in the structured health report) explains the cause and the fix (cwd in the client config, or allow_auto_reroot/extra_roots); .lmstudio is now also treated as a suspicious persisted root. Thanks @albinekb.
  • Shadow-mode CLI reads/searches now record Context IR lineage (#566). Follow-up to #550. The MCP dispatcher records a Context IR provenance entry for every tool call (server/call_tool.rs), but the shadow-mode hook's single-shot lean-ctx read/grep subprocess dropped it — so ctx_proof and IR exports were blind to compressed shadow reads. record_file_read/record_search now thread the rendered-output excerpt + measured tool duration into a disk-backed ContextIrV1 load→record→save, mirroring the MCP path (same 200-char char-boundary excerpt bound; mode/pattern ride the IR pattern slot; the read's original_tokens and the search's raw matched-line estimate are the IR input so the stored compression ratio is accurate — no fabricated values). The two remaining MCP read side effects from #550 — the in-memory loop/correction detectors and the bounce/adaptive-threshold signals — are now delivered via connect-only daemon routing: when an MCP daemon is already running, a shadow-mode lean-ctx read/grep routes the call through it (/v1/tools/callcall_tool_guarded), so loop detection, correction-loop auto-degrade, bounce tracking and adaptive thresholds all fire on the daemon's long-lived state — full MCP parity, for free. The hook child connects only: it reuses a live daemon but never auto-starts one (a per-call subprocess auto-starting daemons would proliferate them, the #453 class of bug), falling back to the enriched standalone path (disk-backed learning sinks + the Context IR above) when no daemon is reachable or on Windows. This resolves the design decision #566 was gated on; the connect-only invariant is documented in daemon_client::try_daemon_tool_call_blocking and regression-guarded by hook_connect_only_566.
  • PowerShell-native cmdlets route through lean-ctx (#561). Follow-up to #556: shadow/harden mode already recognised the Windows powershell shell tool, covering the Unix-style PS aliases (cat/ls/rg). The command-rewrite layer now also maps the PowerShell-native cmdlets and their short aliases — Get-Content/gclean-ctx read (honoring -Path, -TotalCount/-Head/-First and -Tail/-Last), Select-String/slslean-ctx grep (-Pattern, -Path), and Get-ChildItem/gcilean-ctx ls (-Path). Parameter names are matched case-insensitively; anything with an unrecognised flag, a pipeline, multiple operands, or an out-of-project path passes through untouched (same conservative contract as the Unix rewrites), so determinism and redaction guarantees are inherited. The PowerShell cmdlets are detected only in the rewrite path and are deliberately kept out of the POSIX shell-alias surface.
  • Addon security hardening — trust, policy, signing, sandbox, audit (#863). Because an addon is executable trust (a stdio addon runs code on your machine; an http addon receives your context; its output enters the model), the ecosystem ships with defense-in-depth across three tiers:
    • Trust tier + risk review. A registry-controlled addon.verified flag splits the catalog into verified (maintainer-audited) and community, shown in addon list/info and the install preview. core::addons::trust::assess statically reviews the [mcp] wiring (remote endpoint, non-HTTPS, inline shell, fetch-and-exec, unpinned upstream, secret-bearing env) at info/warn/ danger severity. The same logic backs a registry CI validator (registry::validate_entries, run by cargo test): unique slugs, required provenance for installable entries, no shell/fetch/non-HTTPS/unpinned wiring, and zero findings for verified entries.
    • Install policy floor — [addons]. A global-only config block (never merged from a project-local file): policy (open/verified_only/ allowlist/locked), allowlist, require_signature, sandbox, block_risky. policy::gate enforces it in install before any gateway mutation. Fully permissive by default; distribute via MDM or pin through the signed org-policy floor.
    • Registry signing. A user-override registry can shadow trusted names; with require_signature = true it is honoured only if a sidecar addon_registry.json.sig carries a valid Ed25519 signature by a trusted org key (same anchor as policy org trust).
    • Opt-in OS sandbox. addons.sandbox = auto|strict wraps spawned stdio servers in sandbox-exec (macOS) / bwrap (Linux) at the single spawn point — outbound-network isolation in auto, read-only fs + refuse-if-no-launcher in strict. Off by default.
    • Runtime redaction + audit. Downstream tool output is run through the shell-layer secret redaction and audit-tagged as untrusted before it reaches the model (runtime::scrub_output). New small, unit-tested modules core::addons::{trust,policy,signing,sandbox, runtime}; binding registry-review checklist in CONTRIBUTING.md.

Changed

  • Leaderboard — no top-50 cap, real pagination, everyone findable. The community leaderboard previously truncated to the top 50 accounts, so most contributors never appeared and the headline community energy could silently drop when the cut-off shifted. GET /api/leaderboard now paginates (?page, ?per_page, default 50 / max 200) and supports case-insensitive name search (?q=), while two new fields — total_tokens_saved and total_cost_avoided_usd — report the uncapped community totals across all opted-in accounts, independent of the displayed page or any filter. The server-rendered /leaderboard page and the website /metrics page gained matching search + pagination controls; the landing-page hero energy stat and the in-app cockpit now read the uncapped totals so headline numbers stay stable. Global ranks are preserved across pages. Pagination, ranking, totals and search are pure, unit-tested functions (paginate, all_ranked_cards). (gitlab #868#871)

Fixed

  • ctx_impact dropped C# same-namespace blast radius after the first reindex (#398). A C# class used within its own namespace (no using, DI-injected) reappeared as a leaf node after the first background reindex. The private ctx_impact builder wrote precise type_ref edges into the PropertyGraph, but every ProjectIndex::save() mirrors graph_index over the graph via clear_code_graph() — and graph_index emitted no type-usage edges, so the reindex silently wiped the blast radius (a dual-writer bug). A new core::type_ref_edges module is now the single source of truth for C#/Java consumer→definer file resolution (namespace-aware, failsafe-capped), shared by both the durable graph_index mirror and the ctx_impact builder; graph_index now emits these precise edges instead of the old coarse alphabetical namespace-chain heuristic, so a reindex reproduces the blast radius instead of dropping it. GRAPH_ENGINE_VERSION is bumped (2→3) so stale graphs self-heal on the next query, and the regression tests now run through the index mirror — the exact gap every prior #398 fix missed. (The grep hook also now redirects only output_mode=content, passing files_with_matches/count through untouched, since the path-swap returned wrong results for those.) (gitlab #915#918)
  • ctx_read left an empty [] metadata field on incompressible files (#509). The entropy (and density) read modes append a [techniques…] tag listing which compression techniques fired. On a file where none did (high-entropy, no duplicate blocks) the technique list is empty, so the header rendered a bare H̄=4.2 [] — the same empty-trailing-field waste fixed for ctx_semantic_search's (rrf: X, ) in #511. A techniques_tag helper now omits the bracket segment entirely when the list is empty (and keeps the leading space
    • [a, b] form otherwise), so the header is clean on both paths. (#509-A output audit; output stays a deterministic function of content/mode per #498.)
  • Pi AGENTS.md advertised renamed tools that no longer exist (#548). The Pi installer writes a curated static templates/PI_AGENTS.md, and its tool-mapping table still listed ctx_grep/ctx_find/ctx_ls — tools renamed long ago to ctx_search/ctx_glob/ctx_tree. Pi agents that followed the table issued unknown-tool calls. The template (and the matching pi.rs setup hint) now use the canonical names, and a new parity test (tests/rules_template_tool_names.rs) ties every shipped agent template to the live MCP registry: any ctx_* reference that is not a registered tool fails the build, so a future rename can never drift silently again. (First slice of the #548 agent-rules unification — marker/dedup consolidation, content-aware freshness, and rules.tomlsync semantics follow.)
  • Rule injection skipped content/compression changes when the version was unchanged (#548). The injector's freshness check was version-only: it compared the on-disk <!-- version: N --> against RULES_VERSION and skipped the rewrite when they matched. So a change that alters the rendered body without bumping the version — toggling shadow_mode, switching compression_level, or editing a canonical section between releases — left every agent's rules block stale until the next version bump. RulesFile::block_matches_render now compares the on-disk block byte-for-byte (whitespace-insensitive) against a fresh render for the active parameters, and the skip path requires both is_current() and that content match. Re-running sync/inject after a compression-level change now regenerates the block as expected; an unchanged config stays idempotent. (Second slice of the #548 agent-rules unification, after the Pi-template parity guard.)
  • rules diff/sync.lean-ctx/rules.toml semantics, and a rules diff false-positive (#548). Two coupled fixes for the rules-governance commands:
    • sync/diff do not consume rules.toml — now documented and decoupled. rules sync/diff regenerate from the canonical rules_canonical source of truth (preserving user text around the markers) and never read rules.toml, which is the input for rules lint plus a user-editable inventory from rules init. This is now stated in the rules help, the init next-steps, and the RulesConfig/sync docs. detect_drift no longer loads RulesConfig at all, so rules diff works without first running rules init (it previously failed with "No rules config found") — the dead _config parameter is gone and the command is infallible.
    • rules diff reported phantom drift after every sync. Drift picked the shared-vs-dedicated expected block from a content heuristic ("up_to_date and no 'existing user rules'"), which misread freshly synced shared files with no user text (Copilot CLI, Codex CLI, Gemini/OpenCode in shared mode) as the dedicated layout and flagged them as DRIFTED on every run. Drift now compares each target against the canonical block for its real RulesFormat via the new rules_inject::expected_blocks_by_target, keeping sync and diff in agreement. Covered by new tests: detect_drift_without_rules_toml_does_not_ require_init and sync_then_diff_reports_no_drift. (Third slice of the #548 agent-rules unification.)
  • Compression block had two disagreeing marker models, so cross-channel dedup never fired (#548). rules_canonical::render embedded the output-style compression prompt inline inside the <!-- lean-ctx-rules --> block with no delimiters, but the coverage/dedup readers (rules_channel, rules dedup) detect the payload by a separate <!-- lean-ctx-compression --><!-- /lean-ctx-compression --> block. Since the writer never emitted those markers, cursor_compression_covered/client_autoloads_compression were always false on freshly written rule files — so the MCP per-session instructions kept repeating the compression block even for Cursor/Codex that already load it from their rule file (double billing), and rules dedup could not thin a render-produced shared AGENTS.md. The two models are now one: the COMPRESSION_BLOCK_* markers live in rules_canonical (single marker source, re-exported from rules_channel), and render wraps the compression prompt in them for the persistent carriers (Dedicated/Shared — every injected rule file). The ephemeral Bare MCP channel stays unmarked by design (its inclusion is governed by carrier coverage, so a per-session marker would be noise). Content-aware freshness (second slice) re-propagates the new block on the next sync without a version bump. Covered by new tests asserting carriers wrap / Bare does not / Off emits nothing, and an end-to-end check that a render-produced Cursor block is now recognised as compression coverage. (Fourth slice of the #548 agent-rules unification — closes the "one canonical carrier/marker model" acceptance criterion.)
  • Shadow-mode hook reads dropped ~75% of the MCP read side effects (#550). When shadow/harden mode intercepts a native view/grep call it spawns lean-ctx read as a single-shot subprocess. That CLI path recorded only a fraction of what the MCP ctx_read pipeline does, and — crucially — never flushed its buffered telemetry before the process exited, so lean-ctx heatmap stayed empty and lean-ctx gain reported nothing for compressed reads. Three fixes:
    • One flush set, no drift. A new tool_lifecycle::flush_all() is the single source of truth for the buffered-telemetry flush (stats, heatmap, path-mode memory, auto-mode resolver, edit-quality, mode predictor, feedback, threshold learning, LiTM calibration). The daemon shutdown, the parent watchdog and every CLI tool arm (read/grep/ls/find/deps/diff/-c/-t) now call it — the hand-rolled per-arm copies had drifted (the read arm flushed only stats), which is exactly how the gap went unnoticed.
    • CLI read learning parity. record_file_read/record_search now run the same disk-backed learning sinks the MCP background thread does — mode-predictor training, the per-language compression feedback outcome, and the per-call anomaly metric — so auto-mode selection, the feedback loop and dashboard signals improve from shadow-mode reads too (not just direct MCP calls).
    • Mode predictor actually persists now. ModePredictor stored its history in a struct-keyed HashMap<FileSignature, _>, which serde_json cannot serialize ("key must be a string") — so mode_stats.json was never written and the predictor relearned from zero every process. The history now serializes as an entry list (round-trip tested). The in-memory-only loop/correction detectors and the bounce/adaptive signals that need routing through ctx_read::handle are tracked as a follow-up (they cannot be honored from a single-shot subprocess without cross-process state).
  • Windows PowerShell profile path hardcoded to ~\Documents — broke under OneDrive redirection (#558). proxy enable and the shell-hook install resolved the PowerShell profile by hardcoding home\Documents\PowerShell\…. Windows OneDrive folder backup (on by default on most installs) redirects Documents to e.g. …\OneDrive\Documents\…, so lean-ctx wrote to a file PowerShell never reads — the active $PROFILE was never updated and the proxy received no traffic in new terminals. A new resolve_powershell_profile_path asks PowerShell itself for $PROFILE.CurrentUserCurrentHost (authoritative under any folder redirection, preferring pwsh then Windows PowerShell, UTF-8 output) and falls back to the documented default only when no PowerShell host can be launched. Non-Windows hosts keep the static ~/.config/powershell path and never spawn a process (#356).
  • Copilot CLI view (read) and rg (search) tool calls passed through uncompressed (#562). handle_redirect dispatched on the tool name but only matched Read/read/ read_file and Grep/grep/search/ripgrep, so two documented GitHub Copilot CLI tool names — view (its read tool) and rg (its search alias) — slipped through without compression in shadow/harden mode. The dispatch is now a tested classify_redirect helper that includes view (→ read) and rg (→ grep); the Claude/Cursor/CodeBuddy matchers are unchanged because those hosts never emit those names and Copilot CLI fires the hook for every tool call.
  • Copilot/VS Code Claude models ignored lean-ctx — no .github/copilot-instructions.md (#555). lean-ctx init --agent copilot installed the MCP server plus a deliberately weak AGENTS.md pointer but never wrote .github/copilot-instructions.md, the repo-level file VS Code Copilot Chat auto-applies to every request. Claude- family models (Sonnet/Opus) therefore ignored the tool mapping while GPT-5.x followed it ~95% of the time. init now writes the strong dedicated ruleset into .github/copilot-instructions.md as an idempotent <!-- lean-ctx-rules --> block (user content is preserved, never clobbered) and pins github.copilot.chat.codeGeneration.useInstructionFiles: true in the project .vscode/settings.json as a safety net (an explicit user value is honoured); uninstall removes the block.
  • Shadow mode ignored glob and Windows powershell tool calls (#556). Shadow/harden mode silently passed two documented Copilot CLI tools straight through: the glob tool ("find files matching patterns") had no arm in the redirect hook, and the powershell shell tool (paired with bash on Windows) was not recognised as a shell, so command rewrites never fired there. handle_redirect now intercepts Glob/glob — warming the shared ctx_glob core via a new lean-ctx glob subcommand and recording the intercept in shadow.log, then letting the native path-list result through — is_shell_tool (now shared by both hook entry points) covers PowerShell/powershell/pwsh, and the Claude/Cursor/CodeBuddy redirect matchers include Glob so the hook fires for it. Copilot CLI already dispatches every tool, so its glob/ powershell calls are covered automatically.
  • Codex proxy never compressed — ChatGPT login bypasses it; the API-key config was a no-op (#554). lean-ctx proxy enable reported success for Codex yet Requests/Compressed/Tokens saved stayed at 0, for two reasons. (1) A Codex ChatGPT login (the default) authenticates via OAuth directly against chatgpt.com/backend-api, so a custom openai_base_url is ignored and the proxy never sees the traffic — the Claude Pro/Max situation, but with no warning. lean-ctx now detects a ChatGPT login (~/.codex/auth.json auth_mode = "chatgpt", overridable by an explicit OPENAI_API_KEY) and prints an honest skip notice pointing at the MCP tools instead of writing dead config. (2) In API-key mode lean-ctx wrote [env] OPENAI_BASE_URL into ~/.codex/config.toml, which Codex does not read; it now writes the documented top-level openai_base_url key (openai/codex#12031), migrates the dead legacy entry, and preserves any custom remote endpoint. Uninstall/cleanup/preview handle both forms.
  • lean-ctx index build-semantic cold-starts the embedding model again (#545). On a machine without the model cached, the build dead-ended with "embedding model not downloaded — auto-download … failed" even though no download was ever attempted: the build path checked is_available() (a pure file-existence check) and bailed before the download could run — a regression from the #519 ORT-teardown guard. build_or_update now downloads the model first via a new EmbeddingEngine::ensure_downloaded() (pure network/file IO, no ORT init) and only loads the ONNX Runtime once the files are present, so the cold bootstrap works again and the #519 teardown safety is preserved. The passive search path is unchanged.
  • Copilot CLI hooks silently no-opped — wrong payload field names and missing modifiedArgs (#551). Copilot CLI sends camelCase toolName + toolArgs (a JSON-encoded string), but the rewrite/redirect/observe handlers only read snake_case tool_name/tool_input/command, so every Copilot tool call passed through unchanged; even once parsed, the preToolUse output omitted Copilot's modifiedArgs field, so rewrites/redirects would never have taken effect. A new hook_handlers::payload resolves the tool name, args (a tool_input object, a toolArgs object, or a toolArgs JSON-string) and command across all handlers, observe gains a Copilot postToolUse branch so its telemetry is recorded instead of dropped, and the hook now emits Copilot's documented permissionDecision + modifiedArgs contract alongside Claude's hookSpecificOutput.updatedInput and Cursor's updated_input in a single response. Snake-case (Claude/Cursor) stays regression-tested. Thanks for the detailed report.
  • CLAUDE.md/CODEBUDDY.md pointer block duplicated on every setup/doctor --fix (#549). The block-detection constants pointed at the wrong marker: *_MD_BLOCK_START/END referenced the canonical rules marker (<!-- lean-ctx-rules -->) while the installer writes the AGENTS pointer block (<!-- lean-ctx -->), so existing.contains(START) was always false — the doctor reported the block as missing and every run appended a fresh copy, accumulating duplicates. The constants now point at AGENTS_BLOCK_START/END (one fix for both the doctor false-negative and the duplication), a new remove_all_blocks() collapses any already-accumulated duplicates back to a single canonical block in the installers and strip_*_md_block, and the doctor fixtures seed the real pointer marker.

[3.8.12] — 2026-06-24

Added

  • Addon ecosystem — lean-ctx addon (#858). A package manager for community extensions: an addon wraps an external MCP server behind a small lean-ctx-addon.toml manifest and plugs into the MCP gateway with one lean-ctx addon add — no fork, no recompile. list / search / info browse a curated registry (bundled rust/data/addon_registry.json, overridable per entry via <data_dir>/addon_registry.json); add resolves a registry name or a local manifest path, discloses the exact transport/command/args/env it will run, then — after confirmation (--yes to skip; refuses non-interactively without it) — wires a [[gateway.servers]] entry via the safe global-only Config::update_global path and records it in <data_dir>/addons/installed.json; remove unwinds exactly what it wired. Registry entries without a runnable [mcp] block are listed (directory + homepage link), never installed with fabricated wiring. Reuses the gateway trust model (global-only, opt-in) and the cli::prompt confirmation gate; no new config section, so schema parity is untouched. Manifest, registry and install logic live in small, unit-tested core::addons::{manifest,registry,store,install} modules. Spec: addon-manifest-v1 · guide: docs/guides/addons.md.
  • Repo-stack-aware profile recommendation — lean-ctx profile suggest (#851). Scans the current repo for deterministic, local signals (languages + source-file count, monorepo layout via pathutil::has_multi_repo_children + workspace markers, build/CI markers, configured LLM providers) and recommends a context profile plus key settings (profile, output_density, proxy.history_mode; proxy.effort is left off — it is never inferred from a repo). Prints the exact export / config set commands to apply it, plus task-oriented alternatives (ci-debug first when CI is detected, then hotfix/bugfix/review). Strictly read-only — it never writes config. --json for scripting. The mapping (core::profile_suggest::suggest) is a pure, unit-tested function separated from the gitignore-aware scan, so the suggestion is a deterministic function of the repo + environment (no network, no telemetry).
  • Review-before-overwrite for consequential CLI writes (#852). State-mutating writes that could clobber existing state now print a before→after diff plus a risk note and require confirmation (or --yes) — mirroring the yolo / secure pattern, and refusing to run non-interactively without --yes. Covers lean-ctx config set for security/egress-relevant keys (path_jail, shell_security, sandbox_level, secret_detection.*, boundary_policy, proxy.*_upstream) and lean-ctx knowledge remember when it would overwrite an existing fact with a materially different value (the prior value is archived). The knowledge gate reuses the exact overwrite predicate the write path applies (check_contradiction), so additive, identical, near-identical (>0.8 similarity) and no-op writes stay frictionless. The shared prompt/confirm helper is now a single cli::prompt module (extracted from security_cmd), and config-key risk classification lives in core::config::risk — deterministic and local-only. The MCP ctx_knowledge tool path is unchanged (agent writes stay versioned and contradiction-warned without an interactive gate).
  • Tool & rule budget — lean-ctx tools health (#848). A deterministic, local-only "rot" report answering whether every always-on token earns its place. Cross-references the fixed cost of each advertised MCP tool schema, the MCP instructions, and every auto-loaded rules file with recorded usage (the post-dispatch cost ledger) to flag: tools that cost schema tokens every session but are never called (unused), heavy-schema tools used for <1% of calls (low-use), rules files that bill the same guidance to a client more than once, and stale knowledge facts (>30d, never retrieved). Reuses existing telemetry and adds no new hot-path cost — per-tool last_used rides the cost-attribution write that already happens. Text (rot candidates only; --all for the full list), --json for scripting, and a Tool Budget panel in the dashboard health view (/api/tools-health). Never auto-applies: every finding is a suggestion (lean-ctx tools lean, lean-ctx rules dedup --apply).
  • Cache-safe cross-provider reasoning-effort control — proxy.effort (#834). One opt-in setting (off | minimal | low | medium | high) pins a single reasoning-effort level across all three providers without breaking the provider prompt cache. lean-ctx translates the constant level to each provider's native parameter — OpenAI reasoning_effort / reasoning.effort, Anthropic output_config.effort, and Gemini thinkingConfig (thinkingLevel on 3.x, thinkingBudget on 2.5 pro/flash) — only on models that accept it and only when the client didn't set its own value. Unlike per-turn "effort routing" (which flips effort between turns and invalidates the cache — OpenAI lists effort changes as a cache-invalidation cause; Anthropic breaks its message-cache breakpoints), the level is a constant, so the cached prefix stays byte-stable (#448/#498) and only the model's reasoning depth changes. Conservative by design: off is a strict no-op, it never overrides a client value, never enables reasoning the client didn't ask for (Anthropic adaptive-only; Gemini skips 2.5 flash-lite and never sends both thinking fields), is model-gated (never turns a working 200 into a 400) and deterministic. lean-ctx proxy status surfaces the active level plus per-provider steer counts. Set via proxy.effort or the LEAN_CTX_PROXY_EFFORT env (env wins).
  • Unified security posture + lean-ctx yolo / secure master switches (#507). Decouples lean-ctx's two independent security planes and makes them discoverable: containment (path jail + shell gating — protects the machine from the agent) vs secret defense (.env/credential redaction — protects secrets from the LLM provider). lean-ctx security status prints a posture board (and a coarse STRICT / RELAXED / OPEN label) reused by lean-ctx doctor, which now also shows a dedicated Secret redaction line. lean-ctx yolo (alias security open) drops containment in one step — writes path_jail = false + shell_security = "off", takes effect immediately, and deliberately keeps secret redaction on; lean-ctx secure (alias security strict / lockdown) restores the secure defaults. The standalone .env switch is lean-ctx security secrets <on|off>. path_jail is now a first-class, schema-documented config key (the blanket "any path" opt-out, equivalent to allow_paths = ["/"]), so granular re-enabling via lean-ctx config set … / lean-ctx allow <cmd> composes cleanly after a yolo. Disabling either plane requires a confirmation (or --yes) and refuses to run non-interactively, so an agent can never silently weaken security.
  • Observation tier — synthesized, recall-prioritized entity summaries (GL #802). A 9th cognition-loop step distils clusters of related facts into compact, per-entity observations (Hindsight-inspired). Synthesis is deterministic by default — facts are grouped by an entity anchor (file path in key/value, else category) and each cluster of ≥ cognition_synthesis_min_cluster (default 3) facts is written through the normal remember() path, so versioning, persistence and idempotency come for free and the value stays byte-stable (#498). An optional LLM refinement sits behind llm.enabled with the deterministic digest as fallback. Recall gives a balanced boost to relevant synthesized observations (above incidental matches, below an exact key hit). Facts are now epistemically typed on write (evidence vs. inference) via infer_from_category, feeding salience and — opt-in via archetype_aware_decay — slower decay for structural evidence. Gated by cognition_loop_max_steps >= 9 (the new default; set 8 to disable); visible as observation_synthesis in lean-ctx introspect cognition.
  • Configurable shell-security mode — enforce | warn | off (GL #788). One switch now governs all command gating (the allowlist and the hard blocks: eval/exec/source, $()/backticks at command position, interpreter -c), applied at a single chokepoint so MCP ctx_shell and the CLI (lean-ctx -c/-t) behave identically. enforce stays the secure default; warn runs every check but only logs violations; off is a deliberate opt-out that skips gating entirely while compression stays fully active. Set via shell_security in config or the LEAN_CTX_SHELL_SECURITY env (env wins; unknown values fall back to enforce, never fail open). off does not lift the read-only-output doctrine (no >/tee/heredoc writes via shell). lean-ctx doctor surfaces the active mode whenever it is not enforce. Supersedes the CLI-only LEAN_CTX_ALLOWLIST_WARN_ONLY (kept for backward compatibility).
  • /v1 contract clients published under one name — lean-ctx-client. The thin, engine-independent clients now ship on every registry under a single consistent name: PyPI (import module stays leanctx), npm, and crates.io. Replaces docs that pointed at an unrelated third-party leanctx / unpublished @leanctx/sdk (GL #783). A dedicated, idempotent publish-clients.yml workflow ships the family independently of the engine.
  • Cognition v2 — science-grounded context engineering, deterministic by default, provably active. Ten neuroscience/physics-motivated mechanisms are wired to real hot-path call sites and made inspectable via lean-ctx introspect cognition (each subsystem reports wired/active/last-run/count; also surfaced in lean-ctx doctor). All decision layers are deterministic by default (Rule #498 / prompt cache intact); stochastic exploration is gated behind LEAN_CTX_STOCHASTIC.
    • Time-variant Φ (attention). Context salience is recomputed and EMA-blended on every re-read instead of being frozen on first sight (context_ledger).
    • Ebbinghaus forgetting + spacing effect. Knowledge confidence decays as R = exp(-Δt/S) with stability S growing per retrieval, replacing linear decay. Configurable via forgetting_model (ebbinghaus|linear), base_stability_days, and LEAN_CTX_LIFECYCLE_FORGETTING (memory_lifecycle).
    • Hebbian eviction. Co-accessed cache entries protect each other from eviction ("fire together, wire together") via a deterministic association bonus (cache, hebbian_cache).
    • Complementary-learning-systems consolidation. Idle/loop replay lifts the confidence of related, frequently-retrieved facts (cognition_loop).
    • Integration-aware Φ (IIT non-redundancy / MMR). The context compiler now selects via greedy Maximal-Marginal-Relevance and deduplicates on content (fixes a bug that compared file paths), so near-duplicate items collapse to one (context_compiler, context_field).
    • Global-workspace ignition. High-salience Φ-outliers (z-score > θ, default LEAN_CTX_GWT_IGNITION_Z) are broadcast/pinned and resist reinjection downgrades (context_ledger, context_gate).
    • Learned field weights (bandit). Φ field weights are chosen by a Thompson bandit — deterministic argmax-of-posterior-mean by default, sampling only under LEAN_CTX_STOCHASTIC (bandit, context_field, adaptive_thresholds).
    • Sharp-wave-ripple idle replay. A quiet gap (default 300 s, LEAN_CTX_COGNITION_IDLE_SECS) triggers a deeper replay-consolidation pass in the background (cognition_scheduler, cognition_loop).
    • FEP prefetch (active inference). After a read, likely-next files from the co-access graph are surfaced as a deterministic warmup hint — never an automatic read (fep_prefetch, context_gate).
    • Immune detector (artificial immune system). External provider data is screened for prompt-injection/poisoning before it can become a fact, edge or cache entry; untrusted workspaces get a stricter screen (coupled to Workspace Trust) (immune_detector, consolidation, ctx_provider).
  • lean-ctx introspect cognition / introspect qubo. New CLI to prove which cognition subsystems are wired and active, and to run the experimental QUBO-vs-greedy selection benchmark.
  • QUBO selection spike (research only). A deterministic simulated-annealing QUBO solver and benchmark harness for redundancy-aware context selection, gated behind LEAN_CTX_EXPERIMENTAL_QUBO. On clean problems it reaches parity with the greedy knapsack (no measurable win), so greedy remains the default; promotion is conditional on a future measurable gain (qubo_select).
  • Opt-in debug log — LEAN_CTX_DEBUG_LOG / lean-ctx debug-log (#520). A human-readable, off-by-default trace of every MCP tool call (tool, arguments, outcome) and every shell-hook routing decision (compress / track / pass-through and why), for diagnosing "why did lean-ctx do X?" without attaching a debugger. Enable via the LEAN_CTX_DEBUG_LOG env (truthy) or lean-ctx config set debug_log true; read or clear it with lean-ctx debug-log (--clear). Writes to a single rolling file under the state dir; never on the hot path when disabled, and the body carries no secrets (arguments are redaction-screened).
  • In-band remote-proxy expansion marker — <lc_expand:HASH> (#493). Lets the cold-prefix/CCR retrieval layer work through a remote proxy with no shared filesystem: the model can emit a <lc_expand:HASH> marker in its output and the proxy splices the referenced content back in band, across all three providers (OpenAI chat + responses, Anthropic, Gemini). Opt-in and cache-safe by construction (the marker is deterministic), follow-up to #482.

Security

  • Shell allowlist now enforced on the -t / track path (external audit, finding 1). exec_argv (used by the default shell hook _lc() { lean-ctx -t "$@" } for multi-arg commands) never called check_shell_allowlist, so every aliased invocation like _lc git status bypassed the restriction that lean-ctx -c enforces. Both paths now share a single allowlist_gate, so the track path blocks non-allowlisted commands (exit 126) exactly like the compress path.
  • Agent API keys are no longer captured or forwarded to ctx_shell children (external audit, finding 2). The agent-runtime-env bridge forwarded every CODEX_*/CLAUDE_*/OPENCODE_*/GEMINI_*… var — including *_API_KEY, *_TOKEN, *_SECRET, *_PASSWORD — into the env of every command the agent ran, where output redaction can't stop network exfiltration. is_forwardable now excludes credential-shaped names (only session/thread identifiers cross the bridge), and load retroactively scrubs such vars from any capture file written by an older build, removing the plaintext secret at rest.
  • Path-jail relaxations are now surfaced loudly (external audit, finding 3). path_jail = false, the no-jail build feature and the env channels (LEAN_CTX_ALLOW_PATH, LEAN_CTX_EXTRA_ROOTS, LEAN_CTX_ALLOW_IDE_DIRS) that widen or disable the jail are inherited from the IDE/launchd env and previously loosened the boundary with no in-band signal. The MCP and HTTP servers now emit a [SECURITY] warning at startup for each active relaxation, and lean-ctx doctor reports env-channel relaxations alongside the config-level ones.
  • Workspace Trust for project-local .lean-ctx.toml overrides (external audit, finding 4). A cloned repo's .lean-ctx.toml is merged over the global config and could raise security-sensitive settings — replace the shell allowlist, widen the path jail (allow_paths/extra_roots), repoint the proxy upstream, define command aliases, change rules_scope/rules_injection. For an untrusted workspace those overrides are now withheld (comfort knobs like compression/theme still apply) with a [SECURITY] warning; lean-ctx doctor shows the state. Grant trust with lean-ctx trust (and lean-ctx untrust / lean-ctx trust status / --list); trust is pinned to the workspace path and a content hash of .lean-ctx.toml, so editing the file re-gates it. Headless use can opt in via LEAN_CTX_TRUST_WORKSPACE=1 or LEAN_CTX_TRUSTED_ROOTS.

Changed

  • Change-aware pre-push gate + no-test advisory (#850/#849). scripts/preflight.sh now classifies the diff against origin/main: a docs-only push (README, CHANGELOG, *.md, website, scripts) skips the Rust gates (fmt/clippy/rustdoc/Windows cross-compile) and the pre-push hook finishes in ~0.1 s instead of ~140 s. gen_docs --check still runs whenever Rust or a committed file under docs/reference/generated/** changed. CI is unchanged and remains the source of truth (a docs-only diff cannot turn a Rust gate red, so the local skip can never cause a local-green / CI-red split); make preflight forces the full gate. A change to contract code (proxy/, tools/, config/schema/) with no test signal in the diff prints a no-test advisory — blocking under LEAN_CTX_PREFLIGHT_STRICT_TESTS=1.
  • Faster semantic search on a native ONNX Runtime (#497). The embedding/index stack moves from the pure-Rust rten backend to native ort (ONNX Runtime 2.0), with a rebuilt indexing pipeline (int8-quantized vectors, tighter HNSW, a compact postcard on-disk format). ONNX Runtime is loaded at runtime (ort's load-dynamic), resolved across platforms from ORT_DYLIB_PATH, Nix profiles, and well-known system locations — so it is provided once by the platform onnxruntime package (declared as a dependency in the Arch/Homebrew packages), pip install onnxruntime, or a manual ORT_DYLIB_PATH. The ort crate is exact-pinned (=2.0.0-rc.12) until a stable 2.0 ships. One-time re-index: the new index format is not backward-compatible; the first semantic search after upgrade rebuilds the index automatically (a load-time version guard removes any stale index rather than risk mis-decoding it). The jina-code-v2 built-in (pre-existing broken) is removed; code-specialized embeddings remain available through the hf:org/repo[@rev] custom scheme (hf:jinaai/jina-embeddings-v2-base-code), which auto-probes the model's ONNX I/O signature. Thanks to @omar-mohamed-khallaf for the optimization work.
  • lean-ctx bypass renamed to lean-ctx raw (external audit, finding 5). The "bypass" wording read to a model like a security bypass, but it only skips output compression — the shell allowlist and path jail still apply. lean-ctx bypass stays as a back-compat alias; model-visible hints now use raw and state that the allowlist still holds.
  • Fewer, less-duplicated MCP read tools (#509 Phase 1+2 / #527, #528, #532). The read-variant cluster (ctx_smart_read, ctx_multi_read) folds into a single ctx_read (multi-path + auto mode); the former tool names stay as deprecated aliases that still work but no longer cost schema tokens in tools/list, shrinking the always-on surface. Internally, ctx_read modes are now a type-safe ReadMode vocabulary (parsed once, FromStr/Display) instead of ad-hoc strings, with behavioural-equivalence tests and the eval A/B gate guarding zero output regression. SessionCache is retained (the decoupling thesis was evaluated and rejected as net-negative).
  • Configurable ctx_shell timeouts + opt-in writes (#526 / #523, #529). The hard-coded 2-min / 10-min shell ceilings are now tunable via shell_timeout_secs and shell_heavy_timeout_secs (env LEAN_CTX_SHELL_TIMEOUT*), and the read-only output doctrine can be relaxed deliberately with shell_allow_writes (env LEAN_CTX_SHELL_ALLOW_WRITES) so a trusted operator can permit >/tee/heredoc writes through ctx_shell — off by default, part of making prohibitive security opt-in rather than absolute (#526).
  • Leaner always-on tool & rules schema (#510/#517, #505/#508). Power-tier tool descriptions are reworked workflow-first and de-duplicated, and the optimized tools schema + canonical rules consolidation land, trimming the fixed per-session token cost of advertised tools and auto-loaded rules without changing behaviour (eval-gated).

Fixed

  • config set now accepts every valid Option config key (persona, bypass_hints) instead of rejecting them as "Unknown config key" (#856). config set resolves keys via the hand-written schema (ConfigSchema::lookup) only. An Option<_> scalar field defaults to None, so serde omits it from Config::default() and it never appears in config_derived_keys() (which feeds only config validate/apply). Any such field that wasn't hand-registered was therefore accepted from config.toml but rejected by config set and flagged "unknown" by config validate — the class behind the path_jail report (fixed earlier in #507). Auditing all 17 root-level Option fields found two more: persona and bypass_hints are now registered in the root schema (persona as an open string so custom <name>.toml personas stay valid; bypass_hints as enum(on|off|aggressive) so config set validates the value). A new regression test (option_scalar_keys_are_cli_settable) asserts the Option-scalar knobs resolve via schema lookup, guarding the whole class.
  • lean-ctx -c no longer kills hook-running git commit/git push at the 2-minute default (#854). The shell wrapper enforces DEFAULT_TIMEOUT (2 min) on ordinary commands and HEAVY_TIMEOUT (10 min) on build/test commands, but git commit/git push were treated as ordinary — even though, in a repo with hooks, git commit fans out into cargo clippy (pre-commit) and git push into the full scripts/preflight.sh (pre-push), each of which routinely runs 310 min. The wrapper SIGKILLed git mid-hook, leaving the tree staged-but-uncommitted or the push half-done. is_heavy_command now classifies git commit and git push as heavy (10-min ceiling, 32 MB buffer); read-only verbs (git status/log/diff) stay on the default ceiling because matching is on the full git <verb> prefix.
  • Hybrid/dense cold-start no longer re-embeds the whole corpus inline (#512). On a large repo, the first ctx_semantic_search mode=hybrid (or dense) call on an MCP server that started before the on-disk dense index existed would embed the entire corpus under the 120s per-request watchdog. The watchdog abandons the response but cannot cancel the spawned compute, so the embed kept running — observed as a 500%+ CPU child for >10 min after the call "returned". A new cold-start guard counts the chunks a re-embed would touch (EmbeddingIndex::pending_chunk_count) and, above a budget (default 2000 chunks, tunable via LEAN_CTX_HYBRID_INLINE_EMBED_MAX; 0 disables — the pre-#512 behavior), refuses the inline embed across all four search entry points (hybrid/dense × the MCP tool and the CLI/editor search_hits path): hybrid degrades to the coherent BM25(+graph) ranking (the same fallback used when dense is disabled) and dense fails fast — both with a one-line hint to build the index once, out of band (lean-ctx index build-semantic). Warm and incremental paths (a few changed chunks on an existing index) are untouched and still embed inline.
  • Shell-output compression can no longer inflate token counts (Windows CI flake). The VCS branch of compress_output (git/jj/gh/glab/hg) returned its authoritative compressor's result even when it was not strictly shorter — so a compact git log --oneline stays verbatim — but it skipped the token guard the other paths use. A tiny adversarial git status body could reshape into a one-token-larger summary, breaking the compress_output_never_inflates_tokens property on Windows. The VCS path now allows equal (verbatim) output but rejects any growth, restoring the never-inflate invariant deterministically. Pinned with a regression unit test for the exact failing input.
  • Cold-prefix repack is now sticky, persistent, and marker-stable (#499). Three fixes to the opt-in big-gap repack (#480): (1) once a resumed conversation is judged cold and repacked, the decision latches so every warm follow-up keeps the same deterministic prefix compression and hits the cache written at the cold turn — the previous one-shot repack re-sent the uncompressed prefix on the very next turn and busted its own fresh cache (net-negative for the common resume-then-continue case); (2) per-conversation baselines now persist to disk (cold_prefix_touch.json, atomic write) and reload on proxy startup, so a long idle gap that straddles a daemon restart is still detected; (3) the conversation key ignores the volatile cache_control marker, so a moving cache breakpoint no longer flips the key into a permanent first-sighting that never repacks. All three are cache-safe by construction (deterministic re-compression) and covered by new N→N+1, restart, and marker-stability tests. Thanks to @phawrylak for the precise analysis.
  • gain no longer reports 0 saved when MCP tools wrote to a different data dir (#500). The savings headline, gain score, cost view and net-of-injection line now sum stats across every auto-resolved data dir that holds a stats.json. When an agent host launches the lean-ctx MCP server with a different HOME/XDG_* than the user's shell (e.g. a containerised Hermes Agent) the savings landed in a sibling tree while the CLI read an empty primary dir and showed a false zero. Aggregation is a no-op without a split and is skipped entirely when LEAN_CTX_DATA_DIR pins one dir, so non-split users are unaffected. The empty-state screen now also cross-checks the tamper-evident savings ledger and, when it holds events that stats.json does not, names the data-dir split outright (lean-ctx savings / lean-ctx doctor). Finally, the proxy "bridge OFF — savings cannot be measured" caveat is suppressed whenever there are real (MCP-measured) savings to show, since gain measures MCP-tool savings directly and needs no proxy. Thanks to the reporter for the detailed Hermes + OpenRouter writeup.
  • Billing edge no longer downgrades a paying account on a billing-service blip (GL #785). Entitlement resolution at the cloud edge now caches each user's last known plan (in-memory, short TTL) and, when the upstream billing service is unreachable or returns a bad response, serves that cached plan instead of silently falling back to Free. Successful lookups refresh the cache; only never-seen accounts fall to Free. A transient upstream outage can no longer lock a Pro subscriber out of paid features mid-session.
  • Windows PowerShell/cmd no longer rewrites the lean-ctx path (#518 / #521). The terminal-integration shell hook used a Unix-style /c/... path that PowerShell and cmd can't execute, so lean-ctx invocations failed on Windows. The hook now emits the native binary path on PowerShell/cmd, restoring terminal integration there.
  • No more flaky ORT SIGSEGV on process exit (#519 / #522). Short-lived processes that loaded the ONNX Runtime model could crash with a ~30% flaky SIGSEGV/EXC_BAD_ACCESS during static OpSchema teardown at exit (arm64 macOS). lean-ctx now skips the detached ORT model load in short-lived processes that won't use it, removing the teardown crash without affecting real search.
  • Inherited LEAN_CTX_ACTIVE no longer silently disables compression (#533 / #537). LEAN_CTX_ACTIVE served double duty as both a shell-hook re-entrancy guard and a compression bypass; when an agent (e.g. Codex) inherited it into the MCP server's environment, every tool output came back uncompressed. Re-entrancy ownership now rides a dedicated LEAN_CTX_WRAPPED marker, so an inherited LEAN_CTX_ACTIVE no longer turns compression off.
  • ctx_read raw:true / mode=raw now honored and documented (#513 / #514). The verbatim escape hatch was silently ignored on the raw:true argument and undocumented for mode=raw, so non-Opus models (GLM 5.2 report) fought the compression by retrying reads. Both forms now reliably return uncompressed, un-elided bytes and are documented as the way to get exact file content.
  • allow_paths / shell_allowlist_extra failures are no longer silent (#540 / #541, #542). Two invisible-over-MCP failure modes are surfaced at the point of the block: (1) a project-local .lean-ctx.toml whose security-sensitive overrides are withheld because the workspace is untrusted now names the ignored keys and the lean-ctx trust / global-config remedies; (2) when the runtime resolves a global config.toml that doesn't exist (an edit that landed in a different dir — XDG vs legacy ~/.lean-ctx, or a sandboxed/container $HOME), both the allowlist and path-jail block messages now say so and name the path actually read. The stderr-only tracing::warn was invisible to MCP clients (OpenCode), making these read as "the setting does nothing".

[3.8.11] — 2026-06-20

Fixed

  • #478 — JetBrains plugin now writes its backend port file to XDG_DATA_HOME, matching the Rust data_dir. After the #408 path refactor, LeanCtxPaths treated config.toml as a data marker and fell back to XDG_CONFIG_HOME, so the plugin wrote the port file under ~/.config/lean-ctx while the Rust reader looks under ~/.local/share/lean-ctx. The file was never found (BACKEND_REQUIRED), disabling every IDE-side ctx_* action. Data-dir resolution now mirrors the Rust implementation (single-dir override, layout pin, data-only markers with config.toml excluded), with regression tests for fresh installs, mixed configs and XDG pins. Thanks @dasTholo.
  • lean-ctx uninstall now also removes the auto-update agent and every XDG data directory. A full uninstall left the 6-hourly self-update LaunchAgent (com.leanctx.autoupdate) running and never deleted the real runtime dirs (~/.local/share, ~/.local/state, ~/.cache — >150 MB), because remove_data_dir resolved through dirs::data_dir(), which collapses onto ~/Library/Application Support on macOS. Uninstall now calls update_scheduler::remove_schedule() and resolves every XDG category through core::paths (honoring LEAN_CTX_*_DIR / XDG_*), with a regression test that asserts every canonical directory is covered exactly once.
  • Onboarding command box now shows LEAN_CTX_DISABLED=1 instead of the non-existent lean-ctx off / on toggle. The box advertised subcommands that don't exist (they fail with "unknown command"); the real global switch is the LEAN_CTX_DISABLED=1 environment variable.

[3.8.10] — 2026-06-20

Fixed

  • #462 / #474 — restricted shell mode no longer rejects for/while/if loops, case blocks and subshells. The allowlist checker now expands a compound command down to its leaf command segments and validates each segment against the allowlist, so legitimate constructs (for f in *.rs; do cat $f; done, if test -f x; then ls; fi, ( ls && pwd )) run under restricted mode while injection attempts smuggled through the same constructs stay blocked.
  • #476 / #477 — lean-ctx uninstall --help no longer performs a real uninstall. The --help/-h flag fell through to the uninstaller, which removed the installation instead of printing usage. The CLI now short-circuits uninstall --help/-h to print the usage text and exit without touching processes, configs, data or the binary.
  • #356 — the "lean-ctx wants to access your Documents folder" prompt is now closed even for brew upgrade-only installs. The path guards + LaunchAgent Seatbelt wrapper already made daemon/proxy boot promptless, and lean-ctx update/dev-install regenerate the plists with that wrapper. The remaining hole was a user who only runs brew upgrade (which bypasses lean-ctx's updater), so their pre-Seatbelt plists were never regenerated. New belt-and- suspenders: a launchd-standalone process (ppid 1) now re-execs itself under the deny-~/Documents Seatbelt at startup if it is not already wrapped (reexec_under_seatbelt_if_needed, called first thing in main). A sentinel env var baked into the plists (LEAN_CTX_SEATBELT) prevents any double-wrap for current-code plists; terminal/editor children (host TCC grant) and non-macOS are unaffected. Verified by the existing tcc_sandbox.sh SIGKILL-on-access boot test. This makes the daemon/proxy promptless independent of code signature, so no Apple Developer ID is required.
  • #451 — ctx_shell / lean-ctx -c no longer run agent commands in a non-POSIX interactive shell. $SHELL is the user's interactive shell; when it is Nushell, Fish, Elvish, xonsh or PowerShell, an agent's bash/POSIX command silently mis-executes. detect_shell now honors $SHELL only when it is POSIX-compatible (bash/zsh/sh/dash/ash/ksh/mksh) and otherwise falls back to a real POSIX shell. zsh/bash users are unaffected; LEAN_CTX_SHELL still forces a specific shell regardless of the gate.
  • Shell gotcha auto-learning now correlates fail→fix in CLI (lean-ctx -c) mode. pending_errors were #[serde(skip)] and cleared on load, so a fix spanning two separate lean-ctx -c processes never correlated (only the long-lived daemon could). They are now persisted (bounded by MAX_PENDING + a 15-min TTL, pruned on load), so a later process loads the pending error and correlates the fix — the gotcha loop now works in the hybrid CLI-shell setup.

Added

  • #668 — FinOps showback: readable project names. The savings ledger stores only a truncated repo hash (never a path), so the finops export project column was opaque. An opt-in <config_dir>/finops-aliases.toml ([projects] <repo_hash> = "Team", also --aliases=FILE / $LEAN_CTX_FINOPS_ALIASES) now maps hashes to human-readable names at export time only — the ledger, the signed batch and the hash chain are never touched, so privacy guarantees and signatures stay intact. Unmapped hashes fall back to the hash, so an incomplete map never drops rows. New core/finops_export/aliases.rs; applies to all targets (FOCUS / CBF / Vantage).
  • #674 — central, signed org policy distribution + admin. lean-ctx policy org sign <pack.toml> --org <name> wraps a policy pack in an Ed25519-signed artifact; endpoints policy org trust <pubkey> (pin once, out-of-band) and policy org install <artifact>, after which the runtime folds the org pack in as an un-bypassable floor beneath the local .lean-ctx/policy.toml. The local pack can only ever tighten it: deny_tools union, allow_tools intersect, redaction union (org patterns win clashes), the stricter filter action, the tighter egress/max_context_tokens caps, the longer audit_retention_days. Two independent checks gate enforcement — the signature must verify and the signer key must be pinned — so a forged or untrusted artifact is ignored, never enforced, and never bricks the agent (fail-open); with no key pinned nothing is enforced (opt-in). --advisory distributes a policy for preview without enforcing it; policy org status shows the effective floor and policy org verify checks an artifact offline. Pluggable source (LEANCTX_ORG_POLICY / LEANCTX_ORG_TRUST_KEY for MDM). New core::policy::org + core::policy::floor; contract docs/contracts/org-policy-v1.md.
  • #677 — signed CISO compliance report. lean-ctx compliance report --from <rfc3339> --to <rfc3339> [--framework eu-ai-act|iso42001|soc2]... [--pack <name|path>] [--format json|csv|pdf|text] composes the engine's evidence surfaces into one Ed25519-signed artifact for a date range: OWASP Top-10-for-Agents alignment, framework coverage (verified live against the resolved pack), what enforcement blocked (ToolDenied) and redacted (SecretDetected) over the period (folded from the append-only audit chain, with the segment's head_hash bound into the signed payload), and the retention posture (pack audit_retention_days intent vs. plan entitlement). The signed JSON is always written and is offline-verifiable with lean-ctx compliance verify <report.json> (no audit trail, no LeanCTX needed); --format csv|pdf additionally emits that human rendering — the PDF is a real, dependency-free PDF 1.7. Honest by construction: a quiet period reports zero blocks, and a broken local chain is reported (chain_valid = false), never hidden. New core::compliance_report module; contract docs/contracts/compliance-report-v1.md.
  • #676 — egress / output DLP on agent writes & actions. A new [egress] policy-pack section governs what the agent emits (the output side of the Great Filter), checked before dispatch of ctx_edit writes and ctx_shell/ctx_execute actions — so a blocked write never touches disk and a blocked command never runs. forbidden_patterns are regexes that refuse a write/action on match (e.g. a prod-DB DSN or a destructive query); block_secrets refuses content carrying detected secrets (the pack's [redaction] patterns) or PII (the #675 checksum-validated detectors); max_writes_per_min is a per-process sliding-window rate limit on agent writes/actions. Blocked egress returns [POLICY BLOCKED] and is audited (ToolDenied) with a non-sensitive reason (forbidden-pattern:…, secret, pii:…, rate-limit) — never the matched content. Egress obeys the same opt-in / fail-open / Local-Free guarantees; forbidden_patterns accumulate and the scalars override down the extends chain. New core::egress module.
  • #675 — inbound content filters (PII / classification / prompt-injection). A new [filters] policy-pack section adds net-new detectors that run inside the enforcement pipeline before tool output reaches the agent (the input side of the Great Filter). Each detector takes an action — off / warn / redact / block: pii finds Swiss AHV (EAN-13), IBAN (mod-97), payment cards (Luhn) and email, each checksum-validated to keep false positives low; classification gates files marked confidential/secret (banner lines or a Classification: field, not prose mentions; blocked_labels is configurable); injection masks/blocks OWASP-LLM01 prompt-injection lines (reusing output_sanitizer::detect_injection). Decisions are audit-logged privacy-preservingly — only (class, count) pairs (e.g. pii:iban×2), never the matched value. Filters obey the same opt-in / fail-open / Local-Free guarantees as the rest of the pack; actions override and blocked_labels accumulate down the extends chain. New core::input_filters module.
  • #673 — context policy packs are now enforced at runtime. A project pack (.lean-ctx/policy.toml, authored from any built-in via lean-ctx policy show <name> --toml) is applied at the MCP hot path: deny_tools/allow_tools gate which tools the agent may call (denied calls return [POLICY DENIED] and are audited as ToolDenied), [redaction] patterns strip matches ([REDACTED:<name>]) from tool output before it reaches the model, default_read_mode sets the ctx_read fallback when the caller omits mode, and max_context_tokens tightens (never loosens) the session token ceiling. Enforcement is opt-in (no pack → unchanged behavior), fail-open on an invalid pack, and Local-Free — only the agent pipeline is constrained, never a human's own reads. The ctx/ctx_session/ctx_policy meta tools are never gated, so a pack can never lock the operator out.
  • #454 — prefer_native_editor config to opt out of lean-ctx edit operations. Set prefer_native_editor = true (or LEAN_CTX_PREFER_NATIVE_EDITOR=1) so the lean-ctx edit tool (ctx_edit) is neither advertised in list_tools nor dispatchable (direct or via ctx_call); the agent falls back to the host's built-in editor UI. Read / search / shell / memory tools are unaffected. Colorized diffs are intentionally left to host extensions rather than the MCP tool output, which must stay byte-stable for prompt caching (#498).

[3.8.9] — 2026-06-18

Added

  • Hermes context-engine plugin + ctx_transcript_compact core tool — lean-ctx can now be Hermes Agent's active context engine, not just an MCP server it might call. The new integrations/hermes-lean-ctx plugin is a thin Python ContextEngine that replaces Hermes' built-in ContextCompressor: it keeps the system preamble + a fresh tail verbatim, replaces older turns with a recoverable summary, and injects lean-ctx's recall tools (ctx_search, ctx_semantic_search, ctx_read, ctx_expand, ctx_knowledge, ctx_summary) natively into the agent. Compaction itself lives in a new daemon tool, ctx_transcript_compact (the 77th MCP tool): deterministic, prompt-cache-friendly compaction of OpenAI-format message arrays that never splits a tool_call/tool_result pair and offloads the raw turns into session memory. The plugin prefers this core tool over /v1 and falls back to local Python compaction when the daemon is unreachable, so the agent loop never breaks. Includes session-lifecycle persistence (resume on start, ctx_summary + ctx_handoff on end), model-window presets, a runnable head-to-head benchmark harness (vs. import-guarded ContextCompressor/hermes-lcm), and a dedicated CI job (pytest + offline benchmark smoke). lean-ctx init --agent hermes now also points to the engine plugin.
  • ACE-inspired auto-learning loop — gotchas now learn themselves, distil, and surface (study of kayba-ai/agentic-context-engine). Previously the GotchaStore could correlate an error with its later fix but nothing ever fed it real shell outcomes, so it stayed empty in production. The loop is now wired end to end:
    • Live captureshell::exec hands every finished command to gotcha_tracker::record_shell_outcome, gated by a cheap is_correlatable_command filter (cargo/npm/pytest/go/docker/git…) so only build/test/run output is inspected. A process-global in-memory store keyed by project hash keeps the pending_errors (which are #[serde(skip)]) alive across commands inside a long-lived daemon, mirroring diagnostics_store; durable gotchas are persisted when a fix is correlated.
    • Reflector — a deterministic reflect() distils the store into Playbook deltas: recurring fixes (≥2 occurrences with a resolution) become proven strategies, error signatures that recur across ≥2 distinct sessions with no recorded fix become recurring pitfalls. It folds into the session Playbook during ctx_compress via the existing dedup/stable-ID add_delta.
    • Offline mininglean-ctx learn --mine <dir> scans a directory of .jsonl transcripts/logs for high-precision error markers (Rust E-codes, tsc/pytest/npm signatures), aggregating recurring signatures across files read-only — it never mutates stored state.
    • Learning Ledgerlean-ctx gotchas ledger renders a human-readable summary (errors observed, fixes correlated, repeats avoided, promoted to knowledge) plus the distilled strategies/pitfalls, making the learning visible.
  • Semantic near-duplicate detection on ctx_knowledge remember — the lexical similarity check only caught facts sharing tokens, so paraphrases of the same decision silently accumulated. remember now also runs an embedding cosine pass (threshold 0.86) before upsert and appends a non-destructive "SEMANTIC NEAR-DUPLICATES" advisory listing paraphrases the lexical pass missed, so the agent can judge/merge them. Self-matches and already-judged pairs are excluded; the embedding path is behind the default embeddings feature.

Fixed

  • High idle CPU when no session is running (#453) — on v3.8.8 (macOS, Claude Code & OpenCode) a connected-but-idle agent pegged a whole CPU core in the lean-ctx process. A sample of the live process showed the leanctx-index thread burning ~100% while every other thread (tokio workers, memory-guard, main) sat parked in cond_wait/nanosleep — a CPU-bound worker, not a busy timer loop (the screenshot's "2 idle wake-ups" at 97.5% CPU confirmed it). Root cause: LeanCtxServer::new() ran an eager full index build (graph + BM25 + line-search) on every server start whenever a project root was detected. A warm cache still burned ~1 core for 69 s per start; multiplied across two agents and stdio respawns it never settled. Fixed comprehensively:
    • No eager startup build (primary fix) — the startup scan is removed; the server falls back to the demand-driven lazy warming it already documents (#152). A session that sits idle or only uses ctx_read/ctx_shell/ ctx_tree now pays zero indexing cost (measured: idle CPU stays at 0.0%); graph/search tools still warm their index on first use. The eager call was an unrelated regression slipped in via #294.
    • Long-lived HTTP serve keeps a one-time background warm-up — only the persistent lean-ctx serve process (never the per-respawn stdio path) kicks off a single deduped background index build at startup. Without it the first heavy/search tool call on a large project root raced a cold scan against the per-request timeout and, on CPU-constrained CI runners, starved the request handlers into 504 request_timeout (the SDK-conformance regression). Idle CPU still settles flat once the build completes, so #453 idle hygiene is preserved.
    • stdio transport no longer respawns on a single bad frame — the codec mapped any decode error to the same None as a true EOF, so one malformed JSON-RPC message tore down the server (rmcp QuitReason::Closed), the agent respawned it, and the fresh process paid another index build — a CPU churn loop. Malformed frames are now skipped (the bad frame is already consumed) and the stream resyncs onto the next message; only a real stream end closes the transport.
    • No duplicate daemons — concurrent MCP servers launching at once could all pass the is_daemon_running() check in a TOCTOU window and each spawn a daemon. start_daemon() now serializes that critical section with an exclusive, bounded-wait file lock.
    • Leaner proxy reload — the #449 upstream-reload loop's default interval is relaxed from 2 s to 5 s; Config::load()'s internal content-hash cache already skips re-parsing an unchanged config.toml, so each idle tick is just a small file read.
    • memory-guard idle backoff — RSS sampling stretches from every 3 s to every 15 s once memory has been stably calm, and snaps back instantly under any pressure (OOM reaction time during real work is unchanged).
  • Quick settings that "keep resetting" are now diagnosable and stable (#450) — a value saved in the dashboard could be silently shadowed so it appeared to revert to defaults (lite/off), and lean-ctx config validate only said "no config" without telling you where it looked. There are four mechanisms and none of them was visible: an env var (LEAN_CTX_*), a project-local .lean-ctx.toml override (compression_level/terse_agent/tool_profile), a divergent resolved config dir (dashboard writes path X, runtime reads path Y), or an unparseable config.toml falling back to defaults. Fixed by making the provenance explicit and the path stable:
    • config validate shows the source — it now always prints the resolved config.toml path (even when missing), the layout-pin state, any parse error, and the active env / project-local overrides, with a one-line explanation of why a value can appear to "reset".
    • Dashboard surfaces provenance/api/settings returns config_path, config_exists, parse_error and a per-setting local_override; the Quick Settings panel shows which config.toml is read and warns (and disables the toggle) when an env var or a project-local .lean-ctx.toml is winning.
    • Dashboard pins the layoutlean-ctx dashboard now runs the same layout_pin::heal() as the daemon/server start paths, so it can no longer write config.toml into a divergent dir the runtime never reads.
  • Dashboard no longer times out on load; heavy index/graph routes never block (#452) — opening the dashboard mounted ~22 <cockpit-*> components that each fired loadData() from connectedCallback() at once — a thundering herd of /api/graph, /api/call-graph, /api/symbols, /api/search-index and /api/tree requests that ran synchronous, file-count-scaling index/graph builds and starved the trivial /api/settings handler until the client aborted after 8 s ("Settings timeout"). Fixed on two layers:
    • Frontend lazy-load (primary fix) — components no longer load in connectedCallback(); the router's view-loader fetches only the active view, so #context/settings issues a single /api/settings request instead of triggering every panel's data load at once.
    • Backend single-flight + non-blocking (hardening)graph_index and bm25_index gained a get_or_start_build coordinator (one background build per root, concurrent callers deduplicated) modeled on call_graph. Heavy routes (/api/tree, /api/symbols, /api/call-graph, /api/search-index, /api/search) now return 202 {status:"building"} with progress instead of blocking on a full scan; the affected panels poll and show an "index building…" state until the build completes.
  • ctx_shell is clearly labelled and runs profile-free (#451)
    • Pi renderer — the Pi extension rendered shell calls with a bare $ prefix (inherited from Pi's bash renderer), making ctx_shell look like a native interactive bash shell. It now renders an explicit ctx_shell label.
    • Profile-free shellctx_shell (MCP execute_command_with_env) and the CLI lean-ctx -c paths now neutralize inherited BASH_ENV/ENV so a non-interactive sh -c/bash -c can no longer be hijacked into sourcing a profile/rc file (e.g. an exec nu snippet silently replacing the shell). Shell behavior is now deterministic and independent of user shell config.
    • Sharper description — the tool description (MCP and Pi) states it runs the system shell ($SHELL) profile-free, so agents stop treating it as a config-loaded interactive bash.
  • Proxy upstream is now live from config.toml — no more stale upstream on a long-lived proxy (#449) — the proxy froze its provider upstreams in ProxyState at startup and never re-read them, so a later lean-ctx config set proxy.openai_upstream … (or any config.toml edit) had no effect until a manual restart — and a shell export LEAN_CTX_OPENAI_UPSTREAM=… could never reach an already-running, service-managed proxy at all (the env simply does not propagate into a running process). Now:
    • Live reload — a background task re-resolves the upstreams from config.toml every ~5s (LEAN_CTX_PROXY_RELOAD_SECS to tune) and publishes any change through a tokio::sync::watch channel that every provider handler reads per request, so config set takes effect on the running proxy within seconds, without a restart. An invalid value keeps the last good upstream instead of silently dropping to the provider default.
    • config.toml is the source of truth for long-lived proxies; a LEAN_CTX_*_UPSTREAM env var remains a start-time override only (it cannot reach a process that is already running). MCP hosts make this acute: Codex (and others) launch the lean-ctx MCP server with a stripped, allowlisted environment that omits LEAN_CTX_*_UPSTREAM, so the proxy it spawns never sees it — config.toml is the only mechanism that reaches every proxy.
    • Root cause for service/MCP-managed proxies — directory pinning — a launchd-spawned proxy inherits only launchd's minimal environment (no HOME, no XDG vars) and so resolved a different config/data dir than the CLI: it never read the user's config.toml (live reload had nothing to read) and derived a mismatched session token (its /status 401'd). The proxy/daemon LaunchAgent plists now bake in the exact HOME + LEAN_CTX_{CONFIG,DATA,STATE,CACHE}_DIR the installing CLI resolves, so a managed process always agrees with the CLI.
    • Observability/status and lean-ctx proxy status now report the active upstreams; proxy status derives liveness from the public /health endpoint (so a running proxy is never misreported as down) and warns in two cases: a LEAN_CTX_*_UPSTREAM set in the shell that never reached the proxy (with the exact config set command to persist it), and a proxy started with an env override now masking a later config.toml edit. doctor carries the same drift check.
    • lean-ctx proxy restart — new subcommand that cleanly restarts the managed service (re-reads config.toml, drops any start-time env override).
  • ctx_impact resolves C# extension-method hosts and disambiguates types by namespace (GH #398 follow-ups, #640#643) — the two deferred #398 follow-ups are now closed:
    • Extension methods (#642) — a call value.WordCount() to a C# extension method (static int WordCount(this string s)) names neither the defining static class nor any of its types, so it produced no edge and left the host a false-negative leaf. A new deep_queries::ext_methods extractor collects this-parameter methods, and ctx_impact links each value.Foo() call to the defining file (file + symbol TypeRef edge), self-filtered and capped.
    • Namespace-aware resolution (#641)TypeDef now carries its C# namespace (block-, file-scoped and nested), and type_ref_targets resolves hybridly: a definer in the consumer's visible namespace (own namespace + enclosing namespaces + usings) always links — even past the cap — and its homonyms in other namespaces are dropped, so same-named types are no longer conflated. With no namespace match the global fallback still links, with the too-generic cap raised 3 → 5. Java (no namespaces) keeps the fallback path. Both capabilities are wired into the embeddings and minimal builder paths; all new regressions are gated on tree-sitter so they exercise both. Outputs stay deterministic (sorted/deduped, bounded indexes; #498).
  • ctx_impact now sees C# types used only in expression position (GH #398 follow-up) — the v3.8.3 fix linked same-namespace C# consumers to definers for types in declaration positions (fields, parameters, return types, base_list, generics, casts, typeof), but a type referenced only in expression position still produced no TypeRef edge, so ctx_impact reported the defining file as a false-negative leaf. Now covered in deep_queries::type_uses: static calls/fields and enum values via a member-access receiver (Engine.Create(), Engine.Default, Status.Active) and attributes ([ApiController], which additionally resolves to the …Attribute class name). Only PascalCase receivers are collected and the existing def-index resolution discards any name that is not a real project type, so precision is unchanged. The new end-to-end regression is gated on tree-sitter rather than embeddings, so it also exercises the index_graph_file_minimal builder path that the earlier #398 e2e tests never reached. (Extension-method hosts and namespace-aware resolution were the remaining follow-ups, now closed above.)
  • lean-ctx update / config init --full no longer reset or leak config values (#443) — persisting a single setting could silently rewrite other customized keys in the global config.toml (e.g. compression_levellite, max_ram_percent → 5). Three root causes, now closed by construction:
    • (A) default-seed clobberconfig init --full historically wrote Config::default(), and save() overwrites every key present in both the incoming document and the file (config_io::merge_table), resetting customized values. (Already mitigated via config_for_full_init; now superseded.)
    • (B) project-local leakConfig::load() folds project-local .lean-ctx.toml overrides into the in-memory struct, so the common load() → mutate → save() pattern (18 call sites across 10 files) wrote those per-project values back into the global file.
    • (C) corrupt-file clobberwrite_toml_preserving_minimal wrote a fresh document when the existing file failed to parse, discarding a hand-broken config. The fix introduces a leak-free persistence API — Config::load_global() (reads the global file only, never merging project-local overrides) and Config::update_global() (read global-only → mutate → minimal save, and refuses to touch an unparseable file) — and migrates every persist site to it. The runtime read path (Config::load(), with project-local merge) is unchanged. In addition, write_toml_preserving_minimal now refuses to overwrite an unparseable config instead of clobbering it, and config init --full emits a fully annotated reference document seeded with the user's current values (lossless round-trip, independent of schema completeness).
  • XDG layout no longer flips back to ~/.lean-ctx (GL #623) — once an install resolved to the XDG four-dir layout, a single stray marker appearing in ~/.lean-ctx (a legacy residue, a restored backup, a concurrent older binary, even an empty sessions/) silently re-collapsed config/data/state/cache onto that one directory via single_dir_override, after which config.toml was no longer found and the dashboard graph disappeared (data had moved to $XDG_DATA_HOME/lean-ctx/graphs). A new layout pin ($XDG_CONFIG_HOME/lean-ctx/layout.toml, mode = "xdg") records the commitment: the resolver reads it before the legacy/mixed heuristic and never re-adopts ~/.lean-ctx for a pinned install. The pin is written (and a residual ~/.lean-ctx auto-drained) by every independent long-running writer and repair path — setup, the MCP server start, the daemon (init_foreground_daemon, incl. the launchd/systemd autostart), and doctor --fix (after it migrates + reclaims). Marker detection was hardened so an empty sessions//graphs/ directory (or a zero-byte stats.json) no longer counts as data, and the Docker self-heal shell hook no longer touches ~/.lean-ctx (heal timestamp → $XDG_STATE_HOME, lock count → $XDG_DATA_HOME). doctor now reports the active layout mode (xdg-pinned / single-dir / legacy).
  • Re-reads stop blowing up to full content (cache hit-rate regression) — with mode omitted (the recommended usage), a file first read in a compressed mode (map/signatures) was resolved to full on its second read by the cache_hit shortcut, even though full content had never been delivered (full_content_delivered=false). The 2nd read therefore re-delivered the entire file — more tokens than the first read — a compression bounce that also meant stub hits only began at the 3rd read, which agents rarely reach. Measured lifetime cache hit-rate had collapsed to ~5% (down from ~90%). The resolver now only short-circuits to full once full content was actually delivered; otherwise it falls through to the predictor, which reproduces the cached compressed mode and serves it from the compressed-output cache as a cheap, consistent hit. Explicit mode="full" reads (for editing) are unchanged.
  • Cache-aware pruning no longer churns the cached prompt prefix (#448) — on cache-metered rails (Anthropic), the default cache-aware history pruner rewrote already-cached history every time the prune boundary advanced a STRIDE (~every 16 messages), invalidating the provider prompt-cache prefix from the first changed message and re-billing cheap reads (0.1x) as writes (1.25x). Pruning now skips the client's cache_control-marked prefix and only ever rewrites not-yet-cached content, so a growing conversation keeps hitting the cache. Per-message tool-result compression is unchanged (it is content-deterministic and prefix-stable), and requests without cache_control (e.g. OpenAI) are byte-for-byte unaffected.
  • ctx_retrieve / ctx_share no longer serve stale cached content — both paths returned the cached full content for a file (get_full_content) with no staleness check, so an agent that retrieved a file — or received one via a cross-agent ctx_share handover — could be handed a version that no longer matched disk if the file had been edited since it was first read. This is the classic handover failure: agent A edits a handover file, agent B reads the pre-edit cached copy and "does not see the changes". ctx_read was already safe (it revalidates by mtime and content hash and re-reads on any mismatch); the two retrieve/share accessors bypassed that guard. Both now go through a new staleness-safe accessor (SessionCache::current_full_content) that validates the cached entry against disk (mtime + hash) and transparently re-reads the current bytes when the cache is behind the file, so a retrieve or handover always reflects the latest content.

[3.8.8] — 2026-06-17

Added

  • lean-ctx update <version> pins a specific release (#447)update now takes an optional version (lean-ctx update 3.8.5, v-prefix optional) and installs that exact tagged GitHub release instead of the latest, so you can roll back or A/B an older build. It reuses the normal update path — SHA256-verified download, atomic binary swap, post_update_rewire — so the same checksum guarantee applies and no data, config or logs are touched (only the binary is swapped; downgrades read your existing data as-is). Invalid versions are rejected before any network call; --check reports whether the pinned version differs. The auto-update scheduler is unchanged (still tracks latest).
  • R2 benchmark faithful-arm preflight (#361)bench/agent-task/r2/preflight.mjs proves, before any priced run, that the pi arm routes shell through ctx_shell (native bash suppressed) and actually compresses it — the "green preflight = running as designed" gate the tokbench reviewer asked for, ruling out R1's 102 native bash / 0 ctx_shell. The shell-suppression decision is now the single, unit-tested invariant resolveSuppressedBuiltins (packages/pi-lean-ctx), so the routing fix can never silently regress.
  • Proxy accepts a trusted non-loopback HTTP upstream behind an opt-in (#440) — Codex and other clients that sit in front of the proxy need to point it at an upstream like http://host.docker.internal:2455, but validate_upstream_url rejected every non-loopback http:// URL with a misleading "must use HTTPS" error and no escape hatch. A trusted plaintext upstream is now allowed via LEAN_CTX_ALLOW_INSECURE_HTTP_UPSTREAM=1 or [proxy] allow_insecure_http_upstream = true; the startup banner and doctor flag the plaintext hop so it stays a conscious choice. Documented end-to-end in docs/reference/05-advanced.md, including the supports_websockets = false Codex HTTP/SSE setup as an alternative to the native WebSocket transport below.
  • Native WebSocket /responses transport for Codex (#440) — Codex CLI and the OpenAI SDK default to a persistent WebSocket connection (ws://…/responses, one response.create event per turn), so the HTTP-only proxy forced clients to set supports_websockets = false. The proxy now speaks the Responses WebSocket protocol natively: GET /responses (and /v1/responses) upgrades to a WebSocket, each response.create turn is bridged to the configured HTTP/SSE upstream with lean-ctx's tool-output compression applied, and every upstream SSE event is relayed back verbatim as a WebSocket frame. Method routing keeps POST on the HTTP/SSE forwarder, so both transports share one upstream, auth path and compression logic (proxy::openai_responses_ws). Codex works as a drop-in now without disabling WebSockets.

Changed

  • Rust crate migrated to edition 2024 (#438)cargo fix --edition plus manual fixes for #[cfg(windows)] FFI (unsafe extern "system") and feature-gated paths cargo fix cannot reach on a single host. Newly-unsafe std::env::set_var / remove_var calls are fully documented: the 13 production sites carry exact // SAFETY: justifications (all single-threaded CLI/startup paths), while the ~390 test sites route through one audited helper, crate::test_env, instead of repeating the same comment at every call. Profile switching no longer mutates the environment from the multi-threaded MCP server — set_active_profile records the active profile in a thread-safe in-process cell, removing a latent env::set_var data race. Nested if / if let collapsed to edition-2024 let-chains tree-wide. No behavioural change. Thanks @dasTholo for the original migration PR (#438).
  • OpenCode plugin no longer double-registers the built-in overrides (#441) — the plugin exposed ctx_read/ctx_search/ctx_glob/ctx_edit/ctx_shell both as static replacements of the native read/grep/glob/edit/bash tools and again under their ctx_* names via dynamic MCP registration, so the model saw two copies of each and paid for the duplicate schemas. The five already-overridden tools are now filtered out of the dynamic set; every other ctx_* tool is still registered dynamically. Thanks @omar-mohamed-khallaf.
  • Default shell allowlist now includes the C/C++ compilers (#361) — under mode=replace, ctx_shell enforces the allowlist, but gcc/cc/clang/ g++/c++/clang++ were missing even though rustc/go/javac were, so a coding agent could not compile an ad-hoc reproducer (gcc repro.c) without an explicit opt-in (reported by the tokbench review, which set LEAN_CTX_ALLOWLIST_WARN_ONLY=1 to work around it). They are compile-only — executing the produced binary stays gated like any other path — so the security boundary is unchanged.

Fixed

  • gain dashboard shows the per-day lean-ctx version again (#307) — the "richer theme rendering" pass replaced the per-day version column in the RECENT DAYS section with a gradient bar, so lean-ctx gain and gain --deep silently stopped attributing each day's compression rate to a release (regressing the feature added in v3.7.1). The version is still recorded on every day's stats and the gain --daily table still showed it — only the dashboard renderer dropped it. The bar is kept (now padded to a fixed width so the column lines up) and the version is re-appended (v{x.y.z}, for pre-tracking days).
  • Secret redaction stops corrupting type annotations and drops its duplicate rules (#430)ctx_edit carried a second copy of the redaction regex set that never got the non-secret-literal guard added for #430; worse, its generic-long-secret branch kept the matched value before the [REDACTED] tag, so a real key could leak into diff evidence. Diff masking now goes through the single core::redaction source of truth. That guard is also widened: right-hand sides that are type expressions — password: Promise<string>, apiKey: Record<string, unknown>, token: string[] — are recognized as non-secrets (real keys never contain <>|()[]{}), so reading TypeScript through ctx_read no longer masks password: undefined-style annotations as API keys.
  • ctx_read exposes the same schema in Pi as in Codex / MCP (#432) — the Pi adapter hand-wrote a ctx_read schema with only path / offset / limit / mode, so an agent running in Pi never saw fresh or start_line even though the canonical MCP schema (and the Pi handler internally) already supported them — making cross-harness instructions like ctx_read(mode="full", fresh=true) look invalid in Pi only. The Pi schema now matches the registry: start_line (with offset kept as a back-compat alias) and fresh are exposed and wired through both the MCP-bridge and CLI read paths.
  • proxy enable now also routes Pi / forge through the proxy (#361) — Pi and forge resolve their endpoint from ~/.pi/agent/models.json (providers.<name>.baseUrl) + OAuth, not from ANTHROPIC_BASE_URL / OPENAI_BASE_URL, so the shell and Claude/Codex env wiring silently bypassed them (the tokbench review had to hand-edit models.json). proxy enable / disable now wire Pi's anthropic (bare origin) and openai (/v1-suffixed) providers when ~/.pi/agent exists, preserving any custom remote endpoint unless --force and reverting only the endpoints it set. Pi's OAuth keeps working because the proxy forwards the credential verbatim to the real upstream.
  • config init --full no longer resets the existing config to defaults (#443) — the command rebuilt the file from Config::default() and saved that over the user's config.toml. Because the TOML merge writes every default value, this silently reverted custom settings (proxy port, compression level, provider setup, …) on every init --full. The command now loads the existing config and re-serializes that (falling back to defaults only when no file exists), preserving user values while still materializing the fully-commented template; an unparseable file aborts with a clear message instead of being overwritten.
  • OpenCode (and 18 other agents) now get the ctx_* usage rules injected (#442) — rule injection was gated on rules_already_present(), a hand-maintained list that only knew about five agents. For everyone else it returned false, so with auto_inject_rules unset the setup skipped injection and the model never saw the "prefer ctx_* tools" guidance — defeating the whole point of MCP-only mode. Detection is now derived from the single build_rules_targets catalog (rules_inject::any_rules_marker_present), so every supported agent is covered and can never drift from the writer again. The OpenCode hook additionally injects the rules into AGENTS.md when running MCP-only (shadow mode off) and MCP is registered, so the guidance lands even without the interception plugin.
  • Impact graph self-heals after an upgrade so C# same-namespace edges apply (#398) — the v3.8.3 fix added type_ref edges for C#/Java types consumed without a using/import (same-namespace/package visibility), but those edges only exist in a freshly built graph. ctx_impact rebuilt the property graph only when it was completely empty, so after upgrading, an existing graph (built before the edges existed) was served unchanged — leaving the consumed class a false-negative leaf that reported "no impact". The property graph now records the engine generation that produced it (engine_version + built_with in graph.meta.json), and ctx_impact analyze/diff/chain detect a graph built by an older engine and transparently rebuild it once before querying. Combined with the XDG resolver fixes (#436/#439) — which keep the graph and config.toml in a single stable location — a stale or misplaced graph can no longer mask the real blast radius. Thanks @nigeldun.
  • Direct writers stop re-creating ~/.lean-ctx after migration (#439) — the resolver fix (#436) flips the data tree to XDG, but several feature-specific writers still hard-coded ~/.lean-ctx and re-created it post-split regardless of where the resolver pointed: multi-agent shared_knowledge.json (core::agents), Jira OAuth credentials (core::providers::jira_oauth), the personal-cloud cache/knowledge readers (cloud_client / cloud_sync), the LaunchAgent proxy logs and scheduled-update logs (proxy_autostart / update_scheduler), the A2A task store (core::a2a::task), the cloud mode_stats reader (cli::cloud) and the ctxpkg publisher signing key (core::context_package::keys). All now route through the typed data_dir() / state_dir() resolvers — the same categories doctor --fix migrates them to — so a post-migration session reads and writes the XDG dirs, while legacy single-dir installs still resolve in place. The source-level legacy-path firewall (rust/tests) was tightened to catch both the multi-line dirs::home_dir()…join(".lean-ctx") chains and the join(".lean-ctx/…") subpath form it previously missed, so the tracked-debt allowlist can only shrink.
  • doctor shows ~ instead of the absolute home path (#437) — dozens of checks printed the full /Users/<name>/… (or /home/<name>/…) path, leaking the username and adding noise. Two chokepoint helpers in doctor/common.rs (tildify_home for formatted lines, display_user_path for raw paths, with component-boundary safety so a sibling like …/<name>-backup is never mangled) collapse the home dir back to ~ at the central output sinks, so doctor and doctor integrations no longer print an absolute home path.
  • Data dir no longer re-adopts a marker-free ~/.lean-ctx (#436) — the data resolver returned the legacy ~/.lean-ctx whenever that directory merely existed, even after doctor --fix had moved every data marker to the XDG dirs. Config/state/cache had already flipped to $XDG_* in that case, so data silently diverged from its siblings and editor sessions kept writing active_transcript.json / context_radar.jsonl back into ~/.lean-ctx. The legacy/mixed decision now lives in a single source of truth (paths::single_dir_override): a legacy dir wins only while it still holds data markers, so once split, data flips to $XDG_DATA_HOME/lean-ctx like the rest. A cross-category contract test plus a source-level legacy-path firewall (rust/tests) lock the invariant in so it can never silently regress.
  • doctor --fix now empties a residual ~/.lean-ctx (#434) — after the data moved to XDG, leftover reports (doctor/, setup/, status/) and the empty directory lingered, so the next run re-detected the old location and the fix report itself was written back into the legacy dir. --fix now drains any remaining non-runtime entries into the typed XDG dirs and removes the empty directory (xdg_migrate::reclaim_legacy), and the report lands in XDG.
  • doctor reports the real config.toml location after a split (#435) — the config.toml check and the path-jail hint were hardcoded to ~/.lean-ctx, so after the XDG split doctor pointed users at a stale path. Both now resolve through Config::path() / config_dir() and show where the file actually lives.
  • doctor score matches the checks it prints (#433)passed/total were two hand-maintained counters that drifted: rendered ✗ checks ("XDG layout", "data dir split") were shown but never counted, so the summary overstated health. Every check now flows through one accumulator that counts exactly what it renders; advisory lines (LSP, providers, MCP bridges) are rendered but explicitly excluded from the score, so display and tally can no longer diverge.
  • Secret redaction no longer mangles source files read via ctx_read (#430) — the key/value secret pattern matched TypeScript type annotations and language literals such as password: undefined, secret: string and token: null, replacing the value with [REDACTED:API key param] and corrupting files read through ctx_read. The redactor now skips a denylist of obvious non-secret literals (undefined/null/none/true/false/string/number/boolean/…). The same pass fixed two latent under-redaction bugs: AWS keys and generic long secrets were annotated in place (the secret kept, [REDACTED] merely appended) instead of removed. The shell tee redactor and the ctx_read redactor now share one implementation (core::redaction), so the two layers can never drift apart again.
  • Dashboard tool profile "Lean" no longer reverts to "Power" (#431) — selecting Lean persisted tool_profile = "lean", but the config loader didn't recognise it (logging Unknown tool_profile 'lean' and falling back to Power) and the settings API reported the effective profile (Power) rather than the unpinned state. lean/lazy/reset are now understood everywhere as the unpin sentinel (centralised in tool_profiles::is_unpinned_alias), the loader self-heals silently, and the dashboard reports — and round-trips — Lean (the toggle is labelled "Lean (default)").
  • Dashboard settings page no longer times out on load (#431) — route handlers ran synchronously on the small async worker pool, so one slow endpoint (e.g. a graph/index build) could starve a trivial GET /api/settings for minutes on few-core machines. Handlers now run on the blocking thread pool, keeping light endpoints responsive, and any handler crossing 1s is logged for diagnosis.
  • ctx_read accepts offset/limit aliases (#432) — agents trained on the native Read tool send offset/limit, but the schema only documented start_line, so those range reads were silently ignored. offset is now an alias for start_line and limit bounds the window (lines:N-M); the aliases are advertised in the tool schema and the generated manifest/reference docs, with PI_AGENTS.md aligned.
  • macOS "access your Documents" prompt eliminated structurally (#356) — the daemon, proxy and auto-updater run as LaunchAgents (their own TCC identity, ppid 1), so any access they make under ~/Documents, ~/Desktop or ~/Downloads pops the privacy prompt in lean-ctx's name — and because every release re-signs the binary, the grant is voided on each update, re-prompting forever. The earlier opt-out path guards (v3.8.0v3.8.7) were per-call-site and fragile, and the stable code-signing identity only made one "Allow" stick — neither satisfied users who refuse Documents access outright. The three LaunchAgents are now wrapped in sandbox-exec with a minimal Seatbelt profile (allow default; deny file-read*/file-write* under the three protected home dirs — rust/src/core/tcc_guard_sandbox.rs), so the kernel refuses any such access silently with EPERM: TCC is never consulted and the prompt can no longer appear, with no "Allow" required. Everything else stays permitted, so functionality is intact; the path guards and stable signing remain as defense-in-depth. The profile is smoke-tested before use (no KeepAlive crash-loop on a malformed profile), existing installs adopt the wrapper automatically on the next lean-ctx update, and a new regression (rust/tests/tcc_sandbox.sh) boots the daemon under the production wrapper.

[3.8.7] — 2026-06-15

Added

  • Dashboard: sort the live call feed by per-call cost (#426) — the Live Activity feed already showed per-call detail (tool, file/query, tokens in → out, tokens saved, read mode); it now has a Sort selector — Recent / Top saved / Largest / Slowest — so you can rank tool calls by cost and instantly see which reads/searches/shell calls were expensive vs cheap. Read-only, reuses the existing /api/events journal data; no new routes.
  • Dashboard: Quick Settings — flip core switches from the UI (#427) — a new Settings tab (Context area) flips the four high-impact, mid-session switches without dropping to the terminal: compression level (off/lite/standard/max), tool profile (minimal/standard/power/lean), structure_first (on/off) and terse agent (off/lite/full/ultra). Writes go through a new /api/settings endpoint that inherits the dashboard's Bearer-token auth and CSRF-Origin check, validates every value against the config schema and a fixed four-key allow-list (no arbitrary config keys are writable), and persists to config.toml exactly like the matching CLI commands. Settings pinned by a LEAN_CTX_* environment variable are flagged in the UI so a toggle never silently no-ops.
  • Dashboard: --open=browser|none|vscode reveal control (#424)lean-ctx dashboard always launched the system browser, which is jarring inside an editor or behind a reverse proxy. A new --open=<mode> flag (or --no-open), resolved as --open > LEAN_CTX_DASHBOARD_OPEN > the browser default, picks the reveal behaviour: browser (launch the system browser, unchanged default), none (start silently and just print the URL) or vscode (suppress the external browser and print the VS Code Simple Browser steps). Flag parsing is case-insensitive and falls back to browser on an unknown value.

Fixed

  • macOS: the "lean-ctx wants to access your Documents folder" prompt no longer returns after every update (#356) — lean-ctx binaries are ad-hoc signed, so their cdhash changes on every build. macOS TCC anchors an ad-hoc binary's privacy grant to that cdhash, so each update looked like a brand-new program and re-popped the prompt — clicking "Allow" only lasted until the next build. New lean-ctx codesign-setup (macOS) creates a dedicated keychain with a persistent self-signed code-signing identity and trusts it once (a single Touch ID / login password confirmation). dev-install and the self-updater now sign every build with that identity, giving TCC a stable Designated Requirement (identifier "com.leanctx.cli" and certificate leaf = H"…") instead of a per-build cdhash. Result: a single "Allow" survives all future updates. Falls back to ad-hoc signing when the identity isn't set up, so the binary always runs.

  • doctor --fix now fully empties ~/.lean-ctx instead of leaving items behind (#429) — the XDG split migration skipped any entry whose destination already existed and left the source in place. On Windows (and after any partial earlier run or a parallel data dir) the targets routinely pre-existed, so ~30 legacy items lingered and doctor warned about the single-dir install forever, no matter how often you ran --fix. Collisions are now reconciled instead of skipped: directories are merged child-by-child, a source file byte-identical to the destination is dropped as a duplicate, and a genuinely different source is moved aside next to the winner under a *.legacy name. The destination is never overwritten and nothing is lost, so the legacy directory empties out and the warning clears. doctor --fix now reports N moved/merged, N duplicate(s) dropped, N kept as *.legacy.

  • macOS TCC "Documents" prompt — definitive structural fix (#356) — the privacy prompt asking to access your Documents folder, which kept returning after every lean-ctx update despite earlier patches (v3.8.0, v3.8.2), is now fixed at the root. The TCC guard (may_probe_path) was opt-in per call site, so every new or forgotten heuristic filesystem probe re-introduced the prompt (whack-a-mole). The model is inverted to a choke-point / opt-out design:

    • safe_canonicalize — the sink that ~8 heuristic call sites funnel through — returns the path lexically (no stat/realpath) when the process is launchd-standalone and the path is under ~/Documents, ~/Desktop or ~/Downloads.
    • every duplicated project-marker probe (config, graph_index, setup, dashboard, knowledge_bootstrap, graph_provider) now routes through the single guarded pathutil::has_project_marker, with one marker set.
    • is_safe_scan_root refuses launchd-standalone scans under the protected dirs before any marker probe or read_dir; has_multi_repo_children now also refuses nested protected paths (e.g. ~/Documents/proj), not just the bare magic dirs. The project-local .lean-ctx.toml read and the git rev-parse / cwd-fallback in project-root detection are guarded too.

    Why it kept coming back: lean-ctx update run from a terminal makes the daemon inherit the terminal's TCC grant, masking the bug; end users run the daemon and proxy as LaunchAgents (ppid 1, standalone), where the unguarded probes hit ~/Documents and prompt — and every update changes the binary's code signature, invalidating any prior grant. A new macOS sandbox-exec regression test (rust/tests/tcc_sandbox.sh) boots the daemon as a standalone process under a profile that SIGKILLs on any ~/Documents access, reproducing the real end-user condition that terminal testing hid, alongside standalone unit tests in pathutil / graph_index / session.

    Note: installing the update that contains this fix may show the prompt one last time (the old, still-running binary's signature changes as it is replaced); after that it stays quiet.

  • auto_update_mcp = false now suppresses MCP writes on every registration path (#281) — earlier fixes only gated the shared JSON-config writer and configure_agent_mcp; the per-agent hook writers (Claude, JetBrains, OpenClaw, Crush, OpenCode) and the editor-registry registration in interactive setup, non-interactive setup and doctor --fix still wrote MCP server entries unconditionally. The check is now centralized in hooks::should_register_mcp() and applied on every path: hooks, rules and skills still install, only the MCP server entry is withheld. A subprocess regression test guards it.

  • ctx_read map/signatures no longer serve pre-rebuild output after ctx_index build-full (#420) — the CLI build-full path cleared the daemon read cache, but the MCP tool runs in the process that owns the SessionCache, so a forced rebuild left ctx_read map/signatures returning stale output. The MCP tool now invalidates the in-process graph cache and clears the SessionCache in-process, matching the CLI guarantee.

  • Dashboard auto-refreshes the active view on data change and tab focus (#425) — the 10s poll only refreshed the status bar and flagged the manual refresh button; the main panels listen to lctx:refresh, which only the manual button dispatched, so stats/metrics stayed static until a reload. The poll now dispatches lctx:refresh on a content-hash change while the tab is visible (panels reload in place, preserving UI state), and a visibilitychange handler catches up immediately when the tab regains focus.

  • lean-ctx watch backfills recent events on start (#560)watch set the tail offset to EOF on startup, so an idle launch showed a blank screen even when events.jsonl was already populated. It now seeds the view with the last 20 events (bounded, O(n) memory) and advances the offset to EOF, so the live poll stream continues without re-emitting them.

  • Homebrew installs no longer run a stale shadowed binary (#559) — a brew-managed shim (/opt/homebrew/bin/lean-ctx../Cellar/lean-ctx/<old>) could shadow the freshly built ~/.local/bin/lean-ctx on PATH, so the daemon and CLI ran different builds (md5 drift). After installing, lean-ctx repoints any Cellar/linuxbrew shim at the just-installed binary and warns about any other PATH entry that still resolves before it. (The drift helper is correctly gated to unix so the Windows cross-compile stays warning-clean.)

  • JetBrains plugin ships under a discoverable release-asset name (#418)buildPlugin emitted lean-ctx-<version>.zip, indistinguishable from a source archive in the GitHub Release asset list, so the plugin looked "missing" even though it was attached. The artifact is renamed to lean-ctx-jetbrains-plugin-<version>.zip before upload, and the release job now fails loudly if buildPlugin produced no zip.

Security

  • PathJail keeps resolving symlinks under TCC-protected dirs (#356 follow-up) — the #356 choke-point accidentally routed PathJail's canonicalization through the same TCC guard (canonicalize_or_selfsafe_canonicalize_boundedsafe_canonicalize), so a launchd-standalone daemon validating a path under ~/Documents got a lexical (unresolved) path and could miss a symlink jail escape. Security canonicalization is now split from heuristic canonicalization: PathJail (jail root, candidate ancestor, extra-roots, TOCTOU re-check, and the allow-list) uses a new unguarded pathutil::canonicalize_secure[_bounded] that always resolves symlinks; only self-initiated heuristic probes keep the guard. The jail only ever runs on a path the client explicitly asked for, so a one-time prompt there is legitimate, while #356's self-initiated boot prompts stay suppressed (verified by the sandbox-exec boot test plus a new canonicalize_secure_bypasses_tcc_guard_for_pathjail unit test).
  • Cookbook dev-dependency upgrade — Vite 6 → 8 (#595) — the example apps now build on Vite ^8.0.16 with @vitejs/plugin-react ^6 (peer vite ^8), pulling a patched esbuild and clearing the esbuild dev-server advisory (GHSA-67mh-4wv8-2f99). npm audit reports 0 vulnerabilities; the knowledge-graph-explorer example builds and typechecks unchanged. Node engine floor raised to >=20.19.0 to match Vite 8's requirement.

[3.8.6] — 2026-06-15

Added

  • CodeBuddy AI platform support (#423) — CodeBuddy joins Claude Code / Codex as a first-class agent: detection, init / setup / uninstall, MCP wiring at ~/.codebuddy/mcp.json, dedicated rules injection, and the same path-jail protection as .claude / .codex (~/.codebuddy in IDE_CONFIG_DIRS, the broad-root guard, and the home/agent-dir checks). Thanks @studyzy.
  • Structure-first cold reads (structure_first, #361) — an opt-in bias (off by default; env LEAN_CTX_STRUCTURE_FIRST) for auto to prefer map on a cold read of a medium-sized source file. It is the one read saving that survives a phase-isolated harness (no warm-session re-read to amortise a full read) and is capability-safe: the active-diagnostic / edit-fail / small-file guards still force full.
  • gain now reports net-of-injection bill impact (#361)lean-ctx gain (and gain --json) surface the observed proxy turns, the total injected overhead (per-turn tax × turns) and net_tokens_saved (which can go negative and says so), so the meter reconciles to the provider bill instead of a tool-local ratio. The proxy persists its request count to make this honest.
  • Faithful benchmark arm config (#361)bench/agent-task/r2/ ships a zero-injection, capability-safe lean-ctx arm (rules_injection=off, minimal tool profile, structure_first, proxy on with cache-aware pruning) plus the pi extension config and proxy env wiring, so an independent benchmark runs lean-ctx "installed = running as designed".

Changed

  • Suspect files are never compressed away on a fix task (#361) — when the task text explicitly names a file (e.g. "fix the sort in versioncmp.c"), auto now forces full for that file ahead of any compression-favouring intent, so the agent always gets the body it needs to localise and edit the defect.
  • The proxy protects build/test fidelity and foreign tools (#361) — a generic/foreign shell tool_result that looks like a build failure or test run is preserved verbatim at the wire (compiler errors, panics and test summaries kept intact), and vendor-prefixed tools (forge_read, pi.shell, …) are now classified by name segment so a foreign source read is protected and a foreign shell log is compressed. Request-body compression is deterministic, keeping the provider prompt-cache prefix byte-stable.
  • The pi extension can route shell through ctx_shell (#361) — a new routeShell opt-in (env LEAN_CTX_PI_ROUTE_SHELL, implied by replace mode) suppresses the native bash builtin so build/test/log output is compressed and metered (lossless for signal), while the read/list/search builtins stay available alongside ctx_*.

Fixed

  • [archive] could exhaust host RAM and force a reboot (#417) — archived tool outputs (.txt + .meta.json + SQLite FTS) were written on every large call, but the configured max_disk_mb / max_age_hours limits were never enforced: archive::cleanup() had no production caller and the FTS cap deleted only DB rows, orphaning the (much larger) .txt blobs. The store therefore grew unbounded on disk and starved the host of RAM via the page cache. cleanup() now enforces both the age TTL and the on-disk size budget, prunes the content files and FTS index together (no more orphans), runs at MCP start and periodically off the hot path, and lean-ctx cache prune reclaims the archive too.
  • doctor reported the proxy as broken on Windows (#416) — proxy autostart has no backend on Windows, so doctor treated its absence as a hard failure (a permanent 27/28). The proxy check is now platform-aware: a reachable proxy is green, an unreachable proxy on a platform without autostart is a warning (run lean-ctx proxy start), and "running but autostart not installed" is a warning rather than a failure on macOS/Linux.
  • setup reported compression settings it never saved (#415) — the wizard printed "✓ Compression: …" before writing and swallowed the write error, so a failed save still looked successful. Success (and the rules-prompt injection) is now reported only after the config is actually persisted. doctor also displayed "power" for an unpinned install; it now correctly reports "lean (default)".
  • A data dir split across two trees could not be merged (#414) — when both a legacy (~/.lean-ctx) and an XDG tree held a stats.json, the old migration bailed and doctor pointed at lean-ctx setup instead of doctor --fix. doctor --fix now consolidates every non-canonical data tree into the canonical one (newer file wins, never clobbering a newer copy) before the XDG split, the hint points to the right command, and $XDG_DATA_HOME/lean-ctx is included in split detection.
  • JetBrains plugin now ships as a downloadable GitHub Release asset (#418) — the plugin .zip is built and attached to every release. It was missing from v3.8.5 because the plugin's Release Asset job only ran on release events, which a GITHUB_TOKEN-created release never triggers. The plugin version is now single-sourced in gradle.properties and mirrors the engine release via -Pversion=<tag>, so it can no longer drift (it had been stuck at 3.8.3).
  • The wake-up briefing listed dead and foreign agents (#419)ctx_overview read the raw AgentRegistry, so it showed peers from crashed or exited MCP processes (and from other projects). It now prunes stale entries (cleanup_stale) and scopes the list to the current project root, matching what ctx_agent list and the dashboard already do.
  • ctx_read map/signatures served pre-rebuild output (#420)lean-ctx graph build --force and lean-ctx index build-full only dropped the in-process graph cache, but a running daemon kept serving stale map/signatures from its long-lived SessionCache in another process. Both commands now also flush the daemon's read cache over IPC (never auto-starting one), so derivations re-derive on the next read.
  • ctx_multi_read ignored auto mode (#421) — batch reads forced autofull, so every file came back fully expanded regardless of the active profile. ctx_multi_read now honours auto like a single ctx_read, resolving the optimal mode per file. Tool descriptions, schemas and the injected rules (bumped to v12) now steer agents to omit mode (= auto) and reserve full for the read immediately before an edit.
  • ctx_semantic_search was hidden in the default profile (#422) — the meaning-based search tool was categorised under Memory and absent from the lean core set, so it never appeared in the default ("lean") gate. It is now a Core tool and part of the advertised core surface; the setup/doctor tool counts are derived dynamically instead of a hard-coded "13".
  • A cold read could cost more tokens than the raw file (#361) — an independent benchmark measured ctx_read auto-mode payloads up to +21.6% larger than the source on a small codebase: on a tiny file the one-line framing header (file ref + deps/exports summary) is net overhead that only amortises across re-reads, and the CLI one-shot path used a divergent resolver that lacked the small-file guard. ctx_read now enforces a hard anti-inflation invariant — a read never returns more tokens than the raw file. When framing would exceed the bare content (auto-resolved or full reads) the file is shipped verbatim, so a read is break-even at worst and a win whenever a compressed mode or cached re-read applies; an explicitly requested view (map/signatures/lines:) is always honoured untouched. The same guarantee now covers the additive one-shot CLI path, which also routes through the unified auto-mode resolver. Re-reads are unaffected (the cache keys on path and re-derives the file ref). Follow-up: map mode no longer repeats exports the API: section already lists with full signatures — the same symbols were emitted twice (once as a bare exports: line, once in API:). A shared exports_not_in_signatures helper now drives the MCP, CLI and benchmark map renderers, so every export is shown exactly once (re-exports/const aliases the API can't capture still surface) and the scorecard measures the deduped output agents actually receive.
  • A knowledge store could grow to 2× its fact cap on import (#417)remember() hard-caps a project's facts at max_facts, but the bulk import_facts() path still used the old max_facts * 2 guard, so a merge/import could inflate a store to twice its budget before any eviction fired (observed live as a doctor capacity CRIT, e.g. facts 232/200). The import path now runs the memory lifecycle as soon as it exceeds max_facts, draining the excess by importance (archived, not lost). The eviction invariant now holds on every write path (remember, import, persist-merge).
  • Knowledge stores for deleted projects accumulated forever (#615) — a store at knowledge/<hash>/ is keyed to a project_root; when that root is deleted (a removed git worktree, a thrown-away project) the store can never be written again, so its eviction cap can never self-heal and it lingers as pure disk bloat (one such store surfaced live as a permanent doctor capacity CRIT). lean-ctx doctor now reports orphaned stores and the reclaimable size, lean-ctx cache prune reclaims them (alongside BM25/graph/archive), and doctor --fix prunes them as part of a repair. Detection is conservative — a store with an empty (legacy/global) root or a still-existing root is never touched, and only the explicit prune commands delete (never the background lifecycle), so a temporarily-unmounted drive can't trigger data loss.
  • auto_update_mcp = false was still ignored on several MCP registration paths (#281) — earlier fixes gated the shared JSON-config writer and the editor-target helper (configure_agent_mcp), but the per-agent hook writers (Claude, JetBrains, OpenClaw, Crush, OpenCode) and the editor-registry registration in interactive setup, non-interactive setup and doctor --fix still wrote MCP server entries unconditionally. Every registration path now honours the flag: hooks, rules and skills still install, only the MCP server entry is withheld, so a locked-down environment stays MCP-free after setup/onboard/init/doctor --fix.

[3.8.5] — 2026-06-14

Added

  • JetBrains / IntelliJ IDE plugin (#413) — a native plugin (community contribution by @dasTholo) that drives lean-ctx from inside JetBrains IDEs: PSI-backed navigation, a refactoring engine (rename / move / inline / safe delete), symbolic body edits and an in-IDE tool window. The Rust engine gains a matching ctx_refactor surface and an LSP layer (lsp::backend, jetbrains_backend, edit_apply, port_discovery) that talks to the IDE over a localhost-only, token-authenticated HTTP channel and re-validates every plugin-reported path against the project PathJail (BLAKE3 TOCTOU guard, atomic writes). It also works headless (tree-sitter range edits without a running IDE). Kotlin / Kotlin-Script (.kt / .kts) are now recognised for indexing.
  • First-class Lua / Luau graph indexing (#360) — symbols, require edges and the call graph are now extracted for Lua and Luau sources.
  • lean-ctx dashboard --auth-token (#377) — a fixed dashboard auth token via flag or env (env takes precedence) for reverse-proxy deployments, with token-aware connection reuse.
  • lean-ctx doctor --fix splits a legacy/mixed install into the XDG dirs (#408): moves data/state/cache out of the config dir on demand. The migration is all-or-nothing, idempotent/resumable (existing files are never clobbered) and crash-safe (atomic rename with a copy+remove fallback across filesystems). Read-only lean-ctx doctor reports a pending split. New per-category overrides LEAN_CTX_CONFIG_DIR, LEAN_CTX_STATE_DIR, LEAN_CTX_CACHE_DIR.
  • Multilingual intent routing (#591) — intent detection now handles non-English queries.

Changed

  • XDG Base Directory compliance (#408) — lean-ctx now separates its files into the standard XDG categories so the config dir can be mounted read-only:

    • Config (config.toml, shell hooks, env.sh) → $XDG_CONFIG_HOME/lean-ctx.
    • Data (sessions, vectors, graphs, knowledge, archives, memory, stats.json) → $XDG_DATA_HOME/lean-ctx — the fresh-install default flipped here from the old config dir.
    • State (events, journals, logs, ledgers, agent_runtime_env.json) → $XDG_STATE_HOME/lean-ctx.
    • Cache (semantic cache, models, learned patterns) → $XDG_CACHE_HOME/lean-ctx.

    Existing legacy (~/.lean-ctx) and mixed ($XDG_CONFIG_HOME/lean-ctx) installs keep working unchanged in single-dir mode; an explicit LEAN_CTX_DATA_DIR still forces one directory and is never auto-split.

  • pi-lean-ctx bridge tool parity (#409)ctx_search, ctx_tree and ctx_multi_read are now exposed through the Pi bridge, guarded by a Node CI gate.

Fixed

  • Embedding index clobbered by parallel remember (#412) — embedding-index writes are now serialized under the per-project lock, fixing degraded recall when multiple remember calls raced.
  • auto_update_mcp = false ignored during setup/onboard/init (#281) — the first fix gated only the editor-target registration (configure_agent_mcp); the hooks-layer MCP writers still wrote server entries unconditionally — the shared JSON-config writer behind Aider/Continue/Qwen/Zed/Amazon Q/Trae/Neovim/…, plus Copilot CLI, Gemini/Antigravity and Hermes. The flag is now honored on every registration path: hooks, rules and skills still install, only the MCP server entry is withheld, so a locked-down environment stays MCP-free after setup/onboard/init/doctor --fix.
  • Session extra_roots not honored in path resolution (#403) — extra roots are propagated at init and respected by the resolver.
  • Verbatim reads compressed on the CLI path (#404) — verbatim reads are now exempt from terse compression on the CLI.
  • Config::load served stale config (#406, #407) — the load cache is now invalidated by content hash so live edits apply immediately.
  • pi-lean-ctx MCP bridge did not shut down cleanly (#405).

Security

  • Captured agent API keys now stored in the state dir at 0o600 (#408) — keys such as GEMINI_API_KEY no longer sit alongside config files.
  • esbuild forced to ≥0.28.1 in the cookbook (#595) — closes GHSA-gv7w-rqvm-qjhr (dev-scope: missing binary integrity verification) by deduping the whole cookbook tree onto a patched esbuild.

Internal

  • make preflight CI-parity gate — a local fmt / clippy / doc / doc-drift / Windows-cross-compile / test gate wired into a pre-push hook, so the deterministic CI jobs can no longer go red only after the full CI matrix.

[3.8.4] — 2026-06-13

Fixed

  • ctx_tree/ctx_search/ctx_glob ignored an out-of-scope path and scanned the whole project instead (#401): when an explicit path (or paths) argument pointed outside the project root — or was otherwise unresolvable — the dispatcher's PathJail rejection was swallowed and the tools silently fell back to the project root, returning the entire repository tree for an unrelated path. The resolution error is now surfaced (ERROR: path escapes project root: … (root: …)) instead of a misleading full-tree result. Non-existent paths inside the project keep their clear "does not exist" message.

Added

  • lean-ctx doctor overhead (#572): per-client fixed-cost report — how many tokens your editor pays every session for tool schemas, instructions and rules files, with duplicate detection across CLAUDE.md/.cursorrules/AGENTS.md.
  • lean-ctx rules dedup [--apply] (#578): finds and removes lean-ctx-owned duplicate rule files and stale marked blocks across editors. The .cursorrules template is now a pointer to the canonical rules, and the compression block is no longer double-injected for Cursor.

Changed

  • Token-efficiency epic, phase 1 (#571) — fixed per-session overhead cut from ~13.7K to ~6.0K tokens on a typical setup:
    • Lean default tool surface (#575): setup no longer pins a tool_profile; the default surface is 13 lazy-core tools instead of 61. lean-ctx tools lean/reset manage it explicitly.
    • Schema diet (#576): core tool descriptions and schemas trimmed 3031→1935 tokens (36%); large action enums folded into pipe-delimited descriptions; a budget regression test keeps it from creeping back.
    • Instructions cap (#579): the static instruction skeleton stays ≤400 tokens (Off/Compact CRP) / ≤500 (TDD); the decoder block is mode-aware and canonical rule blocks were condensed.
    • Honest metrics (#573): dashboard, footer and ledger report observed tokens only — the modeled 2.5× grep baseline moves to the estimated series; ctx_cost splits cached vs uncached input at cache-read pricing; the benchmark measures the real CCP resume payload.
    • Self-describing outputs (#580): plain notation uses real language keywords (struct/trait/pub), and TDD symbol outputs carry a minimal inline legend (≤15 tokens) so agents never guess the notation.
  • Codex hook: native rewrite instead of block-and-retry (#399, community contribution): on Codex ≥ 0.20 the PreToolUse hook now returns updatedInput to rewrite shell commands through lean-ctx in place — no more deny + model-retry round-trip per command.

Security

  • Bumped the postgres crate family past three fresh RUSTSEC advisories (unbounded SCRAM iteration DoS, hstore/DataRow decode panics) — found by cargo-deny the moment they were published; lean-ctx never exposed the vulnerable paths to untrusted servers (#399).

Fixed

  • lean-ctx overview flooded the terminal with thousands of node_modules entries on projects without a top-level .git (#400): the ignore crate only applies .gitignore files inside git repositories — in a monorepo whose subprojects carry their own .gitignore but whose root is not a git repo, every scanner walked node_modules wholesale (74k+ files in the report). Two-part fix, applied to all 15 directory walkers (graph/BM25/ trigram index builders, ctx_impact, ctx_search/ctx_tree/ctx_glob, CLI scans): a shared walk_filter now prunes unambiguous vendor dirs (node_modules, __pycache__, bower_components, virtualenvs with a pyvenv.cfg) regardless of git state, and require_git(false) makes .gitignore files effective without a .git directory. Explicit roots stay reachable (ctx_tree node_modules/react works), and respect_gitignore=false remains the escape hatch for searching inside vendor dirs.
  • macOS privacy prompts ("lean-ctx would like to access …") fired repeatedly while the MCP server was running (#356 follow-up): editors spawn the user-level MCP server with cwd == $HOME. A ctx_search/ctx_tree/ ctx_glob call whose path fell back to "." then walked the entire home directory — every stat under ~/Library, ~/Desktop, ~/Pictures trips a TCC prompt (Calendar/Reminders/AddressBook/Photos), and the walk burned 1020 s per call. The index builders already refused broad roots; the direct walk fallbacks did not. All three walk tools now share that same root policy (new walk_guard): relative paths are absolutized against the process cwd first — so lean-ctx grep/ls inside a real project keep working — and broad or privacy-protected roots ($HOME, /, ~/Library, TCC dirs without project markers) return an actionable error telling the agent to pass an explicit project path instead of silently scanning.
  • ctx_impact reported C# classes as leaf nodes when consumers had no using directive (#398): C# resolves types in the same namespace without any import, and DI-style code never news its dependencies — so a class consumed only as a type (constructor parameter, field, property, base class, generic argument) produced zero graph edges and a false-negative "no files depend on X". The property-graph builder now extracts type usages from the AST (fields, parameters, returns, base lists, generics, casts, typeof) for C# and Java — the two supported languages with implicit same-namespace/package visibility — and links consumer files to defining files with type_ref edges, which impact_analysis already traverses. Names defined in more than 3 files are skipped as too generic to attribute.
  • Same root cause, second symptom: classes consumed only as a type were flagged by the dead_code smell — its SQL already exempted type_ref targets, but nothing ever created those edges. The builder now also emits symbol-level type_ref edges, so DI-consumed classes no longer show up as dead code while genuinely unreferenced ones still do.
  • Both property-graph builder paths (default and minimal) now share one analysis pass and definition index, so the fix applies regardless of build features.

[3.8.2] — 2026-06-12

Fixed

  • Codex PreToolUse shell compression no longer blocks with a manual re-run prompt: Codex now supports native updatedInput rewrites for PreToolUse hooks, so hook codex-pretooluse emits the documented allow+rewrite JSON on stdout instead of exiting 2 with "Re-run with ..." feedback. Rewritable Bash commands are transparently replaced with the lean-ctx -c ... command while preserving normal tool execution.
  • Linux: ctx_* tools broke for projects under /c/… and other single-letter roots (#397): the MSYS2/Git-Bash drive mapping (/c/Users/…C:/Users/…) in the MCP path normalizer ran unconditionally — on Linux/macOS, where /c/… is a literal directory, every file-addressing tool then failed with file not found on a nonexistent C:/… path (and absolute arguments were re-joined under the already-translated root, doubling it). The mapping is now gated on Windows hosts (cfg!(windows)) — that is the only platform where MSYS2/Git-Bash clients hand POSIX drive paths to a native Windows binary. On other hosts, /c/… passes through untouched; regression tests cover both sides.
  • lean-ctx doctor reported "no rules file found" right after lean-ctx setup (#396): the 3.8 layout (GL #555) intentionally replaced the always-loaded ~/.claude/rules/lean-ctx.md with a CLAUDE.md block + on-demand skill — setup even removes the legacy file — but the doctor check still demanded it, so a clean install could never reach a full pass and the suggested fix (init --agent claude) couldn't recreate the file either. Both doctor views (doctor and doctor integrations) now share one layout detector (claude_instructions_state) that accepts every state setup can produce: CLAUDE.md block (+ skill), dedicated injection (SessionStart hook + skill), legacy rules file, project scope, and rules_injection=off. Docs that still described the retired rules file were updated as well.
  • macOS still prompted "lean-ctx would like to access files in your Documents folder" on every upgrade (#356, reopened): the first fix (3.8.0) removed the scan-heuristic probes, but the prompt actually came from the launchd daemon's boot path — a process that is its own TCC identity, and whose grant is invalidated by every update (binary swap → new cdhash → re-prompt). Traced empirically with a deny-sandbox + crash-stack bisection; two independent boot-time offenders fixed:
    1. serve booted with cwd / and walked every stored session, stat-ing each session's project_root/shell_cwd (project-marker probes + canonicalize) — paths that usually live under ~/Documents. Broad roots ("/", HOME, agent sandboxes) now bail out before the scan — they can never own a session (this also stops shell_cwd.starts_with("/") from leaking an arbitrary project's session into the daemon default).
    2. ContextLedger::load → prune ran realpath over every persisted ledger entry at boot for its dedupe key; the key is now lexical-only. Defense in depth: launchd-owned processes (ppid 1) are detected as TCC-standalone and never stat/canonicalize paths under ~/Documents/Desktop/Downloads in heuristics (has_project_marker, session-root matching, normalize_tool_path); editor/CLI children inherit their host's TCC grant and keep full behavior. Verified with a SIGKILL-on-Documents-access sandbox: daemon boot (30 s soak), proxy boot, and the full lean-ctx update rewire now run clean against a real data dir with 600+ sessions rooted under ~/Documents.
  • Pi: ctx_grep/ctx_find/ctx_ls silently searched the wrong directory (#395): path was optional and fell back to the extension's cwd, so an agent working elsewhere got results from the wrong tree and was derailed; the calls also rendered without their arguments. path is now required (schema + description make the scope explicit), and the three tools reuse Pi's native call renderers so every invocation shows its pattern and directory in the transcript.
  • OpenCode × ChatGPT-OAuth broke behind the proxy (#366): proxy enable exported OPENAI_BASE_URL without the /v1 suffix the OpenAI SDK convention expects (default is https://api.openai.com/v1). OpenCode therefore sent Responses-API calls to …:4444/responses — a path its ChatGPT-OAuth plugin does not recognize (it matches /v1/responses), so subscription traffic leaked through the proxy to the platform API with the wrong credential: "Missing scopes: api.responses.write". The shell exports and the Codex CLI config now advertise http://127.0.0.1:<port>/v1; with that base, OpenCode's OAuth plugin correctly routes ChatGPT-subscription requests directly to chatgpt.com (analogous to the Claude Pro/Max guard), while API-key traffic keeps flowing through the proxy. Stale /v1-less entries in Codex config.toml are migrated on the next proxy enable; the proxy also collapses accidental /v1/v1/… double prefixes from clients that append /v1 themselves. Verified end-to-end against OpenCode 1.2.15.
  • Dashboard token race: lean-ctx dashboard persisted its fresh auth token before binding the port. Two racing starts both wrote dashboard.token; the bind loser exited, leaving a token on disk the surviving server never accepted — every "already running" browser open then hit silent 401s. The token is now saved only after a successful bind.
  • Live Activity feed masked errors as "No events recorded yet": a failed /api/events poll (daemon restart, expired token, timeout) was rendered as an empty feed. The dashboard now keeps the last known events and shows the actual error with a recovery hint instead.
  • Status bar showed "No session" while agents were active: /api/session matched sessions against the dashboard process's own cwd (usually HOME — a broad root that rightly matches nothing). It now falls back to the most recently updated session rooted in a real project.

Performance

  • /api/events no longer re-parses the event log on every poll: the file-backed event load is cached on (path, mtime, length) — the 3-second dashboard poll now costs a stat() instead of reading and parsing up to 10k JSONL lines.

[3.8.1] — 2026-06-12

The Field-Report Patch. Five issues straight from users' terminals, fixed the same week v3.8.0 shipped: daemon enable --help no longer installs the service it was asked to explain (#393), allow_paths finally expands ~ and $VAR instead of matching them literally (#392), and ctx_shell closes the download-to-file, xargs-delegation and "strict mode that only warned" gaps from the #391 security report. Plus: service file paths are printed where you need them with a new daemon restart (#394), and /reopen works anywhere in a comment (#388).

Added

  • lean-ctx daemon restart (GH #394): stops the supervised service and/or a manually started daemon, then starts it again through whichever channel was active before.
  • Service file paths are printed on daemon enable/disable, shown in daemon status and lean-ctx doctor (GH #394): the exact LaunchAgent plist / systemd user unit path plus the unit name, so systemctl --user / launchctl targets are obvious without searching.
  • lean-ctx doctor Path-jail check (GH #392): reports the effective jail state (active / path_jail = false / compile-time no-jail), flags allow_paths entries that can never match (unset $VAR, missing directory) and the allow_paths = ["/"] pattern.
  • Consolidated filesystem-boundary reference (GH #392): docs/reference/appendix-paths-and-config.md §5 documents path_jail vs allow_paths vs extra_roots, the no-jail cargo feature and the removed LEAN_CTX_NO_JAIL env var; SECURITY.md cross-links it.

Fixed

  • daemon enable --help executed instead of showing help (GH #393): --help/-h/help anywhere in lean-ctx daemon …, lean-ctx proxy … or lean-ctx allow … now prints usage and never executes the verb (an agent in read-only plan mode installed the systemd service by asking for help).
  • allow_paths / extra_roots entries with ~, $VAR or ${VAR} were matched literally (GH #392): config files see no shell, so "$HOME/code" silently never matched and PathJail kept rejecting paths the user had explicitly allowed. Entries (and the LEAN_CTX_ALLOW_PATH / LEAN_CTX_EXTRA_ROOTS env lists, which MCP hosts pass shell-less too) are now expanded; unset variables warn and are reported by doctor.

Security

  • ctx_shell hardening (GH #391): download-to-file flags are now treated as file writes (curl -o/-O/--output/--remote-name, wget's default file-download mode — wget -qO-/--spider stay allowed, dd of= except /dev/null); xargs/nohup join the delegation-aware checks so … | xargs bash -c '…' cannot smuggle inline code past the interpreter block in either allowlist or blocklist-only mode; shell_strict_mode = true now actually blocks command substitution in arguments and pipe-to-bare-interpreter (both previously only logged a warning while claiming to block); substitution detection now also covers double-quoted "$(…)" (single quotes still exempt — the shell doesn't expand there). SECURITY.md states the ctx_shell threat model explicitly: defense in depth for agent mistakes, not an OS sandbox — kernel-grade isolation belongs to containers/seccomp and the agent's own permission model.

Changed

  • /reopen matches anywhere in a comment (GH #388): "Please /reopen" works now; previously the comment had to start with the command.

[3.8.0] — 2026-06-12

The Governance & Proof release. Agents become accountable identities, context gets enforceable policy, and savings become auditable evidence: Ed25519-bound agent registry, deterministic evidence bundles with an offline verifier, EU AI Act / ISO 42001 / SOC 2 coverage reports, context policy packs, org SSO (OIDC) + org audit log, and a FinOps surface that exports the signed ledger to Datadog, CloudZero, Vantage and FOCUS. The Context OS opens up — WASM extensions, personas, plugin tools, Python/TS/Rust SDKs with a lockstep conformance matrix — while the dashboard reorganizes around the four jobs (decides · remembers · guards · proves). Underneath: a P0 security hardening series, attribute-safe dashboard escaping, MCP failures that finally set isError (#389), a cache-aware proxy that stops defeating provider prompt caching (#534), and a long tail of field-reported crash and correctness fixes.

Added

  • First-class agent identities (GL #433, H3 Epic D): core/agent_registry.rs + lean-ctx agent register/list/show/heartbeat/suspend/resume/decommission/offboard-owner/check. Agents become registered identities with a mandatory human owner (accountability principle), Ed25519 key binding, lifecycle states (decommission is final and audit-closed), best-effort attestation (binary + role-config hash, drift surfaces on heartbeat with exit 3) and SPIFFE-compatible workload ids (spiffe://<domain>/agent/<role>/<id>). Owner offboarding suspends all of an owner's active agents in one locked transaction (SCIM hook for ENT-2); every transition writes tamper-evident audit entries via four new additive OCP Part 4 event types. Registry is cross-process safe (advisory file lock). Docs: docs/enterprise/agent-identity.md with an honest attestation threat model.
  • Evidence Bundle v1 + standalone offline verifier (GL #425, H3 Epic A): lean-ctx audit evidence --from --to [--framework] exports a deterministic ZIP (evidence-bundle-v1 contract) — audit-chain segment, resolved policy pack, CGB + framework coverage reports, Ed25519-signed manifest; identical inputs produce byte-identical bundles. New independent verifier packages/leanctx-verify (no engine code, no network, 4 deps) replays the hash chain and validates signatures in five auditor-readable PASS/FAIL steps; mutation tests prove 1-byte flips, truncation and wrong keys are detected. Auditor guide: docs/enterprise/reading-evidence.md.
  • Framework compliance reports — EU AI Act, ISO 42001, SOC 2 (GL #424, H3 Epic A): machine-readable mapping matrices under compliance/mappings/*.toml (framework-edition pinned, semi-annual review cycle, explicit residual gaps) and three new builtin policy packs implementing the enforceable slice of each framework (eu-ai-act-deployer, iso42001-aligned, soc2-context). New lean-ctx policy coverage --framework <id> [pack] renders the audit-conversation artifact: every control as ENFORCED (live-verified against the resolved pack) / ENGINE (CI-proven guarantee) / GAP (documented organisational duty) — for the EU AI Act reference setup that is 11 of 14 controls technically enforced. Honesty is mechanized: every full claim must name a CI test (tests/compliance_frameworks.rs proves enforcement AND that violations are detectable — tampered logs fail verification, weak packs downgrade to NOT-ENFORCED), and a drift test fails the build when claims and tests diverge. Not legal advice; aligned ≠ certified.
  • Business plan — $149/mo flat, self-serve governance (GL #533, contract billing-plane-v3): new tier between Team and Enterprise with 50 flat seats, 20 GB hosted index, 10 managed connectors, private registry, org SSO via OIDC (new sso_oidc entitlement key, additive on every plan) and 365-day audit retention. Self-serve via lean-ctx cloud upgrade --plan business; existing subscribers are switched in place (prorated) instead of double-billed. SAML/SCIM (sso_scim) stays Enterprise. billing-plane-v1 remains frozen — v3 is a purely additive catalog delta.
  • Datadog/Prometheus FinOps export — metrics contract + scrape token (GL #401): /metrics now exposes verified ledger savings (lean_ctx_ledger_tokens_saved_total, lean_ctx_cost_saved_usd_total, 30 s cache over the hash-chained ledger) and a lean_ctx_info series carrying project/profile/agent_role/model/version tags (kube-state-metrics _info idiom — one series per process, no cardinality explosion). New LEAN_CTX_SCRAPE_TOKEN env: a read-only Bearer token valid only for GET /metrics, so monitoring agents never hold the dashboard credential. The exposition surface is frozen in docs/reference/metrics-contract.json, enforced by rust/tests/metrics_contract.rs (update via LEANCTX_UPDATE_METRICS_CONTRACT=1). Ready-to-import Datadog assets: integrations/datadog/ (OpenMetrics conf.yaml, Token-Economy dashboard, savings-drop + SLO-violation monitors), guide: docs/integrations/datadog.md.
  • lean-ctx finops export — CloudZero, Vantage & FOCUS cost export (GL #402): turns the hash-chained savings ledger into daily showback rows (day × project × agent × model × tool) with the model price pinned per event — no pricing table to maintain, reproducible forever. Targets: --target=focus (FOCUS 1.2 CSV, all 21 Mandatory columns + 1.0 compat set, passes the official FinOps Foundation focus-validator), --target=cbf (CloudZero AnyCost; --upload posts per-month Stream drops with replace_drop = idempotent re-runs), --target=vantage (custom-provider CSV; --upload posts multipart, additive semantics documented). Savings are emitted as Credit/Discount rows with negative cost — Usage spend stays clean for budgets. Guide: docs/integrations/finops.md.
  • Agentless Datadog push (GL #401): opt-in direct submit to the Datadog Metrics API v2 — LEAN_CTX_DATADOG_PUSH=1 and DD_API_KEY required (a stray API key alone never enables egress), DD_SITE + LEAN_CTX_DATADOG_INTERVAL_SECS optional. Counters go out as per-interval deltas (baseline cycle first — lifetime totals never spike a graph), gauges every cycle, all series tagged project/profile/agent_role/model/version. Runs as a background loop in lean-ctx dashboard.
  • Quality loop v1 — edit failures teach mode selection (GL #494): ctx_edit outcomes are now correlated with the last read mode of the file. An old_string miss after a compressed read (a) escalates the next auto read of that file to full (one-shot, 1 h TTL) and (b) feeds a per-(extension × mode) failure rate; pairs crossing the documented risky threshold (≥2 fails and ≥25 % fail rate, hysteresis exit <15 %) resolve to full until they recover. New resolver sources edit_fail_escalation / edit_quality_penalty, persisted in ~/.lean-ctx/edit_quality.json (bounded, 30 d decay), surfaced in ctx_metrics under "Edit quality". Contract: docs/contracts/quality-loop-v1.md. Golden test: rust/tests/quality_loop_golden.rs.
  • ctxpkg hosted registry — client side (GL #406): lean-ctx pack publish is real — preflight (parse, ed25519 signature, scoped-name check) then PUT to the registry at ctxpkg.com with a ctxp_… token (--token/CTXPKG_TOKEN). lean-ctx pack install ns/name[@version] resolves, downloads, verifies the artifact SHA-256 against the index, runs the standard import gates, re-verifies the signature locally and pins the result in .lean-ctx/ctxpkg.lock. lean-ctx pack export --sign signs bundles with an auto-managed ed25519 key (~/.lean-ctx/keys/ctxpkg-ed25519.key, 0600). Edge: account routes for namespace claim + publish-token lifecycle. Contract: docs/contracts/ctxpkg-registry-v1.md.
  • lean-ctx policy coverage — automated partial CGB assessment (GL #426): statically grades a resolved policy pack against the Context Governance Benchmark v1.0-draft — credential fixtures vs. redaction patterns, regulated-identifier classes, budget cap, retention, tool posture, egress restriction. PASS/FAIL/INCONCLUSIVE per aspect, --json for CI gating (exit 1 on FAIL), and an explicit honesty line instead of a maturity grade: 7 of 32 controls are statically checkable, the rest need the manual assessment.
  • Context Governance Benchmark — spec + self-assessment (GL #426): CGB v1.0-draft published as its own tool-neutral spec repo (context-governance-benchmark): 32 measurable controls in 6 domains (sensitivity/redaction, provenance, budget, audit/evidence, access scoping, lifecycle/retention), three levels (Basic/Hardened/Audited), maturity grades C1C4, CC-BY-4.0, RFC-light governance and a CI wordlist lint that bans product names from normative text. LeanCTX's own honest self-assessment lands in docs/compliance/cgb-self-assessment.md: C2 — Managed (Basic 96%, Hardened 80%, Audited 50%), with declared gaps incl. no independent redaction verification and no one-step egress inventory — graded down where claims couldn't be hard-verified.
  • Dashboard: one tabbed page per job area (GL #487, Redesign P2): the sidebar now carries six destinations — Home plus one entry per four-jobs area (Context, Memory, Protection, Proof, Project Map) — and each area is a single page whose views are tabs with canonical #area/tab deep links (#context/triage, #proof/roi, …). Every pre-#487 hash (#live, #health, #graph, …) still resolves and is rewritten to its canonical form; the last-used tab per area is remembered. New Protection area: the Guards tab hosts the existing reliability view, the new Risk & Policies tab shows live session-risk warnings (/api/context-risk) and the OWASP agentic-risk coverage map served by the new /api/owasp endpoint (same data as lean-ctx audit). The in-component Project-Map tab bar was removed in favour of the area strip.
  • Dashboard: four-jobs language pass (GL #488, Redesign P3): onboarding modal tells the four-jobs story (decides · remembers · guards · proves) with token savings framed as the receipt, includes Protection, and the status bar links the estimated figure to the signed ledger in Proof.
  • Agent-task benchmark v1 harness (GL #493): outcome evidence instead of token arithmetic — does lean-ctx change task success rate and cost per solved task? bench/agent-task/ runs two identical Claude-Code-headless arms (native vs. lean-ctx MCP, fresh HOME per run, hard-pinned MCP surface via --strict-mcp-config) over a deterministic SWE-bench-Verified subset (sorted round-robin by repo, frozen as tasks.lock.json), judged by the official SWE-bench evaluation; usage/cost come from the runtime's own final report — nothing is estimated. Pre-registered protocol with numbered amendments (PROTOCOL.md), self-hashing result artifact ready for ssh-keygen -Y sign; negative results publish unchanged.
  • LoCoMo memory benchmark harness (#291): a model-free, deterministic retrieval-recall benchmark over LoCoMo-style long conversations — every turn is stored as a memory, every question recalls top-k and is scored against the gold answers (answer containment, token-F1, exact match, recalled-context vs. full-transcript tokens). Ships a committed reference-suite with publishable numbers (benchmark/locomo/LOCOMO.md: 100% containment@5 at 29.4% token reduction), a locomo_bench binary for full-dataset runs, and a CI smoke test.
  • Context policy packs (GL #489): governance presets as code. A pack pins a team's context-governance expectations in reviewable TOML — default read mode, allowed/denied tools, named redaction regexes, audit-retention expectation, context-budget cap — with single inheritance (extends) whose semantics are security-first: denies and redaction accumulate down the chain, scalars override, allowlists replace deliberately. Five curated built-ins ship embedded (baseline, strict-redaction, finance-eu, healthcare, open-source); lean-ctx policy list|show|validate lists, resolves and lints packs (project pack: .lean-ctx/policy.toml). v1 is the format + tooling; runtime enforcement follows. Contract: docs/contracts/context-policy-packs-v1.md; guide: docs/guides/policy-packs.md.
  • Org audit log + retention (GL #484): a unified, append-only governance audit log for orgs, surfaced to the owner at /account/audit with a filterable table and CSV export. Every governance path now writes best-effort events (SSO config/verify/enforce/remove/login, invite create/redeem/revoke) into one org_audit_log; the retired SSO-only table is migrated and dropped by an idempotent boot migration. Retention is the owner-plan window from the billing-plane-v1 SSOT (Team 90 days, Enterprise ~10 years) and is enforced server-side both by a daily fleet sweep and on read, so an owner never sees a row older than they're entitled to keep. Reads are owner-only, cursor-paginated, and bounded. Contract: docs/contracts/org-audit-log-v1.md.
  • Org SSO (OIDC) (GL #482): self-serve single sign-on for Team and Enterprise orgs. Owners configure an OIDC provider (Okta, Entra ID, Google Workspace, any compliant OP) under Account → Billing, prove domain ownership via a DNS-TXT record (checked over DNS-over-HTTPS), and optionally require SSO for everyone — the owner stays password-exempt (break-glass). Members click Continue with SSO, authenticate at the IdP, and land in a normal session with just-in-time user + org-membership provisioning. Edge runs the Relying Party (Authorization Code + PKCE, discovery/JWKS cache, ID-token verification with nonce binding and HS*/none rejection); the control plane is the system of record (AEAD-sealed client secret, append-only billing_sso_audit). API keys never touch URLs — a single-use 60-second handoff code carries the session to the browser. Contract: docs/contracts/org-sso-oidc-v1.md; setup guide: docs/guides/org-sso-setup.md.
  • Team invite links (GL #385): owners mint one-time links (leanctx.com/join/?code=…) instead of copy-pasting tokens. Codes are 256-bit, stored hashed, expire after 7 days, and redeem exactly once (atomic claim; a failed seat check releases the claim for retry). The public join page issues the member token once, with prefilled CLI + MCP setup snippets; pending invites are revocable from the dashboard like member tokens. Redeem endpoint is rate-limited per IP and answers every dead code with one neutral 404. Contract: docs/contracts/team-invite-links-v1.md.
  • Device overview (GL #387): every authenticated Personal-Cloud push now carries an X-Device-Label header (the machine's hostname), tracked server-side as fire-and-forget display metadata — never auth, quota, or billing input. /account/cloud lists each machine with last sync, last surface and push count, plus a per-row Forget control (GET/DELETE /api/account/devices). Contract: docs/contracts/device-overview-v1.md.
  • Supporters wall + dashboard badge (GL #393): the public supporters wall is live end-to-end — Stripe checkout fields (display name, message, opt-in) are captured idempotently by the billing webhook, clamped to 60/140 chars, profanity-gated and served via the public GET /api/supporters edge; leanctx.com/support/ renders the wall client-side (plaintext-only, tier pills, newest first). Cancelling the subscription hides the entry on the next subscription.deleted webhook, and an internal-key moderation API (GET …/supporters/moderation, PATCH …/supporters/{id}) provides an audited kill-switch. Locally, the dashboard's support bar now swaps its ask for a thank-you when the machine is linked to a supporting account — served by the new /api/billing-badge endpoint from the cached plan only (no network, purely cosmetic, never gates a local capability).
  • Email digests (GL #386): the cloud server now sends a monthly Pro digest (tokens saved, agent actions, sessions, CEP score — from synced snapshots) and a weekly Team digest (net tokens, USD, actions, top model/tool — from the hosted server's savings summary). Idempotent per period with automatic catch-up and SMTP retry; silent when a period has no real data. Every email carries a one-click, login-free unsubscribe (hashed, rotating tokens); GET/PUT /api/account/digest exposes the preference to the dashboard. Contract: docs/contracts/email-digest-v1.md. Cloud-server CORS now allows PUT/PATCH (digest toggle + team settings).
  • Weekly team-ROI webhook (GL #388): team servers post a weekly savings summary (net tokens, USD, measured actions, 7-day window, top mover, top model/tool) to Slack, Discord, or any JSON webhook. Configured via roiWebhookUrl in team.json (https-only, validated at boot) or self-serve through the team dashboard's new Integrations card (PUT /api/account/team/settings → control plane re-renders the config). Posts once per ISO week with retry-on-failure; weeks without reported data stay silent — no synthetic numbers. Payload shape auto-detects the vendor (Slack text, Discord content, generic both).
  • Per-member savings drilldown (GL #389): new audit-scoped team-server endpoint GET /v1/savings/member/{signer} — one member's latest totals, model/tool breakdowns and a member-only 90-day cumulative series (carry- forward replay of that signer's snapshot history). Signer ids are validated against [A-Za-z0-9_-]{1,64} before any filesystem access; unknown signers are a clean 404. Proxied through the control plane (/api/billing/team/{id}/savings/member/{signer}) and the account edge (/api/account/team/savings/member/{signer}); the team dashboard's member rows are now clickable and open an inline drilldown panel (own series chart, top models, top tools). Contract: docs/contracts/billing-plane-v2.md.
  • model2vec static-embedding support (GL #452): the embedding engine now drives EmbeddingBag-topology ONNX graphs (model2vec exports like hf:minishlab/potion-base-8M) next to classic transformers. Topology is detected from the graph's input signature (input_ids + offsets) at load time; the adapter feeds flat ids + batch offsets, skips mean-pooling (the graph pools internally) and probes dimensions off the rank-2 output. ~500x faster inference at ~30 MB — built for initial indexing of large repos and semantic search on weak hardware. Live-verified end-to-end (256d, L2-normed, semantic sanity); guide section in docs/guides/custom-embeddings.md.
  • Minimal org model on the cloud plane (GL #468): team checkouts now create an organization with the buyer as owner; memberships inherit the owners' best active plan at the entitlements edge (never downgrading a personal plan) and /api/account/entitlements carries the org {id, name, role} for the dashboard's new organization section.
  • Zero-knowledge Personal Cloud vaults (GL #467): knowledge and gotchas now sync as client-side-encrypted blobs (XChaCha20-Poly1305, domain-separated HKDF keys knowledge-vault-v1 / gotcha-vault-v1 derived from the account API key the server only stores hashed). The first vault push purges the account's legacy plaintext rows; dashboards read the client-declared entry_count from blob metadata. Contract: docs/contracts/personal-cloud-encryption-v1.md.
  • Team server billing-plane endpoints (GL #463): GET /v1/storage reports the hosted workspace footprint (allocated-blocks sizing, hard links counted once, symlinks never followed, 60 s cache; camelCase per billing-plane-v2) and GET /v1/usage serves the unified snapshot — signed-ledger savings roll-up, measured toolCalls, and a snake_case storage block. Both audit-scope-gated like /v1/metrics; quota via LEANCTX_TEAM_STORAGE_QUOTA_BYTES. Unblocks the control plane's hourly Stripe metering job and threshold mails against real team servers.
  • lean-ctx doctor --migrate-check (GL #396): v1.0 migration-readiness audit — config.toml keys validated against the schema (free-form sections like ide_paths respected), active deprecations, data-layout writability, frozen-contract set. --json for fleet rollouts; exit 0 = "ready for 1.0". Plus the launch program docs: docs/releases/v1.0-runbook.md (RC/freeze/ bug-bash/rollback/launch-day plan), docs/releases/migration-1.0.md (zero-breaking-changes guide) and marketing/launch-v1/ (Show HN + Product Hunt drafts with tokbench-informed Q&A prep).
  • Custom embedding models (GL #397, upstream #328): ctx_semantic_search can now load any HuggingFace repo with an ONNX export via model = "hf:org/repo[@revision]" ([embedding] in config.toml or LEAN_CTX_EMBEDDING_MODEL). Includes revision pinning with an unpinned-warning, automatic dimension probing from the ONNX graph ([embedding].dimensions as declared fallback), per-repo+revision storage isolation, and SHA-256 lockfiles (model.lock.json, trust-on-first-use) that reject silent upstream content swaps. Model or revision changes trigger the established one-shot re-index. New guide: docs/guides/custom-embeddings.md.
  • SDK conformance matrix (GL #395): all three first-party SDKs (leanctx on PyPI, @leanctx/sdk on npm, lean-ctx-client on crates.io) now cover the entire public /v1 surface — added context_summary, search_events, event_lineage and metrics to every client. The shared conformance kit grows from 4 to 14 lockstep checks, including two drift gates: route_coverage (a server route without an SDK method fails within one CI run) and engine_compat (SDK declares its supported http_mcp contract versions). New CI job sdk-conformance runs all three kits against a real lean-ctx serve build via scripts/sdk-conformance.sh and publishes docs/reference/sdk-conformance-matrix.md (current state: 3/3 SDKs, 14/14 checks PASS). SDK majors follow the engine contract major. Completing the audit: live adapter smoke tests (OpenAI/LangChain/ LlamaIndex/CrewAI run one real tool round trip each against the live server, optional frameworks skip cleanly) and a release gate (scripts/check-sdk-versions.py, first job of the release workflow): an engine release fails hard when an SDK cannot speak the shipped http_mcp contract version, and warns on >1 minor SDK-family drift.
  • Contract freeze & SemVer/deprecation policy (GL #394): all 29 contract docs are now classified frozen / stable / experimental in a stability matrix (CONTRACTS.md, SSOT core/contracts.rs::contract_docs()). Two new CI gates enforce the freeze: tests/contracts_frozen.rs (every doc classified; frozen docs content-hashed against docs/contracts/frozen-hashes.json — semantic changes must land as a new -v2.md file) and tests/openapi_stability.rs (public /v1 surface vs. docs/reference/openapi-v1.snapshot.json; additive diffs pass, removed or mutated routes fail). GET /v1/capabilities additionally returns a contract_status map so clients can verify stability guarantees at runtime. The deprecation register DEPRECATIONS.toml (compiled into the binary, ≥ 2 minor releases between announcement and removal) feeds a new lean-ctx doctor check that warns about every deprecation shipping in the installed build.
  • Personal-Cloud auto-push (GL #384): opt-in lean-ctx cloud autosync on pushes the Pro surfaces (knowledge, commands, CEP, gotchas, buddy, feedback) silently once per day from the background task — offline keeps the day's slot open for retry, a Pro gate (402) consumes it quietly (no error spam on Free accounts).
  • Hosted Personal Index for Pro (GL #392): lean-ctx sync index push|pull|status syncs the project's retrieval index (BM25 + embeddings) across devices — a fresh machine gets working ctx_semantic_search without a local re-index. Bundles are encrypted client-side (XChaCha20-Poly1305; key HKDF-derived from the account API key, which the backend stores only as a hash): the server holds ciphertext it cannot read. Per-account quota from the plan's hosted_index_mb (Pro: 1 GB; open self-hosted deployments: 1 GB default), display-first — an over-quota push warns and blocks, it never bills. New backend routes PUT/GET/DELETE /api/sync/index/{project}
    • GET /api/sync/index; the Personal-Cloud dashboard payload gains a hosted_index block (projects, used bytes, quota). The local index is never gated (Local-Free Invariant; tests/local_free_invariant.rs). Contract: docs/contracts/hosted-personal-index-v1.md.
  • Hosted-index SLO gate (GL #391): the team server now measures every /v1 request in an outermost middleware and derives the three GA-gate signals — rolling p50/p95/p99 latency, availability (non-5xx share over the last 4096 requests) and index freshness (seconds since the last successful Index-scoped tool call). Exposed via /v1/metrics (new slo block) and /v1/metrics?format=prometheus (leanctx_team_* series for Datadog/ Prometheus scrape agents). New CLI: lean-ctx team slo-report --server <url> --token <token> [--json] renders the gate and exits non-zero on violation (CI-friendly). SLO definitions ship in docs/examples/team-slos.toml; the SLO engine understands the new metrics team_query_p95_ms, team_availability_pct, team_index_lag_seconds. Runbook: docs/guides/hosted-index-slo.md.
  • Accuracy conformance checks for lossy read modes (P1, GL #441): lean-ctx conformance now verifies structural invariants of map, signatures, aggressive and entropy against a fixed Rust fixture — determinism, symbol retention, body stripping, and real compression. CI gates on regressions in the modes agents rely on for correctness.
  • Honest metering on phase-isolated / non-caching workloads (#361): lean-ctx gain now states its denominator — savings are compression on lean-ctx-touched traffic, not the full provider bill — via a Methodology line and a new injected_overhead_tokens_per_turn field in gain --json (net bill impact = tokens_saved injected_overhead_tokens_per_turn × turns). New core::context_overhead measures the fixed per-turn prefix lean-ctx injects (tool schemas + server instructions + rules block). A new rules_injection = "off" (also none/disabled) writes no rules file — for hosts that supply their own steering, or phase-isolated/non-caching harnesses where the injected prefix is pure re-billed overhead. The performance-tuning journey gains a "workload fit" section documenting the proxy as the way to reach tool output the ctx_* tools can't wrap. Prompted by an independent, reproducible external benchmark.
  • Team RBAC roles (Commercial Plane, EPIC 13.2): a TeamRole (viewer/member/admin/owner) layer over the existing fine-grained TeamScopes. A token's effective scopes are scopes role.scopes(), enforced by the unchanged team middleware (zero new enforcement paths). Roles are monotonic (viewer ⊆ member ⊆ admin = owner). New CLI: lean-ctx team token create --role <role> (still supports --scopes, or both). Additive / Team-Cloud only — never gates local. SSO/SCIM, org-shared knowledge graph, and audit-retention dashboards build on this and remain tracked on the commercial plane. Contract updated: docs/contracts/team-server-contract-v1.md.
  • Billing plane: real plans + usage metering (Commercial Plane, EPIC 13.6): new core::billing turns the upgrade flow into real plans (free/team/ enterprise) with explicit Entitlements, plus usage-based metering derived read-only from the Ed25519-signed savings ledger (EPIC 12.20). Usage is privacy-preserving and only billable on a signed + intact chain. Crucially, entitlement_allows upholds the Local-Free Invariant — every local feature is allowed on every plan (incl. Free); the local binary has no entitlement checks (enforced by tests/local_free_invariant.rs). New CLI: lean-ctx billing <plans|entitlements|usage> [--json] (informational only). Quota semantics disambiguated: 0 = none, UNBOUNDED = unlimited. Checkout/ provisioning are documented as a hosted control-plane concern (no fakes). Contract: docs/contracts/billing-plane-v1.md.
  • WASM extension runtime (Context OS, EPIC 12.8 + 12.10): a sandboxed, language-independent way to contribute compressors and context providers as plain .wasm modules — no recompile of lean-ctx. Behind the off-by-default wasm Cargo feature (features.wasm_runtime in /v1/capabilities), upholding the Local-Free Invariant (free, compile-optional). Uniform ABI v1 (memory + alloc(i32)->i32 + entry(i32,i32,i32)->i64 packed ptr/len); guests run against an empty linker (no syscalls/network/fs/clock — sandboxed by the runtime itself) with a fresh Store per call for thread-safety + determinism. WasmCompressor registers as a first-class compressor (host-enforced byte budget, graceful fallback on traps, conformance-checked); WasmProvider registers as a first-class ContextProvider (lenient result-JSON mapping). Opt-in discovery from LEAN_CTX_WASM_DIR (*.wasm compressors; *.wasm + <stem>.provider.json sidecar providers). Contract: docs/contracts/wasm-abi-v1.md.
  • Context OS guide + non-coding cookbook (Context OS, EPIC 12.18): docs/context-os/guide.md maps the whole platform — principles (Local-Free Invariant), architecture, capability discovery, the four ways to build your own tool (SDK / plugin tool / hook / extension), ingestion+extractors+personas, the savings→ROI substrate, and the plane model. docs/context-os/cookbook-non-coding.md adds four runnable, verified recipes (lead-gen, research, support, data-analysis) plus a custom-vertical template, all using real personas/extractors/SDKs/adapters.
  • Framework adapters (Context OS, EPIC 12.6): leanctx.adapters exposes the lean-ctx tool surface to popular agent frameworks — OpenAI function calling (to_openai_tools / run_openai_tool_call, a pure transform with no extra dep), LangChain (to_langchain_tools), LlamaIndex (to_llamaindex_tools), and CrewAI (to_crewai_tools). Each framework is an optional, lazily-imported dependency (leanctx[langchain|llamaindex|crewai]); all adapters share one tool normalizer and the same call_tool_text path so they behave identically. Tested with/without each framework installed.
  • Python SDK (Context OS, EPIC 12.4): new leanctx package (clients/python/) — a thin, standard-library-only client (urllib, zero runtime deps) for the HTTP /v1 contract, mirroring the TS/Rust SDKs: health, manifest, capabilities, openapi, list_tools, call_tool/call_tool_text, and subscribe_events (SSE). Structured errors (LeanCtxConfigError/TransportError/HTTPError) and the shared run_conformance kit (lockstep with the TS SDK). Ships a README, pyproject.toml, in-process HTTP-server tests, and a python-sdk CI job.
  • TypeScript SDK GA + shared conformance kit (Context OS, EPIC 12.5): @leanctx/sdk gains capabilities() and openapi() for full /v1 discovery parity, a typed CapabilitiesV1, and a new runConformance(client) kit that returns a client-side scorecard (health, capabilities shape, OpenAPI shape, tools listing). The kit mirrors the server-side lean-ctx conformance and is kept in lockstep with the Python SDK so every client proves the same contract. Adds a README and tests.
  • Non-code compression tuning (Context OS, EPIC 12.14): two new compressors tuned for non-code corpora, registered in extension-registry-v1prose (collapse blank-line runs, strip/collapse intra-line whitespace, drop adjacent duplicate lines) and markdown (everything prose does plus strip HTML comments, drop image/badge syntax, and rewrite [text](url) links to their visible text). Both are deterministic and honor a hard byte budget (conformance-checked). Non-coding personas now default to them: researchmarkdown; lead-gen/supportprose.
  • Format extractors & chunkers (Context OS, EPIC 12.13): new core::extractors (extractors-v1) turns non-code documents/data into clean LLM text + structure-aware chunks. JSON (per array element / object entry), CSV/TSV (RFC-4180-aware, header-prefixed row groups), EML (salient headers + body, text/plain from multipart), HTML (rendered Markdown paragraphs, reusing web::html_to_text), and PDF (reusing web::pdf) — with a verbatim paragraph fallback for plain text. Every extractor is total/graceful (never panics, non-empty input always yields ≥1 non-empty chunk, deterministic). The text chunkers (csv/json/eml/html) register into extension-registry-v1, so they surface in /v1/capabilities and are conformance-checked.
  • Conformance & reproducibility scorecard (Context OS, EPIC 12.17): new core::conformance + lean-ctx conformance [--json] produce a Scorecard proving an instance honors its own contracts. Checks span three categories — contracts (all machine-verified versions present), reproducibility (/v1/capabilities and /v1/openapi.json are byte-deterministic), and extensions (every registered compressor/chunker/read-mode satisfies determinism, byte-budget, UTF-8, and coverage invariants — built-in and extension-provided). Exits non-zero on failure; gated in CI via tests/conformance_suite.rs. Contract: conformance-v1.
  • Extension trust & sandbox model (Context OS, EPIC 12.3): every plugin subprocess (hooks + manifest tools) now runs under a SandboxPolicy derived from a new [trust] manifest section (extension-trust-v1). Least privilege by default: the child runs with a scrubbed environment (fixed allowlist — host secrets in env never leak) and a working-directory jail, on top of the existing per-call timeout. Plugins declare capabilities (network, fs_write = consent surface, surfaced in /v1/capabilities; env_passthrough = opt out of env scrubbing). Unknown permissions are a fail-closed manifest error. Declared permissions appear per plugin under extensions.plugins[].permissions.
  • ROI / metering substrate (Context OS, EPIC 12.20): new core::savings_ledger::roi derives a RoiReport strictly from the signed savings batch (BatchTotals + committed chain head + Ed25519 signature) — adding derived metering metrics (net tokens, USD, per-event averages, top models/tools) and provenance (chain_valid, signed, signer key). This is the minimal, privacy-preserving aggregate the Cloud plane meters on: no raw events, paths, prompts, or code — only numbers and hashes — and it is read-only w.r.t. the local ledger. Exposed via lean-ctx savings roi [--json].
  • Plane separation + Local-Free-Invariant CI gate (Context OS, EPIC 12.19): the Personal (local) plane is now a documented, machine-checked boundary. core::server_capabilities classifies every feature flag as LOCAL_ALWAYS_ON, LOCAL_OPTIONAL (compile-only), or COMMERCIAL_PLANE (additive team/cloud). A CI conformance test (tests/local_free_invariant.rs) fails the build if the default plane isn't personal, any local capability isn't unconditionally free, the planes overlap, or a local capability reacts to a LEAN_CTX_LICENSE/LEAN_CTX_PLAN/LEAN_CTX_ACCOUNT env var; a unit test fails if a new feature flag is added without classification. Contract: docs/contracts/local-free-invariant-v1.md.
  • Built-in personas + persona-aware intent/terse (Context OS, EPIC 12.16): ships four non-coding presets alongside codingresearch, lead-gen (alias sales), support, data-analysis — each with its own tool surface, read-mode/compressor/chunker defaults, intent taxonomy, and sensitivity floor. The terse agent prompt is now persona-parametrized: non-coding personas append a domain vocabulary block + their intent list, while the coding persona leaves the prompt byte-for-byte unchanged (no regression). Available presets surface under presets in GET /v1/capabilities.
  • Context persona model (Context OS, EPIC 12.15): new core::persona (persona-spec-v1) — a declarative bundle that shapes the entire context surface for a domain (tool surface, default read-mode, compressor/chunker, intent taxonomy, sensitivity floor), not just coding. Personas are selectable via LEAN_CTX_PERSONA or persona = "…" in config, resolved against built-in presets then <personas_dir>/<name>.toml (override LEAN_CTX_PERSONAS_DIR). The built-in coding persona reproduces today's defaults — the tool surface still resolves to power when nothing is pinned (no regression), and explicit tool-profile settings always win. The active persona surfaces at GET /v1/capabilities under server.persona, with available presets under presets. Contract: docs/contracts/persona-spec-v1.md.
  • Native tool registration without forking (Context OS, EPIC 12.11): a plugin can declare [[tools]] in its manifest (name, description, command, timeout, JSON input schema). Enabled plugins' tools are discovered (PluginManager::tool_specs), adapted into native MCP tools (registered::plugin_tool::PluginTool), and registered dynamically in build_registry() — no fork, no code edit. They surface in GET /v1/capabilities under extensions.tools and in the agent's tool list, and run sandboxed through the shared subprocess runner (piped stdio, LEAN_CTX_PLUGIN_DIR/LEAN_CTX_TOOL env, bounded per-tool timeout). A plugin tool whose name collides with a native tool is skipped (native wins, so a plugin can never shadow core behavior). The hook executor and tool invocation now share one run_subprocess runner; an end-to-end test proves discover → register → invoke.
  • Pluggable read-modes / compressors / chunkers (Context OS, EPIC 12.9): new core::extension_registry (extension-registry-v1) exposes stable, object-safe traits — ReadMode, Compressor, Chunker — backed by a process-global registry seeded with real built-ins (full read-mode; identity/whitespace compressors; lines/paragraph chunkers) registered through the exact same public API extensions use (no special-casing). Extensions register custom transforms by name; the live registry contents now surface under extensions.{read_modes,compressors,chunkers} in GET /v1/capabilities.
  • Generic ingestion front-door (Context OS, EPIC 12.12): intake is no longer gated by is_code_file. A new core::ingestion front-door (ingestion-spec-v1) classifies every path by content kindCode / Document / Data / Text / Binary — via an extension fast-path plus a bounded binary sniff (NUL/control-byte ratio over the first 8 KB). So any text corpus (markdown, csv, json, yaml, html, email, logs, transcripts, even unknown-but-textual files) now reaches BM25/semantic/knowledge — not just source code. Genuine binaries (images, media, archives, compiled artifacts, and binary documents like PDF/DOCX whose extractors arrive in 12.13) are excluded. The duplicate is_code_file in the CLI indexer is removed; bm25_index::is_code_file remains the single canonical code detector, now one input to the front-door. Code repositories are fully backward-compatible — everything that indexed before still indexes.
  • lean-ctx-client Rust crate — the embedding boundary (Context OS, EPIC 12.2): a thin, stable HTTP client for the /v1 contract so any program (an agent harness, a lead-gen worker, a research bot) can integrate lean-ctx over the process boundary without linking the engine. It is the Rust counterpart of the TypeScript SDK (cookbook/sdk) and speaks the same versioned contract: health, manifest, capabilities, openapi.json, paginated tools, tools/call (raw result + flattened text), and events as a blocking SSE iterator. Open-ended documents are returned as serde_json::Value so new server keys never break a client build; errors carry the stable error_code (not the human message) for branching. The crate is deliberately decoupled — it does not depend on the engine crate, re-exports no internals, and documents its non-goals (full-crate linking stays unsupported; integration = process boundary). One small dependency (ureq), blocking by design, #![forbid(unsafe_code)], and covered by a dedicated CI job (fmt + clippy -D warnings + tests against a real localhost HTTP server + docs). Lives at clients/rust/lean-ctx-client.
  • Plugin hooks are now live in the core pipeline (Context OS, EPIC 12.7): the plugin seam that previously only existed in PluginManager is wired into the running server, so a third-party plugin can finally observe the engine without forking it. pre_read and post_compress fire around the central ctx_read choke point (carrying the path and the realized original → compressed token counts), and on_session_start fires once per server process (stdio + HTTP + daemon). Every firing goes through a zero-cost guard (PluginManager::has_listener / notify): with no plugin declaring a hook — the default — the hot read path allocates nothing and spawns no thread, so users without plugins pay exactly zero. Hooks run in the background with per-plugin error isolation and a per-hook timeout (a failing or slow plugin can never block or corrupt a read). The registry is initialized exactly once per process via the existing idempotent init(). An end-to-end test proves a real ctx_read triggers an installed plugin's pre_read hook, and a new LEAN_CTX_PLUGINS_DIR override lets containers/CI/tests point the registry at an isolated plugins root (distinct from the per-hook LEAN_CTX_PLUGIN_DIR the executor exports to a plugin's own child process). All five hook points are now live: on_session_start/on_session_end bracket each server process (the end hook fires synchronously at shutdown so it always runs before exit), pre_read/post_compress wrap reads, and on_knowledge_update fires when ctx_knowledge(action="remember") writes a fact (carrying category:key).
  • OpenAPI spec — GET /v1/openapi.json (Context OS, EPIC 12.1): the public /v1 surface is now described by an OpenAPI 3.0.3 document generated from a single in-code endpoint inventory (core::openapi), so SDK/codegen tooling in any language can consume it. A drift test (openapi_contract_up_to_date) binds the inventory to the Endpoints table in http-mcp-contract-v1.md, so a new public route must update both — code and docs can't diverge. Internal/experimental routes (agent registry, A2A, .well-known, shutdown) are intentionally excluded from the published spec.
  • Capabilities discovery — GET /v1/capabilities (Context OS, EPIC 12.1): a runtime discovery document so any client — in any language — can learn what a lean-ctx instance supports and branch on real features instead of making trial calls. Reports the contract version, server name/version, deployment plane (personal/team/cloud), wire transports, built-in presets (personas), read_modes, the tools surface, a features map (always-on capabilities plus compiled Cargo features like semantic_search/team_server/cloud_server), runtime-discovered extensions (plugins), and all machine-verified contracts versions in one place — no secrets ever included. Versioned by capabilities-contract-v1 (CAPABILITIES_CONTRACT_VERSION); the documented key set is bound to the code SSOT (core::server_capabilities) by a drift test, and a formal /v1 deprecation policy is documented alongside the contract.
  • MCP Tool-Catalog Gateway — ctx_tools (the answer to "more tools → less adoption"): lean-ctx can now sit in front of any number of downstream MCP servers and expose them through a single meta-tool instead of injecting every downstream schema into the system prompt. The agent calls ctx_tools find with a natural-language need; the gateway aggregates the downstream catalogs (TTL-cached), ranks them with the same BM25 engine as ctx_search, and returns a top-N ChoiceCard shortlist (server::tool + one-line description + key params). ctx_tools call then proxies the real call to the owning server and returns its (firewall- and sensitivity-filtered) result. Net effect: unlimited downstream tools at roughly constant context cost. Transports: local stdio (spawns the server as a child process) and remote streamable HTTP (with custom headers / bearer auth) — built on the official rmcp client, no bespoke JSON-RPC. Global-only config and off by default ([gateway] / [[gateway.servers]]); spawning downstream processes can never be enabled by an untrusted project. Granular tool surface → 72.
  • Per-item sensitivity policy floor ([sensitivity]): classify every context item as public < internal < confidential < secret (path heuristics + secret/PII detection incl. Luhn-validated cards and ISO-7064 IBANs) and enforce a uniform floor before content ever reaches the model — redact (mask the spans) or drop (withhold the item). Applied uniformly to tool outputs and knowledge facts. Global-only and off by default.
  • Reproducible scorecard — lean-ctx benchmark scorecard: a deterministic, machine-independent report of compression savings, retrieval recall/MRR, and latency over a synthetic, byte-reproducible corpus. The JSON and human output embed a determinism_digest, so two runs of the same code anywhere produce the same fingerprint — the artifact is self-verifying. Wired into CI as an uploaded artifact.

Changed

  • Parallel dashboard tracks consolidated (GL #476#479, #486, #490): the four-jobs IA from the redesign epic and the incremental UX/data passes that shipped in parallel now live on one branch. The epic layout wins (slim Home, Proof group with ROI & Plan + Trends, Simple = Home only); the data passes win correctness and language — relative search scores (top hit = 100%), the verified-bridge line in the Home hero (estimated ⇄ signed ledger), Context Triage / Context Contents / Episodes labels, estimate-methodology tooltips, per-task episode metrics, the dead Symbols signature column removed and vendor noise filtered from the Compression Lab. Search keeps the inline ±12-line preview and gains an "Open in Lab →" handoff. On the Rust side ctx_search now returns a SearchOutcome that separates the modeled native-grep baseline (estimated stats) from raw observed tokens (verified ledger), so the two series can never cross-contaminate.
  • Four-jobs cockpit navigation + slim Home (GL #470/#486, phase 1): the sidebar now tells the same story as the website — Context (decides what agents read), Memory (remembers what agents learn), Proof (proves what you save) and Project Map (understands your codebase) — instead of 17 flat entries. Simple mode is the 5-second answer: Home only. Home itself slimmed down to status strip + receipt + gauge/triage + one trend + top-3 commands (expandable); the cost-analysis card moved to ROI & Plan (labelled as the estimated, all-time view next to the verified-ledger methodology) and the MCP-vs-shell / task-breakdown doughnuts moved to Trends. Every view stays reachable via Advanced mode, deep links and the command palette.
  • Large modules split by domain (P1, GL #439, #440): cli/dispatch/analytics.rs (1685 LOC) → analytics/{gain,savings,billing,graph}, core/stats/format.rs (1532) → format/{util,cep,dashboard,views}, rules_inject.rs (1542) → rules_inject/{content,targets,detect,write,skills}. No behavior change; entry-point visibility narrowed to the dispatch layer.

Fixed

  • Scorecard determinism restored (#211 contract): benchmark entropy numbers fed the scorecard's reproducibility digest through the regular compression path, whose opportunistic semantic redundancy filter (#544) kicks in as soon as the shared embedding engine finishes loading — two runs in the same process could disagree (e.g. entropy=0.00 vs 57.29 on the small corpus). Benchmarks now pin the filter off via the new entropy_compress_deterministic, keeping the digest machine-independent (and cutting the determinism test from 25 min to 4 s).
  • Signed artifacts always embed the key that actually signed them: every signer that embeds its public key next to the signature (handoff transfer bundles, evidence bundles, wrapped publish) previously resolved the keypair twice — once to sign, once to read the public key. If the key store moved or the key was regenerated between the two reads (concurrent data-dir changes, parallel processes), the artifact carried a public key that could never verify its own signature. New atomic agent_identity::sign_with_public_key / sign_bytes_with APIs resolve the keypair exactly once; all three call sites migrated. (pipeline red since the #551 efficiency program landed): the try_shared_engine_returns_none_when_not_initialized unit test asserted on the process-global SHARED_ENGINE OnceLock while the new #551 background activation (triggered by any sibling test touching entropy compression) could load — and in CI even download — the model mid-suite. The test now lives in its own integration-test binary (tests/embeddings_shared_engine.rs, fresh process = deterministic), ensure_engine_background() is a no-op under cfg!(test), and CI exports LEAN_CTX_EMBEDDINGS_AUTO_DOWNLOAD=0 so the suite is hermetic. Also un-sticks the Coverage job: the silent engine load made run_project_benchmark("src") exceed tarpaulin's 180 s timeout.
  • ctx_shell/ctx_execute failures now set MCP isError + structuredContent (GitHub #389): every tool call returned CallToolResult::success regardless of the shell exit code — MCP clients (OpenCode guards, Claude Code, Cursor) had no programmatic way to detect failures and were forced to regex-parse the [exit:N] text footer. A new ShellOutcome (Exit(code) | Blocked) now flows from the shell tools through dispatch into the MCP result: non-zero exit sets isError: true + structuredContent: {"exitCode": N}, allowlist/ validation rejections set isError: true + {"blocked": true}. Covered end-to-end: the degraded session-lock path (which previously even dropped the exit footer), the auto-checkpoint early return, the reference-store substitution, ctx_call chaining, and ctx_execute (single/batch — first failing task fails the batch — and file preconditions). Exit 0 stays byte-identical (no metadata churn).
  • OpenClaw: setup --auto re-injected the legacy mcpServers key and broke 2026.6.1+ hot-reload (GitHub #390): OpenClaw moved to a nested mcp.servers schema with strict validation; the editor-registry writer still wrote top-level camelCase mcpServers, so every watchdog tick produced config reload skipped (invalid config): Unrecognized key — with gateway-down risk on restart if the stale block won. OpenClaw now has a dedicated ConfigType::OpenClaw writer: it detects the version via meta.lastTouchedVersion (>= 2026.6.1 or an existing mcp.servers block → nested schema; older → legacy camelCase), migrates our stale mcpServers.lean-ctx entry away (dropping the key when empty, foreign entries preserved), and is strictly idempotent — watchdog re-runs leave the file byte-identical (verified via mtime). init --agent openclaw, setup --auto, lean-ctx doctor (flags stale legacy blocks) and both uninstall paths (editor-registry + textual lean-ctx uninstall, which now also strips an emptied mcpServers {} leftover) share the same schema logic. Invalid JSON is never text-injected for openclaw.json — a malformed write would take the gateway down.
  • Shell parser: >| noclobber redirect treated as a pipe (GitHub #387): date --fsdfs >| out 2>&1 split at the |, so the redirect target (out) was checked against the shell allowlist as a command and blocked. The segment splitter now recognises >| as a redirect operator; file-write targets are never allowlist-checked.
  • gain --deep crash on multibyte paths/agent ids (GitHub #386): every display truncation helper (ctx_gain::truncate_str / shorten_path, stats::format::truncate_cmd, ctx_architecture hotspot paths) sliced at byte offsets and panicked mid-codepoint for umlauts/CJK/emoji; one helper could also underflow for tiny widths. All cuts are now char-boundary-safe (swept 0..=len+2 in tests).
  • report-issue now embeds the crash log (GitHub #386 follow-up): the last 3 entries of <data_dir>/logs/crash.log (location, payload, truncated backtrace) ship with every report, so panic reports are actionable instead of arriving empty.
  • SIGABRT coredumps from the panic hook itself (GitHub #378): the process-wide panic hook used eprintln!, which panics on I/O errors — when a background worker's stderr was gone (terminal closed → EPIPE), any ordinary panic became a double panic and the runtime aborted the whole process (38 coredumps reported). The hook now writes its message best-effort (write_all, errors ignored) and wraps the crash-log write in catch_unwind; a panic can never escalate to SIGABRT through the hook anymore.
  • MCP token footprint: installers no longer force the full toolset (GitHub #385): every generated MCP config carried LEAN_CTX_FULL_TOOLS=1, advertising 69+ tool schemas (~15k tokens) to the client on every turn — lean-ctx showed up as one of the biggest token consumers in users' own usage breakdowns. New installs/refreshes now use the core toolset (13 tools + ctx_call/ctx_expand for on-demand access); opt back in via tool_profile = "power" in config.toml or LEAN_CTX_FULL_TOOLS=1 in the server env.
  • Pi: stale ~/.pi/agent/mcp.json entry defeated the embedded bridge (GitHub #361, found by the tokbench independent benchmark): Pi has no native MCP adapter, but init --agent pi wrote a lean-ctx mcp.json entry that older pi-lean-ctx versions read as "adapter configured" and disabled their embedded MCP bridge — the session cache silently never engaged. The installer no longer writes that entry anywhere (hooks path + editor-registry target + setup target all removed) and init --agent pi migrates existing configs by deleting the stale entry (file removed entirely when lean-ctx was its only content).
  • Uninstall: perfect-clean guarantee (GL #558, Discord report): lean-ctx uninstall now leaves zero artifacts behind. Backup sweep covers installer subdirectories (hooks/, rules/, skills/, steering/, VS Code User/, .gemini/antigravity-cli) and project-local CWD config dirs; lean-ctx-owned script backups and orphaned config backups are removed; {"hooks": {}, "version": 1} boilerplate shells are deleted instead of kept; now-empty installer directories are swept as the final filesystem step (non-empty dirs survive untouched); platform data dirs (~/Library/Application Support/lean-ctx, %LOCALAPPDATA%\lean-ctx, ~/.local/share/lean-ctx) are removed. Verified end-to-end: 8-agent install + proxy enable → uninstall → 0 lean-ctx references, 0 .bak files, 0 leftover dirs.
  • Claude rules file regression (GL #555 follow-up, GL #558): rules_inject still wrote the always-loaded ~/.claude/rules/lean-ctx.md on init --agent claude, undoing the token-footprint fix. Claude Code no longer gets a rules target — the CLAUDE.md block + on-demand skill carry the guidance.
  • Setup .bak churn (GL #558): re-running setup/init no longer rewrites identical hook scripts, so no backup files pile up for unchanged content.
  • Audit chain forked under concurrent processes (found via GL #425 E2E): prev_hash came from a per-process cache, so two processes appending simultaneously both chained onto the same parent (and could interleave half-written lines). record() now takes an exclusive advisory file lock and reads the chain tail from the file itself; regression test runs 4 concurrent writers and demands one valid 100-entry chain. The evidence generator additionally splits historic glued lines losslessly and refuses unparseable data inside an attested period.
  • Claude Code: instruction footprint cut from ~12k to <500 tokens (GL #555): the ~/.claude/CLAUDE.md block imported the full ruleset via @rules/lean-ctx.md and the project AGENTS.md block via @LEAN-CTX.md. Claude Code expands @-imports inline at launch and loads every rules file without paths: frontmatter unconditionally — stacking the same ruleset up to three times per session (field reports: 12.3k tokens of memory files before the first message). The CLAUDE.md block is now self-contained (v3, no imports), the AGENTS.md block carries a 3-line inline mapping with a plain-text pointer, and the always-loaded lean-ctx-owned rules files (~/.claude/rules/lean-ctx.md, project .claude/rules/lean-ctx.md) are removed on update (marker-checked) — deep documentation lives in the on-demand lean-ctx skill.
  • Claude Code: compactions now actually reset the re-read cache (GL #555): every Claude hook payload carries session_id, so the generic session catch-all matched before the compaction check — hook_event_name: "PreCompact" was never recorded and sync_if_compacted() never reset full_content_delivered flags. After a host compaction, ctx_read kept answering [unchanged] stubs that pointed at evicted context, and agents recovered by switching to native Read for the rest of the session. PreCompact is now detected ahead of the catch-all (regression-tested with the real payload shape), so the first re-read after compaction delivers full content again.
  • Tool schemas hardened for strict validators (GL #545): 20 tool schemas (incl. ctx_expand) declared type: object + properties without an explicit required array — valid JSON Schema, but strict Pydantic-based backends (OpenAI, Azure, SGLang) reject it and OpenCode surfaces Invalid schema for function 'lean-ctx_ctx_expand': None is not of type 'array'. Every advertised schema (built-ins and plugin manifests) now passes normalize_for_strict_validators(): recursive explicit required: [] on object schemas and items on array schemas, at every nesting level. Regression gate: rust/tests/tool_schema_strictness.rs walks the whole registry.
  • Windows: proxy/daemon survive AI-client MCP recycling (GL #545): the auto-started proxy and daemon were spawned as plain child processes. On Windows they inherit the parent's console and Job object; AI clients (OpenCode, Codex, Claude Code) run MCP servers inside kill-on-close Jobs, so recycling the MCP process silently killed the proxy mid-flight — observed as Cannot connect to API: The socket connection was closed unexpectedly, cold-start latency and agents falling back to native tools. Background spawns now use ipc::process::spawn_detached() (DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB, graceful fallback when the Job denies breakaway). No behaviour change on macOS/Linux.
  • Proxy history pruning defeated provider prompt caching (GL #534): the Anthropic/OpenAI proxy handlers summarized everything older than the last 6 messages on every request. That rolling boundary rewrote a previously-stable message each turn, so the provider's prefix-matching prompt cache (Anthropic cache_control, OpenAI automatic caching) missed from that point on — users saw uncached input jump from ~210k to 80100k+ tokens per turn (cache writes at 1.25× instead of reads at 0.1×). History is now pruned at a frozen, cache-aware compaction boundary that only advances in deterministic 16-message strides (≥8 recent messages always intact): between jumps the request prefix stays byte-identical and the prompt cache keeps hitting; a jump costs one re-write, then caching resumes on the smaller history. Pruning is content-deterministic and preserves cache_control breakpoints; tool-result compression is prefix-stable and unchanged. New [proxy].history_mode config key / LEAN_CTX_PROXY_HISTORY_MODE env: cache-aware (default), rolling (legacy max-savings), off. Invariant locked by a byte-stability test simulating 80 growing turns.
  • ctx_edit evidence diff corrupted by terse post-processing (GH #382): the evidence (diff) block embeds verbatim source lines, but the generic terse stage still ran over ctx_edit output — dictionary abbreviation (return 0ret 0), blank-line stripping and line-score filtering silently dropped/mangled diff lines, making agents conclude a correct edit went wrong (the file on disk was always right). Two-layer fix: ctx_edit joins the read family in the terse exemption, and the terse pipeline itself is now fence-aware — content inside ``` / ~~~ fences passes through byte-exact while surrounding prose still compresses, protecting every current and future tool that embeds code blocks.
  • CI green again across all three OS runners: the billing-catalog golden fixture now normalizes CRLF before comparing (Windows autocrlf checkouts), the path_resolve CWD-independence test canonicalizes both sides before comparing (macOS /var symlink, Windows 8.3 short names), the team_billing module doc no longer intra-doc-links a private const (rustdoc -D warnings), and the six new org/cloud contract docs are classified (Experimental) in contract_docs(). The frozen team-server-contract-v1.md is restored byte-exact; its additive storageQuotaBytes/roiWebhookUrl keys moved to a new team-server-contract-v2.md (Stable), per the contract-file rule. Second wave: the CLI fidelity/pipe-guard integration tests pin LEAN_CTX_ALLOWLIST_WARN_ONLY=1 (they assert compression behavior, not enforcement — on CI stderr is no TTY, so the new agent-mode allowlist blocked their for/while test scripts with exit 126), the ISSUER_CACHE/ATTEMPTS statics are documented in LOCK_ORDERING.md (L45/L46), and docs/reference/generated/mcp-tools.md is regenerated for the ctx_agent brief/return actions and ctx_knowledge as_of.
  • Cockpit backlog triple (GL #454, #455, #456): the Routes view now understands axum — .route("/path", get(handler)) incl. chained methods (get(a).post(b)), qualified forms (axum::routing::post) and module-path handlers — plus hand-rolled "/api/…" => match routers, taking this codebase from 0 to 136 detected routes. The Call Graph starts framed: an initial zoom-to-fit runs once the force layout settles (manual pan/zoom is never overridden) and link opacity now fades with edge density, so 150-node graphs stop rendering as an over-zoomed hairball. And when token auth is on but the browser has none, the first 401 swaps the page for a single centered token prompt (validates against /api/health, stores in sessionStorage, reloads) instead of two dozen raw unauthorized cards.
  • Dashboard polish from the function audit (GL #478): the Explorer tree is now a real WAI-ARIA tree — role=tree/treeitem/group, aria-expanded, roving tabindex and full keyboard support (arrows expand/collapse/navigate, Enter/Space toggle, Home/End jump) with a visible focus ring. Search results stopped pretending: clicking a hit opens an inline file preview (±12 lines around the match, hit line highlighted) served by the existing compression-demo endpoint, with full keyboard access. Procedures now auto-learn: every recorded episode re-runs workflow detection (procedural_memory::auto_detect_from_episodes), so recurring tool sequences appear on the Memory page without anyone calling detect by hand. The status-bar daemon indicator finally explains itself — the tooltip describes what green/red means and how to recover (lean-ctx serve -d).
  • Data truthfulness (GL #479): the dashboard now tells the whole story behind its savings numbers. The verified ledger covers measured shell and search compression (cli_shell, ctx_shell, ctx_search events with raw, unmultiplied baselines) instead of only ctx_read — closing the unexplained 24x gap between Home and the ROI view. The 2.5x native-grep counterfactual used by the estimated stats is now a documented, named constant (NATIVE_SEARCH_BASELINE_FACTOR), surfaced in the Home tooltips and in a new "Methodology: verified vs. estimated" card on the ROI view. Inferred agent activity no longer shows negative ages on UTC+N machines (event timestamps are local wall-clock and are now interpreted as such).
  • No more WARN noise when scanning project subdirectories (P1, GL #438): graph_index now walks ancestors for project markers, so repo/rust/src inside ~/Documents is a legitimate scan root (the .git lives two levels up). Marker-less trees under blocked home dirs stay refused.
  • Windows symlink parity at every security boundary (P1, GL #442): pathjail, ctx_edit, config_io and read_file_nofollow now reject NTFS junctions and all other reparse points (not just symlinks) via the shared pathutil::is_symlink_or_reparse check; non-Unix read_file_nofollow previously followed links without any check.
  • Stale cache stubs can no longer mislead the agent (P0-7, GL #419): staleness now treats any mtime change as stale (backward mtimes from git checkout previously read as fresh) and verifies the content hash before serving an [unchanged] stub when the mtime claims no change (same-second writes, restored timestamps). Opt out: LEAN_CTX_CACHE_VERIFY=0.
  • Panics are now diagnosable after the fact (P0-8, GL #420, upstream #378): every panic appends thread, location, payload and backtrace to ~/.lean-ctx/logs/crash.log (0o600, size-rotated) — stderr-only reporting was lost for daemon/LaunchAgent/MCP-child processes.
  • Copilot CLI hooks work on Windows (#381): the generated hook entries carried only a bash command — but Copilot CLI runs the powershell field on Windows, so the hooks had no runnable command there, errored, and made the CLI reject every tool call. Entries now carry both fields, each with a quoted binary path (bash gets the MSYS-style conversion; powershell uses the call operator — Windows install paths routinely contain spaces). Also, global hooks were written to ~/.github/hooks/hooks.json, a location Copilot never reads: they now go to the documented user-level ~/.copilot/hooks/hooks.json (honoring COPILOT_HOME), existing pre-#381 configs are upgraded in place (missing-powershell detection), and lean-ctx entries are migrated out of the stale legacy file (deleted when it was ours alone, foreign hooks preserved).
  • Dashboard "ROI & Plan" view is live, not a frozen snapshot (user-reported): the view fetched /api/roi exactly once per navigation — the cockpit's 10 s poll only refreshed the status footer, and the lctx:refresh event was only dispatched by the manual ↻ button. Sitting next to the live-updating footer, the static ROI numbers looked broken. The ROI view now re-fetches on the same 10 s cadence while it is the active view, flicker-free (the "Loading…" placeholder renders only before the first payload; background refreshes swap content in place, guarded against overlapping fetches) and shows a muted "Updated HH:MM:SS · auto-refreshes every 10 s" line so liveness is visible. Drive-by: the Commander view's lctx:refresh listener was the only one without an active-view guard (and was never removed on disconnect) — it now follows the standard guarded pattern.
  • proxy enable no longer breaks Claude Pro/Max subscriptions (community-reported): the proxy forwards the caller's credential upstream but never injects one, so it can only compress Claude traffic in API-key (pay-as-you-go) mode. A Claude Pro/Max subscription authenticates via OAuth directly against api.anthropic.com, and that token is rejected by any custom ANTHROPIC_BASE_URL — so unconditionally pointing ~/.claude/settings.json (and the shell ANTHROPIC_BASE_URL export) at the local proxy produced a login loop / 401 the moment Claude Code started, while OpenAI-compatible backends (Ollama, Codex) kept working. proxy enable now detects whether an Anthropic API key is available (ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN in the environment, or an apiKeyHelper/key in ~/.claude/settings.json) via anthropic_api_key_available() and, when none is found, skips the Claude redirect (leaving Claude Code on Anthropic directly), omits the ANTHROPIC_BASE_URL shell export (OpenAI/Gemini exports are unaffected), and repairs any pre-existing stale local redirect. It prints a clear explanation and points subscription users to the ctx_* MCP tools for savings; --force overrides for keys stored where we can't probe (e.g. a keychain). lean-ctx doctor gained a check that flags an enabled proxy still routing Claude through the proxy without an API key, with the exact fix (proxy disable, or export a key + re-enable). Documented in docs/reference/05-advanced.md.
  • Shell-output redirected to a file is always byte-faithful — compression never corrupts cmd > out: when compression was forced (the agent shell hook runs lean-ctx -c, and the hook deliberately bypasses its own [ ! -t 1 ] pipe guard for agents), the compressed digest was written into a real file on a redirect — so git status --short > files.txt, git diff > patch.txt, cmd >> log, etc. landed an abbreviated/deduplicated summary instead of the exact bytes, producing contradictory diffs and silently dropped lines for any downstream tool that re-read the file. exec() now detects when stdout is a regular file (fstat/handle metadata via std, no new deps) and passes the output through verbatim even under LEAN_CTX_COMPRESS/-c. This is enforced at the single exec choke point, so it holds for every caller (shell hook, direct CLI, Pi/MCP bridges) and every redirect form; pipes (an agent's captured stdout) and TTYs are unaffected and keep compressing. Regression-tested both ways: a redirect-to-file is byte-identical to the raw command while the same command + env stays compressed when piped.
  • Agent hooks always use an absolute binary path (#367): generated hook commands (Codex, Cursor, Claude, Gemini, Antigravity, …) emitted a bare lean-ctx, which fails with exit 127 when the host runs the hook under a non-login shell whose PATH lacks the install dir. resolve_binary_path() now always resolves to the absolute path (matching MCP setup / doctor); stale bare-command configs are rewritten on the next init / doctor.
  • Proxy forwards the OpenAI-Project header (#366): project-scoped OpenAI keys carry their scope via OpenAI-Project (sent by OpenCode and the OpenAI SDK on the Responses API). The proxy's request-header whitelist dropped it, so the upstream rejected the call with Missing scopes: api.responses.write. openai-project (and openai-organization) are now forwarded verbatim.
  • gemini setup installs the Antigravity CLI plugin hooks (#284): lean-ctx init --agent gemini configured the Antigravity CLI MCP target but never wrote its plugin hooks, so hooks landed only in the legacy ~/.gemini/settings.json that agy ignores. The gemini path now also installs the agy plugin (~/.gemini/config/plugins/lean-ctx); auto-detect already covers the standalone antigravity-cli target.
  • The Antigravity CLI plugin is a self-contained, spec-"compliant" bundle (#284): the agy plugin lean-ctx writes (~/.gemini/config/plugins/lean-ctx/) now ships its own mcp_config.json next to plugin.json + hooks/hooks.json, so the ctx_* tools travel with the plugin and it validates clean under agy plugin validate (✔ mcpServers, ✔ hooks). This was verified against the real agy binary, which stages plugins to exactly this path and shape via agy plugin install — i.e. the reporter's documented ~/.gemini/antigravity-cli/plugins/<name>/ + root hooks.json layout is what the docs say, but agy v1.0.x actually reads ~/.gemini/config/plugins/<name>/ with hooks/hooks.json (the doc's own "global plugins" section agrees). The profile copy (~/.gemini/antigravity-cli/mcp_config.json) is kept for back-compat; agy keys MCP servers by name, so the dual definition is harmless. Root-cause note for the "hooks still not firing" reports: hook execution in agy is gated by its own server-side feature flag enable_json_hooks (a proto field applied via applyFeatureProviderJSONHooksConfig; experiment json-hooks-enabled) and cannot be forced from a local ~/.gemini/config/config.json (verified). lean-ctx therefore installs the hooks in the precise location/format agy expects and they light up automatically once that flag reaches the account — note agy -p print mode bypasses the hook subsystem entirely (hooks run in interactive sessions only). lean-ctx doctor integrations now verifies the full bundle (plugin.json + hooks/hooks.json + plugin-local mcp_config.json) so install and doctor stay in lockstep and doctor --fix repairs any drift.
  • CEP meter counts cache hits and sessions for long-lived servers (#361): cep.sessions and total_cache_hits could stay 0 even with confirmed cache activity — the meter only recorded on an auto_checkpoint that a short workload may never reach, and repeated snapshots within one process dropped the cumulative cache-hit/read delta (only the first snapshot's value was kept). CEP is now recorded on the live-stats cadence (so even brief sessions register) and accumulates per-snapshot deltas, so lean-ctx gain reflects real cache savings.
  • Pi: no envelope overhead on tiny reads (#361): a ctx_read of a very small file appended a "Compressed N → N tokens (0%)" footer even when nothing was saved, making the payload larger than the source. The footer is now suppressed when there is no actual saving (compression stats are still recorded for telemetry); cached re-reads and genuinely compressed reads keep their footer.
  • ctx_smells dead-code no longer flags instantiated classes (#365): added an end-to-end regression test (build graph → scan) confirming imported-and-instantiated Python classes are not reported as dead code while a never-referenced class still is — locking in the symbol-level call/import edges the graph builder creates.
  • ctx_read is byte-faithful — the terse layer no longer mangles file reads (reported via a community A/B code-review evaluation): the server's post-dispatch terse stage (prose dictionary returnret, stringstr, … plus line-score filtering) was skipped for reads only when the read had already saved tokens. A verbatim mode="full" (or lines:) read saves 0 tokens, so it was silently routed through the prose compressor — abbreviating keywords and dropping repeated lines. This violated the full contract ("guaranteed complete content"), corrupted source the agent edits against, and could drop the exact cross-file lines needed for data-flow review. skip_terse now skips the whole read family (ctx_read, ctx_multi_read, ctx_smart_read, ctx_compress, ctx_overview) unconditionally; reads keep only their own mode-aware, structure-preserving compression (map/signatures/aggressive).
  • An explicit read always returns content, never a stored-reference stub (same report): the ephemeral context firewall already exempts file reads, but the opt-in reference_results path did not — enabling it turned a large ctx_read into an [Reference: …] Output stored … preview the agent could not edit against. A single firewall::is_protected_read predicate is now the source of truth for "an explicit read returns content," honoured by both the firewall and the reference-results path, so ctx_read/ctx_multi_read/ctx_smart_read are never stubbed regardless of config.
  • Generated artifacts always reference the running build — autostart / MCP / hooks can't diverge (#2444): resolve_portable_binary() (which backs the daemon + proxy autostart plists, the daemon spawn, the MCP server command, agent + shell hooks, and the update scheduler) resolved which lean-ctx first, so the baked path depended on ambient PATH ordering at generation time. On a machine with both a Homebrew and a ~/.local/bin install this was non-deterministic — the daemon LaunchAgent captured the stale Homebrew copy while the proxy/MCP config captured ~/.local/bin, silently running two different builds at once. The decision is now a pure, unit-tested choose_binary_path() that prefers the currently-running executable (current_exe()), falling back to PATH only when the running binary lives in a transient Cargo build dir (cargo run -- setup, where the installed copy is the intended target). Keeps generated hook commands absolute (#367).
  • MCP server can no longer go dark — every tool handler runs under a watchdog (#271): the recurring TypeError: Cannot read properties of undefined (reading 'invoke') was the client losing its tool handles after the server stopped replying. Root cause: handlers were dispatched via tokio::task::block_in_place, which pins one of the few core async workers and — being synchronous — cannot be interrupted by a tokio::time::timeout on the same task, so a handler that blocked (e.g. the nested block_in_place inside ctx_multi_read exhausting the blocking pool under concurrent reads) silently swallowed the JSON-RPC response. Every handler now runs on the dedicated blocking pool via spawn_blocking, awaited under a watchdog deadline (LEAN_CTX_TOOL_TIMEOUT_SECS, default 120s; ctx_shell/ctx_execute exempt): core workers stay free for the stdio loop and on timeout/panic the server returns a clean error instead of dropping the reply. The specific nested block_in_place in ctx_multi_read is also removed at the source (now bounded_lock + panic guard). Covered by a 16-way concurrency stress test through the full dispatch path plus timeout/panic unit tests.
  • SIGABRT crash in the background indexer — deep ASTs no longer overflow the stack (#378): graph indexing aborted the whole daemon on files with deeply nested syntax (machine-generated source, deep C/C++ headers, long call chains). The release profile is panic = "unwind", so a worker panic can't SIGABRT — the crash was a stack overflow, whose handler calls abort() and which catch_unwind cannot intercept. Every tree-sitter AST walk recursed once per node depth on a ~2 MiB worker stack. New core::ast_walk provides iterative, heap-stack pre-order traversal (for_each_descendant, for_each_descendant_pruned, find_descendant_by_kind) — depth is now bounded by the heap, not the call stack, with identical pre-order semantics; every recursive walk on the indexing path (deep_queries, cyclomatic, swift signature params) was converted. Defense-in-depth: the indexer runs on a named leanctx-index thread with a 16 MiB stack + graceful spawn-failure handling, and ModeGuard::drop is now panic-free (try_borrow_mut) to remove a latent double-panic → abort path. Guarded by 20k-deep and 12k-deep overflow regression tests.
  • ctx_read no longer panics on UTF-8 files with multibyte characters (#379): the structural-hint and shell-result extractors in core::auto_findings truncated labels with raw byte slices (&s[..s.len().min(N)]), so a cut that landed inside a multibyte codepoint (e.g. a Cyrillic #//// comment near byte 70) panicked with "byte index N is not a char boundary" — surfacing to the MCP client as a -32603 error and an empty read. All nine truncation sites now use str::floor_char_boundary, which snaps the cut down to a valid boundary while preserving the byte budget. Guarded by multibyte regression tests across every layer (content hint, failed-command/test-result shell paths, and the dedup key).

Security

  • Dashboard: attribute-safe HTML escaping everywhere (CodeQL #61#65): the central LctxFmt.esc used a textContent/innerHTML round-trip that escapes &<> but not quotes, and cexpEsc in the explorer did the same — a " in a file path, symbol name or knowledge value could break out of title="…" / aria-label="…" attributes (DOM XSS). All escape helpers (central + every per-component fallback, 35 sites across 15 files) now escape & < > " ' via numeric entities; the dangerous identity fallbacks (F.esc || String) are gone. Verified by a functional breakout test.
  • CLI shell allowlist is now enforced for agents (P0-1, GL #413): lean-ctx -c blocks allowlist violations (exit 126) whenever the caller is non-interactive (stderr is not a TTY) or in hook-child mode — the CLI path is no longer weaker than the MCP path. Humans at a terminal keep the warn-only behavior; LEAN_CTX_ALLOWLIST_WARN_ONLY=1 is the explicit opt-out. The block message explains the one-line fix (lean-ctx allow <cmd>).
  • Cloud credentials are written 0o600, atomically (P0-2, GL #414): ~/.lean-ctx/cloud/credentials.json is created owner-only (dir 0o700) via tmp+rename; pre-existing world-readable files are tightened on load.
  • Deterministic path resolution (P0-3, GL #415): relative tool paths are never resolved against the process CWD anymore (daemon CWD ≠ project); resolution is strictly project_root → shell_cwd → jail_root.
  • Proxy can no longer start unauthenticated (P0-4, GL #416): start_proxy_with_token(None) now auto-resolves the session token instead of disabling auth. Provider routes still accept provider API keys, so IDE clients need no setup.
  • Postgres provider validates schema identifiers (P0-5, GL #417): the agent-controlled schema param is restricted to [A-Za-z_][A-Za-z0-9_$]* (max 63 chars) before SQL interpolation — closes an injection vector.
  • ctx_edit rejects symlinks (P0-6, GL #418): reads open with O_NOFOLLOW (plus an lstat pre-check on all platforms) and writes refuse symlink destinations — closes a TOCTOU window where a link planted inside the jail could read or overwrite files outside it.
  • Cloud/infra CLIs removed from the default shell allowlist (P0-9, GL #421): terraform, ansible, kubectl, helm, az, aws, gcloud, firebase, heroku, vercel, netlify, fly, wrangler, pulumi now require explicit opt-in (lean-ctx allow <cmd>) — they mutate remote infrastructure with ambient credentials. Dev-essential tools (git, cargo, rm, psql, …) are unchanged.
  • Home-level IDE config dirs are jail-opt-in (P0-10, GL #422): ~/.cursor, ~/.claude & co. are no longer automatically reachable through the PathJail (they expose foreign projects' sessions, MCP configs and tokens). Opt in via allow_ide_config_dirs = true or LEAN_CTX_ALLOW_IDE_DIRS=1; ~/.lean-ctx stays allowed.

[3.7.5] — 2026-06-06

The Web & Research release. lean-ctx reaches beyond the codebase: the new ctx_url_read tool pulls web pages, PDFs and YouTube videos into context as compressed, citation-backed text — research, docs and transcripts without leaving the agent loop. Alongside it ship three field-reported fixes: background scans never hydrate cloud placeholders (#363), the proxy stops 401-ing OpenAI-compatible provider keys (#362), and the Pi extension's session cache finally engages (#361).

Added

  • ctx_url_read — the web & research layer (the web counterpart of ctx_read): fetch a public web page, PDF, or YouTube video and get back compressed, citation-backed context. HTML pages and PDFs are parsed to clean Markdown/text; a YouTube URL is resolved to its transcript and flattened into compact, quotable text. Seven distillation modes (auto | markdown | text | links | facts | quotes | transcript): the facts and quotes modes return discrete claims, each carrying a confidence score and the source URL it came from, so web research is auditable. Extractive, relevance-ranked research-compression distils a whole page down to a token budget (max_tokens, default 6000; max_items caps facts/quotes, default 12), and an optional query focuses extraction on what you actually need. Fetching is SSRF-guarded — only http/https, with private, loopback and link-local addresses blocked and revalidated after every redirect. Ships with the binary and is exposed automatically wherever lean-ctx runs as an MCP server (granular tool surface → 69).

Changed

  • Pi: the embedded MCP bridge is on by default, and every read is cached through it (#361): the bridge that holds the persistent session cache was opt-in, and even when connected only a plain ctx_read was routed through it — line-range reads (offset/limit) and the grep/ls/find tools always spawned a fresh one-shot CLI, so the ~13-token cached re-read essentially never happened on Pi (an independent benchmark measured cep.sessions: 0 even with the bridge connected). The bridge now starts by default (opt out with LEAN_CTX_PI_ENABLE_MCP=0 / "enableMcp": false), and all ctx_read variants — including lines:N-M ranges — route through it with a CLI fallback, so unchanged re-reads are cheap and register as real CEP sessions. The #168 steering ("Prefer over native …") is now also carried by the Pi extension's own tool descriptions, and PI_AGENTS.md plus the setup output steer agents to the ctx_* tools instead of the un-compressed native read/bash/grep (which are not routed through lean-ctx in additive mode).

Fixed

  • Background scans never hydrate cloud placeholders (OneDrive / iCloud) (#363): starting an agent in — or above — a cloud-synced folder made lean-ctx's directory walks read every file to index it, forcing OneDrive "Files On-Demand" (and iCloud "dataless" files) to download. That is slow, burns quota, and pops OneDrive sync warnings. A new metadata-only core::cloud_files check (Windows FILE_ATTRIBUTE_OFFLINE / RECALL_ON_OPEN / RECALL_ON_DATA_ACCESS, macOS SF_DATALESS) is now a filter_entry predicate on every walker (resident search index, ctx_search, graph, BM25, ctx_tree), so a placeholder file or folder is pruned before it is ever opened — detection reads attributes only and never triggers a download. The resident search index also gained the is_safe_scan_root guard the graph/BM25 builders already had (so it never auto-indexes $HOME), and the common cloud roots (OneDrive, Dropbox, Google Drive) are blocked as scan roots.
  • Proxy stops 401-ing OpenCode's OpenAI-compatible provider keys (#362): the proxy's loopback auth gate only accepted Authorization: Bearer sk-… / gsk_… as a provider credential, so OpenCode (@ai-sdk/openai) pointed at an OpenAI-compatible upstream — Azure, OpenRouter, Groq, a local vLLM/Ollama gateway, or a project/service key — was rejected with 401 Unauthorized — lean-ctx proxy requires authentication, even though #353 had already fixed the bare-/responses routing. On a provider route the gate now accepts any non-empty credential (the proxy binds to loopback only and forwards the header verbatim, so the real upstream still validates the key); a missing, empty, or bare-scheme Authorization is still rejected.

[3.7.4] — 2026-06-05

The Superintelligence Context release. All six cross-disciplinary North-Star bets are now wired into live code: active-context prefetch that learns which providers actually help, task-conditioned compression (an Information-Bottleneck proxy), self-managing memory that consolidates itself in the background, a context immune system (signed audit + prompt-injection detection), stigmergic swarm credit (per-agent heatmap + Shapley attribution), and a physically-grounded energy and carbon ledger. Alongside the science: a heavy performance pass — int8-quantized embeddings (turbovec), SIMD dense search, a shared file-content cache that kills the search double-read, lazy demand-driven startup, and lossless JSON/JSONL compaction — plus IDE permission inheritance, opt-out instruction-file injection, three new --json CLI commands, and a batch of proxy/runtime/dashboard fixes. Everything new is free OSS; nothing is feature-gated.

Added

  • Active-context prefetch that learns — persistent provider bandit (North-Star bet 01): ctx_preload used to instantiate its ProviderBandit fresh on every call, so it never learned which data sources were actually useful for a given kind of task. The bandit (Thompson sampling over a Beta posterior) is now persisted per project (provider_bandit.json) and closes the Active-Inference loop: task-type → prediction → execution → observation → bandit update → better future predictions. A preload that returns useful chunks is a positive signal; an empty/failed one is negative. Over time lean-ctx prefetches the providers that have repeatedly paid off for this project and stops wasting calls on the ones that don't.

  • Task-conditioned compression — an Information-Bottleneck proxy in entropy mode (North-Star bet 02): the entropy read-mode compressed purely by Shannon self-information, so a rare-but-irrelevant line was kept while a common-but-task-critical line could be dropped. When an active session intent exists, entropy_compress_task_conditioned now rescues low-entropy lines that mention task keywords — keeping what is either surprising (high H) or task-relevant (mentions the goal's concepts), and compressing away only what is both uninformative and off-task. Falls back to pure adaptive entropy when no intent is active, so non-task reads are byte-identical.

  • Context immune system — signed audit trail + prompt-injection detection (North-Star bet 04): two provenance/safety steps. (1) Audit entries are now Ed25519-signed (a signature over the chained entry_hash, keyed by the local lean-ctx identity), so a record carries cryptographic proof of which installation produced it — not just a hash chain a local writer could rebuild. (2) A conservative detect_injection heuristic scans tool output for known prompt-injection patterns (role-override like "ignore all previous instructions", role-hijack, ChatML/[INST] token smuggling, role-boundary markers). On a hit it logs a warning and emits a SecurityViolation audit event. It targets high-specificity phrases that almost never appear in legitimate source/docs, so false positives are rare (verified against real code and comments in tests).

  • Stigmergic swarm substrate — per-agent heatmap traces + Shapley context credit (North-Star bet 05): the access heatmap was agent-agnostic — every read pooled anonymously. HeatEntry now carries a per-agent access map (a stigmergic pheromone field), populated in the live read path via a canonical current_agent_id() resolver (LEAN_CTX_AGENT_ID / LCTX_AGENT_ID / local, shared with the savings ledger). A new context_credit() computes Shapley-inspired attribution: when several agents touch the same file, each contributor earns credit proportional to how many other agents also benefited — the raw signal for routing one agent toward what another already found useful, and for crediting the context that actually helped the swarm.

  • rules_injection config — opt out of touching shared instruction files (#343): a new top-level option (shared default | dedicated, env LEAN_CTX_RULES_INJECTION) controls how lean-ctx delivers its tool-mapping rules to the shared-instruction-file agents (Claude Code, Codex, OpenCode, Gemini CLI). The default shared keeps today's behavior — a marker-delimited block written into CLAUDE.md / AGENTS.md / GEMINI.md for zero-config discoverability. The new dedicated mode never edits those user-authored files; instead it uses each agent's own config-driven, fully-removable auto-load path and a lean-ctx-owned rules file:

    • Claude Code & Codex — the rules summary is injected at session start via the existing SessionStart hook's additionalContext (model-visible, nothing persisted to CLAUDE.md/AGENTS.md; any prior lean-ctx block is stripped on switch).
    • OpenCode — the dedicated ~/.config/opencode/rules/lean-ctx.md is registered (by absolute path, idempotently) in opencode.json instructions[], and the old AGENTS.md block is removed.
    • Gemini CLI — the dedicated ~/.gemini/LEANCTX.md is registered in settings.json context.fileName (seeding the default GEMINI.md so the user's own context file keeps loading), and the old GEMINI.md block is removed. Switching back to shared, and lean-ctx uninstall, cleanly reverse every registration (instructions[] / context.fileName collapse back to their pristine default) and delete the dedicated files — no orphaned entries. Toggling is driven entirely by the flag: the same rules sync writes a block in shared mode and an untouched user file + separate rules file in dedicated mode.
  • permission_inheritance config — lean-ctx tools honor your IDE's permission rules (community request): when lean-ctx is mounted as an MCP server its tools (notably ctx_shell) execute inside the lean-ctx process, bypassing the host IDE's own permission engine — so an OpenCode user who set bash/rm * to ask/deny would have that guard silently skipped whenever the agent reached for ctx_shell instead of the native tool. A new top-level option (off default | on, env LEAN_CTX_PERMISSION_INHERITANCE) makes lean-ctx mirror the active IDE's permission config onto its own tools. When on, before dispatch lean-ctx reads the IDE permission rules (v1: OpenCode opencode.json / opencode.jsonc, global + project merged) and applies the equivalent decision to the matching tool: ctx_shell/ctx_executebash (incl. granular git * / rm *, and top-level command patterns), ctx_read/ctx_multi_read/ctx_smart_readread, ctx_editedit, ctx_searchgrep. deny blocks the call, ask holds it back with an actionable message (MCP can't show an interactive prompt for these tools), and allow (or no matching rule) proceeds. The most specific rule wins (longest pattern; named tool beats global *), ties broken toward the more restrictive action. lean-ctx never writes the IDE's permission block — inheritance is read-only and runtime-only; the policy is cached briefly and the default (off) adds zero hot-path cost. lean-ctx doctor reports the status and, when on, how many OpenCode rules are being mirrored.

  • Self-managing memory — the cognition loop now actually runs, and feedback steers retention (North-Star bet 03): the eight-step background cognition loop (seed-promote → structural repair → lateral synthesis → contradiction resolution → hebbian strengthen → decay → compact) existed and was enabled by default (autonomy.cognition_loop_enabled, cognition_loop_interval_secs = 3600) but nothing ever triggered it outside an explicit ctx_knowledge action=cognition_loop call — so knowledge never self-organized on its own. A new core::cognition_scheduler fires it opportunistically from the MCP dispatch path: time-gated to the configured interval, single-flight (an in-flight loop is never double-spawned), panic-safe (RAII guard frees the slot), and cheap on the hot path (one config read + two atomic loads when not due). Because the server is request-driven this beats a wall-clock thread — maintenance happens exactly when there is activity to consolidate and never when the agent is idle. Additionally, the confidence-decay schedule now closes the reward loop: a fact's explicit thumbs-up/down (feedback_up/feedback_down) scales its decay — net-positive feedback keeps it longer, net-negative forgets it faster (logarithmic, capped, and floored so a single downvote never collapses a healthy fact and nothing is ever hard-deleted).

  • Thermodynamic accounting — energy and carbon avoided, surfaced in ctx_gain (North-Star bet 06): lean-ctx already estimated grid energy avoided (0.4 J/saved-token, reconciled with the website /metrics methodology) but only for display strings. The footprint is now a first-class, physically-grounded figure: core::energy adds a transparent carbon model (G_CO2_PER_KWH = 475 g/kWh — the global-average grid intensity, override-able per machine via LEAN_CTX_GRID_CO2_G_PER_KWH so cleaner grids report honestly), and GainSummary carries energy_wh + co2_grams derived from tokens_saved. ctx_gain now shows an Impact: line (… grid energy avoided | … CO₂e) and emits both fields in its JSON, so the savings ledger's environmental dividend is auditable, not just cosmetic. All figures are surfaced as estimates; nothing is persisted into the hash-chained ledger (energy is a pure function of the already-recorded saved tokens, so the tamper-evident chain is untouched).

  • Three new --json CLI commands for editor/programmatic use: lean-ctx semantic-search (fixes the editor search path), lean-ctx repomap, and lean-ctx knowledge recall all gain structured --json output so editor integrations and scripts consume results without scraping human-formatted text.

  • gain auto-publishes public metrics in the background: when gain.auto_publish is enabled, the MCP server now performs a throttled background publish of the (opt-in) public savings metrics on startup, so the leaderboard/hero stats stay current without a manual lean-ctx gain --publish. Throttled so it never publishes more than once per interval and never blocks startup.

  • dashboard --base-path for reverse-proxy subpath mounting (#355): the web dashboard can be served under a subpath (e.g. https://host/leanctx/) behind a reverse proxy; all asset and API URLs are rewritten to honor the base path.

Performance

  • Shared file-content cache removes the search double-read (#148): building the trigram search index and then answering a ctx_search query used to read the entire candidate corpus from disk twice — once to index, once to scan — and the BM25 index read it yet again. A new resident, bounded core::content_cache (LRU, invalidated by (mtime, size)) now lets the index build, ctx_search, and BM25 share a single in-memory copy per file: read once, reuse many times. Entries self-invalidate the instant a file changes on disk, the cache refuses inserts under memory pressure, and it is dropped first by the eviction orchestrator (UnloadIndices / EmergencyDrop) so it never competes with the heavier indices for headroom.
  • Lazy, demand-driven index warming on server startup (#152): the MCP server no longer kicks off a full repo graph + BM25 scan (and extra-root scans) eagerly in initialize. A session that only ever calls ctx_read / ctx_shell / ctx_tree now pays zero startup indexing cost. Each tool is classified by what it actually needs (None / Search / Heavy); the first call to a search-backed or graph-backed tool triggers a one-shot, once-per-root background warm (extra roots warmed on that same first heavy pre-warm), so the prebuilt index is ready exactly when — and only if — something uses it.
  • int8-quantized embeddings + SIMD-friendly scoring (turbovec-inspired): dense embedding vectors are stored int8-quantized, cutting the resident index memory roughly 4× and making similarity scoring SIMD-friendly. Recall is preserved within tolerance; the smaller footprint also reduces eviction pressure on the shared caches.
  • SIMD cosine + threshold-gated HNSW cache for dense search: dense/semantic search uses a SIMD cosine kernel and only builds/keeps the HNSW graph when the corpus is large enough to pay for it (threshold-gated), so small projects stay lightweight while large ones get sublinear search.
  • Read-mostly session cache + off-hot-path telemetry (#147, #149): the per-request session state is served from a read-mostly cache and telemetry/event work is moved off the hot path, removing redundant locking and disk churn from the common ctx_read flow.
  • Lossless JSON/JSONL compaction: large JSON/JSONL tool output is compacted losslessly (insignificant whitespace removed, structure preserved) before counting, so structured payloads cost fewer tokens without changing a single value.
  • Bounded cold BM25 build in the ctx_semantic_search MCP handler (#150): a first semantic search on a cold index now builds the BM25 index under a bounded budget instead of an unbounded scan, so the initial query returns promptly on large repos.
  • Proxy parses each request body once: the compressing proxy parses the request body a single time and reuses the parsed form across compression + introspection, and additionally protects multi-file read tool results from lossy command-output compression.

Changed

  • server::call_tool_guarded post-processing split into composable stages (#144): the ~1000-line guarded dispatch path is now a thin orchestrator. The self-contained, synchronous pipeline stages (budget exhaustion/warning gates, Context-IR source-kind mapping, terse-compression gating, final token-count + savings correction) live in a unit-tested server::post_process module, and the large &self-coupled side-effect blocks (tool-receipt + intent + session-save + cost attribution; shared Context-OS persist + bus events) move into named server::post_dispatch methods. Behaviour, ordering, and await points are identical — purely a maintainability/readability change with new direct unit tests for the extracted stages.
  • Tool registry is the single schema source (#141): the granular per-tool schema definitions are generated from one registry instead of being maintained in parallel, retiring a recurring source of drift between the advertised tool surface and the actual handlers (guarded by an up-to-date regression test).
  • Unified path resolution across the core (#145): project/path resolution is consolidated into one code path with a project-marker test, removing subtle inconsistencies between callers that resolved roots differently.
  • Tool descriptions steer agents to the ctx_* tools (#168): MCP tool descriptions now nudge agents toward the lean-ctx tools over native equivalents, with a regression test that fails the build if the steering language regresses.

Fixed

  • MCP advertises the full profile surface to dynamic-tools clients (#358): clients that consume the dynamic tool categories now see the complete profile-authoritative tools/list, and the always-on ctx_call gateway is exposed so no tool is unreachable for those clients (#204).
  • Proxy accepts bare provider endpoints for the OpenCode Responses API (#353): a provider base URL without the full path suffix is normalized correctly, so OpenCode's Responses-API requests are routed and compressed instead of failing.
  • macOS install/update no longer touches ~/Documents (#356): installation and update paths stop writing into ~/Documents, avoiding spurious permission prompts and stray files on macOS.
  • Dashboard info-tip tooltips never clip (#357): info-tip tooltips in the web dashboard are portaled to <body>, so they render above surrounding cards instead of being clipped by overflow.
  • Runtime robustness: bounded WAL, dead-owner lock reclaim, fact eviction & doctor thresholds (#357-adjacent runtime hardening): the write-ahead log is now bounded, locks held by dead owners are reclaimed instead of stalling, knowledge-fact eviction is corrected, and lean-ctx doctor thresholds are tuned so its health checks reflect real conditions.
  • Pi: explicit LEAN_CTX_PI_ENABLE_MCP=1 now always starts the embedded MCP bridge (#361): a lean-ctx entry in ~/.pi/agent/mcp.json (written by lean-ctx init --agent pi) no longer silently disables the embedded bridge. Pi has no native MCP support, so that entry alone never served the tools — meaning an explicit opt-in could leave both the bridge and the adapter inactive, and the session cache (with its ~13-token re-reads) never engaged. The explicit flag now wins; /lean-ctx only notes the possible-duplicate case when pi-mcp-adapter is genuinely also running.
  • Deterministic HNSW index construction: the approximate-nearest-neighbor index now seeds each node's level from its insertion index (splitmix64) instead of OS entropy, so the same corpus always builds the same graph and returns the same results. This removes run-to-run recall variance (and the flaky recall test it caused) and makes semantic-search results reproducible.
  • Dashboard graph/code-map shows a clear language message instead of an endless loading/“run index build” state (#360): for projects built from languages the code-map does not index (e.g. Lua/Luau), the Dependencies, Symbols, and Roads views now explain that the graph only supports specific languages and that BM25 search/compression still work — instead of suggesting an index rebuild that can never populate the graph. /api/graph reports the graph-supported languages plus any unsupported source languages detected in the project.

[3.7.3] — 2026-06-04

Compression where the agent actually is — and fidelity where it matters. A shell MCP tool so the Codex Desktop/Cloud app compresses even without lifecycle hooks, plus a self-diagnosing, additive shell allowlist so permitting one command no longer means wiping out the defaults. Navigation output (map/ signatures) now carries line ranges so agents jump straight to a symbol, and already-compact formats (TOON) pass through untouched instead of being recompressed away. The proxy also speaks OpenAI's new Responses API.

Supersedes 3.7.2: an automated release misfire published an incomplete 3.7.2 to crates.io / npm before this work had landed, and those registries permanently reject re-publishing a version — so 3.7.3 is the first clean release of this work across every channel (crates.io, npm, GitHub, Homebrew).

Added

  • OpenAI Responses API support in the proxy (#346, thanks @Lctrs): clients that moved to OpenAI's new Responses API (POST /v1/responses) — opencode, the OpenAI Agents SDK — were forwarded untouched because the proxy only understood Chat Completions (messages). The proxy now compresses the Responses-API shape too: each function_call_output.output (the Responses analogue of a role:"tool" message — a string, or an input_text content array) is run through the same pattern pipeline as every other tool result. The conversation input array is intentionally left structurally intact (no history pruning) so a function_call can never be separated from its matching function_call_output and trigger a 400. Retrieve/cancel/delete sub-paths (/v1/responses/{id}/…) pass through cleanly, and /status introspection now reports an accurate token breakdown for Responses requests (instructions → system prompt; input items → user/assistant/tool buckets; input_image counted). Chat Completions remains fully supported.
  • shell MCP tool (#337): the instruction-only fix in 3.7.1 wasn't enough — the Codex Desktop / Cloud app loads the MCP server but its agent ignores ctx_shell and reaches for a native shell/Bash tool, so nothing compressed. lean-ctx now exposes a shell tool (familiar name + model-optimized description) that transparently delegates to the same 95+-pattern compression pipeline as ctx_shell, giving the Desktop/Cloud app the compression the CLI gets via hooks. Registered for all MCP clients.
  • lean-ctx allow <cmd> (#341): permit a binary on the shell allowlist additively through the new shell_allowlist_extra config field — so allowing e.g. acli keeps git/cargo/npm/… intact instead of replacing the whole built-in list. lean-ctx allow --list prints the effective allowlist plus the exact config path in use; --remove reverts. Picked up on the next command — no MCP/daemon restart needed.
  • Line ranges in map / signatures output (#340, thanks @iohansson): every entity in the navigation-focused map and signatures views now carries a compact @Lstart[-end] suffix (e.g. fn ⊛ build() → Config @L42-58), so an agent can jump straight to a symbol instead of issuing a follow-up search. Spans are populated consistently across the tree-sitter extractors (all languages, not just Kotlin) and the regex fallbacks (TS/JS, Rust, Python, Go, generic), with Vue/Svelte SFC spans shifted back to file-absolute lines. Mode-aware by design: the suffix is emitted only in map/signatures (MCP + CLI) where locating code is the point — compression-first paths (aggressive, entropy, full-body reads, and ctx_compress/ctx_outline/ctx_fill/ctx_analyze/repo-graph) stay byte-identical and pay zero extra tokens. The map/signatures compression caches are version-bumped so stale range-less entries are never served.
  • Format-aware passthrough for already-compact output (#342, thanks @pomazanbohdan): ctx_shell / lean-ctx -c no longer recompress output that is already in a compact, token-oriented format. lean-ctx detects TOON (Token-Oriented Object Notation) by its structural markers — the tabular key[N]{f1,f2}: header and the length-prefixed key[N]: array header — and returns it verbatim, because a second pass saves little while rewriting the exact line/field shape an agent needs to validate a CLI output contract. The decision is output-shape based, not command based, so any tool emitting TOON is covered without enumerating it in excluded_commands. Controlled by the new preserve_compact_formats config (default ["toon"]; set to [] to disable) and surfaced as a "Compact-format passthrough" line in lean-ctx doctor.
  • Clearer path to the public leaderboard (community feedback): the lean-ctx gain recap now always shows a one-line, state-aware hint — how to publish to leanctx.com/metrics with --leaderboard, how to claim a display name (--name="…") instead of showing up as "anonymous", and how to --unpublish. The gain --publish output likewise points a private-only publisher to the public board and nudges nameless leaderboard entries toward a handle, so getting on (and managing) the leaderboard is never a guess.
  • VS Code / Cursor extension, now publishable (community feedback): the editor extension is consolidated into a single, marketplace-ready package (vscode-extension) and shipped to the VS Code Marketplace and Open VSX (Cursor, VSCodium, Windsurf) via a dedicated, tag-triggered CI workflow (publish-vscode.yml). It gains binary auto-detection (PATH / ~/.cargo/bin / Homebrew, for GUI editors with a stripped PATH), setup / doctor / gain / heatmap / web-dashboard commands, one-click workspace MCP wiring, plus an Apache-2.0 license and PNG icon. The duplicate scaffold (packages/vscode-lean-ctx) was removed.

Fixed

  • MCP stdio stays protocol-clean (#348): confirmed and regression-guarded that lean-ctx routes all tracing diagnostics to stderr — never the stdout JSON-RPC transport — so a log line can never be interleaved into an MCP client's message stream and break parsing. This has held since ≤3.7.1 (the transport writes only framed JSON-RPC, the auto-started proxy runs as a subprocess with stdout redirected to null, and tool handlers return strings rather than printing); a source-level guard now fails the build if the logging writer is ever switched to stdout.
  • shell_allowlist edits silently ignored in MCP/editor mode (#341): allowlist changes looked like no-ops while lean-ctx -c (CLI, warn-only) still ran the command, due to three compounding traps. (1) A malformed config.toml fell back to the defaults with the warning printed only to stderr — invisible over an MCP/stdio transport; the parse error is now surfaced in the block message and in lean-ctx doctor. (2) Setting shell_allowlist directly replaced the entire default list — the new additive shell_allowlist_extra (written by lean-ctx allow) avoids that footgun. (3) The "not in allowlist" message now names the exact config path the runtime reads plus the precise additive fix, so a config-path/HOME mismatch between the editor's MCP process and your shell is immediately visible. lean-ctx doctor gains a "Shell allowlist" check (effective command count + parse status).
  • Codex instructions no longer claim Desktop "can't" run hooks (#350, thanks @iohansson): the block lean-ctx injected into ~/.codex/AGENTS.md (and LEAN-CTX.md) asserted as fact that "lifecycle hooks do not run" in the Codex Desktop/Cloud app — false (hooks do run there, trust-gated via /hooks since Codex 0.129.0) and traceable to a misread of openai/codex#13019, which is about completion notifications, not lifecycle hooks. A false absolute like that is exactly the kind of thing that confuses the agent. The instructions now make no surface-specific "hooks don't run" claim; they frame the lean-ctx MCP/CLI tools (ctx_shell / ctx_read / ctx_search, or lean-ctx -c) as the path that compresses reliably on every surface regardless of hook status, and lean-ctx doctor's Codex note is corrected to match. A regression test fails the build if the docs ever re-introduce a "hooks do not run" / "no automatic compression" absolute.
  • Proxy no longer mangles file/source reads (#351, external testing feedback): the request-compressing proxy treated every tool result as shell output, so a Read of a large source file was run through command-output truncation (head/tail + "safety" lines) on the very next turn — gutting the file the model was mid-refactor on and forcing an uncounted re-read. The proxy now resolves each tool result's originating tool name (tool_use/tool_calls/function_call/Gemini functionResponse.name) and never lossy-compresses a file read or content that heuristically looks like source code, across all four providers (Anthropic, OpenAI Chat, OpenAI Responses, Gemini). Shell/search/command output still compresses as before. History pruning is likewise code-aware: an older file read is replaced with an honest, actionable "re-read the file if you need it again" stub instead of a misleading 3-line excerpt.
  • Proxy stopped failing large-refactor and long-generation calls (#351, external testing feedback): the request-body ceiling was 10 MiB, so a big-codebase refactor with several files in context hit a hard 400 mid-task — now 64 MiB and configurable via LEAN_CTX_PROXY_MAX_BODY_MB. A single 2-minute total request timeout also aborted long streaming generations (e.g. Opus doing a large refactor) mid-stream; it is replaced by a connect timeout plus a read (idle) timeout (LEAN_CTX_PROXY_CONNECT_TIMEOUT_SECS, LEAN_CTX_PROXY_READ_TIMEOUT_SECS, defaults 15s / 300s), so a slow-but-alive stream is never cut while a genuinely dead upstream still fails.

Changed

  • Identifier α-substitution (§MAP) is now opt-in (#351, external testing feedback): aggressive reads on large projects used to replace long identifiers with short α-codes plus a §MAP: decode table (symbol_map_auto, previously auto-on above 50 source files). A tester found the abbreviated form obscured package/symbol names exactly when editing. It is now off by default — set symbol_map_auto = true (or LEAN_CTX_SYMBOL_MAP=1) to opt back in for maximum exploration savings.
  • Editing intents always read the full file (#351, external testing feedback): when the active task classifies as refactor, fix-bug, or generate, auto-mode reads now resolve to full regardless of model tier — you cannot safely edit a file you can only partially see, and an abbreviated/signatures view just forced a follow-up read. Exploration/review intents still compress as before.
  • Per-model cost breakdown in the proxy (#351, external testing feedback): /status now reports a per_model array (requests, estimated tokens saved, and USD saved priced from the shared model table) instead of a single flat number, and discloses that savings are request-side and do not subtract agent re-reads. Token figures remain explicitly labelled estimates.

[3.7.1] — 2026-06-03

Wrapped Viral-Loop. The honest Wrapped recap is now shareable end-to-end: a first-run "aha", one-click sharing, an opt-in hosted permalink, and an opt-in public leaderboard — privacy-safe and anonymous-first.

Added

  • First-run "aha" (lean-ctx discover): the first run surfaces a concrete, projected token saving for the current project (one-time marker in ~/.lean-ctx), with discover --card exporting a shareable "Ghost Tokens" SVG. Non-UTF-8 shell histories (zsh metafied format) are now read lossily so the projection never silently sees empty history.
  • One-click share (gain --copy / --open): copy a ready-to-post share line to the clipboard or open the generated SVG/HTML card in the browser — cross-platform (pbcopy/clip/wl-copy/xclip/xsel, open/start/xdg-open).
  • Hosted Wrapped permalink (gain --publish / --unpublish): anonymously publish a whitelisted, privacy-safe slice of the recap and get a shareable leanctx.com/w/<id> URL (copied to clipboard). Whitelist-only (deny_unknown_fields), one-time edit_token stored locally for later removal, optional account claim. Server-rendered page carries per-card Open Graph / Twitter meta; og:image is a resvg-rasterized 1200×630 PNG. Contract: docs/contracts/wrapped-permalink-v1.md.
  • Opt-in public leaderboard (gain --publish --leaderboard): off by default; when set, the card is listed on leanctx.com/leaderboard (server-rendered, top 50 by realized tokens saved). Only the user-chosen display name is person-facing; everything else is an aggregate. JSON at /api/leaderboard.
  • Per-day version in lean-ctx gain (#307): each row in "Recent Days" and the gain --daily table now shows the lean-ctx version active that day, so a compression change can be attributed to a release. Days recorded before this field stay blank (). The version is stamped on each day's stats and carried through the cross-process stats merge.

Fixed

  • 2>&1 (and >&, &>, N>&M) misread as a command (#334): the shell-allowlist parser split a single & as a background separator even inside a redirect, so pnpm run compile 2>&1 was parsed as pnpm run compile 2> and a bogus command 1, which was then blocked. A & adjacent to > is now correctly treated as part of the redirect, not a separator; genuine background & still splits. Fixes false '1' is not in the shell allowlist blocks in MCP mode (Cursor/opencode/etc.).
  • Auto-update ignored config.toml (#335): a scheduler installed earlier kept running lean-ctx update even after the user set updates.auto_update = false, because the update command never re-checked config. Scheduled runs (--quiet/--scheduled) now obey config: auto_update = false skips the update and removes the orphaned scheduler (self-heal), and notify_only = true downgrades to a check (never installs). Manual lean-ctx update is an explicit action and always proceeds.
  • macOS bash login shells missed the hook and PATH (user report): bash login shells (Terminal.app, IDE terminals, bash -l) read ~/.bash_profile/~/.profile, never ~/.bashrc — yet the hook (and the installer's ~/.local/bin PATH export) land in ~/.bashrc. lean-ctx setup now ensures the login profile sources ~/.bashrc (idempotent, Debian/Ubuntu-style), so the hook and PATH take effect in login shells. install.sh prints the matching one-liner; uninstall removes the snippet. zsh is unaffected (it always reads ~/.zshrc).
  • Event feed flooded with false "denied" policy violations: auto-preload candidates from the project graph are repo-relative (e.g. rust/src/core/foo.rs); the path jail resolved them against the daemon's CWD (not the project root), so every candidate failed with "no existing ancestor" and was logged as a policy violation. Relative candidates now resolve against the jail root, and a genuinely missing file is no longer mislabeled as a security denial. As defense-in-depth, ctx_preload now resolves its jail root from the dispatch-provided project root when no explicit path and no session root are available, so it never silently jails against the daemon CWD in any IDE.
  • ctx_search and the background index build could hang on special files (FIFOs, sockets, devices) (#336): a regular-file guard now skips non-regular paths before any blocking read — read_to_string on a named pipe blocks forever waiting for a writer, which surfaced as random, unlogged hangs. ctx_search additionally enforces a wall-clock deadline (LEAN_CTX_SEARCH_DEADLINE_MS, default 10s) and returns partial results with a note instead of hanging. Reproduced with a real FIFO and covered by regression tests (search_skips_named_pipe_without_hanging, build_skips_named_pipe_without_hanging).
  • No compression in the Codex Desktop / Cloud app (#337): lean-ctx's transparent shell/file compression for Codex is hook-driven (the codex-pretooluse hook reroutes commands through lean-ctx -c), but the Codex Desktop and Cloud app run in app-server mode where lifecycle hooks do not fire (OpenAI codex#13019) — so identical commands compressed in the Codex CLI but not in the app. The Codex instructions (~/.codex/AGENTS.md + LEAN-CTX.md) now state this explicitly and direct the agent to proactively route work through the MCP tools (ctx_shell/ctx_read/ctx_search) or lean-ctx -c in the Desktop/Cloud app, which is the channel that is available there. lean-ctx doctor adds a Codex note so a healthy config no longer looks like a silent failure. (Hooks remain the automatic path in the Codex CLI once trusted via /hooks.)

[3.7.0] — 2026-06-01

Shadow Mode + Meaningful Instructions. Rules injected into agents are now actionable (concrete tool names, examples, workflow), and a new shadow_mode transparently intercepts native Read/Grep/Shell calls for users who want full automatic routing.

Added

  • Shadow Mode (lean-ctx config set shadow_mode true): transparently intercepts native Read/Grep/Shell via hooks, strengthens MCP instructions to MUST-level, activates immediate bypass hints on first native tool use, logs all intercepts to ~/.lean-ctx/shadow.log for audit transparency. Visible in lean-ctx doctor and lean-ctx status.
  • 6-step workflow in all injected rules: Orient → Locate → Read → Edit → Verify → Record — agents can follow blindly without memorizing tool names.
  • Tool Mapping table in rules: every injected rule file now includes a MANDATORY table with exact tool names, parameters, and runnable examples (ctx_read("src/main.rs", "full")).
  • Proactive section in RULES_DEDICATED: ctx_overview at session start, ctx_compress at phase boundaries, ctx_knowledge(action="wakeup") for prior findings.
  • Compression Bypass ladder: lines:N-Mfullraw=true — documented escape hatch when compression hides detail.
  • Risk Gate guidance: before editing exported symbols, auth, DB schemas, or 3+ files — run ctx_impact + ctx_callgraph.
  • Registry-driven hook refresh + doctor staleness check: lean-ctx doctor detects stale hooks, IDE path misconfiguration, and auto-refreshes outdated rules on first tool call.
  • Reference appendices generated from code: docs-gen renders MCP tool reference, CLI reference, and journey golden outputs directly from source — with CI drift-gate to catch divergence.
  • Complete user-journey reference (14 journeys): install-to-first-save through performance tuning, with IDE quickstarts and golden output examples.
  • Semantic-index observability (#249): lean-ctx index status and lean-ctx doctor surface BM25 state (idle/building/ready/failed), build duration, persisted size, and failure notes.
  • Verified savings ledger (lean-ctx savings [summary|verify|export]): an auditable, append-only per-event record (~/.lean-ctx/savings/ledger.jsonl) behind the aggregate gain numbers. Each value-producing read logs the counterfactual (baseline vs actual tokens), resolved pricing model, the tokenizer that produced the counts (o200k_base, recorded separately from the model so the proxy gap is disclosed), a privacy-preserving repo hash, and a tamper-evident SHA-256 hash chain. Cross-process safe (advisory file lock). Local-only, on by default; opt out with LEAN_CTX_SAVINGS_LEDGER=off.
  • Bounce-netting (honest savings): a compressed read later invalidated by a full re-read now records a negative "bounce" event, so lean-ctx savings and gain --wrapped report the realized saving (gross → bounce → net) instead of a gross upper bound. Bounce persists across processes via the ledger.
  • Wrapped share card + page (lean-ctx gain --svg / --share): export the Wrapped recap as a dependency-free 1200×630 SVG (social/OG image) or a self-contained, self-hostable HTML page (opt-in permalink, SVG embedded, zero telemetry). --share --base-url=… emits Open Graph / Twitter meta for rich link unfurling. Every surface labels the pricing model, marks fallback prices (est.), and states USD as an upper bound.

Changed

  • Proper uninstall (user request): lean-ctx uninstall is now complete and self-contained. It (1) stops every process first — daemon, proxy, and stray lean-ctx PIDs (current process + IDE-owned MCP servers excluded) so nothing respawns or holds the files being removed, and (2) removes the binary itself — the managed copy/symlink in ~/.local/bin (or $LEAN_CTX_INSTALL_DIR), /usr/local/bin, and the running executable (unlinked safely on Unix). Package-manager installs defer with the right cargo/brew command; an in-repo target/release build is never touched. New --keep-binary flag; install.sh --uninstall (and curl … | sh -s -- --uninstall) run the same teardown for users without the binary on PATH. Previously the binary was left behind with only a printed rm hint.
  • Edit-failure recovery context (#331): when ctx_edit can't apply an edit, the error now carries the information the agent would otherwise re-read the file to find. Identical old_string/new_string are rejected outright; an already-applied edit (new_string present, old_string gone) is named explicitly; a mismatch surfaces the closest matching line plus a whitespace/indent hint and re-reads the file in full. If the target file doesn't exist, lean-ctx searches the enclosing repo and suggests same-named files (moved vs. truly missing); if old_string lives in a different file, it points there — so the agent re-targets instead of assuming it picked the right file. All searches respect .gitignore and are bounded (depth/file/hit caps) so they stay cheap on the error path.
  • Rules version v10 → v11: all templates (RULES_SHARED, RULES_DEDICATED, lean-ctx.mdc, lean-ctx-hybrid.mdc) rewritten with actionable structure. Existing installations auto-upgrade on next lean-ctx setup or lean-ctx update.
  • MCP instructions include workflow hint: "Orient(ctx_overview) → Locate(ctx_search) → Read(ctx_read) → Edit → Verify → Record".
  • bypass_hint.rs respects shadow_mode: when active, hints trigger on first native use (not after 5 calls) with stronger "intercepted" wording.
  • Hook redirect messaging: in shadow_mode, redirected Read/Grep outputs include a header explaining the interception and suggesting direct ctx_* usage.

Fixed

  • Config.toml overwritten on update (#330): all config writes now use toml_edit-based format-preserving merge with atomic backup. User comments, formatting, and unknown keys survive any write. Minimal-diff mode: only non-default values are written (no config bloat).
  • WSL cache hit rate near 0% (#329): mtime=None on DrvFS no longer causes spurious invalidation; path normalization uses canonicalize (with verbatim-prefix stripping) for consistent cache keys; lean-ctx cache stats now shows both CLI and MCP session cache metrics.
  • Semantic index stuck "warming up" forever (#249): on a repo whose BM25 index exceeded the disk cap, the index rebuilt from scratch every call. Three fixes: (1) disk persist ceiling decoupled from RAM profile (default 512 MB); (2) save reports typed SaveOutcome with actionable notes; (3) ctx_compose deferred message is state-aware and honest.
  • Test-runner output compressed/truncated, losing pass/fail summaries: test-runner commands across all ecosystems are now kept verbatim; test-outcome markers survive truncation on every code path.
  • Knowledge store split on Windows (#325): forward-slash/casing-normalized project hash converges CLI and MCP on a single store. Pre-fix backslash-keyed stores auto-migrate.
  • Parallel remember calls clobbered each other (#326): read-modify-write serialized with in-process + cross-process file locks; atomic temp-file-then-rename saves prevent JSON corruption.
  • Windows \\?\ prefix from canonicalize: normalize_tool_path now uses safe_canonicalize (strips extended-length prefix) and skips root-only paths (/, C:/).
  • IDE hook integrations check: doctor now correctly parses hook binary path from minified JSON.
  • Docs-drift gate line-ending agnostic: Windows CI no longer fails due to CRLF vs LF in generated docs.
  • Benchmark system info detection on Windows: RAM + CPU detection now works on all platforms.

Security

  • Shell-command injection in the Node SDK (CodeQL js/shell-command-constructed-from-input): switched to execFileSync — no shell interpretation.
  • XSS in VS Code sidebar webview (CodeQL js/xss, 3× high): all dynamic values escaped.
  • Missing origin check on webview message handler (CodeQL js/missing-origin-check): rejects untrusted origins.

[3.6.26] — 2026-05-30

EPIC 6 — Perfect-First (Track A). A focused correctness + hygiene pass so the session/knowledge layer behaves perfectly across projects, the disk footprint stays bounded, and cold-start UX is useful immediately.

Fixed

  • Windows file paths corrupted in tool output (#324): absolute Windows paths in ctx_search/ctx_compose output (and every tool using protocol::shorten_path*) were rendered with separators stripped (C:\Users\…\win-build-log.txtCUserszir…win-build-log.txt) because client render layers (JSON/markdown/terminal) treated backslashes as escape sequences. All displayed paths are now normalized to forward slashes, which are valid on Windows and never escape-interpreted. shorten_path_relative also relativizes on slash-normalized strings (component-boundary checked) so it works regardless of the client's separator style.
  • Project root never resolves to HOME / / / agent sandbox dirs (#2361): best_root_from_uris, root_from_env, resolve_roots_once, and the initialize handler now reject broad/unsafe directories as a project root via pathutil::is_broad_or_unsafe_root, even when a client reports one. This was the root cause of cross-project context bleed (the "HOME mega-session").
  • Cross-project session leakage (#2362): SessionState::load_latest() no longer falls back to the global latest.json pointer — it is strictly project-scoped and returns None for an unsafe cwd. A new load_global_latest_pointer() covers the explicit "show my last session anywhere" UX, and consolidate_latest() loads the session for its explicit project root instead of the process cwd.
  • Noise auto-findings suppressed (#2363): findings whose files live in VCS/dependency/build/cache dirs, virtualenvs, vendored code, home dotfiles (~/.ssh/config …), or binary/log artifacts are dropped, and ctx_search no longer emits Found ? in N files when no meaningful pattern could be identified. Knowledge recall now boosts exact key/category matches above incidental lexical hits.
  • Cold-start ctx_overview returns a useful partial view (#2365): instead of only "INDEXING IN PROGRESS, try again", it returns detected project markers, a depth-2 gitignore-aware tree, and persistent knowledge while the graph builds in the background.

Added

  • lean-ctx sessions doctor [--apply] (#2362): detects sessions rooted at a broad/unsafe path and non-destructively quarantines them to sessions/quarantine/.
  • Archive FTS disk cap enforcement (#2364): the archive index (archives/index.db) now enforces an on-disk size cap (default 500 MB, override via LEAN_CTX_ARCHIVE_DB_MAX_MB) by pruning the oldest entries + VACUUM. A new daemon-safe storage_maintenance pass also prunes accumulated quarantined BM25 indexes on startup, and lean-ctx doctor gains an Archive FTS footprint check.

Changed

  • Self-healing rules refresh (#2365): when an outdated rules file is detected on the first tool call of a session, lean-ctx auto-refreshes the rules on disk (off the async runtime) instead of only nudging the user to run lean-ctx setup.

[3.6.24] — 2026-05-30

Added

  • Knowledge Intelligence — Revision Tracking: KnowledgeFact gains a revision_count field. Confirmations increment it, supersedes carry it forward. Output distinguishes "Remembered (revision 1)" vs "Confirmed (revision N, confirmed Nx)" vs "Updated → revision N (previous archived)". Recall shows rev N for multi-revision facts. Backward-compatible via #[serde(default)].
  • Knowledge Intelligence — Cross-Key Conflict-Surfacing: find_cross_key_similar() detects semantically similar facts across different keys using Jaccard similarity (threshold > 0.35). When remember stores a fact, similar facts from other keys are surfaced in a SIMILAR FACTS section with similarity percentages. New judge action lets agents resolve pairs as supersedes/compatible/unrelated. JudgedPair storage suppresses future noise for already-judged pairs. Recall output annotates facts with ↳ supersedes/↳ compatible relationship arrows.
  • Knowledge Intelligence — Activity-weighted Documentation Nudges: Replaces the fixed 30-call counter with weighted activity scoring. Edits +4, shell test/build +3, shell +2, new file read +1, cache-hit +0, knowledge/session calls reset to 0. Triggers only when weighted_score >= 20 AND significant_tools >= 5 AND no documentation in 8 minutes. Contextual nudge text based on dominant tool type (shell-heavy, edit-heavy, or generic). Fallback 30-call counter preserved as safety net.
  • bunx in default shell allowlist (#310).

Fixed

  • RAM Guardian measures daemon RSS instead of CLI process (#317): lean-ctx doctor was showing the CLI's ~14 MB instead of the daemon's actual memory. Added get_rss_bytes_for_pid(pid) for Linux (/proc/{pid}/status) and macOS (ps -o rss= -p {pid}). Doctor now reads the daemon PID and reports its real RSS with (daemon) label.
  • Orphan MCP processes no longer accumulate RAM (#317): Added parent-process watchdog (checks every 5s if parent PID changed, exits cleanly when IDE closes) and startup orphan cleanup (kills lean-ctx processes reparented to PID 1). Prevents MCP server processes from surviving after IDE restarts.
  • lean-ctx restart no longer kills active MCP servers (#317): find_killable_pids() excludes MCP server processes from force-kill during restart, preventing a kill loop where the IDE immediately respawns them.
  • Jira Cloud 410 Gone error (#315): Migrated from deprecated GET /rest/api/3/search to POST /rest/api/3/search/jql with nextPageToken pagination. Server/Data Center deployments (detected via JIRA_DEPLOYMENT=server) continue using GET /rest/api/2/search.
  • Provider discovery ignores project root (#316): handle_discover() and handle_mcp_resources() now pass project_root to init_with_project_root() so project-local provider configs are found.
  • Cross-source hints path normalization (#316): hints_for_file() now accepts project_root for consistent graph_relative_key normalization.
  • JSONC parser tolerates trailing commas (#311, #312): Prevents parse failures in MCP config files with trailing commas. Also detects duplicate MCP scope registration (workspace + user) and warns.
  • CI structural test relaxations: Three tests (scenario_shell_compression_with_saved_tokens_skips_terse, raw_shell_skips_all_postprocessing, ctx_handoff_create_show_list_pull_clear) relaxed to check for component presence instead of exact multiline matches, preventing false failures from unrelated code changes.

Changed

  • Reverted thinking-mode guard (#313): The is_thinking_mode_active() defensive check in PreToolUse hooks was removed — the original Claude Code bug it worked around has been fixed upstream, and the guard could reduce token savings.

Hardening

  • Graceful error handling: Replaced potential panics with proper error returns and added logging for silent save failures across knowledge, session, and stats persistence.

Refactoring

  • CLI dispatch split: Extracted dispatch.rs (1800+ lines) into analytics.rs, network.rs, and other submodules.
  • Doctor module split: Decomposed doctor/mod.rs (2321 lines) into common.rs + checks.rs.
  • Editor registry split: Split writers.rs (2580 lines) into a proper module with subfiles.
  • Server dedup: Consolidated duplicated has_project_marker / PROJECT_MARKERS logic.

[3.6.25] — 2026-05-30

Added

  • Jira Cloud OAuth 2.0 (3LO) (#318): authenticate built-in and custom Jira data sources via the standard 3-legged OAuth flow instead of Basic auth + API token. New lean-ctx provider auth jira runs the interactive flow (loopback redirect, browser consent, accessible-resource/cloudId discovery), persists tokens to ~/.lean-ctx/credentials/jira-oauth.json (0600), and auto-refreshes on expiry with refresh-token rotation. lean-ctx provider list / provider logout round out the surface. The CLI is secret-free: users register their own Atlassian OAuth app and supply the client id/secret via env. Basic auth continues to work unchanged; OAuth is selected automatically when a credential exists or JIRA_AUTH=oauth is set.
  • Context-pressure triage in the Context Cockpit (#249): the Context Manager moves from observation to triage. The Files in Context table gains sortable Used (re-read count), Last (recency), and Evict columns — the Evict score combines high token cost + long idle + rarely re-read so the best eviction candidate is one click away. A triage banner maps the live pressure band to a concrete next action (Healthy / Elevated → prefer map+signatures / High → compress or evict / Critical → evict or handoff pack). The ledger now tracks per-item access_count (backward-compatible via #[serde(default)]).
  • Offline-first Context Cockpit: Chart.js, D3 and the UI fonts are now self-hosted (no external CDN), so the dashboard renders identically offline and with large sessions; libs degrade gracefully with an inline notice if one fails to load. Added a dashboard-wide ⌘K / Ctrl+K command palette with fuzzy search across every view, quick actions (refresh, theme toggle) and full keyboard navigation, plus an embedded favicon and clearer route labels.
  • Friendly first run (UX P0.3): running bare lean-ctx in an interactive terminal now prints a short quickstart (one obvious next step: lean-ctx setup) instead of silently starting the stdio MCP server and appearing to hang. MCP clients (which pipe stdin, not a TTY) and explicit lean-ctx mcp are unaffected — they still get the server.
  • --help leads with the essentials (UX P1): a GETTING STARTED block (setup / doctor / gain) now sits at the top of the help, above the full reference — newcomers see the 3 commands they need first instead of scanning 150 lines.
  • Efficiency Epic — resident line-search index: ctx_search now narrows candidate files in memory via a RAM-resident trigram index (core/search_index.rs) before reading them, eliminating the per-call directory walk + full-corpus read. Benchmarked 17×–1000× faster (p50, warm) on a 2000-file corpus with byte-identical recall. Falls back to the walk path when the index is absent/building; opt-out via LEAN_CTX_DISABLE_SEARCH_INDEX=1.
  • ctx_compose task composer: one call returns extracted keywords, semantically ranked files, exact match locations, and the most relevant symbol's body inline — replacing the typical search→read→outline→read chain.
  • Benchmark harness (rust/benches/efficiency.rs + benchmarks/efficiency/): reproducible latency (p50/p95/p99) + token report comparing the walk and resident-index paths, with a recall-parity assertion.
  • Submodular context packing (core/context_packing.rs): generic greedy max-coverage selector with a provable 1 1/e approximation guarantee (NemhauserWolseyFisher). ctx_compose now uses it to inline the non-redundant set of symbol bodies with maximal keyword coverage under a token budget, instead of just the first match. Budget via LEAN_CTX_COMPOSE_SYMBOL_TOKENS (default 600).
  • Search index Bloom tier (core/search_index.rs): monorepos whose trigram postings would exceed the memory budget now build compact per-file Bloom filters (~3× smaller, ~12 bits/trigram) instead of falling back to a full directory walk. Bloom filters have zero false negatives (a superset of true matches that ctx_search regex-verifies), so recall is identical to the exact tier. The MAX_FILES ceiling rose 20k→200k. Proven by a parity fuzz test (Bloom ⊇ postings for every query) + end-to-end recall test.
  • Hebbian co-access graph (core/cooccurrence.rs): a persistent, decaying "files that fire together, wire together" association graph. Files surfaced for the same task strengthen their mutual link (LTP); every update decays all weights (the forgetting curve) and prunes below threshold. Bounded by neighbour/file caps. Becomes an associative retrieval signal over time.
  • Spreading-activation retrieval (core/spreading_activation.rs): ACT-R-style associative ranker. Activation seeds at the files a task names and spreads over the project graph (fan-out-normalised, decaying → provably convergent even on cycles), surfacing structurally-close files lexical search misses. ctx_compose runs it over the union of the static import/call graph and the learned co-access graph as a budgeted, additive ## Related (associative…) section (LEAN_CTX_COMPOSE_GRAPH_BUDGET_MS, default 1500).
  • Retrieval eval harness (tests/retrieval_eval.rs): a labelled benchmark (queries + relevance judgments) measuring recall@k, MRR and R-precision. Gates the associative ranker as regression-free (recall ≥ lexical for every query) with a measured gain (mean recall@3 1.00 vs 0.00 lexical, R-precision 1.00 — it recovers in-cluster files without flooding unrelated ones).

Hardening

  • ctx_compose semantic ranking is wall-time budgeted (H1): the only O(corpus) stage (a cold BM25 build) runs in a cache-sharing worker thread bounded by LEAN_CTX_COMPOSE_BUDGET_MS (default 2500). On overrun the call returns immediately with exact-match + symbol sections and a "warming" note, while the worker finishes warming the resident cache for the next call — the agent loop can no longer stall on a cold index.
  • ctx_compose full-path test coverage (H2): new tests/ctx_compose_scenarios.rs exercises the semantic + exact-match + symbol pipeline on a real mini-corpus and asserts the tight-budget degradation path never stalls.
  • Instruction token cap is priority-aware (H3): the compression/output-style guidance suffix is now protected from truncation; only the variable session/knowledge/gotcha blocks are shed when the 1200-token cap is exceeded. Previously a large on-disk session could silently drop the agent's output-style contract.

Changed

  • lean-ctx config points to the simpler surface (UX P2): the full config dump now ends with a tip toward config show (the 5 high-level knobs) and config set <key> <value>, so the 100+ keys no longer feel like the only entry point. The simplified config template (config init) now defaults compression_level = "lite", matching the new friendly default.
  • Friendly-by-default output style (UX P0): the default compression_level is now lite (plain-English "concise" guidance — bullets, no filler) instead of standard (the symbolic dense style). New users, and anyone opening their generated rules files or inspecting the MCP instructions, now see readable directives rather than the → ∵ ∴ vocabulary or CRP MODE. The denser symbolic "power modes" stay one line away (compression_level = "standard" | "max", or LEAN_CTX_COMPRESSION). This only shapes the model's prose; tool-output compression is governed separately and is unchanged — engine efficiency is unaffected.
  • ctx_read auto-mode delivers task-relevant bodies: in map/signatures mode with an active task, the body of the best-matching symbol is inlined, avoiding a follow-up full read. The map heuristic threshold was raised 3000→6000 tokens, and the redundant double disk read in auto-mode selection was removed (cached token counts are reused).
  • Alpha/§MAP symbol substitution is now off by default for agent-facing output (it traded per-call bytes for agent decode work). CLI/batch pipelines can opt back in with LEAN_CTX_SYMBOL_MAP=1.
  • Resident graph-index cache (core/graph_cache.rs): try_load_graph_index reuses a deserialized ProjectIndex from RAM, instead of re-reading + decompressing + parsing on every graph query.
  • BM25 + graph caches use a (mtime, size) content fingerprint instead of mtime alone: coarse (12 s) filesystem mtime could miss a same-second background rebuild; pairing it with the file size catches those rewrites without the cost of hashing a multi-MB index on every per-query freshness check. A rebuild is still picked up immediately within the TTL window.

Fixed

  • CLI --help banner tool count no longer drifts (UX P0): the N MCP tools figure in the banner is now derived from the live registry (server::registry::tool_count()) instead of a hardcoded literal — it read 61 while the README and feature catalog already said 63. A unit test pins the banner to the registry count so the three figures can never diverge again.
  • Instruction token-cap truncation was O(lines) tokenizationstruncate_to_token_cap re-counted tokens once per line while walking back from the end. On large session/knowledge blocks this is wasteful, and it timed out the coverage job's ptrace-instrumented run. Replaced with a binary search over line boundaries (O(log lines) tokenizations, identical output).
  • CI: dropin_install_tests failed on shell-less runners (regression from #309): the new "is the shell installed?" guard skips writing zsh hooks when no zsh binary is present, but the drop-in install tests assert the hooks are written — so they failed on the zsh-less ubuntu-latest runner. Added LEAN_CTX_SHELL_HOOK_FORCE (1/true/all or a comma list like zsh,bash) to force hook installation regardless of detection — useful in minimal containers / custom images, and the seam the tests use to stay host-independent.
  • ctx_edit concurrent-edit timeout under multi-agent load (#320): the global cache write-lock was held across the entire disk I/O of an edit, so a second agent editing a different file could time out waiting on the first. Edits now serialize per file via a shared core::path_locks registry, perform disk I/O with no global lock, and take the global cache lock only briefly to apply the resulting cache effect. Concurrent edits to different files now run in parallel; edits to the same file remain correctly serialized.
  • Eval harness reported zero recall on Windows: recall_at_k/mean_reciprocal_rank compared retrieved paths (OS separator, \ on Windows) against expected fixtures (/) with ends_with, so every comparison missed and recall/MRR collapsed to 0 on Windows. Both sides are now normalized to / before comparison.
  • Flaky CI on Windows: made the ctx_tree token-savings test deterministic via a synthetic fixture (instead of walking the live repo, whose size + path tokenization varied by platform), and de-flaked spawned_background_task_doesnt_block_caller by polling for completion with a generous deadline instead of a fixed sleep.

Hardening

  • Per-file advisory lock registry (core/path_locks.rs): a process-wide per_file_lock(path) shared by ctx_read and ctx_edit serializes access to the same file without contending on a global lock, with bounded GC of unused entries. Lock-ordering documentation (LOCK_ORDERING.md) updated accordingly.

Refactoring

  • config/mod.rs split: extracted the enum surface (TeeMode, TerseAgent, OutputDensity, ResponseVerbosity, CompressionLevel, RulesScope) into config/enums.rs, trimming ~250 lines from the module.
  • Premium lean-ctx wrapped artifact: the shareable text summary is now TTY-aware with ANSI colouring, box drawing and a savings sparkline (plain text when piped / NO_COLOR).

[3.6.23] — 2026-05-28

Fixed

  • lean-ctx update creates .zshenv on systems without zsh (#309): install_all_with_style() unconditionally wrote shell hooks for both zsh and bash regardless of whether the shell was installed. Now checks for shell binary existence (/bin/zsh, /usr/bin/zsh, etc.) before installing hooks. Systems with only bash no longer get a spurious .zshenv.
  • lean-ctx config set rejects valid config keys (#308): The config set command only supported ~12 hardcoded keys while the config schema defines 80+. Implemented a generic schema-based setter (config/setter.rs) that validates any key against the ConfigSchema, parses values by type (bool, integer, float, string, enum, string[]), and performs a TOML round-trip with full serde validation. Keys like proxy_enabled, profile, compression_level, memory_profile now work as expected.

Added

  • lean-ctx gain: 30-day USD savings (#307): The dashboard now shows a "past 30 days" line with the estimated dollar savings for the last 30 days, in addition to the all-time total.
  • lean-ctx gain: version in Recent Days header (#307): The "Recent Days" section now displays the current lean-ctx version (e.g. v3.6.23) for easier troubleshooting in screenshots.
  • Generic config set with enum validation: Setting enum keys (e.g. compression_level) now shows allowed values on invalid input instead of a generic error.

[3.6.22] — 2026-05-28

Security

  • Security Hardening V2 (8 phases): Comprehensive security audit and hardening across the entire codebase:
    • Phase 1: Shell substitution blocking — eval, exec, source, backtick-at-command-position detection
    • Phase 2: Role system hardening — parameterized roles_dir_project_from(), stricter role validation
    • Phase 3: Shell file access controls — lock-timeout secret redaction
    • Phase 4: PathJail bypass removal — eliminated #[cfg(feature = "no-jail")] escape hatches in tests
    • Phase 5: Secret detection unification — consolidated redaction pipeline
    • Phase 6: Dangerous flag detection — --checkpoint-action, GIT_SSH=, PATH= override warnings
    • Phase 7: HTTP + audit hardening — request validation, audit trail improvements
    • Phase 8: Unicode normalization (U+2028/U+2029 → newline), CLI warn-first validation, empty-allowlist gap fix

Fixed

  • Critical: preToolUse hook DENY loop (#306): Cursor and other AI agents entered infinite retry loops when lean-ctx hooks returned DENY responses. Eliminated all DENY paths — hooks now always return valid ALLOW JSON, even for disabled mode, invalid payloads, or non-shell tools. Removed build_dual_deny_output() entirely.
  • Graph index disappears after upgrade (user report): CLI index build-full and Dashboard used different project root hashes (CLI used raw cwd, Dashboard promoted to git root). Unified detect_project_root() to always promote to git root, matching Dashboard behavior. Users in subdirectories now see the same index.
  • index build-full incomplete rebuild: Previously only cleared JSON graph index + BM25. Now also clears call_graph.json.zst, graph.db, and graph.meta.json, then rebuilds the SQLite property graph. Timeout increased from 2min to 5min.
  • Knowledge overflow from finding-auto duplicates: Auto-consolidated findings without a file reference all received the key finding-auto, creating hundreds of duplicate facts. The cognition loop's contradiction resolver couldn't keep up, causing contradict event spam in the dashboard. Keys are now generated from the finding summary (unique per finding).
  • cargo build --release truncated by lean-ctx: Heavy build commands hit the 8MB/120s output limit. Added adaptive exec limits: build tools (cargo build, npm install, docker build, etc.) now get 32MB/10min instead of 8MB/2min.
  • Disabled hook test expected empty output (#306 follow-up): Updated hook_rewrite_disabled_produces_no_output test to expect ALLOW JSON output instead of empty stdout.

Added

  • ctx_tree / lean-ctx ls gitignore toggle: New respect_gitignore parameter (MCP) / --no-gitignore flag (CLI) to show files regardless of .gitignore rules. Default: gitignore respected (backward compatible). Fixes user report where all-gitignored folders appeared empty.
  • LEAN_CTX_SHELL_ALLOWLIST_OVERRIDE env var: Completely replaces the config-based allowlist for deterministic testing. Unlike LEAN_CTX_SHELL_ALLOWLIST (which merges), this overrides everything.
  • 37 heavy-command prefixes for adaptive exec limits: cargo build/test/clippy, npm install/ci, docker build, go build/test, mvn, gradle, dotnet, swift, flutter, pip install, bundle install, mix compile, and more.

[3.6.21] — 2026-05-27

Fixed

  • RAM Guardian now performs real cache eviction under memory pressure (#300): Previously, the memory_guard eviction callback only called jemalloc_purge(), which returns already-freed pages to the OS but never evicts actual data (SessionCache, BM25 index, etc.). Now a new EvictionOrchestrator bridges the RSS-based memory guardian to the HomeostasisController, enabling 5-stage graduated eviction: trim compressed outputs → evict probationary entries → unload BM25 index → evict protected entries → emergency full cache clear.
  • jemalloc_purge() error handling: Previously swallowed errors with let _ =. Now logs failures via tracing::debug for diagnosability.
  • is_under_pressure() no longer expensive in hot loops: Was calling MemorySnapshot::capture() (which does Config::load() + syscalls) on every invocation in BM25/graph index builders. Now reads a cached AtomicU8 flag set by the guardian thread — O(1) with zero allocations.

Added

  • EvictionOrchestrator (core/eviction_orchestrator.rs): New module connecting memory_guard (RSS monitoring) to HomeostasisController (graduated eviction). Holds Arc references to SessionCache and SharedBm25Cache, executes eviction actions with non-blocking try_read/try_write to avoid stalling the guardian thread.
  • SessionCache eviction methods: trim_compressed_outputs(), evict_probationary(), evict_to_budget(), approximate_bytes(), trim_shared_blocks() — enable fine-grained memory reclamation under pressure.
  • BM25 cache management: bm25_cache::unload() drops the cached index (rebuilt on next search), bm25_cache::memory_usage() reports current heap usage.
  • Doctor pressure hints: RAM Guardian check now shows the active pressure level and recommends memory_profile = "low" or increasing max_ram_percent when under pressure.

[3.6.20] — 2026-05-27

Fixed

  • Critical: OnceLock reentrancy deadlock on Linux (#301): All shell hook commands (ls, cat, etc.) and lean-ctx update hung after upgrading to v3.6.19. Caused by active_profile_name() calling Config::load(), which re-entered find_project_root()'s OnceLock via SessionState::load_latest()normalize_loaded_session()active_profile(). Fixed by reading the profile config key directly from disk (bypassing the full Config::load() pipeline) and removing the active_profile() call from session normalization.

[3.6.19] — 2026-05-26

Added

  • Built-in passthrough profile: No output modification — always full content, zero compression. Use via LEAN_CTX_PROFILE=passthrough or lean-ctx config set profile passthrough. Includes default_mode=full, crp_mode=off, degradation.enforce=false, pipeline: all false, max_tokens_per_file=10M, max_context_tokens=1M.
  • Persistent profile selection via config.toml: New profile field in config.toml provides a fallback when LEAN_CTX_PROFILE env var is not set. Resolution order: env var → config.toml → "coder" default. Set via lean-ctx config set profile <name>.
  • Profile config schema entry: lean-ctx config show now displays the profile key.

Fixed

  • LEAN_CTX_FULL_TOOLS=0 incorrectly treated as ON: is_ok() only checked existence, not value. Now =0 and =false are correctly treated as disabled.
  • mode=full returning stubs/deltas in passthrough mode: handle_full_with_auto_delta ignored no_degrade and passthrough profiles. Cache stubs and auto-deltas are now skipped when no_degrade=true or when the active profile has default_mode=full + crp_mode=off.
  • MCP schema claimed default mode was full: The ctx_read tool description said "default: full" but the actual default was auto (resolved by AutoModeResolver). Agents that omitted the mode argument got compressed output instead of full content. Schema now correctly states "default: auto".
  • Silent fallback to coder profile: When LEAN_CTX_PROFILE pointed to a non-existent profile name, lean-ctx silently fell back to coder without any warning. Now logs a tracing::warn with the missing profile name and creation instructions.

[3.6.18] — 2026-05-26

Added

  • Structured read modes for non-code filesctx_read mode map now produces token-efficient semantic summaries for Markdown (heading outline with nesting), JSON (key structure with types and counts), YAML (key hierarchy), TOML (section headers + top-level keys), and lock files (workspace crate dependency summaries for Cargo.lock, package counts for package-lock.json/yarn.lock/go.sum). Up to 95% token savings vs. full reads on large config and documentation files (#299)
  • Unified AutoModeResolver — New centralized module (auto_mode_resolver.rs) consolidates all auto-mode selection logic that was previously scattered across mode_predictor.rs, context_gate.rs, and intent_router.rs. Single entry point resolve() produces a deterministic mode decision with full trace logging. Config/data files like Cargo.toml, package.json correctly get full mode while structured formats (JSON, YAML, TOML, lock files) are routed to map mode (#297)
  • GraphProvider unified facadeGraphProvider enum now wraps both PropertyGraph (SQLite, symbol-level) and ProjectIndex (JSON, file-level) behind a single API. New methods: file_catalog(), file_info(), files_in_dir(), index_dir(). All 12 consumer modules (ctx_overview, ctx_graph, ctx_impact, ctx_symbol, ctx_prefetch, ctx_preload, heatmap, task_relevance, graph_export, dashboard) migrated from direct ProjectIndex usage to GraphProvider (#298)
  • Template instructions SSoT — New rules_canonical.rs module provides canonical_hybrid_instructions() as the single source of truth for all template instruction generation. CLAUDE.md, lean-ctx.mdc, and daemon LITM injection all derive from the same canonical table, eliminating instruction drift (#296)
  • CLI graph query commands — Five new CLI subcommands for querying the code graph without the daemon: lean-ctx graph related <file>, lean-ctx graph impact <file>, lean-ctx graph symbol <name>, lean-ctx graph context <file>, lean-ctx graph status
  • UTF-8 locale enforcementapply_utf8_locale() sets LC_CTYPE=C.UTF-8 as fallback when no UTF-8 locale is inherited from the parent process. Applied to all 5 shell spawn paths (MCP execute_command_with_env, CLI exec_direct/exec_inherit/exec_buffered, CLI passthrough). Fixes Cyrillic/CJK/emoji M-notation mangling on Linux where Cursor spawns without user shell profile

Fixed

  • mode=full silently downgraded (#295) — Explicit mode=full requests were being overridden by the pressure degradation system and context gate heuristics. full mode is now treated as an explicit user intent that bypasses all degradation, bounce tracking, and overlay-based downgrades
  • Shell allowlist blocking legitimate commands (#294) — Expanded allowlist for Cursor workflows: $() command substitution relaxed to only block dangerous patterns (not all subshells), argument-position backticks allowed, gh data commands (pr list, issue list, api, run list) now compressible instead of passthrough. Prevents agent retry loops on blocked commands
  • Bypass hint false positives (#292) — Reduced false "you should use lean-ctx tools" warnings when agents legitimately use native Read/Grep for specific use cases. Doctor warnings for config downstream improved
  • ctx_prefetch crash without graphctx_prefetch now gracefully falls back to direct prefetching of changed_files when no graph is available, instead of returning "no graph available" error. Fixes failures in fresh/temporary project directories
  • PropertyGraph race condition on Windows — Background graph build populates symbol nodes and edges before file_catalog entries, causing ctx_overview to report "0 files". open_best_effort now requires file_catalog_count > 0 on both the early-return and fallback paths before considering a PropertyGraph as populated
  • UTF-8 locale for shell commands — MCP server and CLI now set LC_CTYPE=C.UTF-8 fallback for child processes, fixing Cyrillic and CJK output mangling on Linux

Changed

  • Token efficiency optimizations — Comprehensive audit-driven improvements across the engine:
    • BM25 index cache uses Arc<BM25Index> instead of clone() — eliminates full index copies on every access
    • Stats now adjusted after post-processing (terse, hints) to reflect actual tokens sent to models
    • Cache hit token benchmark uses dynamic count_tokens() measurement instead of hardcoded constant
    • Compression floor lowered from 50 to 30 tokens, enabling pattern compression for small outputs
    • INSTRUCTION_CAP switched from byte-based (4096) to token-based (1200 tokens) for accurate truncation
    • Graph index scan shares content cache with edge builder, eliminating redundant file I/O
    • Deduplicated extract_content_hint into single shared function
    • SessionCache eviction upgraded from segmented LRU to RRF (Reciprocal Rank Fusion) scoring combining recency, frequency, and size signals
  • Dead code removal — Removed unused migrate_index_to_property_graph and remove_file_catalog functions after graph consolidation

[3.6.17] — 2026-05-25

Added

  • Antigravity CLI 2.0 as separate init targetlean-ctx init --agent antigravity-cli writes MCP config to ~/.gemini/antigravity-cli/mcp_config.json, distinct from the IDE target (antigravity). lean-ctx init --agent gemini now auto-configures both Antigravity IDE and CLI paths (#284)
  • Doctor: daemon diagnosticslean-ctx doctor shows systemctl --user is-active state on Linux, warns when loginctl enable-linger is not set (required for boot-time start without login), and displays crash-loop log restart count with file path (#288, #289)
  • Crash-loop log path API — New crash_loop_log_path() public function for programmatic access to the MCP server restart history

Fixed

  • Uninstall completeness (#274).bak files containing lean-ctx content, .lean-ctx.invalid.*.bak temporaries, ~/.config/lean-ctx XDG data directory, and project-local .lean-ctx/ + .lean-ctx-id files are now cleaned up. Claude CLI MCP registry entries removed via claude mcp remove. --keep-config flag preserves MCP configs and rules for reinstall
  • Linux daemon autostart (#288, #289)systemctl --user enable failures now print actionable error messages with manual fix commands. is_installed() checks systemctl is-enabled in addition to service file existence. Linger hint displayed when linger is not active
  • Windows paths with spaces — Shell hook rewrites use shell_tokenize() (respects single/double quotes, backslash escapes) instead of split_whitespace(). shell_quote() properly quotes arguments containing special characters
  • Windows drive-letter grep parsingparse_grep_line() and extract_file_from_match() correctly skip C: drive prefix, preventing misinterpretation as file path separator
  • Panic loop-undo (#277, #271)catch_unwind handler in call_tool now calls record_error_outcome() on the loop detector, so panicking tools are correctly counted as failures and subject to throttling instead of infinite retry
  • PowerShell detection DRY (#286) — Replaced inline shell.to_lowercase().contains("powershell") check in shell/exec.rs with platform::is_powershell(), single source of truth
  • Windows CI (#286) — Fixed unused variable: quiet in daemon_autostart.rs on non-Unix platforms. Shell wrapping tests now use platform-aware assertions (expect_wrapped() helper) that work on both Unix (single-quotes) and Windows (double-quotes with escaping)
  • Index scoping — Project index scans restricted to project root via is_safe_scan_root() guard. index status CLI output shows real values instead of nulls
  • Workflow singleton — Workflow state is now agent-scoped (workflow-{agent_id}.json) instead of global active.json. Stale workflows auto-cleaned after TTL expiry
  • JSONC UTF-8 safetystrip_json_comments uses floor_char_boundary/ceil_char_boundary for all string slicing, preventing panics on multi-byte characters in comments
  • ls -lah size passthrough — Human-readable sizes (e.g. 4.0K, 1.2M) from ls -lh/ls -lah are preserved instead of being converted to 0B
  • MCP server crash hardeningMutex::lock().unwrap() in hot paths replaced with graceful fallbacks. memory_guard uses eviction loop instead of process::exit. CSPRNG fallback for dashboard nonce generation
  • Proxy: accept provider API keys on loopback — Provider routes now accept API keys from local clients (#276)

Changed

  • Antigravity IDE renamed — The existing Antigravity target is now labeled "Antigravity IDE" in display names and doctor output, distinguishing it from the new "Antigravity CLI" target

[3.6.16] — 2026-05-22

Added

  • First-class OpenClaw agent supportlean-ctx init --agent openclaw writes the MCP server entry to ~/.openclaw/openclaw.json under mcp.servers.lean-ctx (nested JSON structure), installs global rules to ~/.openclaw/rules/lean-ctx.md, and copies the LeanCTX SKILL.md to ~/.openclaw/skills/lean-ctx/. lean-ctx doctor detects OpenClaw installations. lean-ctx setup auto-configures when ~/.openclaw/ exists
  • Context package graph-native architecture (.ctxpkg v2) — New ContextGraph data model (ContextNode, ContextEdge) with activation weights and temporal metadata. Graph-merge composition with conflict detection and contradiction resolution. Ed25519 package signing with hex-encoded key verification. Manifest schema version 2 with scoped package names (@scope/name) and conformance levels (Basic, Graph, Cognitive). New docs/specs/ with JSON schema
  • LeanCTX Custom GPT documentation — Knowledge base and system prompt prepared for creating a ChatGPT Custom GPT to answer lean-ctx documentation questions (files in docs/gpt/, gitignored)

Fixed

  • ctx_session finding panic on em-dash (#272)parse_finding_value crashed on multi-byte separators like " — " (space + U+2014 EM DASH + space = 5 bytes) because the code assumed a 3-byte ASCII separator. Now dynamically determines separator length using str::len(). Added 6 regression tests including exact repro with Cyrillic text from the issue report
  • Panic handler returns isError: false — The catch_unwind block in the MCP server returned panics as successful tool results (isError: false), hiding crashes from AI agents. Now returns CallToolResult::error so isError: true is set correctly

[3.6.15] — 2026-05-22

Fixed

  • MCP crash: "Cannot read properties of undefined (reading 'invoke')" — Identified and fixed 4 distinct crash vectors that caused intermittent MCP server death on v3.6.14 (#271):
    • 5 Mutex::lock().unwrap() calls in the MCP request hot path (list_tools, active_tool_defs, ctx_load_tools) replaced with graceful fallbacks that degrade instead of crashing
    • memory_guard hard process::exit(137) replaced with 3-attempt eviction loop — server now aggressively reclaims memory but never hard-exits
    • Nested block_in_place in bounded_lock eliminated to prevent Tokio blocking-pool exhaustion under concurrent tool calls
    • CSPRNG expect() in dashboard nonce/token generation replaced with time-based fallback
  • parse().unwrap() for SocketAddr in 2 dashboard routes replaced with direct SocketAddr::new() construction
  • tempfile().expect() in ctx_execute replaced with graceful error return

Changed

  • Dashboard: modular route architecture — Monolithic context.rs (617 lines) and graph.rs (364 lines) split into focused sub-modules (context/{core,overlay,diagnostics,aggregated}.rs, graph/{deps,callgraph,analysis}.rs)
  • Dashboard: API consolidation — 3 new aggregated endpoints (/api/context-summary, /api/context-capabilities, /api/context-history) reduce parallel fetches from 18 to 11 in the Context Manager view
  • Dashboard: shared frontend utilities — Extracted common rendering logic (gauges, formatters, path shortening) into lib/shared.js; TTL-cached API layer in lib/api.js with event-based data broadcasting
  • Dashboard: removed dead code — Deleted legacy dashboard.html (3057 lines) and CockpitContextLayer component

Added

  • Context Commander — New action-oriented dashboard component with context pressure visualization, budget bands, and risk analysis
  • Configurable proxy timeoutLEAN_CTX_PROXY_TIMEOUT_MS env var / proxy_timeout_ms in config.toml (default: 200ms) (#270)
  • Dynamic tool categoriesLCTX_DEFAULT_CATEGORIES env var / default_tool_categories in config.toml to control which tool categories are active by default
  • Global degradation disableLCTX_NO_DEGRADE=1 env var / no_degrade = true in config.toml to globally disable all read mode degradation

[3.6.14] — 2026-05-22

Added

  • First-class Augment AI agent supportlean-ctx init --agent augment wires up both Augment configuration surfaces: Auggie CLI (~/.augment/settings.json) and VS Code extension (globalStorage/augment.vscode-augment/.../mcpServers.json, JSON array with stable UUID-keyed upserts). Rules injected at ~/.augment/rules/lean-ctx.md. lean-ctx doctor reports per-surface MCP drift including "disabled": true detection. Full cross-platform support (Linux, macOS, Windows). Contributed by @parker-brown-family (#264, #267)
  • Context package system renamed to .ctxpkg — Package format, CLI commands, transport envelopes, and documentation all use .ctxpkg extension. Legacy .lctxpkg files remain importable for backward compatibility
  • ctx_multi_read server-side output cap — Output capped at 512KB by default (configurable via LCTX_MAX_MULTI_READ_BYTES) to prevent MCP client-side truncation. When exceeded, remaining files are skipped with a clear warning (#263)
  • Degradation policy warningauto_degrade_read_mode() now emits an explicit ⚠ Context pressure warning when mode=full is downgraded to mode=map or mode=signatures, including the verdict and bypass hint (start_line=1 or ctx_compress) (#262)
  • 28 new regression tests — 14 UTF-8 boundary tests (Cyrillic, CJK, emoji, exact user scenario), 10 degradation verdict tests, 4 ctx_multi_read cap tests

Fixed

  • UTF-8 character boundary panics — 13 string truncation sites across the codebase now use str::floor_char_boundary() / str::ceil_char_boundary() instead of raw byte slicing, preventing panics on multi-byte characters like Cyrillic, CJK, or emoji. Affected: hash_fast (4096 byte prefix/suffix), curl/cargo/test/just pattern compression, codebook display, gotcha tracker, mcp_compress, ctx_edit preview, ctx_preload hints, dashboard context, tool_defs, stats format, dashboard token masking, cloud email masking. Report and initial PR by @cburgess (#265, #266)
  • Context package system hardening — Fixed critical receive --apply bug, Graph edge import (uses get_node_by_symbol instead of get_node_by_path), Session/Patterns/Insights import, auto-load caching (prevents re-application), registry validation, HMAC signing (signs all fields including metadata), CLI flag parsing (--flag value and --flag=value), memory leaks (.leak() removed), HTTP response status checking for send
  • lean-ctx update proxy race conditionpost_update_rewire() now restarts the proxy and waits for health before writing ANTHROPIC_BASE_URL to Claude Code settings, preventing a connectivity gap (#234)

Changed

  • Removed PackageLayer::Artifacts — Dead enum variant removed; builder derives layers from actual content
  • Manifest validation expanded — Checks hex format of hashes, byte_size > 0, duplicate layers
  • Import hardened — File extension check (accepts .ctxpkg and .lctxpkg), size limit (MAX_PACKAGE_FILE_BYTES)

[3.6.13] — 2026-05-21

Added

  • Plan mode support for VS Code, Claude Code, and Windsurf — New plan_mode.rs module detects IDE plan/read-only contexts and exposes a curated subset of 12 read-only tools (ctx_read, ctx_search, ctx_tree, ctx_overview, ctx_plan, ctx_metrics, ctx_compress, ctx_session, ctx_knowledge, ctx_graph, ctx_retrieve, ctx_provider). lean-ctx setup auto-configures VS Code planAgent.additionalTools and Claude Code permissions.allow entries. Includes lean-ctx doctor plan mode status check
  • MCP readOnlyHint tool annotations — All read-only MCP tools now declare readOnlyHint: true in their tool definitions, enabling IDE plan agents to use them without explicit user approval. Write tools (ctx_edit, ctx_fill, ctx_delta, ctx_handoff, ctx_ledger, ctx_multi_read) correctly declare readOnlyHint: false
  • Dynamic tool filtering — New server/dynamic_tools.rs module filters exposed tools based on client capabilities. Plan-mode clients only see read-only tools; full-mode clients see all 62 tools
  • GitLab provider — Built-in GitLab data source provider (issues, merge requests, pipelines) activates automatically when GITLAB_TOKEN is set. Joins GitHub, Jira, and PostgreSQL as built-in providers
  • Provider consolidation pipeline (production-wired)apply_artifacts_to_stores() now runs in a background thread from both ctx_provider and ctx_preload, indexing provider data into BM25, Graph, Knowledge, and Session Cache. Previously, provider data was only cached — now it's fully searchable, generates cross-source hints in ctx_read, and contributes knowledge facts
  • MCP Bridge stdio transport support[providers.mcp_bridges.<name>] now accepts command + args for stdio-based MCP servers in addition to HTTP url. Bridges register with unique IDs (mcp:<name>) and support resources, read_resource, and tools actions
  • Cross-source hints in ctx_read — When reading a file, ctx_read now shows related issues, PRs, and external data linked via the graph index (e.g., "Related: [Issue] github://issues/42 — Auth bug")
  • ctx_semantic_search external result attribution — Search results from external providers now show clear type labels: [Issue], [PR], [Ticket], [Schema], [Wiki] with full provider URIs
  • lean-ctx doctor MCP bridge diagnostics — New diagnostic section validates configured MCP bridges (URL reachability, config completeness, auto_index status warning)
  • lean-ctx doctor plan mode check — Reports whether VS Code and Claude Code are configured for plan mode tool access
  • 13 wiring-proof integration tests — New provider_wiring_proof.rs test suite proves every connection in the provider pipeline is functional (consolidation → BM25/Graph/Knowledge/Cache → search/hints/recall). Catches "functional silos" where code exists but isn't connected to runtime
  • 10 E2E provider pipeline scenarios — New provider_pipeline_e2e.rs covers full pipeline, cross-source edges, knowledge extraction, MCP bridge registration, multi-source consolidation
  • Plan mode scenario tests — New plan_mode_scenarios.rs with 11 tests covering VS Code settings injection, Claude Code permissions, idempotency, merge behavior, and status detection
  • Power user worksession test suite — New power_user_worksession.rs with 12 end-to-end scenarios simulating a full coding session: initial read → edit → diff → search → knowledge → cache → overview → multi-read → compress → graph → context
  • Lock contention hardening tests — New lock_contention_hardening.rs with 14 scenarios testing bounded lock timeouts, concurrent access, I/O health escalation, and WSL2/NFS environment detection
  • LEAN_CTX_CLIENT_HINT env override — Client capability detection can now be overridden for testing and edge-case environments
  • lean-ctx doctor provider status — Shows active providers and their auth status
  • lean-ctx doctor Copilot CLI MCP check — Separate diagnostic for Copilot CLI MCP configuration (distinct from VS Code MCP)
  • VS Code Extension .vscode/mcp.json support — New standard path with type: "stdio" transport
  • ctx_ledger reset clears cache delivery flags — Prevents stale "already delivered" states
  • Knowledge.json size warning — Warns when knowledge file exceeds 1 MB during load
  • CLI smoke tests — New integration tests for gain --json, grep, ls, doctor commands

Fixed

  • PowerShell @args splatting fails on single commands_lc function now resolves the native command via Get-Command -CommandType Application before invocation, preventing "not recognized" errors when @args is used with compound argument strings
  • Fish shell lean-ctx-off leaks env varset -e LEAN_CTX_ENABLED (which removes the var) changed to set -gx LEAN_CTX_ENABLED 0 (which sets it to 0), matching Bash/Zsh behavior and preventing child shells from re-activating
  • Bash/Zsh lean-ctx-off leaks env varunset LEAN_CTX_ENABLED changed to export LEAN_CTX_ENABLED=0 for consistent disable semantics across shells
  • Provider init ignores project rootctx_provider and ctx_preload now call init_with_project_root(Some(root)) instead of init_builtin_providers(), enabling config-based provider discovery scoped to the actual project directory
  • Windows CI failure: dead is_running_in_powershell() — Removed unused #[cfg(windows)] function that triggered -Dwarnings failure on windows-latest CI
  • Lock contention in 12 MCP toolsctx_read, ctx_edit, ctx_delta, ctx_fill, ctx_handoff, ctx_knowledge, ctx_multi_read, ctx_smart_read, ctx_prefetch, ctx_ledger, ctx_preload, ctx_provider now use bounded lock acquisition with adaptive timeouts instead of indefinite waits
  • Adaptive timeout death spiral — SlowFs/Degraded environments now get longer timeouts (1.5×/2×), not shorter, preventing cascading failures
  • UTF-8 safe truncation — No more panics on multi-byte character boundaries in hook handlers, ctx_read, ctx_overview, and server dispatch
  • Cache staleness for missing files — A missing file is now correctly treated as stale (previously wasn't)
  • compound_lexer Unicode — Switched from byte-based to char-based parsing; fixed $(…) subshell detection
  • Windows shell output decoding — Tries UTF-8 first, then Active Code Page (ACP) as fallback
  • ctx_read lock contention — Returns actionable error message instead of hanging silently
  • ctx_read not-found — Provides actionable hint after retry failure
  • BM25 zstd decompression bomb — Bounded decode prevents memory exhaustion from malformed compressed index
  • Copilot hooks merge — No longer overwrites existing hooks during setup
  • ctx_knowledge rehydrate time budget — Capped at 10 seconds to prevent blocking
  • ctx_execute respects GIT_PAGER/PAGER — Only sets pager env vars when not already set by user

Changed

  • providers.auto_index default is now true — New installations automatically index provider data into BM25/Graph/Knowledge stores. Previously defaulted to false (cache-only)
  • MCP tool count — 61 → 62 (added ctx_provider)
  • Tool descriptions — Updated pkgdesc in AUR packages and description in Cargo.toml to reflect 62 tools
  • ctx_read post-dispatch — Enrichment bounded to 3s; ledger/eviction/elicitation run async (no longer inline in output)
  • VS Code/Copilot client detection — Now also recognizes "Visual Studio Code" and "vscode" client identifiers
  • Knowledge rehydrate limit — Maximum archives reduced from 12 to 4 for faster startup
  • Shell pattern pipeline — ANSI-stripped output flows through all compressor stages

Removed

  • Dead code cleanup — Removed Config::providers_mcp_bridges() (unused after init.rs refactoring), hints_from_index() (unused wrapper), is_running_in_powershell() (Windows-only, never called), unused ProjectIndex import
  • Inline eviction/elicitation hints in ctx_read response — Now only debug-logged, no longer appended to tool output

[3.6.12] — 2026-05-21

Added

  • Context Engine architecture — Cross-source intelligence engine that unifies file reads, shell output, and external data sources into a single context graph. Includes ContentChunk abstraction, ProviderRegistry, cross-source edge hints, provider bandit (Thompson sampling), and active inference prefetching
  • Config-based data source providers — Connect any REST API to lean-ctx without code. Drop a TOML/JSON file into ~/.config/lean-ctx/providers/ and lean-ctx auto-discovers it. Supports 6 auth methods (bearer, API key, basic, header, query param, none), dot-notation response extraction, and project-local providers
  • Built-in providers — GitHub (issues, PRs, actions), Jira (issues, sprints, projects), PostgreSQL (tables, schema, queries) activate automatically when their env vars are set
  • ctx_provider tool — MCP tool to query any registered data source: ctx_provider(provider="github", resource="issues", params={...})
  • MCP Bridge integration — Connect external MCP servers as data sources via [providers.mcp_bridges.<name>] config. Supports HTTP (url) and stdio (command+args) transports. Each bridge gets a unique ID (mcp:<name>), supports resources, read_resource, and tools actions. New mcp_resources convenience action on ctx_provider lists all resources from configured bridges
  • Full provider consolidation pipeline — All provider data (GitHub, GitLab, Jira, Postgres, MCP bridges, custom REST) now flows through the complete consolidation pipeline into BM25 index, Graph index, Knowledge facts, AND session cache. Background thread applies artifacts to all stores without blocking tool responses
  • lean-ctx doctor MCP bridge check — New diagnostic section validates configured MCP bridges (URL reachability, config completeness, auto_index status)
  • core/io_health module — Environment detection (WSL2, NFS, FUSE, sshfs), freeze counter with 60s decay window, adaptive timeout calculation (Fast/SlowFs/Degraded escalation levels)
  • server/bounded_lock module — Self-healing lock acquisition helpers for all MCP tools; returns None on timeout allowing graceful degradation instead of indefinite hangs
  • core/output_sanitizer module — Last-pass output filter that detects and removes degenerate CJK runs, symbol floods, and garbled artifacts before output reaches the client
  • lean-ctx proxy cleanup command — Removes stale ANTHROPIC_BASE_URL entries from Claude Code/Codex settings when the proxy is disabled
  • lean-ctx doctor stale proxy check — New diagnostic that detects ANTHROPIC_BASE_URL pointing to local proxy when proxy is not enabled, with actionable fix instructions
  • Website docs — New pages: Context Control & Overlays (/docs/context-control), Budgets & SLOs (/docs/budgets-and-slos), Observatory (/docs/observatory)

Fixed

  • Garbled Chinese characters in Cursor Thought panel (#257, moshuying report) — Unicode-heavy compression symbols (, , , , ) confused Cursor's lightweight Thought summarizer model, causing degenerate completion. Three-layer fix: (1) output sanitizer removes CJK artifact lines, (2) Cursor-aware ASCII-safe symbol substitution in compression prompts, (3) TDD shortcuts use ASCII-only replacements (->, ok, FAIL, WARN)
  • Stale ANTHROPIC_BASE_URL after proxy disable (#256) — Users who disabled the proxy were left with ANTHROPIC_BASE_URL pointing to 127.0.0.1:4444 in Claude Code settings, causing 401 errors. doctor --fix and proxy cleanup now auto-detect and remove stale URLs. Proxy 401 responses include actionable JSON error messages
  • Random freezes on WSL2/NFS/FUSE — Self-healing I/O protection layer: safe_canonicalize_bounded() now applies timeout on ALL platforms (was Windows-only); 12 registered tools use bounded_lock helpers with adaptive timeouts. System auto-detects slow environments and adapts: 3+ freezes in 60s → degraded mode (ReDev1L report)
  • Proxy auto-starts without explicit enablespawn_proxy_if_needed() now checks proxy_enabled == Some(true) before spawning (webut report)
  • Multi-user port conflict — Proxy port is now deterministic per-user via UID-based assignment (4444 + (uid - 1000) % 1000). Supports three override levels: env var → config key → UID-based auto-port (webut report)
  • Hardcoded port 4444 fallbacks — All proxy subcommands now use default_port() instead of hardcoded 4444
  • BM25 stale-index noise — Downgraded "stale index detected" log from WARN to DEBUG
  • Windows test failurecanonicalize_bounded test now uses std::env::temp_dir() instead of hardcoded /tmp
  • Shell allowlist test flake — Empty allowlist test explicitly sets env var instead of removing it
  • CI documentation check — Updated MCP tool count 61→62 across all docs to match registry
  • Bare URL rustdoc warnings — Wrapped bare URLs in doc comments with angle brackets

Changed

  • providers.auto_index default is now true — New installations automatically index provider data into BM25/Graph/Knowledge. Previously defaulted to false (cache-only)
  • ctx_semantic_search external result formatting — Provider-sourced results now show clear attribution: [Issue] github://issues/42 — Auth bug instead of raw URIs
  • MCP Bridge unique IDs — Each configured MCP bridge registers with mcp:<name> instead of shared mcp_bridge, allowing multiple bridges to coexist
  • MCP tool count — 61 → 62 (added ctx_provider)
  • Compression symbols — TDD shortcuts now use ASCII-safe symbols (-> instead of , ok instead of ) for better downstream model compatibility
  • Rules injection — Cursor config files (.cursorrules, .cursor/rules/) now receive ASCII-safe compression prompts; other editors get full Unicode prompts

[3.6.11] — 2026-05-20

Fixed

  • Linux proxy restart loop (11258+ restarts) — When the lean-ctx binary is replaced during runtime (e.g. upgrade), Linux marks /proc/self/exe with (deleted) suffix. find_binary() in the systemd unit generator would write this corrupted path into ExecStart, causing systemd to pass (deleted) as a CLI argument on every restart. Now uses resolve_portable_binary() which strips the suffix. Additionally, the CLI dispatch defensively removes (deleted) from args if already present in existing units (webut report)
  • Windows ctx_read hangs — Session lock acquire and path canonicalization now have bounded timeouts (5s for RwLock, 2s for canonicalize()) preventing indefinite hangs on Windows reparse points and network paths (Butetengoy report)
  • Manifest generator uses stale tool_defsgen_mcp_manifest now reads from ToolRegistry (61 tools) instead of static granular_tool_defs() (56 tools), ensuring the website manifest always reflects the actual registered tool count

Changed

  • Context budget auto-escalationpressure_downgrade() now applies more aggressive mode downgrades based on ContextPressure: SuggestCompression downgrades automap, ForceCompression downgrades fullmap and auto|mapsignatures
  • Cache-stable LITM output — Dynamic session statistics (ACTIVE SESSION v…) moved from output prefix to suffix, preserving a stable prefix for LLM prefix-caching compatibility
  • ToolRegistry as SSOT for list_toolslist_tools handler now reads tool definitions from the registry instead of static tool_defs/, eliminating schema drift between exposed schemas and handler implementations
  • OnceLock for project rootfind_project_root() result cached via std::sync::OnceLock, eliminating repeated git rev-parse subprocess calls
  • Compaction sync tail-seekfind_latest_compaction() reads only the last 4KB of context_radar.jsonl instead of the entire file, bounding I/O for large radar logs

Removed

  • Dead code cleanup: removed unused functions, #[allow(dead_code)] attributes replaced with _ prefixes or deleted across 8 files

[3.6.10] — 2026-05-20

Fixed

  • Knowledge recall blocks all agents for 58s — Embedding engine loading (ONNX model ~25MB) no longer blocks recall. New try_shared_engine() returns instantly if model isn't loaded yet; auto/hybrid mode uses non-blocking path. Only explicit mode=semantic may trigger model load. Retrieval signal persistence moved to background thread (save_knowledge_deferred) so 436KB+ JSON writes don't stall the MCP thread (#ReDev1L report)
  • start_line=1 forces unnecessary disk re-reads (#253) — Clients like opencode that always send start_line=1 no longer trigger mode override to lines:1-999999 + fresh=true. start_line=1 is now correctly treated as a no-op since line 1 is the default. Only start_line > 1 activates the lines-mode override
  • Git write-commands incorrectly compressedgit commit, git push, git pull, git merge, git rebase, git cherry-pick, git tag, git reset are now classified as verbatim (zero compression). Prevents terse engine from abbreviating subcommands in output that AI agents may re-use (daviddatu_ report)
  • PowerShell command wrapping — Single full-command strings (e.g. git commit -m "...") are no longer incorrectly wrapped in & '...' quotes on PowerShell, which caused "executable not found" errors
  • Terse dictionary safety — Removed git subcommand abbreviations (commit→cmt, branch→br, checkout→co, merge→mrg, rebase→rb, stash→st) from the GIT dictionary to prevent output corruption

[3.6.9] — 2026-05-19

Added

  • Context IR hot-path lineage — Every tool call now records source kind, tokens, duration, and content excerpt into the Context Intermediate Representation for full lineage tracking
  • Plugin-ready traits — Extracted CompressionPattern trait (patterns/) and ContextProvider trait (providers/) for future plugin extensibility
  • Pytest verbose compression — Dedicated pattern for pytest -v output: consolidates per-test lines, strips fixtures/collection/metadata, preserves tracebacks and test identifiers (#251, contributed by @sisyphusse1-ops)
  • Active Context Gate — Pressure-based auto-downgrade: when context utilization exceeds 75%, reads are automatically downgraded (full→map, map→signatures). Φ scores now computed with real task context from SessionState

Fixed

  • Workflow persistence blocking reads after crash — Workflows inactive >30 minutes are now auto-expired on load and at runtime. Read-only tools (ctx_read, ctx_multi_read, ctx_smart_read, ctx_search, ctx_tree, ctx_session) always pass through the workflow gate regardless of state
  • Misleading cache-hit message — Changed "Already in your context window" to neutral [unchanged, use cached context] with hint about fresh=true for forced re-read. Prevents confusion when server-scoped cache returns hits for files not seen by the current agent
  • Unable to clear context pressure (#244)ctx_ledger(action=reset) now correctly clears all ledger state
  • Windows CI CRLF assertion — Normalized line endings in include_str! test assertions
  • Flaky CI tests — Serialized environment-variable tests (serial_test), fixed anomaly persistence debounce race, relaxed attention stress threshold for shared runners

Changed

  • ARCHITECTURE.md — Fixed documentation drift: updated tool counts, Context IR description, dispatch flow diagram, removed references to non-existent files
  • CONTRACTS.md — Restructured as "LeanCTX Protocol Family" with Extension Contracts section for future plugin interfaces
  • README.md — Conversion-optimized structure with better hero section, install commands, and social proof

Tests

  • 18 new scenario tests for workflow staleness + cache message fixes (bazsi_reported_scenarios.rs)
  • 4 new workflow staleness/passthrough tests (workflow_done_scenarios.rs)
  • Context IR hot-path recording tests, trait implementation tests, doc integrity tests (hardening_ir_traits.rs)
  • Adversarial safety tests for pytest xfail/xpass and test name preservation

[3.6.8] — 2026-05-18

Added

  • Post-RRF Reranking Pipeline — New core/search_reranking.rs module with 5 scientifically-grounded signals applied after Reciprocal Rank Fusion:
    • Query-Type Classifier (SACL, EMNLP 2025) — Auto-detects Symbol / Natural Language / Architecture queries and adjusts BM25:Dense weight ratio (1.4:0.6 / 1.0:1.0 / 0.6:1.4)
    • Definition Boost (CoRNStack, ICLR 2025) — Symbol queries boost defining chunks (struct/function/class) by 3x via ChunkKind + AST keyword matching
    • File Coherence Boost (SweRank, 2025) — Files with multiple relevant chunks get a normalized 20% score boost
    • Noise Penalties (CoRNStack) — Test files (0.3x), legacy/compat (0.3x), examples (0.3x), barrel/index (0.5x), type stubs (0.7x) are automatically down-ranked
    • MMR Diversity (Carbonell & Goldstein, SIGIR 1998) — File-saturation decay prevents single-file dominance in top-k results via greedy reselection
  • BM25 Path-Enrichment (SACL, +712.8% recall) — File stem and parent directory are doubled into BM25 document content, enabling path-aware queries like "auth handler"
  • find_related action in ctx_semantic_search — Chunk-based similarity search: given a file path + line, finds semantically related code chunks across the project

Fixed

  • Workflow "done" state blocks all tools permanentlyhandle_complete now clears the workflow file (terminal state) instead of persisting it. Added safety nets: gate auto-clears stale "done" workflows, list_tools no longer restricts visibility in terminal state, and ctx_handoff pull/import refuses to restore "done" workflows
  • ctx_read lines:N-M mode hangs on large files — Line-range reads no longer trigger expensive build_graph_related_hint and find_similar_and_update_semantic_index computations (fast path bypasses all hint generation)

Tests

  • 15 new reranking scenario tests covering symbol boost, NL queries, test penalization, diversity, coherence, legacy/compat, type stubs, architecture classification, barrel files, qualified symbols, and multi-signal interaction
  • 10 new workflow scenario tests validating stop/clear/complete/handoff behavior with "done" state

[3.6.7] — 2026-05-18

Added

  • 3-Layer Model Registry (#242) — Replaced hardcoded substring matching for model context windows with a data-driven registry system:

    • Bundled registry (data/model_registry.json) — compiled into binary, covers 40+ models
    • Local registry (~/.config/lean-ctx/model_registry.json) — auto-updated via lean-ctx update
    • User overrides ([model_context_windows] in config.toml) — highest priority
    • Supports exact match, prefix match (e.g. gpt-5.5-0513 matches gpt-5.5), and family fallback
    • GPT-5.5: 1,048,576 | GPT-4.1: 1,047,576 | Gemini: 1,048,576 | Claude: 200,000
  • ctx_shell env parameter (#241) — New optional env object in tool schema lets LLMs explicitly pass environment variables to child processes. Useful for agent runtime vars (e.g. CODEX_THREAD_ID).

  • Agent env auto-forwarding (#241) — CODEX_*, CLAUDE_*, OPENCODE_*, HERMES_* prefixed environment variables from the parent MCP server process are automatically forwarded to child commands. Solves the problem of agent hosts starting MCP servers with a stripped environment.

  • PathJail container bypass (#240) — PathJail automatically disables in Docker/Podman containers via is_container() detection. Manual opt-out via path_jail = false in config.toml or LEAN_CTX_NO_JAIL=1 env var.

  • Copilot CLI support (#243) — Dedicated CopilotCli config type that writes to ~/.copilot/mcp-config.json with the correct format (mcpServers key, "type": "local", "tools": ["*"]). Copilot CLI is now a separate target from VS Code.

Fixed

  • Benchmark honesty — Structural compression modes (map, signatures) are now excluded from "best mode" ranking for non-code file types (Markdown, JSON, CSS, HTML, YAML, XML). These modes extract code structures (functions, classes) and are not applicable to data/markup files. Previous reports showed misleading 100% savings for JSON and 99.9% for Markdown; corrected to 0.5% and 5.6% respectively.

  • Copilot CLI MCP config (#243) — lean-ctx init --agent copilot now writes to ~/.copilot/mcp-config.json (not VS Code's Application Support path). Uses "mcpServers" container key, "type": "local", and includes required "tools": ["*"] field per GitHub docs.

  • PathJail CWD fallback (#240) — Project root derivation now includes a guarded CWD fallback with is_broad_or_unsafe_root() protection. Differentiated error messages explain why a path was rejected and how to fix it.

  • Invalid JSON config handling — All IDE config writers now use text-based injection for invalid JSON files instead of destructive overwrites. Original files are preserved; users get clear instructions on how to fix syntax errors.

Changed

  • VS Code / Copilot split — The combined "VS Code / Copilot" target is now two separate targets: "VS Code" (agent_key: vscode) and "Copilot CLI" (agent_key: copilot). Existing VS Code configurations are not affected.

[3.6.6] — 2026-05-17

Added

  • ABC-Inspired Agent Hardening — 5-phase enforcement inspired by the Agentic Brownfield Coding project:
    • Bypass Hints — Detects when agents use native Read/Grep instead of lean-ctx tools and emits a single-line reminder with cooldown logic. Configurable via bypass_hints config key or LEAN_CTX_BYPASS_HINTS env var (modes: gentle, firm, off).
    • Tool Description Enhancement — All core tool descriptions now explicitly state "replaces native X" to guide AI agents directly from the MCP schema.
    • Rules Deduplication — Removed redundant tool mapping tables from injected rules. Tool descriptions now carry the mapping, rules focus on mode selection, anti-patterns, and editing workflow.
    • lean-ctx harden CLI — Activates strict enforcement mode (LEAN_CTX_HARDEN=1 in MCP configs). Optionally denies Bash in Claude Code's permissions.deny.
    • lean-ctx export-rules CLI — Exports high-confidence knowledge facts as editor-native rules (MDC for Cursor, AGENTS.md, CLAUDE.md).

Fixed

  • git status --porcelain truncation — Shell compression no longer truncates git status output when it doesn't match specific section parsing (e.g. --porcelain, --short flags). Developers now always see full status information.
  • init --agent rules injection — Global rules and skill file are now correctly injected. Fixed data dir split causing empty gain field in responses. (#238, #239)
  • Integration test alignmentrules_consistency and rules_inject tests updated to match new deduplicated rule content.

[3.6.5] — 2026-05-17

Fixed

  • CLAUDE_CONFIG_DIR support — MCP instructions and rules file paths now respect CLAUDE_CONFIG_DIR env var instead of hardcoding ~/.claude. Absolute paths under $HOME are collapsed to tilde form for display. Includes integration tests. (#235, contributed by @cburgess)
  • OpenCode rules location — Rules are now written to ~/.config/opencode/AGENTS.md (SharedMarkdown fenced section) instead of ~/.config/opencode/rules/lean-ctx.md which OpenCode never loads. Doctor check and uninstall updated accordingly. (#237)
  • Linux CI warnings — Fixed unreachable_pub in Landlock module, borrow_as_ptr in syscall wrappers, unnecessary_wraps on remove_linux_scheduler, and unused_variables/dead_code for platform-gated items.
  • MCP Resource Notificationsnotifications/resources/updated sent to subscribed clients after significant ledger changes (new entries, pressure threshold crossings). Enables proactive context refresh in supporting IDEs.
  • ctx_load_tools — New tool for explicit category management (load/unload/list). After each change, notifications/tools/list_changed is sent to subscribed clients so they re-fetch the tool list.
  • notifications/tools/list_changed — Outbound notification sent after dynamic tool category load/unload via ctx_load_tools. Clients automatically re-fetch the tool list.
  • MCP Peer Storage — Server stores the rmcp Peer<RoleServer> from initialize() for bidirectional notification delivery.

[3.6.4] — 2026-05-17

Added

  • Cognition Loop — Hebbian-inspired 8-step background knowledge reorganization: seed promote, structural repair, fidelity check, lateral synthesis, contradiction resolution, co-retrieval strengthening, decay, and compaction. Trigger manually via ctx_knowledge action=cognition_loop or configure automatic runs with autonomy.cognition_loop_interval_secs. (#cognition-loop)
  • Knowledge Archetypes — Typed knowledge nodes with 10 archetypes (Architecture, Decision, Gotcha, Convention, Dependency, Pattern, Workflow, Preference, Observation, Fact). Archetypes influence salience-based ranking and are auto-inferred from category names. Fully backward-compatible via #[serde(default)].
  • Fidelity Scoring — Two-tier quality metric (structural + semantic) for knowledge facts. Structural fidelity is computed deterministically from source presence, confirmation count, confidence, freshness, and feedback. Fidelity scores influence recall ranking.
  • Hebbian Edge Strengthening — Knowledge relation edges now carry strength (0.01.0) and decay_rate fields. Co-retrieved facts strengthen their edges via a saturating Hebbian formula. Exponential time-based decay and threshold-based pruning keep the graph lean.
  • Cross-Agent Knowledge Bridge — Controlled sharing of high-confidence facts between agents. Only publishable archetypes (Architecture, Convention, Decision, Dependency, Gotcha) with confidence ≥ 0.8 can be shared. Imported facts carry provenance tracking and a 10% trust penalty. New actions: bridge_publish, bridge_pull, bridge_status.
  • Auto-Update Scheduler — Native lean-ctx update --schedule with OS-specific schedulers (macOS LaunchAgent, Linux systemd/cron, Windows Task Scheduler). Subcommands: --schedule off, --schedule status, --schedule notify, --schedule 12h. Default is OFF — requires explicit opt-in.
  • Setup Auto-Update Opt-In — Interactive lean-ctx setup now asks whether to enable automatic updates (Step 9/11). Respects user freedom: default is N, non-interactive mode never enables, and the setting is always changeable via CLI or config.
  • --quiet flag for updaterlean-ctx update --quiet suppresses output when already current. Used by the auto-update scheduler to avoid noisy cron/LaunchAgent logs.
  • Session Update Notification — One-shot per-session update hint via session_update_hint(). Returns a single notification when a newer version is available, then stays silent for the rest of the session.
  • [updates] config section — New config block with auto_update (default false), check_interval_hours (default 6), and notify_only (default false). Overridable via LEAN_CTX_AUTO_UPDATE, LEAN_CTX_UPDATE_INTERVAL_HOURS, LEAN_CTX_UPDATE_NOTIFY_ONLY env vars.

Security

  • Constant-time token comparison — Proxy bearer token validation uses subtle::ConstantTimeEq to prevent timing side-channels.
  • Header forwarding allowlist — Proxy no longer blindly forwards all headers; only an explicit FORWARDED_HEADERS allowlist is passed through.
  • Secret detection — Regex-based scanning for API keys, tokens, and credentials in file reads and tool output. Integrated into io_boundary as a pre-read filter.
  • Shell allowlist — Configurable command allowlist for sandboxed shell execution with extract_base_command validation.
  • Audit trail — SHA-256 chained audit log for security-relevant events (tool denials, cross-project reads, capability checks). CLI: lean-ctx audit.
  • Capability-based access controlCapability enum with per-tool requirements and per-role grants. Tools are denied if the agent's role lacks the required capabilities.
  • macOS Seatbelt sandboxingsandbox-exec based process isolation for shell commands on macOS.
  • Linux Landlock sandboxing — Landlock LSM-based filesystem restrictions for shell commands on Linux.
  • OWASP Agentic Top 10 alignment — Module mapping lean-ctx security features to the OWASP Top 10 for Agentic Applications.
  • Signed handoff bundles — Ed25519 signatures on agent handoff bundles for provenance verification.
  • PathJail expanded — 16 path-like parameter keys now validated (including destination, old_path, new_path, config_path, output).
  • Reference store — Large tool outputs (>4000 chars) stored server-side with opaque IDs to prevent context bloat.
  • Proxy metrics — Atomic counters for request totals, tokens saved, and bytes compressed.

[3.6.3] — 2026-05-17

Fixed

  • Windows PowerShell lean-ctx -c quoting bug — Dynamic aliases (npm, pnpm, etc.) failed on PowerShell 5 with ObjectNotFound error because @args inside double-quoted strings was splatted instead of treated literally. Fixed by extracting the script block body into a variable with backtick-escaped @args.
  • commitcmt string mangling — The terse compression dictionary replaced "commit" inside compound words (pre-commit), quoted strings, and colon-delimited contexts. Fixed replace_whole_word to use a proper word-boundary function that treats hyphens, underscores, and quotes as word characters.
  • Dashboard Live Observatory "0 tokens" display — Non-file tools (e.g. ctx_search, ctx_shell) showed "Original · 0 tokens" when clicking "Compare". Now shows a token savings summary bar for non-file operations and reserves the full before/after text comparison for file reads (ctx_read, ctx_multi_read).

[3.6.2] — 2026-05-16

Fixed

  • Token Buddy broken ASCII art — Buddy sprite displayed as comma-separated single line instead of multi-line ASCII art. Root cause: ascii_art (a JSON array) was passed directly to the HTML escaper without joining with newlines. Fixed in cockpit-overview.js.
  • Context Ledger not recording MCP reads — Files read via the MCP server path were not appearing in the "Files in Context" dashboard section. Root cause: the dispatch layer was checking the wrong data directory (~/.lean-ctx vs ~/.config/lean-ctx set via LEAN_CTX_DATA_DIR). Ledger recording now correctly happens in dispatch/mod.rs after tool execution.
  • Config schema validation rejecting ide_paths and lsp sections — Users configuring per-IDE allowed paths or LSP binary overrides received "Unknown key" warnings. Added ide_paths (dynamic keys), lsp (with language-specific entries), and top-level project_root to the schema.

Changed

  • Dashboard navigation icons — Replaced ASCII-art navigation indicators ([~], [##], [<>], etc.) with clean SVG outline icons (Feather-style). Each view now has a distinct, professional icon.
  • "Index required" guidance — Dependencies, Call Graph, and Symbols pages now show a clear empty state with instructions to run lean-ctx index build when no index data is available, instead of generic "loading" or error messages.

[3.6.1] — 2026-05-16

Added

  • lean-ctx config apply — New command to validate config, restart daemon/proxy, and run safety checks (RAM limits, session count). Alias: config reload. (#231)
  • ctx_multi_read fresh parameter — New fresh: bool argument to bypass cache and force full re-read for all paths. Essential for subagents that don't share the parent's cache. (#230)
  • Per-IDE allowed paths — Configure project-specific file access restrictions per IDE integration. (#221)
  • Response verbosity control — Configurable verbosity levels for tool responses. (#222)
  • LSP graceful degradation — LSP server now degrades gracefully when tree-sitter parsing fails, with doctor health check and config.toml configuration support.
  • FTS5 archive search — Full-text search over archived context entries using SQLite FTS5 for fast historical queries.
  • Project root configuration — Explicit project_root config option for multi-project workspaces.
  • lean-ctx restart command — Restart all lean-ctx processes cleanly without manual kill.
  • Zed ctx_edit guard — Prevents accidental edits in Zed when file is not in project scope.
  • LEAN_CTX_SAVINGS_FOOTER env var — Shows compression savings in shell output when enabled.
  • enable_wakeup_ctx config option — Control whether background context wakeup is active.

Fixed

  • pi-lean-ctx disabling built-in tools (#232) — Pi extension now runs in "additive" mode by default, preserving Pi's native tools (read, bash, ls, find, grep). Set LEAN_CTX_PI_MODE=replace for the old behavior that disables overlapping builtins.
  • ctx_multi_read stale cache (#230) — Subagents that inherit the parent's process but not its cache state can now use fresh: true to bypass stale entries.
  • ctx_read deadlock with concurrent subagents (#226, #229) — Reduced lock contention by minimizing blocking_write() scope and adding a timeout guard. Prevents async runtime contention when multiple agents read the same file simultaneously.
  • Zombie process management — Complete overhaul: lean-ctx stop now unloads macOS LaunchAgent/Linux systemd service before sending SIGTERM, distinguishes MCP server/hook child processes (which are not killed, as IDEs respawn them), and cleans up reliably without requiring a reboot.
  • XSS in cockpit-live.js — Sanitized user-controlled strings in dashboard output to prevent script injection.
  • MCP config not updated after lean-ctx update (#224) — settings.json / MCP config now auto-refreshes after binary update so IDEs pick up new tool versions immediately.
  • ctx_shell missing compression info (#225) — renderCall/renderResult properly delegated to baseBashTool; compression savings now visible in Pi agent output.
  • Windsurf hooks installationhooks.json is now installed regardless of the --global flag, fixing cases where Windsurf-specific hooks were silently skipped.
  • Windows LSP URI handling — Correct file:///C:/ URI format on Windows; prevents "file not found" errors in LSP diagnostics.
  • Opencode backup integration — Fixed configuration backup path resolution for opencode IDE.
  • Dashboard "Context Handles" empty — Frontend correctly maps API fields (ref_label, source_path, pinned as string→boolean).
  • Chat messages/logs ordering — Newest entries displayed first across all dashboard sections.
  • CI stability — Test timeout increased to 90 min for Windows cold-cache; --lib flag for macOS tests prevents daemon hangs; msys2/setup-msys2 action pinned to prevent supply-chain attacks; background index build skipped when LEAN_CTX_DISABLED is set.

Changed

  • Dashboard redesigned — Three separate tabs (Live Context, Items, System) consolidated into a single vertically-scrolling page. Eliminates duplicate information, provides a unified view with stat grid (IDE, Context %, Files, Saved tokens, Tool Calls), estimated context window, context handles, chat history, and recent activity — all on one page.
  • Proxy status simplified — Removed confusing standalone "Proxy" cell. Status now integrated into the "IDE" cell showing hook tier (e.g., "Full (9/9)" for Cursor Tier 1). Cursor users no longer see misleading "Proxy: Idle" since Cursor does not route through external proxies.
  • Model detection improved — Background models (flash, mini, haiku, nano, small) are now ignored when persisting detected model, ensuring only the primary user-facing model is stored. Model detection staleness window extended from 1h to 24h.
  • model_context_window consolidated — Redundant branches merged: Claude/O-series → 200k, GPT/Codex/DeepSeek → 128k, Gemini → 1M, Mistral/Codestral → 256k.
  • Pi extension dependencies — Deprecated @mariozechner libraries replaced with @earendil-works packages. (#220)
  • Clippy clean — All warnings resolved across the entire codebase (needless_pass_by_value, if_same_then_else, uninlined_format_args, redundant_closure, map_unwrap_or, collapsible_if).
  • Documentation — Tool counts harmonized to 56+ across all docs; LSP and FTS5 features documented.
  • Codebase streamlining — UX hardening pass: clearer error messages, reduced log noise, faster startup.

[3.6.0] — 2026-05-14

Added

  • Context Radar — Full budget breakdown showing system prompt (rules), user messages, agent responses, lean-ctx tools, other MCP tools, native reads, and shell output as percentage of context window. Compaction-aware: distinguishes current-window metrics from cumulative session totals. Exposed via ctx_session budget, dashboard API, and ctx_radar tool.
  • Unified Context Intelligence — IDE hooks across Cursor (10 observe events including afterMCPExecution, postToolUse, afterShellExecution, beforeReadFile, afterAgentResponse, afterAgentThought, beforeSubmitPrompt, preCompact, sessionStart, sessionEnd), Claude Code (PostToolUse, UserPromptSubmit, Stop, PreCompact, SessionStart/End), Windsurf (post_mcp_tool_use, post_run_command, post_cascade_response, pre_user_prompt), and Codex/Gemini. Captures ~90% of context traffic automatically — no user configuration needed.
  • LLM Proxy Introspection — Request analyzer (introspect.rs) for Anthropic, OpenAI, and Gemini APIs with RequestBreakdown struct providing exact system prompt tokens, message tokens, tool definition tokens, and image counts. Ground-truth token counts when proxy is active.
  • Rules Scanner — Scans .cursorrules, .cursor/rules/*.mdc, AGENTS.md, and global rules at MCP server start. Counts tokens per file and provides RulesTokens estimate for system prompt budget.
  • Windows Named Pipe IPC — Reliable daemon IPC using WaitNamedPipeW for proper pipe existence checks (replaces broken fs::metadata), retry loop with 50ms backoff on ERROR_PIPE_BUSY and NotFound, stderr fallback to inherit() instead of null() for visible errors. 5 new Windows-specific unit tests. (PR #219)
  • Dashboard Context Cockpit — Complete redesign with tab-based UI: Overview (KPIs, pressure gauge), Budget Radar (stacked bar chart with legends), Context Items (active files with compression stats), Runtime (control plane, dynamic tools, bounce detection), and Timeline (recent events). Each section includes user-friendly explanations.
  • Bounce Detection — New bounce_tracker module detects when compressed reads are immediately followed by full re-reads ("bounces"), tracks wasted tokens per file extension, and adjusts savings metrics to report honest numbers.
  • Context Gate — New context_gate module provides pre-dispatch mode override (bounce-prevention, intent-target, graph-proximity, knowledge-relevance) and post-dispatch recording with eviction/elicitation hints for every read operation.
  • MCP Resources — 5 subscribe-capable resources (lean-ctx://context/summary, /pressure, /plan, /pinned, /bounce) expose context state to supporting IDEs without tool-call overhead.
  • MCP Prompts — 5 slash commands (/context-focus, /context-review, /context-reset, /context-pin, /context-budget) for IDE-native context management.
  • Elicitation — Rate-limited context decision suggestions (max 1x per 20 tool calls) for pressure, large files, and budget exhaustion with graceful fallback hints.
  • Dynamic Tools — 6 tool categories (core, arch, debug, memory, metrics, session) with on-demand loading via tools/list_changed for clients that support it; Windsurf 100-tool limit handled automatically.
  • Client Capability Detection — Runtime detection of 9 IDE clients with Tier 14 classification; dynamically gates MCP resources, prompts, elicitation, and dynamic tools based on client support.
  • Dashboard Control Plane — 4 new API endpoints (/api/context-bounce, /api/context-client, /api/context-pressure, /api/context-dynamic-tools) with Runtime Control Plane panel showing IDE indicator, pressure gauge, bounce stats, and dynamic tool status.
  • Hybrid Enforcement — Automatic rewrite of rg, ls, and find commands to lean-ctx equivalents via shell hooks, ensuring all reads go through the cached MCP path.
  • Silent-by-default — All meta output (budget warnings, session hints, compression stats) gated behind protocol::meta_visible(), keeping tool results clean for programmatic consumers.
  • Pi Extension improvements — Builtin tool replacement: ctx_ versions automatically disable overlapping Pi builtins. MCP bridge cleanup removes redundant CLI tool prefix filter. (PR #216)

Fixed

  • Budget not resetting on /newBudgetTracker and context_radar.jsonl now reset on MCP initialize (the real session boundary when IDE starts a new connection), not on task change. SharedSession mode correctly skips reset to avoid killing counters for other clients in daemon setups.
  • Tool preference lost after compaction — LITM end_block now includes tool-preference reinforcement line (ctx_read>Read ctx_shell>Shell ...) for sessions with 3+ tool calls, surviving IDE compaction.
  • ctx_read hang in subagents (#215) — Removed redundant tokio::task::block_in_place call and minimized cache_lock.blocking_write() scope to prevent async runtime contention.
  • ctx_read 57s on large files — Introduced 32KB content limit for semantic indexing and 200-entry cap for similarity search, reducing 64KB Cyrillic markdown from 57s to 0.59s.
  • Windows cargo-binstall failures (#213) — Development-only binaries (gen_mcp_manifest, gen_tdd_schema) moved from [[bin]] to [[example]] so cargo install and cargo-binstall skip them.
  • Windows doctor bashrc false positive (#214) — is_active_shell_impl now checks BASH_VERSION on Windows before flagging .bashrc as outdated.
  • Windows env.sh bash validation — Skip bash -n syntax check on Windows where backslash paths are invalid bash.
  • Windows named pipe pipe_exists_true test — Changed #[test] to #[tokio::test] since ServerOptions::create() requires a Tokio runtime context.
  • macOS process hangs on update — Atomic binary replacement prevents corruption during self-update.
  • env.sh for-loop syntax error (#212) — Removed 2>/dev/null from for _lf in loop that broke POSIX shell parsing.
  • JSONL audit trail lost on reset — Session reset and new session events now rotate context_radar.jsonl to .prev instead of truncating.

Changed

  • Logging defaults — CLI default remains warn (clean output); daemon/MCP mode defaults to info. Early init_logging() in run() skips MCP entry paths so init_mcp_logging() can set its own level.
  • Radar memory capload_events() caps at 50,000 entries (keeps last N), preventing unbounded memory growth in extremely long sessions.
  • LITM compaction threshold — Tool-preference injection in end_block lowered from >10 to >3 tool calls, matching typical compaction timing in Claude Code (58 calls).
  • lettre advisory ignored — RUSTSEC-2026-0141 (Boring TLS hostname verification) added to deny.toml and audit.toml ignore lists; lean-ctx uses rustls, not Boring TLS.

[3.5.25] — 2026-05-13

Added

  • Process concurrency guard — New process_guard module limits concurrent lean-ctx processes to 4 via flock/fcntl slot locks, preventing CPU saturation when multiple agents trigger simultaneous operations.
  • Terse pipeline input cap & timeoutcompress() now skips inputs >64KB and enforces a 500ms deadline with per-stage budget checks, preventing runaway CPU on large outputs (#210).
  • Trigram set capscoring.rs limits the seen_trigrams HashSet to 10,000 entries, preventing unbounded memory growth on large inputs.
  • Property-based compression tests — Added proptest dev-dependency with invariant tests: safeguard_ratio never inflates, entropy_compress never exceeds original tokens, compress_output never inflates, and entropy output is a subset of input lines.
  • Canonical rules policy — New rules_canonical.rs module provides a single source of truth for all rule generation (MUST USE / NEVER USE tables, MCP instructions) across Hybrid and MCP modes.
  • Contract tests for rules consistency — 11 cross-IDE contract tests verify generated rules contain MUST/NEVER language, no contradictions between Hybrid/MCP modes, and correct tool mappings.
  • MCP JSON instructions field — Editor MCP configs now include an instructions field (where clients support it) with the canonical lean-ctx tool policy, truncated per client constraints.

Changed

  • Rules language strengthened — All rule templates, .cursorrules, MDC files, and SKILL.md now use CRITICAL: ALWAYS, MUST USE, and NEVER USE instead of PREFER / should. Ensures agents treat lean-ctx tool usage as mandatory.
  • Background index throttledspawn_index_build_background now runs with nice -n 19 and ionice -c 3 (Linux) to prevent CPU contention during setup.
  • env.sh self-heal hardened — Container self-heal logic now includes a 60-second cooldown and PID-lock check (max 4 concurrent), preventing heal loops in multi-shell environments.
  • Dictionary optimizationapply_dictionaries performs case-insensitive contains() check before replace_whole_word, reducing unnecessary string operations.
  • Quality gate optimizationextract_identifiers capped at 200 entries; identifier lookup in check() uses HashSet instead of linear contains().
  • Entropy compression safeguardentropy_compress now falls back to the original content when compression would inflate token count.

Fixed

  • 100% CPU on terse with large inputs (#210) — Combination of input cap, timeout budget, trigram cap, and process guard eliminates all known CPU hotspot scenarios.
  • Stale include_str! paths in integration testssecurity_hardening.rs and security_resolve_path_guard.rs updated to reference modularized file locations (session/state.rs, tools/server_paths.rs, registry-only dispatch).
  • Clippy warnings — Fixed map().flatten()and_then(), needless borrows, trailing commas, raw string hashes, and let...else patterns across multiple files.

[3.5.24] — 2026-05-13

Changed

  • Eliminate CliRedirect hook mode — Removed the HookMode::CliRedirect variant entirely. All agents now use either Hybrid (MCP for reads/search + shell hooks for command compression) or Mcp (MCP only). Cursor and Gemini CLI, previously CliRedirect, are now Hybrid with full MCP support. This ensures reads and searches always go through the cached MCP path while shell commands are compressed via hooks — the best of both worlds.
  • Cursor: automatic MCP installationlean-ctx init --agent cursor and lean-ctx setup now automatically install the lean-ctx MCP server config in ~/.cursor/mcp.json with all 50+ tools auto-approved. Previously, CliRedirect mode actively prevented MCP installation, causing Cursor to miss cached reads and search compression.
  • Gemini CLI: Hybrid mode with MCP — Gemini CLI now gets MCP server config alongside its shell hooks, enabling cached reads via ctx_read while preserving shell compression via hooks.
  • All agents default to Hybridrecommend_hook_mode() now returns Hybrid for all agents with shell access (Cursor, Gemini, Codex, Claude Code, OpenCode, Crush, Hermes, Pi, Qoder, Windsurf, Amp, Cline, Roo, Copilot, Kiro, Qwen, Trae, Antigravity, Amazon Q, Verdent). Only unknown agents without shell access fall back to Mcp.
  • Hybrid rules template v2 — Updated .cursor/rules/lean-ctx.mdc template to clearly instruct agents to use ctx_read and ctx_search (MCP) for reads/search, and lean-ctx -c (CLI) for shell commands.
  • SKILL.md updated — Removed --mode cli-redirect examples, updated to show Hybrid as the default mode for all agents.

Added

  • LEAN_CTX_QUIET=1 production mode — New environment variable that suppresses all informational output for production use: savings footers ([lean-ctx: X→Y tok, -Z%]), session-start hook messages, tee-log hints, and verbose reroute messages. Shell compression still runs — only the human-visible annotations are hidden. Codex users can set this in ~/.codex/config.toml under [mcp_servers.lean-ctx.env] to match default Codex output verbosity.
  • Redirect subprocess timeout increasedhandle_redirect timeout increased from 3s to 10s for more reliable operation on slow filesystems.

Removed

  • HookMode::CliRedirect — Enum variant, all match arms, CLI_REDIRECT_RULES constant, build_cli_redirect_instructions() function, and the lean-ctx-cli-redirect.mdc template file have been removed.
  • DedicatedCliRedirect / CursorMdcCliRedirect — Rules injection variants removed from rules_inject.rs.
  • disable_agent_mcp() call path — The init_cmd.rs code path that called disable_agent_mcp() for CliRedirect agents has been removed. All agents now call configure_agent_mcp().

Fixed

  • Cursor reads/search not using MCP — Root cause: CliRedirect mode prevented MCP installation, and .cursorrules / rule files instructed CLI-first usage. Now all rule files consistently instruct Hybrid mode (MCP reads + CLI shell).
  • Inconsistent rule files.cursorrules, AGENTS.md, project-level and global .cursor/rules/lean-ctx.mdc now all consistently instruct Hybrid mode instead of conflicting CLI-first vs MCP-first directives.

[3.5.23] — 2026-05-13

Added

  • RAM Guardian — adaptive memory management — New memory_guard module with RSS-based memory monitoring, peak tracking, and adaptive tiered eviction. Background guard task monitors memory pressure and triggers cache eviction at configurable thresholds (max_ram_percent, default 5%). Uses jemalloc as global allocator on Unix for aggressive memory return (dirty_decay_ms:1000, muzzy_decay_ms:1000). New jemalloc_purge() and force_purge() for explicit arena cleanup. Platform-specific RSS reading via task_info() (macOS) and /proc/self/status (Linux). New dependencies: tikv-jemallocator, tikv-jemalloc-ctl, zstd, memmap2.
  • zstd-compressed session cacheCacheEntry now stores content as zstd-compressed Vec<u8> instead of raw String, reducing in-memory cache footprint by ~6080%. New CacheEntry::new(), content(), set_content() API. SessionCache::store() signature changed from content: String to content: &str.
  • Memory estimation and unload for indexesBM25Index::memory_usage_bytes() / unload() and EmbeddingIndex::memory_usage_bytes() / unload() enable the RAM Guardian to reclaim index memory under pressure.
  • Dashboard memory API — New /api/memory endpoint exposing RSS, peak RSS, system RAM, pressure level, allocator type, and max sessions.
  • lean-ctx doctor RAM Guardian diagnostics — Doctor output now shows current RSS, system RAM, percentage, limit, and allocator type.
  • Configurable savings footer suppression — New savings_footer config option (auto | always | never) and LEAN_CTX_SAVINGS_FOOTER env var. In auto mode (default), token savings footers like [42 tok saved (30%)] are shown in CLI but suppressed in MCP/agent context to prevent context pollution. Addresses user feedback about footers being added to agent context.
  • Explicit server shutdownLeanCtxServer::shutdown() clears cache, saves session, and triggers force_purge() on MCP client disconnect.
  • Config schema: max_ram_percent, savings_footer — Both new configuration keys exposed via lean-ctx config schema with types, defaults, descriptions, and env var overrides.

Fixed

  • CLI savings footer bypasscli/common.rs::print_savings() was formatting footers independently of protocol::format_savings(), ignoring the savings_footer configuration. Now delegates to the central formatting function.
  • Daemon-delegated output footer leakage — When CLI commands (read, grep, ls) delegate to the daemon, the daemon's response could contain savings footers even when the CLI client has LEAN_CTX_SAVINGS_FOOTER=never. New filter_daemon_output() function strips footer lines client-side based on the client's own footer configuration.
  • Shared session store cap — Reduced MAX_CACHED_SESSIONS from 64 to 8 to prevent unbounded memory growth in multi-IDE setups.

Changed

  • CacheEntry API — Direct field access (entry.content) replaced with method call (entry.content()). All tools (ctx_compress, ctx_delta, ctx_share, ctx_dedup, ctx_read, ctx_preload) and tests updated.

[3.5.22] — 2026-05-13

Fixed

  • read: overlay/FUSE stat() raceread_file_lossy now opens the file first and uses fstat() on the file descriptor instead of a separate stat() syscall. Fixes sporadic "No such file or directory" errors in Docker overlay/FUSE filesystems (e.g. Codex sandboxes) where stat() can return ENOENT for files that exist. Adds a single retry with 50 ms backoff on NotFound before giving up.

Added

  • Native Windows daemon support — IPC abstraction layer — New ipc/ module (mod.rs, process.rs, unix.rs, windows.rs) provides a platform-independent daemon transport layer. Unix uses UDS (unchanged behavior), Windows uses Named Pipes (\\.\pipe\lean-ctx-{hash}). All OS-specific code (libc::kill, PermissionsExt, UnixStream) is now isolated in ipc/unix.rs and ipc/windows.rs — no other module needs #[cfg(unix)] for daemon logic. windows-sys 0.59 added as target dependency. Implements #209.
  • HTTP-based daemon shutdown — New POST /v1/shutdown endpoint enables cross-platform graceful daemon shutdown. stop_daemon() now tries HTTP shutdown first, then SIGTERM/TerminateProcess as fallback, then force kill as last resort. No more direct libc::kill(SIGTERM) in daemon.rs.
  • build_app_router() extraction — Shared Axum router construction extracted from serve() and serve_uds(), eliminating ~70 lines of code duplication. Both TCP (serve()) and IPC (serve_ipc()) use the same router builder.
  • Parallel call graph build with progress trackingCallGraph::build_parallel() uses rayon for concurrent file analysis. New get_or_start_build() returns cached results immediately or starts a background build with live progress (BuildProgress struct with files_total, files_done, edges_found). Dashboard polls via /api/call-graph/status.
  • Dashboard: call graph progress barcockpit-graph.js shows a live progress bar during call graph builds instead of a blank loading state. Auto-polls every 2s and renders the completed graph once ready.
  • Dashboard: project file browser in Compression Labcockpit-compression.js now has two tabs: "Recent" (context ledger/events) and "Project" (all indexed files from /api/graph-files). Project tab includes search, file count, and token count per file. New /api/graph-files API endpoint returns indexed files sorted by token count.
  • Dashboard: improved compression lab layout — Sidebar/main grid layout with responsive breakpoint at 900px. File list shows token counts, mode auto-switches when selecting recently read files, search input for project files.

Fixed

  • 100% CPU after lean-ctx setup on Ubuntu — Two root causes fixed: (1) env.sh self-heal script could recursively spawn lean-ctx init via BASH_ENV outside containers. Now guarded with container detection (/.dockerenv), recursion guard (_LEAN_CTX_HEAL), and LEAN_CTX_ACTIVE propagation. (2) Graph index scanning could scan entire $HOME when setup was run outside a project. Now guarded with is_safe_scan_root() check, cross-process lock (startup_guard), 50k entry limit, and 2-minute timeout. LEAN_CTX_NO_INDEX env var skips indexing entirely. Fixes #210.
  • daemon.rs/daemon_client.rs now platform-independent — Removed all #[cfg(unix)] gates from lib.rs, cli/dispatch.rs, and setup.rs for daemon modules. daemon_client.rs auto-start works on all platforms (previously returned None on non-Unix).
  • Dashboard call graph timeout — Increased from 15s/30s to 60s to accommodate larger projects during initial build.

Changed

  • serve_uds() replaced by serve_ipc() — Takes a DaemonAddr enum instead of a PathBuf. Callers use daemon::daemon_addr() instead of daemon::daemon_socket_path().
  • daemon_socket_path() removed — Replaced by daemon::daemon_addr() which returns a DaemonAddr enum. All call sites updated (setup.rs, dispatch.rs).
  • Security hardening test updateduds_socket_sets_permissions now checks ipc/unix.rs instead of http_server/mod.rs (chmod 600 logic moved during IPC extraction).

[3.5.21] — 2026-05-12

Fixed

  • graph.db and graph.meta.json now honor LEAN_CTX_DATA_DIR — Property graph files are stored in $DATA_DIR/graphs/<project_hash>/ (consistent with the JSON graph index). Transparent migration moves existing files from <project>/.lean-ctx/ on first access. CodeGraph::open() signature changed from &Path to &str. All 12+ call sites updated. Hardcoded .lean-ctx/graph.db strings in ctx_impact and ctx_architecture replaced with actual resolved paths. Fixes #205.
  • Graph index UX: correct labels and configurable caplean-ctx gain now shows "files" instead of misleading "nodes" when using the JSON graph index fallback. A "(capped)" suffix appears when the file scan limit is reached. New config key graph_index_max_files (default: 5000, up from hardcoded 2000). Warning emitted when cap is hit. Fixes #206.
  • Config documentation accuracy — Removed phantom [compaction] section and non-existent [archive] fields from website docs. Corrected wrong defaults (compression_level: "off" not "standard", buddy_enabled: true not false, custom_aliases fields: command/alias not name/command, loop_detection.blocked_threshold: 0 not 6, autonomy.consolidate_cooldown_secs: 120 not 300). Added missing sections ([cloud], [proxy], [memory.*], etc.). Fixes #208.

Added

  • Dashboard expandable event details — Event cards in the Live Observatory are now clickable with an accordion pattern. Expanded panels show all available metrics: token savings bar, compression strategy, before/after lines, mode, path, duration. New /api/events/:id endpoint for lazy-loading full event details. Implements #207.
  • lean-ctx config schema — New CLI command that outputs a complete JSON schema of all configuration keys, types, defaults, descriptions, and env var overrides. Single source of truth for config documentation.
  • lean-ctx config validate — New CLI command that validates config.toml against the schema. Warns about unknown keys with Levenshtein-distance "did you mean?" suggestions. Exit code 1 on errors (CI-friendly).
  • Graph property graph tests — 6 new tests covering graph_dir() with/without LEAN_CTX_DATA_DIR, transparent migration (move and skip-when-exists), meta_path() integration, and CodeGraph::open() with custom data directory.

[3.5.20] — 2026-05-12

Fixed

  • Codex installer respects CODEX_HOMElean-ctx init --agent codex now reads the CODEX_HOME environment variable to determine the Codex config directory. Previously, all Codex files (config.toml, hooks.json, AGENTS.md, LEAN-CTX.md) were always written to ~/.codex, even when CODEX_HOME pointed elsewhere. All 11 call sites updated to use resolve_codex_dir(). Fixes #202.
  • Codex feature flag migrated from codex_hooks to hooks — The installer now writes hooks = true (the current Codex feature flag) instead of the deprecated codex_hooks = true. Existing codex_hooks = true entries are automatically migrated to hooks = true during install. The uninstall parser also handles both variants. Fixes #203.
  • lean-ctx ls rejects unsupported flags — Flags like -la, -l, -R are now rejected with a clear error message and usage hint instead of being silently treated as path arguments. Supported flags: --all/-a, --depth N. The shell hook continues to pass ls flags transparently to the system ls. Fixes #201.
  • Windows path format for inline rewriteshandle_rewrite_inline() (used by the OpenCode plugin) now returns native OS paths instead of unconditionally converting to Unix/MSYS format (/c/Users/...). On Windows, sanitize_exe_path() normalizes MSYS paths via normalize_tool_path(). Bash shell hooks still use to_bash_compatible_path() as before. New from_bash_to_native_path() function provides the inverse conversion. Fixes #204.

Added

  • Path normalization tests — 11 new normalize_tool_path() tests covering MSYS drives, backslashes, double slashes, trailing slashes, and verbatim prefixes. 6 new from_bash_to_native_path() tests including Windows/Unix roundtrips. Platform-specific sanitize_exe_path() tests for Windows MSYS normalization.

[3.5.19] — 2026-05-12

Added

  • Shell hook drop-in install — Users with .d/-style dotfiles (chezmoi, yadm, stow, oh-my-zsh custom/) now get hook fragments installed as numbered drop-in files (e.g. ~/.zshenv.d/00-lean-ctx.zsh) instead of inline fenced blocks. Detection is automatic (Style::Auto); override with --style=inline or --style=dropin. Transparent migration between styles preserves hand-edits via timestamped backups (.lean-ctx-<UTC>.bak). (#196)
  • Output policy classification — New OutputPolicy enum (Passthrough, Verbatim, Compressible) provides centralized command classification for the compression pipeline. Commands like gh api, az login, docker ps, kubectl get pods are now correctly classified and never compressed.

Fixed

  • Dashboard: 7 frontend data mismatch bugs — Complete attribute-by-attribute audit of all 17 dashboard pages revealed field name mismatches between frontend components and backend API responses:
    • cockpit-overview.js — SLO compliance now calculated from slo.snapshot.slos array; Verification card uses verif.total/verif.pass; streak_days === 0 no longer hidden by falsy check
    • cockpit-health.js — SLOs render from .slos (not .results); Anomalies handle direct array response; Verification uses correct total/pass/warn_runs fields; Bug Memory (Gotchas) uses trigger/resolution/occurrences/first_seen and handles enum severity/category
    • cockpit-agents.js — Swimlanes use actual API fields (id, role, status, status_message, last_active_minutes_ago, pid) instead of expected-but-absent name/model/tool_calls
    • cockpit-memory.js — Episodes use actions.length for tool count, tokens_used for token display, and parse tagged Outcome enum correctly
    • cockpit-live.jstokens_saved === 0 no longer hidden by falsy check in buildToolDetail
    • cockpit-compression.js — Removed unsupported diff mode from UI
    • cockpit-graph.js — Tooltip dynamically shows "B", "tok", or "lines" based on available size metric
  • Token Pressure accuracy — Context field temperature now uses pressure.utilization (weighted decay) instead of raw total_tokens_sent / window_size, and budget_remaining uses pressure.remaining_tokens for consistency with the Token Pressure card
  • Truncation bug causing increased token usage — Removed aggressive 8000-byte fallback truncation in patterns::compress_output that produced [… N lines omitted …] markers, causing AI models to retry commands. Large outputs now flow through the safety-aware compress_if_beneficial pipeline instead. Fixes #199.
  • Dashboard format utilitiespc() NaN guard for percentage formatting; fu() type guard for unit formatting; fmtNum normalized to consistent 'K' suffix
  • Dashboard route visibility — All dashboard route handlers narrowed from pub fn to pub(super) fn
  • Clippy duration_suboptimal_unitsDuration::from_millis(30_000)Duration::from_secs(30) in 4 locations
  • Shell hook: ls and find missing from alias list — Both commands are now included as Category::DirList in the generated shell hook, so ls and find output is tracked/compressed in hooked shells. Fixes #200.
  • Shell hook: non-interactive agent commands not tracked — The TTY guard ([ ! -t 1 ]) now has an agent-aware bypass: when LEAN_CTX_AGENT, CODEX_CLI_SESSION, CLAUDECODE, or GEMINI_SESSION env vars are present, commands are tracked even in non-interactive shells (Docker, Codex bash -c). Fixes #200.
  • Flaky SSE replay test — Rewrote events_endpoint_replays_tool_call_event to append directly to the event bus instead of depending on a fire-and-forget spawn_blocking task, eliminating CI timing failures on contended runners.

[3.5.18] — 2026-05-12

Fixed

  • gh api output no longer compressed — Commands like gh api repos/.../actions/jobs/.../logs are now passthrough (no compression, no truncation). Previously, large API responses were silently truncated by the generic 8000-byte fallback, making CI log debugging impossible. Also applies to gh run view --log and --log-failed flags.

[3.5.17] — 2026-05-12

Security

  • [Critical] LLM Proxy bearer token auth — The proxy server now supports optional bearer token authentication via LEAN_CTX_PROXY_TOKEN environment variable, preventing unauthorized access from other local processes.
  • [Critical] Symlink hijack protection on all write pathswrite_atomic() and context package atomic_write() now reject writes through symlinks, preventing an attacker from redirecting config writes to arbitrary files.
  • [Critical] Shell command validation — documented accepted risk — Explicitly documented in SECURITY.md that shell command validation is delegated to the AI agent's permission model by design, with CWD jail and output capping as compensating controls.
  • [High] Claude binary path validationclaude mcp add-json now validates that the resolved claude binary comes from a trusted directory (.claude/, /usr/local/bin/, /opt/homebrew/, etc.), preventing PATH hijack attacks. Override with LEAN_CTX_TRUST_CLAUDE_PATH=1.
  • [High] TOCTOU mitigation for config writes — New write_atomic_with_backup_checked() validates file mtime between read and write, detecting concurrent external modifications.
  • [High] Auto-approve transparencylean-ctx setup now displays a banner listing all auto-approved MCP tools with count. New --no-auto-approve flag disables auto-approve in editor configurations.
  • [High] Full integrity verification for context packagesverify_integrity() now validates content_hash, sha256 (composite hash of name:version:content_hash), and byte_size — previously only content_hash was checked.
  • [High] PathJail TOCTOU — documented accepted risk — Documented in SECURITY.md that the race condition between jail_path check and file operation requires openat/O_NOFOLLOW at syscall level for complete mitigation.
  • [High] Database TLS — documented accepted risk — Cloud server DB connection is localhost-only by default. Production deployments should use ?sslmode=require in DATABASE_URL.
  • [Medium] Timestamped config backups — Backup files now include Unix epoch timestamps (e.g., .lean-ctx.1715464800.bak) instead of overwriting a single .lean-ctx.bak file.
  • [Medium] Email enumeration timing fix — Login endpoint now performs a dummy Argon2id verification when the user doesn't exist, equalizing response time to prevent email existence oracle attacks.
  • [Medium] Verification token TTL reduced — Email verification tokens reduced from 24h to 2h. Old pending tokens are now invalidated before issuing new ones.
  • [Medium] Knowledge fact provenance trackingKnowledgeFact struct now includes imported_from: Option<String> field, set to name@version when facts are imported from context packages.

Fixed

  • Dependabot: mermaid security update — Updated mermaid from 10.9.5 to 10.9.6 in cookbook examples (CSS injection fix).

[3.5.16] — 2026-05-11

Security

  • [Critical] Path traversal in tee show — The lean-ctx tee show <filename> CLI command accepted path separators and .. in the filename argument, allowing reads of arbitrary files outside the tee log directory. Now enforces strict basename-only validation.
  • [Critical] Python/Shell injection via intent parameter — The ctx_execute tool interpolated the intent parameter raw into generated Python and shell scripts, allowing code injection through crafted intent strings. Now sanitized to alphanumeric characters only (max 200 chars).
  • [Critical] CSPRNG failure silently ignored — Two getrandom::fill calls (token generation + CSP nonce) silently discarded errors, which could result in predictable all-zero tokens/nonces. Now panics on CSPRNG failure to guarantee cryptographic safety.
  • [Critical] Dashboard path traversal bypass — The /api/compression-demo endpoint allowed absolute paths to bypass pathjail filesystem jail. All paths now go through jail_path unconditionally.
  • [Critical] MCP stdio integer overflow — Malicious Content-Length headers could cause integer overflow in frame length calculation, leading to unbounded memory allocation. Now uses checked_add with strict size cap.
  • [High] Token exposure on loopback — Anonymous loopback GET requests to the dashboard received the auth token injected into HTML, allowing any local process to steal it. Now requires explicit ?token= query parameter.
  • [High] Nonce-based CSP replaces unsafe-inline — Dashboard Content-Security-Policy upgraded from script-src 'unsafe-inline' to per-response cryptographic nonce, eliminating XSS via inline script injection.
  • [High] Panic payloads leaked to MCP clients — Tool panics returned full panic messages (potentially containing secrets/paths) to clients. Now returns generic error; details logged server-side only.
  • [High] ctx_execute output not redacted — Output from ctx_execute bypassed the redaction engine, potentially leaking secrets. Now applies redact_text_if_enabled like ctx_shell.
  • [High] Cross-project data access via ctx_share — Shared agent data was stored in a flat directory, allowing agents from different projects to read each other's data. Now scoped under project_hash subdirectory.
  • [High] PowerShell command interpolation — On Windows, PowerShell commands were interpolated into script strings. Now writes to temp file and executes via -File.
  • [High] Cloud server error information leakinternal_error helper returned raw database/OS error strings to HTTP clients. Now returns generic {"error":"internal_error"}.
  • [High] SSE subscriber cap enforced — The 64-subscriber-per-channel cap previously only logged a warning but still allowed new subscriptions. Now returns None and falls back to dead channel, preventing resource exhaustion.
  • [High] Rust sandbox inherited full environment — The execute_rust function (rustc + compiled binary) did not strip inherited environment variables, exposing secrets and enabling LD_PRELOAD-style attacks. Now applies the same env_clear() + allowlist as other sandbox runtimes.
  • [Medium] Argon2id password hashing — Cloud server passwords migrated from salted SHA-256 to Argon2id with legacy fallback for existing hashes.
  • [Medium] SQLite busy_timeout — Added 5-second busy_timeout to all SQLite connections to prevent SQLITE_BUSY errors under contention.
  • [Medium] ReDoS mitigation for filter rules — Both runtime and validation paths for user-authored filter TOML patterns now use RegexBuilder with 1 MiB DFA size limit.
  • [Medium] Context summary redaction/v1/context/summary endpoint now redacts events at Summary level before aggregation, preventing leakage of sensitive knowledge keys/categories.
  • [Medium] A2A handoff error sanitization — Parse and write errors no longer include OS-level details or filesystem paths in HTTP responses.
  • [Medium] ctx_search and ctx_tree parameter clampingmax_results capped at 500, depth capped at 10 to prevent resource exhaustion.
  • [Medium] ctx_shell cwd fail-closed — Invalid working directory now returns error instead of silently falling back to process cwd.
  • [Medium] Community detection graceful degradation — All SQLite unwrap() calls in community.rs replaced with proper error handling returning empty graphs instead of panicking.
  • [Medium] Defense-in-depth path canonicalizationread_file_lossy now verifies canonical paths stay within project root (warning-only layer behind primary jail_path enforcement).
  • [Medium] Sandbox environment isolationctx_execute subprocesses now start with env_clear() + explicit allowlist (PATH, HOME, LANG, TERM, TMPDIR) instead of inheriting all parent environment variables.
  • [Medium] Hook temp file hardening — Temp directory for hook redirects now has chmod 700 (Unix), and filenames include PID scoping to prevent symlink races.
  • [Medium] PowerShell temp file cleanup.ps1 temp files are now deleted on all exit paths (success, spawn error, wait error).
  • [Medium] ctx_execute temp file lifecycle.dat temp files are now cleaned up by Rust after sandbox execution (regardless of script success), with file size validation before processing.
  • [Medium] /health rate limiting — Health endpoint no longer bypasses rate limiter and concurrency semaphore, preventing use as amplification oracle.
  • [Low] validate_filter_file regex bounds — Validation path now uses bounded RegexBuilder matching runtime behavior.
  • [Low] Corrected check_secret_path_for_tool tool name — Changed hardcoded "ctx_read" to "resolve_path" for accurate policy logging.

Fixed

  • Structural output protectiongit diff, git show, git blame, git log -p, git stash show, diff, colordiff, icdiff, and delta output was being mangled by up to three compression layers (pattern compression + terse pipeline + generic compressors like log_dedup/truncation). These commands now get a dedicated fast path: only the specific pattern compressor runs (light cleanup: strip index headers, limit context lines), all other compression stages are bypassed. Every +/- line, hunk header, and blame annotation is preserved verbatim. Also protected in the MCP server path (ctx_shell).
  • zsh shell hook breaks command completion — After sourcing the lean-ctx shell hook, tab completion for aliased commands (git, cargo, docker, etc.) stopped working. Added a zsh completion wrapper (_lean_ctx_comp) that delegates to the original command's completion function via _normal. Fixes #193.

Added

  • Roadmap: Context Runtime research modules — 13 new core modules implementing research from information theory, graph theory, and cognitive science:
    • adaptive_chunking — Content-defined chunking with Rabin-Karp fingerprinting and entropy-aware split points
    • attention_placement — Attention allocation scoring based on recency, frequency, and structural importance
    • cognitive_load — Cognitive load estimation using Halstead metrics and cyclomatic complexity
    • cyclomatic — Cyclomatic complexity analysis via control-flow graph extraction
    • gamma_cover — Gamma cover set selection for minimal representative context subsets
    • graph_features — Property graph feature extraction (betweenness, clustering coefficient, community bridge detection)
    • information_bottleneck — Information bottleneck compression with iterative Blahut-Arimoto
    • mdl_selector — Minimum Description Length model selection for compression strategy
    • memory_consolidation — Memory consolidation with exponential decay and importance-weighted retention
    • progressive_compression — Multi-level progressive compression with quality gates
    • splade_retrieval — Sparse Lexical and Expansion retrieval for context-aware search
    • structural_diff — AST-level structural diff for semantic change detection
    • structural_tokenizer — Language-aware tokenization using tree-sitter AST for 18 languages
  • Louvain community detection O(m) — Rewrote community.rs from O(n²) adjacency scan to edge-list-based Louvain with modularity optimization, supporting weighted edges and hierarchical communities.
  • Enhanced PageRank — Damped PageRank with configurable alpha, convergence detection, and seed biasing for context-aware node ranking.
  • SPLADE-enhanced BM25 — BM25 index now supports sparse expansion terms for improved recall on semantically related queries.
  • Config module restructured — Split monolithic config.rs into config/mod.rs, config/memory.rs, config/proxy.rs, config/serde_defaults.rs for maintainability.
  • shell_activation config option — New shell_activation setting in config.toml with three modes: always (default, backward-compatible), agents-only (auto-activates only in AI agent sessions like Claude Code, Cursor, Windsurf), and off (fully manual). Controlled via config file or LEAN_CTX_SHELL_ACTIVATION environment variable. Addresses feedback that lean-ctx shell hooks were too invasive for users who only need it in specific AI agent contexts.
  • .lean-ctx-id project identity file — Projects can now declare a unique identity via a .lean-ctx-id file in the project root. This takes highest priority in composite project hashing, solving Docker environments where multiple projects share the same mount path (e.g. /workspace). Simply create a file with a unique name (e.g. echo "my-project-alpha" > .lean-ctx-id).
  • Identity-aware storage for all cachesgraph_index, semantic_cache, bandit, and embedding_index now use composite project hashes (path + identity markers) instead of path-only hashes. Includes automatic migration from legacy storage locations. Fixes cross-project context bleed in Docker environments.
  • Security hardening test strengthened — Dashboard token embedding no longer falls back to loopback bypass; tests now verify the stricter valid_query-only gate.

[3.5.15] — 2026-05-11

Fixed

  • Dashboard "unauthorized" on localhost — Users accessing the dashboard on localhost after v3.5.14 saw /api/stats: unauthorized because the browser didn't have the auth token. The server now auto-injects the token into HTML for loopback connections (127.0.0.1, ::1) so the JS fetch interceptor can authenticate API calls automatically. API auth remains fully active — no bypass, no CSRF risk. Fixes webut's report.
  • Dashboard probe sends Bearer — The dashboard_responding health probe now sends the saved Bearer token, so the "already running" detection works correctly with auth-enabled dashboards.
  • Large file crash / MCP hang — Reading very large files (multi-GB) via ctx_read or ctx_smart_read caused the MCP server to allocate unbounded RAM and crash. Now enforced at 4 layers: binary file detection rejects before any I/O, metadata().len() checks reject before allocation, read_file_lossy refuses unbounded reads on stat() failure, and MCP dispatch returns Err(ErrorData) instead of Ok("ERROR:...") to prevent client retries. Fixes sb's report.

Added

  • Binary file detection (core::binary_detect) — Detects 100+ binary file extensions (Parquet, SQLite, ONNX, ZIP, images, ML models, bytecode, archives, fonts, disk images) plus magic-byte NULL check on the first 8 KB. Returns human-readable file type labels (e.g. "columnar data file", "ML model file"). Used across ctx_read, ctx_smart_read, ctx_multi_read, and ctx_prefetch.
  • Live Observatory event explanations — Every event in the dashboard's Live Observatory now has a ? help icon. Click to expand an inline explanation of what the event means and whether user action is needed. SLO violations ("violated · CompressionRatio") and compression events ("entropy_adaptive · 293 → 264 lines") are now clearly documented. Event type legend added to "How it works" section.
  • 3 new security hardening testsdashboard_api_auth_never_bypassed_for_loopback, dashboard_probe_sends_bearer_token, loopback injection signature validation.
  • memory_cleanup setting — New config/env option (LEAN_CTX_MEMORY_CLEANUP) with two modes: aggressive (default, 5 min idle TTL — best for single-IDE use) and shared (30 min TTL — best when multiple IDEs or models share lean-ctx context). Visible in lean-ctx doctor and lean-ctx config. Suggested by sb.

Improved

  • Graceful error messages for binary/oversize files — Instead of crashing or returning generic errors, binary files get a helpful message like "Binary file detected (.parquet, columnar data file). Use a specialized tool for this file type." Oversize files suggest mode="lines:1-100" for partial reads.
  • MCP error semantics — Binary/oversize file errors now return Err(ErrorData::invalid_params(...)) at the MCP dispatch level, signaling to clients that retrying won't help. Previously returned Ok("ERROR: ...") which caused some clients to retry indefinitely.

[3.5.14] — 2026-05-10

Performance

  • BLAKE3 hashing — Replaced all MD5 (md5_hex, md5_hex_bytes) with BLAKE3 via centralized core::hasher module. 12 duplicate hash functions eliminated across the codebase. BLAKE3 is ~3x faster than MD5 for large inputs with better collision resistance.
  • Tree-sitter Query Cache — Compiled tree-sitter Query objects are now cached in OnceLock<HashMap> statics in chunks_ts, signatures_ts, and deep_queries. Eliminates re-compilation of query patterns on every file parse. Parser instances reuse via thread_local!.
  • Token cache upgrade — Token cache enlarged from 256→2048 entries with BLAKE3-based keys and LRU-like eviction (half-evict instead of full clear). Reduces redundant BPE tokenization across sessions.
  • SQLite Property Graph optimized — Added PRAGMA cache_size = -8000, mmap_size = 256MB, temp_store = MEMORY. 5 new composite indices on nodes(kind), nodes(kind, file_path), edges(kind), edges(source_id, kind), edges(target_id, kind). busy_timeout(5000ms) for WAL contention.
  • Parallel indexingrayon::par_iter for CPU-bound deep-query parsing in ctx_impact build (embeddings feature path).
  • ModePredictor ArcModePredictor stored as Arc<ModePredictor> to avoid deep cloning on every ctx_read call.
  • Compact JSON serializationProjectIndex::save() uses serde_json::to_string (compact) instead of to_string_pretty, reducing index file size and serialization time.
  • Server dispatch deduplicatedcount_tokens called once per request instead of redundantly after terse pass when content unchanged.

Improved

  • Rules: Mode Selection Decision Tree — Adopted community-contributed improvement (credit: Zeel Connor). Rules now include a numbered decision tree for ctx_read mode selection and an anti-pattern warning against using full for context-only files. Applied across all rule formats (shared, dedicated, Cursor MDC, CLI-redirect).
  • Flaky test fixes — BM25 tests (save_writes_project_root_marker, max_bm25_cache_bytes_reads_env) now acquire test_env_lock() to prevent env::set_var race conditions. ContextBus tests use isolated temp SQLite databases via test_bus() instead of shared global DB.

Added

  • core::hasher module — Centralized BLAKE3 hashing: hash_hex(bytes), hash_str(s), hash_short(s). Single source of truth for all non-cryptographic hashing.
  • core::community module — Louvain-based community detection on the Property Graph (file clustering by dependency).
  • core::pagerank module — PageRank computation on the Property Graph for file importance scoring.
  • core::smells module — Code smell detection (long functions, deep nesting, high complexity).
  • ctx_smells tool — MCP + CLI tool for code smell analysis with graph-enriched scoring.
  • 58 MCP tools — Up from 57 in previous release (added ctx_smells).

[3.5.13] — 2026-05-10

Fixed

  • Instruction files no longer compressed — SKILL.md, AGENTS.md, RULES.md, .cursorrules, and files in /skills/, /.cursor/rules/, /.claude/rules/ are now always delivered in full mode, bypassing all heuristic/bandit/adaptive mode selection. This was the root cause of agents losing instructions after v3.4.7 when the Intent Router was introduced. Guards added in 5 code paths: resolve_auto_mode, predict_from_defaults, select_mode_with_task, auto_degrade_read_mode, and CLI read_cmd. Fixes #159 regression, resolves GlemSom's report.
  • Markdown files exempt from aggressive compression.md, .mdx, .txt, .rst files no longer fall into the aggressive default bucket in predict_from_defaults. These file types return None (= full mode) to prevent stripping prose/instruction content.
  • Windows Claude Code PowerShell compatibility — Claude Code hook matchers now include PowerShell|powershell on Windows, so PreToolUse hooks fire regardless of whether Claude uses Bash or PowerShell. Rewrite script also accepts PowerShell tool names. Fixes #192.

Added

  • is_instruction_file() public API — Reusable guard function detecting instruction/skill/rule files by filename and path patterns. Used across MCP, CLI, and server dispatch paths.
  • Lean4 formal proofs — Theorems 12-13 in ReadModes.lean: instruction files always resolve to full mode, content is always preserved.
  • 7 new regression testsinstruction_file_detection, resolve_auto_mode_returns_full_for_instruction_files, defaults_never_compress_markdown, and PowerShell hook matcher tests.

[3.5.12] — 2026-05-09

Improved

  • RAM optimization: eliminate double tokenizationextract_chunks in bm25_index.rs, artifact_index.rs, and chunks_ts.rs no longer allocates a tokens: Vec<String> per chunk. Token count is computed inline; the vector is set to Vec::new(). add_chunk tokenizes from content once for the inverted index and overwrites token_count from the fresh result. This eliminates one redundant allocation + tokenization pass per chunk during index build.
  • MemoryProfile fully wired — The MemoryProfile enum (low / balanced / performance) now actively controls runtime behavior:
    • max_bm25_cache_bytes() respects profile limits (64 / 128 / 512 MB), with explicit user config taking precedence.
    • Semantic cache (SemanticCacheIndex) is skipped entirely when memory_profile = low.
    • Embedding engine loading is skipped in ctx_semantic_search and ctx_knowledge when memory_profile = low.
  • Doctor shows active memory profilelean-ctx doctor now displays the effective memory profile (low / balanced / performance), its source (env / config / default), and what it controls (cache limits, embedding status). Helps users understand and debug RAM behavior.
  • MCP manifest regenerated — Updated mcp-tools.json to reflect current tool count (57 granular tools).

[3.5.11] — 2026-05-09

Fixed

  • Cache-loop elimination for hybrid-mode agents — When an agent reads a file with mode=auto (compressed) and then re-reads with mode=full, the full content is now delivered immediately instead of returning a 2-line "already in context" stub. Previously, agents (especially smaller/local models) needed 3 calls to get full content: auto → full (stub) → fresh. A new full_content_delivered flag on cache entries tracks whether uncompressed content was already sent for the current hash.
  • Cache stub text no longer provokes unnecessary calls — The "file already in context" message no longer suggests fresh=true, which misled weaker models into making a redundant third call. New text: "File content unchanged since last read (same hash). Already in your context window."
  • AGENTS.md Pi-header replaced on non-Pi agents — When a project had AGENTS.md from a prior lean-ctx init --agent pi but was later initialized for OpenCode or another agent, the Pi-specific header ("CLI-first Token Optimization for Pi") persisted. The generic lean-ctx block now replaces it automatically.
  • Doctor check count mismatch (16/15) — The daemon health check incremented passed but was not counted in effective_total, causing the summary to show e.g. "16/15 checks passed". Fixed by including the daemon check in the total (+5 instead of +4).
  • "INDEXING IN PROGRESS" no longer blocks read output — When the graph index is still building, the autonomy pre-hook returned the indexing notice as auto-context, which was prepended to the actual tool output. This is now suppressed — the file content is returned immediately while indexing continues in the background.

Improved

  • RAM usage reduced during compaction/checkpoint — Four targeted optimizations to prevent memory spikes reported during OpenCode session compaction:
    • Codebook uses borrows instead of clonesbuild_from_files now accepts &[(&str, &str)] instead of Vec<(String, String)>, eliminating a full duplication of all cached file contents (~2MB saved at 500k tokens).
    • Auto-checkpoint skips signature extraction — Periodic checkpoints now use include_signatures: false, avoiding expensive tree-sitter parsing. Explicit ctx_compress calls still extract signatures.
    • Compressed output variants capped at 3 per cache entry — Prevents unbounded growth of the compressed_outputs HashMap.
    • Codebook early-exit at >50,000 lines — Skips the codebook deduplication phase entirely for very large caches, preventing HashMap/HashSet memory explosions.

[3.5.10] — 2026-05-09

Added

  • 4-layer terse compression engine — Scientifically grounded compression pipeline replacing the legacy output_density / terse_agent settings with a unified CompressionLevel system (off / lite / standard / max):
    • Layer 1 — Deterministic Output Terse (engine.rs): Surprisal scoring, content/function-word filtering, filler-line removal, and a quality gate that preserves all paths and identifiers.
    • Layer 2 — Pattern-Aware Residual (residual.rs): Runs after pattern compression, applies terse on the remaining output with attribution split.
    • Layer 3 — Agent Output Shaping (agent_prompts.rs): Scale-aware brevity prompts injected into LLM instructions — telegraph-English-inspired format for max, dense atomic facts for standard, concise bullets for lite.
    • Layer 4 — MCP Description Terse (mcp_compress.rs): Compresses tool descriptions and lazy-load stubs for reduced schema overhead.
  • Unified CompressionLevel configuration — Single compression_level setting in config.toml replaces the legacy output_density and terse_agent options. Resolution order: LEAN_CTX_COMPRESSION env var → compression_level config → legacy fallback. CLI: lean-ctx compression <off|lite|standard|max> (alias: lean-ctx terse).
  • Quality gate for terse compression (quality.rs) — Ensures all file paths and code identifiers survive compression. If max level fails the quality check, automatically falls back to standard. Inputs shorter than 5 lines skip compression entirely.
  • Agent prompt injection across all IDEs (rules_inject.rs) — Compression prompts are automatically injected into 7 agent rules files (Cursor .cursorrules, ~/.cursor/rules/lean-ctx.mdc, Claude .claude/rules/lean-ctx.md, AGENTS.md, CRUSH, Qoder, Kiro). Injection runs from lean-ctx compression, lean-ctx setup, and on MCP server startup — ensuring retroactive consistency when users change settings.
  • Context Proof V2 (context_proof_v2.rs) — Proof-carrying context with claim extraction, quality levels Q0Q4, and structured verification output.
  • Claim extractor (claim_extractor.rs) — Decomposes session context into atomic verifiable claims for the proof system.
  • 29 new Lean4 formal proofs — Two new proof modules bringing the total to 82 machine-checked theorems with zero sorry:
    • TerseQuality.lean (12 theorems): Quality gate correctness, conjunction semantics, idempotence, empty-set triviality.
    • TerseEngine.lean (17 theorems): Compression level ordering, Max-to-Standard fallback correctness, structural marker preservation, filter-subset invariant, high-score line protection.
  • Terse evaluation harness (terse_eval.rs) — Integration test covering git diff, JSON API, Docker build, Cargo build, and Rust error outputs across all compression levels.
  • Domain-aware dictionaries (dictionaries.rs) — Whole-word replacement dictionaries for general programming terms, Git operations, and domain-specific abbreviations. Applied after quality gate to prevent identifier corruption.
  • Surprisal-based line scoring (scoring.rs) — Information-theoretic scoring using bigram surprisal to identify high-information-density lines for preservation.

Improved

  • Dashboard: shared utilities refactored — New shared.js library with common dashboard utilities, reducing code duplication across cockpit components.
  • Dashboard: cockpit components polished — Updated Context Explorer, Agent Sessions, Graph Visualizer, Knowledge Base, Memory Inspector, Compression Stats, and Overview with improved layouts, consistent styling, and better data presentation.
  • Setup flow consolidated — Premium feature configuration (compression, TDD) unified into a single interactive prompt flow. Shell alias refresh integrated.
  • Test suite robustnessterse_agent_tests.rs rewritten to explicitly control both LEAN_CTX_COMPRESSION and LEAN_CTX_TERSE_AGENT env vars, eliminating dependency on local config state. Mutex poison recovery added. 5 new tests for the CompressionLevel system alongside 6 fixed legacy backward-compat tests.
  • Intensive benchmarks updatedintensive_benchmarks.rs now benchmarks the new 4-layer terse pipeline instead of the removed protocol::compress_output.

Fixed

  • Token counter overflow (counter.rs) — savings_pct no longer panics when dictionary replacements expand text beyond the original token count.
  • Short input over-compression — Inputs shorter than 5 lines are now passed through unchanged, preventing the terse engine from removing single-line outputs like file reads.
  • Legacy pipeline cleanup — Removed deprecated compress_output, OutputDensity functions from protocol.rs. All compression now routes through the unified terse pipeline.

[3.5.9] — 2026-05-09

Fixed

  • Codex config corruption with tool approval entries (GitHub #191) — When Codex auto-adds per-tool approval entries ([mcp_servers.lean-ctx.tools.ctx_read], etc.) to config.toml, the parent [mcp_servers.lean-ctx] section could be missing (e.g. after a v3.5.6 upgrade removed it). upsert_codex_toml now detects orphaned [mcp_servers.lean-ctx.*] sub-tables and inserts the parent section before them instead of appending at the end, which Codex's TOML parser rejected with "invalid transport".
  • AGENTS.md reference uses absolute path — The lean-ctx block in ~/.codex/AGENTS.md now references `~/.codex/LEAN-CTX.md` instead of LEAN-CTX.md (same directory), preventing AI agents from misinterpreting the relative reference as the project working directory.

Security

  • fast-uri 3.1.0 → 3.1.2 (VSCode extension) — Fixes GHSA-v39h-62p7-jpjc (malformed fragment decoding) and GHSA-q3j6-qgpj-74h6 (URI parsing vulnerability).

Improved

  • Dashboard cockpit polish — Refined Context Explorer with improved layout, resizable panels, and better file tree navigation. Updated styling across all cockpit components for consistency. Improved graph visualization layout and memory inspector presentation.

[3.5.8] — 2026-05-08

Security

  • CodeQL #40 (High): XSS in dashboard searchcockpit-search.js fallback esc() function was function(s) { return String(s); } — no HTML escaping. Replaced with safe textContentinnerHTML implementation matching format.js.
  • CodeQL #38/#39 (Medium): Unpinned GitHub Actionscodecov/codecov-action@v4 and EmbarkStudios/cargo-deny-action@v2 are now pinned to commit SHAs (b9fd7d16…, 5bb39ff5…) in ci.yml.

Fixed

  • Codex config corruption on mode change (GitHub #189) — When lean-ctx setup or lean-ctx update ran with v3.5.6 (where Codex was CLI-Redirect mode), remove_codex_toml_section removed the [mcp_servers.lean-ctx] parent section but left orphaned sub-tables like [mcp_servers.lean-ctx.env], causing Codex to fail with "invalid transport in mcp_servers.lean-ctx".
    • remove_codex_toml_section now removes all TOML sub-tables via prefix matching when removing a parent section.
    • ensure_codex_mcp_server now detects orphaned sub-tables and inserts the parent section before them instead of appending at the end.
    • ensure_codex_mcp_server now uses toml_quote_value for Windows backslash-safe TOML quoting (was using raw format! with double quotes).

[3.5.7] — 2026-05-08

Security

  • BM25 index memory balloon fix (GitHub #188) — Oversized BM25 cache files (observed up to 50 GB in monorepos with vendor/generated code) could cause the daemon to allocate unbounded memory on startup, leading to system-wide swapping and OOM conditions. This release implements an 8-layer defense:
    1. Load-time size guardBM25Index::load() now checks file metadata before reading. Indexes exceeding the configurable limit (default 512 MB) are quarantined by renaming to .quarantined and skipped.
    2. Save-time size guardBM25Index::save() refuses to persist serialized data exceeding the limit, preventing bloated indexes from being written in the first place.
    3. Chunk count warning — Indexes with >50,000 chunks trigger a tracing::warn suggesting extra_ignore_patterns in config.toml.
    4. Default vendor/build ignores — 14 glob patterns (vendor/**, dist/**, build/**, .next/**, __pycache__/**, *.min.js, *.bundle.js, etc.) are now excluded from BM25 indexing by default.
    5. File count caplist_code_files() stops collecting after 5,000 files per project, preventing runaway indexing in massive repos.
    6. Configurable limit — New bm25_max_cache_mb setting in config.toml (default: 512). Override per-project or via LEAN_CTX_BM25_MAX_CACHE_MB env var.
    7. Project root markersave() writes a project_root.txt file alongside each index, enabling orphan detection when the original project directory is deleted.
    8. lean-ctx doctor BM25 health check — Doctor now scans all vector directories, warns about large indexes (>100 MB), and fails for oversized indexes. lean-ctx doctor --fix automatically prunes quarantined, oversized, and orphaned caches.

Fixed

  • Codex integration mode changed from CLI-Redirect to Hybrid — Codex exists in three variants (CLI, Desktop App, Cloud Agent) that share ~/.codex/config.toml. Only the CLI variant has reliable shell hooks; Desktop and Cloud require MCP. lean-ctx now treats Codex as Hybrid (MCP + CLI hooks where available) instead of CLI-Redirect, ensuring all three variants work correctly.
  • Codex hook installer now writes MCP server entrylean-ctx init --agent codex now ensures [mcp_servers.lean-ctx] exists in ~/.codex/config.toml. Previously, only CLI hooks and codex_hooks = true were written, leaving Desktop/Cloud variants without MCP access.
  • Codex LEAN-CTX.md upgrade detectioninstall_codex_instruction_docs() now compares file content instead of just checking for the string "lean-ctx". This ensures the instruction file is updated when the template changes (e.g., CLI-only → Hybrid mode), instead of being silently skipped on every subsequent install.
  • Dashboard HTTP parser handles large POST bodies — The dashboard TCP handler now reads complete HTTP messages using Content-Length header parsing instead of assuming the entire request fits in the first read. POST requests to API endpoints (e.g., knowledge CRUD, memory management) no longer fail silently when the body exceeds 8 KB. Maximum message size enforced at 2 MB.

Added

  • Cockpit dashboard (complete rewrite) — The localhost dashboard has been rebuilt from scratch as a modular single-page application:
    • 12 Web Components: Overview, Live Activity, Context Explorer, Knowledge Base, Graph Visualizer, Agent Sessions, Memory Inspector, Compression Stats, Health Monitor, Search, Remaining Token Budget, Navigation.
    • Modular Rust backend: Monolithic route handler (~1,200 lines) replaced with 10 focused route modules (routes/agents.rs, context.rs, graph.rs, knowledge.rs, memory.rs, stats.rs, system.rs, tools.rs, helpers.rs, mod.rs).
    • Shared JS libraries: api.js (fetch wrapper with token auth), charts.js (SVG charting), format.js (number/byte/duration formatting), router.js (hash-based SPA routing), shared.js (common utilities).
    • Full CSS redesign: 800+ lines of modern CSS with dark theme, responsive layout, data tables, card grids, and chart containers.
    • Legacy dashboard preserved at /legacy route for backwards compatibility.
  • lean-ctx cache prune command — New CLI command to scan ~/.lean-ctx/vectors/, remove quarantined (.quarantined) files, oversized indexes, and orphaned directories (project root no longer exists). Reports count and freed space.
  • lean-ctx doctor BM25 cache health check — Proactive diagnostics for BM25 index health, integrated into the standard doctor report. --fix auto-prunes.

Improved

  • Codex instruction docs now document Hybrid mode~/.codex/LEAN-CTX.md now includes both MCP tool table (ctx_read, ctx_shell, ctx_search, ctx_tree) and CLI fallback instructions, with guidance on when to use which path depending on the Codex variant.
  • Website: Codex moved to Hybrid in Context OS table — All 11 locale files and the ContextOsPage agent table updated. Codex now correctly appears under Hybrid mode instead of CLI-Redirect.
  • Website: Codex editor guide updated — DocsGuideEditorsPage now describes Codex as running in Hybrid mode across CLI, Desktop, and Cloud variants.

[3.5.6] — 2026-05-08

Fixed

  • Daemon auto-restart on setup and updatelean-ctx setup and lean-ctx update now automatically stop and restart the daemon with the current binary. Previously, a running daemon would be left untouched, causing stale-binary mismatches after updates. Both interactive and non-interactive (--yes) flows are covered.
  • Proactive stale daemon cleanupis_daemon_running() now removes orphaned PID and socket files when the referenced process is dead. This prevents connection attempts to stale Unix Domain Sockets after crashes or reboots.
  • UDS connection timeouts — All daemon socket connections now have a 3-second connect timeout and 10-second I/O timeout. Previously, connections to a stale or unresponsive socket could block indefinitely, cascading into system-wide hangs.
  • Daemon readiness wait reduced — The CLI auto-start readiness loop was reduced from 12 seconds to 3 seconds, keeping CLI commands responsive even when the daemon is slow to start.

Improved

  • Website navigation completeness — Added /docs/concepts/multi-agent to the Docs mega dropdown. Mobile navigation now includes all Context OS pages (Integrations, Shared Sessions, Context Bus, SDK) that were previously desktop-only.
  • Daemon documentation updated — Integrations pillar and Context OS overview pages now document auto-restart on update, stale-file cleanup, and connection timeouts across all 11 languages.

[3.5.5] — 2026-05-08

Fixed

  • Search command compression blocked by auth-flow false positiverg, grep, find, fd, ag, and ack outputs were silently skipped by the compression pipeline whenever the search results contained OAuth-related strings (device_code, user_code, verification_uri, etc.) anywhere in the matched source code. This caused 0% savings for any rg search over a codebase that implements or references OAuth device-code flows — even though the output was search results, not an actual auth prompt. The fix skips the contains_auth_flow guard for search commands in both the CLI (shell/compress.rs) and MCP (ctx_shell) paths. Real auth flows (e.g. az login, gh auth login) are still preserved verbatim for non-search commands. Reported by aguarella (Discord).
  • Central shorter_only guard for all shell patterns — Added a centralized length check in patterns/mod.rs that wraps every compressor (FilterEngine, try_specific_pattern, json_schema, log_dedup, test). No pattern can return Some(result) unless result is strictly shorter than the original output. Eliminates a class of bugs where patterns claimed compression without actually reducing size.
  • grep compressor removes verbatim threshold — Removed the <= 100 lines early return that passed small rg/grep outputs through uncompressed. All search outputs are now grouped by file with per-file match limits, regardless of size. Combined with the shorter_only guard, small outputs that can't be meaningfully compressed correctly return None instead of faking 0% savings.
  • gh CLI verbatim returns replaced with Nonegh pr diff, gh api, gh search, gh workflow, and unknown gh subcommands no longer return Some(output.to_string()) (which falsely claimed compression). They now return None, allowing fallback compressors or the caller to handle the output appropriately.
  • safeguard_ratio aligned with CLI behavior — The MCP compression guard now uses a 5% floor only for small outputs (<2,000 tokens) and allows aggressive compression for large outputs, matching the CLI pipeline behavior.
  • ctx_shell search command inflation guard — For search commands (rg, grep, etc.), the MCP handler now explicitly checks c.len() <= output.len() before using the compressed result, preventing any inflation from reaching the agent.
  • Codex AGENTS.md overwriteinstall_codex_instruction_docs now uses marked-block insertion (<!-- lean-ctx -->...<!-- /lean-ctx -->) instead of overwriting ~/.codex/AGENTS.md, preserving user instructions. Reported by Vitu (Discord).

Added

  • Knowledge CLI: export/import/remove — Full CLI parity with MCP ctx_knowledge:
    • lean-ctx knowledge export [--format json|jsonl|simple] [--output <path>]
    • lean-ctx knowledge import <path> [--merge replace|append|skip-existing] [--dry-run]
    • lean-ctx knowledge remove --category <cat> --key <key>
    • Core: import_facts() with merge strategies, export_simple() for interop, parse_import_data() with auto-format detection.
    • Context OS: knowledge import events tracked via KnowledgeRemembered bus event.
  • Context OS optimizations — Connection pooling for Context Bus R/W, broadcast channels replacing mutex-guarded Vec, inverted token index for BM25 search, LRU session eviction, metrics consolidation cleanup.

Fixed (cont.)

  • Dashboard scroll after fullscreenswitchView() now closes any active fullscreen before tab transitions, restoring scroll in all views. (GitHub #186)

[3.5.4] — 2026-05-07

Fixed

  • gh CLI compression safety — Unknown gh subcommands (gh pr diff, gh api, gh search, gh workflow, gh auth, gh secret, etc.) now pass through verbatim instead of being truncated to 10 lines. Previously, fallback compressors (JSON, log-dedup) could also strip content from gh api and gh search output. The fix returns Some(output) for unmatched commands (blocking fallback compression), matching the safe behavior already used by git and glab patterns.
  • Uninstall proxy cleanuplean-ctx uninstall now cleans up Claude Code (ANTHROPIC_BASE_URL in settings.json) and Codex CLI (OPENAI_BASE_URL in config.toml) proxy settings. Previously only shell exports (Gemini) were removed, leaving Claude/Codex pointing at the dead local proxy after uninstall. If a saved upstream exists, Claude Code settings are restored to the original URL.
  • CLI ls/grep daemon path resolutionlean-ctx ls . and lean-ctx grep <pattern> . now resolve relative paths to absolute before sending to the daemon, fixing incorrect directory listings when the daemon's CWD differs from the CLI's CWD.

Added

  • Context Bus v2: Multi-Agent Coordination — Major upgrade to the event bus with versioned events, causal lineage, consistency levels, and multi-agent conflict detection.
    • Event versioning: Every event now carries a monotonic version per (workspace, channel) and an optional parentId for causal chains.
    • Consistency levels: Events classified as local (informational), eventual (shared, async), or strong (requires sync) — enables agents to prioritize reactions.
    • K-bounded staleness guard: When a shared-mode agent falls behind by >10 events, tool responses include a [CONTEXT STALE] warning.
    • Knowledge conflict detection: Concurrent writes to the same knowledge key by different agents inject [CONFLICT] warnings before proceeding.
    • Enriched payloads: Event payloads now include path, category, key, and reasoning (from active session task) for richer observability.
    • SSE backfill on lag: When a broadcast subscriber falls behind, missed events are automatically backfilled from SQLite instead of dropped.
    • New REST endpoints: GET /v1/context/summary (materialized workspace view), GET /v1/events/search (FTS5 full-text search), GET /v1/events/lineage (causal chain traversal).
    • Team Server scopes expanded: ctx_session, ctx_knowledge, ctx_artifacts, ctx_proof, ctx_verify mapped to sessionMutations, knowledge, artifacts, search scopes.
    • Session race fix: SharedSessionStore::get_or_load uses atomic entry API to prevent TOCTOU races under concurrent agent loads.
  • Configurable proxy upstreams — Teams routing through custom API gateways can now set proxy.anthropic_upstream, proxy.openai_upstream, and proxy.gemini_upstream via lean-ctx config set or environment variables. Upstreams are resolved once at proxy startup (env > config > default).
  • Proxy upstream diagnosticslean-ctx doctor validates proxy upstream URLs (self-referential loop detection, URL format) and reports which upstreams are active.
  • 6 new adversarial compression testsgh pr diff, gh api, gh search, gh workflow verbatim passthrough, plus shell-hook-level diff preservation test.

Changed

  • Dry-run uninstalllean-ctx uninstall --dry-run now previews Claude Code and Codex proxy cleanup actions.

[3.5.3] — 2026-05-07

Fixed

  • Dashboard command counter — Shell commands in track-only mode (e.g. git status, docker ps) that use exec_inherit are now counted via exec_inherit_tracked(), and record_shell_command no longer skips zero-token commands. Previously many commands went unrecorded in the dashboard.
  • SLO false positivesCompressionRatio SLO now requires a minimum of 5,000 original tokens before evaluating, and the threshold was raised from 0.75 to 0.90. Eliminates constant "violated CompressionRatio" warnings caused by full mode reads.
  • X11 clipboard in vim — Removed explicit stripping of DISPLAY, XAUTHORITY, and WAYLAND_DISPLAY environment variables from exec_buffered, restoring X11 clipboard sync after exiting vim/vi in Claude Code.
  • pack_cmd unwrapLocalRegistry::open() now returns a graceful error instead of panicking on IO failures.
  • cursor.rs JSON type safetymerge_cursor_hooks now validates JSON types before unwrapping, preventing panics when hooks.json contains unexpected structures.

Added

  • Rules-staleness detection — On the first MCP tool call of a session, lean-ctx checks whether the agent's rules file contains the current version marker. If outdated, a [RULES OUTDATED] warning is injected into the tool response, prompting the agent to re-read rules or run lean-ctx setup.

Changed

  • Codebase maintainability — Split doctor.rs (2,348 lines) into doctor/{mod,integrations,fix}.rs and uninstall.rs (1,859 lines) into uninstall/{mod,agents,parsers}.rs for better modularity.
  • Cloud-server cleanup — Removed unused jwt_secret field from cloud-server config and auth state.

[3.5.2] — 2026-05-07

Fixed

  • Agent zombie cleanupcleanup_stale() now marks dead processes as Finished immediately regardless of age, fixing the "phantom agents" bug where terminated MCP sessions (e.g. from Claude Code subagents, /superpowers, /gsd plugins) stayed listed as "Active" in the Agent World dashboard indefinitely. Previously, agents were only cleaned up after 24 hours. Fixes the issue reported by daviddatu_.
  • Dashboard live-filterbuild_agents_json() now calls cleanup_stale() on every API request and additionally filters by is_process_alive() as a safety net, ensuring the Agent World dashboard never shows zombie entries.
  • CLI/MCP feature parity — new core::tool_lifecycle module ensures CLI commands (lean-ctx read, lean-ctx grep, lean-ctx ls, lean-ctx -c) trigger the same side effects as MCP tools: session tracking, Context Ledger updates, heatmap recording, intent detection, and knowledge consolidation. Previously CLI-only users lost ~60% of Context OS features.
  • Daemon double-recording bug — CLI reads routed through the daemon no longer record a second (sent, sent) stats entry with 0% savings, which was diluting the overall savings rate on the dashboard.
  • Search savings accuracyctx_search now estimates native grep baseline cost at 2.5× raw match tokens (accounting for context lines, separators, and full paths), up from 1× which showed misleadingly low savings.
  • Track-mode dilution — Shell commands in track-only mode (no compression) no longer record (0, 0) token entries that inflated command counts without contributing savings, improving the dashboard savings rate from ~30% to 86%+.
  • Crash-loop backoff guard — MCP server startup now detects rapid restart loops (>5 starts in 30s) and applies exponential backoff (up to 60s), preventing system hangs during binary updates.
  • Stats flush for short-lived CLI — explicit stats::flush() calls after CLI read, grep, ls, diff, deps commands ensure token savings from hook subprocesses are persisted to disk immediately.

Changed

  • Agent HookMode reclassification — CRUSH, Hermes, OpenCode, Pi, and Qoder moved from CliRedirect to Hybrid mode because their hook mechanisms cannot guarantee full interception of all tool types. Only Cursor, Codex CLI, and Gemini CLI remain in pure CLI-redirect mode.
  • Claude Code Hybrid mode — Claude Code now uses Hybrid mode (MCP + hooks) instead of CLI-redirect. lean-ctx init --agent claude installs the MCP server entry in ~/.claude.json and configures PreToolUse hooks for Bash compression. This ensures full functionality even in headless (-p) mode where PreToolUse hooks don't fire.
  • Antigravity dedicated hooklean-ctx init --agent antigravity now has its own installation function (no longer shares with Gemini CLI), correctly configuring MCP at ~/.gemini/antigravity/mcp_config.json and hook matchers for Antigravity's native tools (run_command, view_file, grep_search).

[3.5.1] — 2026-05-06

Fixed

  • Tool Registry not initializedctx_tree, ctx_discover_tools, and 23 other trait-based tools returned "Unknown tool" because the registry was never wired up at server startup. All 56 advertised tools are now dispatchable. Fixes #184.
  • Copilot CLI MCP pathlean-ctx init --agent copilot now creates .github/mcp.json with the correct "mcpServers" key (per GitHub Copilot CLI spec), in addition to .vscode/mcp.json with the VS Code "servers" key. Previously wrote to the wrong path (.github/copilot/mcp.json) with the wrong key format.
  • Agent-scoped project ruleslean-ctx init --agent copilot no longer creates .cursorrules or .claude/rules/ files. Project rules are now scoped to the requested agent(s).
  • SKILL.md for Copilot/VS Codelean-ctx setup now installs SKILL.md for GitHub Copilot / VS Code users, and lean-ctx doctor checks the correct path (~/.vscode/skills/lean-ctx/SKILL.md).

[3.5.0] — 2026-05-06

Added

  • Context OS Runtime — full integration of shared sessions, event bus, and SSE endpoints for real-time multi-agent collaboration. Agents can subscribe to context changes, broadcast events, and share session state across workspaces.
  • Daemon Mode — persistent background daemon with CLI-first dispatch. lean-ctx daemon start/stop/status manages the process. All CLI commands route through the daemon for sub-millisecond response times and shared state.
  • Context Package System — versioned, shareable context bundles with lean-ctx pack create/list/info/export/import/install/remove/auto-load. Package layers (knowledge, gotchas, config, graph) enable portable project intelligence.
  • Context Field Theory (CFT) — unified model for context management with Context Potential Function, Rich Context Ledger, Context Overlay System, Context Handles, and Context Compiler.
  • Provider Framework — pluggable provider system with GitLab integration and caching layer for external context sources.
  • Autonomy Drivers — configurable agent autonomy levels with intent routing and degradation policies.
  • Context IR — intermediate representation for context compilation, enabling cross-provider optimization.
  • Instruction Compilerlean-ctx instructions command compiles project-specific rules into optimized agent instructions.
  • Context Proof Systemlean-ctx proof generates verifiable context provenance chains for audit trails.
  • Team Server: Context OS scopesSessionMutations, Knowledge, and Audit scopes for fine-grained team permissions via lean-ctx team token create.
  • Qoder & QoderWork support — new editor integration for Qoder IDE. PR #180 by @zsefvlol.
  • 56 MCP tools — exposed all registered tools for installed agents, including new ctx_verify, ctx_proof, ctx_provider, ctx_artifacts, ctx_index tools. Fixes #176.
  • 38 Context OS integration tests — comprehensive test suite covering multi-client concurrency, event bus, shared sessions, and SSE endpoints.
  • Windows OpenCode guide — step-by-step manual for OpenCode on Windows 10. PR #181 by @HamedEmine.

Changed

  • CLI-First Architecture — all new modules (daemon, providers, instruction compiler, proof, overview, knowledge, compress, verify) are accessible as CLI subcommands, reducing MCP schema overhead.
  • Server Refactor — modular tool registry with ToolTrait, pipeline stages, and per-tool dispatch for cleaner extensibility.
  • A2A alignmentScratchpadEntry now aligns with A2AMessage types for cross-agent interoperability.
  • HTTP-MCP contract — extended with full Context OS API surface documentation.
  • Shell pattern library — expanded to 95+ output compression patterns including clang, fd, glab, just, ninja.
  • Property Graph — enhanced with metadata layer and reproducibility contract.

Fixed

  • CLI relative path resolution — paths are now resolved to absolute before sending to the daemon, preventing "file not found" errors when working directory differs.
  • install.sh POSIX compliance — rewritten as pure POSIX sh so curl | sh works on dash (Ubuntu/Debian default). PR #175 by @narthanaj.
  • Qoder MCP config — added LEAN_CTX_FULL_TOOLS to Qoder configuration for complete tool exposure. Includes clippy fixes.
  • Team SSE endpoint — removed dead code and properly wired audit_event into the SSE stream.

[3.4.7] — 2026-05-01

Added

  • ctx_call meta-tool — compatibility tool for MCP clients with static tool registries (e.g. Pi Coding Agent). Invoke any ctx_* tool by name via a stable schema without requiring dynamic tools/list refresh. Fixes #174.
  • Interactive Graph Explorerctx_graph action=export-html generates a self-contained, interactive HTML visualization with pan/zoom, node selection, transitive highlighting, and PNG export.
  • Self-Hosted Team Serverlean-ctx team serve enables shared context across workspaces with token-based auth, scoped permissions, rate limiting, and audit logging.

Changed

  • Dual-format hook outputlean-ctx hook rewrite/redirect now emits a combined JSON response compatible with both Cursor (permission/updated_input) and Claude Code (hookSpecificOutput). All IDEs that support PreToolUse hooks now work with the same command.
  • JetBrains config format~/.jb-mcp.json now uses the official mcpServers snippet format matching JetBrains AI Assistant documentation (was: nonstandard servers array).
  • Shell hook block markerslean-ctx init --global now writes stable # lean-ctx shell hook — begin/end markers, making updates idempotent and safe across reinstalls.

Fixed

  • Claude Code hooks not intercepting subagent callsextract_json_field in hook handlers was too rigid for pretty-printed or spaced JSON from Claude Code. Now robustly handles all formatting styles. Fixes Discord report.
  • Claude Code hooks overwriting other pluginsinstall_claude_hook_config now merges PreToolUse hooks instead of replacing the entire matcher group, preserving hooks from other plugins (e.g. obra/superpowers).
  • lean-ctx doctor false positive "pipe guard missing" — on Windows Git Bash with XDG config paths, doctor now correctly detects shell hooks in both ~/.lean-ctx/ and ~/.config/lean-ctx/ directories, with both forward and backslash path separators. Fixes Discord report.
  • Pi Coding Agent array parametersget_str_array now accepts JSON-encoded strings (e.g. "[\"a\",\"b\"]") in addition to native JSON arrays, fixing ctx_multi_read for the Pi MCP bridge. Fixes #173.
  • Windows CI test failureworkspace_config tests now use serde_json::json! for path serialization, preventing invalid JSON escapes on Windows.

[3.4.6] — 2026-04-30

Added

  • Unified call graph tool — new ctx_callgraph supports direction=callers|callees behind one stable entry point.
  • Graph diagram in unified graph APIctx_graph now supports action=diagram (with kind=deps|calls and optional depth).
  • Release-gate hardening tests — added golden/edge coverage for tokens.rs, preservation.rs, handoff_ledger.rs, and workflow store roundtrips.
  • README entry paths — new 3-tier onboarding/runtime paths (Quick, Power, Enterprise) with concrete commands and expected outcomes.
  • Knowledge graph auto-bootstrap — when the dashboard's knowledge graph is empty, lean-ctx now automatically generates initial facts (project root, languages, index stats) so users see data immediately.
  • Startup guard (cross-process lock) — new core::startup_guard module provides file-based locking with stale eviction, used to serialize concurrent startup and background maintenance.
  • Cookbook TypeScript SDK — real integration examples with typed SDK.

Changed

  • Deprecation aliases (no breaking change):
    • ctx_callers/ctx_callees now route to ctx_callgraph with deprecation hints.
    • ctx_graph_diagram now routes to ctx_graph action=diagram with deprecation hint.
    • ctx_wrapped now routes to ctx_gain action=wrapped with deprecation hint.
  • Tool metadata alignment — descriptors, editor auto-approve lists, and docs updated for the unified entry points and 49-tool manifest.
  • Documentation/version hygiene — README and VISION now consistently reference 49 MCP tools and current runtime state.
  • Legacy cleanup — removed unlinked core/watcher.rs orphan module (no runtime references).
  • Cloud: OAuth2 client credentials — cloud sync now supports OAuth2 token-based authentication.
  • Memory: configurable policies + knowledge relations — knowledge facts support temporal relations and configurable retention policies.

Fixed

  • SIGABRT under concurrent MCP startup — multiple agent sessions starting simultaneously could crash the process. Fixed with catch_unwind at the process entry point, a cross-process startup lock, and capped Tokio worker/blocking threads. Fixes #171.
  • Dashboard stale index auto-rebuildgraph_index and vector_index now detect when indexed files are missing and automatically rebuild, preventing empty Knowledge Graph and broken Compression Lab views.
  • Dashboard Compression Lab path healing — when a file path from the index no longer exists (e.g. after refactoring), the API now tries suffix/filename matching against indexed files and returns actionable candidates. The UI shows clickable suggestions instead of a bare error.
  • Background maintenance stampede — rules injection, hook refresh, and version checks are now guarded by a cross-process lock, preventing multiple instances from running expensive maintenance simultaneously during agent session initialization.
  • Panic hardening in verification/stats paths — replaced remaining production unwrap() usage in critical library paths:
    • core/output_verification.rs fallback regex paths
    • core/stats/mod.rs optional buffer extraction
  • CLI guidance consistencylean-ctx wrapped now clearly points users to the canonical lean-ctx gain --wrapped path.
  • Cookbook npm audit vulnerabilities — resolved all reported npm audit issues in the cookbook package.

[3.4.5] — 2026-04-28

Added

  • Agent Harness: Roles & Permissions — 5 built-in roles (coder, reviewer, debugger, ops, admin) with configurable tool policies and shell access. Custom roles via .lean-ctx/roles/*.toml with inheritance. Server-side middleware blocks unauthorized tools with clear feedback. ctx_session action=role to list/switch roles at runtime.
  • Agent Harness: Budget Tracking — per-session budget enforcement against role limits (context tokens, shell invocations, cost USD). Warning at 80%, blocking at 100%. ctx_session action=budget to check status. Budgets reset on role switch or session reset.
  • Agent Harness: Events — new EventKind variants: RoleChanged, PolicyViolation, BudgetWarning, BudgetExhausted. All rendered in TUI Observatory with appropriate icons and colors.
  • Agent Harness: Cost Attribution — real-time per-tool-call cost estimation using ModelPricing, recorded into the budget tracker for accurate USD tracking.
  • Agent Harness documentation — new docs page with full i18n (53 keys × 11 languages), accessible at /docs/agent-harness.
  • LEAN_CTX_DATA_DIR for cloud config — cloud client now respects the LEAN_CTX_DATA_DIR environment variable for its config directory. PR #168 by @glemsom.

Fixed

  • MCP server crash recovery — tool handler panics no longer kill the server (panic = "unwind" + catch_unwind). Server returns error message and stays alive for the next call. PR #167 by @DustinReynoldsPE.
  • lean-ctx setup ignoring config changes — running setup a second time no longer silently ignores the user's new choices for terse_agent and output_density. Values are now upserted instead of skipped when keys already exist in config.toml.
  • Dashboard cost mismatch with lean-ctx gain — dashboard computed cost savings with hardcoded pricing ($2.50/M input) while gain used dynamic model-specific rates. Dashboard now syncs pricing from the gain API for consistent numbers.
  • ctx_session tool description missing actionsrole and budget actions were implemented but not listed in the MCP tool descriptor, so LLMs couldn't discover them. Now documented in granular tool defs and templates.

Credits

  • @DustinReynoldsPE — MCP panic recovery (PR #167)
  • @glemsom — LEAN_CTX_DATA_DIR cloud support (PR #168)

[3.4.4] — 2026-04-28

Fixed

  • Observatory File Heatmap blank — the File Heatmap panel in lean-ctx watch stayed empty because historical per-file access data was never loaded on TUI startup. Now pre-populates from the persistent heatmap.json so file activity is visible immediately. Also fixed EventTail offset tracking to prevent event loss during concurrent writes. Fixes #166.
  • Windows agent hook installsdirs::home_dir() does not respect HOME/USERPROFILE overrides on Windows, causing hooks to install into incorrect directories during CI and in some user setups. Introduced a centralized core::home::resolve_home_dir() that checks HOME, USERPROFILE, and HOMEDRIVE+HOMEPATH before falling back to dirs::home_dir(). All 13 agent installers and the hook manager now use this resolver.
  • Windows claude mcp add-json invocation.cmd shims cannot be executed directly via CreateProcess; now routes through cmd /C for reliable invocation.
  • Clippy 1.95 compliance — resolved all new lints introduced by Rust 1.95: needless_raw_string_hashes, map_unwrap_or, unnecessary_trailing_comma, duration_suboptimal_units, while_let_loop across 30+ source files.
  • cargo-deny 0.19 migration — updated deny.toml to new schema, removed deprecated advisory fields, added missing dependency licenses (0BSD, CDLA-Permissive-2.0).
  • Windows benchmark stabilitybench_rrf_eviction_vs_legacy no longer panics from Instant underflow on short-lived processes.
  • Coverage timeoutbenchmark_task_conditioned_compression now skipped under tarpaulin instrumentation and uses smaller input to prevent CI timeouts.
  • Uninstall dry-runlean-ctx uninstall --dry-run no longer accidentally removes components.

Changed

  • License updated to Apache-2.0 — all references across the repository and website (11 languages) updated from MIT to Apache-2.0.
  • Clippy pedantic across entire codebase — comprehensive refactoring to satisfy clippy::pedantic with zero warnings: Copy derives, map_or/is_ok_and patterns, Duration::from_hours/from_mins, while let loops, and raw string simplification.
  • cfg(tarpaulin) declared in Cargo.toml — prevents unexpected_cfgs lint failures when coverage attributes are used.

[3.4.3] — 2026-04-27

Fixed

  • Pi Agent compression loop — agents using pi-lean-ctx could get stuck in a compression loop where bash output was too aggressively compressed, preventing the agent from extracting needed information. The bash tool now supports a raw=true parameter that bypasses compression entirely when exact output is critical. Fixes #159.
  • Hook handlers ignore LEAN_CTX_DISABLEDhandle_rewrite, handle_codex_pretooluse, handle_copilot, and handle_rewrite_inline now check LEAN_CTX_DISABLED env var and exit immediately when set. This prevents Claude Code subagents and rewind operations from being blocked by hooks. Fixes #162.
  • Telemetry claims in README/SECURITY.md — replaced inaccurate "Zero telemetry / Zero network requests" claims with honest documentation of what network activity exists (daily version check, opt-in anonymous stats). Fixes #160.

Added

  • Version check opt-out — new update_check_disabled = true config option and LEAN_CTX_NO_UPDATE_CHECK=1 env var to completely disable the daily version check against leanctx.com/version.txt.
  • Pi Agent raw parameterbash tool in pi-lean-ctx now accepts raw=true to skip compression, matching ctx_shell raw=true behavior in the MCP server.
  • is_disabled() guard — centralized helper in hook_handlers.rs for consistent LEAN_CTX_DISABLED checks across all hook entry points.
  • New integration testshook_rewrite_disabled_produces_no_output and codex_pretooluse_disabled_exits_cleanly verify the disabled guard behavior. run_hook_test helper explicitly removes inherited env vars to prevent test pollution.

Changed

  • Data sharing default flippedlean-ctx setup now asks [y/N] (opt-in) instead of [Y/n] (opt-out). Users must explicitly choose to enable anonymous stats sharing.
  • Pi Agent tool prompts overhauleddescription fields for all 5 Pi tools (bash, read, ls, find, grep) rewritten to provide clear guidance on which tool to use for which task, aligning with Pi Agent's architecture where description is the primary LLM guidance field. Redundant promptGuidelines removed from ls/find/grep.
  • Pi Agent explicit entry pointpi-lean-ctx now uses ./extensions/index.ts as explicit entry point instead of relying on default resolution. PR #158 by @riicodespretty.

Credits

  • @glemsom — Pi Agent prompt improvements (PR #157) and architectural insights on promptGuidelines behavior (PR #161)
  • @johnwhoyou — LEAN_CTX_DISABLED hook handler fix (PR #163)
  • @riicodespretty — explicit extension entry point (PR #158)
  • @pavelxdd — telemetry transparency request (Issue #160)

[3.4.2] — 2026-04-26

Fixed

  • Unicode SIGABRT in ctx_overview — directory path truncation used byte-index slicing (&dir[len-47..]) which panicked on multi-byte UTF-8 characters (Chinese, Japanese, Korean, emoji paths). Replaced with truncate_start_char_boundary() that respects char boundaries. Fixes #154.
  • Windows shell detection in Git Bash / MSYS2find_real_shell() now checks MSYSTEM/MINGW_PREFIX env vars before PSModulePath, preventing incorrect PowerShell detection when running inside Git Bash. Fixes #156.

Added

  • Shell hint in MCP instructions (Windows) — on Windows, instructions now include detected shell type with explicit guidance (e.g. "SHELL: bash (POSIX). Use POSIX commands, not PowerShell cmdlets"), helping LLMs generate correct commands for the active shell environment.
  • Shell mismatch hint in ctx_shell responses (Windows) — when a command fails and contains PowerShell cmdlets while the detected shell is POSIX, a correction hint is appended to the response.
  • shell_name() public API — returns the short shell basename (e.g. "bash", "powershell", "cmd") for use in instructions and hints.

[3.4.1] — 2026-04-25

Performance and token optimization release. Reduces per-session overhead by up to 64%.

Added

  • LEAN_CTX_NO_CHECKPOINT env var — disable auto-checkpoint injection independently from minimal_overhead
  • PreparedSave patternSession.save() split into prepare_save() (CPU-only serialization under lock) + write_to_disk() (background I/O via tokio::task::spawn_blocking), removing disk I/O from the tool response hot path
  • md5_hex_fast — 8x faster fingerprinting for outputs >16 KB by hashing prefix + suffix + length instead of full content
  • Benchmark tests — 8 new tests covering token overhead budgets, cache effectiveness, compression density, session save latency, and MD5 performance

Changed

  • count_tokens called once per tool response (was up to 4x) — cached result reused for hints, cost attribution, and logging
  • CostStore writes deferred to background thread via spawn_blocking
  • mcp-live.json writes debounced to every 5th tool call (80% fewer disk writes)
  • compress_output skipped entirely for Normal density (no string copy)
  • Auto-checkpoint, meta-strings (savings/stale notes, shell hints, archive hints), and session blocks now all suppressed under minimal_overhead

Fixed

  • Integer overflow crash in shell_efficiency_hint when output tokens exceeded input tokens — now uses saturating_sub
  • Synchronous save() restores retry counter on disk write failure, preserving auto-save semantics

[3.4.0] — 2026-04-25

Addresses GitHub issues #150, #151, #152, #153.

Changed (BREAKING)

  • Lazy tools now the default — Only 9 core tools are exposed by default instead of 46. This reduces per-turn input token overhead by ~80%. Use LEAN_CTX_FULL_TOOLS=1 to opt back in to all tools. The ctx_discover_tools tool lets agents discover and load additional tools on demand. (#153)

Added

  • JSONC comment supportlean-ctx setup and all editor config writers now parse JSON with // and /* */ comments using a built-in JSONC stripper. Config files with comments (e.g. opencode.json) are no longer treated as invalid and overwritten. (#151)
  • XDG Base Directory compliance — New installs use $XDG_CONFIG_HOME/lean-ctx (default ~/.config/lean-ctx/) instead of ~/.lean-ctx. Existing ~/.lean-ctx directories are detected and used automatically — no migration required. (#152)
  • minimal_overhead config option — Set minimal_overhead = true in config or LEAN_CTX_MINIMAL=1 env var to skip session/knowledge/gotcha blocks in MCP instructions, minimizing token overhead for cost-sensitive workflows. (#153)
  • Shell hook disable — New --no-shell-hook flag for lean-ctx init, shell_hook_disabled = true config option, and LEAN_CTX_NO_HOOK=1 env var to disable the _lc() shell wrapper across all shells (bash, zsh, fish, PowerShell). MCP tools remain fully active. (#150)

Fixed

  • Shell hook source lines now use the resolved data directory path instead of hardcoded ~/.lean-ctx, matching XDG-compliant installations.
  • upsert_source_line detection works for both legacy and XDG hook paths (including Windows backslash paths).

[3.3.9] — 2026-04-24

Security & Safety Hardening (GitHub Issue #149)

Comprehensive response to the TheDecipherist adversarial security review comparing lean-ctx vs RTK across 16 safety-critical scenarios. The review was conducted against v3.2.5 — many findings were already fixed in 3.3.x, and v3.3.9 addresses the remaining gaps.

Already Fixed (confirmed with adversarial tests since v3.3.x)

  • git diff code content: compress_diff_keep_hunks() preserves all +/- changed lines, only trims context to max 3 lines per hunk
  • df root filesystem: Verbatim passthrough — no compression applied to df output
  • pytest xfail/xpass: Summary explicitly includes xfailed, xpassed, skipped, and warnings counters
  • git status DETACHED HEAD: Passes through verbatim including "HEAD detached at" warning
  • ls shows .env: No file filtering — all files including .env are shown
  • pip list all packages: Full package list preserved — no truncation
  • git stash verbatim: Passes git stash output through unchanged
  • ruff file:line:col: Preserves all location references in linter output
  • find full paths: Preserves complete absolute paths
  • wc via pipe: Correctly reads stdin (piped input)
  • Log CRITICAL/FATAL severity: log_dedup and safety_needles explicitly recognize and preserve CRITICAL, FATAL, ALERT, EMERGENCY severity levels

Fixed in v3.3.9

  • git show diff content (CRITICAL): compress_show() now preserves full diff content using compress_diff_keep_hunks() instead of reducing to hash message +N/-M. Code review via git show is now safe.
  • docker ps health status (CRITICAL): Added fallback detection for (unhealthy), (healthy), (health: starting), and Exited(N) annotations that survive even when column-based parsing misaligns.
  • git log default cap (HIGH): Increased from 50 to 100 entries (was ~20 in v3.2.5). With explicit -n/--max-count, no limit is applied. Truncation message clearly indicates omitted count.

New Adversarial Tests

  • adversarial_git_show_preserves_diff_content — verifies code changes survive git show
  • adversarial_git_show_preserves_security_change — verifies security-relevant removals (e.g. CSRF) are visible
  • adversarial_docker_ps_unhealthy_narrow_columns — verifies health status survives tight column layouts
  • adversarial_docker_ps_exited_containers — verifies crashed containers are shown
  • adversarial_git_log_100_plus_commits — verifies 100-entry cap and truncation message
  • adversarial_git_log_explicit_limit_unlimited — verifies -n bypasses default cap
  • adversarial_safeguard_ratio_prevents_over_compression — verifies safety net prevents >85% compression
  • adversarial_shell_hook_preserves_errors_in_truncation — verifies CRITICAL/ERROR lines survive shell hook truncation

Dependency Security

  • rustls-webpki: Confirmed already on patched version 0.103.13 (GHSA-82j2-j2ch-gfr8, DoS via panic on malformed CRL BIT STRING)

[3.3.8] — 2026-04-24

Bug Fixes

  • Windows TOML path quoting (GitHub Issue #147): lean-ctx update and lean-ctx setup now write Windows paths in Codex config.toml using TOML single-quoted literal strings ('C:\...') instead of double-quoted strings. Double-quoted TOML strings treat backslashes as escape sequences, causing Codex to fail with "too few unicode value digits". Affects all Windows users with backslash paths in Codex MCP config.

Improvements

  • Leaner ls output (PR #148 by @glemsom): lean-ctx ls now runs plain ls instead of ls -la by default, reducing token overhead. The agent can add -la flags when needed.

[3.3.7] — 2026-04-23

New Features

  • lean-ctx ghost CLI: New command that reveals hidden token waste — shows unoptimized shell commands, redundant reads, and oversized contexts with monthly USD savings estimate. Supports --json for CI integration.
  • ctx_review MCP tool: Automated code review combining impact analysis (ctx_impact), caller tracking (ctx_callers), and test file discovery. Three actions: review (full analysis), diff-review (review changed files from git diff), checklist (structured review questions).
  • Content-Defined Chunking (Rabin-Karp): Opt-in rolling-hash chunking for ctx_read that creates stable chunk boundaries, improving LLM prompt cache hit rates across edits. Enable via content_defined_chunking = true in config.toml.
  • Claude Code Plugin Manifest: .claude-plugin/manifest.json added for future Claude Code plugin marketplace integration.

Improvements

  • Cache-Safety Doctor Check: lean-ctx doctor now verifies that cache_alignment and provider_cache modules are operational (12 checks total).
  • provider_cache module activated: Previously dormant cache provider module is now wired into the diagnostic pipeline.

[3.3.6] — 2026-04-23

Security Hardening

  • GitHub Actions pinned to SHA: All 10 Actions across CI, Release, and CodeQL workflows are now pinned to immutable commit SHAs instead of mutable version tags, preventing supply-chain attacks. (CodeQL #24-#36)
  • File system race condition fixed: TOCTOU vulnerability in VS Code extension's MCP config writer eliminated. (CodeQL #37)
  • CodeQL Python false positive resolved: Stale language:python scan configuration removed; explicit CodeQL workflow now covers only Rust, JavaScript/TypeScript, and Actions.
  • Email masking in CLI: lean-ctx login/register/forgot-password now mask email addresses in console output. (CodeQL #21-#23)

Bug Fixes

  • TypeScript .js import resolution (GitHub Issue #146): The graph builder now correctly resolves relative .js specifiers to .ts source files per the TypeScript module resolution spec. Covers .js→.ts/.tsx, .jsx→.tsx/.ts, .mjs→.mts, .cjs→.cts.
  • Graceful client disconnect: When an IDE cancels the MCP connection before initialization completes, lean-ctx now exits silently instead of printing a confusing expect initialized request error.
  • Session ID uniqueness: Session IDs now include an atomic counter suffix, preventing collisions when two sessions are created within the same millisecond.

Improvements

  • Environment variable forwarding (PR #144 by @glemsom): pi-lean-ctx now forwards the parent process environment to the lean-ctx subprocess, so config env vars (LEAN_CTX_TERSE_AGENT, LEAN_CTX_ALLOW_PATH, etc.) work correctly.

[3.3.5] — 2026-04-23

Multi-Project Workspace Support (GitHub Issue #141)

  • allow_paths in config.toml: New config field to explicitly allow additional paths in PathJail. Useful for mono-repos and multi-project workspaces where projects live outside the detected root.
  • Auto-detect multi-root workspaces: When the CWD has no project markers but contains 2+ child directories with markers (.git, Cargo.toml, package.json, etc.), lean-ctx auto-detects this as a workspace and allows all child projects via PathJail.
  • Improved error messages: PathJail errors now include a hint suggesting LEAN_CTX_ALLOW_PATH or allow_paths in config.toml.

Windows PowerShell Fixes (GitHub Issue #142)

  • Pipe-guard in profile snippet: The [Console]::IsOutputRedirected check is now embedded directly in the PowerShell profile source line, preventing errors when IDEs redirect stdout.
  • Binary path resolution: resolve_portable_binary() now takes only the first line of where output on Windows, and prefers .cmd/.exe variants to avoid corrupted path detection.

CLI Improvements

  • excluded_commands via CLI (PR #143 by @glemsom): lean-ctx config set excluded_commands "make,go build" now works.

CI Stability

  • Fixed flaky test: startup_prefers_workspace_scoped_session race condition resolved with timestamp separation.
  • Windows CI: Python-dependent sandbox tests now skip gracefully when Python is unavailable on the runner.

[3.3.4] — 2026-04-23

Heredoc Support (GitHub Issue #140)

  • Smart heredoc detection in ctx_shell: Heredocs are no longer blanket-rejected. Only heredoc + file redirect combinations (cat <<EOF > file.txt) are blocked. Legitimate uses like psql <<EOF, git commit -m "$(cat <<'EOF'...)", and input piping are now allowed through.
  • Hook passthrough for heredoc commands: The PreToolUse hook (Claude Code, Codex, Copilot) no longer wraps heredoc-containing commands in lean-ctx -c '...'. Heredocs cannot survive the quoting round-trip (newlines get escaped to \\n), so they are passed through to the shell directly.

Headless MCP Mode

  • New LEAN_CTX_HEADLESS=1 environment variable: When set, the MCP server skips all auto-setup during initialize() — no rules injection, no hook updates, no version check, no agent registry writes. Session management and all MCP tools remain fully functional. Designed for users who manage their own configuration (e.g. custom launchers with --append-system-prompt).

Cloud Auth Hardening

  • Login and Register are now separate commands: lean-ctx login only calls /api/auth/login. lean-ctx register only calls /api/auth/register. The previous behavior auto-fell back to registration on any non-specific login error (network, 500, DNS), which caused users to unknowingly create duplicate accounts.
  • Clear error messages: Specific guidance for wrong password, unverified email, non-existent account, and server errors.

Interactive Setup with Premium Features

  • Setup wizard extended to 7 steps: New "Premium Features" step offers configuration of Terse Agent Mode (off/lite/full/ultra), Tool Result Archive (on/off), and Output Density (normal/terse/ultra) during lean-ctx setup.

Dependency Updates

  • Dependabot #12 resolved: rand 0.8.5 phantom dependency removed via cargo update (GHSA-cq8v-f236-94qc).
  • Updated: tokio 1.52.1, rustls 0.23.39, rmcp 1.5.0, uuid 1.23.1, and 20+ other transitive dependencies.

Premium Features — Tool Result Archive, Terse Agent Mode, Compaction Survival

Tool Result Archive + ctx_expand (Zero-Loss Compression)

  • Archive-on-disk: Large tool outputs (>4096 chars) are automatically archived to ~/.lean-ctx/archives/ before density compression. The compressed response includes an [Archived: ... Retrieve: ctx_expand(id="...")] hint so the agent can retrieve the full original output at any time.
  • New MCP tool ctx_expand: Retrieve archived tool output by ID. Supports full retrieval, line-range retrieval (start_line/end_line), pattern search (search), and listing all archives (action="list").
  • Session-scoped archives: Each archive entry is tagged with the session ID, enabling per-session listing and cleanup.
  • TTL-based cleanup: Archives older than max_age_hours (default 48h) are automatically cleaned up. Configurable via archive.max_age_hours in config.toml or LEAN_CTX_ARCHIVE_TTL env var.
  • Idempotent storage: Content-hash-based IDs ensure the same output is never stored twice.
  • Config: archive.enabled, archive.threshold_chars, archive.max_age_hours, archive.max_disk_mb in config.toml. Env overrides: LEAN_CTX_ARCHIVE, LEAN_CTX_ARCHIVE_THRESHOLD, LEAN_CTX_ARCHIVE_TTL.

Bidirectional Token Optimization (Terse Agent Mode)

  • New terse_agent config: Controls agent output verbosity via instructions injection. Levels: off (default), lite (concise, bullet points), full (max density, diff-only), ultra (expert pair-programmer, minimal narration).
  • Smart CRP interaction: Terse lite/full are skipped when CRP mode is tdd (already maximally dense). ultra always applies as an additional layer.
  • CLI toggle: lean-ctx terse <off|lite|full|ultra> for instant switching.
  • Per-project override: terse_agent = "full" in .lean-ctx.toml.
  • Env override: LEAN_CTX_TERSE_AGENT=full.

Compaction Survival (Session-Resilience)

  • build_resume_block(): Generates a compact (~500 token) session resume containing task, decisions, modified files, next steps, archive references, and stats.
  • Automatic injection: The resume block is injected into MCP instructions whenever an active session with tool calls exists, ensuring context survives agent compaction events.
  • New ctx_session(action="resume") action: Explicit retrieval of the resume block for agents that need on-demand session state.

Bug Fixes

ctx_expand not registered in MCP tool listing

  • Fixed: ctx_expand was implemented (dispatch handler, archive storage, tool definition in list_all_tool_defs()) but was missing from granular_tool_defs() — the function that the MCP server actually uses to build the tools/list response. Agents could never discover or call ctx_expand despite the feature being fully coded. Now registered as tool #47.

TerseAgent::effective() ignores environment variable

  • Fixed: TerseAgent::effective() was supposed to let LEAN_CTX_TERSE_AGENT override the config.toml value, but fell through to the config value when the env var was set to "off". Rewritten to explicitly check the env var first, then fall back to config.

CLI dispatch sync — terse, register, forgot-password not wired in main.rs

  • Fixed: lean-ctx terse, lean-ctx register, and lean-ctx forgot-password were implemented in cli/dispatch.rs but the primary dispatch in main.rs was missing the match arms. All three commands now work from the CLI.
  • New: lean-ctx forgot-password <email> — sends a password reset email via the LeanCTX Cloud API. Previously referenced in help text but not implemented.
  • Help text: Updated in both main.rs and cli/dispatch.rs to consistently list terse, register, and forgot-password.

lean-ctx doctor ignores LEAN_CTX_DATA_DIR (Discord: GlemSom)

  • Fixed: doctor now uses lean_ctx_data_dir() instead of hardcoded ~/.lean-ctx at all 4 locations: shell-hook checks, Docker env.sh path, data directory check, and compact_score(). Users with custom LEAN_CTX_DATA_DIR will now see correct paths in doctor output.

Windows "path escapes project root" (GitHub Issue #139)

  • Fixed: pathjail.rs now uses safe_canonicalize_or_self() (which strips the \\?\ verbatim prefix) instead of raw std::fs::canonicalize(). This resolves the mismatch where Windows canonicalized paths (\\?\C:\Users\...) didn't match normal paths (C:/Users/...), causing false "path escapes project root" errors on Windows with Codex.
  • Windows path normalization hardened: is_under_prefix_windows now strips \\?\ prefix before comparison, and allow_paths_from_env uses the safe canonicalization consistently.

Shell Quoting Hardening

Bug fixes — Argument preservation for complex shell commands

  • Direct argv execution in -t mode: Shell aliases (_lc gh, _lc find, etc.) now bypass the argv-to-string-to-argv round-trip entirely when multiple arguments are present. exec_argv() calls Command::new().args() directly, preserving em-dashes (), # signs, nested quotes, and all other special characters exactly as the user's shell parsed them. Single-string commands still use sh -c for backward compatibility.
  • Single-quote wrapping for hook rewrites: wrap_single_command in hook handlers now uses POSIX single-quote escaping ('...' with '\'' for embedded single quotes) instead of double-quote escaping. This protects $, backticks, !, and " from unintended expansion when commands are passed through Claude Code, Codex, or Copilot hooks.
  • gh added to full passthrough: All gh CLI commands (not just gh auth) are now excluded from compression and tracking. The GitHub CLI's output is typically short, and its complex argument patterns (multi-word --comment values, issue references with #) are prone to quoting issues.

Code quality

  • 20+ new unit tests covering: exec_direct / exec_argv direct execution, quote_posix edge cases (em-dash, $, backtick, nested quotes), wrap_single_command special characters ($HOME, backticks, find with long exclude lists, !), and gh full passthrough verification.
  • All integration tests updated for new single-quote format.

[3.3.3] — 2026-04-28

Session Stability + Dashboard Clarity

Bug fixes — Session root handling (PR #138)

  • Stale session root across checkouts: Fixed issue where switching between project directories could load a session from a different workspace. New load_latest_for_project_root() scans all session files and returns the most recent session matching the target project root, using canonicalized path comparison.
  • Session normalization extracted: normalize_loaded_session() now handles empty-string cleanup and stale project root healing in a single place, called from both load_by_id() and load_latest_for_project_root().
  • Startup context detection: New detect_startup_context() derives the correct project root and shell working directory at MCP server startup, even when the IDE provides only a subdirectory path (e.g. repo/src).
  • Trusted re-rooting: resolve_path() now checks startup_project_root before allowing session re-rooting from absolute paths. Only paths matching the trusted startup root can trigger a re-root, preventing accidental session takeover by untrusted paths.
  • Helper functions: Added session_matches_project_root(), has_project_marker(), and is_agent_or_temp_dir() to session.rs for robust session matching and stale-root detection.

Improvements — Dashboard and metrics clarity

  • 0%-savings tools hidden from lean-ctx gain: Write-only tools like ctx_edit that don't compress output are no longer shown in the "Top Commands" section, preventing confusing "0% savings" entries.
  • 0%-savings tools hidden from ctx_metrics: The MCP ctx_metrics tool now filters out tools with zero token activity from the "By Tool" breakdown.

Code quality

  • Fixed all clippy warnings: resolved MutexGuard held across await points in tests, vec! macro used where array literal suffices, and Default::default() struct update with all fields specified.
  • All 1295 tests pass with zero warnings, zero clippy errors, full parallel execution.

Closed issues

  • #137 (stale session root across checkouts): Fixed by PR #138.

[3.3.2] — 2026-04-22

Codex Hook Fix + Docker Knowledge Collision Prevention

Bug fixes — Codex CLI integration (PR #136)

  • Codex PreToolUse hook: Added dedicated handle_codex_pretooluse() handler that uses block-and-reroute pattern (exit code 2) instead of the incompatible updatedInput field. Commands matched by lean-ctx compression rules are blocked with an actionable re-run suggestion.
  • Codex SessionStart hook: New handle_codex_session_start() injects a short instruction telling Codex to prefer lean-ctx -c "<command>" for shell commands.
  • Refactored rewrite logic: Extracted rewrite_candidate() from handle_rewrite() to share rewrite detection across Claude Code, Codex, Copilot, and inline-rewrite handlers. Eliminates duplicated skip/wrap/compound logic.
  • New hooks/support.rs module: Shared helpers for hook installation — install_named_json_server, upsert_lean_ctx_codex_hook_entries, ensure_codex_hooks_enabled. Reduces code duplication across agent integrations.
  • Hook dispatch updated: lean-ctx hook codex-pretooluse and lean-ctx hook codex-session-start subcommands added to both main.rs and dispatch.rs.
  • Doctor integration: doctor --fix now sets LEAN_CTX_QUIET=1 when running in JSON mode to suppress noisy setup output.

Bug fixes — Knowledge hash collisions in Docker environments

  • New project_hash.rs module: Composite project hashing that combines the project root path with a detected project identity marker. Prevents knowledge collisions when different projects share the same Docker mount path (e.g. /workspace).
  • 8 identity detection sources (checked in priority order):
    1. .git/config → remote "origin" URL (normalized: lowercase, stripped .git suffix, SSH→path conversion)
    2. Cargo.toml[package] name
    3. package.json"name" field
    4. pyproject.toml[project] name or [tool.poetry] name
    5. go.modmodule path
    6. composer.json"name" field
    7. settings.gradle / settings.gradle.ktsrootProject.name
    8. *.sln → solution filename
  • Backward compatible: When no identity marker is found, hash falls back to path-only (identical to pre-3.3.2 behavior). Existing projects without git/manifest files see zero change.
  • Auto-migration: On load(), if the new composite hash directory doesn't exist but the old path-only hash does, knowledge files are automatically copied to the new location. Ownership verification prevents one project from claiming another's data.
  • Consolidated hashing: Removed duplicate hash_project() from gotcha_tracker.rs — now uses shared project_hash::hash_project_root().
  • 20 new tests: Collision avoidance, identity detection for all 8 ecosystems, git URL normalization, migration file copying, ownership verification (accept/reject), backward compatibility, empty directory handling.

Closed issues

  • #125 (feat: more cmdline compression): Closed — all requested patterns (bun, deno, vite) already implemented in v3.3.0+ and expanded further in v3.3.1.
  • #135 (bug: Codex PreToolUse hook uses unsupported updatedInput): Fixed by PR #136.

[3.3.1] — 2026-04-18

Shell Hook Hardening: Complete Developer Environment Coverage

Addresses user-reported issues where npm run dev hangs and shell compression is too aggressive for human-readable output. Massively expands passthrough command coverage across all developer ecosystems.

Bug fixes

  • npm run dev no longer hangs: Script runner commands (npm run dev, yarn start, pnpm serve, bun run watch, etc.) are now recognized as long-running processes and bypass compression entirely. Previously, exec_buffered would wait forever for the dev server to exit.
  • npm run compression less aggressive: compress_run now shows up to 15 lines verbatim (was 5) and keeps the last 10 lines of longer output (was 3).
  • Case-sensitive passthrough patterns fixed: Patterns like bootRun, -S, -A, -B now correctly match after case normalization in is_excluded_command.

Shell passthrough expansion (~85 new entries)

  • Package manager script runners: npm run dev/start/serve/watch/preview/storybook, npm start, npx, pnpm run dev/start/serve/watch, pnpm dev/start/preview, yarn dev/start/serve/watch/preview/storybook, bun run dev/start/serve/watch/preview, bun start, deno task dev/start/serve, deno run --watch
  • Python: flask run, uvicorn, gunicorn, hypercorn, daphne, django-admin runserver, manage.py runserver, python -m http.server, streamlit run, gradio, celery worker/beat, dramatiq, rq worker, ptw, pytest-watch
  • Ruby/Rails: rails server/s, puma, unicorn, thin start, foreman start, overmind start, guard, sidekiq, resque
  • PHP/Laravel: php artisan serve/queue:work/queue:listen/horizon/tinker, php -S, sail up
  • Java/JVM: gradlew bootRun/run, gradle bootRun, mvn spring-boot:run, mvn quarkus:dev, sbt run/~compile, lein run/repl
  • Go: go run, air, gin, realize start, reflex, gowatch
  • .NET: dotnet run, dotnet watch, dotnet ef
  • Elixir: mix phx.server, iex -S mix
  • Swift: swift run, swift package, vapor serve
  • Zig: zig build run
  • Rust: cargo run, cargo leptos watch, bacon
  • Task runners: make dev/serve/watch/run/start, just dev/serve/watch/start/run, task dev/serve/watch, nix develop, devenv up
  • CI/CD: docker compose watch, skaffold dev, tilt up, garden dev, telepresence, act
  • Networking/monitoring: mtr, nmap, iperf/iperf3, ss -l, netstat -l, lsof -i, socat
  • Load testing: ab, wrk, hey, vegeta, k6 run, artillery run

Smart script-runner detection

  • New heuristic: any npm run/pnpm run/yarn/bun run/deno task command where the script name contains dev, start, serve, watch, preview, storybook, hot, live, or hmr is automatically treated as passthrough. Catches variants like npm run dev:ssr, yarn start:production, pnpm run serve:local, bun run watch:css.

New adversarial tests (12 tests)

  • npm install package name/count preservation
  • npm install explicit package names (express, lodash, axios)
  • cargo build error codes (E0308, E0599) with file:line
  • eslint rule IDs and error counts
  • go build file:line error locations
  • docker build step failure errors
  • tsc type error codes (TS2304, TS2339) with file references
  • dotnet build CS0246 errors and build result
  • composer install package counts
  • cargo test failure counts
  • kubectl get pods CrashLoopBackOff/Error status
  • terraform plan destructive action preservation

New passthrough tests (15 test functions)

Organized by ecosystem: npm, pnpm, yarn, bun/deno, Python, Ruby, PHP, Java, Go, .NET, Elixir, Swift/Zig, Rust, task runners, CI/CD, networking, load testing, smart detection, false-positive guard.

Website

  • Fixed i18n validation: removed duplicate docsGettingStarted.evalInit* keys from 10 locale files that caused GitLab CI pipeline failure.

[3.3.0] — 2026-04-21

Adversarial Safety Hardening

This release addresses all 7 confirmed DANGEROUS compression findings from the TheDecipherist/rtk-test adversarial test suite (April 2026). LeanCTX now passes 16/16 comparative safety tests (up from 9/16 in v3.2.5).

CRITICAL fixes

  • git diff code content preserved: Compression no longer reduces diffs to file +N/-M. All +/- lines (actual code changes) are preserved. Only index headers and excess context lines (>3 per hunk) are trimmed. Large diffs (>500 lines) show first 200 + last 50 lines per file. Security-relevant changes (CSRF bypasses, credential removals) are always visible.
  • docker ps health status preserved: Refactored to header-based column parsing. (unhealthy), Exited (1), and multi-word statuses are always preserved verbatim. Container names and images included in output.
  • df verbatim passthrough: Disk usage output is no longer compressed at all. Root filesystem info (/dev/sda1 ... /) can never be hidden by "last N lines" heuristics. Output is typically small (<30 lines), making compression unnecessary.
  • npm audit CVE IDs preserved: Vulnerability details including CVE IDs, severity levels, package names, and fix recommendations are retained (up to 30 detail lines) alongside the summary counts.

HIGH fixes

  • git log truncation increased to 50: Default truncation raised from 20 to 50 entries. User-specified --max-count / -n arguments are now respected (no truncation applied). Truncation message updated to suggest --max-count=N.
  • pytest xfail/xpass/warnings: Summary now includes xfailed, xpassed, and warnings counters. Example: pytest: 15 passed, 1 failed, 2 xfailed, 1 xpassed, 2 warnings (3.5s).
  • grep/rg verbatim up to 100 lines: Outputs with ≤100 lines pass through unchanged. File grouping and context stripping only applies to larger outputs.
  • pip uninstall package names listed: Shows all successfully uninstalled package names (up to 30) instead of just a count.
  • docker logs safety-needle scan: Middle-section truncation now scans for critical keywords (FATAL, ERROR, CRITICAL, panic, OOMKilled, etc.) and preserves up to 20 safety-relevant lines.

Additional hardening

  • git blame verbatim up to 100 lines: Small blame outputs pass through unchanged. Larger outputs summarize by author with line ranges.
  • curl JSON sensitive key redaction: Keys matching token, password, secret, auth, credential, api_key, etc. have their values replaced with REDACTED in schema output.
  • ruff check file:line:col preserved: Outputs with ≤30 issues pass through verbatim, preserving all file:line:col references. Larger outputs show first 20 references plus rule summary.
  • log_dedup regex fix: Fixed a greedy regex ([^\]]*[^\]\s]*) in timestamp stripping that consumed entire log messages, preventing proper deduplication. Added CRITICAL to severity detection.
  • lightweight_cleanup brace collapse: Now only activates for outputs >200 lines with runs of >5 consecutive brace-only lines. Inserts [N brace-only lines collapsed] marker.
  • Safeguard ratio: If pattern compression removes >95% of content (on outputs >100 tokens), the original output is returned with a warning to prevent over-compression.

New: Safety Needles Module

New safety_needles.rs module provides centralized safety-critical keyword detection used across all compression paths. Keywords include: CRITICAL, FATAL, panic, FAILED, unhealthy, Exited, OOMKilled, CVE-, denied, unauthorized, error, WARNING, segfault, SIGSEGV, SIGKILL, out of memory, stack overflow, permission denied, certificate, expired, corrupt.

The truncate_with_safety_scan function in shell.rs ensures these keywords are preserved even during generic middle-section truncation (up to 20 safety-relevant lines kept).

New: lean-ctx safety-levels

New command that displays a transparency table showing exactly how each command type is compressed:

  • VERBATIM (7 commands): df, git status, git stash, ls, find, wc, env — zero compression
  • MINIMAL (11 commands): git diff, git log, docker ps, grep, ruff, npm audit, pytest, etc. — light formatting, all safety-critical data preserved
  • STANDARD (8 commands): cargo build, npm install, eslint, tsc, etc. — structured compression
  • AGGRESSIVE (4 commands): kubectl describe, aws, terraform, docker images — heavy compression for verbose output

Also lists global safety features (needle scan, safeguard ratio, auth detection, min token threshold).

New: lean-ctx bypass "command"

Runs any command with zero compression — guaranteed raw passthrough. Sets LEAN_CTX_RAW=1 internally. Use when you need absolute certainty that output is unmodified:

lean-ctx bypass "git diff HEAD~1"   # guaranteed unmodified
lean-ctx -c "git diff HEAD~1"      # compressed (hunk-preserving)

New: lean-ctx init <shell> (eval pattern)

Shell hook initialization now supports the industry-standard eval pattern used by starship, zoxide, atuin, fnm, and fzf. The shell code is always generated by the currently-installed binary, ensuring it's never stale after upgrades:

# bash: add to ~/.bashrc
eval "$(lean-ctx init bash)"

# zsh: add to ~/.zshrc
eval "$(lean-ctx init zsh)"

# fish: add to ~/.config/fish/config.fish
lean-ctx init fish | source

# powershell: add to $PROFILE
lean-ctx init powershell | Invoke-Expression

The existing file-based method (lean-ctx init --global) continues to work unchanged.

New: Adversarial Test Suite in CI

21 dedicated adversarial + regression tests now run on every push/PR via a new adversarial job in GitHub Actions CI. Tests cover all 16 comparative scenarios from the external audit plus additional safety regression checks. This ensures compression safety is continuously verified.

Changed

  • compression_safety.rs: New module with structured CommandSafety table and SafetyLevel enum
  • shell_init.rs: Refactored hook generation into generate_hook_posix(), generate_hook_fish(), generate_hook_powershell() for reuse by both file-based and eval-based init
  • ci.yml: New adversarial job running cargo test --test adversarial_compression

[3.2.9] — 2026-04-20

Fixed

  • UTF-8 text corrupted on Windows PowerShell (#131): lean-ctx -c with non-ASCII output (Russian, Japanese, Chinese, Arabic, etc.) produced mojibake because String::from_utf8_lossy misinterpreted Windows system codepage bytes as UTF-8. Introduced decode_output() that tries UTF-8 first, then falls back to Win32 MultiByteToWideChar for proper codepage-to-Unicode conversion. On PowerShell, additionally injects [Console]::OutputEncoding = UTF8 and sets SetConsoleOutputCP(65001). Fixed across shell hook, MCP server execute, and sandbox runners.
  • MCP ctx_shell commands hang on stdin (#132, credit: @xsploit): Child processes spawned by the MCP server inherited the JSON-RPC stdin pipe, causing commands like git to block instead of receiving EOF. Fixed by setting stdin(Stdio::null()) on all MCP child processes. Added GIT_TERMINAL_PROMPT=0 and GIT_PAGER=cat to prevent interactive prompts.

Added

  • MCP command timeout: Shell commands executed via ctx_shell now have a configurable timeout (default 120s). Override with LEAN_CTX_SHELL_TIMEOUT_MS env var. Timed-out commands return exit code 124 with a clear error message.
  • Regression tests: Added execute_command_closes_stdin and git_version_returns_when_git_is_available tests to prevent future stdin inheritance regressions.

[3.2.8] — 2026-04-20

Fixed

  • Codex config.toml parse error (empty [] section header): Uninstall left orphaned [mcp_servers.lean-ctx.tools.*] sub-sections when removing the main [mcp_servers.lean-ctx] section, producing an invalid empty [] header on re-setup. Uninstall now removes all mcp_servers.lean-ctx.* sub-sections, and the writer defensively skips [] lines.
  • Gemini CLI MCP server not loading (wrong config path): Setup wrote to ~/.gemini/settings/mcp.json but Gemini CLI reads MCP servers from ~/.gemini/settings.json under the mcpServers key. The MCP config was never loaded by Gemini CLI. Fixed with a new GeminiSettings writer that merges mcpServers into the existing settings.json without overwriting other keys (e.g. hooks).
  • Gemini CLI autoApprove not recognized: Gemini CLI uses "trust": true for auto-approval, not autoApprove. Fixed to use the correct field.
  • Codex codex_hooks=false after reinstall: Uninstall set codex_hooks = false but setup didn't reset it to true, leaving hooks disabled.

Added

  • Autonomous intent inference: ctx_read automatically infers a StructuredIntent from file access patterns (after 2+ files touched) without requiring explicit agent calls. ctx_preload auto-sets intent from task description when none is active or confidence is low.
  • Auto agent registration: MCP initialize handler automatically registers the connecting agent in the AgentRegistry based on client name (Cursor/Claude → coder, Antigravity/Gemini → explorer, etc.). Override via LEAN_CTX_AGENT_ROLE env var.
  • Context Layer dashboard tab: New "Context Layer" tab in the localhost dashboard with Pipeline Stats, Context Window pressure, Mode Distribution, and Context Ledger table. Backed by new API endpoints /api/pipeline-stats, /api/context-ledger, /api/intent.
  • Pipeline & Ledger persistence: PipelineStats and ContextLedger now persist to disk (pipeline_stats.json, context_ledger.json) so dashboard data survives server restarts.
  • Codex/Cursor hooks in setup: lean-ctx setup now explicitly installs Codex hook scripts and Cursor hooks as a dedicated step, ensuring hooks are present even on first setup.

Changed

  • IDE config audit: All 13 supported IDE configurations verified against official vendor documentation (Cursor, Claude Code, Codex, Windsurf, VS Code/Copilot, Gemini CLI, Antigravity, Amazon Q, Hermes, Cline, Roo Code, Amp, Kiro).

[3.2.6] — 2026-04-19

Fixed

  • Project root stuck at agent sandbox path (#124): The MCP session could retain a stale project root from a temporary directory (e.g. ~/.claude, /tmp/). Fixed with multi-layer healing: initialize now validates roots against project markers, session::load_by_id detects and corrects agent/temp roots, and resolve_path can auto-update a suspicious root when given an absolute project path. Agents like Codex that start in sandbox directories now correctly resolve the actual project.
  • lean-ctx gain showing 0% for Shell Hooks (#126): Small savings percentages were rounded to 0% in the "Savings by Source" and "Live Observatory" sections. Introduced format_pct_1dp for one-decimal-place display, <0.1% for very small values, and n/a when no input data exists.
  • install.sh fails on WSL2/Ubuntu (set: Illegal option -o pipefail): curl -fsSL leanctx.com/install.sh | sh failed because install.sh used Bashisms but was executed by POSIX sh (dash). Added a POSIX-compliant preamble that auto-detects and re-executes under bash, with a clear error message if bash is unavailable. Both | sh and | bash now work.
  • Dashboard "Live Observatory" showing 0 tokens saved: The Live Observatory pulled data exclusively from the active MCP session, ignoring shell hook savings. Now falls back to today's aggregate daily stats when no MCP session is active.

Added

  • rules_scope configuration: Control where agent rule files are installed — "global" (home directory only), "project" (repo-local only), or "both" (default). Avoids duplicate rule files that waste context tokens. Configurable via config.toml, LEAN_CTX_RULES_SCOPE env var, lean-ctx config set rules_scope, or per-project .lean-ctx.toml override.
  • Codex/Claude path jail auto-allowlist: When running inside Codex CLI (CODEX_CLI_SESSION set), ~/.codex is automatically added to allowed paths. Similarly, ~/.claude is auto-allowed for Claude Code sessions. No manual LCTX_ALLOW_PATH needed.
  • bunx and vp/vite-plus CLI compression (#125): Shell hook now routes bunx commands through the bun compressor and vp/vite-plus through the Next.js build compressor.
  • lean-ctx update auto-refreshes setup: Running lean-ctx update now automatically re-runs the full setup (shell hooks, MCP configs, rules) after updating, even when already on the latest version. Ensures all wiring stays current.
  • Website docs: rules_scope documented on configuration page in all 11 languages.

[3.2.5] — 2026-04-18

Fixed

  • Critical: shell hook recursion causing 100% CPU/memory — The .zshenv / .bashenv shell hooks introduced in v3.2.4 were missing the LEAN_CTX_ACTIVE recursion guard. When an AI agent (Claude Code, Codex, etc.) ran a command, lean-ctx -c spawned a new shell that re-triggered the hook infinitely, causing a fork bomb. Fixed by checking LEAN_CTX_ACTIVE before intercepting and adding a double-guard in exec(). Users must run lean-ctx setup after updating to refresh the hooks.

[3.2.4] — 2026-04-18

Fixed

  • Git stash compression too aggressive (#114): git stash list with ≤5 entries is now preserved verbatim. git stash show -p correctly routes to the diff compressor instead of the stash compressor. Added safeguard_ratio to ctx_shell to prevent over-compression (minimum 15% of original output preserved).
  • Windows Bash hook path stripping (#113): On Windows with Git Bash / MSYS2, the lean-ctx binary path had slashes stripped (E:packageslean-ctx.exe instead of /e/packages/lean-ctx.exe). resolve_binary() now applies to_bash_compatible_path on all platforms.
  • Windows UNC path breakage (\\?\ prefix): std::fs::canonicalize() on Windows adds extended-length path prefixes that break tools and string comparisons. New core::pathutil module provides safe_canonicalize() and strip_verbatim() used consistently across graph indexing, session state, path jailing, architecture tool, and hook handlers.
  • Dashboard showing empty graphs: detect_project_root_for_dashboard() was using the MCP session's temp sandbox directory instead of the actual project. Now validates project roots against .git and project markers before using them; falls through to shell_cwd when project_root is invalid. Added --project= CLI flag and LEAN_CTX_DASHBOARD_PROJECT env var for explicit override.
  • Dashboard Call Graph/Route Map empty states: Enriched /api/call-graph and /api/routes responses with metadata (indexed file count, symbol count, route candidates) so the UI shows actionable guidance instead of generic "nothing found" messages.
  • Codex uninstall incomplete (#116): lean-ctx uninstall now correctly removes the [mcp_servers.lean-ctx] section from Codex's TOML config, removes ~/.codex/hooks.json, and resets the codex_hooks feature flag.
  • Repo-local config missing fields (#98): merge_local() now supports auto_consolidate, dedup_threshold, consolidate_every_calls, consolidate_cooldown_secs, and bidirectional silent_preload override from .lean-ctx.toml.

Added

  • Hermes Agent support (#112): Full integration for Hermes Agent (Nous Research). lean-ctx init --agent hermes --global configures MCP via YAML (~/.hermes/config.yaml), creates HERMES.md rules. Setup auto-detects ~/.hermes/, doctor checks Hermes config, uninstall cleans up YAML + rules.
  • Kotlin graph analysis (#96): ctx_graph, ctx_callers, and ctx_callees now produce meaningful results for Kotlin projects. Tree-sitter-backed import extraction, call-site analysis, type-definition extraction, and Java interop with stdlib filtering.
  • Repo-local configuration (#98): .lean-ctx.toml in project root for per-project overrides. Supports extra_ignore_patterns (graph/overview exclusions), autonomy settings, and all config fields. lean-ctx cache reset --project clears only current project's cache.
  • Post-update MCP refresh: lean-ctx update now verifies and refreshes MCP configurations for all detected editors after binary replacement.
  • Dashboard "Savings by Source": Live Observatory and lean-ctx gain now show a breakdown of MCP Tools vs. Shell Hooks with individual compression rates and proportional bars.
  • Pi MCP bridge resilience: Host-cancelled tool calls are handled cleanly with abort signal forwarding and error normalization. Hung MCP calls timeout after 120s with automatic reconnect and retry for read-safe tools. Bridge status includes diagnostics (last error, hung tool, retry state).

Community

  • Merged PR #111 — fix Windows graph path compatibility (@Chokitus)
  • Merged PR #115 — handle host-cancelled MCP tool calls in Pi bridge (@frpboy)
  • Merged PR #118 — improve dashboard empty-state UX for Route Map and Call Graph (@frpboy)
  • Merged PR #122 — timeout and retry hung MCP tool calls in Pi bridge (@frpboy)

[3.2.3] — 2026-04-17

Fixed

  • Claude Code project rules missing (cowwoc): lean-ctx init --agent claude-code now creates .claude/rules/lean-ctx.md in the project root (project-local rules), in addition to the existing global ~/.claude/rules/lean-ctx.md. Claude Code reads both locations.
  • --help missing commands (#109): watch (live TUI dashboard) and cache (file cache management) were implemented but not listed in lean-ctx --help.
  • install.sh fails without Rust (#108): curl -fsSL https://leanctx.com/install.sh | sh now auto-detects missing cargo and downloads a pre-built binary instead of failing. Users with Rust still get a source build by default.

[3.2.2] — 2026-04-17

Added

  • Smart Shell Mode: New -t / --track subcommand for human shell usage — full output preserved, only stats recorded. Shell aliases (_lc) now default to track mode instead of compress mode, eliminating unwanted output compression for interactive users.
  • lean-ctx-mode shell function: Switch between track (default), compress, and off modes without editing config files. Available in both POSIX (bash/zsh) and Fish shells.
  • _lc_compress shell function: Explicit compression wrapper for power users who want compressed output in their terminal.
  • Unified Rewrite Registry (rewrite_registry.rs): Single source of truth for all 24+ rewritable commands, used consistently across shell aliases, hook rewrite, and compound command lexer.
  • Compound Command Lexer (compound_lexer.rs): Intelligent splitting of &&, ;, || compound commands for selective rewriting — only rewritable segments get wrapped with -c.
  • Extended hook support: Copilot hooks now recognize runInTerminal, run_in_terminal, shell, and terminal tool names in addition to Bash/bash.
  • Dashboard API routes: New /api/symbols, /api/call-graph, /api/routes, /api/search endpoints for the web dashboard.
  • 22 IDE/agent targets: Rules injection now supports Crush, Verdent, Pi Coding Agent, AWS Kiro, Antigravity, Qwen Code, Trae, Amazon Q Developer, and JetBrains IDEs (22 total).

Fixed

  • Shell commands compressed for humans (#101): ls, git status, and other aliased commands were always compressed because _lc used -c. Now defaults to -t (track) which preserves full output.
  • "Authorization required" on Ubuntu (#101): exec_buffered pipe redirection triggered X11/Wayland auth errors on headless Linux. Track mode uses exec_inherit_tracked (direct stdio), avoiding this entirely.
  • Token counting accuracy: stats::record now uses count_tokens() (tiktoken) instead of byte length for output measurement.
  • Dashboard Windows path normalization: Compression Lab demo paths now correctly handle Windows absolute paths (merged PR #102).
  • Dashboard "d streak" label: Fixed to display "days streak" (merged PR #106).

Community

  • Merged PR #102 — fix compression lab path resolution (@frpboy)
  • Merged PR #103 — add symbols API route (@frpboy)
  • Merged PR #104 — add call graph API route (@frpboy)
  • Merged PR #106 — fix dashboard streak label (@frpboy)

[3.2.1] — 2026-04-17

Fixed

  • crates.io publish: Claude Agent Skill assets (SKILL.md, install.sh) are now packaged inside the Rust crate so cargo publish verification succeeds.
  • Release CI: Build aarch64-unknown-linux-musl via cargo-zigbuild for reliable ARM64 musl cross-compilation (fixes glibc symbol leaks from gcc-aarch64-linux-gnu).

[3.2.0] — 2026-04-17

Breaking

  • License changed from MIT to Apache-2.0. All code from this release onwards is Apache-2.0. See LICENSE and NOTICE.

Added

  • Context Engine + HTTP server mode: lean-ctx serve exposes all 48 MCP tools via REST endpoints with rate limiting, timeouts, and graceful shutdown — enables embedding lean-ctx as a library.
  • Memory Runtime (autopilot): Adaptive forgetting, salience tagging, consolidation engine, prospective memory triggers, and dual-process retrieval router — all token-budgeted and zero-config.
  • Reciprocal Rank Fusion (RRF) cache eviction: Replaces the Boltzmann-weighted eviction scoring. RRF handles signal incomparability (recency vs frequency vs size) without tuned weights (K=60).
  • Claude Code 2048-char truncation fix: Auto-detects Claude Code and delivers ultra-compact instructions (<2048 chars). Full instructions installed as ~/.claude/rules/lean-ctx.md.
  • Claude Agent Skills auto-install: lean-ctx init --agent claude installs SKILL.md + scripts/install.sh under ~/.claude/skills/lean-ctx/.
  • ARM64 Linux support: aarch64-unknown-linux-musl binary in release pipeline. Docker instructions updated for Graviton/ARM64.
  • IDE extensions: JetBrains (Kotlin/Gradle), Neovim (Lua), Sublime Text (Python), Emacs (Elisp) — all thin-client architecture.
  • Security layer: PathJail (FD-based, single choke point for 42 tools), bounded shell capture, size caps, TOCTOU prevention in ctx_edit, symlink leak fix in ctx_search, prompt-injection fencing.
  • Unified Gain Engine: GainScore (0100), ModelPricing (embedded cost table), TaskClassifier (13 categories), ctx_gain MCP tool, TUI/Dashboard/CLI integration.
  • Docker/Claude Code MCP self-healing: env.sh re-injects MCP config when Claude overwrites ~/.claude.json. Doctor detects and hints fix.
  • Compression deep optimization: Thompson Sampling bandits for adaptive thresholds, Tree-sitter AST pruning, IDF-weighted deduplication, Information-Bottleneck task filtering, Verbatim Compaction.
  • lean-ctx -c now compresses on TTY (fixes #100): Previously skipped compression when stdout was a terminal, showing 0% savings.
  • Quality column in ctx_benchmark: Shows per-strategy preservation score (AST + identifier + line preservation).

Fixed

  • CLI -c TTY bypass (#100): lean-ctx -c 'git status' now compresses even in terminal (sets LEAN_CTX_COMPRESS=1).
  • Windows Instant overflow: RRF eviction test used now - Duration which underflows on Windows. Fixed with sleep-based offsets + checked_duration_since.
  • rustls-webpki CVE: Updated from 0.103.11 to 0.103.12 (wildcard/URI certificate name constraint fix).
  • MCP server hangs on large projects: Parallelized tool calls prevent blocking.
  • Dashboard ERR_EMPTY_RESPONSE in Docker: Bind host + panic recovery → HTTP 500 JSON instead of empty response.
  • Kotlin graph analysis: AST-span-based symbol ranges for accurate call-graph edges.

Refactored

  • Dead code elimination: Removed 598 lines (unused eval.rs, CEP benchmark, dead CLI helpers). Reduced #[allow(dead_code)] from 32 to 5.
  • Cache store zero-copy: Replaced CacheEntry clone with lightweight StoreResult struct (no content duplication).
  • Entropy dedup: Precomputed n-gram sets with size-ratio filter (exact Jaccard, no allocation storms).
  • Clippy clean: 0 warnings with -D warnings across all targets (1029 tests passing).

Community

  • Merged PR #94 (responsive dashboard — @frpboy)
  • Merged PR #95 (MCP performance — @frpboy)
  • Merged PR #97 (Kotlin graph support — @Chokitus)

[3.1.5] — 2026-04-15

Fixed

  • claude_config_json_path() simplified: Removed over-complex parent() fallback logic that guessed at .claude.json locations. Now directly uses $CLAUDE_CONFIG_DIR/.claude.json as documented by Claude Code.
  • lean-ctx init --agent claude now prints config path: Previously gave zero feedback about where MCP config was written. Now shows ✓ Claude Code: MCP config created at /path/to/.claude.json — immediately reveals path mismatches (e.g. Docker USER mismatch writing to /root/.claude.json instead of /home/node/.claude.json).
  • refresh_installed_hooks() hardcoded ~/.claude/: Hook detection in hooks.rs ignored $CLAUDE_CONFIG_DIR, always checking ~/.claude/hooks/ and ~/.claude/settings.json. Now uses claude_config_dir().
  • Rules injection hardcoded ~/.claude/CLAUDE.md: rules_inject.rs always wrote to ~/.claude/CLAUDE.md regardless of $CLAUDE_CONFIG_DIR. Now uses claude_config_dir().
  • Uninstall hardcoded ~/.claude/: remove_rules_files() and remove_hook_files() couldn't find Claude Code files when $CLAUDE_CONFIG_DIR was set. Now uses claude_config_dir().
  • Doctor display hardcoded ~/.claude.json: lean-ctx doctor always showed ~/.claude.json even when $CLAUDE_CONFIG_DIR pointed elsewhere. Now shows the actual resolved path.

[3.1.4] — 2026-04-15

Added

  • CLAUDE_CONFIG_DIR support: lean-ctx init --agent claude, lean-ctx doctor, lean-ctx uninstall, hook installation, and all Claude Code detection paths now respect the $CLAUDE_CONFIG_DIR environment variable. Previously hardcoded to ~/.claude.json and ~/.claude/.
  • CLAUDE_ENV_FILE Docker hint: lean-ctx init --global and lean-ctx doctor now recommend setting ENV CLAUDE_ENV_FILE alongside ENV BASH_ENV in Docker containers. Claude Code sources CLAUDE_ENV_FILE before every command — this is the officially recommended shell environment mechanism.
  • Doctor check for CLAUDE_ENV_FILE: In Docker environments, lean-ctx doctor now shows separate checks for both BASH_ENV and CLAUDE_ENV_FILE.

Fixed

  • Claude Code _lc not found in Docker (#89): Root cause was that BASH_ENV alone doesn't work for Claude Code — it uses CLAUDE_ENV_FILE to source shell hooks before each command. Recommended Dockerfile now includes ENV CLAUDE_ENV_FILE="/root/.lean-ctx/env.sh".
  • CLAUDE_CONFIG_DIR ignored everywhere: setup.rs, rules_inject.rs, doctor.rs, hooks.rs, uninstall.rs, and report.rs all hardcoded ~/.claude.json / ~/.claude/. Now all paths go through claude_config_json_path() / claude_config_dir() which check $CLAUDE_CONFIG_DIR first.

[3.1.3] — 2026-04-15

Docker & Container Support

  • Auto-detect Docker/container environments via /.dockerenv, /proc/1/cgroup, and /proc/self/mountinfo
  • Write ~/.lean-ctx/env.sh during lean-ctx init --global — a standalone shell hook file without the non-interactive guard ([ -z "$PS1" ] && return) that most ~/.bashrc files have
  • Docker BASH_ENV warning: when Docker is detected and BASH_ENV is not set, lean-ctx init now prints the exact Dockerfile line needed: ENV BASH_ENV="/root/.lean-ctx/env.sh"
  • lean-ctx setup auto-fallback: detects non-interactive terminals (no TTY on stdin) and automatically runs in --non-interactive --yes mode instead of hanging
  • lean-ctx doctor Docker check: new diagnostic that warns when running in a container with bash but without BASH_ENV set

Critical Fix

  • BASH_ENV="/root/.bashrc" never worked in Docker — Ubuntu/Debian .bashrc has [ -z "$PS1" ] && return which skips the entire file in non-interactive shells. The new env.sh approach bypasses this completely.

[3.1.2] — 2026-04-14

Fix Agent Search Loops in Large Projects

Fixed

  • Agents looping endlessly on search in large/monorepo projects — root cause was a triple failure: over-aggressive compression hid search results from the agent (only 5 matches/file, 80-char truncation, then generic_compress cut to 6 lines), loop detection only caught exact-duplicate calls (threshold 12 was far too high), and no cross-tool or pattern-similarity tracking existed. Agents alternating between ctx_search, ctx_shell rg, and ctx_semantic_search with slight query variations were never detected as looping.

Improved

  • Smarter loop detection — thresholds lowered from 3/8/12 to 2/4/6 (warn/reduce/block). Added cross-tool search-group tracking: any 10+ search calls within 300s triggers block regardless of tool or arguments. Added pattern-similarity detection: searching for "compress", "compression", "compress_output" etc. now counts as the same semantic loop via alpha-root extraction.
  • Configurable loop thresholds — new [loop_detection] section in config.toml with normal_threshold, reduced_threshold, blocked_threshold, window_secs, and search_group_limit fields.
  • Better search result fidelity — grep compression now shows 10 matches per file (was 5) with 160-char line truncation (was 80), preserving full function signatures. generic_compress scales with output size (shows ~1/3 of lines, max 30) instead of a fixed 6-line truncation.
  • Search commands bypass generic compression — grep, rg, find, fd, ag, and ack output is no longer crushed by generic_compress. Pattern-specific compression is applied when available, otherwise results are returned uncompressed.
  • Actionable loop-detected messages — blocked messages now guide agents to use ctx_tree for orientation, narrow with path parameter, and use ctx_read mode='map' instead of generic "change your approach" text.
  • Monorepo scope hints — when ctx_search results span more than 3 top-level directories, a hint is appended suggesting the agent use the path parameter to scope to a specific service.

[3.1.1] — 2026-04-14

Windows Shell Hook Fix + Security

Fixed

  • PowerShell npm/pnpm/yarn broken on Windows — the foreach loop in the PowerShell hook resolved npm to its full application path (C:\Program Files\nodejs\npm.cmd). When this path contained spaces, POSIX-style quoting caused PowerShell to output a string literal instead of executing the command. Now uses bare command names, consistent with git/cargo/etc. (fixes #38)
  • PowerShell _lc off-by-one$args[1..($args.Length)] produced an extra $null element. Replaced with & @args splatting which correctly handles all argument counts.
  • Password shown in cleartext during lean-ctx login — interactive password prompt now uses rpassword to disable terminal echo, so passwords are never visible.

Improved

  • Shell-aware command quotingshell_join moved from main.rs to shell.rs with runtime shell detection. Three quoting strategies: PowerShell (& 'path' with '' escaping), cmd.exe ("path" with \" escaping), and POSIX ('path' with '\'' escaping). Previously used compile-time cfg!(windows) which was untestable and broke Git Bash on Windows.
  • 11 new unit tests for join_command_for covering all three shell quoting strategies with paths containing spaces, special characters, and empty arguments.

Dependencies

  • Added rpassword 7.4.0 for secure password input.

[3.1.0] — 2026-04-14

LeanCTX Cloud — Web Dashboard & Full Data Sync

Added — Cloud Dashboard

  • Web Observatory — full-featured cloud dashboard at leanctx.com/dashboard mirroring the local Observatory. Includes Overview, Daily Stats, Commands, Performance (CEP), Knowledge, Gotchas, Adaptive Models, Buddy, and Settings views.
  • Login & Registration — email/password authentication with email verification, password reset via magic link, and dedicated login/register pages.
  • SPA Navigation — client-side routing with history.pushState for each dashboard view with dedicated URLs (/dashboard/stats, /dashboard/knowledge, etc.).
  • Timeframe Filters — 7d/30d/90d/All time filters on Overview and Stats pages with live chart updates.
  • Knowledge Table — searchable, filterable knowledge entries with category badges, confidence stars, and proper table layout with horizontal scroll on mobile.

Added — Complete Data Sync

  • Buddy Sync — full BuddyState (ASCII art, animation frames, RPG stats, rarity, mood, speech) synced as JSON to the cloud and rendered with live animation on the dashboard.
  • Feedback Thresholds Sync — learned compression thresholds per language synced to the cloud via new /api/sync/feedback endpoint and displayed on the Performance page.
  • Gotchas Sync — both universal and per-project gotchas (~/.lean-ctx/knowledge/*/gotchas.json) are merged and synced.
  • CEP Cache Metrics — daily cache_hits and cache_misses derived from CEP session data for accurate historical stats (previously hardcoded to 0).
  • Command Stats — per-command token savings with source type (MCP/Hook) breakdown.

Added — Cloud Server

  • REST API — Axum-based API server with endpoints for stats, commands, CEP scores, knowledge, gotchas, buddy state, feedback thresholds, and adaptive models.
  • PostgreSQL Schema — tables for users, api_keys, email_verifications, password_resets, stats_daily, knowledge_entries, command_stats, cep_scores, gotchas, buddy_state, feedback_thresholds.
  • Email Verification — SHA256-token-based email verification flow with configurable SMTP.
  • Password Reset — secure token-based password reset with expiry.

Improved

  • Cost Model alignment — cloud dashboard now uses the same computeCost() formula as the local dashboard (input $2.50/M + estimated output $10/M with 450→120 tokens/call reduction), replacing the previous input-only calculation.
  • Adaptive Models explanation — expanded Models page with "What Adaptive Models Do For You" (before/after comparison), "How Models Are Built" (4-step flow), and "Compression Modes" reference table.
  • Daily Stats accuracy — hit rate and cache data now correctly display from CEP-enriched daily stats.
  • Dashboard icons — all SVG icons render with correct dimensions via explicit CSS utility classes.
  • Stats bar color — Original tokens bar changed to blue for better visibility against the green Saved bar.

Removed

  • Teams & Leaderboard — removed team creation, invites, and leaderboard features in favor of utility-focused dashboard.
  • File Watcher — removed unused watcher.rs module.

Security

  • rand crate — updated to >= 0.9.3 to fix unsoundness with custom loggers (GHSA low severity).

Fixed

  • Token count test threshold — updated bench_system_instructions_token_count thresholds to accommodate cloud server feature additions.

[3.0.3] — 2026-04-12

Dashboard Reliability + Automatic Background Indexing

Added

  • Background indexing orchestrator — automatically builds and refreshes dependency graph, BM25 index, call graph, and route map in the background once a project root is known.
  • Dashboard status endpointGET /api/status exposes per-index build states (idle|building|ready|failed) for progress display and troubleshooting.
  • Routes cache — dashboard route map results are cached per project to avoid repeated scans.

Improved

  • Dashboard APIs are non-blocking — graph/search/call-graph/routes endpoints return a building status instead of hanging while indexes are being built.
  • Dashboard UI — views show “Indexing…” + auto-retry with backoff instead of confusing empty states or timeouts.
  • Auto-build on real usage — MCP server triggers background builds when the project root is detected from ctx_read and also from ctx_shell (via effective working directory), without requiring manual reindex commands.

CI

  • AUR release hardening — AUR job runs only when AUR_SSH_KEY is present, verifies SSH access up front, and fails loudly on auth issues.
  • Homebrew verification — formula update step asserts the expected version + SHA are written before pushing.

Kiro IDE Support

  • Kiro steering filelean-ctx init --agent kiro and lean-ctx setup now create .kiro/steering/lean-ctx.md alongside the MCP config, ensuring Kiro uses lean-ctx tools instead of native equivalents.
  • Project-level detectioninstall_project_rules() automatically creates the steering file when a .kiro/ directory exists.

Fixed

  • lean-ctx doctor showed 9/10 instead of 10/10 — session state check was displayed but never counted towards the pass total.
  • Dashboard browser error on Linux — suppressed Chromium stderr noise (sharing_service.cc) when opening dashboard via xdg-open.

[3.0.2] — 2026-04-12

Added — New MCP Tools

  • Symbol & outline navigation
    • ctx_symbol — read a specific symbol by name (code span only)
    • ctx_outline — compact file outline (symbols + signatures)
  • Call graph navigation
    • ctx_callers — find callers of a symbol
    • ctx_callees — list callees of a symbol
  • API surface extraction
    • ctx_routes — extract HTTP routes/endpoints across common frameworks
  • Visualization
    • ctx_graph_diagram — Mermaid diagram for dependency graph / call graph
  • Memory hygiene
    • ctx_compress_memory — compress large memory/config markdown while preserving code fences/URLs
  • Search modes: bm25, dense, hybrid (default)
  • Filters: languages + path_glob to scope results
  • Automation: auto-refreshes stale BM25 indexes; incremental embedding index updates
  • Performance: process-level embedding engine cache (no repeated model load)

Fixed

  • Route extraction: Spring-style Java methods with generic return types are now detected correctly.
  • Graph diagrams: depth is now respected when filtering edges for ctx_graph_diagram.

[3.0.1] — 2026-04-10

LeanCTX Observatory — Real-Time Data Visualization Dashboard

Added — Observatory Dashboard (lean-ctx dashboard)

  • Event Bus — New EventKind-based event system with ring buffer (1000 events) and JSONL persistence (~/.lean-ctx/events.jsonl) with auto-rotation at 10,000 lines. Captures ToolCall, CacheHit, Compression, AgentAction, KnowledgeUpdate, and ThresholdShift events in real time.
  • Live Observatory — Real-time event feed showing all tool calls, cache hits, compression operations, agent actions, and knowledge updates with token savings, mode tags, and file paths. Filter by category (Reads, Shell, Search, Cache).
  • Knowledge Graph — Interactive D3 force-directed graph visualizing project knowledge facts. Nodes sized by confidence, colored by category (Architecture, Testing, Debugging, etc.). Click nodes for detail panel showing temporal validity, confirmation count, and source session.
  • Dependency Map — Force-directed visualization of file dependencies extracted via tree-sitter. Nodes sized by token count, colored by language, with edges representing import relationships. Smart edge resolution for module-style imports (api::Server → file path).
  • Compression Lab — Side-by-side comparison of all compression modes (map, signatures, aggressive, entropy) for any file. Shows original content, compressed output, token savings percentage, and line reduction.
  • Agent World — Multi-agent monitoring panel showing active agents, pending messages, shared contexts, agent types, roles, and last active times.
  • Bug Memory (Gotcha Tracker) — Visual dashboard for auto-detected error patterns with severity, category, trigger/resolution, confidence scores, occurrence counts, and prevention statistics.
  • Search Explorer — BM25 search index visualization with language distribution chart, top chunks by token count, and symbol-level detail.
  • Learning Curves — Adaptive compression threshold visualization showing per-language entropy/Jaccard thresholds and compression outcome scatter plots (compression ratio vs. task success).

Added — Terminal TUI (lean-ctx watch)

  • ratatui-based Terminal UI — Live event feed, file heatmap, token savings, and session stats in the terminal. Reads from events.jsonl with tail-based polling.

Added — Event Instrumentation

  • ctx_read, ctx_shell, ctx_search, ctx_tree and all tools now emit ToolCall events with token counts, mode, duration, and path.
  • Cache hits emit CacheHit events with saved token counts.
  • entropy_compress_adaptive() emits Compression events with before/after line counts and strategy.
  • AgentRegistry.register() emits AgentAction events.
  • ProjectKnowledge.remember() emits KnowledgeUpdate events.
  • FeedbackStore emits ThresholdShift events when learned thresholds change significantly.

Added — New Dashboard APIs

  • GET /api/events — Latest 200 events from JSONL file (cross-process visibility).
  • GET /api/graph — Full project dependency index.
  • GET /api/feedback — Compression feedback outcomes and learned thresholds.
  • GET /api/session — Current session state.
  • GET /api/search-index — BM25 index summary with language distribution and top chunks.
  • GET /api/compression-demo?path=<file> — On-demand compression of any file through all modes with original content preview.

Fixed

  • Live Observatory showed "unknown" for all events due to flat vs. nested kind object mismatch — implemented flattenEvent() parser supporting all 6 event types.
  • Agent World status comparison was case-sensitive (Active vs active) — now case-insensitive.
  • Learning Curves scatter plot showed 0 for x-axis — now computes compression ratio from tokens_saved / tokens_original when compression_ratio field is absent.
  • Compression Lab failed to load files — added rust/ prefix fallback for path resolution and original content field in API response.
  • Dependency Map edges not connecting — added module-to-file path resolution for api::Server-style import targets.

[3.0.0] — 2026-04-10

Major Release: Waves 1-5 — Intelligence Engine, Knowledge Graph, A2A Protocol, Adaptive Compression

This is a major release bringing lean-ctx from 28 to 34 MCP tools, adding 8 read modes (new: task), persistent knowledge with temporal facts, multi-agent orchestration (A2A protocol), adaptive compression with Thompson Sampling bandits, and a complete fix for the context dropout bug (#73).


Wave 1 — Neural Token Optimization & Graph-Aware Filtering

  • Neural token optimizer — Attention-weighted compression that preserves high-information-density lines using Shannon entropy scoring with configurable thresholds.
  • Graph-aware Information Bottleneck filter — Integrates the project knowledge graph into task mode filtering, preserving lines that reference known entities (functions, types, modules) from the dependency graph.
  • Task relevance scoring — Renamed information_bottleneck_filtergraph_aware_ib_filter with KG-powered entity recognition for smarter context selection.

Wave 2 — Context Reordering & Entropy Engine

  • LITM-aware context reordering — Reorders compressed output using a U-curve attention model (Lost-in-the-Middle), placing high-importance content at the start and end of context windows where LLM attention is strongest.
  • Adaptive entropy thresholds — Per-language BPE entropy thresholds with Kolmogorov complexity adjustment that auto-tune based on file characteristics.
  • task read mode — New compression mode that filters content through the Information Bottleneck principle, preserving only task-relevant lines. Achieves 65-85% savings while maintaining semantic completeness.

Wave 3 — Persistent Knowledge & Episodic Memory

  • ctx_knowledge tool — Persistent project knowledge store with temporal validity, confidence decay, and contradiction detection. Actions: remember, recall, timeline, rooms, search, wakeup.
  • Episodic memory — Facts have temporal validity (valid_from/valid_until) and confidence scores that decay over time for unused knowledge.
  • Procedural memory — Cross-session knowledge that automatically surfaces relevant facts based on the current task context.
  • Contradiction detection — When storing a new fact that contradicts an existing one in the same category, the old fact is automatically superseded.

Wave 4 — A2A Protocol & Multi-Agent Orchestration

  • ctx_task tool — Google A2A (Agent-to-Agent) protocol implementation with full task lifecycle: create, assign, update, complete, cancel, list, get.
  • ctx_cost tool — Cost attribution per agent with token tracking. Actions: record, summary, by_agent, reset.
  • ctx_heatmap tool — File access heatmap tracking read counts, compression ratios, and access patterns. Actions: show, hot, cold, reset.
  • ctx_impact tool — Measures the impact of code changes by analyzing dependency chains in the knowledge graph.
  • ctx_architecture tool — Generates architectural overviews from the project's dependency graph and module structure.
  • Agent Card.well-known/agent.json endpoint for A2A agent discovery with capabilities, supported modes, and rate limits.
  • Rate limiter — Per-agent sliding window rate limiting (configurable, default 100 req/min).

Wave 5 — Adaptive Compression (ACON + Bandits)

  • ACON feedback loop — Adaptive Compression via Outcome Normalization. Tracks compression outcomes (quality signals from LLM responses) and adjusts thresholds automatically.
  • Thompson Sampling bandits — Multi-armed bandit approach for selecting optimal compression parameters per file type and language. Uses Beta distributions with configurable priors.
  • Quality signal detection — Automatically detects quality signals in LLM responses (re-reads, error patterns, follow-up questions) to feed the ACON loop.
  • ctx_shell cwd tracking — Shell working directory is now tracked across calls. cd commands are parsed and persisted in the session. New cwd parameter for explicit directory control.

Fix: Context Dropout Bug (#73)

All five root causes of the "lean-ctx loses context after initial read phase" bug have been fixed:

  • Monorepo-aware project_rootdetect_project_root() now finds the outermost ancestor with a project marker (.git, Cargo.toml, package.json, go.work, pnpm-workspace.yaml, nx.json, turbo.json, etc.), not the nearest .git.
  • ctx_shell cwd persistence — New shell_cwd field in session state. cd commands are parsed and the working directory persists across ctx_shell calls. Priority: explicit cwd arg → session shell_cwdproject_root → process cwd.
  • ctx_overview/ctx_preload root fallback — Both tools now fall back to session.project_root when no path parameter is given (previously defaulted to server process cwd).
  • Relative path resolution — All 15+ path-based tools now use resolve_path() which tries: original path → project_root + relative → shell_cwd + relative → fallback.
  • Windows shell chaining; in commands is automatically converted to && when running under cmd.exe.

Improved — Diagnostics

  • lean-ctx doctor — New session state check showing project_root, shell_cwd, and session version.

Stats

  • 34 MCP tools (was 28)
  • 8 read modes (was 7, new: task)
  • 656+ unit tests passing
  • 14 integration tests passing
  • 24 supported editors/AI tools

[2.21.11] — 2026-04-09

Fix: Dashboard, Doctor, and MCP Reliability (#72)

Fixed — Doctor gave false positives for broken MCP configs

  • MCP JSON validationdoctor now validates the actual JSON structure of each MCP config file instead of just checking for the string "lean-ctx". Checks for mcpServerslean-ctxcommand fields, verifies the binary path exists, and reports per-IDE status (valid vs. broken configs).
  • Honest stats check — A missing stats.json is now reported as a warning ("MCP server has not been used yet") instead of counting as a passed check.

Fixed — Dashboard showed empty state without guidance

  • The empty state now includes an actionable troubleshooting checklist with IDE-specific steps (Cursor reload, Claude Code init, config validation).

Fixed — No session created until first tool call batch

  • A session is now created immediately on MCP initialize, so doctor --report always shows session info even before any tools are used.

Fixed — Tool calls only logged when >100ms

  • All tool calls are now logged regardless of duration. Previously, fast calls were silently dropped, making the tool call log appear empty.

Fixed — macOS binary hangs at _dyld_start after install

  • On macOS, copying the binary (via cp, install, or download) could strip the ad-hoc code signature, causing the dynamic linker to hang indefinitely on startup. Both install.sh and the self-updater now run xattr -cr + codesign --force --sign - after placing the binary.

[2.21.10] — 2026-04-09

Fix: Auth/Device Code Flow Output Preserved

Fixed — OAuth device code output no longer compressed (#71)

  • Auth flow detection — New contains_auth_flow() function detects OAuth device code flow output using a two-tier approach:
    • Strong signals (match alone): devicelogin, deviceauth, device_code, device code, device-code, verification_uri, user_code, one-time code
    • Weak signals (require URL in same output): enter the code, use a web browser to open, verification code, waiting for authentication, authorize this device, and 10 more patterns
  • Shell hook passthrough — 21 auth commands added to BUILTIN_PASSTHROUGH: az login, gh auth, gcloud auth, aws sso, firebase login, vercel login, heroku login, flyctl auth, vault login, kubelogin, --use-device-code, and more. These bypass compression entirely.
  • MCP tool passthroughctx_shell::handle() now checks output for auth flows before compression. If detected, full output is preserved with a [lean-ctx: auth/device-code flow detected] note.
  • Shell hook buffered pathcompress_if_beneficial() also checks for auth flows before any compression, covering the exec_buffered path when stdout is not a TTY.

Impact

Previously, when Codex or Claude Code ran an auth command (e.g. az login --use-device-code), the device code was hidden from the user because lean-ctx compressed the output. Now the full output including auth codes is preserved.

Workaround for older versions: Add excluded_commands = ["az login"] to ~/.lean-ctx/config.toml, or prefix commands with LEAN_CTX_DISABLED=1.

[2.21.9] — 2026-04-09

First-Class MCP Support for Pi Coding Agent

Added — pi-lean-ctx v2.0.0 with Embedded MCP Bridge

  • Embedded MCP client — pi-lean-ctx now spawns the lean-ctx binary as an MCP server (JSON-RPC over stdio) and registers all 20+ advanced tools (ctx_session, ctx_knowledge, ctx_semantic_search, ctx_overview, ctx_compress, ctx_metrics, ctx_agent, ctx_graph, ctx_discover, ctx_context, ctx_preload, ctx_delta, ctx_edit, ctx_dedup, ctx_fill, ctx_intent, ctx_response, ctx_wrapped, ctx_benchmark, ctx_analyze, ctx_cache, ctx_execute) as native Pi tools.
  • Automatic pi-mcp-adapter compatibility — If lean-ctx is already configured in ~/.pi/agent/mcp.json (via pi-mcp-adapter), the embedded bridge is skipped to avoid duplicate tool registration.
  • Dynamic tool discovery — Tool schemas come directly from the MCP server at runtime, not hardcoded. The disabled_tools config is respected.
  • Auto-reconnect — If the MCP server process crashes, the bridge reconnects automatically (3 attempts with exponential backoff). CLI-based tools (bash, read, grep, find, ls) continue working regardless.
  • /lean-ctx command enhanced — Now shows binary path, MCP bridge status (embedded vs. adapter), and list of registered MCP tools.

Added — Pi auto-detection in lean-ctx setup

  • Pi Coding Agent is now auto-detected alongside Cursor, Claude Code, VS Code, Zed, and all other supported editors. Running lean-ctx setup writes ~/.pi/agent/mcp.json automatically.
  • lean-ctx init --agent pi now also writes the MCP server config to ~/.pi/agent/mcp.json with lifecycle: lazy and directTools: true.

Improved — Pi diagnostics

  • lean-ctx doctor now shows three Pi states: "pi-lean-ctx + MCP configured", "pi-lean-ctx installed (embedded bridge active)", or "not installed".

Documentation

  • README for pi-lean-ctx completely rewritten with MCP tools table, pi-mcp-adapter compatibility guide, and disabled_tools configuration.
  • PI_AGENTS.md template updated with MCP tools section.

[2.21.8] — 2026-04-09

Self-Updater Shell Alias Refresh + Thinking Budget Tuning

Fixed — lean-ctx update now refreshes shell aliases automatically

  • Shell alias auto-refreshpost_update_refresh() now detects all shell configs (~/.zshrc, ~/.bashrc, config.fish, PowerShell profile) with lean-ctx hooks and rewrites them with the latest _lc() function. Previously, lean-ctx update only refreshed AI tool hooks (Claude, Cursor, Gemini, Codex) but left shell aliases untouched, meaning users had to manually run lean-ctx setup to get new hook logic like the pipe guard.
  • Multi-shell support — If a user has hooks in both .zshrc and .bashrc, both are now updated (previously only the first match was handled).
  • Post-update message — Now explicitly tells users to source ~/.zshrc or restart their terminal.

Changed — Thinking Budget Tuning

  • FixBug intent: Minimal → Medium (bug fixes benefit from deeper reasoning)
  • Explore intent: Medium → Minimal (exploration is lightweight)
  • Debug intent: Medium → Trace (debugging needs full chain-of-thought)
  • Review intent: Medium → Trace (code review needs thorough analysis)

Improved — README & Deploy Checklist

  • README — Added "Updating lean-ctx" section with all update methods, added pipe guard troubleshooting entry.
  • Deploy checklist — Added "Shell Hook Refresh", "README / GitHub Updates" sections, and two new common pitfalls.

[2.21.7] — 2026-04-09

Cleanup + Website Redesign

Changed — Remove Hook E2E Test Suite

  • Removed hook_e2e_tests.rs — The hook E2E test file and its corresponding CI workflow (hook-integration) have been removed. The pipe guard behavior is already covered by the integration tests in integration_tests.rs and the unit tests in cli.rs. This eliminates a redundant CI job that depended on generate_rewrite_script, simplifying the test matrix.

Changed — Website: LeanCTL Section Redesigned

  • Consistent page design — The LeanCTL ecosystem section on the homepage now uses the same visual patterns (compare-cards, layer-cards, stats-grid) as the rest of the page, replacing the custom TUI terminal mockup with ~150 lines of dedicated CSS.
  • Real product facts — Compare cards show concrete token savings from leanctl.com (4,200 → 48 tokens for file reads, 847 → 42 for test output, 4,200 → ~13 for re-reads).
  • Three feature cards — "23 Built-in Tools", "Thinking Steering", "Bring Your Own Key" in the standard layer-card layout.
  • Stats grid — "up to 90% savings", "23 tools", "8 compression modes", "0 data sent to us".

Changed — Navigation: Dedicated Ecosystem Dropdown

  • New top-level nav item — "Ecosystem" mega dropdown with two columns: "AI Agents" (LeanCTL) and "Community" (GitHub, Discord, Blog).
  • Product dropdown cleaned — Removed the ecosystem column from the Product mega dropdown (now 3 columns instead of 4).
  • Mobile menu updated — Ecosystem section with LeanCTL, GitHub, Discord links.

i18n

  • All 11 locale files updated with new ecosystem keys (en/de with translations, others with English fallbacks).

[2.21.6] — 2026-04-08

Shell Hook Pipe Guard — Fix curl | sh Broken by lean-ctx

Fixed — Piped commands corrupted by lean-ctx compression

  • Pipe guard for Bash/Zsh_lc() now checks [ ! -t 1 ] (stdout is not a terminal) before routing through lean-ctx. When piped (e.g. curl -fsSL https://example.com/install.sh | sh), commands run directly without compression. Previously, lean-ctx would buffer and compress the output, corrupting install scripts and other piped data.
  • Pipe guard for Fish_lc now checks not isatty stdout before routing through lean-ctx.
  • Pipe guard for PowerShell_lc now checks [Console]::IsOutputRedirected before routing through lean-ctx.

Important

After updating, run lean-ctx init to regenerate the shell hooks with the pipe guard. Or open a new terminal tab.

Testing

  • 5 new E2E tests for pipe-guard behavior and piped output preservation.
  • 3 new unit tests verifying pipe-guard presence in all shell hook variants (Bash, Fish, PowerShell).
  • All 677 tests passing, zero clippy warnings.

[2.21.5] — 2026-04-08

Windows Updater Infinite Loop Fix (#69)

Fixed — Updater enters infinite loop with 100% CPU on Windows

  • Replaced timeout /t with ping delay — The deferred update .bat script used timeout /t 1 /nobreak for delays. On Windows systems with GNU coreutils in PATH (Git Bash, Cygwin, MSYS2), the GNU timeout binary takes precedence over the Windows built-in, fails instantly with "invalid time interval '/t'", and causes a tight retry loop at 100% CPU. Now uses ping 127.0.0.1 -n 2 >nul which works on every Windows system regardless of PATH.
  • Added retry limit (60 attempts) — The script now exits with an error message after 60 failed attempts (~60 seconds) instead of looping indefinitely. Cleans up the pending binary on timeout.
  • Extracted generate_update_script() as public function for testability.

Testing

  • 10 new unit tests covering: no timeout command usage, ping delay, retry limit, counter increment, timeout exit, pending file cleanup, path substitution (incl. spaces), batch syntax validity, rollback on failure.
  • All 669 tests passing, zero clippy warnings.

[2.21.4] — 2026-04-08

Windows Shell Fix + Antigravity Support

Fixed — Windows: ctx_shell fails with "& was unexpected at this time"

  • PowerShell always preferred — On Windows, find_real_shell() now always attempts to locate PowerShell (pwsh.exe or powershell.exe) before falling back to cmd.exe. Previously, PowerShell was only used if PSModulePath was set — but when IDEs (VS Code, Codex, Antigravity) spawn the MCP server, this env var is often absent. Since AI agents send bash-like syntax (&&, pipes, subshells), cmd.exe cannot parse these commands. This was the root cause of "& was unexpected at this time" errors reported by Windows users.
  • LEAN_CTX_SHELL override — Users can set LEAN_CTX_SHELL=powershell.exe (or any shell path) to force a specific shell, bypassing all detection logic.

Added — antigravity agent support

  • lean-ctx init --agent antigravity — Now recognized as alias for gemini, creating the same hook scripts and settings under ~/.gemini/. Previously, Antigravity users had to know to use --agent gemini or run lean-ctx setup.

Testing

  • 19 new E2E tests covering shell detection, LEAN_CTX_SHELL override, shell command execution (pipes, &&, subshells, env vars), agent init (antigravity alias, unknown agent handling), Windows path handling in generated scripts, and bash script execution with Windows binary paths.
  • 10 new unit tests for Windows shell flag detection and shell detection logic.
  • All 659 tests passing, zero clippy warnings.

[2.21.3] — 2026-04-08

Robust Hook Escaping + Auto-Context Fix

Fixed — Commands with Embedded Quotes Truncated

  • JSON parser rewrite — Hook scripts and Rust handler now correctly parse JSON values containing escaped quotes (e.g. curl -H "Authorization: Bearer token"). Previously, the naive [^"]* regex stopped at the first \" inside the value, truncating the command. Now uses ([^"\\]|\\.)* pattern with proper unescape pass. Affects both bash scripts and Rust extract_json_field.
  • Double-escaping for rewrites — Rewrite output now applies two escaping passes: shell-escape (for the -c "..." wrapper) then JSON-escape (for the hook protocol). Previously, only one pass was applied, causing inner quotes to break both shell and JSON parsing.

Fixed — Auto-Context Noise from Wrong Project (#62 Issue 4)

  • Project root guardsession_lifecycle_pre_hook and enrich_after_read now require a known, non-trivial project_root before triggering auto-context. Previously, when project_root was None or ".", the autonomy system would run ctx_overview on the MCP server's working directory (often a completely different project), injecting irrelevant "AUTO CONTEXT" blocks into responses.

Improved — Cache Hit Message Clarity (#62 Issue 3)

  • Actionable stub — Cache hit responses now include guidance: "File already in context from previous read. Use fresh=true to re-read if content needed again." Previously, the terse F1=main.rs cached 2t 4L stub left AI agents confused about what to do next.

Housekeeping

  • Redirect scripts reduced to minimal exit 0 (removed ~30 lines of dead is_binary/FILE_PATH parsing code that was never reached).
  • 4 new unit tests for escaped-quote JSON parsing and double-escaping.
  • 1 new integration test for auto-context project_root guard.
  • All 611 tests passing, zero clippy warnings.

[2.21.2] — 2026-04-08

Critical Hook Fixes — Production Quality (Discussion #62)

Fixed — Pipe Commands Broken in Shell Hook

  • Pipe quoting fix — Hook rewrite now properly quotes commands containing pipes. Previously curl ... | python3 -m json.tool was rewritten as lean-ctx -c curl ... | python3 ... (pipe interpreted by shell). Now correctly produces lean-ctx -c "curl ... | python3 ...". This also fixes the command not found: _lc errors reported by users.

Fixed — Read/Grep/ListFiles Blocked by Hook (#62)

  • Removed tool blocking — The redirect hook no longer denies native Read, Grep, or ListFiles tools. This was causing Claude Code's Edit tool to fail ("File has not been read yet") because Edit requires a prior native Read. Native tools now pass through freely. The MCP system instructions still guide the AI to prefer ctx_read/ctx_search/ctx_tree, but blocking is removed.

Fixed — find Command Glob Pattern Support

  • Glob patternslean-ctx find "*.toml" now correctly uses glob matching instead of literal substring search. Added glob crate dependency.

Changed — README

  • RTK — Corrected "RTK" references to full name "Rust Token Killer" throughout README and FAQ section.

Housekeeping

  • Removed ~180 lines of dead code from hook_handlers.rs (unused glob matching, binary detection, path exclusion functions that were orphaned by the redirect removal).
  • Added 3 new unit tests for hook rewrite quoting behavior.
  • All 504 tests passing, zero clippy warnings.

[2.21.1] — 2026-04-08

CLI File Caching

Added — Persistent CLI Read Cache (#65)

  • File-based CLI cachinglean-ctx read <file> now caches file content to ~/.lean-ctx/cli-cache/cache.json. Second and subsequent reads of unchanged files return a compact ~13-token cache-hit response instead of the full file content. This directly addresses Issue #65 (pi-lean-ctx zero cache hits) by enabling caching for CLI-mode integrations that don't use the MCP server.
  • Cache management — New lean-ctx cache subcommand with stats, clear, and invalidate <path> actions.
  • --fresh / --no-cache flag — Bypass the CLI cache for a single read when needed.
  • 5-minute TTL — Cache entries expire after 300 seconds, matching the MCP server cache behavior.
  • MD5 change detection — Files are re-read when their content changes, even within the TTL window.
  • Max 200 entries — Oldest entries are evicted when the cache exceeds capacity.
  • 6 new unit tests including integration test for full cache lifecycle.

Fixed — Missing Module Registrations

  • Registered sandbox and loop_detection modules that were present on disk but missing from core/mod.rs.

[2.21.0] — 2026-04-08

Binary File Passthrough, Disabled Tools, Community Contributions

Fixed — Hook Blocks Image Viewing (#67)

  • Binary file passthrough — Hook redirect now detects binary files (images, PDFs, archives, fonts, videos, compiled files) by extension and passes them through to the native Read tool. Previously, the hook would deny all read_file calls when lean-ctx was running, which blocked AI agents from viewing screenshots and images.
  • Updated both Rust handle_redirect() and all bash hook scripts (Claude, Cursor, Gemini CLI) with the same binary extension check.

Added — Disabled Tools Config (#66, @DustinReynoldsPE)

  • disabled_tools config field — Exclude unused tools from the MCP tool list to reduce token overhead from tool definitions. Configure via ~/.lean-ctx/config.toml or LEAN_CTX_DISABLED_TOOLS env var (comma-separated).
  • Example: disabled_tools = ["ctx_benchmark", "ctx_metrics", "ctx_analyze", "ctx_wrapped"]
  • 10 new tests covering parsing, TOML deserialization, and filtering logic.

Closed — Cache Hits Documentation (#65)

  • Clarified that file caching requires MCP server mode (ctx_read), not shell hook mode (lean-ctx -c). Shell hooks compress command output only; the MCP server provides file caching with ~13 token re-reads.

[2.20.0] — 2026-04-07

Sandbox Execution, Progressive Throttling, Compaction Recovery

Added — Sandbox Code Execution

  • ctx_execute — New MCP tool that runs code in 11 languages (JavaScript, TypeScript, Python, Shell, Ruby, Go, Rust, PHP, Perl, R, Elixir) in an isolated subprocess. Only stdout enters the context window — raw data never leaves the sandbox. Supports action=batch for multiple scripts in one call, and action=file to process files in sandbox with auto-detected language.
  • Smart truncation — Large outputs (>32 KB) are truncated with head (60%) + tail (40%) preservation, keeping both setup context and error messages visible.
  • LEAN_CTX_SANDBOX=1 env — Set in all sandbox processes for detection by user code.
  • Timeout support — Default 30s, configurable per-call.

Added — Progressive Throttling (Loop Detection)

  • Automatic agent loop detection — Tracks tool call fingerprints within a 5-minute sliding window. Calls 1-3: normal. Calls 4-8: reduced results + warning. Calls 9-12: stronger warning. Calls 13+: blocked with suggestion to use ctx_batch_execute or vary approach.
  • Deterministic fingerprinting — JSON args are canonicalized (key-sorted) before hashing, so {path: "a", mode: "b"} and {mode: "b", path: "a"} are treated as the same call.
  • Per-tool tracking — Different tools with different args are tracked independently.

Added — Compaction Recovery

  • ctx_session(action=snapshot) — Builds a priority-tiered XML snapshot (~2 KB max) of the current session state including task, modified files, decisions, findings, progress, test results, and stats. Saved to ~/.lean-ctx/sessions/{id}_snapshot.txt.
  • ctx_session(action=restore) — Rebuilds session state from the most recent compaction snapshot. When the context window fills up and the agent compacts, the snapshot allows seamless continuation.
  • Priority tiers — Task and files (P1) are always included. Decisions and findings (P2) next. Tests, next steps, and stats (P3/P4) are dropped first if the 2 KB budget is tight.

[2.19.2] — 2026-04-07

Fixed

  • Gemini CLI hook schema — Fixed "Discarding invalid hook definition for BeforeTool" error. Hook definitions now include the required "type": "command" field and nested "hooks" array structure expected by the Gemini CLI validator. Existing configs without "type" are automatically migrated. (#63)
  • Remote dashboard auth — Fixed dashboard returning {"error":"unauthorized"} when accessed remotely via browser. Auth is now only enforced on /api/* endpoints. HTML pages load freely, with the bearer token automatically injected into API calls. Browser URL with ?token= query parameter is printed on startup for easy remote access. (#64)

[2.19.1] — 2026-04-07

Fixed

  • Cursor hooks.json format — Fixed invalid hooks.json that caused "Config version must be a number; Config hooks must be an object" error in Cursor. Now generates correct format with "version": 1 and hooks as an object with preToolUse key instead of array. Existing broken configs are automatically migrated on next lean-ctx install cursor or MCP server start.
  • cargo publish workflow — Added --allow-dirty to release pipeline to prevent publish failures from checkout artifacts

[2.19.0] — 2026-04-07

Added — Knowledge Intelligence

  • Temporal facts — All facts now track valid_from/valid_until timestamps. When a high-confidence fact changes, the old value is archived (not deleted) with full history
  • Contradiction detectionctx_knowledge(action=remember) automatically detects when a new fact conflicts with an existing high-confidence fact, reporting severity (low/medium/high) and resolution
  • Confirmation tracking — Facts that are re-asserted gain increasing confirmation_count, boosting their reliability score
  • Knowledge roomsctx_knowledge(action=rooms) lists all knowledge categories (rooms) with fact counts, providing a MemPalace-like structured overview
  • Timeline viewctx_knowledge(action=timeline, category="...") shows the full version history of facts in a category, including archived values with validity ranges
  • Cross-session searchctx_knowledge(action=search, query="...") searches across ALL projects and ALL past sessions for matching facts, findings, and decisions
  • Wake-up briefingctx_knowledge(action=wakeup) returns a compact AAAK-formatted briefing of the most important project facts
  • AAAK format — Compact knowledge representation (CATEGORY:key=value★★★|key2=value2★★) used in LLM instructions instead of verbose prose, saving ~60% tokens

Added — Agent Diaries

  • Persistent agent diariesctx_agent(action=diary, category=discovery|decision|blocker|progress|insight) logs structured entries that persist across sessions at ~/.lean-ctx/agents/diaries/
  • Diary recallctx_agent(action=recall_diary) shows the 10 most recent diary entries for an agent with timestamps and context
  • Diary listingctx_agent(action=diaries) lists all agent diaries across the system with entry counts and last-updated times

Added — Wake-Up Context

  • ctx_overview wake-up briefingctx_overview now automatically includes a compact briefing at session start: top project facts (AAAK), last task, recent decisions, and active agents — zero configuration needed

Changed

  • Knowledge block in LLM instructions now uses AAAK compact format instead of verbose prose, reducing knowledge injection tokens by ~60%
  • MCP tool descriptions updated for ctx_knowledge (12 actions) and ctx_agent (11 actions) to document all new capabilities

[2.18.1] — 2026-04-07

Code Quality & Security Hardening

Fixed

  • Shell injection in CLIlean-ctx grep and lean-ctx find no longer shell-interpolate user input; replaced with pure Rust implementation using ignore::WalkBuilder + regex
  • Panic in report_gotchaunwrap() after add_or_merge could panic when gotcha store exceeds capacity (100 entries) and the new entry gets evicted; now returns Option<&Gotcha> safely
  • Broken FilterEngine cache — Removed dead get_or_load() method that stored empty rules in a Mutex and was never called; CACHED_ENGINE static removed
  • unwrap() after is_some() pattern — Replaced fragile double-lookup + unwrap() with idiomatic if let Some() / match in ctx_read, ctx_smart_read, and ctx_delta
  • graph CLI argument parsinglean-ctx graph build /path now correctly separates action from path argument

Added

  • lean-ctx graph CLI command — Build the project dependency graph from the command line (lean-ctx graph [build] [path]); previously only available via MCP ctx_graph tool
  • Consolidated detect_project_root — Single implementation in core::protocol replacing 3 duplicate copies across server.rs, ctx_read.rs, and dashboard/mod.rs

Changed

  • Tokio features trimmedfeatures = ["full"] replaced with 8 specific features (rt, rt-multi-thread, macros, io-std, io-util, net, sync, time), reducing compile time and binary size
  • Security workflow updatedsecurity-check.yml now correctly documents ureq as the allowed HTTP client (for opt-in cloud sync, updates, error reports) instead of claiming "no network"

[2.18.0] — 2026-04-07

Multi-Agent Context Sharing, Semantic Caching, Dashboard & Editor Integrations

Added — Multi-Agent

  • ctx_share tool (28th MCP tool) — Share cached file contexts between agents. Actions: push, pull, list, clear
  • ctx_agent handoff action — Transfer a task to another agent with a summary message, automatically marks the handing-off agent as finished
  • ctx_agent sync action — Combined overview of active agents, pending messages, and shared contexts
  • lctx --agents flag — Launch multiple agents in parallel: lctx --agents claude,gemini starts both in the background with shared context
  • Dashboard /api/agents enhancement — Returns structured JSON with active agents, pending messages, and shared context count

Added — Intent & Semantic Intelligence

  • Multi-intent detectionctx_intent now detects compound queries ("fix X and then test Y") and splits them into sub-intents with individual classifications
  • Complexity classificationctx_intent returns task complexity (mechanical/standard/architectural) based on query analysis, target count, and cross-cutting keywords
  • Heat-ranked file strategyctx_intent file discovery ranks results by heat score (token density + graph connectivity)
  • Semantic cache — TF-IDF + cosine similarity index for finding semantically similar files across reads. Persistent at ~/.lean-ctx/semantic_cache/. Cache warming suggestions based on access patterns. Hints shown on ctx_read cache misses

Added — Dashboard & CLI

  • lean-ctx heatmap — New CLI command for context heat map visualization with color-coded token counts and graph connections
  • Dashboard authentication — Bearer token auth for /api/* endpoints, token generated on first launch at ~/.lean-ctx/dashboard_token
  • Heatmap APIGET /api/heatmap returns project-wide file heat scores as JSON

Added — Editor Integrations

  • VS Code Extension (packages/vscode-lean-ctx) — Status bar token savings, one-click setup, MCP auto-config for GitHub Copilot, command palette (setup, doctor, gain, dashboard, heatmap)
  • Chrome Extension (packages/chrome-lean-ctx) — Manifest V3, auto-compress pastes in ChatGPT, Claude, Gemini. Native messaging bridge for full compression, fallback for comment/whitespace removal

Changed

  • MCP tool count: 25 → 28 across all documentation, READMEs, SKILL.md, and 11 website locales

[2.17.6] — 2026-04-07

Feature: Crush Support (#61)

Added

  • Crush integrationlean-ctx init --agent crush configures MCP in ~/.config/crush/crush.json with the Crush-specific "mcp" key format (instead of "mcpServers")
  • Auto-detectionlean-ctx setup and lean-ctx doctor now detect Crush installations
  • Rules injectionlean-ctx rules creates ~/.config/crush/rules/lean-ctx.md when Crush is installed
  • Prompt generator — Website getting-started page includes Crush with manual config instructions
  • Compatibility page — Crush listed in all compatibility matrices across 11 languages

[2.17.5] — 2026-04-06

Fix: ctx_shell Input Validation (#50)

Added

  • File-write command blockingctx_shell now detects and rejects shell redirects (>, >>), heredocs (<< EOF), and tee commands. Returns a clear error redirecting to the native Write tool
  • Command size limit — Rejects commands over 8KB, preventing oversized heredocs from corrupting the MCP protocol stream
  • Quote-aware redirect parsing — Redirect detection respects single/double quotes, ignores 2> (stderr) and > /dev/null

This prevents the cascading failure reported in #50: Oversized ctx_shell → API Error 400 → MCP stream corruption → "path is required" → MCP stops

[2.17.4] — 2026-04-06

Feature: Hook Redirect Path Exclusion + Automated Publishing

Added

  • Path exclusion for hook redirect (#60) — Exclude specific paths from PreToolUse redirect hook. Paths matching patterns bypass the redirect and allow native Read/Grep/ListFiles to proceed
    • Config: redirect_exclude = [".wolf/**", ".claude/**", "*.json"] in ~/.lean-ctx/config.toml
    • Env var: LEAN_CTX_HOOK_EXCLUDE=".wolf/**,.claude/**" (takes precedence)
    • Glob patterns support *, ?, and ** (recursive directory match)
  • Automated crates.io publishingcargo publish runs automatically after GitHub Release
  • Automated npm publishinglean-ctx-bin and pi-lean-ctx published automatically

[2.17.3] — 2026-04-06

Fix: MCP Stdout Pollution on Windows

Fixed

  • Windows MCP "not valid JSON" errorprintln!("Installed...") messages in install_claude/cursor/gemini_hook_config polluted stdout during MCP server initialization, breaking JSON-RPC protocol. Now suppressed via mcp_server_quiet_mode() guard. (Fixes Lorenzo Rossi's report on Discord)

Changed

  • LanguageSwitcher position — Moved to the right of the "Get Started" button in the header
  • Token Guardian Buddy — Now shown inline in lean-ctx gain output when enabled
  • Bug Memory stats — Active gotchas and prevention stats shown in lean-ctx gain
  • Helpful footerlean-ctx gain now shows links to report-issue, contribute, and gotchas

[2.17.2] — 2026-04-06

Fix: Cross-Platform Hook Handlers

Fixed

  • Windows: PreToolUse hook errors — Agent hooks (Claude Code, Cursor, Gemini) no longer require Bash. Hook logic is now implemented natively in the lean-ctx binary via lean-ctx hook rewrite and lean-ctx hook redirect (#49)
  • "Stuck in file reading" — Fixed hook redirect loop where denied Read/Grep tools caused repeated retries when the MCP server wasn't properly connected
  • Hook auto-migration — Existing .sh-based hook configs are automatically upgraded to native binary commands on next MCP server start

Changed

  • Hook configs now point to lean-ctx hook rewrite / lean-ctx hook redirect instead of .sh scripts
  • refresh_installed_hooks() also updates hook configs (not just scripts) to ensure migration

[2.17.1] — 2026-04-05

Token Guardian Buddy — Data-Driven ASCII Companion

Added

  • Token Guardian Buddy — Tamagotchi-style companion that evolves based on real usage metrics (tokens saved, commands, bugs prevented)
  • Procedural ASCII avatar generation — Over 69 million unique creature combinations from 8 modular body parts (head, eyes, mouth, ears, body, legs, tail, markings)
  • Deterministic identity — Each user gets a unique, persistent buddy based on their system seed
  • XP & leveling system — XP calculated from tokens saved, commands issued, and bugs prevented; level derived via sqrt(xp / 50)
  • Rarity tiers — Egg → Common → Uncommon → Rare → Epic → Legendary, based on lifetime tokens saved
  • Mood system — Dynamic mood (Happy, Focused, Tired, Excited, Zen) derived from compression rate, errors, bugs prevented, and streak
  • RPG stats — Compression, Vigilance, Endurance, Wisdom, Experience (0-100 scale)
  • Name generator — Deterministic adjective + noun combinations (~900 combos, e.g. "Cosmic Orbit")
  • CLI commandslean-ctx buddy with show, stats, ascii, json actions; pet alias
  • Dashboard Buddy card — Glasmorphism UI with rarity-dependent gradients/animations, animated XP bar, SVG radial gauges, styled speech bubble, mood indicator
  • API endpoint/api/buddy serving full BuddyState JSON including ascii_art and xp_next_level

[2.17.0] — 2026-04-04

Premium Experience Upgrade — Architecture, Performance & Polish

Major internal refactoring for long-term maintainability, performance improvements for async I/O, unified error handling, and premium polish across CLI, dashboard, and CI pipeline.

Architecture

  • server.rs split — Monolithic server.rs (1918 lines) split into 4 focused modules: tool_defs.rs (620L), instructions.rs (159L), cloud_sync.rs (136L), server.rs (1001L). Each module has a single responsibility.
  • Centralized error handling — New LeanCtxError enum in core/error.rs with thiserror derive. From impls for io::Error, toml::de::Error, serde_json::Error. Config::save() migrated as first consumer.

Performance

  • Async I/O for ctx_shellexecute_command wrapped in tokio::task::spawn_blocking to prevent blocking the Tokio runtime during shell command execution.

CLI

  • Dynamic version — All hardcoded version strings replaced with env!("CARGO_PKG_VERSION"). Version is now single-sourced from Cargo.toml.
  • report-issue exit code — Empty title now exits with status 1 for proper script error detection.
  • Theme migrationprint_command_box() migrated from hardcoded ANSI to the core::theme system.
  • upgrade → updatelean-ctx upgrade now prints deprecation notice and delegates to lean-ctx update.

Dashboard

  • Offline fonts — Removed Google Fonts CDN dependency, switched to system font stacks.
  • Dynamic version — Version display fetched from /api/version instead of hardcoded.
  • Empty state UX — "No data yet" message links to Getting Started guide.
  • Connection retry — Auto-retry with clear user message when dashboard API is unavailable.

Setup

  • Compact doctor — New doctor::run_compact() provides concise diagnostics during lean-ctx setup, reducing noise for new users.

Tool Robustness

  • ctx_search — Reports count of files skipped due to encoding/permission errors.
  • ctx_read — Warns on unknown mode (falls back to full). Shows message when cached content is used after file read failure.
  • ctx_analyze / ctx_benchmark.unwrap() on min_by_key replaced with if let Some(...) to prevent potential panics.

CI

  • Deduplicated audit — Removed redundant cargo audit job (handled in security-check.yml).
  • Release testscargo test --all-features now runs before release builds in release.yml.

[2.16.6] — 2026-04-04

ctx_edit — MCP-native file editing with Windows CRLF support

Agents in Windsurf + Claude Code extension loop when Edit requires unavailable Read. ctx_edit provides search-and-replace as an MCP tool — no native Read/Edit dependency.

Added

  • ctx_edit MCP tool — reads, replaces, and writes files in one call. Parameters: path, old_string, new_string, replace_all, create.

Fixed

  • CRLF/LF auto-normalization — Windows files with \r\n now match when agents send \n strings (and vice versa). Line endings are preserved.
  • Trailing whitespace tolerance — retries with trimmed trailing whitespace per line if exact match fails.
  • Edit loop prevention — instructions say "NEVER loop on Edit failures — use ctx_edit immediately".
  • PREFER over NEVER — all injected rules use "PREFER lean-ctx tools" instead of "NEVER use native tools".
  • 9 unit tests covering CRLF, LF, trailing whitespace, and combined scenarios.

[2.15.0] — 2026-04-03

Scientific Compression Evolution

Six algorithms from information theory, graph theory, and statistical mechanics now power lean-ctx's compression pipeline — all automatic, all local, zero configuration.

Added

  • Predictive Surprise Scoring — Replaces static Shannon entropy with BPE cross-entropy. Measures how "surprising" each line is to the LLM's tokenizer. Boilerplate scores low and gets removed; complex logic scores high and stays. 1530% better filtering than character-level entropy.
  • Spectral Relevance Propagation — Heat diffusion + PageRank on the project dependency graph. Finds structurally important files even without keyword overlap. Seed files spread relevance along import edges with exponential decay.
  • Boltzmann Context Allocation — Statistical mechanics-based token budget distribution. Specific tasks concentrate tokens on top files (low temperature); broad tasks spread evenly (high temperature). Automatically selects compression mode per file.
  • Semantic Chunking with Attention Bridges — Restructures output to counter LLM "Lost in the Middle" attention bias. Promotes task-relevant chunks to high-attention positions, adds structural boundary markers and tail anchors.
  • MMR Deduplication — Maximum Marginal Relevance removes redundant lines across files using bigram Jaccard similarity. 1025% less noise in multi-file context loads.
  • BPE-Aligned Token Optimization — Final-pass string replacements aligned to BPE token boundaries (function fn , " -> ""->", lifetime elision). 38% additional savings.
  • Auto-Build Graph Indexload_or_build() function automatically builds the project dependency graph on first use. No manual ctx_graph build required — the system is fully zero-config.
  • Fish Shell Doctor Checklean-ctx doctor now detects shell aliases in ~/.config/fish/config.fish (previously only checked zsh/bash).
  • Codex Hook Refresh on Updatelean-ctx update now refreshes Codex PreToolUse hook scripts alongside Claude, Cursor, and Gemini hooks.

Changed

  • Graph edge resolution now maps Rust module paths back to file paths, enabling correct heat diffusion and PageRank propagation across the codebase.
  • Centralized graph index loading across ctx_preload, ctx_overview, autonomy, and ctx_intent — eliminates path mismatch bugs between relative and absolute project roots.

Performance

  • 85.7% session-wide token savings (with CCP) in 30-min coding simulation
  • 96% compression in map/signatures mode with 94% quality preservation
  • 99.3% savings on cache re-reads (13 tokens)
  • 95% git command compression across all patterns
  • 12/12 scientific verification checks passed
  • 39/39 intensive benchmark tests passed

[2.14.5] — 2026-04-02

Changed

  • Internal cleanup — Removed dead code (format_type_short, instruction_encoding_savings) and their orphaned test from the protocol module. Simplified cloud and help text messaging. No functional changes.

[2.14.4] — 2026-04-02

Fixed

  • LEAN_CTX_DISABLED kill-switch now works end-to-end — The shell hook (bash/zsh/fish/powershell) previously ignored LEAN_CTX_DISABLED entirely. Setting it to 1 bypassed compression in the Rust code but the shell aliases were still loaded, spawning a lean-ctx process for every command. Now: the _lc() wrapper short-circuits to command "$@" when LEAN_CTX_DISABLED is set (zero overhead), the auto-start guard skips alias creation, and lean-ctx -c does an immediate passthrough. Closes #42.
  • lean-ctx-status shows DISABLED statelean-ctx-status now prints DISABLED (LEAN_CTX_DISABLED is set) when the kill-switch is active.
  • Help text documents both env vars--help now shows LEAN_CTX_DISABLED=1 (full kill-switch) and LEAN_CTX_ENABLED=0 (prevent auto-start, lean-ctx-on still works).

[2.14.3] — 2026-04-02

Added

  • Full Output Tee — New tee_mode config (always/failures/never) replaces the old tee_on_error boolean. When set to always, full uncompressed output is saved to ~/.lean-ctx/tee/ and referenced in compressed output. Backward-compatible: tee_on_error: true maps to failures. Use lean-ctx tee last to view the most recent log. Closes #2021.
  • Raw Mode — Skip compression entirely with ctx_shell(command, raw=true) in MCP or lean-ctx -c --raw <command> on CLI. New lean-ctx-raw shell function in all hooks (bash/zsh/fish/PowerShell). Use for small outputs or when full detail is critical. Closes #2022.
  • Truncation Warnings — When output is truncated during compression, a transparent marker shows exactly how many lines were omitted and how to get full output (raw=true). Prevents silent data loss — the #1 reason users leave competing tools.
  • LEAN_CTX_DISABLED env var — Master kill-switch that bypasses all compression in both shell hook and MCP server. Set LEAN_CTX_DISABLED=1 to pass everything through unmodified.
  • ANSI Auto-Strip — ANSI escape sequences are automatically stripped before compression, preventing wasted tokens on invisible formatting codes. Centralized strip_ansi implementation replaces 3 duplicated copies.
  • Passthrough URLs — New passthrough_urls config option. Curl commands targeting listed URLs skip JSON schema compression and return full response bodies. Useful for local APIs where full JSON is needed.
  • Zero Telemetry Badge — README and comparison table now explicitly highlight lean-ctx's privacy-first design: zero telemetry, zero network requests, zero PII exposure.
  • User TOML Filters — Define custom compression rules in ~/.lean-ctx/filters/*.toml. User filters are applied before builtin patterns. Supports regex pattern matching with replacement and keep-lines filtering. New CLI: lean-ctx filter [list|validate|init]. Closes #2023.
  • PreToolUse Hook for Codex — Codex CLI now gets PreToolUse-style hook scripts alongside AGENTS.md, matching Claude and Cursor/Gemini behavior. Closes #2024.
  • New AI Tool Integrations — Added opencode, aider, and amp as supported agents. Use lean-ctx init --agent opencode|aider|amp. Total supported agents: 19. Closes #2026.
  • Discover Enhancementlean-ctx discover now shows a formatted table with per-command token estimates, USD savings projection (daily and monthly), and uses real compression stats when available. Shared logic between CLI and MCP tool. Closes #2025.

Changed

  • ctx_shell MCP tool schema now accepts raw boolean parameter.
  • Server instructions include raw mode and tee file hints.
  • Help text updated for new commands (filter, tee last, -c --raw).

[2.14.2] — 2026-04-02

Fixed

  • Shell hook quotinggit commit -m "message with spaces" now works correctly. The _lc() wrapper previously used $* which collapsed quoted arguments into a flat string; fixed to use $@ (bash/zsh), unquoted $argv (fish), and splatted @args (PowerShell) to preserve argument boundaries. Closes #41.
  • Terminal colors preserved — Commands run through the shell hook in a real terminal (outside AI agent context) now inherit stdout/stderr directly, preserving ANSI colors, interactive prompts, and pager behavior. Previously, output was piped through a streaming buffer which caused child processes to disable color output (isatty() returned false). Closes #40.

Removed

  • exec_streaming mode — replaced by exec_inherit_tracked which passes output through unmodified while still recording command usage for analytics.

[2.14.1] — 2026-04-02

Autonomous Intelligence Layer

lean-ctx now runs its optimization pipeline autonomously — no manual tool calls needed. The system self-configures, pre-loads context, deduplicates files, and provides efficiency hints without the user or AI agent triggering anything explicitly.

Added

  • Session Lifecycle Manager — Automatically triggers ctx_overview or ctx_preload on the first MCP tool call of each session, delivering immediate project context
  • Related Files Hints — After every ctx_read, appends [related: ...] hints based on the import graph, guiding the AI to relevant files
  • Silent Background Preload — Top-2 imported files are automatically cached after each ctx_read, eliminating cold-cache latency on follow-up reads
  • Auto-Dedup — When the session cache reaches 8+ files, ctx_dedup runs automatically to eliminate cross-file redundancy (measured: -89.5% in real sessions)
  • Task Propagation — Session task context automatically flows to all ctx_read and ctx_multi_read calls for better compression targeting
  • Shell Efficiency Hints — When grep, cat, or find run through ctx_shell, lean-ctx suggests the more token-efficient MCP equivalent
  • AutonomyConfig — Full configuration struct with per-feature toggles and environment variable overrides (LEAN_CTX_AUTONOMY=false to disable all)
  • PHP/Laravel Support — Full PHP AST extraction, Laravel-specific compression (Eloquent models, Controllers, Migrations, Blade templates), and php artisan shell hook patterns
  • 15 new integration tests for the autonomy layer (autonomy_tests.rs)

Changed

  • System Prompt — Replaced verbose PROACTIVE + OTHER TOOLS blocks with a compact AUTONOMY block, reducing cognitive load on the AI agent (~20 tokens saved per session)
  • ctx_multi_read — Now accepts and propagates session task for context-aware compression

Fixed

  • Version commandlean-ctx --version now uses env!("CARGO_PKG_VERSION") instead of a hardcoded string

Performance

  • Net savings: ~1,739 tokens/session (analytical measurement)
  • Pre-hook wrapper overhead: 10 tokens (one-time)
  • Related hints: ~10 tokens per ctx_read call
  • Silent preload savings: ~974 tokens (eliminates 2 manual reads)
  • Auto-dedup savings: ~750 tokens at 15% reduction on typical cache
  • System prompt delta: -20 tokens

Configuration

All autonomy features are enabled by default. Disable individually or globally:

# ~/.lean-ctx/config.toml
[autonomy]
enabled = true
auto_preload = true
auto_dedup = true
auto_related = true
silent_preload = true
dedup_threshold = 8

Or via environment: LEAN_CTX_AUTONOMY=false

[2.14.0] — 2026-04-02

Intelligence Layer Architecture

lean-ctx transforms from a pure compressor into an Intelligence Layer between user, AI tool, and LLM.

Added

  • ctx_preload MCP tool — proactive context orchestration based on task + import graph
  • L-Curve Context Reorder Engine — classifies lines into 7 categories, reorders for optimal LLM attention

Changed

  • Output-format reordering: file content first, metadata last
  • IB-Filter 2.0 with empirical L-curve attention weights
  • LLM-native encoding with 15+ token optimization rules
  • System prompt cleanup (~200 wasted tokens removed)

Fixed

  • Shell hook compression broken when stdout piped
  • Shell hook stats lost due to early process::exit()