720 KiB
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
automode — 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.mdtemplate. - 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,RenderTransformtrait,.lmd.mdraw read). - SessionStart nudge improvements. Explicit ctx_read cache statistics
for shared-mode hosts (Cursor); fixed stale
ctx_semantic_searchreference.
Fixed
- Cursor Read redirect regression (GH #1250 follow-up).
install_cursor_deny_hookwas re-adding the Read redirect thatmerge_cursor_hookshad removed, causingcli_fulltraffic 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_globinstead 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 returnedNone. - Codex integration test adjusted.
agent_init_codextest updated from 3 to 2 PreToolUse entries after removing the separate deny hook.
Changed
redirect_read_args()now returnsVec<String>with dynamic mode selection instead of a fixed[&str; 4]array.refresh_agent_hooks()is now fully mode-aware — determines the recommendedHookModeper agent and installs the correct artifacts.- Removed PoC
edit_probehandler (served its diagnostic purpose).
[3.9.7] — 2026-07-11
Added
- Runtime
tools/list_changednotifications (GH #1250). When the user changestool_profile,tools_enabled, ordisabled_toolsvia dashboard, CLI, or manual config edit, the MCP server now automatically sends anotifications/tools/list_changedto 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_watchwith 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 buildmemory explosion capped (GH #790). Six fixes that together prevent unbounded RAM growth during index builds:- Memory Guardian activated for CLI builds —
start_guard()now runs beforeensure_all_background(), so pressure/abort checks in graph and BM25 code actually fire (previously only started in daemon mode). graph_index_max_filesdefault 0 → 15 000 — caps the graph scan; override withgraph_index_max_files = 0for unlimited.- Graph
content_cachecapped at 256 MB — stops caching file contents once the budget is hit; the edge builder falls back to disk reads. - Edge build batched (500 files/batch) —
par_iter(ALL)replaced with chunked parallel batches and memory-pressure checks between them. - 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).
- BM25 save streams through zstd —
postcard::to_allocvecreplaced withpostcard::to_io→zstd::Encoder→ temp file; eliminates the intermediate uncompressedVec<u8>allocation entirely. - 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. - Memory Guardian: immediate first RSS sample — closes the 3-second blind window where builders allocated freely with stale Normal pressure flags.
- CLI build evicts content_cache on Hard+ pressure — previously only
called
jemalloc_purge; now clears the 128 MB shared content cache. - Graph + BM25 run sequentially under memory pressure — prevents peak allocations from compounding when the system is already low on RAM.
- Graph scan admission control — uses
index_admission(same as BM25) before parallel fan-out; oversized corpora degrade to sequential. - Graph scan batch size 2000 → 500 — matches BM25's
MAX_BATCH_FILES; reduces per-batch peak from ~40 MB to ~10 MB ofScanFileResultcontent. - Batch-0 pressure check — graph scan now checks
abort_requestedon the very first batch (previously batch 0 always ran unchecked). - 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). - Edge parallel batches check
is_under_pressure— previously only checkedabort_requested(Hard+); Soft pressure now stops edge-building. build-fullandbuild-semanticstart the memory guardian — previously onlybuildactivated the guardian; full/semantic builds ran unprotected.
- Memory Guardian activated for CLI builds —
- Cursor Read redirect removed — savings jump from 9.5 % to 73+ % (GH #1250).
Cursor's
StrReplaceinternally triggers a nativeReadthat the redirect hook intercepted, producing verbatimcli_fulloutput 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'spreToolUseredirect hook — native Reads pass through unmodified (StrReplace works), and the agent usesctx_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-compactread mode. Newctx_readmode: headerless, trailing-whitespace-stripped verbatim content. Used by the Read redirect to produce temp files faithful to the original line structure while giving ~5–10 % compression without breaking the host'soffset/limitwindowed reads (fixes header-in-temp-file offset bug from the originalfullmode, #1021 follow-up).- Dashboard channel-breakdown API.
New
/api/statsfieldchannel_breakdownclassifies every recorded command intoredirect(hook-intercepted native tools),rewrite(shell commands rewritten to lean-ctx), ormcp(direct ctx_* calls). Powers the Cockpit Proof/Trends chart showing which delivery channel contributes what savings.
Changed
- Default
memory_cleanupswitched fromaggressivetoshared. 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 viamemory_cleanup = "aggressive"orLEAN_CTX_MEMORY_CLEANUP=aggressive. - Default
cache_max_tokensraised from 500k to 2M. Four times more headroom for the in-memory read cache — eliminates premature eviction in large codebases. Override viaLEAN_CTX_CACHE_MAX_TOKENS. - Read redirect switched from
fulltofull-compactmode. Redirect hook now produces headerless, whitespace-trimmed temp files. Fixes offset/limit correctness (#1021 follow-up) and saves ~5–10 % on every redirected native Read. - Grep redirect now host-aware (GH #398 follow-up).
grep_content_mode()no longer requires an explicitoutput_mode=contentparameter — when the mode is absent, it detects the host IDE and allows the redirect on Cursor (which defaults tocontent) while blocking it on Claude Code (which defaults tofiles_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 13–70 % 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()incrementscache_hitsandtotal_reads, so re-reads of auto/map/signatures outputs appear in CEP stats and dashboard. read_dedupsavings tracked in CEP stats. The PostToolUseread_deduphook now callsstats::record("cli_read_dedup")stats::flush(), surfacing dedup savings in the redirect channel on the dashboard.
- Dashboard channel-breakdown empty state.
cockpit-remaining.jsnow shows a helpful "No channel data yet" message instead of silently rendering nothing (which previously caused aSyntaxErroron the Proof/Trends page). cap_to_raw()inflation prevention tracked as metric. Events where framed output would exceed raw content are now counted incache_telemetryand shown inctx_cache status.- CLI
lstree-tracking fix.cmd_lsnow passes the realoriginaltoken count tocli_track_treeinstead of0, 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) trailingpwd -(lone dash flag) not matched byhas_trailing_bare_pwd; (2) unquoted eval arguments (eval pwdvseval '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
additionalContextinjection (GH #778). PostToolUse[CODE HEALTH]notices and PreToolUse shadow-mode nudges injected text intoadditionalContexton every qualifying event. On Anthropic models this retroactively mutates the cached prefix, causing 440–520k tokens of cache re-bills per injection. Fix: defaultinject_context = falsein[code_health]config — notices now route toctx_knowledgeand the ContextBus (dashboard). Opt-in via[code_health] inject_context = trueorLEAN_CTX_INJECT_CONTEXT=1. - Marker contamination in source files — root-cause fix. The redirect-suffix
(
--- lean-ctx: ctx_compose ...) was appended directly to.lctxtemp files. When agents copied temp-file content back into source, the marker leaked into.rs/.js/.shfiles, breaking builds. Fix: the suffix is never written to file content; the nudge travels exclusively viaadditionalContext(gated byinject_context) or is suppressed entirely. - Release pipeline: rmcp crates.io compile bug.
rmcp 2.2.0on crates.io callsSseStream::from_bytes_streambutsse-stream 0.2.xonly providesfrom_byte_stream. Restored[patch.crates-io]to upstream git rev67a3085which has the fix;cargo publish --no-verifybypasses broken verification. - Release pipeline: Homebrew SHA256 grep collision. The grep pattern
x86_64-unknown-linux-gnualso matched the-cudavariant, producing multiple SHA256 values and breaking the GitHub Actions output format. Fixed with.tar.gzsuffix anchoring. - agent_wrapper detection —
pwd -and unquoted eval arg (GH #745 follow-up). Two additional Claude Code sandbox variants still hit theevalhard-block in v3.9.3: (1) trailing&& pwd -(lone dash flag) was not matched byhas_trailing_bare_pwd; (2) unquoted eval arguments (eval pwdvseval 'pwd'). Fix:has_trailing_bare_pwdnow accepts anypwdfollowed by flag-only tokens (no redirect operator), coveringpwd,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 withlean-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.
wrapspawnslean-ctx mcp, sends JSON-RPCinitializetools/list, and confirmsctx_readis present — gives instant feedback that the MCP server works before the user opens their editor.
- Agent launch detection.
wrapchecks whether Cursor/VS Code is already running and gives context-aware restart hints (process detection viapgrepon macOS/Linux,taskliston Windows). - install.sh auto-PATH fix. The installer now adds
~/.local/binto PATH automatically (appends to shell RC + exports in current session). Opt out withLEAN_CTX_NO_PATH_FIX=1. - install.sh auto-onboard. After binary installation,
lean-ctx onboardruns automatically. Opt out withLEAN_CTX_NO_ONBOARD=1. - npm postinstall auto-onboard.
npm install -g lean-ctx-binnow runslean-ctx onboardafter 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.onboardandsetupremain 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_overheadinstead of their ownbypass_hintsconfig key — decoupled; (2) cold-start sessions (no ctx_* call yet) never triggered a hint becauseLAST_LCTX_CALL_TSwas 0 — addedSERVER_START_TSfallback; (3) Cursor'sconversation_idUUID never matched lean-ctx'ssession_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
SessionCachewrite-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 forhandle_with_preread(). Graph hints (graph_related_hint()) are now computed after lock release. Documented inLOCK_ORDERING.md. - Dashboard "Evict" UX — missing await + no visual state (GH #744).
_executeOverlay()now awaitsloadData()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+ barepwd) alongside the existing bash redirect path. - Gateway double-counts OpenRouter non-BYOK usage.cost (GH #746).
absorb_openai()no longer sumsusage.cost + upstream_inference_costwhen they are identical (non-BYOK mirror). - Dashboard "Read Full" button risk warning persists (GH #747).
risk.rsnow checksSetView(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
additionalContextsupport 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
.lctxtemp 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_HEADERto 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 authoringlean-ctx-addon.tomlverbatim into a signedkind=addoncontext 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,reviewpublishes disclosed;--checkruns 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 updatere-resolves from the install source. The context registry refuses to importkind=addonpacks (wrong trust chain, useaddon add). The bundled registries are now generated snapshots:gen_registryvalidates, sorts and canonicalizesrust/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
kindpackage taxonomy..ctxpkgmanifests gain an optionalkindfield (context|skills|addon|grammar; defaultcontext, 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 adddownloads the prebuilt binary for the current platform into the managed bin dir (<data_dir>/addons/bin/<name>/<version>/, neverPATH), 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 — zeroPATHinteraction, tamper ⇒ spawn refused,addons.policy = lockedblocks the fetch before any network I/O. Newlean-ctx addon update <name>installs side-by-side, health-gates, then prunes;addon removedeletes the managed binaries;doctorverifies 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 inx-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 (OpenRouterusage.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 viacost_source. - Index-time include/exclude filters (GH #735). The retrieval corpus is
now declared explicitly instead of abusing
.gitignorefor retrieval policy: new[index]config (exclude,include,respect_gitignore) plus per-run CLI overlays on everylean-ctx indexbuild command — repeatable--exclude <glob>/--include <glob>(both--flag valueand--flag=valueforms) 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--excludeappends to config,--includereplaces 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_modeenters thectx_readmode chain (explicit arg > policy pack > persona > profile/auto;"auto"= no opinion),compressorcompactsctx_url_readflowing-text modes through the extension registry (research→markdown,support/lead-gen→prose; extractivefacts/quotes/linksstay verbatim to protect citations),chunkermakes token-budget truncation cut on chunk boundaries (paragraphs / line windows) instead of mid-sentence,intent_taxonomylands as the contract-promised persona block (PERSONA:/INTENTS:/DEFAULTS:) in the MCP session instructions, andsensitivity_floorfolds into[sensitivity]enforcement (Config::sensitivity_effective) — a floor abovepublicenables enforcement out of the box and can only tighten an explicit config (LEAN_CTX_SENSITIVITY=offstays the kill switch). Thecodingdefault 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-ctxprocess; 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-eradeepseek-v3list prices — and presented the estimate as COST. Cost accounting is now three-layered and honest: (1) measured provider cost — OpenRouter chat requests opt intousage: {include: true}(only when the effective upstream is openrouter.ai; api.openai.com never sees the non-standard field), and the response scanner absorbsusage.costpluscost_details.upstream_inference_cost(BYOK) as the authoritative billed charge, which beats every table estimate in the meter, the policy gate andusage_events; (2) live prices for all models — a newcore::gain::live_pricingmodule 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,badges, KPI foot line, CSV columns:free/:extendedvariants) and aLEAN_CTX_LIVE_PRICING=offkill switch, slotted between exact embedded matches and the heuristic inModelPricing::quote; (3) cost provenance — every usage event now recordscost_source(provider|live|list|heuristic|shadow), the admin console marks measured vs estimated spend (✓/measured_requests/estimated_requests),lean-ctx spendprints ✓/* 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_DIRto its hook child processes for Claude-compat — and lean-ctx's guard-host detection took that variable as proof the host is Claude Code, soread_redirect = autoswitched 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 -cpath inherited the host's stdin, so a command that falls back to reading stdin — e.g.rgleft 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 orphanedrgkept a Cursor shell session dead for hours). In agent/pipe contexts (stdin not a TTY) the child now gets/dev/nullstdin (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, theLEAN_CTX_HOOK_BINARYoverride 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, andlean-ctx doctornow flags wrapper scripts whose binary no longer resolves. Thanks @tr3lane for the precise follow-up to #708. ledger evictalways 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 backsremoveandset_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_pathemits forward slashes on Windows. Thanks @ITFinesse for the report.secret_detectionredacted 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 subtractivesecret_detection.exclude_patternsconfig 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/workspacesdeduplicated 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/sessionit never merged the proxy's livestats.last_usefreshness; 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 absorbsstats.last_use, and unsaved changes flush after 60 s (the first change of a fresh session immediately)./api/agentsuses 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 addcorrupted the key file scaffolded bygateway init(GH #716).initwrites the canonical empty setkeys = [];keys addappended a[[keys]]table to that body, leaving BOTH representations in the file — invalid TOML (duplicate key), so the documented onboarding flow (init→keys add) failed on first use, and a fullrevokere-armed the same trap.addnow 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 oldalwaysdefault 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). Setshell_activation = "always"(orLEAN_CTX_SHELL_ACTIVATION=always) to keep the old behavior, e.g. to feed your own shell usage intolean-ctx wrapped;lean-ctx-onstill 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/compressis wire-compatible with LiteLLM's prompt-compression guardrail (GH #700). LiteLLM ≥ v1.92 can call a compression sidecar duringpre_call(guardrail: headroom); the response now carries thetokens_before/tokens_after/compression_ratiotelemetry fields that guardrail logs, alongside the existing richerstatsblock. Point the guardrail'sapi_baseat the lean-ctx daemon and every request through a LiteLLM gateway is compressed deterministically (prompt-cache-safe, #498) — no client change, including Claude Code viaANTHROPIC_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 freecount_tokensendpoint 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)./statusgains averified_savingsblock andlean-ctx proxy statusaVerified:line beside the estimate; per-model pairs persist across restarts inproxy_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/compressrewrite now advertises its retrieval hash in the guardrail's regex-lockedhash=<24-hex>form, and the newGET /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_usagenow records tree-sitter vs regex-fallback hits per file extension, persisted across sessions ingrammar_usage.json(aggregate counters only — no paths or project data).ctx_metricsshows 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
initializerequest" 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'sCode/User/mcp.jsonleft Insiders with an emptyMCP: Open User Configuration— exactly the "server missing in one window" confusion from the multi-window report.setup/initnow detect and write the dedicated Insiders config on all platforms (agent keyvscode-insiders),doctorlists 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_readgainsrepoparam parity in multi-repo mode (GH #696, PR #698 — thanks @getappz).ctx_search/ctx_glob/ctx_treecould already target a registered root viarepo=<alias>, butctx_readcould not — you could find a file in another root yet not read it. Read-only by design (ctx_edit/ctx_patchstay 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.jsonis quarantined, never silently reset (GH #706 — thanks @getappz). A crash mid-write (or disk-full) could leave truncated JSON; the loader'sunwrap_or_default()then wiped months of savings history without a trace on the next write. Unparseable stats now move tostats.json.corrupt(one warning log; the file is evidence and stays recoverable by hand), anddoctorreports 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_rootis captured once at MCPinitialize; when the client later enters a git worktree (Claude CodeEnterWorktreenests 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 bothshell_cwdandproject_rootup to their nearest.gitentry (dir or worktree file); when the boundaries differ, the liveshell_cwdwins. A plaincd rust/inside the same checkout shares the boundary and is untouched, and ashell_cwdwith no git upward gives no signal — so the monorepo behavior stays exactly as before. ctx_readraw 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 explicitcwdparam now updates the live shell cwd (GH #707 follow-up). The worktree-divergence detection readssession.shell_cwd, but that field only trackedcdcommands inside command text — clients that switch checkouts pass the new directory as thecwdargument of every call, so the switch was invisible to path resolution. A jail-accepted explicitcwdis now persisted, verified end-to-end over a real MCP session (read resolves into the worktree copy afterctx_shell cwd=<worktree>).lean-ctx stop/dev-installno 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 fullps ppidancestor 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-installunder 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_serachreturned 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.jsonbetween machines with different usernames, that absolute path is wrong on every other machine — and re-runninginit/doctor --fixthere rewrites the file, ping-ponging your sync forever. Settinghook_binary = "$HOME/.local/bin/lean-ctx"(config) orLEAN_CTX_HOOK_BINARY(env) emits that expression verbatim into every shell-executed hook command — the hook host's shell expands it at run time — anddoctoraccepts 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-serverfeature), 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 doctorpreflights 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 likezuehlke/fast; the gateway resolves the alias, injects upstream credentials and stampsrouted_frominto the ledger./mepersonal 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, 24h–90d windows, savings-share KPI.- Signed org-policy gates (GL enterprise#25/#66) — under a signed,
pinned,
enforced = trueorg policy the forward path refuses: models outside the[routing].allowed_modelsceiling (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 honestRetry-Afterof the seconds until the minute rolls). Errors arrive in the caller's wire shape; refusals are counted onleanctx_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-scopedgateway gdpr export|delete, and Blake3 pseudonymization for person identifiers at rest.
- Multi-window visibility (GH #694).
lean-ctx doctorno longer claims "no active session" when sessions exist for other workspaces: run from a directory that isn't an open project root it now reportsnone 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/workspacesendpoint) 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, oraddons.policy = locked, or the newaddons.grammar_auto_fetch = falsefor 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/listfailure 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, androots/list_changedrestores the retry budget. dev-installon Windows no longer hard-fails withACCESS_DENIEDwhile 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 tolean-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_sharehandovers 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
rememberjust committed (lost-update, #326 class). The consolidation pipeline (apply_artifacts_to_stores) and the gateway memory adapter (addon_memoryingest) both did load → modify → blindsave()from a background thread; a fact committed between their load and save was silently dropped — surfacing as flaky "no current fact exists" errors onctx_knowledge relateright after a successfulremember. Both writers now go throughProjectKnowledge::mutate_lockedlike every other writer. - CI: three timing/environment flakes hardened. The
session_lock_timeoutprompt-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 inLOCK_ORDERING.md(L58/L59). max_ram_percentis 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), andUnloadIndices/EmergencyDropadditionally clear the ANN cache (newann_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/awkfile dumps are verbatim output — no more dictionary-mangled source (GH #688). A range-print likesed -n '10,50p' file.ps1fell into the generic terse pipeline, whose dictionary layer word-substitutes code identifiers with no code-awareness (function→fn,return→ret, bareelselines dropped) — corrupting code read via sed/awk instead ofcat.sed/awk/gawk/mawk/nawknow classify as file viewers likecat/head/tail. In-place edits are excluded via a token-based flag check (-i,-i.bak,-niclusters,--in-place[=suffix], gawk-i inplace) — deliberately NOT a substring match, so filenames likemy-input.txtordata-import.csvcan'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).setupno 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).doctorno longer false-flags a working OpenCode install (GH #686). Two gaps:has_lean_ctx_mcp_entryonly walkedmcp.servers.lean-ctx, but OpenCode's schema (opencode.ai/config.json) nests servers DIRECTLY undermcp— 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 byinstall_all_skillswhen 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_patchvalidated 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:rgdying 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 fromcheck_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/CodeBuddyremove_blocktwin) 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_patchbecomes the first-class edit path (#1008, "Edit Loop v1"). The anchored editor now closes the loop the rules already routed: read withctx_read(mode="anchored")(or tag hits viactx_search(anchored=true)), then patch byline + hashanchor — 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_patchjoins the lazy core and thestandardprofile (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_patchschema shrank ~625 → ~263 tokens; rarely-used params (expected_md5,backup,validate_syntax,evidence) stay supported but are no longer advertised. op=create:ctx_patchcan 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 didctx_editgo?". - 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-anchorCONFLICTretries, against the str_replace baseline (old_stringtokens paid,old_stringmisses). Never estimated, never folded into the read-gain ledger, never printed in tool bodies (#498). Surfaced inctx_metrics,/api/stats → edit_efficiencyand 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_reliabilitysuite 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).
- Advertised where it earns its tokens:
- Hook-aware Cursor guidance — the honest profile (GL #1153–#1157). On
hosts whose installed lean-ctx hooks already compress the native tools
(Cursor: PreToolUse
rewritecovers Shell,redirectcovers Read/Grep), the injected~/.cursor/rules/lean-ctx.mdcnow carries a newHookCoveredprofile 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 MCPinitializeanchor for covered Cursor sessions is reworded the same way. Detection is conservative (both PreToolUse entries must be present; invalid/missinghooks.jsonfalls back to the fullDedicatedmapping), the byte-exact drift check re-syncs the profile when hooks are installed or removed later, and the Cursor hook installer now honoursshadow_mode/compression_levelinstead 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 = autokeeps 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 newPostToolUsehook (lean-ctx hook read-dedup, matcherReadonly) 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 documentedupdatedToolOutputchannel. First reads stay byte-identical (edit safety:old_stringalways 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 bytool_use_idso a duplicate first read is never mistaken for a re-read. Configread_dedup = auto | on | off(envLEAN_CTX_READ_DEDUP);auto(default) activates only on guard hosts, where the PreToolUse redirect is off. Verified end-to-end against headlessclaude -p2.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=searchnow 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.mdregenerated 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 viadoctor overhead --gate) and links the deterministic dual-arm self-verify (digestf5ed145e61ce3689) with its methodology. The CGB self-assessment (C2 — Managed) is surfaced from the README security section and Journey 13.
Fixed
dev-installhonours redirected cargo target dirs (GH #671). Bothrust/dev-install.shand thelean-ctx dev-installcommand located the built binary at a hardcodedtarget/release/…; withCARGO_TARGET_DIRor a~/.cargo/config.toml[build] target-diroverride (one shared build cache across worktrees) they silently symlinked/installed a stale or missing binary. The target dir is now resolved viacargo metadata(env, config files and workspace settings all honoured) with a./targetfallback, 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.exesuffix, andtests/pre_release_check.shfollows suit. Follow-up:install.sh's source-build path (served atleanctx.com/install.sh) had the same hardcode and could link a stale binary from an earlier default-layout build — it now resolves viacargo metadataidentically 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) strandedzod/v3/locales/en.jsand 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 atprepack), 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 withpi-markdown-preview. - MCP server answers
initializebefore doing housekeeping (GH #669). Orphan-process sweep (onepsper 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 withCannot read properties of undefined (reading 'invoke')(upstream: microsoft/vscode#321150). That work is now deferred onto the blocking pool, concurrent with the handshake; atime_to_initialize_mslog line makes the span measurable,lean-ctx doctorsurfaces 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 --yesnow leavesdoctorfully green. Three healers that silently disagreed are aligned: the session-start heal installs the agentSKILL.mdfiles alongside rules (previouslydoctorwarned "run: lean-ctx setup" forever),doctor's shell-hook probe honours a relocatedLEAN_CTX_CONFIG_DIR(no more false "pipe guard missing"), andsetup/onboarddetect Claude Code / CodeBuddy by their state dir (~/.claude/,~/.codebuddy/) exactly likedoctorand the rules injector do — killing the dead loop wheredoctorpointed atsetupbutsetupskipped the client. A new integration gate (onboard_doctor_clean) runs the full journey in an isolatedHOMEand assertsdoctorexits green. ctx_knowledge remembernever stalls on the embedding model again. The firstrememberon 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
rememberwhile the engine is still warming up) never got an embedding, and only a manualembeddings_reindexrepaired that — on a live machine most projects sat at 0 vectors, invisible tomode=semanticrecall.remembernow 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 theAUTO CONTEXTblock on the first tool call (prompt-cache-friendly) instead of anACTIVE SESSIONblock 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 actx_editfallback 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 nativeRead→Editas the primary editing path under the read-before-write gate, and says explicitly to use native tools throughout when no ctx_* tools are available.doctorgains anInstructions/MCP consistencycheck (GL #1139) that flags the hazardous combination — instructions advertising ctx_* while no lean-ctx entry is registered in the Claude MCP config — with alean-ctx setuprepair hint.
Security
ctx_callcan no longer bypass egress DLP or permission inheritance. The guarded dispatch path unwrapsctx_call(name=…, args=…)and runs both checks against the inner tool and its arguments (nestedctx_callis already refused by the handler). Egress payload extraction is centralized in one helper shared by the MCP server andlean-ctx policy enforce, and now also coversctx_patchwrite bodies (new_text,new_body,ops[].new_text).prefer_native_editor(#454) now hides/refusesctx_patchalongsidectx_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 flagsemantic-search --artifactssearches the doc corpus; new guidedocs/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://…(orLEANCTX_DENSE_BACKEND=pgvector) stores embeddings in per-project, per-dimensionvector(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 thepsqlCLI (zero new crate dependencies, mirrors the postgres provider); rows return as per-line JSON for robust parsing; identifiers and literals are strictly validated/escaped. Theqdrant+pgvectorfeatures 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, vianpx -y @tobilu/qmd@2.5.3 mcp) andmemgraph-ingester(structure-aware RAG on a Memgraph code graph, viauvx 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) anddocs/guides/dense-backends.mddocumenting 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 sharedKnowledgeSnapshotto the vendor-neutral Open Knowledge Format (git-diffable Markdown + YAML, relations as Markdown links) or the signed.ctxpkgbundle. 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-blockingAddon Registry Freshnessworkflow (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 standarduv tool installbootstrap; the only runtime requirement is anLLM_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 newaliasessession reset(#653).lean-ctx session newnow clears the active session just likesession 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/.mdownreads (and.txtfiles 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(MCP2025-11-25alignment, #656). The MCP server/client stack now builds on rmcp 2.0:Contentis the spec-unifiedContentBlock, prompt roles use the sharedRole, resources are plainResourcestructs, 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-18and 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, Repomix1.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 junkRead ---finding polluting session memory and every wakeup briefing (F1);setup/onboard--helpprints help instead of executing setup side effects (F2);ctx_callwith misspelled keys (tool/args/params) fails with the exact fix instead of silently dispatching without arguments (F3);ctx_knowledge rememberderives a deterministic key slug whenkeyis omitted and acceptscontent=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 reports0 edges(F5); the project-overview header surfaces persisted call-graph edges instead of contradictingctx_callgraphwith0 edges(F6); barectx_knowledge recalllists recent facts instead of erroring (F7); andctx_session showis accepted as a synonym ofstatus(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 withpath escapes project root, breakingctx_compose/ctx_read/ctx_patch.resolve_pathnow 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
Authorizationheader, so/v1/tools/callfailed with 401 while/healthpassed. 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 (Unix0o600, 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 injectsRootCerts::PlatformVerifierso requests trust the system/enterprise trust store instead of only the bundled WebPKI roots — fixingUnknownIssuerfailures 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_noticehelper that speaks only whenLEAN_CTX_DEBUG=1(and stdout is a TTY).lean-ctx-statusstill reports the current state on demand. doctorrecognizes 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/versionon 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 existingdashboard_respondingprobe, so the browser-open guard anddoctorshare one source of truth.- Native Read no longer breaks Claude Code's read-before-write guard (#637).
The
PreToolUseredirect hook rewrote a nativeReadto a temp.lctxcopy, 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 headlessclaude -p, with no supported off-switch (the hook self-healed back intosettings.json). A newread_redirect = auto | on | offkey (envLEAN_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 defaultautodisables only the Read path-swap on hosts carrying that guard — Claude Code / CodeBuddy, detected inside the hook via theCLAUDE_PROJECT_DIRmarker Claude Code exports to every hook subprocess (CLAUDECODE/CODEBUDDYare honored too) — so native Read → Write/Edit works out of the box; thectx_readMCP tool and the Grep/Glob redirects keep compressing.onrestores always-redirect;offdisables 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
RECOVERrule 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), orctx_read(mode=full|raw=true);[Archived]/tee/firewall handles →ctx_expand(id=…). - Reactive (ctx_read footer). Every lossy
ctx_readview ends with a recovery footer that leads with the native file path, then the MCP fallbacks;full/rawviews carry no footer (deduped by mode).ctx_readgained an explicitrawparameter (verbatim bytes, unframed escape hatch). - CLI / shell-hook surface.
lean-ctx raw "<command>"is now surfaced inhelp,help alland the cheatsheet, so the raw path is reachable without MCP. - Guaranteed raw on disk. The default
tee_modeis nowhigh-compression: whenever a view compresses heavily a verbatim copy is onectx_expand/file read away. The newrecovery_hintsconfig (off|minimal|full) tunes footer verbosity. All recovery grammar (archive, firewall, spill, tee, ctx_shell) flows through onerecoverymodule, so every channel speaks the same non-MCP-first sentence.
- Proactive (MCP rules v4). A new
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.tscould be compressed on the wire and lose those separator lines — after whichctx_editfailed 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 verbatimctx_readmodes (full/raw/lines:N-M) to reproduce every source line, including decorative comments. - The Codex
lean-ctx -cSessionStart hook is no longer a redundant nag (#625). Codex'sPreToolUsehook already rewrites every rewritable Bash command tolean-ctx -c "<command>"transparently (permissionDecision: allow+updatedInput), so the old SessionStart line ("preferlean-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 withlean-ctxuntouched, so the agent always reaches verbatim bytes. ctx_shellnow surfaces when a requestedcwdwas rejected by the project-root jail (#629). Acwdresolving outside the session'sproject_rootis 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 runningpwd && lsin what they thought was dir A actually ran in the root with no indication why. The jail is untouched;effective_cwd_checkednow returns the rejection reason andctx_shellappends 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_searchno longer returns a falseNo matchesfor 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, yielding0 matcheseven though the text was on disk (andget_fresheven 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 optionalLEAN_CTX_SEARCH_INDEX_COALESCE_MS(default0= always verify) coalesces the stat-walk under bursty load on very large indexed trees.ctx_readwas 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 aHostallowlist built from the bound port. When a container binds0.0.0.0:3333but Docker publishes it on a different host port (-p 60000:3333), the browser reaches127.0.0.1:60000and sendsHost: 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 loopbackHostcan't be a DNS-rebinding target, so this is safe, and cross-origin/CSRF is still blocked by theSec-Fetch-Site/Originchecks. Port-remapped loopback publishes now work out of the box withoutLEAN_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_proxyroutes Codex through the proxy,chatgpt_base_urlpoints every/backend-api/*call at lean-ctx — including Codex Desktop's remote-control pairing, which opens a WebSocket to chatgpt.com. The/backend-apihandler only spoke HTTP/SSE (it even stripped theUpgrade/Connectionheaders), so the pairing handshake never completed and remote control stayed broken. The proxy now detects a WebSocket upgrade on/backend-apiand tunnels it verbatim through towss://chatgpt.com, replaying the client's auth plus the shared Cloudflare clearance and relaying every frame in both directions. Opening thatwss://socket needs a process-default rustlsCryptoProvider; because the dependency tree pulls both aws-lc-rs (reqwest) and ring (lettre/ureq),tokio-tungstenitecouldn'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/responsesis 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_searchmatch 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.
signaturescoverage 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 testbenchmeasures answer quality, tokens, turns and walltime across pinned real repos, with a deterministic recorded subset that gates CI and aFINDINGS.md+ regressions report.
- Stable symbol handles (#607). Symbols carry a resolvable, cross-turn
handle (
- Codex ChatGPT-subscription proxy — durable, opt-in model-turn compression
(#603/#616/#621). New
[proxy] codex_chatgpt_proxy(envLEAN_CTX_CODEX_CHATGPT_PROXY, defaultfalse). 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-ctxsetup pins the generatedleanctx-chatgptprovider (model_provider+chatgpt_base_url+ a[model_providers.leanctx-chatgpt]block) so model turns route through the proxy's/backend-api/codex/responsesrail 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'sX-OpenAI-Internal-Codex-Responses-Litemarker so chatgpt.com serves the full Responses stream every model needs (gpt-5.5 was rejected in lite mode); single- and multi-turnprevious_response_idcontinuation verified (#623).lean-ctx doctoris opt-in-aware — the sanctioned rail reads healthy, a half-written pair or anopenai_base_url/backend-api override stays flagged as a stale artifact to heal. Turn it on/off durably withlean-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. ExportingLEAN_CTX_CODEX_CHATGPT_PROXYstill works and is bridged to config onproxy 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_knowledgesurfaces the source's issues/PRs/pipelines without per-call credentials or manualctx_providerruns. Per-connector credentials live only in the privateteam.json(encrypted at rest by the control plane) and are never returned;GET /v1/connectorsexposes 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/usagesnapshot (#283). Themanaged_connectorscount 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 | verifybuilds a snapshot from the live stores, anchors it to the current commit, and stores it on a crash-safe, append-only timeline (index.jsonl);verifyproves 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--gitchecks 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-onlysession … ; snapshot createflow 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 canshow,verify, andrestoreexactly 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 tomemory/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 = falseto trim only the overflow. The target fraction is[memory.lifecycle] reclaim_headroom_pct(envLEAN_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]andctx_knowledge action=restorebring 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(andctx_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.
- Nothing is hard-dropped. Every store — facts, history, procedures and
patterns — evicts through one archive-backed path
(
- Learn-loop enrichment (#980). The gotcha learn-loop now reaches further with
three additions. (1) Multi-target write-back:
lean-ctx learn --applywrites the distilled learnings toAGENTS.md(created if absent) andCLAUDE.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 --minewith 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, sorecallsurfaces them like any other fact (idempotent, capped per pass). - Never-compress path globs (#1150). New
proxy.compress_protecttakes 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*.snapworks anywhere while**/golden/**targets a directory; explicitrawreads andlines: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 orLEAN_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_attributionclassifies 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/statuscache_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 addsproxy/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 withproxy.cache_policy = falseorLEAN_CTX_PROXY_CACHE_POLICY=off. - YAML crusher —
kubectl -o yaml, manifests, CI configs (#985). A newcore/yaml_crushmaps a YAML document onto the JSON value model (yaml_serde) and compacts it through the sharedjson_crushcore: the verbose YAML formatting is dropped and redundantitems/listarrays are factored into_defaults, all behind a_lc_yaml_crushenvelope that round-trips exactly to the parsed value. Wired into the aggressive read path (compressor,ctx_read) for.yaml/.ymland 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); aCondition::YamlCrusharm measures the win. - Columnar crusher for CSV/TSV — reads and shell output (#982). A new
core/tabular_crushrewrites 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/.tsvand 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_droppedwith full CCR recovery (tbl_tee-store prefix,<lc_expand:…>handle). Deterministic (#498) and never inflates. ACondition::TabularCrusharm 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 onfastwhenever the recovery surface changes and on everyfullrun, so the guarantees stay green without slowing unrelated work. - See compression before it ships —
compress diff+ctx_compare(#984). A read-onlycore/compress_previewrenders 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 CLIlean-ctx compress diff <file|-> [--shell "cmd"] [--json]and the read-only MCP toolctx_compare(Debug category). Deterministic and self-describing, so an agent can decide whether a rewrite is worth it. ctx_outlinelevels up — directory outline, deterministic JSON, name filter, verifiable AST backend (gitlab #981). A public review (the ast-grep author, comparing his newast-grep outline) called our outline "fishy" — fair only as a first impression:ctx_outlinehas 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 (matchingast-grep outline src); a directory used to be rejected. Bounded (≤ 600 files, ≤ 1.5 MB/file). - Stable JSON output.
format=jsonproduces byte-stable JSON (#498) — fixed field order, sorted files, no timestamps — for a file or a directory, each file labelled with the extractionbackend(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 withkind=— across file / dir / JSON. - Navigable by default. The text outline now always carries
@Lstart-endline spans (located renderers), so it actually serves "navigate before a full read", as advertised. - Honesty + correctness.
core/signatures.rsgained a module-doc pointing at the tree-sitter primary path; Rustimplblocks render asimpl …, notclass …(both backends); the tool description now states "tree-sitter primary, regex fallback"; deadhandle_via_readremoved. Newextract_signatures_with_backendpowers the per-file backend label.
- Directory outline.
- Active cache-aligner relocate — the opt-in tail-relocate the #940 detector
was the precursor to (#974). With
[proxy] cache_align_relocate(envLEAN_CTX_PROXY_CACHE_ALIGN_RELOCATE) enabled, the proxy rewrites an unanchored Anthropicsystemprompt into a stable block — every volatile value (ISO dates/datetimes, UUIDs, git SHAs) replaced by a constant[ctx#N]placeholder — carrying thecache_controlbreakpoint, 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/statuscache_safetygauges (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 -cprefix guidance (GH #603).install_cline_ruleshardcoded a.clinerulesbody telling the agent to prefix every shell command withlean-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 fromcore::rules_canonical(the single source of truth) like every other dedicated rule file: MCP-first (ctx_*), nolean-ctx -c, wrapped in the canonical markers souninstallcan 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) andopenai_base_url/chatgpt_base_urloverrides in~/.codex/config.tomlto 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,forkand the Desktop picker, thebackend-apibase-URL overrides funnelled Codex's cloud/remote + login traffic through a proxy built only for model turns (breakingcodex cloud/remote), and they made Codex depend on a live local proxy. lean-ctx now writes nothing for ChatGPT auth — Codex talks directly tochatgpt.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/v1proxy rail, where compression actually cuts cost. Upgrading auto-heals: the nextlean-ctx proxy enable/setupstrips the staleleanctx-chatgptprovider and thebackend-apibase-URL overrides, andlean-ctx doctorflags 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 tolean-ctx -c. The allowlist then hard-blocked theevalat 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_wrapperdetects the host scaffold, extracts the real inner command and runs that through the normal allowlist + compression pipeline, so the innergit/cargo/… command is gated and compressed as usual instead of dying on theeval. - cwd tracking preserved: the trailing
pwd -P >| …-cwdsnapshot 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 bareevalthe model itself chose still hits the allowlist's hard block (regression-tested end to end).
- Look through the wrapper: the new
- 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 protectionpreviously added toconfig_io::write_atomicthen hard-blocked every write through such a symlink (refusing to write through symlink), sosetup/initcould no longer register the MCP server or write agent config.- Write through the symlink: the new
resolve_write_targetfollows 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$HOMEis still refused, so a planted symlink can never redirect a config write to a system path. - Opt-in escape hatch for out-of-
$HOMEdotfiles: power users who keep their dotfiles repo outside$HOME(e.g./opt/dotfiles) can now allow specific trusted roots via the newallow_symlink_rootsconfig key (or theLEAN_CTX_ALLOW_SYMLINK_ROOTSenv var). It is empty by default (strict$HOME-only stays the default) and security-sensitive — likeextra_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_dirtolerates a symlinked agent directory (and creates a dangling in-$HOMEtarget), and the Claude/Codex setup steps now surface a clear error instead of silently swallowing a failedcreate_dir_all. The Claude skill also honorsCLAUDE_CONFIG_DIRinstead of a hardcoded~/.claude. - Read-only / cross-FS fallback shared:
config_ionow 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.
- Write through the symlink: the new
- CLI and MCP now always read the same
config.toml(GH #594). When an older release had bakedLEAN_CTX_DATA_DIRinto an editor's MCPenv, 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_DIRequal to the standard$XDG_DATA_HOME/ lean-ctxis 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/updatestrip a staleLEAN_CTX_DATA_DIRfrom the lean-ctx MCP entry across all formats (JSON, Codex TOML, Hermes YAML). - Lossless migration: a
config.tomlstranded 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 toconfig.toml.superseded(never deleted). - doctor: flags
config location — stray config.toml in the data dir, anddoctor --fixunifies it.
- Resolver: a
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-installprints an up-front notice that it builds from source (a contributor workflow that can take minutes) and points end-users atlean-ctx update. When the running binary is behind the latest release, the MCP session andlean-ctx doctornow surface a one-line "run lean-ctx update" nudge — notify-only, never an auto-install (on by default; opt out withupdate_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 defaultembeddingsfeature pullsort(ONNX Runtime), whoseload-dynamicdylib resolver only ships a default library name for windows/linux/android/macos/ios; on FreeBSD thatmatchis non-exhaustive and the build fails insideortitself. lean-ctx already intends to build without ORT (run_inferencehas anot(embeddings)stub), but theort_environment/ort_execution_providersmodules and twoembeddingshelpers (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 abuild-minimalCI 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 rememberpath 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 belowmin_salience(default0= off, lossless) is rejected with a clear reason. Internal restorers (archive rehydrate, cognition auto-promotion) keep using the ungatedremember, 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 leastmin_clusterof 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 ascompacted=on the cognition-loop report. Tunable via[memory.compaction]/LEAN_CTX_COMPACTION_*; runs only in the background loop, never on therememberhot path. - Self-curating memory defaults + actionable capacity guidance (gitlab #969/#972).
prune_unretrieved_after_daysnow defaults to a conservative, recoverable 90 days (was off), so genuinely cold single-confirmation facts are archived instead of accumulating.lean-ctx doctorcapacity 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 are-deliveries forced:line inctx_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 modulecore::read_stub_indexpersists 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), mirroringSessionCache::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-switchLEAN_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_losslesshoists every key present in all items of an array to its dominant value (a_defaultsblock) and keeps only per-item deviations, so it is exactly reconstructible viareconstruct;crush_lossyadditionally records near-unique high-entropy columns (timestamps/UUIDs) in_droppedfor out-of-band CCR recovery. Output is a pure function of the inputValue— no timestamps, counters, randomness, or hash-map order leakage (candidate keys walk aBTreeSet, value frequencies aBTreeMap) — and it never inflates (a no-op returnsNone). 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_jsonconfig key (envLEAN_CTX_CRUSH_VERBATIM_JSON, default off) lets the array-heavy JSON of otherwise byte-verbatim data commands (gh api,jq,kubectl get -o json,curlJSON) 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 touchesVerbatimdata 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_breakpointproxy config key (envLEAN_CTX_PROXY_CACHE_BREAKPOINT, default off) makes the proxy add a singlecache_control: {type:"ephemeral"}breakpoint to thesystemfield 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 clientcache_controland 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 dedicatedbreakpoints_injectedgauge in/statuscache_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_alignerproxy config key (envLEAN_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/statuscache_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.
LoopDetectornow tracksctx_expand/ctx_retrievere-fetches in a dedicated sliding-window counter (retrieve_count, alongside the existing correction counter), exposed as theccr_retrieve_rateanomaly 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 toLite(>=3) thenOff(>=5) for itself. The level is server state that feeds futureCompressionLevel::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::JsonCrusharm in the deterministic A/B eval harness (core::eval_ab) routes JSON/JSONL throughjson_crushinstead of whitespace-only compaction, and a committed JSON-QA fixture (a redundant operator roster with one outlier field) plus the gatejson_crush_condition_preserves_answer_and_beats_baselineprove — 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
/statusandlean-ctx proxy statusnow 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 ownChatGPTlabel 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 breakingfunction_call/function_call_outputpairing (shrink-only, respectsshould_protect). The research-prose squeeze cap is tunable viaLEAN_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
doctorinjected-context linter plus a budget-gated per-session overhead report (#960/#964); ahealthper-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::compressis now crush-backed (gitlab #936). The generic JSON fallback (and thejqroute) prefers the losslessjson_crushform 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 inspectand theawsresource summarizers stay intentionally domain-specific).PATTERN_ENGINE_VERSIONis bumped (1→2) so determinism consumers detect the new output shape.ctx_readaggressive mode compacts JSON structurally (gitlab #936). Reading a.jsonfile inaggressivemode (the auto-resolved mode for large non-code data files) now routes redundant array-of-object payloads through the losslessjson_crushcore 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 afull/rawre-read.mapmode stays a compact structural overview (unchanged). The "must at least halve" gate is centralized injson_crush::{crush_value_if_beneficial, crush_text_if_beneficial}(oneKEEP_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_expandnow 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 thectx_expandslice 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 underjson_<hash>, embedding a content-addressedctx_expandhandle so a dropped datum is never irrecoverable. ctx_searchabsorbsctx_semantic_searchandctx_symbol(#509). Search collapses to a single action-routedctx_search: anactionargument (regexdefault,semantic,symbol,reindex,find_related) routes to the same engines as before, and a missingactionis inferred so existing calls keep working. The two former tools become deprecated aliases — hidden fromtools/listbut 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/.yamlextensions 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-ingestibleIngestKind::Generated, joining the*.lock/*.lockbfiles 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'sfrontend/package-lock.jsonis caught too, unlike a root-anchored ignore glob. An explicitctx_read/ctx_tree/ctx_globof a lockfile is unaffected.
Fixed
- CI on
mainwas red on all threeTestjobs — a stale source-grep test (gitlab #957).scenario_server_degrade_thresholdsasserted the dispatch source literallycontains("correction_count >= 5")etc.; the #941 retrieve-coupled refactor renamed that topressure = 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, totalCompressionLevel::degrade_action(Set/Clear/Leave) extracted from the dispatch — runtime behavior is unchanged (5+ → Off, 3+ → Lite, 0 → clear, 1–2 → 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()seteffective_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 owntask:{CURSOR_TASK_ID}scope (conversation::current_conversation_id), so the stub gate withholds any stub the parent or a sibling delivered (distinct, non-Nonescope → 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 explicitLEAN_CTX_FORCE_FRESH=1still 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 explicitmode=fullre-read; in the defaultautomode 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_readresolvedautowithcache: None, so the resolver's unit-testedunchanged + full_delivered → ("full","cache_hit")short-circuit was dead code on the real read path (a silent divergence fromctx_smart_read, which threaded the cache correctly; introduced by the #683 deterministic cascade).resolve_auto_modeis now cache-aware, the warm path routes anauto→fullcache-hit through the sametry_stub_hit_readonlystub as an explicit full re-read, and the registered read-lock fast path acceptsautotoo (self-guarded by the stub). Compressed- first files still serve their cached compressed output on re-read — no wrong escalation to full. Regression testauto_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 readSessionCacheis 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 receiveFref=path [unchanged NL]for content it never saw (the idle-TTL clear only incidentally masked it). Each entry now records thedelivered_conversation(resolved from the live Cursorconversation_idthat hooks write toactive_transcript.json), andtry_stub_hit_readonlyserves 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-switchLEAN_CTX_CONVERSATION_SCOPE=0. ctx_impactmissed 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_edgesnow resolves Go usages directory-scoped and strict (a common name likeConfig/Serverdeclared 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 thegraph_indexmirror and emitted by thectx_impactbuilder. The old coarse Gopackageheuristic — one arbitrary same-directory edge per file, silently parsed as a top-weightimportsedge in the mirror — is removed: it both missed the real consumer and pulled non-consumers (e.g. an unrelatedlogger.go) into the blast radius. Precisetype_refedges 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 oneresolve_scope(previously the namespace logic was duplicated across three call sites).GRAPH_ENGINE_VERSIONis 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 CLIcallpath (#583). Invoking thectx_toolsmeta-tool from the CLI crashed with "there is no reactor running" because the runtime was resolved viaHandle::current(), which only exists on the MCP path (handlers there run insideblock_in_place). It now usesHandle::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_shellcould 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 updatefailed withUnknownIssuerbehind TLS-inspecting proxies (#578). The updater now validates TLS against the OS trust store via ureq'sPlatformVerifier, so corporate roots installed in the system keychain/store are honored.gain --deepreported "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 alean-ctx exploreCLI — 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-stablepath:start-endranges (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_harnessgains aSearchArm::Explore(output-token metric plus new "exploration" queries inrust/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_envnow 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-apifor aux calls such as the codex_apps streamable-HTTP MCP endpoint); API-key Codex keeps the/v1path. The proxy gained/backend-api/codex/responses(compressed/metered via the OpenAI Responses path tochatgpt.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 doctorwarns 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-treectx_readfails with "path escapes project root". The newMCP server CWDdoctor check (also surfaced in the structured health report) explains the cause and the fix (cwdin the client config, orallow_auto_reroot/extra_roots);.lmstudiois 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-shotlean-ctx read/grepsubprocess dropped it — soctx_proofand IR exports were blind to compressed shadow reads.record_file_read/record_searchnow thread the rendered-output excerpt + measured tool duration into a disk-backedContextIrV1load→record→save, mirroring the MCP path (same 200-char char-boundary excerpt bound;mode/patternride the IRpatternslot; the read'soriginal_tokensand 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-modelean-ctx read/greproutes the call through it (/v1/tools/call→call_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 indaemon_client::try_daemon_tool_call_blockingand regression-guarded byhook_connect_only_566. - PowerShell-native cmdlets route through lean-ctx (#561). Follow-up to #556:
shadow/harden mode already recognised the Windows
powershellshell 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/gc→lean-ctx read(honoring-Path,-TotalCount/-Head/-Firstand-Tail/-Last),Select-String/sls→lean-ctx grep(-Pattern,-Path), andGet-ChildItem/gci→lean-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.verifiedflag splits the catalog into verified (maintainer-audited) and community, shown inaddon list/infoand the install preview.core::addons::trust::assessstatically 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 bycargo 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::gateenforces it ininstallbefore 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 = trueit is honoured only if a sidecaraddon_registry.json.sigcarries a valid Ed25519 signature by a trusted org key (same anchor aspolicy org trust). - Opt-in OS sandbox.
addons.sandbox = auto|strictwraps spawned stdio servers insandbox-exec(macOS) /bwrap(Linux) at the single spawn point — outbound-network isolation inauto, read-only fs + refuse-if-no-launcher instrict. 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 modulescore::addons::{trust,policy,signing,sandbox, runtime}; binding registry-review checklist inCONTRIBUTING.md.
- Trust tier + risk review. A registry-controlled
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/leaderboardnow paginates (?page,?per_page, default 50 / max 200) and supports case-insensitive name search (?q=), while two new fields —total_tokens_savedandtotal_cost_avoided_usd— report the uncapped community totals across all opted-in accounts, independent of the displayed page or any filter. The server-rendered/leaderboardpage and the website/metricspage 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_impactdropped C# same-namespace blast radius after the first reindex (#398). A C# class used within its own namespace (nousing, DI-injected) reappeared as a leaf node after the first background reindex. The privatectx_impactbuilder wrote precisetype_refedges into thePropertyGraph, but everyProjectIndex::save()mirrorsgraph_indexover the graph viaclear_code_graph()— andgraph_indexemitted no type-usage edges, so the reindex silently wiped the blast radius (a dual-writer bug). A newcore::type_ref_edgesmodule is now the single source of truth for C#/Java consumer→definer file resolution (namespace-aware, failsafe-capped), shared by both the durablegraph_indexmirror and thectx_impactbuilder;graph_indexnow 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_VERSIONis 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 onlyoutput_mode=content, passingfiles_with_matches/countthrough untouched, since the path-swap returned wrong results for those.) (gitlab #915–#918)ctx_readleft an empty[]metadata field on incompressible files (#509). Theentropy(anddensity) 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 bareH̄=4.2 []— the same empty-trailing-field waste fixed forctx_semantic_search's(rrf: X, )in #511. Atechniques_taghelper 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.mdadvertised renamed tools that no longer exist (#548). The Pi installer writes a curated statictemplates/PI_AGENTS.md, and its tool-mapping table still listedctx_grep/ctx_find/ctx_ls— tools renamed long ago toctx_search/ctx_glob/ctx_tree. Pi agents that followed the table issued unknown-tool calls. The template (and the matchingpi.rssetup 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: anyctx_*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, andrules.toml↔syncsemantics 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 -->againstRULES_VERSIONand skipped the rewrite when they matched. So a change that alters the rendered body without bumping the version — togglingshadow_mode, switchingcompression_level, or editing a canonical section between releases — left every agent's rules block stale until the next version bump.RulesFile::block_matches_rendernow compares the on-disk block byte-for-byte (whitespace-insensitive) against a fresh render for the active parameters, and the skip path requires bothis_current()and that content match. Re-runningsync/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.tomlsemantics, and arules difffalse-positive (#548). Two coupled fixes for the rules-governance commands:sync/diffdo not consumerules.toml— now documented and decoupled.rules sync/diffregenerate from the canonicalrules_canonicalsource of truth (preserving user text around the markers) and never readrules.toml, which is the input forrules lintplus a user-editable inventory fromrules init. This is now stated in theruleshelp, theinitnext-steps, and theRulesConfig/syncdocs.detect_driftno longer loadsRulesConfigat all, sorules diffworks without first runningrules init(it previously failed with "No rules config found") — the dead_configparameter is gone and the command is infallible.rules diffreported 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 asDRIFTEDon every run. Drift now compares each target against the canonical block for its realRulesFormatvia the newrules_inject::expected_blocks_by_target, keepingsyncanddiffin agreement. Covered by new tests:detect_drift_without_rules_toml_does_not_ require_initandsync_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::renderembedded 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_compressionwere 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), andrules dedupcould not thin a render-produced sharedAGENTS.md. The two models are now one: theCOMPRESSION_BLOCK_*markers live inrules_canonical(single marker source, re-exported fromrules_channel), andrenderwraps the compression prompt in them for the persistent carriers (Dedicated/Shared— every injected rule file). The ephemeralBareMCP 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 nextsyncwithout a version bump. Covered by new tests asserting carriers wrap /Baredoes not /Offemits nothing, and an end-to-end check that arender-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/grepcall it spawnslean-ctx readas a single-shot subprocess. That CLI path recorded only a fraction of what the MCPctx_readpipeline does, and — crucially — never flushed its buffered telemetry before the process exited, solean-ctx heatmapstayed empty andlean-ctx gainreported 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 (thereadarm flushed onlystats), which is exactly how the gap went unnoticed. - CLI read learning parity.
record_file_read/record_searchnow 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.
ModePredictorstored its history in a struct-keyedHashMap<FileSignature, _>, whichserde_jsoncannot serialize ("key must be a string") — somode_stats.jsonwas 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 throughctx_read::handleare tracked as a follow-up (they cannot be honored from a single-shot subprocess without cross-process state).
- One flush set, no drift. A new
- Windows PowerShell profile path hardcoded to
~\Documents— broke under OneDrive redirection (#558).proxy enableand the shell-hook install resolved the PowerShell profile by hardcodinghome\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$PROFILEwas never updated and the proxy received no traffic in new terminals. A newresolve_powershell_profile_pathasks PowerShell itself for$PROFILE.CurrentUserCurrentHost(authoritative under any folder redirection, preferringpwshthen 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/powershellpath and never spawn a process (#356). - Copilot CLI
view(read) andrg(search) tool calls passed through uncompressed (#562).handle_redirectdispatched on the tool name but only matchedRead/read/read_fileandGrep/grep/search/ripgrep, so two documented GitHub Copilot CLI tool names —view(its read tool) andrg(its search alias) — slipped through without compression in shadow/harden mode. The dispatch is now a testedclassify_redirecthelper that includesview(→ read) andrg(→ 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 copilotinstalled the MCP server plus a deliberately weakAGENTS.mdpointer 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.initnow writes the strong dedicated ruleset into.github/copilot-instructions.mdas an idempotent<!-- lean-ctx-rules -->block (user content is preserved, never clobbered) and pinsgithub.copilot.chat.codeGeneration.useInstructionFiles: truein the project.vscode/settings.jsonas a safety net (an explicit user value is honoured); uninstall removes the block. - Shadow mode ignored
globand Windowspowershelltool calls (#556). Shadow/harden mode silently passed two documented Copilot CLI tools straight through: theglobtool ("find files matching patterns") had no arm in the redirect hook, and thepowershellshell tool (paired withbashon Windows) was not recognised as a shell, so command rewrites never fired there.handle_redirectnow interceptsGlob/glob— warming the sharedctx_globcore via a newlean-ctx globsubcommand and recording the intercept inshadow.log, then letting the native path-list result through —is_shell_tool(now shared by both hook entry points) coversPowerShell/powershell/pwsh, and the Claude/Cursor/CodeBuddy redirect matchers includeGlobso the hook fires for it. Copilot CLI already dispatches every tool, so itsglob/powershellcalls are covered automatically. - Codex proxy never compressed — ChatGPT login bypasses it; the API-key config
was a no-op (#554).
lean-ctx proxy enablereported success for Codex yetRequests/Compressed/Tokens savedstayed at0, for two reasons. (1) A Codex ChatGPT login (the default) authenticates via OAuth directly againstchatgpt.com/backend-api, so a customopenai_base_urlis 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.jsonauth_mode = "chatgpt", overridable by an explicitOPENAI_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_URLinto~/.codex/config.toml, which Codex does not read; it now writes the documented top-levelopenai_base_urlkey (openai/codex#12031), migrates the dead legacy entry, and preserves any custom remote endpoint. Uninstall/cleanup/preview handle both forms. lean-ctx index build-semanticcold-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 checkedis_available()(a pure file-existence check) and bailed before the download could run — a regression from the #519 ORT-teardown guard.build_or_updatenow downloads the model first via a newEmbeddingEngine::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 camelCasetoolName+toolArgs(a JSON-encoded string), but the rewrite/redirect/observe handlers only read snake_casetool_name/tool_input/command, so every Copilot tool call passed through unchanged; even once parsed, thepreToolUseoutput omitted Copilot'smodifiedArgsfield, so rewrites/redirects would never have taken effect. A newhook_handlers::payloadresolves the tool name, args (atool_inputobject, atoolArgsobject, or atoolArgsJSON-string) and command across all handlers,observegains a CopilotpostToolUsebranch so its telemetry is recorded instead of dropped, and the hook now emits Copilot's documentedpermissionDecision+modifiedArgscontract alongside Claude'shookSpecificOutput.updatedInputand Cursor'supdated_inputin a single response. Snake-case (Claude/Cursor) stays regression-tested. Thanks for the detailed report. CLAUDE.md/CODEBUDDY.mdpointer block duplicated on everysetup/doctor --fix(#549). The block-detection constants pointed at the wrong marker:*_MD_BLOCK_START/ENDreferenced the canonical rules marker (<!-- lean-ctx-rules -->) while the installer writes the AGENTS pointer block (<!-- lean-ctx -->), soexisting.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 atAGENTS_BLOCK_START/END(one fix for both the doctor false-negative and the duplication), a newremove_all_blocks()collapses any already-accumulated duplicates back to a single canonical block in the installers andstrip_*_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 smalllean-ctx-addon.tomlmanifest and plugs into the MCP gateway with onelean-ctx addon add— no fork, no recompile.list/search/infobrowse a curated registry (bundledrust/data/addon_registry.json, overridable per entry via<data_dir>/addon_registry.json);addresolves a registry name or a local manifest path, discloses the exact transport/command/args/env it will run, then — after confirmation (--yesto skip; refuses non-interactively without it) — wires a[[gateway.servers]]entry via the safe global-onlyConfig::update_globalpath and records it in<data_dir>/addons/installed.json;removeunwinds 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 thecli::promptconfirmation gate; no new config section, so schema parity is untouched. Manifest, registry and install logic live in small, unit-testedcore::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 viapathutil::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.effortis left off — it is never inferred from a repo). Prints the exactexport/config setcommands to apply it, plus task-oriented alternatives (ci-debugfirst when CI is detected, thenhotfix/bugfix/review). Strictly read-only — it never writes config.--jsonfor 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 theyolo/securepattern, and refusing to run non-interactively without--yes. Coverslean-ctx config setfor security/egress-relevant keys (path_jail,shell_security,sandbox_level,secret_detection.*,boundary_policy,proxy.*_upstream) andlean-ctx knowledge rememberwhen 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 singlecli::promptmodule (extracted fromsecurity_cmd), and config-key risk classification lives incore::config::risk— deterministic and local-only. The MCPctx_knowledgetool 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-toollast_usedrides the cost-attribution write that already happens. Text (rot candidates only;--allfor the full list),--jsonfor 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 — OpenAIreasoning_effort/reasoning.effort, Anthropicoutput_config.effort, and GeminithinkingConfig(thinkingLevelon 3.x,thinkingBudgeton 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:offis 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 working200into a400) and deterministic.lean-ctx proxy statussurfaces the active level plus per-provider steer counts. Set viaproxy.effortor theLEAN_CTX_PROXY_EFFORTenv (env wins). - Unified security posture +
lean-ctx yolo/securemaster 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 statusprints a posture board (and a coarse STRICT / RELAXED / OPEN label) reused bylean-ctx doctor, which now also shows a dedicated Secret redaction line.lean-ctx yolo(aliassecurity open) drops containment in one step — writespath_jail = false+shell_security = "off", takes effect immediately, and deliberately keeps secret redaction on;lean-ctx secure(aliassecurity strict/lockdown) restores the secure defaults. The standalone.envswitch islean-ctx security secrets <on|off>.path_jailis now a first-class, schema-documented config key (the blanket "any path" opt-out, equivalent toallow_paths = ["/"]), so granular re-enabling vialean-ctx config set …/lean-ctx allow <cmd>composes cleanly after ayolo. 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 normalremember()path, so versioning, persistence and idempotency come for free and the value stays byte-stable (#498). An optional LLM refinement sits behindllm.enabledwith 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) viainfer_from_category, feeding salience and — opt-in viaarchetype_aware_decay— slower decay for structural evidence. Gated bycognition_loop_max_steps >= 9(the new default; set 8 to disable); visible asobservation_synthesisinlean-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 MCPctx_shelland the CLI (lean-ctx -c/-t) behave identically.enforcestays the secure default;warnruns every check but only logs violations;offis a deliberate opt-out that skips gating entirely while compression stays fully active. Set viashell_securityin config or theLEAN_CTX_SHELL_SECURITYenv (env wins; unknown values fall back toenforce, never fail open).offdoes not lift the read-only-output doctrine (no>/tee/heredoc writes via shell).lean-ctx doctorsurfaces the active mode whenever it is notenforce. Supersedes the CLI-onlyLEAN_CTX_ALLOWLIST_WARN_ONLY(kept for backward compatibility). /v1contract 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 staysleanctx), npm, and crates.io. Replaces docs that pointed at an unrelated third-partyleanctx/ unpublished@leanctx/sdk(GL #783). A dedicated, idempotentpublish-clients.ymlworkflow 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 inlean-ctx doctor). All decision layers are deterministic by default (Rule #498 / prompt cache intact); stochastic exploration is gated behindLEAN_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 stabilitySgrowing per retrieval, replacing linear decay. Configurable viaforgetting_model(ebbinghaus|linear),base_stability_days, andLEAN_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).
- Time-variant Φ (attention). Context salience is recomputed and EMA-blended
on every re-read instead of being frozen on first sight (
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 theLEAN_CTX_DEBUG_LOGenv (truthy) orlean-ctx config set debug_log true; read or clear it withlean-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 calledcheck_shell_allowlist, so every aliased invocation like_lc git statusbypassed the restriction thatlean-ctx -cenforces. Both paths now share a singleallowlist_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_shellchildren (external audit, finding 2). The agent-runtime-env bridge forwarded everyCODEX_*/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_forwardablenow excludes credential-shaped names (only session/thread identifiers cross the bridge), andloadretroactively 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, theno-jailbuild 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, andlean-ctx doctorreports env-channel relaxations alongside the config-level ones. - Workspace Trust for project-local
.lean-ctx.tomloverrides (external audit, finding 4). A cloned repo's.lean-ctx.tomlis 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, changerules_scope/rules_injection. For an untrusted workspace those overrides are now withheld (comfort knobs likecompression/themestill apply) with a[SECURITY]warning;lean-ctx doctorshows the state. Grant trust withlean-ctx trust(andlean-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 viaLEAN_CTX_TRUST_WORKSPACE=1orLEAN_CTX_TRUSTED_ROOTS.
Changed
- Change-aware pre-push gate + no-test advisory (#850/#849).
scripts/preflight.shnow classifies the diff againstorigin/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 --checkstill runs whenever Rust or a committed file underdocs/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 preflightforces 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 underLEAN_CTX_PREFLIGHT_STRICT_TESTS=1. - Faster semantic search on a native ONNX Runtime (#497). The
embedding/index stack moves from the pure-Rust
rtenbackend to nativeort(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'sload-dynamic), resolved across platforms fromORT_DYLIB_PATH, Nix profiles, and well-known system locations — so it is provided once by the platformonnxruntimepackage (declared as a dependency in the Arch/Homebrew packages),pip install onnxruntime, or a manualORT_DYLIB_PATH. Theortcrate 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). Thejina-code-v2built-in (pre-existing broken) is removed; code-specialized embeddings remain available through thehf: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 bypassrenamed tolean-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 bypassstays as a back-compat alias; model-visible hints now userawand 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 singlectx_read(multi-path + auto mode); the former tool names stay as deprecated aliases that still work but no longer cost schema tokens intools/list, shrinking the always-on surface. Internally,ctx_readmodes are now a type-safeReadModevocabulary (parsed once,FromStr/Display) instead of ad-hoc strings, with behavioural-equivalence tests and the eval A/B gate guarding zero output regression.SessionCacheis retained (the decoupling thesis was evaluated and rejected as net-negative). - Configurable
ctx_shelltimeouts + opt-in writes (#526 / #523, #529). The hard-coded 2-min / 10-min shell ceilings are now tunable viashell_timeout_secsandshell_heavy_timeout_secs(envLEAN_CTX_SHELL_TIMEOUT*), and the read-only output doctrine can be relaxed deliberately withshell_allow_writes(envLEAN_CTX_SHELL_ALLOW_WRITES) so a trusted operator can permit>/tee/heredoc writes throughctx_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 setnow accepts every validOptionconfig key (persona,bypass_hints) instead of rejecting them as "Unknown config key" (#856).config setresolves keys via the hand-written schema (ConfigSchema::lookup) only. AnOption<_>scalar field defaults toNone, so serde omits it fromConfig::default()and it never appears inconfig_derived_keys()(which feeds onlyconfig validate/apply). Any such field that wasn't hand-registered was therefore accepted fromconfig.tomlbut rejected byconfig setand flagged "unknown" byconfig validate— the class behind thepath_jailreport (fixed earlier in #507). Auditing all 17 root-levelOptionfields found two more:personaandbypass_hintsare now registered in the root schema (personaas an openstringso custom<name>.tomlpersonas stay valid;bypass_hintsasenum(on|off|aggressive)soconfig setvalidates the value). A new regression test (option_scalar_keys_are_cli_settable) asserts theOption-scalar knobs resolve via schema lookup, guarding the whole class.lean-ctx -cno longer kills hook-runninggit commit/git pushat the 2-minute default (#854). The shell wrapper enforcesDEFAULT_TIMEOUT(2 min) on ordinary commands andHEAVY_TIMEOUT(10 min) on build/test commands, butgit commit/git pushwere treated as ordinary — even though, in a repo with hooks,git commitfans out intocargo clippy(pre-commit) andgit pushinto the fullscripts/preflight.sh(pre-push), each of which routinely runs 3–10 min. The wrapper SIGKILLed git mid-hook, leaving the tree staged-but-uncommitted or the push half-done.is_heavy_commandnow classifiesgit commitandgit pushas heavy (10-min ceiling, 32 MB buffer); read-only verbs (git status/log/diff) stay on the default ceiling because matching is on the fullgit <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(ordense) 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 viaLEAN_CTX_HYBRID_INLINE_EMBED_MAX;0disables — the pre-#512 behavior), refuses the inline embed across all four search entry points (hybrid/dense × the MCP tool and the CLI/editorsearch_hitspath): 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 compactgit log --onelinestays verbatim — but it skipped the token guard the other paths use. A tiny adversarialgit statusbody could reshape into a one-token-larger summary, breaking thecompress_output_never_inflates_tokensproperty 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 volatilecache_controlmarker, 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. gainno longer reports0saved 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 astats.json. When an agent host launches the lean-ctx MCP server with a differentHOME/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 whenLEAN_CTX_DATA_DIRpins 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 thatstats.jsondoes 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, sincegainmeasures 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-ctxpath (#518 / #521). The terminal-integration shell hook used a Unix-style/c/...path that PowerShell and cmd can't execute, solean-ctxinvocations 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_ACCESSduring staticOpSchemateardown 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_ACTIVEno longer silently disables compression (#533 / #537).LEAN_CTX_ACTIVEserved 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 dedicatedLEAN_CTX_WRAPPEDmarker, so an inheritedLEAN_CTX_ACTIVEno longer turns compression off. ctx_read raw:true/mode=rawnow honored and documented (#513 / #514). The verbatim escape hatch was silently ignored on theraw:trueargument and undocumented formode=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_extrafailures 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.tomlwhose security-sensitive overrides are withheld because the workspace is untrusted now names the ignored keys and thelean-ctx trust/ global-config remedies; (2) when the runtime resolves a globalconfig.tomlthat 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-onlytracing::warnwas 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 Rustdata_dir. After the #408 path refactor,LeanCtxPathstreatedconfig.tomlas a data marker and fell back toXDG_CONFIG_HOME, so the plugin wrote the port file under~/.config/lean-ctxwhile the Rust reader looks under~/.local/share/lean-ctx. The file was never found (BACKEND_REQUIRED), disabling every IDE-sidectx_*action. Data-dir resolution now mirrors the Rust implementation (single-dir override, layout pin, data-only markers withconfig.tomlexcluded), with regression tests for fresh installs, mixed configs and XDG pins. Thanks @dasTholo. lean-ctx uninstallnow 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), becauseremove_data_dirresolved throughdirs::data_dir(), which collapses onto~/Library/Application Supporton macOS. Uninstall now callsupdate_scheduler::remove_schedule()and resolves every XDG category throughcore::paths(honoringLEAN_CTX_*_DIR/XDG_*), with a regression test that asserts every canonical directory is covered exactly once.- Onboarding command box now shows
LEAN_CTX_DISABLED=1instead of the non-existentlean-ctx off/ontoggle. The box advertised subcommands that don't exist (they fail with "unknown command"); the real global switch is theLEAN_CTX_DISABLED=1environment variable.
[3.8.10] — 2026-06-20
Fixed
- #462 / #474 — restricted shell mode no longer rejects
for/while/ifloops,caseblocks 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 --helpno longer performs a real uninstall. The--help/-hflag fell through to the uninstaller, which removed the installation instead of printing usage. The CLI now short-circuitsuninstall --help/-hto 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, andlean-ctx update/dev-installregenerate the plists with that wrapper. The remaining hole was a user who only runsbrew 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-~/DocumentsSeatbelt at startup if it is not already wrapped (reexec_under_seatbelt_if_needed, called first thing inmain). 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 existingtcc_sandbox.shSIGKILL-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 -cno longer run agent commands in a non-POSIX interactive shell.$SHELLis the user's interactive shell; when it is Nushell, Fish, Elvish, xonsh or PowerShell, an agent's bash/POSIX command silently mis-executes.detect_shellnow honors$SHELLonly 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_SHELLstill forces a specific shell regardless of the gate. - Shell gotcha auto-learning now correlates fail→fix in CLI (
lean-ctx -c) mode.pending_errorswere#[serde(skip)]and cleared on load, so a fix spanning two separatelean-ctx -cprocesses never correlated (only the long-lived daemon could). They are now persisted (bounded byMAX_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 exportprojectcolumn 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. Newcore/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; endpointspolicy org trust <pubkey>(pin once, out-of-band) andpolicy 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_toolsunion,allow_toolsintersect,redactionunion (org patterns win clashes), the stricter filter action, the tighter egress/max_context_tokenscaps, the longeraudit_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).--advisorydistributes a policy for preview without enforcing it;policy org statusshows the effective floor andpolicy org verifychecks an artifact offline. Pluggable source (LEANCTX_ORG_POLICY/LEANCTX_ORG_TRUST_KEYfor MDM). Newcore::policy::org+core::policy::floor; contractdocs/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'shead_hashbound into the signed payload), and the retention posture (packaudit_retention_daysintent vs. plan entitlement). The signed JSON is always written and is offline-verifiable withlean-ctx compliance verify <report.json>(no audit trail, no LeanCTX needed);--format csv|pdfadditionally 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. Newcore::compliance_reportmodule; contractdocs/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 ofctx_editwrites andctx_shell/ctx_executeactions — so a blocked write never touches disk and a blocked command never runs.forbidden_patternsare regexes that refuse a write/action on match (e.g. a prod-DB DSN or a destructive query);block_secretsrefuses content carrying detected secrets (the pack's[redaction]patterns) or PII (the #675 checksum-validated detectors);max_writes_per_minis 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_patternsaccumulate and the scalars override down theextendschain. Newcore::egressmodule. - #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:piifinds Swiss AHV (EAN-13), IBAN (mod-97), payment cards (Luhn) and email, each checksum-validated to keep false positives low;classificationgates files marked confidential/secret (banner lines or aClassification:field, not prose mentions;blocked_labelsis configurable);injectionmasks/blocks OWASP-LLM01 prompt-injection lines (reusingoutput_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 andblocked_labelsaccumulate down theextendschain. Newcore::input_filtersmodule. - #673 — context policy packs are now enforced at runtime. A project pack
(
.lean-ctx/policy.toml, authored from any built-in vialean-ctx policy show <name> --toml) is applied at the MCP hot path:deny_tools/allow_toolsgate which tools the agent may call (denied calls return[POLICY DENIED]and are audited asToolDenied),[redaction]patterns strip matches ([REDACTED:<name>]) from tool output before it reaches the model,default_read_modesets thectx_readfallback when the caller omitsmode, andmax_context_tokenstightens (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. Thectx/ctx_session/ctx_policymeta tools are never gated, so a pack can never lock the operator out. - #454 —
prefer_native_editorconfig to opt out of lean-ctx edit operations. Setprefer_native_editor = true(orLEAN_CTX_PREFER_NATIVE_EDITOR=1) so the lean-ctx edit tool (ctx_edit) is neither advertised inlist_toolsnor dispatchable (direct or viactx_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_compactcore tool — lean-ctx can now be Hermes Agent's active context engine, not just an MCP server it might call. The newintegrations/hermes-lean-ctxplugin is a thin PythonContextEnginethat replaces Hermes' built-inContextCompressor: 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 atool_call/tool_resultpair and offloads the raw turns into session memory. The plugin prefers this core tool over/v1and falls back to local Python compaction when the daemon is unreachable, so the agent loop never breaks. Includes session-lifecycle persistence (resumeon start,ctx_summary+ctx_handoffon end), model-window presets, a runnable head-to-head benchmark harness (vs. import-guardedContextCompressor/hermes-lcm), and a dedicated CI job (pytest + offline benchmark smoke).lean-ctx init --agent hermesnow 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 theGotchaStorecould 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 capture —
shell::exechands every finished command togotcha_tracker::record_shell_outcome, gated by a cheapis_correlatable_commandfilter (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 thepending_errors(which are#[serde(skip)]) alive across commands inside a long-lived daemon, mirroringdiagnostics_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 duringctx_compressvia the existing dedup/stable-IDadd_delta. - Offline mining —
lean-ctx learn --mine <dir>scans a directory of.jsonltranscripts/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 Ledger —
lean-ctx gotchas ledgerrenders a human-readable summary (errors observed, fixes correlated, repeats avoided, promoted to knowledge) plus the distilled strategies/pitfalls, making the learning visible.
- Live capture —
- 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.remembernow 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 canjudge/merge them. Self-matches and already-judged pairs are excluded; the embedding path is behind the defaultembeddingsfeature.
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-ctxprocess. Asampleof the live process showed theleanctx-indexthread burning ~100% while every other thread (tokio workers,memory-guard, main) sat parked incond_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 6–9 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_treenow 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
servekeeps a one-time background warm-up — only the persistentlean-ctx serveprocess (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 into504 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
Noneas a true EOF, so one malformed JSON-RPC message tore down the server (rmcpQuitReason::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 unchangedconfig.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).
- 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
- 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 validateonly 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.tomloverride (compression_level/terse_agent/tool_profile), a divergent resolved config dir (dashboard writes path X, runtime reads path Y), or an unparseableconfig.tomlfalling back to defaults. Fixed by making the provenance explicit and the path stable:config validateshows the source — it now always prints the resolvedconfig.tomlpath (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/settingsreturnsconfig_path,config_exists,parse_errorand a per-settinglocal_override; the Quick Settings panel shows whichconfig.tomlis read and warns (and disables the toggle) when an env var or a project-local.lean-ctx.tomlis winning. - Dashboard pins the layout —
lean-ctx dashboardnow runs the samelayout_pin::heal()as the daemon/server start paths, so it can no longer writeconfig.tomlinto 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 firedloadData()fromconnectedCallback()at once — a thundering herd of/api/graph,/api/call-graph,/api/symbols,/api/search-indexand/api/treerequests that ran synchronous, file-count-scaling index/graph builds and starved the trivial/api/settingshandler 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/settingsissues a single/api/settingsrequest instead of triggering every panel's data load at once. - Backend single-flight + non-blocking (hardening) —
graph_indexandbm25_indexgained aget_or_start_buildcoordinator (one background build per root, concurrent callers deduplicated) modeled oncall_graph. Heavy routes (/api/tree,/api/symbols,/api/call-graph,/api/search-index,/api/search) now return202 {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.
- Frontend lazy-load (primary fix) — components no longer load in
ctx_shellis 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), makingctx_shelllook like a native interactive bash shell. It now renders an explicitctx_shelllabel. - Profile-free shell —
ctx_shell(MCPexecute_command_with_env) and the CLIlean-ctx -cpaths now neutralize inheritedBASH_ENV/ENVso a non-interactivesh -c/bash -ccan no longer be hijacked into sourcing a profile/rc file (e.g. anexec nusnippet 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.
- Pi renderer — the Pi extension rendered shell calls with a bare
- Proxy upstream is now live from
config.toml— no more stale upstream on a long-lived proxy (#449) — the proxy froze its provider upstreams inProxyStateat startup and never re-read them, so a laterlean-ctx config set proxy.openai_upstream …(or anyconfig.tomledit) had no effect until a manual restart — and a shellexport 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.tomlevery ~5s (LEAN_CTX_PROXY_RELOAD_SECSto tune) and publishes any change through atokio::sync::watchchannel that every provider handler reads per request, soconfig settakes 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.tomlis the source of truth for long-lived proxies; aLEAN_CTX_*_UPSTREAMenv 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 omitsLEAN_CTX_*_UPSTREAM, so the proxy it spawns never sees it —config.tomlis 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'sconfig.toml(live reload had nothing to read) and derived a mismatched session token (its/status401'd). The proxy/daemon LaunchAgent plists now bake in the exactHOME+LEAN_CTX_{CONFIG,DATA,STATE,CACHE}_DIRthe installing CLI resolves, so a managed process always agrees with the CLI. - Observability —
/statusandlean-ctx proxy statusnow report the active upstreams;proxy statusderives liveness from the public/healthendpoint (so a running proxy is never misreported as down) and warns in two cases: aLEAN_CTX_*_UPSTREAMset in the shell that never reached the proxy (with the exactconfig setcommand to persist it), and a proxy started with an env override now masking a laterconfig.tomledit.doctorcarries the same drift check. lean-ctx proxy restart— new subcommand that cleanly restarts the managed service (re-readsconfig.toml, drops any start-time env override).
- Live reload — a background task re-resolves the upstreams from
ctx_impactresolves 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 newdeep_queries::ext_methodsextractor collectsthis-parameter methods, andctx_impactlinks eachvalue.Foo()call to the defining file (file + symbolTypeRefedge), self-filtered and capped. - Namespace-aware resolution (#641) —
TypeDefnow carries its C# namespace (block-, file-scoped and nested), andtype_ref_targetsresolves 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 ontree-sitterso they exercise both. Outputs stay deterministic (sorted/deduped, bounded indexes; #498).
- Extension methods (#642) — a call
ctx_impactnow 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 noTypeRefedge, soctx_impactreported the defining file as a false-negative leaf. Now covered indeep_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…Attributeclass 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 ontree-sitterrather thanembeddings, so it also exercises theindex_graph_file_minimalbuilder 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 --fullno longer reset or leak config values (#443) — persisting a single setting could silently rewrite other customized keys in the globalconfig.toml(e.g.compression_level→lite,max_ram_percent→ 5). Three root causes, now closed by construction:- (A) default-seed clobber —
config init --fullhistorically wroteConfig::default(), andsave()overwrites every key present in both the incoming document and the file (config_io::merge_table), resetting customized values. (Already mitigated viaconfig_for_full_init; now superseded.) - (B) project-local leak —
Config::load()folds project-local.lean-ctx.tomloverrides into the in-memory struct, so the commonload() → mutate → save()pattern (18 call sites across 10 files) wrote those per-project values back into the global file. - (C) corrupt-file clobber —
write_toml_preserving_minimalwrote 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) andConfig::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_minimalnow refuses to overwrite an unparseable config instead of clobbering it, andconfig init --fullemits a fully annotated reference document seeded with the user's current values (lossless round-trip, independent of schema completeness).
- (A) default-seed clobber —
- 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 emptysessions/) silently re-collapsed config/data/state/cache onto that one directory viasingle_dir_override, after whichconfig.tomlwas 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-ctxfor a pinned install. The pin is written (and a residual~/.lean-ctxauto-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), anddoctor --fix(after it migrates + reclaims). Marker detection was hardened so an emptysessions//graphs/directory (or a zero-bytestats.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).doctornow reports the active layout mode (xdg-pinned/single-dir / legacy). - Re-reads stop blowing up to full content (cache hit-rate regression) — with
modeomitted (the recommended usage), a file first read in a compressed mode (map/signatures) was resolved tofullon its second read by thecache_hitshortcut, 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 tofullonce 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. Explicitmode="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-awarehistory pruner rewrote already-cached history every time the prune boundary advanced aSTRIDE(~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'scache_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 withoutcache_control(e.g. OpenAI) are byte-for-byte unaffected. ctx_retrieve/ctx_shareno 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-agentctx_sharehandover — 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_readwas 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) —updatenow 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;--checkreports 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.mjsproves, before any priced run, that the pi arm routes shell throughctx_shell(nativebashsuppressed) and actually compresses it — the "green preflight = running as designed" gate the tokbench reviewer asked for, ruling out R1's102 native bash / 0 ctx_shell. The shell-suppression decision is now the single, unit-tested invariantresolveSuppressedBuiltins(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, butvalidate_upstream_urlrejected every non-loopbackhttp://URL with a misleading "must use HTTPS" error and no escape hatch. A trusted plaintext upstream is now allowed viaLEAN_CTX_ALLOW_INSECURE_HTTP_UPSTREAM=1or[proxy] allow_insecure_http_upstream = true; the startup banner anddoctorflag the plaintext hop so it stays a conscious choice. Documented end-to-end indocs/reference/05-advanced.md, including thesupports_websockets = falseCodex HTTP/SSE setup as an alternative to the native WebSocket transport below. - Native WebSocket
/responsestransport for Codex (#440) — Codex CLI and the OpenAI SDK default to a persistent WebSocket connection (ws://…/responses, oneresponse.createevent per turn), so the HTTP-only proxy forced clients to setsupports_websockets = false. The proxy now speaks the Responses WebSocket protocol natively:GET /responses(and/v1/responses) upgrades to a WebSocket, eachresponse.createturn 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 keepsPOSTon 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 --editionplus manual fixes for#[cfg(windows)]FFI (unsafe extern "system") and feature-gated pathscargo fixcannot reach on a single host. Newly-unsafestd::env::set_var/remove_varcalls 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_profilerecords the active profile in a thread-safe in-process cell, removing a latentenv::set_vardata race. Nestedif/if letcollapsed 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_shellboth as static replacements of the nativeread/grep/glob/edit/bashtools and again under theirctx_*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 otherctx_*tool is still registered dynamically. Thanks @omar-mohamed-khallaf. - Default shell allowlist now includes the C/C++ compilers (#361) — under
mode=replace,ctx_shellenforces the allowlist, butgcc/cc/clang/g++/c++/clang++were missing even thoughrustc/go/javacwere, 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 setLEAN_CTX_ALLOWLIST_WARN_ONLY=1to work around it). They are compile-only — executing the produced binary stays gated like any other path — so the security boundary is unchanged.
Fixed
gaindashboard 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, solean-ctx gainandgain --deepsilently 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 thegain --dailytable 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_editcarried 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 singlecore::redactionsource 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 throughctx_readno longer maskspassword: undefined-style annotations as API keys. ctx_readexposes the same schema in Pi as in Codex / MCP (#432) — the Pi adapter hand-wrote actx_readschema with onlypath/offset/limit/mode, so an agent running in Pi never sawfreshorstart_lineeven though the canonical MCP schema (and the Pi handler internally) already supported them — making cross-harness instructions likectx_read(mode="full", fresh=true)look invalid in Pi only. The Pi schema now matches the registry:start_line(withoffsetkept as a back-compat alias) andfreshare exposed and wired through both the MCP-bridge and CLI read paths.proxy enablenow also routes Pi / forge through the proxy (#361) — Pi and forge resolve their endpoint from~/.pi/agent/models.json(providers.<name>.baseUrl) + OAuth, not fromANTHROPIC_BASE_URL/OPENAI_BASE_URL, so the shell and Claude/Codex env wiring silently bypassed them (the tokbench review had to hand-editmodels.json).proxy enable/disablenow wire Pi'santhropic(bare origin) andopenai(/v1-suffixed) providers when~/.pi/agentexists, preserving any custom remote endpoint unless--forceand reverting only the endpoints it set. Pi's OAuth keeps working because the proxy forwards the credential verbatim to the real upstream.config init --fullno longer resets the existing config to defaults (#443) — the command rebuilt the file fromConfig::default()and saved that over the user'sconfig.toml. Because the TOML merge writes every default value, this silently reverted custom settings (proxy port, compression level, provider setup, …) on everyinit --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 onrules_already_present(), a hand-maintained list that only knew about five agents. For everyone else it returnedfalse, so withauto_inject_rulesunset the setup skipped injection and the model never saw the "preferctx_*tools" guidance — defeating the whole point of MCP-only mode. Detection is now derived from the singlebuild_rules_targetscatalog (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 intoAGENTS.mdwhen 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_refedges for C#/Java types consumed without ausing/import (same-namespace/package visibility), but those edges only exist in a freshly built graph.ctx_impactrebuilt 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_withingraph.meta.json), andctx_impact analyze/diff/chaindetect 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 andconfig.tomlin 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-ctxafter migration (#439) — the resolver fix (#436) flips the data tree to XDG, but several feature-specific writers still hard-coded~/.lean-ctxand re-created it post-split regardless of where the resolver pointed: multi-agentshared_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 cloudmode_statsreader (cli::cloud) and the ctxpkg publisher signing key (core::context_package::keys). All now route through the typeddata_dir()/state_dir()resolvers — the same categoriesdoctor --fixmigrates 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-linedirs::home_dir()…join(".lean-ctx")chains and thejoin(".lean-ctx/…")subpath form it previously missed, so the tracked-debt allowlist can only shrink. doctorshows~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 indoctor/common.rs(tildify_homefor formatted lines,display_user_pathfor raw paths, with component-boundary safety so a sibling like…/<name>-backupis never mangled) collapse the home dir back to~at the central output sinks, sodoctoranddoctor integrationsno 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-ctxwhenever that directory merely existed, even afterdoctor --fixhad 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 writingactive_transcript.json/context_radar.jsonlback 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-ctxlike 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 --fixnow 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.--fixnow 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.doctorreports the realconfig.tomllocation after a split (#435) — theconfig.tomlcheck and the path-jail hint were hardcoded to~/.lean-ctx, so after the XDG splitdoctorpointed users at a stale path. Both now resolve throughConfig::path()/config_dir()and show where the file actually lives.doctorscore matches the checks it prints (#433) —passed/totalwere 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 aspassword: undefined,secret: stringandtoken: null, replacing the value with[REDACTED:API key param]and corrupting files read throughctx_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 thectx_readredactor 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 (loggingUnknown tool_profile 'lean'and falling back to Power) and the settings API reported the effective profile (Power) rather than the unpinned state.lean/lazy/resetare now understood everywhere as the unpin sentinel (centralised intool_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/settingsfor 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_readacceptsoffset/limitaliases (#432) — agents trained on the native Read tool sendoffset/limit, but the schema only documentedstart_line, so those range reads were silently ignored.offsetis now an alias forstart_lineandlimitbounds the window (lines:N-M); the aliases are advertised in the tool schema and the generated manifest/reference docs, withPI_AGENTS.mdaligned.- 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,~/Desktopor~/Downloadspops 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.0–v3.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 insandbox-execwith 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 withEPERM: 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 (noKeepAlivecrash-loop on a malformed profile), existing installs adopt the wrapper automatically on the nextlean-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/eventsjournal 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/settingsendpoint that inherits the dashboard's Bearer-token auth and CSRF-Origincheck, validates every value against the config schema and a fixed four-key allow-list (no arbitrary config keys are writable), and persists toconfig.tomlexactly like the matching CLI commands. Settings pinned by aLEAN_CTX_*environment variable are flagged in the UI so a toggle never silently no-ops. - Dashboard:
--open=browser|none|vscodereveal control (#424) —lean-ctx dashboardalways 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) orvscode(suppress the external browser and print the VS Code Simple Browser steps). Flag parsing is case-insensitive and falls back tobrowseron 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-installand 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 --fixnow fully empties~/.lean-ctxinstead 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 anddoctorwarned 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*.legacyname. The destination is never overwritten and nothing is lost, so the legacy directory empties out and the warning clears.doctor --fixnow reportsN 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 updatedespite 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 (nostat/realpath) when the process is launchd-standalone and the path is under~/Documents,~/Desktopor~/Downloads.- every duplicated project-marker probe (
config,graph_index,setup,dashboard,knowledge_bootstrap,graph_provider) now routes through the single guardedpathutil::has_project_marker, with one marker set. is_safe_scan_rootrefuses launchd-standalone scans under the protected dirs before any marker probe orread_dir;has_multi_repo_childrennow also refuses nested protected paths (e.g.~/Documents/proj), not just the bare magic dirs. The project-local.lean-ctx.tomlread and thegit rev-parse/ cwd-fallback in project-root detection are guarded too.
Why it kept coming back:
lean-ctx updaterun 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~/Documentsand prompt — and every update changes the binary's code signature, invalidating any prior grant. A new macOSsandbox-execregression test (rust/tests/tcc_sandbox.sh) boots the daemon as a standalone process under a profile that SIGKILLs on any~/Documentsaccess, reproducing the real end-user condition that terminal testing hid, alongside standalone unit tests inpathutil/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 = falsenow suppresses MCP writes on every registration path (#281) — earlier fixes only gated the shared JSON-config writer andconfigure_agent_mcp; the per-agent hook writers (Claude, JetBrains, OpenClaw, Crush, OpenCode) and the editor-registry registration in interactive setup, non-interactive setup anddoctor --fixstill wrote MCP server entries unconditionally. The check is now centralized inhooks::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_readmap/signatures no longer serve pre-rebuild output afterctx_index build-full(#420) — the CLIbuild-fullpath cleared the daemon read cache, but the MCP tool runs in the process that owns theSessionCache, so a forced rebuild leftctx_read map/signaturesreturning stale output. The MCP tool now invalidates the in-process graph cache and clears theSessionCachein-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 dispatcheslctx:refreshon a content-hash change while the tab is visible (panels reload in place, preserving UI state), and avisibilitychangehandler catches up immediately when the tab regains focus. -
lean-ctx watchbackfills recent events on start (#560) —watchset the tail offset to EOF on startup, so an idle launch showed a blank screen even whenevents.jsonlwas 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-ctxonPATH, 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 otherPATHentry 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) —
buildPluginemittedlean-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 tolean-ctx-jetbrains-plugin-<version>.zipbefore upload, and the release job now fails loudly ifbuildPluginproduced 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_self→safe_canonicalize_bounded→safe_canonicalize), so a launchd-standalone daemon validating a path under~/Documentsgot 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 unguardedpathutil::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 thesandbox-execboot test plus a newcanonicalize_secure_bypasses_tcc_guard_for_pathjailunit test). - Cookbook dev-dependency upgrade — Vite 6 → 8 (#595) — the example apps now
build on Vite
^8.0.16with@vitejs/plugin-react^6(peervite ^8), pulling a patched esbuild and clearing the esbuild dev-server advisory (GHSA-67mh-4wv8-2f99).npm auditreports 0 vulnerabilities; the knowledge-graph-explorer example builds and typechecks unchanged. Node engine floor raised to>=20.19.0to 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(~/.codebuddyinIDE_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; envLEAN_CTX_STRUCTURE_FIRST) forautoto prefermapon 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 forcefull. gainnow reports net-of-injection bill impact (#361) —lean-ctx gain(andgain --json) surface the observed proxy turns, the total injected overhead (per-turn tax × turns) andnet_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"),
autonow forcesfullfor 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_resultthat 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 newrouteShellopt-in (envLEAN_CTX_PI_ROUTE_SHELL, implied byreplacemode) suppresses the nativebashbuiltin so build/test/log output is compressed and metered (lossless for signal), while the read/list/search builtins stay available alongsidectx_*.
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 configuredmax_disk_mb/max_age_hourslimits were never enforced:archive::cleanup()had no production caller and the FTS cap deleted only DB rows, orphaning the (much larger).txtblobs. 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, andlean-ctx cache prunereclaims the archive too.doctorreported the proxy as broken on Windows (#416) — proxy autostart has no backend on Windows, sodoctortreated 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 (runlean-ctx proxy start), and "running but autostart not installed" is a warning rather than a failure on macOS/Linux.setupreported 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.doctoralso 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 astats.json, the old migration bailed anddoctorpointed atlean-ctx setupinstead ofdoctor --fix.doctor --fixnow 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-ctxis included in split detection. - JetBrains plugin now ships as a downloadable GitHub Release asset (#418) —
the plugin
.zipis built and attached to every release. It was missing from v3.8.5 because the plugin'sRelease Assetjob only ran onreleaseevents, which aGITHUB_TOKEN-created release never triggers. The plugin version is now single-sourced ingradle.propertiesand 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_overviewread the rawAgentRegistry, 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 whatctx_agent listand the dashboard already do. ctx_readmap/signatures served pre-rebuild output (#420) —lean-ctx graph build --forceandlean-ctx index build-fullonly dropped the in-process graph cache, but a running daemon kept serving stalemap/signaturesfrom its long-livedSessionCachein 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_readignoredautomode (#421) — batch reads forcedauto→full, so every file came back fully expanded regardless of the active profile.ctx_multi_readnow honoursautolike a singlectx_read, resolving the optimal mode per file. Tool descriptions, schemas and the injected rules (bumped to v12) now steer agents to omitmode(=auto) and reservefullfor the read immediately before an edit.ctx_semantic_searchwas hidden in the default profile (#422) — the meaning-based search tool was categorised underMemoryand 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_readauto-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_readnow 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 orfullreads) 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:mapmode no longer repeats exports theAPI:section already lists with full signatures — the same symbols were emitted twice (once as a bareexports:line, once inAPI:). A sharedexports_not_in_signatureshelper 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 atmax_facts, but the bulkimport_facts()path still used the oldmax_facts * 2guard, so a merge/import could inflate a store to twice its budget before any eviction fired (observed live as adoctorcapacityCRIT, e.g. facts 232/200). The import path now runs the memory lifecycle as soon as it exceedsmax_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 aproject_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 permanentdoctorcapacityCRIT).lean-ctx doctornow reports orphaned stores and the reclaimable size,lean-ctx cache prunereclaims them (alongside BM25/graph/archive), anddoctor --fixprunes 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 = falsewas 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 interactivesetup, non-interactivesetupanddoctor --fixstill 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 aftersetup/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_refactorsurface 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,
requireedges 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 --fixsplits 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 (atomicrenamewith a copy+remove fallback across filesystems). Read-onlylean-ctx doctorreports a pending split. New per-category overridesLEAN_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 explicitLEAN_CTX_DATA_DIRstill forces one directory and is never auto-split. - Config (
-
pi-lean-ctx bridge tool parity (#409) —
ctx_search,ctx_treeandctx_multi_readare 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 multipleremembercalls raced. auto_update_mcp = falseignored 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 aftersetup/onboard/init/doctor --fix.- Session
extra_rootsnot 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::loadserved 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 asGEMINI_API_KEYno 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 preflightCI-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_globignored an out-of-scopepathand scanned the whole project instead (#401): when an explicitpath(orpaths) 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.cursorrulestemplate 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/resetmanage 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_costsplits 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.
- Lean default tool surface (#575): setup no longer pins a
- Codex hook: native rewrite instead of block-and-retry (#399, community
contribution): on Codex ≥ 0.20 the
PreToolUsehook now returnsupdatedInputto 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/DataRowdecode panics) — found bycargo-denythe moment they were published; lean-ctx never exposed the vulnerable paths to untrusted servers (#399).
Fixed
lean-ctx overviewflooded the terminal with thousands ofnode_modulesentries on projects without a top-level.git(#400): theignorecrate only applies.gitignorefiles inside git repositories — in a monorepo whose subprojects carry their own.gitignorebut whose root is not a git repo, every scanner walkednode_moduleswholesale (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 sharedwalk_filternow prunes unambiguous vendor dirs (node_modules,__pycache__,bower_components, virtualenvs with apyvenv.cfg) regardless of git state, andrequire_git(false)makes.gitignorefiles effective without a.gitdirectory. Explicit roots stay reachable (ctx_tree node_modules/reactworks), andrespect_gitignore=falseremains 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. Actx_search/ctx_tree/ctx_globcall whosepathfell back to"."then walked the entire home directory — everystatunder~/Library,~/Desktop,~/Picturestrips a TCC prompt (Calendar/Reminders/AddressBook/Photos), and the walk burned 10–20 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 (newwalk_guard): relative paths are absolutized against the process cwd first — solean-ctx grep/lsinside 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 projectpathinstead of silently scanning. ctx_impactreported C# classes as leaf nodes when consumers had nousingdirective (#398): C# resolves types in the same namespace without any import, and DI-style code nevernews 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 withtype_refedges, whichimpact_analysisalready 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_codesmell — its SQL already exemptedtype_reftargets, but nothing ever created those edges. The builder now also emits symbol-leveltype_refedges, 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
updatedInputrewrites forPreToolUsehooks, sohook codex-pretooluseemits the documented allow+rewrite JSON on stdout instead of exiting 2 with "Re-run with ..." feedback. Rewritable Bash commands are transparently replaced with thelean-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 withfile not foundon a nonexistentC:/…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 doctorreported "no rules file found" right afterlean-ctx setup(#396): the 3.8 layout (GL #555) intentionally replaced the always-loaded~/.claude/rules/lean-ctx.mdwith 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 (doctoranddoctor 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, andrules_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:
servebooted with cwd/and walked every stored session, stat-ing each session'sproject_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 stopsshell_cwd.starts_with("/")from leaking an arbitrary project's session into the daemon default).ContextLedger::load → pruneranrealpathover 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/Downloadsin 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 fulllean-ctx updaterewire now run clean against a real data dir with 600+ sessions rooted under~/Documents.
- Pi:
ctx_grep/ctx_find/ctx_lssilently searched the wrong directory (#395):pathwas 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.pathis 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 enableexportedOPENAI_BASE_URLwithout the/v1suffix the OpenAI SDK convention expects (default ishttps://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 advertisehttp://127.0.0.1:<port>/v1; with that base, OpenCode's OAuth plugin correctly routes ChatGPT-subscription requests directly tochatgpt.com(analogous to the Claude Pro/Max guard), while API-key traffic keeps flowing through the proxy. Stale/v1-less entries in Codexconfig.tomlare migrated on the nextproxy enable; the proxy also collapses accidental/v1/v1/…double prefixes from clients that append/v1themselves. Verified end-to-end against OpenCode 1.2.15. - Dashboard token race:
lean-ctx dashboardpersisted its fresh auth token before binding the port. Two racing starts both wrotedashboard.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/eventspoll (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/sessionmatched 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/eventsno 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 astat()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 --helpno longer installs the service it was asked to explain (#393),allow_pathsfinally expands~and$VARinstead of matching them literally (#392), andctx_shellcloses 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 newdaemon restart(#394), and/reopenworks 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 indaemon statusandlean-ctx doctor(GH #394): the exact LaunchAgent plist / systemd user unit path plus the unit name, sosystemctl --user/launchctltargets are obvious without searching. lean-ctx doctorPath-jail check (GH #392): reports the effective jail state (active /path_jail = false/ compile-timeno-jail), flagsallow_pathsentries that can never match (unset$VAR, missing directory) and theallow_paths = ["/"]pattern.- Consolidated filesystem-boundary reference (GH #392):
docs/reference/appendix-paths-and-config.md§5 documentspath_jailvsallow_pathsvsextra_roots, theno-jailcargo feature and the removedLEAN_CTX_NO_JAILenv var; SECURITY.md cross-links it.
Fixed
daemon enable --helpexecuted instead of showing help (GH #393):--help/-h/helpanywhere inlean-ctx daemon …,lean-ctx proxy …orlean-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_rootsentries with~,$VARor${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 theLEAN_CTX_ALLOW_PATH/LEAN_CTX_EXTRA_ROOTSenv lists, which MCP hosts pass shell-less too) are now expanded; unset variables warn and are reported by doctor.
Security
ctx_shellhardening (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-/--spiderstay allowed,dd of=except/dev/null);xargs/nohupjoin 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 = truenow 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
/reopenmatches 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.mdwith 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-v1contract) — audit-chain segment, resolved policy pack, CGB + framework coverage reports, Ed25519-signed manifest; identical inputs produce byte-identical bundles. New independent verifierpackages/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). Newlean-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: everyfullclaim must name a CI test (tests/compliance_frameworks.rsproves 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 (newsso_oidcentitlement key, additive on every plan) and 365-day audit retention. Self-serve vialean-ctx cloud upgrade --plan business; existing subscribers are switched in place (prorated) instead of double-billed. SAML/SCIM (sso_scim) stays Enterprise.billing-plane-v1remains frozen — v3 is a purely additive catalog delta. - Datadog/Prometheus FinOps export — metrics contract + scrape token
(GL #401):
/metricsnow 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 alean_ctx_infoseries carryingproject/profile/agent_role/model/versiontags (kube-state-metrics_infoidiom — one series per process, no cardinality explosion). NewLEAN_CTX_SCRAPE_TOKENenv: a read-only Bearer token valid only forGET /metrics, so monitoring agents never hold the dashboard credential. The exposition surface is frozen indocs/reference/metrics-contract.json, enforced byrust/tests/metrics_contract.rs(update viaLEANCTX_UPDATE_METRICS_CONTRACT=1). Ready-to-import Datadog assets:integrations/datadog/(OpenMetricsconf.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 Foundationfocus-validator),--target=cbf(CloudZero AnyCost;--uploadposts per-month Stream drops withreplace_drop= idempotent re-runs),--target=vantage(custom-provider CSV;--uploadposts multipart, additive semantics documented). Savings are emitted asCredit/Discountrows 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=1andDD_API_KEYrequired (a stray API key alone never enables egress),DD_SITE+LEAN_CTX_DATADOG_INTERVAL_SECSoptional. Counters go out as per-interval deltas (baseline cycle first — lifetime totals never spike a graph), gauges every cycle, all series taggedproject/profile/agent_role/model/version. Runs as a background loop inlean-ctx dashboard. - Quality loop v1 — edit failures teach mode selection (GL #494):
ctx_editoutcomes are now correlated with the last read mode of the file. Anold_stringmiss after a compressed read (a) escalates the next auto read of that file tofull(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 tofulluntil they recover. New resolver sourcesedit_fail_escalation/edit_quality_penalty, persisted in~/.lean-ctx/edit_quality.json(bounded, 30 d decay), surfaced inctx_metricsunder "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 publishis real — preflight (parse, ed25519 signature, scoped-name check) thenPUTto the registry at ctxpkg.com with actxp_…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 --signsigns 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,--jsonfor 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 C1–C4, 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 indocs/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/tabdeep 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/owaspendpoint (same data aslean-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 astasks.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 forssh-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-suitewith publishable numbers (benchmark/locomo/LOCOMO.md: 100% containment@5 at 29.4% token reduction), alocomo_benchbinary 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|validatelists, 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/auditwith 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 oneorg_audit_log; the retired SSO-only table is migrated and dropped by an idempotent boot migration. Retention is the owner-plan window from thebilling-plane-v1SSOT (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-Labelheader (the machine's hostname), tracked server-side as fire-and-forget display metadata — never auth, quota, or billing input./account/cloudlists 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/supportersedge;leanctx.com/support/renders the wall client-side (plaintext-only, tier pills, newest first). Cancelling the subscription hides the entry on the nextsubscription.deletedwebhook, 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-badgeendpoint 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/digestexposes the preference to the dashboard. Contract:docs/contracts/email-digest-v1.md. Cloud-server CORS now allowsPUT/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
roiWebhookUrlinteam.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 (Slacktext, Discordcontent, 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 indocs/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/entitlementscarries 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-v1derived 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-declaredentry_countfrom blob metadata. Contract:docs/contracts/personal-cloud-encryption-v1.md. - Team server billing-plane endpoints (GL #463):
GET /v1/storagereports the hosted workspace footprint (allocated-blocks sizing, hard links counted once, symlinks never followed, 60 s cache;camelCaseperbilling-plane-v2) andGET /v1/usageserves the unified snapshot — signed-ledger savings roll-up, measuredtoolCalls, and asnake_casestorageblock. Both audit-scope-gated like/v1/metrics; quota viaLEANCTX_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 likeide_pathsrespected), active deprecations, data-layout writability, frozen-contract set.--jsonfor 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) andmarketing/launch-v1/(Show HN + Product Hunt drafts with tokbench-informed Q&A prep).- Custom embedding models (GL #397, upstream #328):
ctx_semantic_searchcan now load any HuggingFace repo with an ONNX export viamodel = "hf:org/repo[@revision]"([embedding]inconfig.tomlorLEAN_CTX_EMBEDDING_MODEL). Includes revision pinning with an unpinned-warning, automatic dimension probing from the ONNX graph ([embedding].dimensionsas 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 (
leanctxon PyPI,@leanctx/sdkon npm,lean-ctx-clienton crates.io) now cover the entire public/v1surface — addedcontext_summary,search_events,event_lineageandmetricsto 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) andengine_compat(SDK declares its supportedhttp_mcpcontract versions). New CI jobsdk-conformanceruns all three kits against a reallean-ctx servebuild viascripts/sdk-conformance.shand publishesdocs/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 shippedhttp_mcpcontract 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/experimentalin a stability matrix (CONTRACTS.md, SSOTcore/contracts.rs::contract_docs()). Two new CI gates enforce the freeze:tests/contracts_frozen.rs(every doc classified; frozen docs content-hashed againstdocs/contracts/frozen-hashes.json— semantic changes must land as a new-v2.mdfile) andtests/openapi_stability.rs(public/v1surface vs.docs/reference/openapi-v1.snapshot.json; additive diffs pass, removed or mutated routes fail).GET /v1/capabilitiesadditionally returns acontract_statusmap so clients can verify stability guarantees at runtime. The deprecation registerDEPRECATIONS.toml(compiled into the binary, ≥ 2 minor releases between announcement and removal) feeds a newlean-ctx doctorcheck that warns about every deprecation shipping in the installed build. - Personal-Cloud auto-push (GL #384): opt-in
lean-ctx cloud autosync onpushes 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|statussyncs the project's retrieval index (BM25 + embeddings) across devices — a fresh machine gets workingctx_semantic_searchwithout 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'shosted_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 routesPUT/GET/DELETE /api/sync/index/{project}GET /api/sync/index; the Personal-Cloud dashboard payload gains ahosted_indexblock (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
/v1request 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(newsloblock) 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 indocs/examples/team-slos.toml; the SLO engine understands the new metricsteam_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 conformancenow verifies structural invariants ofmap,signatures,aggressiveandentropyagainst 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 gainnow states its denominator — savings are compression on lean-ctx-touched traffic, not the full provider bill — via a Methodology line and a newinjected_overhead_tokens_per_turnfield ingain --json(net bill impact = tokens_saved − injected_overhead_tokens_per_turn × turns). Newcore::context_overheadmeasures the fixed per-turn prefix lean-ctx injects (tool schemas + server instructions + rules block). A newrules_injection = "off"(alsonone/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 thectx_*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-grainedTeamScopes. A token's effective scopes arescopes ∪ 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::billingturns the upgrade flow into real plans (free/team/enterprise) with explicitEntitlements, plus usage-based metering derived read-only from the Ed25519-signed savings ledger (EPIC 12.20).Usageis privacy-preserving and only billable on a signed + intact chain. Crucially,entitlement_allowsupholds the Local-Free Invariant — every local feature is allowed on every plan (incl. Free); the local binary has no entitlement checks (enforced bytests/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
.wasmmodules — no recompile of lean-ctx. Behind the off-by-defaultwasmCargo feature (features.wasm_runtimein/v1/capabilities), upholding the Local-Free Invariant (free, compile-optional). Uniform ABI v1 (memory+alloc(i32)->i32+entry(i32,i32,i32)->i64packedptr/len); guests run against an empty linker (no syscalls/network/fs/clock — sandboxed by the runtime itself) with a freshStoreper call for thread-safety + determinism.WasmCompressorregisters as a first-class compressor (host-enforced byte budget, graceful fallback on traps, conformance-checked);WasmProviderregisters as a first-classContextProvider(lenient result-JSON mapping). Opt-in discovery fromLEAN_CTX_WASM_DIR(*.wasmcompressors;*.wasm+<stem>.provider.jsonsidecar providers). Contract:docs/contracts/wasm-abi-v1.md. - Context OS guide + non-coding cookbook (Context OS, EPIC 12.18):
docs/context-os/guide.mdmaps 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.mdadds 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.adaptersexposes 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 samecall_tool_textpath so they behave identically. Tested with/without each framework installed. - Python SDK (Context OS, EPIC 12.4): new
leanctxpackage (clients/python/) — a thin, standard-library-only client (urllib, zero runtime deps) for the HTTP/v1contract, mirroring the TS/Rust SDKs:health,manifest,capabilities,openapi,list_tools,call_tool/call_tool_text, andsubscribe_events(SSE). Structured errors (LeanCtxConfigError/TransportError/HTTPError) and the sharedrun_conformancekit (lockstep with the TS SDK). Ships a README,pyproject.toml, in-process HTTP-server tests, and apython-sdkCI job. - TypeScript SDK GA + shared conformance kit (Context OS, EPIC 12.5):
@leanctx/sdkgainscapabilities()andopenapi()for full/v1discovery parity, a typedCapabilitiesV1, and a newrunConformance(client)kit that returns a client-side scorecard (health, capabilities shape, OpenAPI shape, tools listing). The kit mirrors the server-sidelean-ctx conformanceand 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-v1—prose(collapse blank-line runs, strip/collapse intra-line whitespace, drop adjacent duplicate lines) andmarkdown(everythingprosedoes 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:research→markdown;lead-gen/support→prose. - 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/plainfrom multipart), HTML (rendered Markdown paragraphs, reusingweb::html_to_text), and PDF (reusingweb::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 intoextension-registry-v1, so they surface in/v1/capabilitiesand are conformance-checked. - Conformance & reproducibility scorecard (Context OS, EPIC 12.17): new
core::conformance+lean-ctx conformance [--json]produce aScorecardproving an instance honors its own contracts. Checks span three categories —contracts(all machine-verified versions present),reproducibility(/v1/capabilitiesand/v1/openapi.jsonare byte-deterministic), andextensions(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 viatests/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
SandboxPolicyderived 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 underextensions.plugins[].permissions. - ROI / metering substrate (Context OS, EPIC 12.20): new
core::savings_ledger::roiderives aRoiReportstrictly 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 vialean-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_capabilitiesclassifies every feature flag asLOCAL_ALWAYS_ON,LOCAL_OPTIONAL(compile-only), orCOMMERCIAL_PLANE(additive team/cloud). A CI conformance test (tests/local_free_invariant.rs) fails the build if the default plane isn'tpersonal, any local capability isn't unconditionally free, the planes overlap, or a local capability reacts to aLEAN_CTX_LICENSE/LEAN_CTX_PLAN/LEAN_CTX_ACCOUNTenv 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
coding—research,lead-gen(aliassales),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 thecodingpersona leaves the prompt byte-for-byte unchanged (no regression). Available presets surface underpresetsinGET /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 viaLEAN_CTX_PERSONAorpersona = "…"in config, resolved against built-in presets then<personas_dir>/<name>.toml(overrideLEAN_CTX_PERSONAS_DIR). The built-incodingpersona reproduces today's defaults — the tool surface still resolves topowerwhen nothing is pinned (no regression), and explicit tool-profile settings always win. The active persona surfaces atGET /v1/capabilitiesunderserver.persona, with available presets underpresets. 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 inbuild_registry()— no fork, no code edit. They surface inGET /v1/capabilitiesunderextensions.toolsand in the agent's tool list, and run sandboxed through the shared subprocess runner (piped stdio,LEAN_CTX_PLUGIN_DIR/LEAN_CTX_TOOLenv, 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 onerun_subprocessrunner; 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 (fullread-mode;identity/whitespacecompressors;lines/paragraphchunkers) registered through the exact same public API extensions use (no special-casing). Extensions register custom transforms by name; the live registry contents now surface underextensions.{read_modes,compressors,chunkers}inGET /v1/capabilities. - Generic ingestion front-door (Context OS, EPIC 12.12): intake is no longer gated by
is_code_file. A newcore::ingestionfront-door (ingestion-spec-v1) classifies every path by content kind —Code/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 duplicateis_code_filein the CLI indexer is removed;bm25_index::is_code_fileremains 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-clientRust crate — the embedding boundary (Context OS, EPIC 12.2): a thin, stable HTTP client for the/v1contract 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, paginatedtools,tools/call(raw result + flattened text), andeventsas a blocking SSE iterator. Open-ended documents are returned asserde_json::Valueso new server keys never break a client build; errors carry the stableerror_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 atclients/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
PluginManageris wired into the running server, so a third-party plugin can finally observe the engine without forking it.pre_readandpost_compressfire around the centralctx_readchoke point (carrying the path and the realizedoriginal → compressedtoken counts), andon_session_startfires 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 idempotentinit(). An end-to-end test proves a realctx_readtriggers an installed plugin'spre_readhook, and a newLEAN_CTX_PLUGINS_DIRoverride lets containers/CI/tests point the registry at an isolated plugins root (distinct from the per-hookLEAN_CTX_PLUGIN_DIRthe executor exports to a plugin's own child process). All five hook points are now live:on_session_start/on_session_endbracket each server process (the end hook fires synchronously at shutdown so it always runs before exit),pre_read/post_compresswrap reads, andon_knowledge_updatefires whenctx_knowledge(action="remember")writes a fact (carryingcategory:key). - OpenAPI spec —
GET /v1/openapi.json(Context OS, EPIC 12.1): the public/v1surface 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 inhttp-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, deploymentplane(personal/team/cloud), wiretransports, built-inpresets(personas),read_modes, thetoolssurface, afeaturesmap (always-on capabilities plus compiled Cargo features likesemantic_search/team_server/cloud_server), runtime-discoveredextensions(plugins), and all machine-verifiedcontractsversions in one place — no secrets ever included. Versioned bycapabilities-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/v1deprecation 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 callsctx_tools findwith a natural-language need; the gateway aggregates the downstream catalogs (TTL-cached), ranks them with the same BM25 engine asctx_search, and returns a top-N ChoiceCard shortlist (server::tool+ one-line description + key params).ctx_tools callthen 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 officialrmcpclient, 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 aspublic < 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) ordrop(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 adeterminism_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_searchnow returns aSearchOutcomethat 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
entropynumbers 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.00vs57.29on the small corpus). Benchmarks now pin the filter off via the newentropy_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 atomicagent_identity::sign_with_public_key/sign_bytes_withAPIs resolve the keypair exactly once; all three call sites migrated. (pipeline red since the #551 efficiency program landed): thetry_shared_engine_returns_none_when_not_initializedunit test asserted on the process-globalSHARED_ENGINEOnceLockwhile 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 undercfg!(test), and CI exportsLEAN_CTX_EMBEDDINGS_AUTO_DOWNLOAD=0so the suite is hermetic. Also un-sticks the Coverage job: the silent engine load maderun_project_benchmark("src")exceed tarpaulin's 180 s timeout. ctx_shell/ctx_executefailures now set MCPisError+structuredContent(GitHub #389): every tool call returnedCallToolResult::successregardless 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 newShellOutcome(Exit(code) | Blocked) now flows from the shell tools through dispatch into the MCP result: non-zero exit setsisError: true+structuredContent: {"exitCode": N}, allowlist/ validation rejections setisError: 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_callchaining, andctx_execute(single/batch — first failing task fails the batch — and file preconditions). Exit 0 stays byte-identical (no metadata churn).- OpenClaw:
setup --autore-injected the legacymcpServerskey and broke 2026.6.1+ hot-reload (GitHub #390): OpenClaw moved to a nestedmcp.serversschema with strict validation; the editor-registry writer still wrote top-level camelCasemcpServers, so every watchdog tick producedconfig reload skipped (invalid config): Unrecognized key— with gateway-down risk on restart if the stale block won. OpenClaw now has a dedicatedConfigType::OpenClawwriter: it detects the version viameta.lastTouchedVersion(>= 2026.6.1 or an existingmcp.serversblock → nested schema; older → legacy camelCase), migrates our stalemcpServers.lean-ctxentry 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 + textuallean-ctx uninstall, which now also strips an emptiedmcpServers {}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>&1split 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 --deepcrash on multibyte paths/agent ids (GitHub #386): every display truncation helper (ctx_gain::truncate_str/shorten_path,stats::format::truncate_cmd,ctx_architecturehotspot 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-issuenow 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 incatch_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_expandfor on-demand access); opt back in viatool_profile = "power"in config.toml orLEAN_CTX_FULL_TOOLS=1in the server env. - Pi: stale
~/.pi/agent/mcp.jsonentry defeated the embedded bridge (GitHub #361, found by the tokbench independent benchmark): Pi has no native MCP adapter, butinit --agent piwrote alean-ctxmcp.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) andinit --agent pimigrates 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 uninstallnow leaves zero artifacts behind. Backup sweep covers installer subdirectories (hooks/,rules/,skills/,steering/, VS CodeUser/,.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.bakfiles, 0 leftover dirs. - Claude rules file regression (GL #555 follow-up, GL #558):
rules_injectstill wrote the always-loaded~/.claude/rules/lean-ctx.mdoninit --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
.bakchurn (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_hashcame 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.mdblock imported the full ruleset via@rules/lean-ctx.mdand the projectAGENTS.mdblock via@LEAN-CTX.md. Claude Code expands@-imports inline at launch and loads every rules file withoutpaths: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 andsync_if_compacted()never resetfull_content_deliveredflags. After a host compaction,ctx_readkept answering[unchanged]stubs that pointed at evicted context, and agents recovered by switching to nativeReadfor 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) declaredtype: object+propertieswithout an explicitrequiredarray — valid JSON Schema, but strict Pydantic-based backends (OpenAI, Azure, SGLang) reject it and OpenCode surfacesInvalid schema for function 'lean-ctx_ctx_expand': None is not of type 'array'. Every advertised schema (built-ins and plugin manifests) now passesnormalize_for_strict_validators(): recursive explicitrequired: []on object schemas anditemson array schemas, at every nesting level. Regression gate:rust/tests/tool_schema_strictness.rswalks 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 useipc::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 ~2–10k to 80–100k+ 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 preservescache_controlbreakpoints; tool-result compression is prefix-stable and unchanged. New[proxy].history_modeconfig key /LEAN_CTX_PROXY_HISTORY_MODEenv:cache-aware(default),rolling(legacy max-savings),off. Invariant locked by a byte-stability test simulating 80 growing turns. ctx_editevidence diff corrupted by terse post-processing (GH #382): theevidence (diff)block embeds verbatim source lines, but the generic terse stage still ran overctx_editoutput — dictionary abbreviation (return 0→ret 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_editjoins 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_resolveCWD-independence test canonicalizes both sides before comparing (macOS/varsymlink, Windows 8.3 short names), theteam_billingmodule doc no longer intra-doc-links a private const (rustdoc-D warnings), and the six new org/cloud contract docs are classified (Experimental) incontract_docs(). The frozenteam-server-contract-v1.mdis restored byte-exact; its additivestorageQuotaBytes/roiWebhookUrlkeys moved to a newteam-server-contract-v2.md(Stable), per the contract-file rule. Second wave: the CLI fidelity/pipe-guard integration tests pinLEAN_CTX_ALLOWLIST_WARN_ONLY=1(they assert compression behavior, not enforcement — on CI stderr is no TTY, so the new agent-mode allowlist blocked theirfor/whiletest scripts with exit 126), theISSUER_CACHE/ATTEMPTSstatics are documented in LOCK_ORDERING.md (L45/L46), anddocs/reference/generated/mcp-tools.mdis regenerated for thectx_agentbrief/return actions andctx_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 rawunauthorizedcards. - 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 existingcompression-demoendpoint, 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 callingdetectby 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_searchevents with raw, unmultiplied baselines) instead of onlyctx_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_indexnow walks ancestors for project markers, sorepo/rust/srcinside~/Documentsis a legitimate scan root (the.gitlives 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_ioandread_file_nofollownow reject NTFS junctions and all other reparse points (not just symlinks) via the sharedpathutil::is_symlink_or_reparsecheck; non-Unixread_file_nofollowpreviously 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 checkoutpreviously 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
bashcommand — but Copilot CLI runs thepowershellfield 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 (bashgets the MSYS-style conversion;powershelluses 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(honoringCOPILOT_HOME), existing pre-#381 configs are upgraded in place (missing-powershelldetection), 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/roiexactly once per navigation — the cockpit's 10 s poll only refreshed the status footer, and thelctx:refreshevent 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'slctx:refreshlistener was the only one without an active-view guard (and was never removed on disconnect) — it now follows the standard guarded pattern. proxy enableno 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 againstapi.anthropic.com, and that token is rejected by any customANTHROPIC_BASE_URL— so unconditionally pointing~/.claude/settings.json(and the shellANTHROPIC_BASE_URLexport) at the local proxy produced a login loop / 401 the moment Claude Code started, while OpenAI-compatible backends (Ollama, Codex) kept working.proxy enablenow detects whether an Anthropic API key is available (ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKENin the environment, or anapiKeyHelper/key in~/.claude/settings.json) viaanthropic_api_key_available()and, when none is found, skips the Claude redirect (leaving Claude Code on Anthropic directly), omits theANTHROPIC_BASE_URLshell export (OpenAI/Gemini exports are unaffected), and repairs any pre-existing stale local redirect. It prints a clear explanation and points subscription users to thectx_*MCP tools for savings;--forceoverrides for keys stored where we can't probe (e.g. a keychain).lean-ctx doctorgained 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 indocs/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 runslean-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 — sogit 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 viastd, no new deps) and passes the output through verbatim even underLEAN_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 whosePATHlacks 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 nextinit/doctor. - Proxy forwards the
OpenAI-Projectheader (#366): project-scoped OpenAI keys carry their scope viaOpenAI-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 withMissing scopes: api.responses.write.openai-project(andopenai-organization) are now forwarded verbatim. geminisetup installs the Antigravity CLI plugin hooks (#284):lean-ctx init --agent geminiconfigured the Antigravity CLI MCP target but never wrote its plugin hooks, so hooks landed only in the legacy~/.gemini/settings.jsonthatagyignores. The gemini path now also installs theagyplugin (~/.gemini/config/plugins/lean-ctx); auto-detect already covers the standaloneantigravity-clitarget.- The Antigravity CLI plugin is a self-contained, spec-"compliant" bundle (#284): the
agyplugin lean-ctx writes (~/.gemini/config/plugins/lean-ctx/) now ships its ownmcp_config.jsonnext toplugin.json+hooks/hooks.json, so thectx_*tools travel with the plugin and it validates clean underagy plugin validate(✔ mcpServers,✔ hooks). This was verified against the realagybinary, which stages plugins to exactly this path and shape viaagy plugin install— i.e. the reporter's documented~/.gemini/antigravity-cli/plugins/<name>/+ roothooks.jsonlayout is what the docs say, butagyv1.0.x actually reads~/.gemini/config/plugins/<name>/withhooks/hooks.json(the doc's own "global plugins" section agrees). The profile copy (~/.gemini/antigravity-cli/mcp_config.json) is kept for back-compat;agykeys MCP servers by name, so the dual definition is harmless. Root-cause note for the "hooks still not firing" reports: hook execution inagyis gated by its own server-side feature flagenable_json_hooks(a proto field applied viaapplyFeatureProviderJSONHooksConfig; experimentjson-hooks-enabled) and cannot be forced from a local~/.gemini/config/config.json(verified). lean-ctx therefore installs the hooks in the precise location/formatagyexpects and they light up automatically once that flag reaches the account — noteagy -pprint mode bypasses the hook subsystem entirely (hooks run in interactive sessions only).lean-ctx doctor integrationsnow verifies the full bundle (plugin.json+hooks/hooks.json+ plugin-localmcp_config.json) so install and doctor stay in lockstep anddoctor --fixrepairs any drift. - CEP meter counts cache hits and sessions for long-lived servers (#361):
cep.sessionsandtotal_cache_hitscould stay0even with confirmed cache activity — the meter only recorded on anauto_checkpointthat 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, solean-ctx gainreflects real cache savings. - Pi: no envelope overhead on tiny reads (#361): a
ctx_readof 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_smellsdead-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_readis 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 dictionaryreturn→ret,string→str, … plus line-score filtering) was skipped for reads only when the read had already saved tokens. A verbatimmode="full"(orlines:) read saves 0 tokens, so it was silently routed through the prose compressor — abbreviating keywords and dropping repeated lines. This violated thefullcontract ("guaranteed complete content"), corrupted source the agent edits against, and could drop the exact cross-file lines needed for data-flow review.skip_tersenow 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_resultspath did not — enabling it turned a largectx_readinto an[Reference: …] Output stored …preview the agent could not edit against. A singlefirewall::is_protected_readpredicate is now the source of truth for "an explicit read returns content," honoured by both the firewall and the reference-results path, soctx_read/ctx_multi_read/ctx_smart_readare 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) resolvedwhich lean-ctxfirst, so the baked path depended on ambientPATHordering at generation time. On a machine with both a Homebrew and a~/.local/bininstall 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-testedchoose_binary_path()that prefers the currently-running executable (current_exe()), falling back toPATHonly 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 viatokio::task::block_in_place, which pins one of the few core async workers and — being synchronous — cannot be interrupted by atokio::time::timeouton the same task, so a handler that blocked (e.g. the nestedblock_in_placeinsidectx_multi_readexhausting the blocking pool under concurrent reads) silently swallowed the JSON-RPC response. Every handler now runs on the dedicated blocking pool viaspawn_blocking, awaited under a watchdog deadline (LEAN_CTX_TOOL_TIMEOUT_SECS, default 120s;ctx_shell/ctx_executeexempt): 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 nestedblock_in_placeinctx_multi_readis also removed at the source (nowbounded_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'tSIGABRT— the crash was a stack overflow, whose handler callsabort()and whichcatch_unwindcannot intercept. Every tree-sitter AST walk recursed once per node depth on a ~2 MiB worker stack. Newcore::ast_walkprovides 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 namedleanctx-indexthread with a 16 MiB stack + graceful spawn-failure handling, andModeGuard::dropis 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_readno longer panics on UTF-8 files with multibyte characters (#379): the structural-hint and shell-result extractors incore::auto_findingstruncated 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-32603error and an empty read. All nine truncation sites now usestr::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.escused atextContent/innerHTMLround-trip that escapes&<>but not quotes, andcexpEscin the explorer did the same — a"in a file path, symbol name or knowledge value could break out oftitle="…"/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 -cblocks 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=1is 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.jsonis 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
schemaparam 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 viaallow_ide_config_dirs = trueorLEAN_CTX_ALLOW_IDE_DIRS=1;~/.lean-ctxstays allowed.
[3.7.5] — 2026-06-06
The Web & Research release. lean-ctx reaches beyond the codebase: the new
ctx_url_readtool 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 ofctx_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): thefactsandquotesmodes 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_itemscapsfacts/quotes, default 12), and an optionalqueryfocuses extraction on what you actually need. Fetching is SSRF-guarded — onlyhttp/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_readwas 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 measuredcep.sessions: 0even with the bridge connected). The bridge now starts by default (opt out withLEAN_CTX_PI_ENABLE_MCP=0/"enableMcp": false), and allctx_readvariants — includinglines:N-Mranges — 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, andPI_AGENTS.mdplus the setup output steer agents to thectx_*tools instead of the un-compressed nativeread/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_filescheck (WindowsFILE_ATTRIBUTE_OFFLINE/RECALL_ON_OPEN/RECALL_ON_DATA_ACCESS, macOSSF_DATALESS) is now afilter_entrypredicate 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 theis_safe_scan_rootguard 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 with401 Unauthorized — lean-ctx proxy requires authentication, even though #353 had already fixed the bare-/responsesrouting. 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-schemeAuthorizationis 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
--jsonCLI 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_preloadused to instantiate itsProviderBanditfresh 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
entropymode (North-Star bet 02): theentropyread-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_conditionednow 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
signatureover the chainedentry_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 conservativedetect_injectionheuristic 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 aSecurityViolationaudit 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.
HeatEntrynow carries a per-agent access map (a stigmergic pheromone field), populated in the live read path via a canonicalcurrent_agent_id()resolver (LEAN_CTX_AGENT_ID/LCTX_AGENT_ID/local, shared with the savings ledger). A newcontext_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_injectionconfig — opt out of touching shared instruction files (#343): a new top-level option (shareddefault |dedicated, envLEAN_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 defaultsharedkeeps today's behavior — a marker-delimited block written intoCLAUDE.md/AGENTS.md/GEMINI.mdfor zero-config discoverability. The newdedicatedmode 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
SessionStarthook'sadditionalContext(model-visible, nothing persisted toCLAUDE.md/AGENTS.md; any prior lean-ctx block is stripped on switch). - OpenCode — the dedicated
~/.config/opencode/rules/lean-ctx.mdis registered (by absolute path, idempotently) inopencode.jsoninstructions[], and the oldAGENTS.mdblock is removed. - Gemini CLI — the dedicated
~/.gemini/LEANCTX.mdis registered insettings.jsoncontext.fileName(seeding the defaultGEMINI.mdso the user's own context file keeps loading), and the oldGEMINI.mdblock is removed. Switching back toshared, andlean-ctx uninstall, cleanly reverse every registration (instructions[]/context.fileNamecollapse back to their pristine default) and delete the dedicated files — no orphaned entries. Toggling is driven entirely by the flag: the samerules syncwrites a block insharedmode and an untouched user file + separate rules file indedicatedmode.
- Claude Code & Codex — the rules summary is injected at session start via the existing
-
permission_inheritanceconfig — lean-ctx tools honor your IDE's permission rules (community request): when lean-ctx is mounted as an MCP server its tools (notablyctx_shell) execute inside the lean-ctx process, bypassing the host IDE's own permission engine — so an OpenCode user who setbash/rm *toask/denywould have that guard silently skipped whenever the agent reached forctx_shellinstead of the native tool. A new top-level option (offdefault |on, envLEAN_CTX_PERMISSION_INHERITANCE) makes lean-ctx mirror the active IDE's permission config onto its own tools. Whenon, before dispatch lean-ctx reads the IDE permission rules (v1: OpenCodeopencode.json/opencode.jsonc, global + project merged) and applies the equivalent decision to the matching tool:ctx_shell/ctx_execute←bash(incl. granulargit */rm *, and top-level command patterns),ctx_read/ctx_multi_read/ctx_smart_read←read,ctx_edit←edit,ctx_search←grep.denyblocks the call,askholds it back with an actionable message (MCP can't show an interactive prompt for these tools), andallow(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'spermissionblock — inheritance is read-only and runtime-only; the policy is cached briefly and the default (off) adds zero hot-path cost.lean-ctx doctorreports 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 explicitctx_knowledge action=cognition_loopcall — so knowledge never self-organized on its own. A newcore::cognition_schedulerfires 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/metricsmethodology) but only for display strings. The footprint is now a first-class, physically-grounded figure:core::energyadds a transparent carbon model (G_CO2_PER_KWH = 475g/kWh — the global-average grid intensity, override-able per machine viaLEAN_CTX_GRID_CO2_G_PER_KWHso cleaner grids report honestly), andGainSummarycarriesenergy_wh+co2_gramsderived fromtokens_saved.ctx_gainnow shows anImpact: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
--jsonCLI commands for editor/programmatic use:lean-ctx semantic-search(fixes the editor search path),lean-ctx repomap, andlean-ctx knowledge recallall gain structured--jsonoutput so editor integrations and scripts consume results without scraping human-formatted text. -
gainauto-publishes public metrics in the background: whengain.auto_publishis 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 manuallean-ctx gain --publish. Throttled so it never publishes more than once per interval and never blocks startup. -
dashboard --base-pathfor 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_searchquery 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, boundedcore::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 callsctx_read/ctx_shell/ctx_treenow 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_readflow. - 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_searchMCP 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_guardedpost-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-testedserver::post_processmodule, and the large&self-coupled side-effect blocks (tool-receipt + intent + session-save + cost attribution; shared Context-OS persist + bus events) move into namedserver::post_dispatchmethods. 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-onctx_callgateway 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 doctorthresholds are tuned so its health checks reflect real conditions. - Pi: explicit
LEAN_CTX_PI_ENABLE_MCP=1now always starts the embedded MCP bridge (#361): alean-ctxentry in~/.pi/agent/mcp.json(written bylean-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-ctxonly 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/graphreports 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
shellMCP 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: eachfunction_call_output.output(the Responses analogue of a role:"tool"message — a string, or aninput_textcontent array) is run through the same pattern pipeline as every other tool result. The conversationinputarray is intentionally left structurally intact (no history pruning) so afunction_callcan never be separated from its matchingfunction_call_outputand trigger a 400. Retrieve/cancel/delete sub-paths (/v1/responses/{id}/…) pass through cleanly, and/statusintrospection now reports an accurate token breakdown for Responses requests (instructions→ system prompt;inputitems → user/assistant/tool buckets;input_imagecounted). Chat Completions remains fully supported. shellMCP 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 ignoresctx_shelland reaches for a nativeshell/Bashtool, so nothing compressed. lean-ctx now exposes ashelltool (familiar name + model-optimized description) that transparently delegates to the same 95+-pattern compression pipeline asctx_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 newshell_allowlist_extraconfig field — so allowing e.g.aclikeepsgit/cargo/npm/… intact instead of replacing the whole built-in list.lean-ctx allow --listprints the effective allowlist plus the exact config path in use;--removereverts. Picked up on the next command — no MCP/daemon restart needed.- Line ranges in
map/signaturesoutput (#340, thanks @iohansson): every entity in the navigation-focusedmapandsignaturesviews 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 inmap/signatures(MCP + CLI) where locating code is the point — compression-first paths (aggressive,entropy, full-body reads, andctx_compress/ctx_outline/ctx_fill/ctx_analyze/repo-graph) stay byte-identical and pay zero extra tokens. Themap/signaturescompression 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 -cno 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 tabularkey[N]{f1,f2}:header and the length-prefixedkey[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 inexcluded_commands. Controlled by the newpreserve_compact_formatsconfig (default["toon"]; set to[]to disable) and surfaced as a "Compact-format passthrough" line inlean-ctx doctor. - Clearer path to the public leaderboard (community feedback): the
lean-ctx gainrecap now always shows a one-line, state-aware hint — how to publish toleanctx.com/metricswith--leaderboard, how to claim a display name (--name="…") instead of showing up as "anonymous", and how to--unpublish. Thegain --publishoutput 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
tracingdiagnostics 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 tonull, 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_allowlistedits silently ignored in MCP/editor mode (#341): allowlist changes looked like no-ops whilelean-ctx -c(CLI, warn-only) still ran the command, due to three compounding traps. (1) A malformedconfig.tomlfell 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 inlean-ctx doctor. (2) Settingshell_allowlistdirectly replaced the entire default list — the new additiveshell_allowlist_extra(written bylean-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 doctorgains 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(andLEAN-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/hookssince Codex 0.129.0) and traceable to a misread ofopenai/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, orlean-ctx -c) as the path that compresses reliably on every surface regardless of hook status, andlean-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
Readof 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/GeminifunctionResponse.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
400mid-task — now64 MiBand configurable viaLEAN_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):aggressivereads 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 — setsymbol_map_auto = true(orLEAN_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, orgenerate,auto-mode reads now resolve tofullregardless of model tier — you cannot safely edit a file you can only partially see, and an abbreviated/signaturesview just forced a follow-up read. Exploration/review intents still compress as before. - Per-model cost breakdown in the proxy (#351, external testing feedback):
/statusnow reports aper_modelarray (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), withdiscover --cardexporting 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 shareableleanctx.com/w/<id>URL (copied to clipboard). Whitelist-only (deny_unknown_fields), one-timeedit_tokenstored locally for later removal, optional account claim. Server-rendered page carries per-card Open Graph / Twitter meta;og:imageis aresvg-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 onleanctx.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 thegain --dailytable 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, sopnpm run compile 2>&1was parsed aspnpm run compile 2>and a bogus command1, 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 allowlistblocks in MCP mode (Cursor/opencode/etc.).- Auto-update ignored
config.toml(#335): a scheduler installed earlier kept runninglean-ctx updateeven after the user setupdates.auto_update = false, because theupdatecommand never re-checked config. Scheduled runs (--quiet/--scheduled) now obey config:auto_update = falseskips the update and removes the orphaned scheduler (self-heal), andnotify_only = truedowngrades to a check (never installs). Manuallean-ctx updateis 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/binPATH export) land in~/.bashrc.lean-ctx setupnow ensures the login profile sources~/.bashrc(idempotent, Debian/Ubuntu-style), so the hook and PATH take effect in login shells.install.shprints 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_preloadnow resolves its jail root from the dispatch-provided project root when no explicitpathand no session root are available, so it never silently jails against the daemon CWD in any IDE. ctx_searchand 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_stringon a named pipe blocks forever waiting for a writer, which surfaced as random, unlogged hangs.ctx_searchadditionally 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-pretoolusehook reroutes commands throughlean-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) orlean-ctx -cin the Desktop/Cloud app, which is the channel that is available there.lean-ctx doctoradds 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_modetransparently 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.logfor audit transparency. Visible inlean-ctx doctorandlean-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_overviewat session start,ctx_compressat phase boundaries,ctx_knowledge(action="wakeup")for prior findings. - Compression Bypass ladder:
lines:N-M→full→raw=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 doctordetects stale hooks, IDE path misconfiguration, and auto-refreshes outdated rules on first tool call. - Reference appendices generated from code:
docs-genrenders 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 statusandlean-ctx doctorsurface 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 aggregategainnumbers. 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 withLEAN_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 savingsandgain --wrappedreport 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 uninstallis now complete and self-contained. It (1) stops every process first — daemon, proxy, and straylean-ctxPIDs (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 rightcargo/brewcommand; an in-repotarget/releasebuild is never touched. New--keep-binaryflag;install.sh --uninstall(andcurl … | sh -s -- --uninstall) run the same teardown for users without the binary on PATH. Previously the binary was left behind with only a printedrmhint. - Edit-failure recovery context (#331): when
ctx_editcan't apply an edit, the error now carries the information the agent would otherwise re-read the file to find. Identicalold_string/new_stringare rejected outright; an already-applied edit (new_stringpresent,old_stringgone) 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); ifold_stringlives in a different file, it points there — so the agent re-targets instead of assuming it picked the right file. All searches respect.gitignoreand 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 nextlean-ctx setuporlean-ctx update. - MCP instructions include workflow hint: "Orient(ctx_overview) → Locate(ctx_search) → Read(ctx_read) → Edit → Verify → Record".
bypass_hint.rsrespects 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=Noneon DrvFS no longer causes spurious invalidation; path normalization usescanonicalize(with verbatim-prefix stripping) for consistent cache keys;lean-ctx cache statsnow 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)
savereports typedSaveOutcomewith actionable notes; (3)ctx_composedeferred 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
remembercalls 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_pathnow usessafe_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 toexecFileSync— 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_composeoutput (and every tool usingprotocol::shorten_path*) were rendered with separators stripped (C:\Users\…\win-build-log.txt→CUserszir…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_relativealso 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 theinitializehandler now reject broad/unsafe directories as a project root viapathutil::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 globallatest.jsonpointer — it is strictly project-scoped and returnsNonefor an unsafe cwd. A newload_global_latest_pointer()covers the explicit "show my last session anywhere" UX, andconsolidate_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, andctx_searchno longer emitsFound?in N fileswhen no meaningful pattern could be identified. Knowledge recall now boosts exact key/category matches above incidental lexical hits. - Cold-start
ctx_overviewreturns 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 tosessions/quarantine/.- Archive FTS disk cap enforcement (#2364): the archive index (
archives/index.db) now enforces an on-disk size cap (default 500 MB, override viaLEAN_CTX_ARCHIVE_DB_MAX_MB) by pruning the oldest entries + VACUUM. A new daemon-safestorage_maintenancepass also prunes accumulated quarantined BM25 indexes on startup, andlean-ctx doctorgains 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:
KnowledgeFactgains arevision_countfield. 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 showsrev Nfor 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). Whenrememberstores a fact, similar facts from other keys are surfaced in aSIMILAR FACTSsection with similarity percentages. Newjudgeaction lets agents resolve pairs assupersedes/compatible/unrelated.JudgedPairstorage suppresses future noise for already-judged pairs. Recall output annotates facts with↳ supersedes/↳ compatiblerelationship 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 >= 20ANDsignificant_tools >= 5AND 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. bunxin default shell allowlist (#310).
Fixed
- RAM Guardian measures daemon RSS instead of CLI process (#317):
lean-ctx doctorwas showing the CLI's ~14 MB instead of the daemon's actual memory. Addedget_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-ctxprocesses reparented to PID 1). Prevents MCP server processes from surviving after IDE restarts. lean-ctx restartno 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/searchtoPOST /rest/api/3/search/jqlwithnextPageTokenpagination. Server/Data Center deployments (detected viaJIRA_DEPLOYMENT=server) continue usingGET /rest/api/2/search. - Provider discovery ignores project root (#316):
handle_discover()andhandle_mcp_resources()now passproject_roottoinit_with_project_root()so project-local provider configs are found. - Cross-source hints path normalization (#316):
hints_for_file()now acceptsproject_rootfor consistentgraph_relative_keynormalization. - 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) intoanalytics.rs,network.rs, and other submodules. - Doctor module split: Decomposed
doctor/mod.rs(2321 lines) intocommon.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_MARKERSlogic.
[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 jiraruns the interactive flow (loopback redirect, browser consent, accessible-resource/cloudIddiscovery), persists tokens to~/.lean-ctx/credentials/jira-oauth.json(0600), and auto-refreshes on expiry with refresh-token rotation.lean-ctx provider list/provider logoutround 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 orJIRA_AUTH=oauthis 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-itemaccess_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-ctxin 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 explicitlean-ctx mcpare unaffected — they still get the server. --helpleads with the essentials (UX P1): aGETTING STARTEDblock (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_searchnow 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 viaLEAN_CTX_DISABLE_SEARCH_INDEX=1. ctx_composetask 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 provable1 − 1/eapproximation guarantee (Nemhauser–Wolsey–Fisher).ctx_composenow 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 viaLEAN_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 thatctx_searchregex-verifies), so recall is identical to the exact tier. TheMAX_FILESceiling 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_composeruns 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_composesemantic ranking is wall-time budgeted (H1): the onlyO(corpus)stage (a cold BM25 build) runs in a cache-sharing worker thread bounded byLEAN_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_composefull-path test coverage (H2): newtests/ctx_compose_scenarios.rsexercises 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 configpoints to the simpler surface (UX P2): the full config dump now ends with a tip towardconfig show(the 5 high-level knobs) andconfig set <key> <value>, so the 100+ keys no longer feel like the only entry point. The simplified config template (config init) now defaultscompression_level = "lite", matching the new friendly default.- Friendly-by-default output style (UX P0): the default
compression_levelis nowlite(plain-English "concise" guidance — bullets, no filler) instead ofstandard(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 orCRP MODE. The denser symbolic "power modes" stay one line away (compression_level = "standard" | "max", orLEAN_CTX_COMPRESSION). This only shapes the model's prose; tool-output compression is governed separately and is unchanged — engine efficiency is unaffected. ctx_readauto-mode delivers task-relevant bodies: inmap/signaturesmode with an active task, the body of the best-matching symbol is inlined, avoiding a follow-up full read. Themapheuristic 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_indexreuses a deserializedProjectIndexfrom 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 (1–2 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
--helpbanner tool count no longer drifts (UX P0): theN MCP toolsfigure in the banner is now derived from the live registry (server::registry::tool_count()) instead of a hardcoded literal — it read61while the README and feature catalog already said63. 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) tokenizations —
truncate_to_token_capre-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_testsfailed on shell-less runners (regression from #309): the new "is the shell installed?" guard skips writing zsh hooks when nozshbinary is present, but the drop-in install tests assert the hooks are written — so they failed on the zsh-lessubuntu-latestrunner. AddedLEAN_CTX_SHELL_HOOK_FORCE(1/true/allor a comma list likezsh,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_editconcurrent-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 sharedcore::path_locksregistry, 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_rankcompared retrieved paths (OS separator,\on Windows) against expected fixtures (/) withends_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_treetoken-savings test deterministic via a synthetic fixture (instead of walking the live repo, whose size + path tokenization varied by platform), and de-flakedspawned_background_task_doesnt_block_callerby polling for completion with a generous deadline instead of a fixed sleep.
Hardening
- Per-file advisory lock registry (
core/path_locks.rs): a process-wideper_file_lock(path)shared byctx_readandctx_editserializes 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.rssplit: extracted the enum surface (TeeMode,TerseAgent,OutputDensity,ResponseVerbosity,CompressionLevel,RulesScope) intoconfig/enums.rs, trimming ~250 lines from the module.- Premium
lean-ctx wrappedartifact: 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 updatecreates.zshenvon 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 setrejects valid config keys (#308): Theconfig setcommand 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 likeproxy_enabled,profile,compression_level,memory_profilenow 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 setwith 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
- Phase 1: Shell substitution blocking —
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-fulland Dashboard used different project root hashes (CLI used raw cwd, Dashboard promoted to git root). Unifieddetect_project_root()to always promote to git root, matching Dashboard behavior. Users in subdirectories now see the same index. index build-fullincomplete rebuild: Previously only cleared JSON graph index + BM25. Now also clearscall_graph.json.zst,graph.db, andgraph.meta.json, then rebuilds the SQLite property graph. Timeout increased from 2min to 5min.- Knowledge overflow from
finding-autoduplicates: Auto-consolidated findings without a file reference all received the keyfinding-auto, creating hundreds of duplicate facts. The cognition loop's contradiction resolver couldn't keep up, causingcontradictevent spam in the dashboard. Keys are now generated from the finding summary (unique per finding). cargo build --releasetruncated 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_outputtest to expect ALLOW JSON output instead of empty stdout.
Added
ctx_tree/lean-ctx lsgitignore toggle: Newrespect_gitignoreparameter (MCP) /--no-gitignoreflag (CLI) to show files regardless of.gitignorerules. Default: gitignore respected (backward compatible). Fixes user report where all-gitignored folders appeared empty.LEAN_CTX_SHELL_ALLOWLIST_OVERRIDEenv var: Completely replaces the config-based allowlist for deterministic testing. UnlikeLEAN_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_guardeviction callback only calledjemalloc_purge(), which returns already-freed pages to the OS but never evicts actual data (SessionCache, BM25 index, etc.). Now a newEvictionOrchestratorbridges the RSS-based memory guardian to theHomeostasisController, 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 withlet _ =. Now logs failures viatracing::debugfor diagnosability.is_under_pressure()no longer expensive in hot loops: Was callingMemorySnapshot::capture()(which doesConfig::load()+ syscalls) on every invocation in BM25/graph index builders. Now reads a cachedAtomicU8flag set by the guardian thread — O(1) with zero allocations.
Added
EvictionOrchestrator(core/eviction_orchestrator.rs): New module connectingmemory_guard(RSS monitoring) toHomeostasisController(graduated eviction). HoldsArcreferences toSessionCacheandSharedBm25Cache, executes eviction actions with non-blockingtry_read/try_writeto 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 increasingmax_ram_percentwhen under pressure.
[3.6.20] — 2026-05-27
Fixed
- Critical: OnceLock reentrancy deadlock on Linux (#301): All shell hook commands (
ls,cat, etc.) andlean-ctx updatehung after upgrading to v3.6.19. Caused byactive_profile_name()callingConfig::load(), which re-enteredfind_project_root()'sOnceLockviaSessionState::load_latest()→normalize_loaded_session()→active_profile(). Fixed by reading theprofileconfig key directly from disk (bypassing the fullConfig::load()pipeline) and removing theactive_profile()call from session normalization.
[3.6.19] — 2026-05-26
Added
- Built-in
passthroughprofile: No output modification — always full content, zero compression. Use viaLEAN_CTX_PROFILE=passthroughorlean-ctx config set profile passthrough. Includesdefault_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
profilefield in config.toml provides a fallback whenLEAN_CTX_PROFILEenv var is not set. Resolution order: env var → config.toml → "coder" default. Set vialean-ctx config set profile <name>. - Profile config schema entry:
lean-ctx config shownow displays theprofilekey.
Fixed
LEAN_CTX_FULL_TOOLS=0incorrectly treated as ON:is_ok()only checked existence, not value. Now=0and=falseare correctly treated as disabled.mode=fullreturning stubs/deltas in passthrough mode:handle_full_with_auto_deltaignoredno_degradeand passthrough profiles. Cache stubs and auto-deltas are now skipped whenno_degrade=trueor when the active profile hasdefault_mode=full+crp_mode=off.- MCP schema claimed default mode was
full: Thectx_readtool description said "default: full" but the actual default wasauto(resolved by AutoModeResolver). Agents that omitted themodeargument got compressed output instead of full content. Schema now correctly states "default: auto". - Silent fallback to
coderprofile: WhenLEAN_CTX_PROFILEpointed to a non-existent profile name, lean-ctx silently fell back tocoderwithout any warning. Now logs atracing::warnwith the missing profile name and creation instructions.
[3.6.18] — 2026-05-26
Added
- Structured read modes for non-code files —
ctx_readmodemapnow 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 acrossmode_predictor.rs,context_gate.rs, andintent_router.rs. Single entry pointresolve()produces a deterministic mode decision with full trace logging. Config/data files likeCargo.toml,package.jsoncorrectly getfullmode while structured formats (JSON, YAML, TOML, lock files) are routed tomapmode (#297) - GraphProvider unified facade —
GraphProviderenum now wraps bothPropertyGraph(SQLite, symbol-level) andProjectIndex(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 directProjectIndexusage toGraphProvider(#298) - Template instructions SSoT — New
rules_canonical.rsmodule providescanonical_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 enforcement —
apply_utf8_locale()setsLC_CTYPE=C.UTF-8as fallback when no UTF-8 locale is inherited from the parent process. Applied to all 5 shell spawn paths (MCPexecute_command_with_env, CLIexec_direct/exec_inherit/exec_buffered, CLIpassthrough). Fixes Cyrillic/CJK/emoji M-notation mangling on Linux where Cursor spawns without user shell profile
Fixed
mode=fullsilently downgraded (#295) — Explicitmode=fullrequests were being overridden by the pressure degradation system and context gate heuristics.fullmode 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,ghdata 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_prefetchcrash without graph —ctx_prefetchnow gracefully falls back to direct prefetching ofchanged_fileswhen 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_catalogentries, causingctx_overviewto report "0 files".open_best_effortnow requiresfile_catalog_count > 0on 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-8fallback 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 ofclone()— 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_CAPswitched 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_hintinto single shared function - SessionCache eviction upgraded from segmented LRU to RRF (Reciprocal Rank Fusion) scoring combining recency, frequency, and size signals
- BM25 index cache uses
- Dead code removal — Removed unused
migrate_index_to_property_graphandremove_file_catalogfunctions after graph consolidation
[3.6.17] — 2026-05-25
Added
- Antigravity CLI 2.0 as separate init target —
lean-ctx init --agent antigravity-cliwrites MCP config to~/.gemini/antigravity-cli/mcp_config.json, distinct from the IDE target (antigravity).lean-ctx init --agent gemininow auto-configures both Antigravity IDE and CLI paths (#284) - Doctor: daemon diagnostics —
lean-ctx doctorshowssystemctl --user is-activestate on Linux, warns whenloginctl enable-lingeris 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) —
.bakfiles containing lean-ctx content,.lean-ctx.invalid.*.baktemporaries,~/.config/lean-ctxXDG data directory, and project-local.lean-ctx/+.lean-ctx-idfiles are now cleaned up. Claude CLI MCP registry entries removed viaclaude mcp remove.--keep-configflag preserves MCP configs and rules for reinstall - Linux daemon autostart (#288, #289) —
systemctl --user enablefailures now print actionable error messages with manual fix commands.is_installed()checkssystemctl is-enabledin 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 ofsplit_whitespace().shell_quote()properly quotes arguments containing special characters - Windows drive-letter grep parsing —
parse_grep_line()andextract_file_from_match()correctly skipC:drive prefix, preventing misinterpretation as file path separator - Panic loop-undo (#277, #271) —
catch_unwindhandler incall_toolnow callsrecord_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 inshell/exec.rswithplatform::is_powershell(), single source of truth - Windows CI (#286) — Fixed
unused variable: quietindaemon_autostart.rson 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 statusCLI output shows real values instead of nulls - Workflow singleton — Workflow state is now agent-scoped (
workflow-{agent_id}.json) instead of globalactive.json. Stale workflows auto-cleaned after TTL expiry - JSONC UTF-8 safety —
strip_json_commentsusesfloor_char_boundary/ceil_char_boundaryfor all string slicing, preventing panics on multi-byte characters in comments ls -lahsize passthrough — Human-readable sizes (e.g.4.0K,1.2M) fromls -lh/ls -lahare preserved instead of being converted to0B- MCP server crash hardening —
Mutex::lock().unwrap()in hot paths replaced with graceful fallbacks.memory_guarduses eviction loop instead ofprocess::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 support —
lean-ctx init --agent openclawwrites the MCP server entry to~/.openclaw/openclaw.jsonundermcp.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 doctordetects OpenClaw installations.lean-ctx setupauto-configures when~/.openclaw/exists - Context package graph-native architecture (
.ctxpkgv2) — NewContextGraphdata 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). Newdocs/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_sessionfinding panic on em-dash (#272) —parse_finding_valuecrashed 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 usingstr::len(). Added 6 regression tests including exact repro with Cyrillic text from the issue report- Panic handler returns
isError: false— Thecatch_unwindblock in the MCP server returned panics as successful tool results (isError: false), hiding crashes from AI agents. Now returnsCallToolResult::errorsoisError: trueis 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_guardhardprocess::exit(137)replaced with 3-attempt eviction loop — server now aggressively reclaims memory but never hard-exits- Nested
block_in_placeinbounded_lockeliminated to prevent Tokio blocking-pool exhaustion under concurrent tool calls - CSPRNG
expect()in dashboard nonce/token generation replaced with time-based fallback
- 5
parse().unwrap()for SocketAddr in 2 dashboard routes replaced with directSocketAddr::new()constructiontempfile().expect()inctx_executereplaced with graceful error return
Changed
- Dashboard: modular route architecture — Monolithic
context.rs(617 lines) andgraph.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 inlib/api.jswith event-based data broadcasting - Dashboard: removed dead code — Deleted legacy
dashboard.html(3057 lines) andCockpitContextLayercomponent
Added
- Context Commander — New action-oriented dashboard component with context pressure visualization, budget bands, and risk analysis
- Configurable proxy timeout —
LEAN_CTX_PROXY_TIMEOUT_MSenv var /proxy_timeout_msin config.toml (default: 200ms) (#270) - Dynamic tool categories —
LCTX_DEFAULT_CATEGORIESenv var /default_tool_categoriesin config.toml to control which tool categories are active by default - Global degradation disable —
LCTX_NO_DEGRADE=1env var /no_degrade = truein config.toml to globally disable all read mode degradation
[3.6.14] — 2026-05-22
Added
- First-class Augment AI agent support —
lean-ctx init --agent augmentwires 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 doctorreports per-surface MCP drift including"disabled": truedetection. 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.ctxpkgextension. Legacy.lctxpkgfiles remain importable for backward compatibility ctx_multi_readserver-side output cap — Output capped at 512KB by default (configurable viaLCTX_MAX_MULTI_READ_BYTES) to prevent MCP client-side truncation. When exceeded, remaining files are skipped with a clear warning (#263)- Degradation policy warning —
auto_degrade_read_mode()now emits an explicit⚠ Context pressurewarning whenmode=fullis downgraded tomode=mapormode=signatures, including the verdict and bypass hint (start_line=1orctx_compress) (#262) - 28 new regression tests — 14 UTF-8 boundary tests (Cyrillic, CJK, emoji, exact user scenario), 10 degradation verdict tests, 4
ctx_multi_readcap 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 --applybug, Graph edge import (usesget_node_by_symbolinstead ofget_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 valueand--flag=value), memory leaks (.leak()removed), HTTP response status checking forsend lean-ctx updateproxy race condition —post_update_rewire()now restarts the proxy and waits for health before writingANTHROPIC_BASE_URLto 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
.ctxpkgand.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.rsmodule 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 setupauto-configures VS CodeplanAgent.additionalToolsand Claude Codepermissions.allowentries. Includeslean-ctx doctorplan mode status check - MCP
readOnlyHinttool annotations — All read-only MCP tools now declarereadOnlyHint: truein 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 declarereadOnlyHint: false - Dynamic tool filtering — New
server/dynamic_tools.rsmodule 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_TOKENis 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 bothctx_providerandctx_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 inctx_read, and contributes knowledge facts - MCP Bridge stdio transport support —
[providers.mcp_bridges.<name>]now acceptscommand+argsfor stdio-based MCP servers in addition to HTTPurl. Bridges register with unique IDs (mcp:<name>) and supportresources,read_resource, andtoolsactions - Cross-source hints in
ctx_read— When reading a file,ctx_readnow shows related issues, PRs, and external data linked via the graph index (e.g., "Related: [Issue] github://issues/42 — Auth bug") ctx_semantic_searchexternal result attribution — Search results from external providers now show clear type labels:[Issue],[PR],[Ticket],[Schema],[Wiki]with full provider URIslean-ctx doctorMCP bridge diagnostics — New diagnostic section validates configured MCP bridges (URL reachability, config completeness,auto_indexstatus warning)lean-ctx doctorplan 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.rstest 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.rscovers full pipeline, cross-source edges, knowledge extraction, MCP bridge registration, multi-source consolidation - Plan mode scenario tests — New
plan_mode_scenarios.rswith 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.rswith 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.rswith 14 scenarios testing bounded lock timeouts, concurrent access, I/O health escalation, and WSL2/NFS environment detection LEAN_CTX_CLIENT_HINTenv override — Client capability detection can now be overridden for testing and edge-case environmentslean-ctx doctorprovider status — Shows active providers and their auth statuslean-ctx doctorCopilot CLI MCP check — Separate diagnostic for Copilot CLI MCP configuration (distinct from VS Code MCP)- VS Code Extension
.vscode/mcp.jsonsupport — New standard path withtype: "stdio"transport ctx_ledger resetclears 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,doctorcommands
Fixed
- PowerShell
@argssplatting fails on single commands —_lcfunction now resolves the native command viaGet-Command -CommandType Applicationbefore invocation, preventing "not recognized" errors when@argsis used with compound argument strings - Fish shell
lean-ctx-offleaks env var —set -e LEAN_CTX_ENABLED(which removes the var) changed toset -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-offleaks env var —unset LEAN_CTX_ENABLEDchanged toexport LEAN_CTX_ENABLED=0for consistent disable semantics across shells - Provider init ignores project root —
ctx_providerandctx_preloadnow callinit_with_project_root(Some(root))instead ofinit_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-Dwarningsfailure onwindows-latestCI - Lock contention in 12 MCP tools —
ctx_read,ctx_edit,ctx_delta,ctx_fill,ctx_handoff,ctx_knowledge,ctx_multi_read,ctx_smart_read,ctx_prefetch,ctx_ledger,ctx_preload,ctx_providernow 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_lexerUnicode — 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_readlock contention — Returns actionable error message instead of hanging silentlyctx_readnot-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_knowledgerehydrate time budget — Capped at 10 seconds to prevent blockingctx_executerespectsGIT_PAGER/PAGER— Only sets pager env vars when not already set by user
Changed
providers.auto_indexdefault is nowtrue— New installations automatically index provider data into BM25/Graph/Knowledge stores. Previously defaulted tofalse(cache-only)- MCP tool count — 61 → 62 (added
ctx_provider) - Tool descriptions — Updated
pkgdescin AUR packages anddescriptionin Cargo.toml to reflect 62 tools ctx_readpost-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 afterinit.rsrefactoring),hints_from_index()(unused wrapper),is_running_in_powershell()(Windows-only, never called), unusedProjectIndeximport - Inline eviction/elicitation hints in
ctx_readresponse — 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
ContentChunkabstraction,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_providertool — 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>), supportsresources,read_resource, andtoolsactions. Newmcp_resourcesconvenience action onctx_providerlists 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 doctorMCP bridge check — New diagnostic section validates configured MCP bridges (URL reachability, config completeness,auto_indexstatus)core/io_healthmodule — Environment detection (WSL2, NFS, FUSE, sshfs), freeze counter with 60s decay window, adaptive timeout calculation (Fast/SlowFs/Degraded escalation levels)server/bounded_lockmodule — Self-healing lock acquisition helpers for all MCP tools; returnsNoneon timeout allowing graceful degradation instead of indefinite hangscore/output_sanitizermodule — Last-pass output filter that detects and removes degenerate CJK runs, symbol floods, and garbled artifacts before output reaches the clientlean-ctx proxy cleanupcommand — Removes staleANTHROPIC_BASE_URLentries from Claude Code/Codex settings when the proxy is disabledlean-ctx doctorstale proxy check — New diagnostic that detectsANTHROPIC_BASE_URLpointing 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_URLpointing to127.0.0.1:4444in Claude Code settings, causing 401 errors.doctor --fixandproxy cleanupnow 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 usebounded_lockhelpers with adaptive timeouts. System auto-detects slow environments and adapts: 3+ freezes in 60s → degraded mode (ReDev1L report) - Proxy auto-starts without explicit enable —
spawn_proxy_if_needed()now checksproxy_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 failure —
canonicalize_boundedtest now usesstd::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_indexdefault is nowtrue— New installations automatically index provider data into BM25/Graph/Knowledge. Previously defaulted tofalse(cache-only)ctx_semantic_searchexternal result formatting — Provider-sourced results now show clear attribution:[Issue] github://issues/42 — Auth buginstead of raw URIs- MCP Bridge unique IDs — Each configured MCP bridge registers with
mcp:<name>instead of sharedmcp_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→,okinstead 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/exewith(deleted)suffix.find_binary()in the systemd unit generator would write this corrupted path intoExecStart, causing systemd to pass(deleted)as a CLI argument on every restart. Now usesresolve_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_defs —
gen_mcp_manifestnow reads fromToolRegistry(61 tools) instead of staticgranular_tool_defs()(56 tools), ensuring the website manifest always reflects the actual registered tool count
Changed
- Context budget auto-escalation —
pressure_downgrade()now applies more aggressive mode downgrades based onContextPressure: SuggestCompression downgradesauto→map, ForceCompression downgradesfull→mapandauto|map→signatures - 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_tools —
list_toolshandler now reads tool definitions from the registry instead of statictool_defs/, eliminating schema drift between exposed schemas and handler implementations - OnceLock for project root —
find_project_root()result cached viastd::sync::OnceLock, eliminating repeatedgit rev-parsesubprocess calls - Compaction sync tail-seek —
find_latest_compaction()reads only the last 4KB ofcontext_radar.jsonlinstead 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 explicitmode=semanticmay 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=1forces unnecessary disk re-reads (#253) — Clients like opencode that always sendstart_line=1no longer trigger mode override tolines:1-999999+fresh=true.start_line=1is now correctly treated as a no-op since line 1 is the default. Onlystart_line > 1activates the lines-mode override- Git write-commands incorrectly compressed —
git commit,git push,git pull,git merge,git rebase,git cherry-pick,git tag,git resetare 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
CompressionPatterntrait (patterns/) andContextProvidertrait (providers/) for future plugin extensibility - Pytest verbose compression — Dedicated pattern for
pytest -voutput: 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 aboutfresh=truefor 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.rsmodule 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, +7–12.8% recall) — File stem and parent directory are doubled into BM25 document content, enabling path-aware queries like "auth handler"
find_relatedaction inctx_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 permanently —
handle_completenow clears the workflow file (terminal state) instead of persisting it. Added safety nets: gate auto-clears stale "done" workflows,list_toolsno longer restricts visibility in terminal state, andctx_handoffpull/import refuses to restore "done" workflows ctx_readlines:N-M mode hangs on large files — Line-range reads no longer trigger expensivebuild_graph_related_hintandfind_similar_and_update_semantic_indexcomputations (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 vialean-ctx update - User overrides (
[model_context_windows]in config.toml) — highest priority - Supports exact match, prefix match (e.g.
gpt-5.5-0513matchesgpt-5.5), and family fallback - GPT-5.5: 1,048,576 | GPT-4.1: 1,047,576 | Gemini: 1,048,576 | Claude: 200,000
- Bundled registry (
-
ctx_shell
envparameter (#241) — New optionalenvobject 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 viapath_jail = falsein config.toml orLEAN_CTX_NO_JAIL=1env var. -
Copilot CLI support (#243) — Dedicated
CopilotCliconfig type that writes to~/.copilot/mcp-config.jsonwith the correct format (mcpServerskey,"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 copilotnow 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_hintsconfig key orLEAN_CTX_BYPASS_HINTSenv 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 hardenCLI — Activates strict enforcement mode (LEAN_CTX_HARDEN=1in MCP configs). Optionally denies Bash in Claude Code'spermissions.deny.lean-ctx export-rulesCLI — Exports high-confidence knowledge facts as editor-native rules (MDC for Cursor,AGENTS.md,CLAUDE.md).
- 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
Fixed
git status --porcelaintruncation — Shell compression no longer truncatesgit statusoutput when it doesn't match specific section parsing (e.g.--porcelain,--shortflags). Developers now always see full status information.init --agentrules injection — Global rules and skill file are now correctly injected. Fixed data dir split causing emptygainfield in responses. (#238, #239)- Integration test alignment —
rules_consistencyandrules_injecttests 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_DIRenv var instead of hardcoding~/.claude. Absolute paths under$HOMEare 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.mdwhich OpenCode never loads. Doctor check and uninstall updated accordingly. (#237) - Linux CI warnings — Fixed
unreachable_pubin Landlock module,borrow_as_ptrin syscall wrappers,unnecessary_wrapsonremove_linux_scheduler, andunused_variables/dead_codefor platform-gated items. - MCP Resource Notifications —
notifications/resources/updatedsent 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_changedis sent to subscribed clients so they re-fetch the tool list.notifications/tools/list_changed— Outbound notification sent after dynamic tool category load/unload viactx_load_tools. Clients automatically re-fetch the tool list.- MCP Peer Storage — Server stores the rmcp
Peer<RoleServer>frominitialize()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_loopor configure automatic runs withautonomy.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.0–1.0) anddecay_ratefields. 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 --schedulewith 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 setupnow 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. --quietflag for updater —lean-ctx update --quietsuppresses 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 withauto_update(default false),check_interval_hours(default 6), andnotify_only(default false). Overridable viaLEAN_CTX_AUTO_UPDATE,LEAN_CTX_UPDATE_INTERVAL_HOURS,LEAN_CTX_UPDATE_NOTIFY_ONLYenv vars.
Security
- Constant-time token comparison — Proxy bearer token validation uses
subtle::ConstantTimeEqto prevent timing side-channels. - Header forwarding allowlist — Proxy no longer blindly forwards all headers; only an explicit
FORWARDED_HEADERSallowlist is passed through. - Secret detection — Regex-based scanning for API keys, tokens, and credentials in file reads and tool output. Integrated into
io_boundaryas a pre-read filter. - Shell allowlist — Configurable command allowlist for sandboxed shell execution with
extract_base_commandvalidation. - 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 control —
Capabilityenum with per-tool requirements and per-role grants. Tools are denied if the agent's role lacks the required capabilities. - macOS Seatbelt sandboxing —
sandbox-execbased 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 -cquoting bug — Dynamic aliases (npm, pnpm, etc.) failed on PowerShell 5 withObjectNotFounderror because@argsinside double-quoted strings was splatted instead of treated literally. Fixed by extracting the script block body into a variable with backtick-escaped@args. commit→cmtstring mangling — The terse compression dictionary replaced "commit" inside compound words (pre-commit), quoted strings, and colon-delimited contexts. Fixedreplace_whole_wordto 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 incockpit-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-ctxvs~/.config/lean-ctxset viaLEAN_CTX_DATA_DIR). Ledger recording now correctly happens indispatch/mod.rsafter tool execution. - Config schema validation rejecting
ide_pathsandlspsections — Users configuring per-IDE allowed paths or LSP binary overrides received "Unknown key" warnings. Addedide_paths(dynamic keys),lsp(with language-specific entries), and top-levelproject_rootto 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 buildwhen 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 freshparameter — Newfresh: boolargument 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
doctorhealth check andconfig.tomlconfiguration support. - FTS5 archive search — Full-text search over archived context entries using SQLite FTS5 for fast historical queries.
- Project root configuration — Explicit
project_rootconfig option for multi-project workspaces. lean-ctx restartcommand — Restart all lean-ctx processes cleanly without manual kill.- Zed
ctx_editguard — Prevents accidental edits in Zed when file is not in project scope. LEAN_CTX_SAVINGS_FOOTERenv var — Shows compression savings in shell output when enabled.enable_wakeup_ctxconfig 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). SetLEAN_CTX_PI_MODE=replacefor the old behavior that disables overlapping builtins. ctx_multi_readstale cache (#230) — Subagents that inherit the parent's process but not its cache state can now usefresh: trueto bypass stale entries.ctx_readdeadlock with concurrent subagents (#226, #229) — Reduced lock contention by minimizingblocking_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 stopnow 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_shellmissing compression info (#225) —renderCall/renderResultproperly delegated tobaseBashTool; compression savings now visible in Pi agent output.- Windsurf hooks installation —
hooks.jsonis now installed regardless of the--globalflag, 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,pinnedas 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;
--libflag for macOS tests prevents daemon hangs;msys2/setup-msys2action pinned to prevent supply-chain attacks; background index build skipped whenLEAN_CTX_DISABLEDis 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_windowconsolidated — Redundant branches merged: Claude/O-series → 200k, GPT/Codex/DeepSeek → 128k, Gemini → 1M, Mistral/Codestral → 256k.- Pi extension dependencies — Deprecated
@mariozechnerlibraries replaced with@earendil-workspackages. (#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, andctx_radartool. - 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 withRequestBreakdownstruct 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 providesRulesTokensestimate for system prompt budget. - Windows Named Pipe IPC — Reliable daemon IPC using
WaitNamedPipeWfor proper pipe existence checks (replaces brokenfs::metadata), retry loop with 50ms backoff onERROR_PIPE_BUSYandNotFound, stderr fallback toinherit()instead ofnull()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_trackermodule 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_gatemodule 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_changedfor clients that support it; Windsurf 100-tool limit handled automatically. - Client Capability Detection — Runtime detection of 9 IDE clients with Tier 1–4 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, andfindcommands 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
/new—BudgetTrackerandcontext_radar.jsonlnow reset on MCPinitialize(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_blocknow includes tool-preference reinforcement line (ctx_read>Read ctx_shell>Shell ...) for sessions with 3+ tool calls, surviving IDE compaction. ctx_readhang in subagents (#215) — Removed redundanttokio::task::block_in_placecall and minimizedcache_lock.blocking_write()scope to prevent async runtime contention.ctx_read57s 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-binstallfailures (#213) — Development-only binaries (gen_mcp_manifest,gen_tdd_schema) moved from[[bin]]to[[example]]socargo installandcargo-binstallskip them. - Windows
doctorbashrc false positive (#214) —is_active_shell_implnow checksBASH_VERSIONon Windows before flagging.bashrcas outdated. - Windows
env.shbash validation — Skipbash -nsyntax check on Windows where backslash paths are invalid bash. - Windows named pipe
pipe_exists_truetest — Changed#[test]to#[tokio::test]sinceServerOptions::create()requires a Tokio runtime context. - macOS process hangs on update — Atomic binary replacement prevents corruption during self-update.
env.shfor-loop syntax error (#212) — Removed2>/dev/nullfromfor _lf inloop that broke POSIX shell parsing.- JSONL audit trail lost on reset — Session reset and new session events now rotate
context_radar.jsonlto.previnstead of truncating.
Changed
- Logging defaults — CLI default remains
warn(clean output); daemon/MCP mode defaults toinfo. Earlyinit_logging()inrun()skips MCP entry paths soinit_mcp_logging()can set its own level. - Radar memory cap —
load_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_blocklowered from >10 to >3 tool calls, matching typical compaction timing in Claude Code (5–8 calls). lettreadvisory ignored — RUSTSEC-2026-0141 (Boring TLS hostname verification) added todeny.tomlandaudit.tomlignore lists; lean-ctx uses rustls, not Boring TLS.
[3.5.25] — 2026-05-13
Added
- Process concurrency guard — New
process_guardmodule limits concurrentlean-ctxprocesses to 4 viaflock/fcntlslot locks, preventing CPU saturation when multiple agents trigger simultaneous operations. - Terse pipeline input cap & timeout —
compress()now skips inputs >64KB and enforces a 500ms deadline with per-stage budget checks, preventing runaway CPU on large outputs (#210). - Trigram set cap —
scoring.rslimits theseen_trigramsHashSet to 10,000 entries, preventing unbounded memory growth on large inputs. - Property-based compression tests — Added
proptestdev-dependency with invariant tests:safeguard_rationever inflates,entropy_compressnever exceeds original tokens,compress_outputnever inflates, and entropy output is a subset of input lines. - Canonical rules policy — New
rules_canonical.rsmodule 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
instructionsfield — Editor MCP configs now include aninstructionsfield (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 useCRITICAL: ALWAYS,MUST USE, andNEVER USEinstead ofPREFER/should. Ensures agents treat lean-ctx tool usage as mandatory. - Background index throttled —
spawn_index_build_backgroundnow runs withnice -n 19andionice -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 optimization —
apply_dictionariesperforms case-insensitivecontains()check beforereplace_whole_word, reducing unnecessary string operations. - Quality gate optimization —
extract_identifierscapped at 200 entries; identifier lookup incheck()uses HashSet instead of linearcontains(). - Entropy compression safeguard —
entropy_compressnow falls back to the original content when compression would inflate token count.
Fixed
- 100% CPU on
tersewith 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 tests —security_hardening.rsandsecurity_resolve_path_guard.rsupdated 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, andlet...elsepatterns across multiple files.
[3.5.24] — 2026-05-13
Changed
- Eliminate
CliRedirecthook mode — Removed theHookMode::CliRedirectvariant entirely. All agents now use eitherHybrid(MCP for reads/search + shell hooks for command compression) orMcp(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 installation —
lean-ctx init --agent cursorandlean-ctx setupnow automatically install the lean-ctx MCP server config in~/.cursor/mcp.jsonwith 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_readwhile preserving shell compression via hooks. - All agents default to Hybrid —
recommend_hook_mode()now returnsHybridfor 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 toMcp. - Hybrid rules template v2 — Updated
.cursor/rules/lean-ctx.mdctemplate to clearly instruct agents to usectx_readandctx_search(MCP) for reads/search, andlean-ctx -c(CLI) for shell commands. - SKILL.md updated — Removed
--mode cli-redirectexamples, updated to show Hybrid as the default mode for all agents.
Added
LEAN_CTX_QUIET=1production 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.tomlunder[mcp_servers.lean-ctx.env]to match default Codex output verbosity.- Redirect subprocess timeout increased —
handle_redirecttimeout increased from 3s to 10s for more reliable operation on slow filesystems.
Removed
HookMode::CliRedirect— Enum variant, all match arms,CLI_REDIRECT_RULESconstant,build_cli_redirect_instructions()function, and thelean-ctx-cli-redirect.mdctemplate file have been removed.DedicatedCliRedirect/CursorMdcCliRedirect— Rules injection variants removed fromrules_inject.rs.disable_agent_mcp()call path — Theinit_cmd.rscode path that calleddisable_agent_mcp()for CliRedirect agents has been removed. All agents now callconfigure_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.mdcnow 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_guardmodule 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%). Usesjemallocas global allocator on Unix for aggressive memory return (dirty_decay_ms:1000,muzzy_decay_ms:1000). Newjemalloc_purge()andforce_purge()for explicit arena cleanup. Platform-specific RSS reading viatask_info()(macOS) and/proc/self/status(Linux). New dependencies:tikv-jemallocator,tikv-jemalloc-ctl,zstd,memmap2. - zstd-compressed session cache —
CacheEntrynow stores content as zstd-compressedVec<u8>instead of rawString, reducing in-memory cache footprint by ~60–80%. NewCacheEntry::new(),content(),set_content()API.SessionCache::store()signature changed fromcontent: Stringtocontent: &str. - Memory estimation and unload for indexes —
BM25Index::memory_usage_bytes()/unload()andEmbeddingIndex::memory_usage_bytes()/unload()enable the RAM Guardian to reclaim index memory under pressure. - Dashboard memory API — New
/api/memoryendpoint exposing RSS, peak RSS, system RAM, pressure level, allocator type, and max sessions. lean-ctx doctorRAM Guardian diagnostics — Doctor output now shows current RSS, system RAM, percentage, limit, and allocator type.- Configurable savings footer suppression — New
savings_footerconfig option (auto|always|never) andLEAN_CTX_SAVINGS_FOOTERenv var. Inautomode (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 shutdown —
LeanCtxServer::shutdown()clears cache, saves session, and triggersforce_purge()on MCP client disconnect. - Config schema:
max_ram_percent,savings_footer— Both new configuration keys exposed vialean-ctx config schemawith types, defaults, descriptions, and env var overrides.
Fixed
- CLI savings footer bypass —
cli/common.rs::print_savings()was formatting footers independently ofprotocol::format_savings(), ignoring thesavings_footerconfiguration. 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. Newfilter_daemon_output()function strips footer lines client-side based on the client's own footer configuration. - Shared session store cap — Reduced
MAX_CACHED_SESSIONSfrom 64 to 8 to prevent unbounded memory growth in multi-IDE setups.
Changed
CacheEntryAPI — 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() race —
read_file_lossynow opens the file first and usesfstat()on the file descriptor instead of a separatestat()syscall. Fixes sporadic "No such file or directory" errors in Docker overlay/FUSE filesystems (e.g. Codex sandboxes) wherestat()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 inipc/unix.rsandipc/windows.rs— no other module needs#[cfg(unix)]for daemon logic.windows-sys0.59 added as target dependency. Implements #209. - HTTP-based daemon shutdown — New
POST /v1/shutdownendpoint enables cross-platform graceful daemon shutdown.stop_daemon()now tries HTTP shutdown first, thenSIGTERM/TerminateProcessas fallback, then force kill as last resort. No more directlibc::kill(SIGTERM)indaemon.rs. build_app_router()extraction — Shared Axum router construction extracted fromserve()andserve_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 tracking —
CallGraph::build_parallel()uses rayon for concurrent file analysis. Newget_or_start_build()returns cached results immediately or starts a background build with live progress (BuildProgressstruct withfiles_total,files_done,edges_found). Dashboard polls via/api/call-graph/status. - Dashboard: call graph progress bar —
cockpit-graph.jsshows 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 Lab —
cockpit-compression.jsnow 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-filesAPI 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 setupon Ubuntu — Two root causes fixed: (1)env.shself-heal script could recursively spawnlean-ctx initviaBASH_ENVoutside containers. Now guarded with container detection (/.dockerenv), recursion guard (_LEAN_CTX_HEAL), andLEAN_CTX_ACTIVEpropagation. (2) Graph index scanning could scan entire$HOMEwhensetupwas run outside a project. Now guarded withis_safe_scan_root()check, cross-process lock (startup_guard), 50k entry limit, and 2-minute timeout.LEAN_CTX_NO_INDEXenv var skips indexing entirely. Fixes #210. daemon.rs/daemon_client.rsnow platform-independent — Removed all#[cfg(unix)]gates fromlib.rs,cli/dispatch.rs, andsetup.rsfor daemon modules.daemon_client.rsauto-start works on all platforms (previously returnedNoneon non-Unix).- Dashboard call graph timeout — Increased from 15s/30s to 60s to accommodate larger projects during initial build.
Changed
serve_uds()replaced byserve_ipc()— Takes aDaemonAddrenum instead of aPathBuf. Callers usedaemon::daemon_addr()instead ofdaemon::daemon_socket_path().daemon_socket_path()removed — Replaced bydaemon::daemon_addr()which returns aDaemonAddrenum. All call sites updated (setup.rs,dispatch.rs).- Security hardening test updated —
uds_socket_sets_permissionsnow checksipc/unix.rsinstead ofhttp_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&Pathto&str. All 12+ call sites updated. Hardcoded.lean-ctx/graph.dbstrings inctx_impactandctx_architecturereplaced with actual resolved paths. Fixes #205. - Graph index UX: correct labels and configurable cap —
lean-ctx gainnow 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 keygraph_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_aliasesfields:command/aliasnotname/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/:idendpoint 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 validatesconfig.tomlagainst 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/withoutLEAN_CTX_DATA_DIR, transparent migration (move and skip-when-exists),meta_path()integration, andCodeGraph::open()with custom data directory.
[3.5.20] — 2026-05-12
Fixed
- Codex installer respects
CODEX_HOME—lean-ctx init --agent codexnow reads theCODEX_HOMEenvironment 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 whenCODEX_HOMEpointed elsewhere. All 11 call sites updated to useresolve_codex_dir(). Fixes #202. - Codex feature flag migrated from
codex_hookstohooks— The installer now writeshooks = true(the current Codex feature flag) instead of the deprecatedcodex_hooks = true. Existingcodex_hooks = trueentries are automatically migrated tohooks = trueduring install. The uninstall parser also handles both variants. Fixes #203. lean-ctx lsrejects unsupported flags — Flags like-la,-l,-Rare 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 passlsflags transparently to the systemls. Fixes #201.- Windows path format for inline rewrites —
handle_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 vianormalize_tool_path(). Bash shell hooks still useto_bash_compatible_path()as before. Newfrom_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 newfrom_bash_to_native_path()tests including Windows/Unix roundtrips. Platform-specificsanitize_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-zshcustom/) 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=inlineor--style=dropin. Transparent migration between styles preserves hand-edits via timestamped backups (.lean-ctx-<UTC>.bak). (#196) - Output policy classification — New
OutputPolicyenum (Passthrough,Verbatim,Compressible) provides centralized command classification for the compression pipeline. Commands likegh api,az login,docker ps,kubectl get podsare 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 fromslo.snapshot.slosarray; Verification card usesverif.total/verif.pass;streak_days === 0no longer hidden by falsy checkcockpit-health.js— SLOs render from.slos(not.results); Anomalies handle direct array response; Verification uses correcttotal/pass/warn_runsfields; Bug Memory (Gotchas) usestrigger/resolution/occurrences/first_seenand handles enumseverity/categorycockpit-agents.js— Swimlanes use actual API fields (id,role,status,status_message,last_active_minutes_ago,pid) instead of expected-but-absentname/model/tool_callscockpit-memory.js— Episodes useactions.lengthfor tool count,tokens_usedfor token display, and parse taggedOutcomeenum correctlycockpit-live.js—tokens_saved === 0no longer hidden by falsy check inbuildToolDetailcockpit-compression.js— Removed unsupporteddiffmode from UIcockpit-graph.js— Tooltip dynamically shows "B", "tok", or "lines" based on available size metric
- Token Pressure accuracy — Context field
temperaturenow usespressure.utilization(weighted decay) instead of rawtotal_tokens_sent / window_size, andbudget_remainingusespressure.remaining_tokensfor consistency with the Token Pressure card - Truncation bug causing increased token usage — Removed aggressive 8000-byte fallback truncation in
patterns::compress_outputthat produced[… N lines omitted …]markers, causing AI models to retry commands. Large outputs now flow through the safety-awarecompress_if_beneficialpipeline instead. Fixes #199. - Dashboard format utilities —
pc()NaN guard for percentage formatting;fu()type guard for unit formatting;fmtNumnormalized to consistent 'K' suffix - Dashboard route visibility — All dashboard route handlers narrowed from
pub fntopub(super) fn - Clippy
duration_suboptimal_units—Duration::from_millis(30_000)→Duration::from_secs(30)in 4 locations - Shell hook:
lsandfindmissing from alias list — Both commands are now included asCategory::DirListin the generated shell hook, solsandfindoutput 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: whenLEAN_CTX_AGENT,CODEX_CLI_SESSION,CLAUDECODE, orGEMINI_SESSIONenv vars are present, commands are tracked even in non-interactive shells (Docker, Codexbash -c). Fixes #200. - Flaky SSE replay test — Rewrote
events_endpoint_replays_tool_call_eventto append directly to the event bus instead of depending on a fire-and-forgetspawn_blockingtask, eliminating CI timing failures on contended runners.
[3.5.18] — 2026-05-12
Fixed
gh apioutput no longer compressed — Commands likegh api repos/.../actions/jobs/.../logsare 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 togh run view --logand--log-failedflags.
[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_TOKENenvironment variable, preventing unauthorized access from other local processes. - [Critical] Symlink hijack protection on all write paths —
write_atomic()and context packageatomic_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 validation —
claude mcp add-jsonnow validates that the resolvedclaudebinary comes from a trusted directory (.claude/,/usr/local/bin/,/opt/homebrew/, etc.), preventing PATH hijack attacks. Override withLEAN_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 transparency —
lean-ctx setupnow displays a banner listing all auto-approved MCP tools with count. New--no-auto-approveflag disables auto-approve in editor configurations. - [High] Full integrity verification for context packages —
verify_integrity()now validatescontent_hash,sha256(composite hash of name:version:content_hash), andbyte_size— previously onlycontent_hashwas checked. - [High] PathJail TOCTOU — documented accepted risk — Documented in SECURITY.md that the race condition between
jail_pathcheck and file operation requiresopenat/O_NOFOLLOWat 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=requireinDATABASE_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.bakfile. - [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 tracking —
KnowledgeFactstruct now includesimported_from: Option<String>field, set toname@versionwhen 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— Thelean-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
intentparameter — Thectx_executetool interpolated theintentparameter 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::fillcalls (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-demoendpoint allowed absolute paths to bypasspathjailfilesystem jail. All paths now go throughjail_pathunconditionally. - [Critical] MCP stdio integer overflow — Malicious
Content-Lengthheaders could cause integer overflow in frame length calculation, leading to unbounded memory allocation. Now useschecked_addwith 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 fromscript-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_executeoutput not redacted — Output fromctx_executebypassed the redaction engine, potentially leaking secrets. Now appliesredact_text_if_enabledlikectx_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 underproject_hashsubdirectory. - [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 leak —
internal_errorhelper 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
Noneand falls back to dead channel, preventing resource exhaustion. - [High] Rust sandbox inherited full environment — The
execute_rustfunction (rustc + compiled binary) did not strip inherited environment variables, exposing secrets and enablingLD_PRELOAD-style attacks. Now applies the sameenv_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_BUSYerrors under contention. - [Medium] ReDoS mitigation for filter rules — Both runtime and validation paths for user-authored filter TOML patterns now use
RegexBuilderwith 1 MiB DFA size limit. - [Medium] Context summary redaction —
/v1/context/summaryendpoint now redacts events atSummarylevel 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_searchandctx_treeparameter clamping —max_resultscapped at 500,depthcapped at 10 to prevent resource exhaustion. - [Medium]
ctx_shellcwd 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 incommunity.rsreplaced with proper error handling returning empty graphs instead of panicking. - [Medium] Defense-in-depth path canonicalization —
read_file_lossynow verifies canonical paths stay within project root (warning-only layer behind primaryjail_pathenforcement). - [Medium] Sandbox environment isolation —
ctx_executesubprocesses now start withenv_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 —
.ps1temp files are now deleted on all exit paths (success, spawn error, wait error). - [Medium]
ctx_executetemp file lifecycle —.dattemp files are now cleaned up by Rust after sandbox execution (regardless of script success), with file size validation before processing. - [Medium]
/healthrate limiting — Health endpoint no longer bypasses rate limiter and concurrency semaphore, preventing use as amplification oracle. - [Low]
validate_filter_fileregex bounds — Validation path now uses boundedRegexBuildermatching runtime behavior. - [Low] Corrected
check_secret_path_for_tooltool name — Changed hardcoded"ctx_read"to"resolve_path"for accurate policy logging.
Fixed
- Structural output protection —
git diff,git show,git blame,git log -p,git stash show,diff,colordiff,icdiff, anddeltaoutput 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: stripindexheaders, 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 pointsattention_placement— Attention allocation scoring based on recency, frequency, and structural importancecognitive_load— Cognitive load estimation using Halstead metrics and cyclomatic complexitycyclomatic— Cyclomatic complexity analysis via control-flow graph extractiongamma_cover— Gamma cover set selection for minimal representative context subsetsgraph_features— Property graph feature extraction (betweenness, clustering coefficient, community bridge detection)information_bottleneck— Information bottleneck compression with iterative Blahut-Arimotomdl_selector— Minimum Description Length model selection for compression strategymemory_consolidation— Memory consolidation with exponential decay and importance-weighted retentionprogressive_compression— Multi-level progressive compression with quality gatessplade_retrieval— Sparse Lexical and Expansion retrieval for context-aware searchstructural_diff— AST-level structural diff for semantic change detectionstructural_tokenizer— Language-aware tokenization using tree-sitter AST for 18 languages
- Louvain community detection O(m) — Rewrote
community.rsfrom 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.rsintoconfig/mod.rs,config/memory.rs,config/proxy.rs,config/serde_defaults.rsfor maintainability. shell_activationconfig option — Newshell_activationsetting inconfig.tomlwith three modes:always(default, backward-compatible),agents-only(auto-activates only in AI agent sessions like Claude Code, Cursor, Windsurf), andoff(fully manual). Controlled via config file orLEAN_CTX_SHELL_ACTIVATIONenvironment variable. Addresses feedback that lean-ctx shell hooks were too invasive for users who only need it in specific AI agent contexts..lean-ctx-idproject identity file — Projects can now declare a unique identity via a.lean-ctx-idfile 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 caches —
graph_index,semantic_cache,bandit, andembedding_indexnow 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
localhostafter v3.5.14 saw/api/stats: unauthorizedbecause 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_respondinghealth 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_readorctx_smart_readcaused 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_lossyrefuses unbounded reads onstat()failure, and MCP dispatch returnsErr(ErrorData)instead ofOk("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 acrossctx_read,ctx_smart_read,ctx_multi_read, andctx_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 tests —
dashboard_api_auth_never_bypassed_for_loopback,dashboard_probe_sends_bearer_token, loopback injection signature validation. memory_cleanupsetting — New config/env option (LEAN_CTX_MEMORY_CLEANUP) with two modes:aggressive(default, 5 min idle TTL — best for single-IDE use) andshared(30 min TTL — best when multiple IDEs or models share lean-ctx context). Visible inlean-ctx doctorandlean-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 returnedOk("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 centralizedcore::hashermodule. 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
Queryobjects are now cached inOnceLock<HashMap>statics inchunks_ts,signatures_ts, anddeep_queries. Eliminates re-compilation of query patterns on every file parse. Parser instances reuse viathread_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 onnodes(kind),nodes(kind, file_path),edges(kind),edges(source_id, kind),edges(target_id, kind).busy_timeout(5000ms)for WAL contention. - Parallel indexing —
rayon::par_iterfor CPU-bound deep-query parsing inctx_impact build(embeddings feature path). - ModePredictor Arc —
ModePredictorstored asArc<ModePredictor>to avoid deep cloning on everyctx_readcall. - Compact JSON serialization —
ProjectIndex::save()usesserde_json::to_string(compact) instead ofto_string_pretty, reducing index file size and serialization time. - Server dispatch deduplicated —
count_tokenscalled 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_readmode selection and an anti-pattern warning against usingfullfor 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 acquiretest_env_lock()to preventenv::set_varrace conditions. ContextBus tests use isolated temp SQLite databases viatest_bus()instead of shared global DB.
Added
core::hashermodule — Centralized BLAKE3 hashing:hash_hex(bytes),hash_str(s),hash_short(s). Single source of truth for all non-cryptographic hashing.core::communitymodule — Louvain-based community detection on the Property Graph (file clustering by dependency).core::pagerankmodule — PageRank computation on the Property Graph for file importance scoring.core::smellsmodule — Code smell detection (long functions, deep nesting, high complexity).ctx_smellstool — 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 CLIread_cmd. Fixes #159 regression, resolves GlemSom's report. - Markdown files exempt from aggressive compression —
.md,.mdx,.txt,.rstfiles no longer fall into theaggressivedefault bucket inpredict_from_defaults. These file types returnNone(= full mode) to prevent stripping prose/instruction content. - Windows Claude Code PowerShell compatibility — Claude Code hook matchers now include
PowerShell|powershellon 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 tests —
instruction_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 tokenization —
extract_chunksinbm25_index.rs,artifact_index.rs, andchunks_ts.rsno longer allocates atokens: Vec<String>per chunk. Token count is computed inline; the vector is set toVec::new().add_chunktokenizes fromcontentonce for the inverted index and overwritestoken_countfrom the fresh result. This eliminates one redundant allocation + tokenization pass per chunk during index build. - MemoryProfile fully wired — The
MemoryProfileenum (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 whenmemory_profile = low. - Embedding engine loading is skipped in
ctx_semantic_searchandctx_knowledgewhenmemory_profile = low.
- Doctor shows active memory profile —
lean-ctx doctornow 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.jsonto 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 withmode=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 newfull_content_deliveredflag 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.mdfrom a priorlean-ctx init --agent pibut 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
passedbut was not counted ineffective_total, causing the summary to show e.g. "16/15 checks passed". Fixed by including the daemon check in the total (+5instead 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 clones —
build_from_filesnow accepts&[(&str, &str)]instead ofVec<(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. Explicitctx_compresscalls still extract signatures. - Compressed output variants capped at 3 per cache entry — Prevents unbounded growth of the
compressed_outputsHashMap. - Codebook early-exit at >50,000 lines — Skips the codebook deduplication phase entirely for very large caches, preventing HashMap/HashSet memory explosions.
- Codebook uses borrows instead of clones —
[3.5.10] — 2026-05-09
Added
- 4-layer terse compression engine — Scientifically grounded compression pipeline replacing the legacy
output_density/terse_agentsettings with a unifiedCompressionLevelsystem (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 formax, dense atomic facts forstandard, concise bullets forlite. - Layer 4 — MCP Description Terse (
mcp_compress.rs): Compresses tool descriptions and lazy-load stubs for reduced schema overhead.
- Layer 1 — Deterministic Output Terse (
- Unified
CompressionLevelconfiguration — Singlecompression_levelsetting inconfig.tomlreplaces the legacyoutput_densityandterse_agentoptions. Resolution order:LEAN_CTX_COMPRESSIONenv var →compression_levelconfig → 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. Ifmaxlevel fails the quality check, automatically falls back tostandard. 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 fromlean-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 Q0–Q4, 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.jslibrary 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 robustness —
terse_agent_tests.rsrewritten to explicitly control bothLEAN_CTX_COMPRESSIONandLEAN_CTX_TERSE_AGENTenv vars, eliminating dependency on local config state. Mutex poison recovery added. 5 new tests for theCompressionLevelsystem alongside 6 fixed legacy backward-compat tests. - Intensive benchmarks updated —
intensive_benchmarks.rsnow benchmarks the new 4-layer terse pipeline instead of the removedprotocol::compress_output.
Fixed
- Token counter overflow (
counter.rs) —savings_pctno 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,OutputDensityfunctions fromprotocol.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.) toconfig.toml, the parent[mcp_servers.lean-ctx]section could be missing (e.g. after a v3.5.6 upgrade removed it).upsert_codex_tomlnow 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.mdnow references`~/.codex/LEAN-CTX.md`instead ofLEAN-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 search —
cockpit-search.jsfallbackesc()function wasfunction(s) { return String(s); }— no HTML escaping. Replaced with safetextContent→innerHTMLimplementation matchingformat.js. - CodeQL #38/#39 (Medium): Unpinned GitHub Actions —
codecov/codecov-action@v4andEmbarkStudios/cargo-deny-action@v2are now pinned to commit SHAs (b9fd7d16…,5bb39ff5…) inci.yml.
Fixed
- Codex config corruption on mode change (GitHub #189) — When
lean-ctx setuporlean-ctx updateran with v3.5.6 (where Codex was CLI-Redirect mode),remove_codex_toml_sectionremoved 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_sectionnow removes all TOML sub-tables via prefix matching when removing a parent section.ensure_codex_mcp_servernow detects orphaned sub-tables and inserts the parent section before them instead of appending at the end.ensure_codex_mcp_servernow usestoml_quote_valuefor Windows backslash-safe TOML quoting (was using rawformat!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:
- Load-time size guard —
BM25Index::load()now checks file metadata before reading. Indexes exceeding the configurable limit (default 512 MB) are quarantined by renaming to.quarantinedand skipped. - Save-time size guard —
BM25Index::save()refuses to persist serialized data exceeding the limit, preventing bloated indexes from being written in the first place. - Chunk count warning — Indexes with >50,000 chunks trigger a
tracing::warnsuggestingextra_ignore_patternsinconfig.toml. - 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. - File count cap —
list_code_files()stops collecting after 5,000 files per project, preventing runaway indexing in massive repos. - Configurable limit — New
bm25_max_cache_mbsetting inconfig.toml(default: 512). Override per-project or viaLEAN_CTX_BM25_MAX_CACHE_MBenv var. - Project root marker —
save()writes aproject_root.txtfile alongside each index, enabling orphan detection when the original project directory is deleted. lean-ctx doctorBM25 health check — Doctor now scans all vector directories, warns about large indexes (>100 MB), and fails for oversized indexes.lean-ctx doctor --fixautomatically prunes quarantined, oversized, and orphaned caches.
- Load-time size guard —
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 entry —
lean-ctx init --agent codexnow ensures[mcp_servers.lean-ctx]exists in~/.codex/config.toml. Previously, only CLI hooks andcodex_hooks = truewere written, leaving Desktop/Cloud variants without MCP access. - Codex LEAN-CTX.md upgrade detection —
install_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-Lengthheader 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
/legacyroute for backwards compatibility.
lean-ctx cache prunecommand — 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 doctorBM25 cache health check — Proactive diagnostics for BM25 index health, integrated into the standard doctor report.--fixauto-prunes.
Improved
- Codex instruction docs now document Hybrid mode —
~/.codex/LEAN-CTX.mdnow 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 update —
lean-ctx setupandlean-ctx updatenow 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 cleanup —
is_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-agentto 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 positive —
rg,grep,find,fd,ag, andackoutputs 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 anyrgsearch 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 thecontains_auth_flowguard 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_onlyguard for all shell patterns — Added a centralized length check inpatterns/mod.rsthat wraps every compressor (FilterEngine,try_specific_pattern,json_schema,log_dedup,test). No pattern can returnSome(result)unlessresultis strictly shorter than the original output. Eliminates a class of bugs where patterns claimed compression without actually reducing size. grepcompressor removes verbatim threshold — Removed the<= 100 linesearly return that passed smallrg/grepoutputs through uncompressed. All search outputs are now grouped by file with per-file match limits, regardless of size. Combined with theshorter_onlyguard, small outputs that can't be meaningfully compressed correctly returnNoneinstead of faking 0% savings.ghCLI verbatim returns replaced withNone—gh pr diff,gh api,gh search,gh workflow, and unknownghsubcommands no longer returnSome(output.to_string())(which falsely claimed compression). They now returnNone, allowing fallback compressors or the caller to handle the output appropriately.safeguard_ratioaligned 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_shellsearch command inflation guard — For search commands (rg,grep, etc.), the MCP handler now explicitly checksc.len() <= output.len()before using the compressed result, preventing any inflation from reaching the agent.- Codex
AGENTS.mdoverwrite —install_codex_instruction_docsnow 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
importevents tracked viaKnowledgeRememberedbus 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 fullscreen —
switchView()now closes any active fullscreen before tab transitions, restoring scroll in all views. (GitHub #186)
[3.5.4] — 2026-05-07
Fixed
ghCLI compression safety — Unknownghsubcommands (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 fromgh apiandgh searchoutput. The fix returnsSome(output)for unmatched commands (blocking fallback compression), matching the safe behavior already used bygitandglabpatterns.- Uninstall proxy cleanup —
lean-ctx uninstallnow cleans up Claude Code (ANTHROPIC_BASE_URLinsettings.json) and Codex CLI (OPENAI_BASE_URLinconfig.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/grepdaemon path resolution —lean-ctx ls .andlean-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
versionper (workspace, channel) and an optionalparentIdfor causal chains. - Consistency levels: Events classified as
local(informational),eventual(shared, async), orstrong(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, andreasoning(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_verifymapped tosessionMutations,knowledge,artifacts,searchscopes. - Session race fix:
SharedSessionStore::get_or_loaduses atomicentryAPI to prevent TOCTOU races under concurrent agent loads.
- Event versioning: Every event now carries a monotonic
- Configurable proxy upstreams — Teams routing through custom API gateways can now set
proxy.anthropic_upstream,proxy.openai_upstream, andproxy.gemini_upstreamvialean-ctx config setor environment variables. Upstreams are resolved once at proxy startup (env > config > default). - Proxy upstream diagnostics —
lean-ctx doctorvalidates proxy upstream URLs (self-referential loop detection, URL format) and reports which upstreams are active. - 6 new adversarial compression tests —
gh pr diff,gh api,gh search,gh workflowverbatim passthrough, plus shell-hook-level diff preservation test.
Changed
- Dry-run uninstall —
lean-ctx uninstall --dry-runnow 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 useexec_inheritare now counted viaexec_inherit_tracked(), andrecord_shell_commandno longer skips zero-token commands. Previously many commands went unrecorded in the dashboard. - SLO false positives —
CompressionRatioSLO 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 byfullmode reads. - X11 clipboard in vim — Removed explicit stripping of
DISPLAY,XAUTHORITY, andWAYLAND_DISPLAYenvironment variables fromexec_buffered, restoring X11 clipboard sync after exiting vim/vi in Claude Code. - pack_cmd unwrap —
LocalRegistry::open()now returns a graceful error instead of panicking on IO failures. - cursor.rs JSON type safety —
merge_cursor_hooksnow validates JSON types before unwrapping, preventing panics whenhooks.jsoncontains 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 runlean-ctx setup.
Changed
- Codebase maintainability — Split
doctor.rs(2,348 lines) intodoctor/{mod,integrations,fix}.rsanduninstall.rs(1,859 lines) intouninstall/{mod,agents,parsers}.rsfor better modularity. - Cloud-server cleanup — Removed unused
jwt_secretfield from cloud-server config and auth state.
[3.5.2] — 2026-05-07
Fixed
- Agent zombie cleanup —
cleanup_stale()now marks dead processes asFinishedimmediately regardless of age, fixing the "phantom agents" bug where terminated MCP sessions (e.g. from Claude Code subagents,/superpowers,/gsdplugins) 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-filter —
build_agents_json()now callscleanup_stale()on every API request and additionally filters byis_process_alive()as a safety net, ensuring the Agent World dashboard never shows zombie entries. - CLI/MCP feature parity — new
core::tool_lifecyclemodule 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 accuracy —
ctx_searchnow 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 CLIread,grep,ls,diff,depscommands ensure token savings from hook subprocesses are persisted to disk immediately.
Changed
- Agent HookMode reclassification — CRUSH, Hermes, OpenCode, Pi, and Qoder moved from
CliRedirecttoHybridmode 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 claudeinstalls the MCP server entry in~/.claude.jsonand configures PreToolUse hooks for Bash compression. This ensures full functionality even in headless (-p) mode where PreToolUse hooks don't fire. - Antigravity dedicated hook —
lean-ctx init --agent antigravitynow has its own installation function (no longer shares with Gemini CLI), correctly configuring MCP at~/.gemini/antigravity/mcp_config.jsonand hook matchers for Antigravity's native tools (run_command,view_file,grep_search).
[3.5.1] — 2026-05-06
Fixed
- Tool Registry not initialized —
ctx_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 path —
lean-ctx init --agent copilotnow creates.github/mcp.jsonwith the correct"mcpServers"key (per GitHub Copilot CLI spec), in addition to.vscode/mcp.jsonwith the VS Code"servers"key. Previously wrote to the wrong path (.github/copilot/mcp.json) with the wrong key format. - Agent-scoped project rules —
lean-ctx init --agent copilotno longer creates.cursorrulesor.claude/rules/files. Project rules are now scoped to the requested agent(s). - SKILL.md for Copilot/VS Code —
lean-ctx setupnow installs SKILL.md for GitHub Copilot / VS Code users, andlean-ctx doctorchecks 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/statusmanages 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 Compiler —
lean-ctx instructionscommand compiles project-specific rules into optimized agent instructions. - Context Proof System —
lean-ctx proofgenerates verifiable context provenance chains for audit trails. - Team Server: Context OS scopes —
SessionMutations,Knowledge, andAuditscopes for fine-grained team permissions vialean-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_indextools. 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 alignment —
ScratchpadEntrynow aligns withA2AMessagetypes 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.shPOSIX compliance — rewritten as pure POSIX sh socurl | shworks on dash (Ubuntu/Debian default). PR #175 by @narthanaj.- Qoder MCP config — added
LEAN_CTX_FULL_TOOLSto Qoder configuration for complete tool exposure. Includes clippy fixes. - Team SSE endpoint — removed dead code and properly wired
audit_eventinto the SSE stream.
[3.4.7] — 2026-05-01
Added
ctx_callmeta-tool — compatibility tool for MCP clients with static tool registries (e.g. Pi Coding Agent). Invoke anyctx_*tool by name via a stable schema without requiring dynamictools/listrefresh. Fixes #174.- Interactive Graph Explorer —
ctx_graph action=export-htmlgenerates a self-contained, interactive HTML visualization with pan/zoom, node selection, transitive highlighting, and PNG export. - Self-Hosted Team Server —
lean-ctx team serveenables shared context across workspaces with token-based auth, scoped permissions, rate limiting, and audit logging.
Changed
- Dual-format hook output —
lean-ctx hook rewrite/redirectnow 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.jsonnow uses the officialmcpServerssnippet format matching JetBrains AI Assistant documentation (was: nonstandardserversarray). - Shell hook block markers —
lean-ctx init --globalnow writes stable# lean-ctx shell hook — begin/endmarkers, making updates idempotent and safe across reinstalls.
Fixed
- Claude Code hooks not intercepting subagent calls —
extract_json_fieldin 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 plugins —
install_claude_hook_confignow merges PreToolUse hooks instead of replacing the entire matcher group, preserving hooks from other plugins (e.g. obra/superpowers). lean-ctx doctorfalse 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 parameters —
get_str_arraynow accepts JSON-encoded strings (e.g."[\"a\",\"b\"]") in addition to native JSON arrays, fixingctx_multi_readfor the Pi MCP bridge. Fixes #173. - Windows CI test failure —
workspace_configtests now useserde_json::json!for path serialization, preventing invalid JSON escapes on Windows.
[3.4.6] — 2026-04-30
Added
- Unified call graph tool — new
ctx_callgraphsupportsdirection=callers|calleesbehind one stable entry point. - Graph diagram in unified graph API —
ctx_graphnow supportsaction=diagram(withkind=deps|callsand optionaldepth). - 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_guardmodule 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_calleesnow route toctx_callgraphwith deprecation hints.ctx_graph_diagramnow routes toctx_graph action=diagramwith deprecation hint.ctx_wrappednow routes toctx_gain action=wrappedwith 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.rsorphan 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_unwindat the process entry point, a cross-process startup lock, and capped Tokio worker/blocking threads. Fixes #171. - Dashboard stale index auto-rebuild —
graph_indexandvector_indexnow 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.rsfallback regex pathscore/stats/mod.rsoptional buffer extraction
- CLI guidance consistency —
lean-ctx wrappednow clearly points users to the canonicallean-ctx gain --wrappedpath. - 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/*.tomlwith inheritance. Server-side middleware blocks unauthorized tools with clear feedback.ctx_session action=roleto 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=budgetto check status. Budgets reset on role switch or session reset. - Agent Harness: Events — new
EventKindvariants: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_DIRfor cloud config — cloud client now respects theLEAN_CTX_DATA_DIRenvironment 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 setupignoring config changes — running setup a second time no longer silently ignores the user's new choices forterse_agentandoutput_density. Values are now upserted instead of skipped when keys already exist inconfig.toml.- Dashboard cost mismatch with
lean-ctx gain— dashboard computed cost savings with hardcoded pricing ($2.50/M input) whilegainused dynamic model-specific rates. Dashboard now syncs pricing from the gain API for consistent numbers. ctx_sessiontool description missing actions —roleandbudgetactions 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_DIRcloud support (PR #168)
[3.4.4] — 2026-04-28
Fixed
- Observatory File Heatmap blank — the File Heatmap panel in
lean-ctx watchstayed empty because historical per-file access data was never loaded on TUI startup. Now pre-populates from the persistentheatmap.jsonso file activity is visible immediately. Also fixedEventTailoffset tracking to prevent event loss during concurrent writes. Fixes #166. - Windows agent hook installs —
dirs::home_dir()does not respectHOME/USERPROFILEoverrides on Windows, causing hooks to install into incorrect directories during CI and in some user setups. Introduced a centralizedcore::home::resolve_home_dir()that checksHOME,USERPROFILE, andHOMEDRIVE+HOMEPATHbefore falling back todirs::home_dir(). All 13 agent installers and the hook manager now use this resolver. - Windows
claude mcp add-jsoninvocation —.cmdshims cannot be executed directly viaCreateProcess; now routes throughcmd /Cfor 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_loopacross 30+ source files. cargo-deny0.19 migration — updateddeny.tomlto new schema, removed deprecated advisory fields, added missing dependency licenses (0BSD,CDLA-Permissive-2.0).- Windows benchmark stability —
bench_rrf_eviction_vs_legacyno longer panics fromInstantunderflow on short-lived processes. - Coverage timeout —
benchmark_task_conditioned_compressionnow skipped under tarpaulin instrumentation and uses smaller input to prevent CI timeouts. - Uninstall dry-run —
lean-ctx uninstall --dry-runno 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::pedanticwith zero warnings:Copyderives,map_or/is_ok_andpatterns,Duration::from_hours/from_mins,while letloops, and raw string simplification. cfg(tarpaulin)declared in Cargo.toml — preventsunexpected_cfgslint failures when coverage attributes are used.
[3.4.3] — 2026-04-27
Fixed
- Pi Agent compression loop — agents using
pi-lean-ctxcould get stuck in a compression loop wherebashoutput was too aggressively compressed, preventing the agent from extracting needed information. Thebashtool now supports araw=trueparameter that bypasses compression entirely when exact output is critical. Fixes #159. - Hook handlers ignore
LEAN_CTX_DISABLED—handle_rewrite,handle_codex_pretooluse,handle_copilot, andhandle_rewrite_inlinenow checkLEAN_CTX_DISABLEDenv 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 = trueconfig option andLEAN_CTX_NO_UPDATE_CHECK=1env var to completely disable the daily version check againstleanctx.com/version.txt. - Pi Agent
rawparameter —bashtool inpi-lean-ctxnow acceptsraw=trueto skip compression, matchingctx_shell raw=truebehavior in the MCP server. is_disabled()guard — centralized helper inhook_handlers.rsfor consistentLEAN_CTX_DISABLEDchecks across all hook entry points.- New integration tests —
hook_rewrite_disabled_produces_no_outputandcodex_pretooluse_disabled_exits_cleanlyverify the disabled guard behavior.run_hook_testhelper explicitly removes inherited env vars to prevent test pollution.
Changed
- Data sharing default flipped —
lean-ctx setupnow asks[y/N](opt-in) instead of[Y/n](opt-out). Users must explicitly choose to enable anonymous stats sharing. - Pi Agent tool prompts overhauled —
descriptionfields 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 wheredescriptionis the primary LLM guidance field. RedundantpromptGuidelinesremoved fromls/find/grep. - Pi Agent explicit entry point —
pi-lean-ctxnow uses./extensions/index.tsas 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
promptGuidelinesbehavior (PR #161) - @johnwhoyou —
LEAN_CTX_DISABLEDhook 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 withtruncate_start_char_boundary()that respects char boundaries. Fixes #154. - Windows shell detection in Git Bash / MSYS2 —
find_real_shell()now checksMSYSTEM/MINGW_PREFIXenv vars beforePSModulePath, 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_shellresponses (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_CHECKPOINTenv var — disable auto-checkpoint injection independently fromminimal_overheadPreparedSavepattern —Session.save()split intoprepare_save()(CPU-only serialization under lock) +write_to_disk()(background I/O viatokio::task::spawn_blocking), removing disk I/O from the tool response hot pathmd5_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_tokenscalled once per tool response (was up to 4x) — cached result reused for hints, cost attribution, and loggingCostStorewrites deferred to background thread viaspawn_blockingmcp-live.jsonwrites debounced to every 5th tool call (80% fewer disk writes)compress_outputskipped entirely forNormaldensity (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_hintwhen output tokens exceeded input tokens — now usessaturating_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=1to opt back in to all tools. Thectx_discover_toolstool lets agents discover and load additional tools on demand. (#153)
Added
- JSONC comment support —
lean-ctx setupand 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-ctxdirectories are detected and used automatically — no migration required. (#152) minimal_overheadconfig option — Setminimal_overhead = truein config orLEAN_CTX_MINIMAL=1env var to skip session/knowledge/gotcha blocks in MCP instructions, minimizing token overhead for cost-sensitive workflows. (#153)- Shell hook disable — New
--no-shell-hookflag forlean-ctx init,shell_hook_disabled = trueconfig option, andLEAN_CTX_NO_HOOK=1env 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_linedetection 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 diffcode content:compress_diff_keep_hunks()preserves all+/-changed lines, only trims context to max 3 lines per hunkdfroot filesystem: Verbatim passthrough — no compression applied todfoutputpytestxfail/xpass: Summary explicitly includesxfailed,xpassed,skipped, andwarningscountersgit statusDETACHED HEAD: Passes through verbatim including "HEAD detached at" warninglsshows.env: No file filtering — all files including.envare shownpip listall packages: Full package list preserved — no truncationgit stashverbatim: Passes git stash output through unchangedrufffile:line:col: Preserves all location references in linter outputfindfull paths: Preserves complete absolute pathswcvia pipe: Correctly reads stdin (piped input)- Log
CRITICAL/FATALseverity:log_dedupandsafety_needlesexplicitly recognize and preserve CRITICAL, FATAL, ALERT, EMERGENCY severity levels
Fixed in v3.3.9
git showdiff content (CRITICAL):compress_show()now preserves full diff content usingcompress_diff_keep_hunks()instead of reducing tohash message +N/-M. Code review viagit showis now safe.docker pshealth status (CRITICAL): Added fallback detection for(unhealthy),(healthy),(health: starting), andExited(N)annotations that survive even when column-based parsing misaligns.git logdefault 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 survivegit showadversarial_git_show_preserves_security_change— verifies security-relevant removals (e.g. CSRF) are visibleadversarial_docker_ps_unhealthy_narrow_columns— verifies health status survives tight column layoutsadversarial_docker_ps_exited_containers— verifies crashed containers are shownadversarial_git_log_100_plus_commits— verifies 100-entry cap and truncation messageadversarial_git_log_explicit_limit_unlimited— verifies-nbypasses default capadversarial_safeguard_ratio_prevents_over_compression— verifies safety net prevents >85% compressionadversarial_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 updateandlean-ctx setupnow write Windows paths in Codexconfig.tomlusing 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
lsoutput (PR #148 by @glemsom):lean-ctx lsnow runs plainlsinstead ofls -laby default, reducing token overhead. The agent can add-laflags when needed.
[3.3.7] — 2026-04-23
New Features
lean-ctx ghostCLI: New command that reveals hidden token waste — shows unoptimized shell commands, redundant reads, and oversized contexts with monthly USD savings estimate. Supports--jsonfor CI integration.ctx_reviewMCP 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_readthat creates stable chunk boundaries, improving LLM prompt cache hit rates across edits. Enable viacontent_defined_chunking = trueinconfig.toml. - Claude Code Plugin Manifest:
.claude-plugin/manifest.jsonadded for future Claude Code plugin marketplace integration.
Improvements
- Cache-Safety Doctor Check:
lean-ctx doctornow verifies thatcache_alignmentandprovider_cachemodules are operational (12 checks total). provider_cachemodule 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:pythonscan configuration removed; explicit CodeQL workflow now covers only Rust, JavaScript/TypeScript, and Actions. - Email masking in CLI:
lean-ctx login/register/forgot-passwordnow mask email addresses in console output. (CodeQL #21-#23)
Bug Fixes
- TypeScript
.jsimport resolution (GitHub Issue #146): The graph builder now correctly resolves relative.jsspecifiers to.tssource 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 requesterror. - 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-ctxnow 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_pathsin 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_PATHorallow_pathsinconfig.toml.
Windows PowerShell Fixes (GitHub Issue #142)
- Pipe-guard in profile snippet: The
[Console]::IsOutputRedirectedcheck 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 ofwhereoutput on Windows, and prefers.cmd/.exevariants to avoid corrupted path detection.
CLI Improvements
excluded_commandsvia 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_sessionrace 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 likepsql <<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=1environment variable: When set, the MCP server skips all auto-setup duringinitialize()— 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 loginonly calls/api/auth/login.lean-ctx registeronly 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.5phantom dependency removed viacargo update(GHSA-cq8v-f236-94qc). - Updated:
tokio1.52.1,rustls0.23.39,rmcp1.5.0,uuid1.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 viaarchive.max_age_hoursinconfig.tomlorLEAN_CTX_ARCHIVE_TTLenv 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_mbinconfig.toml. Env overrides:LEAN_CTX_ARCHIVE,LEAN_CTX_ARCHIVE_THRESHOLD,LEAN_CTX_ARCHIVE_TTL.
Bidirectional Token Optimization (Terse Agent Mode)
- New
terse_agentconfig: 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/fullare skipped when CRP mode istdd(already maximally dense).ultraalways 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_expandwas implemented (dispatch handler, archive storage, tool definition inlist_all_tool_defs()) but was missing fromgranular_tool_defs()— the function that the MCP server actually uses to build thetools/listresponse. Agents could never discover or callctx_expanddespite the feature being fully coded. Now registered as tool #47.
TerseAgent::effective() ignores environment variable
- Fixed:
TerseAgent::effective()was supposed to letLEAN_CTX_TERSE_AGENToverride 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, andlean-ctx forgot-passwordwere implemented incli/dispatch.rsbut the primary dispatch inmain.rswas 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.rsandcli/dispatch.rsto consistently listterse,register, andforgot-password.
lean-ctx doctor ignores LEAN_CTX_DATA_DIR (Discord: GlemSom)
- Fixed:
doctornow useslean_ctx_data_dir()instead of hardcoded~/.lean-ctxat all 4 locations: shell-hook checks, Docker env.sh path, data directory check, andcompact_score(). Users with customLEAN_CTX_DATA_DIRwill now see correct paths in doctor output.
Windows "path escapes project root" (GitHub Issue #139)
- Fixed:
pathjail.rsnow usessafe_canonicalize_or_self()(which strips the\\?\verbatim prefix) instead of rawstd::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_windowsnow strips\\?\prefix before comparison, andallow_paths_from_envuses the safe canonicalization consistently.
Shell Quoting Hardening
Bug fixes — Argument preservation for complex shell commands
- Direct argv execution in
-tmode: 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()callsCommand::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 usesh -cfor backward compatibility. - Single-quote wrapping for hook rewrites:
wrap_single_commandin 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. ghadded to full passthrough: AllghCLI commands (not justgh auth) are now excluded from compression and tracking. The GitHub CLI's output is typically short, and its complex argument patterns (multi-word--commentvalues, issue references with#) are prone to quoting issues.
Code quality
- 20+ new unit tests covering:
exec_direct/exec_argvdirect execution,quote_posixedge cases (em-dash,$, backtick, nested quotes),wrap_single_commandspecial characters ($HOME, backticks,findwith long exclude lists,!), andghfull 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 bothload_by_id()andload_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 checksstartup_project_rootbefore 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(), andis_agent_or_temp_dir()tosession.rsfor robust session matching and stale-root detection.
Improvements — Dashboard and metrics clarity
- 0%-savings tools hidden from
lean-ctx gain: Write-only tools likectx_editthat 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 MCPctx_metricstool now filters out tools with zero token activity from the "By Tool" breakdown.
Code quality
- Fixed all clippy warnings: resolved
MutexGuardheld across await points in tests,vec!macro used where array literal suffices, andDefault::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 incompatibleupdatedInputfield. 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 preferlean-ctx -c "<command>"for shell commands. - Refactored rewrite logic: Extracted
rewrite_candidate()fromhandle_rewrite()to share rewrite detection across Claude Code, Codex, Copilot, and inline-rewrite handlers. Eliminates duplicated skip/wrap/compound logic. - New
hooks/support.rsmodule: 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-pretooluseandlean-ctx hook codex-session-startsubcommands added to bothmain.rsanddispatch.rs. - Doctor integration:
doctor --fixnow setsLEAN_CTX_QUIET=1when running in JSON mode to suppress noisy setup output.
Bug fixes — Knowledge hash collisions in Docker environments
- New
project_hash.rsmodule: 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):
.git/config→ remote "origin" URL (normalized: lowercase, stripped.gitsuffix, SSH→path conversion)Cargo.toml→[package] namepackage.json→"name"fieldpyproject.toml→[project] nameor[tool.poetry] namego.mod→modulepathcomposer.json→"name"fieldsettings.gradle/settings.gradle.kts→rootProject.name*.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()fromgotcha_tracker.rs— now uses sharedproject_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 devno 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_bufferedwould wait forever for the dev server to exit.npm runcompression less aggressive:compress_runnow 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,-Bnow correctly match after case normalization inis_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 taskcommand where the script name containsdev,start,serve,watch,preview,storybook,hot,live, orhmris automatically treated as passthrough. Catches variants likenpm run dev:ssr,yarn start:production,pnpm run serve:local,bun run watch:css.
New adversarial tests (12 tests)
npm installpackage name/count preservationnpm installexplicit package names (express,lodash,axios)cargo builderror codes (E0308, E0599) with file:lineeslintrule IDs and error countsgo buildfile:line error locationsdocker buildstep failure errorstsctype error codes (TS2304, TS2339) with file referencesdotnet buildCS0246 errors and build resultcomposer installpackage countscargo testfailure countskubectl get podsCrashLoopBackOff/Error statusterraform plandestructive 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 diffcode content preserved: Compression no longer reduces diffs tofile +N/-M. All+/-lines (actual code changes) are preserved. Onlyindexheaders 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 pshealth 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.dfverbatim 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 auditCVE 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 logtruncation increased to 50: Default truncation raised from 20 to 50 entries. User-specified--max-count/-narguments are now respected (no truncation applied). Truncation message updated to suggest--max-count=N.pytestxfail/xpass/warnings: Summary now includesxfailed,xpassed, andwarningscounters. Example:pytest: 15 passed, 1 failed, 2 xfailed, 1 xpassed, 2 warnings (3.5s).grep/rgverbatim up to 100 lines: Outputs with ≤100 lines pass through unchanged. File grouping and context stripping only applies to larger outputs.pip uninstallpackage names listed: Shows all successfully uninstalled package names (up to 30) instead of just a count.docker logssafety-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 blameverbatim up to 100 lines: Small blame outputs pass through unchanged. Larger outputs summarize by author with line ranges.curlJSON sensitive key redaction: Keys matchingtoken,password,secret,auth,credential,api_key, etc. have their values replaced withREDACTEDin schema output.ruff checkfile:line:col preserved: Outputs with ≤30 issues pass through verbatim, preserving allfile:line:colreferences. Larger outputs show first 20 references plus rule summary.log_dedupregex fix: Fixed a greedy regex ([^\]]*→[^\]\s]*) in timestamp stripping that consumed entire log messages, preventing proper deduplication. AddedCRITICALto severity detection.lightweight_cleanupbrace 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 structuredCommandSafetytable andSafetyLevelenumshell_init.rs: Refactored hook generation intogenerate_hook_posix(),generate_hook_fish(),generate_hook_powershell()for reuse by both file-based and eval-based initci.yml: Newadversarialjob runningcargo test --test adversarial_compression
[3.2.9] — 2026-04-20
Fixed
- UTF-8 text corrupted on Windows PowerShell (#131):
lean-ctx -cwith non-ASCII output (Russian, Japanese, Chinese, Arabic, etc.) produced mojibake becauseString::from_utf8_lossymisinterpreted Windows system codepage bytes as UTF-8. Introduceddecode_output()that tries UTF-8 first, then falls back to Win32MultiByteToWideCharfor proper codepage-to-Unicode conversion. On PowerShell, additionally injects[Console]::OutputEncoding = UTF8and setsSetConsoleOutputCP(65001). Fixed across shell hook, MCP server execute, and sandbox runners. - MCP
ctx_shellcommands hang on stdin (#132, credit: @xsploit): Child processes spawned by the MCP server inherited the JSON-RPC stdin pipe, causing commands likegitto block instead of receiving EOF. Fixed by settingstdin(Stdio::null())on all MCP child processes. AddedGIT_TERMINAL_PROMPT=0andGIT_PAGER=catto prevent interactive prompts.
Added
- MCP command timeout: Shell commands executed via
ctx_shellnow have a configurable timeout (default 120s). Override withLEAN_CTX_SHELL_TIMEOUT_MSenv var. Timed-out commands return exit code 124 with a clear error message. - Regression tests: Added
execute_command_closes_stdinandgit_version_returns_when_git_is_availabletests to prevent future stdin inheritance regressions.
[3.2.8] — 2026-04-20
Fixed
- Codex
config.tomlparse 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 allmcp_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.jsonbut Gemini CLI reads MCP servers from~/.gemini/settings.jsonunder themcpServerskey. The MCP config was never loaded by Gemini CLI. Fixed with a newGeminiSettingswriter that mergesmcpServersinto the existingsettings.jsonwithout overwriting other keys (e.g.hooks). - Gemini CLI
autoApprovenot recognized: Gemini CLI uses"trust": truefor auto-approval, notautoApprove. Fixed to use the correct field. - Codex
codex_hooks=falseafter reinstall: Uninstall setcodex_hooks = falsebut setup didn't reset it totrue, leaving hooks disabled.
Added
- Autonomous intent inference:
ctx_readautomatically infers aStructuredIntentfrom file access patterns (after 2+ files touched) without requiring explicit agent calls.ctx_preloadauto-sets intent from task description when none is active or confidence is low. - Auto agent registration: MCP
initializehandler automatically registers the connecting agent in theAgentRegistrybased on client name (Cursor/Claude → coder, Antigravity/Gemini → explorer, etc.). Override viaLEAN_CTX_AGENT_ROLEenv 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:
PipelineStatsandContextLedgernow persist to disk (pipeline_stats.json,context_ledger.json) so dashboard data survives server restarts. - Codex/Cursor hooks in setup:
lean-ctx setupnow 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:initializenow validates roots against project markers,session::load_by_iddetects and corrects agent/temp roots, andresolve_pathcan 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 gainshowing 0% for Shell Hooks (#126): Small savings percentages were rounded to 0% in the "Savings by Source" and "Live Observatory" sections. Introducedformat_pct_1dpfor one-decimal-place display,<0.1%for very small values, andn/awhen no input data exists.install.shfails on WSL2/Ubuntu (set: Illegal option -o pipefail):curl -fsSL leanctx.com/install.sh | shfailed becauseinstall.shused Bashisms but was executed by POSIXsh(dash). Added a POSIX-compliant preamble that auto-detects and re-executes underbash, with a clear error message ifbashis unavailable. Both| shand| bashnow 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_scopeconfiguration: 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 viaconfig.toml,LEAN_CTX_RULES_SCOPEenv var,lean-ctx config set rules_scope, or per-project.lean-ctx.tomloverride.- Codex/Claude path jail auto-allowlist: When running inside Codex CLI (
CODEX_CLI_SESSIONset),~/.codexis automatically added to allowed paths. Similarly,~/.claudeis auto-allowed for Claude Code sessions. No manualLCTX_ALLOW_PATHneeded. bunxandvp/vite-plusCLI compression (#125): Shell hook now routesbunxcommands through the bun compressor andvp/vite-plusthrough the Next.js build compressor.lean-ctx updateauto-refreshes setup: Runninglean-ctx updatenow 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_scopedocumented on configuration page in all 11 languages.
[3.2.5] — 2026-04-18
Fixed
- Critical: shell hook recursion causing 100% CPU/memory — The
.zshenv/.bashenvshell hooks introduced in v3.2.4 were missing theLEAN_CTX_ACTIVErecursion guard. When an AI agent (Claude Code, Codex, etc.) ran a command,lean-ctx -cspawned a new shell that re-triggered the hook infinitely, causing a fork bomb. Fixed by checkingLEAN_CTX_ACTIVEbefore intercepting and adding a double-guard inexec(). Users must runlean-ctx setupafter updating to refresh the hooks.
[3.2.4] — 2026-04-18
Fixed
- Git stash compression too aggressive (#114):
git stash listwith ≤5 entries is now preserved verbatim.git stash show -pcorrectly routes to the diff compressor instead of the stash compressor. Addedsafeguard_ratiotoctx_shellto 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.exeinstead of/e/packages/lean-ctx.exe).resolve_binary()now appliesto_bash_compatible_pathon all platforms. - Windows UNC path breakage (
\\?\prefix):std::fs::canonicalize()on Windows adds extended-length path prefixes that break tools and string comparisons. Newcore::pathutilmodule providessafe_canonicalize()andstrip_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.gitand project markers before using them; falls through toshell_cwdwhen project_root is invalid. Added--project=CLI flag andLEAN_CTX_DASHBOARD_PROJECTenv var for explicit override. - Dashboard Call Graph/Route Map empty states: Enriched
/api/call-graphand/api/routesresponses 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 uninstallnow correctly removes the[mcp_servers.lean-ctx]section from Codex's TOML config, removes~/.codex/hooks.json, and resets thecodex_hooksfeature flag. - Repo-local config missing fields (#98):
merge_local()now supportsauto_consolidate,dedup_threshold,consolidate_every_calls,consolidate_cooldown_secs, and bidirectionalsilent_preloadoverride from.lean-ctx.toml.
Added
- Hermes Agent support (#112): Full integration for Hermes Agent (Nous Research).
lean-ctx init --agent hermes --globalconfigures MCP via YAML (~/.hermes/config.yaml), createsHERMES.mdrules. Setup auto-detects~/.hermes/, doctor checks Hermes config, uninstall cleans up YAML + rules. - Kotlin graph analysis (#96):
ctx_graph,ctx_callers, andctx_calleesnow 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.tomlin project root for per-project overrides. Supportsextra_ignore_patterns(graph/overview exclusions), autonomy settings, and all config fields.lean-ctx cache reset --projectclears only current project's cache. - Post-update MCP refresh:
lean-ctx updatenow verifies and refreshes MCP configurations for all detected editors after binary replacement. - Dashboard "Savings by Source": Live Observatory and
lean-ctx gainnow 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-codenow creates.claude/rules/lean-ctx.mdin the project root (project-local rules), in addition to the existing global~/.claude/rules/lean-ctx.md. Claude Code reads both locations. --helpmissing commands (#109):watch(live TUI dashboard) andcache(file cache management) were implemented but not listed inlean-ctx --help.- install.sh fails without Rust (#108):
curl -fsSL https://leanctx.com/install.sh | shnow auto-detects missingcargoand 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/--tracksubcommand 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-modeshell function: Switch betweentrack(default),compress, andoffmodes without editing config files. Available in both POSIX (bash/zsh) and Fish shells._lc_compressshell 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, andterminaltool names in addition toBash/bash. - Dashboard API routes: New
/api/symbols,/api/call-graph,/api/routes,/api/searchendpoints 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_lcused-c. Now defaults to-t(track) which preserves full output. - "Authorization required" on Ubuntu (#101):
exec_bufferedpipe redirection triggered X11/Wayland auth errors on headless Linux. Track mode usesexec_inherit_tracked(direct stdio), avoiding this entirely. - Token counting accuracy:
stats::recordnow usescount_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 socargo publishverification succeeds. - Release CI: Build
aarch64-unknown-linux-muslviacargo-zigbuildfor reliable ARM64 musl cross-compilation (fixes glibc symbol leaks fromgcc-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 serveexposes 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 claudeinstallsSKILL.md+scripts/install.shunder~/.claude/skills/lean-ctx/. - ARM64 Linux support:
aarch64-unknown-linux-muslbinary 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 inctx_search, prompt-injection fencing. - Unified Gain Engine:
GainScore(0–100),ModelPricing(embedded cost table),TaskClassifier(13 categories),ctx_gainMCP tool, TUI/Dashboard/CLI integration. - Docker/Claude Code MCP self-healing:
env.shre-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 -cnow 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
-cTTY bypass (#100):lean-ctx -c 'git status'now compresses even in terminal (setsLEAN_CTX_COMPRESS=1). - Windows
Instantoverflow: RRF eviction test usednow - Durationwhich underflows on Windows. Fixed withsleep-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
CacheEntryclone with lightweightStoreResultstruct (no content duplication). - Entropy dedup: Precomputed n-gram sets with size-ratio filter (exact Jaccard, no allocation storms).
- Clippy clean: 0 warnings with
-D warningsacross 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-complexparent()fallback logic that guessed at.claude.jsonlocations. Now directly uses$CLAUDE_CONFIG_DIR/.claude.jsonas documented by Claude Code.lean-ctx init --agent claudenow 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.jsoninstead of/home/node/.claude.json).refresh_installed_hooks()hardcoded~/.claude/: Hook detection inhooks.rsignored$CLAUDE_CONFIG_DIR, always checking~/.claude/hooks/and~/.claude/settings.json. Now usesclaude_config_dir().- Rules injection hardcoded
~/.claude/CLAUDE.md:rules_inject.rsalways wrote to~/.claude/CLAUDE.mdregardless of$CLAUDE_CONFIG_DIR. Now usesclaude_config_dir(). - Uninstall hardcoded
~/.claude/:remove_rules_files()andremove_hook_files()couldn't find Claude Code files when$CLAUDE_CONFIG_DIRwas set. Now usesclaude_config_dir(). - Doctor display hardcoded
~/.claude.json:lean-ctx doctoralways showed~/.claude.jsoneven when$CLAUDE_CONFIG_DIRpointed elsewhere. Now shows the actual resolved path.
[3.1.4] — 2026-04-15
Added
CLAUDE_CONFIG_DIRsupport:lean-ctx init --agent claude,lean-ctx doctor,lean-ctx uninstall, hook installation, and all Claude Code detection paths now respect the$CLAUDE_CONFIG_DIRenvironment variable. Previously hardcoded to~/.claude.jsonand~/.claude/.CLAUDE_ENV_FILEDocker hint:lean-ctx init --globalandlean-ctx doctornow recommend settingENV CLAUDE_ENV_FILEalongsideENV BASH_ENVin Docker containers. Claude Code sourcesCLAUDE_ENV_FILEbefore every command — this is the officially recommended shell environment mechanism.- Doctor check for
CLAUDE_ENV_FILE: In Docker environments,lean-ctx doctornow shows separate checks for bothBASH_ENVandCLAUDE_ENV_FILE.
Fixed
- Claude Code
_lcnot found in Docker (#89): Root cause was thatBASH_ENValone doesn't work for Claude Code — it usesCLAUDE_ENV_FILEto source shell hooks before each command. Recommended Dockerfile now includesENV CLAUDE_ENV_FILE="/root/.lean-ctx/env.sh". CLAUDE_CONFIG_DIRignored everywhere:setup.rs,rules_inject.rs,doctor.rs,hooks.rs,uninstall.rs, andreport.rsall hardcoded~/.claude.json/~/.claude/. Now all paths go throughclaude_config_json_path()/claude_config_dir()which check$CLAUDE_CONFIG_DIRfirst.
[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.shduringlean-ctx init --global— a standalone shell hook file without the non-interactive guard ([ -z "$PS1" ] && return) that most~/.bashrcfiles have - Docker BASH_ENV warning: when Docker is detected and
BASH_ENVis not set,lean-ctx initnow prints the exact Dockerfile line needed:ENV BASH_ENV="/root/.lean-ctx/env.sh" lean-ctx setupauto-fallback: detects non-interactive terminals (no TTY on stdin) and automatically runs in--non-interactive --yesmode instead of hanginglean-ctx doctorDocker check: new diagnostic that warns when running in a container with bash but withoutBASH_ENVset
Critical Fix
BASH_ENV="/root/.bashrc"never worked in Docker — Ubuntu/Debian.bashrchas[ -z "$PS1" ] && returnwhich skips the entire file in non-interactive shells. The newenv.shapproach 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, andctx_semantic_searchwith 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 inconfig.tomlwithnormal_threshold,reduced_threshold,blocked_threshold,window_secs, andsearch_group_limitfields. - 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_compressscales 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_treefor orientation, narrow withpathparameter, and usectx_read mode='map'instead of generic "change your approach" text. - Monorepo scope hints — when
ctx_searchresults span more than 3 top-level directories, a hint is appended suggesting the agent use thepathparameter 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
foreachloop 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
_lcoff-by-one —$args[1..($args.Length)]produced an extra$nullelement. Replaced with& @argssplatting which correctly handles all argument counts. - Password shown in cleartext during
lean-ctx login— interactive password prompt now usesrpasswordto disable terminal echo, so passwords are never visible.
Improved
- Shell-aware command quoting —
shell_joinmoved frommain.rstoshell.rswith runtime shell detection. Three quoting strategies: PowerShell (& 'path'with''escaping), cmd.exe ("path"with\"escaping), and POSIX ('path'with'\''escaping). Previously used compile-timecfg!(windows)which was untestable and broke Git Bash on Windows. - 11 new unit tests for
join_command_forcovering all three shell quoting strategies with paths containing spaces, special characters, and empty arguments.
Dependencies
- Added
rpassword 7.4.0for 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/dashboardmirroring 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.pushStatefor 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/feedbackendpoint 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_hitsandcache_missesderived 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.rsmodule.
Security
- rand crate — updated to
>= 0.9.3to fix unsoundness with custom loggers (GHSA low severity).
Fixed
- Token count test threshold — updated
bench_system_instructions_token_countthresholds 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 endpoint —
GET /api/statusexposes 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
buildingstatus 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_readand also fromctx_shell(via effective working directory), without requiring manual reindex commands.
CI
- AUR release hardening — AUR job runs only when
AUR_SSH_KEYis 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 file —
lean-ctx init --agent kiroandlean-ctx setupnow create.kiro/steering/lean-ctx.mdalongside the MCP config, ensuring Kiro uses lean-ctx tools instead of native equivalents. - Project-level detection —
install_project_rules()automatically creates the steering file when a.kiro/directory exists.
Fixed
lean-ctx doctorshowed 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 viaxdg-open.
[3.0.2] — 2026-04-12
Symbol Intelligence + Hybrid Semantic Search
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 symbolctx_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
Improved — ctx_semantic_search
- Search modes:
bm25,dense,hybrid(default) - Filters:
languages+path_globto 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:
depthis now respected when filtering edges forctx_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. CapturesToolCall,CacheHit,Compression,AgentAction,KnowledgeUpdate, andThresholdShiftevents 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 fromevents.jsonlwith tail-based polling.
Added — Event Instrumentation
ctx_read,ctx_shell,ctx_search,ctx_treeand all tools now emitToolCallevents with token counts, mode, duration, and path.- Cache hits emit
CacheHitevents with saved token counts. entropy_compress_adaptive()emitsCompressionevents with before/after line counts and strategy.AgentRegistry.register()emitsAgentActionevents.ProjectKnowledge.remember()emitsKnowledgeUpdateevents.FeedbackStoreemitsThresholdShiftevents 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
kindobject mismatch — implementedflattenEvent()parser supporting all 6 event types. - Agent World status comparison was case-sensitive (
Activevsactive) — now case-insensitive. - Learning Curves scatter plot showed 0 for x-axis — now computes compression ratio from
tokens_saved / tokens_originalwhencompression_ratiofield is absent. - Compression Lab failed to load files — added
rust/prefix fallback for path resolution andoriginalcontent 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
taskmode filtering, preserving lines that reference known entities (functions, types, modules) from the dependency graph. - Task relevance scoring — Renamed
information_bottleneck_filter→graph_aware_ib_filterwith 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.
taskread 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_knowledgetool — 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_tasktool — Google A2A (Agent-to-Agent) protocol implementation with full task lifecycle:create,assign,update,complete,cancel,list,get.ctx_costtool — Cost attribution per agent with token tracking. Actions:record,summary,by_agent,reset.ctx_heatmaptool — File access heatmap tracking read counts, compression ratios, and access patterns. Actions:show,hot,cold,reset.ctx_impacttool — Measures the impact of code changes by analyzing dependency chains in the knowledge graph.ctx_architecturetool — Generates architectural overviews from the project's dependency graph and module structure.- Agent Card —
.well-known/agent.jsonendpoint 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_shellcwd tracking — Shell working directory is now tracked across calls.cdcommands are parsed and persisted in the session. Newcwdparameter 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_root—detect_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_shellcwd persistence — Newshell_cwdfield in session state.cdcommands are parsed and the working directory persists acrossctx_shellcalls. Priority: explicitcwdarg → sessionshell_cwd→project_root→ process cwd.ctx_overview/ctx_preloadroot fallback — Both tools now fall back tosession.project_rootwhen nopathparameter 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 undercmd.exe.
Improved — Diagnostics
lean-ctx doctor— New session state check showingproject_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 validation —
doctornow validates the actual JSON structure of each MCP config file instead of just checking for the string "lean-ctx". Checks formcpServers→lean-ctx→commandfields, verifies the binary path exists, and reports per-IDE status (valid vs. broken configs). - Honest stats check — A missing
stats.jsonis 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, sodoctor --reportalways 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. Bothinstall.shand the self-updater now runxattr -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
- Strong signals (match alone):
- 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 passthrough —
ctx_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 path —
compress_if_beneficial()also checks for auth flows before any compression, covering theexec_bufferedpath 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_toolsconfig 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-ctxcommand 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 setupwrites~/.pi/agent/mcp.jsonautomatically. lean-ctx init --agent pinow also writes the MCP server config to~/.pi/agent/mcp.jsonwithlifecycle: lazyanddirectTools: true.
Improved — Pi diagnostics
lean-ctx doctornow 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_toolsconfiguration. - 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-refresh —
post_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 updateonly refreshed AI tool hooks (Claude, Cursor, Gemini, Codex) but left shell aliases untouched, meaning users had to manually runlean-ctx setupto get new hook logic like the pipe guard. - Multi-shell support — If a user has hooks in both
.zshrcand.bashrc, both are now updated (previously only the first match was handled). - Post-update message — Now explicitly tells users to
source ~/.zshrcor restart their terminal.
Changed — Thinking Budget Tuning
FixBugintent: Minimal → Medium (bug fixes benefit from deeper reasoning)Exploreintent: Medium → Minimal (exploration is lightweight)Debugintent: Medium → Trace (debugging needs full chain-of-thought)Reviewintent: 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 inintegration_tests.rsand the unit tests incli.rs. This eliminates a redundant CI job that depended ongenerate_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 —
_lcnow checksnot isatty stdoutbefore routing through lean-ctx. - Pipe guard for PowerShell —
_lcnow checks[Console]::IsOutputRedirectedbefore 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 /twithpingdelay — The deferred update.batscript usedtimeout /t 1 /nobreakfor delays. On Windows systems with GNU coreutils in PATH (Git Bash, Cygwin, MSYS2), the GNUtimeoutbinary takes precedence over the Windows built-in, fails instantly with "invalid time interval '/t'", and causes a tight retry loop at 100% CPU. Now usesping 127.0.0.1 -n 2 >nulwhich 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
timeoutcommand usage,pingdelay, 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.exeorpowershell.exe) before falling back tocmd.exe. Previously, PowerShell was only used ifPSModulePathwas 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.execannot parse these commands. This was the root cause of "& was unexpected at this time" errors reported by Windows users. LEAN_CTX_SHELLoverride — Users can setLEAN_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 forgemini, creating the same hook scripts and settings under~/.gemini/. Previously, Antigravity users had to know to use--agent geminior runlean-ctx setup.
Testing
- 19 new E2E tests covering shell detection,
LEAN_CTX_SHELLoverride, 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 Rustextract_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 guard —
session_lifecycle_pre_hookandenrich_after_readnow require a known, non-trivialproject_rootbefore triggering auto-context. Previously, whenproject_rootwasNoneor".", the autonomy system would runctx_overviewon 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 terseF1=main.rs cached 2t 4Lstub left AI agents confused about what to do next.
Housekeeping
- Redirect scripts reduced to minimal
exit 0(removed ~30 lines of deadis_binary/FILE_PATHparsing 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.toolwas rewritten aslean-ctx -c curl ... | python3 ...(pipe interpreted by shell). Now correctly produceslean-ctx -c "curl ... | python3 ...". This also fixes thecommand not found: _lcerrors 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 patterns —
lean-ctx find "*.toml"now correctly uses glob matching instead of literal substring search. Addedglobcrate 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 caching —
lean-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 cachesubcommand withstats,clear, andinvalidate <path>actions. --fresh/--no-cacheflag — 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
sandboxandloop_detectionmodules that were present on disk but missing fromcore/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_filecalls 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_toolsconfig field — Exclude unused tools from the MCP tool list to reduce token overhead from tool definitions. Configure via~/.lean-ctx/config.tomlorLEAN_CTX_DISABLED_TOOLSenv 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. Supportsaction=batchfor multiple scripts in one call, andaction=fileto 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=1env — 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_executeor 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": 1and hooks as an object withpreToolUsekey instead of array. Existing broken configs are automatically migrated on nextlean-ctx install cursoror MCP server start. - cargo publish workflow — Added
--allow-dirtyto release pipeline to prevent publish failures from checkout artifacts
[2.19.0] — 2026-04-07
Temporal Knowledge, Contradiction Detection, Agent Diaries & Cross-Session Search
Added — Knowledge Intelligence
- Temporal facts — All facts now track
valid_from/valid_untiltimestamps. When a high-confidence fact changes, the old value is archived (not deleted) with full history - Contradiction detection —
ctx_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 rooms —
ctx_knowledge(action=rooms)lists all knowledge categories (rooms) with fact counts, providing a MemPalace-like structured overview - Timeline view —
ctx_knowledge(action=timeline, category="...")shows the full version history of facts in a category, including archived values with validity ranges - Cross-session search —
ctx_knowledge(action=search, query="...")searches across ALL projects and ALL past sessions for matching facts, findings, and decisions - Wake-up briefing —
ctx_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 diaries —
ctx_agent(action=diary, category=discovery|decision|blocker|progress|insight)logs structured entries that persist across sessions at~/.lean-ctx/agents/diaries/ - Diary recall —
ctx_agent(action=recall_diary)shows the 10 most recent diary entries for an agent with timestamps and context - Diary listing —
ctx_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 briefing —
ctx_overviewnow 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) andctx_agent(11 actions) to document all new capabilities
[2.18.1] — 2026-04-07
Code Quality & Security Hardening
Fixed
- Shell injection in CLI —
lean-ctx grepandlean-ctx findno longer shell-interpolate user input; replaced with pure Rust implementation usingignore::WalkBuilder+regex - Panic in
report_gotcha—unwrap()afteradd_or_mergecould panic when gotcha store exceeds capacity (100 entries) and the new entry gets evicted; now returnsOption<&Gotcha>safely - Broken
FilterEnginecache — Removed deadget_or_load()method that stored empty rules in aMutexand was never called;CACHED_ENGINEstatic removed unwrap()afteris_some()pattern — Replaced fragile double-lookup +unwrap()with idiomaticif let Some()/matchinctx_read,ctx_smart_read, andctx_deltagraphCLI argument parsing —lean-ctx graph build /pathnow correctly separates action from path argument
Added
lean-ctx graphCLI command — Build the project dependency graph from the command line (lean-ctx graph [build] [path]); previously only available via MCPctx_graphtool- Consolidated
detect_project_root— Single implementation incore::protocolreplacing 3 duplicate copies acrossserver.rs,ctx_read.rs, anddashboard/mod.rs
Changed
- Tokio features trimmed —
features = ["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 updated —
security-check.ymlnow correctly documentsureqas 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_sharetool (28th MCP tool) — Share cached file contexts between agents. Actions:push,pull,list,clearctx_agenthandoff action — Transfer a task to another agent with a summary message, automatically marks the handing-off agent as finishedctx_agentsync action — Combined overview of active agents, pending messages, and shared contextslctx --agentsflag — Launch multiple agents in parallel:lctx --agents claude,geministarts both in the background with shared context- Dashboard
/api/agentsenhancement — Returns structured JSON with active agents, pending messages, and shared context count
Added — Intent & Semantic Intelligence
- Multi-intent detection —
ctx_intentnow detects compound queries ("fix X and then test Y") and splits them into sub-intents with individual classifications - Complexity classification —
ctx_intentreturns task complexity (mechanical/standard/architectural) based on query analysis, target count, and cross-cutting keywords - Heat-ranked file strategy —
ctx_intentfile 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 onctx_readcache 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 API —
GET /api/heatmapreturns 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 integration —
lean-ctx init --agent crushconfigures MCP in~/.config/crush/crush.jsonwith the Crush-specific"mcp"key format (instead of"mcpServers") - Auto-detection —
lean-ctx setupandlean-ctx doctornow detect Crush installations - Rules injection —
lean-ctx rulescreates~/.config/crush/rules/lean-ctx.mdwhen 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 blocking —
ctx_shellnow detects and rejects shell redirects (>,>>), heredocs (<< EOF), andteecommands. 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)
- Config:
- Automated crates.io publishing —
cargo publishruns automatically after GitHub Release - Automated npm publishing —
lean-ctx-binandpi-lean-ctxpublished automatically
[2.17.3] — 2026-04-06
Fix: MCP Stdout Pollution on Windows
Fixed
- Windows MCP "not valid JSON" error —
println!("Installed...")messages ininstall_claude/cursor/gemini_hook_configpolluted stdout during MCP server initialization, breaking JSON-RPC protocol. Now suppressed viamcp_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 gainoutput when enabled - Bug Memory stats — Active gotchas and prevention stats shown in
lean-ctx gain - Helpful footer —
lean-ctx gainnow shows links toreport-issue,contribute, andgotchas
[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 rewriteandlean-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 redirectinstead of.shscripts 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 commands —
lean-ctx buddywithshow,stats,ascii,jsonactions;petalias - Dashboard Buddy card — Glasmorphism UI with rarity-dependent gradients/animations, animated XP bar, SVG radial gauges, styled speech bubble, mood indicator
- API endpoint —
/api/buddyserving fullBuddyStateJSON includingascii_artandxp_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
LeanCtxErrorenum incore/error.rswiththiserrorderive.Fromimpls forio::Error,toml::de::Error,serde_json::Error.Config::save()migrated as first consumer.
Performance
- Async I/O for ctx_shell —
execute_commandwrapped intokio::task::spawn_blockingto 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 fromCargo.toml. - report-issue exit code — Empty title now exits with status 1 for proper script error detection.
- Theme migration —
print_command_box()migrated from hardcoded ANSI to thecore::themesystem. - upgrade → update —
lean-ctx upgradenow prints deprecation notice and delegates tolean-ctx update.
Dashboard
- Offline fonts — Removed Google Fonts CDN dependency, switched to system font stacks.
- Dynamic version — Version display fetched from
/api/versioninstead 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 duringlean-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()onmin_by_keyreplaced withif let Some(...)to prevent potential panics.
CI
- Deduplicated audit — Removed redundant
cargo auditjob (handled insecurity-check.yml). - Release tests —
cargo test --all-featuresnow runs before release builds inrelease.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_editMCP 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\nnow match when agents send\nstrings (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. 15–30% 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. 10–25% less noise in multi-file context loads.
- BPE-Aligned Token Optimization — Final-pass string replacements aligned to BPE token boundaries (
function→fn," -> "→"->", lifetime elision). 3–8% additional savings. - Auto-Build Graph Index —
load_or_build()function automatically builds the project dependency graph on first use. No manualctx_graph buildrequired — the system is fully zero-config. - Fish Shell Doctor Check —
lean-ctx doctornow detects shell aliases in~/.config/fish/config.fish(previously only checked zsh/bash). - Codex Hook Refresh on Update —
lean-ctx updatenow 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, andctx_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_DISABLEDentirely. Setting it to1bypassed compression in the Rust code but the shell aliases were still loaded, spawning alean-ctxprocess for every command. Now: the_lc()wrapper short-circuits tocommand "$@"whenLEAN_CTX_DISABLEDis set (zero overhead), the auto-start guard skips alias creation, andlean-ctx -cdoes an immediate passthrough. Closes #42. lean-ctx-statusshows DISABLED state —lean-ctx-statusnow printsDISABLED (LEAN_CTX_DISABLED is set)when the kill-switch is active.- Help text documents both env vars —
--helpnow showsLEAN_CTX_DISABLED=1(full kill-switch) andLEAN_CTX_ENABLED=0(prevent auto-start,lean-ctx-onstill works).
[2.14.3] — 2026-04-02
Added
- Full Output Tee — New
tee_modeconfig (always/failures/never) replaces the oldtee_on_errorboolean. When set toalways, full uncompressed output is saved to~/.lean-ctx/tee/and referenced in compressed output. Backward-compatible:tee_on_error: truemaps tofailures. Uselean-ctx tee lastto view the most recent log. Closes #2021. - Raw Mode — Skip compression entirely with
ctx_shell(command, raw=true)in MCP orlean-ctx -c --raw <command>on CLI. Newlean-ctx-rawshell 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_DISABLEDenv var — Master kill-switch that bypasses all compression in both shell hook and MCP server. SetLEAN_CTX_DISABLED=1to pass everything through unmodified.- ANSI Auto-Strip — ANSI escape sequences are automatically stripped before compression, preventing wasted tokens on invisible formatting codes. Centralized
strip_ansiimplementation replaces 3 duplicated copies. - Passthrough URLs — New
passthrough_urlsconfig 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, andampas supported agents. Uselean-ctx init --agent opencode|aider|amp. Total supported agents: 19. Closes #2026. - Discover Enhancement —
lean-ctx discovernow 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_shellMCP tool schema now acceptsrawboolean 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 quoting —
git 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_streamingmode — replaced byexec_inherit_trackedwhich 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_overvieworctx_preloadon 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_dedupruns automatically to eliminate cross-file redundancy (measured: -89.5% in real sessions) - Task Propagation — Session task context automatically flows to all
ctx_readandctx_multi_readcalls for better compression targeting - Shell Efficiency Hints — When
grep,cat, orfindrun throughctx_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=falseto disable all)- PHP/Laravel Support — Full PHP AST extraction, Laravel-specific compression (Eloquent models, Controllers, Migrations, Blade templates), and
php artisanshell hook patterns - 15 new integration tests for the autonomy layer (
autonomy_tests.rs)
Changed
- System Prompt — Replaced verbose
PROACTIVE+OTHER TOOLSblocks with a compactAUTONOMYblock, 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 command —
lean-ctx --versionnow usesenv!("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_readcall - 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_preloadMCP 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()