Skip to main content

lean_ctx/core/config/
model.rs

1use serde::{Deserialize, Serialize};
2
3#[allow(clippy::wildcard_imports)]
4use super::*;
5fn default_true() -> bool {
6    true
7}
8
9/// Global lean-ctx configuration loaded from `config.toml`, merged with project-local overrides.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(default)]
12pub struct Config {
13    pub ultra_compact: bool,
14    #[serde(default, deserialize_with = "serde_defaults::deserialize_tee_mode")]
15    pub tee_mode: TeeMode,
16    /// Verbosity of the reactive recovery footer on compressed output
17    /// (`off|minimal|full`, default `minimal`). See [`RecoveryHints`].
18    #[serde(default)]
19    pub recovery_hints: RecoveryHints,
20    #[serde(default)]
21    pub output_density: OutputDensity,
22    pub checkpoint_interval: u32,
23    pub excluded_commands: Vec<String>,
24    pub passthrough_urls: Vec<String>,
25    pub custom_aliases: Vec<AliasEntry>,
26    /// Output formats that are already compact/token-oriented and must be
27    /// preserved verbatim instead of being recompressed (#342). Matched against
28    /// the *output shape* (not the command name), so any tool emitting the
29    /// format is covered without enumerating commands in `excluded_commands`.
30    /// Default: `["toon"]`. Set to `[]` to disable and always recompress.
31    #[serde(default = "serde_defaults::default_preserve_compact_formats")]
32    pub preserve_compact_formats: Vec<String>,
33    /// Opt-in: apply the lossless JSON crusher to *verbatim* data commands
34    /// (`gh api`, `jq`, `kubectl get -o json`, `curl` JSON). Off by default, so
35    /// those outputs stay byte-for-byte verbatim. When on, an array-heavy JSON
36    /// payload the crusher can at least halve is reshaped into a compact, fully
37    /// reconstructible form; everything else stays verbatim. See
38    /// [`Config::crush_verbatim_json_enabled`] (#936).
39    #[serde(default)]
40    pub crush_verbatim_json: bool,
41    /// Commands taking longer than this threshold (ms) are recorded in the slow log.
42    /// Set to 0 to disable slow logging.
43    pub slow_command_threshold_ms: u64,
44    #[serde(default = "serde_defaults::default_theme")]
45    pub theme: String,
46    /// Privacy-safe default-on telemetry with explicit and environment opt-outs.
47    #[serde(default)]
48    pub telemetry: TelemetryConfig,
49    /// Legacy `[cloud]` opt-in, migrated on load; never written back.
50    #[serde(default, skip_serializing)]
51    pub cloud: LegacyCloudConfig,
52    /// LeanCTX | Hive connector (`[hive]`).
53    #[serde(default)]
54    pub hive: crate::core::config::hive::HiveConfig,
55    /// Model declaration for measured-vs-estimated cost reporting (MCP-only IDEs).
56    #[serde(default)]
57    pub cost: CostConfig,
58    #[serde(default)]
59    pub decision_loop: DecisionLoopConfig,
60    /// User-controlled never-lossy zones (#1570 P4): protected file globs
61    /// and `<protect>` span handling.
62    #[serde(default)]
63    pub protection: ProtectionConfig,
64    /// Code-health engine: cognitive complexity, naming, coupling, edit-gate.
65    #[serde(default)]
66    pub code_health: CodeHealthConfig,
67    /// Decision layer: pre-edit check mode for `.leanctx/decisions`.
68    #[serde(default)]
69    pub decisions: DecisionsConfig,
70    #[serde(default)]
71    pub autonomy: AutonomyConfig,
72    #[serde(default)]
73    pub providers: ProvidersConfig,
74    #[serde(default)]
75    pub knowledge_routing: KnowledgeRoutingConfig,
76    #[serde(default)]
77    pub proxy: ProxyConfig,
78    /// Conversation-history compression (`[conversation]`, opt-in; #1123).
79    #[serde(default)]
80    pub conversation: ConversationConfig,
81    /// Proxy-layer response shaping (`[response_shaping]`, #1125).
82    #[serde(default)]
83    pub response_shaping: ResponseShapingConfig,
84    #[serde(default)]
85    pub ocla: OclaConfig,
86    /// Global-only, explicit consent and independent pins for optional local code.
87    #[serde(default)]
88    pub intelligence_runtime: IntelligenceRuntimeConfig,
89    /// Counterfactual Shadow Mode reporting (`[shadow]`), opt-in.
90    #[serde(default)]
91    pub shadow: ShadowConfig,
92    /// User-only value display (`[value_display]`): status line, recaps,
93    /// prompt segment, milestones. Never reaches the model context.
94    #[serde(default)]
95    pub value_display: ValueDisplayConfig,
96    /// Generalized L1/L2/L3 cache settings (`[cache]`).
97    #[serde(default)]
98    pub cache: CacheConfig,
99    #[serde(default)]
100    pub agents: sections::AgentsConfig,
101    /// Whether the API proxy is enabled. Tri-state:
102    /// - None: undecided (fresh install, will prompt on interactive setup)
103    /// - Some(true): user opted in, proxy managed by lean-ctx
104    /// - Some(false): user opted out, never touch proxy or endpoints
105    #[serde(default)]
106    pub proxy_enabled: Option<bool>,
107    #[serde(default)]
108    pub proxy_port: Option<u16>,
109    /// Proxy reachability timeout in milliseconds. Default: 200.
110    /// Override via LEAN_CTX_PROXY_TIMEOUT_MS env var.
111    #[serde(default)]
112    pub proxy_timeout_ms: Option<u64>,
113    /// Strict proxy auth: when true, authenticate ONLY via the Bearer token
114    /// (`LEAN_CTX_PROXY_TOKEN`) and disable the provider-API-key fallback. Default
115    /// false keeps the loopback-friendly behavior where any local AI tool's own
116    /// provider key authenticates (the proxy never injects upstream credentials —
117    /// it forwards the caller's key verbatim). Enable on shared/multi-user hosts to
118    /// require the token; clients must then send `Authorization: Bearer [REDACTED:Authorization header]
119    #[serde(default)]
120    pub proxy_require_token: bool,
121    /// Skip ALL proxy authentication on loopback-bound listeners (#755).
122    /// When true **and** the proxy binds a loopback address, every request is
123    /// accepted without a Bearer token or provider API key — MCP clients,
124    /// browser dashboards, and CLI tools all work without auth setup.
125    /// Ignored on non-loopback binds (gateway mode always requires auth).
126    /// Env override: `LEAN_CTX_PROXY_LOOPBACK_OPEN`.
127    #[serde(default)]
128    pub proxy_loopback_open: bool,
129    /// Bind address for the proxy listener (gateway mode, enterprise#8).
130    /// Default `None` = `127.0.0.1` — local-safe, nothing changes for existing
131    /// installs. Set `"0.0.0.0"` (or a specific interface IP) to serve a whole
132    /// org from one host; any non-loopback bind hard-disables the provider-key
133    /// auth fallback (Bearer token becomes mandatory) and enables the
134    /// `proxy_allowed_hosts` Host-header allowlist. Env override:
135    /// `LEAN_CTX_PROXY_BIND_HOST`. An unparseable value falls back to loopback,
136    /// never to an open bind.
137    #[serde(default)]
138    pub proxy_bind_host: Option<String>,
139    /// Host-header allowlist for a non-loopback proxy bind (gateway mode):
140    /// DNS-rebinding protection. Entries are hostnames or IPs without port
141    /// (e.g. `"gateway.example.com"`). Loopback names are always allowed.
142    /// Ignored (loopback-only guard, today's behavior) while the bind is
143    /// loopback. Empty + non-loopback bind = only loopback Host headers pass,
144    /// so configure this when exposing the gateway.
145    #[serde(default)]
146    pub proxy_allowed_hosts: Vec<String>,
147    /// Proxy-wide request rate limit in requests/second (token bucket, burst =
148    /// 2x). `None` (default) = unlimited on a loopback bind — today's behavior —
149    /// and 50 rps with burst 100 on a non-loopback bind (gateway mode ships a
150    /// sane floor, enterprise#37). `0` disables the limiter even in gateway
151    /// mode (explicit opt-out).
152    #[serde(default)]
153    pub proxy_max_rps: Option<u32>,
154    /// Require Bearer-token authentication for the dashboard. Default `true`:
155    /// the dashboard generates (or uses the pinned) token and rejects `/api/*`
156    /// and `/metrics` without it. Set to `false` to run the dashboard with **no
157    /// auth token** — useful for a local/Docker setup where managing a token is
158    /// inconvenient. No-auth mode is not unprotected: cross-origin and CSRF
159    /// attacks from a malicious local website are blocked by request-header
160    /// validation instead (`Sec-Fetch-Site`, `Origin`/`Host` same-origin, and a
161    /// `Host` allowlist against DNS rebinding — see `dashboard::no_auth_request_ok`).
162    /// Override per-run via the `--no-auth` / `--auth=<bool>` flag or the
163    /// `LEAN_CTX_DASHBOARD_AUTH` env var.
164    #[serde(default = "serde_defaults::default_true")]
165    pub dashboard_auth: bool,
166    /// Provider prompt-cache hit rate for net-of-injection calculation (#1104).
167    /// Share of turns whose prefix is served from the provider cache; an
168    /// estimate, default 0.75. Prices: `context_overhead::prefix_cost_factor`.
169    #[serde(default)]
170    pub dashboard_cache_hit_rate: Option<f64>,
171    #[serde(default = "serde_defaults::default_buddy_enabled")]
172    pub buddy_enabled: bool,
173    #[serde(default = "serde_defaults::default_true")]
174    pub enable_wakeup_ctx: bool,
175    /// File globs delivered verbatim: native reads skip the hook redirect and
176    /// automatic `ctx_read` modes return them in full (#60).
177    #[serde(default)]
178    pub redirect_exclude: Vec<String>,
179    /// Tools to exclude from the MCP tool list returned by list_tools.
180    /// Accepts exact tool names (e.g. `["ctx_graph", "ctx_agent"]`).
181    /// Empty by default — all tools listed, no behaviour change.
182    #[serde(default)]
183    pub disabled_tools: Vec<String>,
184    /// Prefer the host agent's native editor over lean-ctx edit operations (#454).
185    /// When true, the lean-ctx edit tool(s) (see [`EDIT_TOOL_NAMES`]) are neither
186    /// advertised in `list_tools` nor dispatchable (direct or via `ctx_call`), so
187    /// the agent falls back to the host's built-in editing UI. Reads / search /
188    /// shell / memory tools are unaffected. Override via
189    /// `LEAN_CTX_PREFER_NATIVE_EDITOR=1`.
190    #[serde(default)]
191    pub prefer_native_editor: bool,
192    /// Tool categories to activate by default for dynamic-tool-capable clients.
193    /// Values: "core" (always on), "arch", "debug", "memory", "metrics", "session".
194    /// Example: `default_tool_categories = ["core", "arch", "memory"]`
195    /// Override via LCTX_DEFAULT_CATEGORIES env var (comma-separated).
196    /// Empty = lean-ctx default (`core`). Add `session` explicitly to evaluate
197    /// experimental local collaboration tools.
198    #[serde(default)]
199    pub default_tool_categories: Vec<String>,
200    /// Disable all automatic read-mode degradation (auto_degrade + context_gate pressure).
201    /// When true, lean-ctx never downgrades requested read modes regardless of pressure.
202    /// Override via LCTX_NO_DEGRADE=1 env var.
203    #[serde(default)]
204    pub no_degrade: bool,
205    /// Legacy opt-in delta preference. Concrete per-call full/lines requests
206    /// take precedence and stay fresh; this cannot replace their content with
207    /// a delta. Use mode=auto or mode=diff for reduced re-reads.
208    /// Retained for configuration compatibility. LCTX_DELTA_EXPLICIT=1/0 overrides.
209    #[serde(default)]
210    pub delta_explicit: bool,
211    /// Persistent profile name. Checked after LEAN_CTX_PROFILE env var.
212    /// Set via `lean-ctx config set profile passthrough` or editing config.toml.
213    #[serde(default)]
214    pub profile: Option<String>,
215    /// Active task-specific Context Kit. `LEAN_CTX_KIT` takes precedence.
216    /// Set with `lean-ctx kit load <name>`.
217    #[serde(default)]
218    pub active_kit: Option<String>,
219    /// Named configuration overlay selected from `[profiles.<name>]`.
220    /// `LEAN_CTX_CONFIG_PROFILE` takes precedence over this persisted selector.
221    #[serde(default)]
222    pub config_profile: Option<String>,
223    /// Partial configuration overlays keyed by profile name. Each overlay is
224    /// recursively merged over the base configuration at load time.
225    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
226    pub profiles: std::collections::BTreeMap<String, toml::Table>,
227    /// Tool visibility profile: "minimal" (5), "standard" (15), or "power" (all).
228    /// Override via LEAN_CTX_TOOL_PROFILE env var.
229    /// Existing installs default to "power" (backward compat).
230    #[serde(default)]
231    pub tool_profile: Option<String>,
232    /// Explicit list of enabled tool names. Used only when no tool_profile is pinned (tool_profile takes precedence); leave tool_profile unset to apply this list.
233    /// The universal invoker `ctx_call` stays advertised so unlisted tools remain
234    /// reachable — add `ctx_call` to `disabled_tools` to make this allowlist authoritative.
235    /// Example: `tools_enabled = ["ctx_read", "ctx_shell", "ctx_search"]`
236    #[serde(default)]
237    pub tools_enabled: Vec<String>,
238    /// Active context persona (`persona-spec-v1`). Selects the domain bundle —
239    /// tool surface, read-mode/compressor/chunker defaults, intent taxonomy,
240    /// sensitivity floor. Override via `LEAN_CTX_PERSONA`. Defaults to `coding`.
241    #[serde(default)]
242    pub persona: Option<String>,
243    #[serde(default)]
244    pub loop_detection: LoopDetectionConfig,
245    /// Controls where lean-ctx installs agent rule files.
246    /// Values: "both" (default), "global" (home-dir only), "project" (repo-local only).
247    /// Override via LEAN_CTX_RULES_SCOPE env var.
248    #[serde(default)]
249    pub rules_scope: Option<String>,
250    /// Controls how rules are injected for shared-instruction-file agents.
251    /// Values: "shared" (default, marker block in CLAUDE.md/CODEBUDDY.md/AGENTS.md/GEMINI.md),
252    /// "dedicated" (never touch those files; use each agent's config-driven
253    /// auto-load: SessionStart hook / instructions[] / context.fileName, #343), or
254    /// "off" (write no rules file at all — for hosts that supply their own
255    /// tool-steering workflow or phase-isolated/non-caching harnesses, #361).
256    /// Override via LEAN_CTX_RULES_INJECTION env var.
257    #[serde(default)]
258    pub rules_injection: Option<String>,
259    /// Mirror the host IDE's tool-permission rules onto lean-ctx's own MCP tools.
260    /// Values: "on" (default, GH #1428) or "off". When "on", lean-ctx reads the active
261    /// IDE's permission config (v1: OpenCode) and applies the equivalent
262    /// deny/ask/allow decision to the matching lean-ctx tool — so `ctx_shell`
263    /// honors your `bash`/`rm *` rules instead of bypassing them.
264    /// Override via LEAN_CTX_PERMISSION_INHERITANCE env var.
265    #[serde(default)]
266    pub permission_inheritance: Option<String>,
267    /// Extra glob patterns to ignore in graph/overview/preload (repo-local).
268    /// Example: `["externals/**", "target/**", "temp/**"]`
269    #[serde(default)]
270    pub extra_ignore_patterns: Vec<String>,
271    /// Controls agent output verbosity via instructions injection.
272    /// Values: "off" (default), "lite", "full", "ultra".
273    /// Override via LEAN_CTX_TERSE_AGENT env var.
274    #[serde(default)]
275    pub terse_agent: TerseAgent,
276    /// Unified compression level (replaces separate terse_agent + output_density).
277    /// Values: "off" (default), "lite", "standard", "max".
278    /// Override via LEAN_CTX_COMPRESSION env var.
279    #[serde(default)]
280    pub compression_level: CompressionLevel,
281    /// Global compression intensity 0.0 (lossless) – 1.0 (max), mapped onto the
282    /// read modes / entropy / IB stages (see `core::aggressiveness`). `None`
283    /// (default) keeps each mode's built-in default. Override via the
284    /// `LEAN_CTX_AGGRESSIVENESS` env var or the `ctx_read` `aggressiveness` arg.
285    #[serde(default)]
286    pub compression_aggressiveness: Option<f64>,
287    /// Archive configuration for zero-loss compression.
288    #[serde(default)]
289    pub archive: ArchiveConfig,
290    /// Memory policy (knowledge/lifecycle budgets & thresholds; the episodic
291    /// and procedural sections are retired and have no effect).
292    #[serde(default)]
293    pub memory: MemoryPolicy,
294    /// Additional paths allowed by PathJail (absolute).
295    /// Useful for multi-project workspaces where the jail root is a parent directory.
296    /// Override via LEAN_CTX_ALLOW_PATH env var (path-list separator).
297    #[serde(default)]
298    pub allow_paths: Vec<String>,
299    /// Allow jailed tool access to home-level IDE config dirs (~/.cursor, VS Code,
300    /// Cline/Roo, JetBrains, …). Tri-state: `None` = not asked yet (setup prompts
301    /// once), `Some(false)` = declined, `Some(true)` = opted in. Those dirs can
302    /// expose other agents' sessions, MCP configs and credentials, so the effective
303    /// default is off. `~/.lean-ctx` (own data dir) is always allowed. The opt-in
304    /// set is registry-derived, covering every supported editor. Override via
305    /// LEAN_CTX_ALLOW_IDE_DIRS=1.
306    #[serde(default)]
307    pub allow_ide_config_dirs: Option<bool>,
308    /// Extra project roots for multi-root workspaces.
309    /// Tools like ctx_tree and ctx_search can scan across all roots in a single call.
310    /// These paths are automatically added to PathJail's allow-list.
311    /// Override via LEAN_CTX_EXTRA_ROOTS env var (path-list separator).
312    #[serde(default)]
313    pub extra_roots: Vec<String>,
314    /// Read-only roots: sibling subtrees the agent may READ but never WRITE.
315    /// Reads resolve as if they were extra_roots; every write tool (edit, refactor,
316    /// handoff/session export, memory compaction) is default-denied inside these
317    /// paths. Useful for reference repos mounted next to the project.
318    /// Override via LEAN_CTX_READ_ONLY_ROOTS env var (path-list separator).
319    #[serde(default)]
320    pub read_only_roots: Vec<String>,
321    /// Extra trusted roots OUTSIDE `$HOME` that lean-ctx may follow when an agent
322    /// config file/dir (`~/.claude.json`, `~/.codex/config.toml`, …) is a symlink
323    /// pointing there (#596). Empty by default → the strict `$HOME`-only boundary
324    /// stays in force (a planted symlink can never redirect a config write out of
325    /// the user's home, preserving the GL#442 symlink-hijack protection). Add a
326    /// parent like `/opt/dotfiles` only for a location you own and trust. Like
327    /// `extra_roots`, security-sensitive: stripped from untrusted project-local
328    /// configs. Override via LEAN_CTX_ALLOW_SYMLINK_ROOTS env var (path-list sep).
329    #[serde(default)]
330    pub allow_symlink_roots: Vec<String>,
331    /// Enable content-defined chunking (Rabin-Karp) for cache-optimal output ordering.
332    /// Stable chunks are emitted first to maximize prompt cache hits.
333    #[serde(default)]
334    pub content_defined_chunking: bool,
335    /// Skip session/knowledge/gotcha blocks in MCP instructions to minimize token overhead.
336    /// Override via LEAN_CTX_MINIMAL env var.
337    ///
338    /// Default `true` (deliberate): initialize-time instructions stay byte-stable
339    /// across sessions, which keeps the provider prompt-cache prefix warm (#498)
340    /// and holds the fixed per-session cost at the `doctor overhead --gate`
341    /// budget. Session continuity is NOT lost — the wakeup briefing (task,
342    /// findings, knowledge) is delivered through the first tool call's
343    /// `--- AUTO CONTEXT ---` block instead, which only bills when the agent
344    /// actually works. Set to `false` to additionally inject the ACTIVE SESSION
345    /// / PROJECT MEMORY blocks directly into the MCP `initialize` instructions.
346    /// #1540: this key never disables the archive/ephemeral firewall — only
347    /// the `LEAN_CTX_MINIMAL` env escape hatch skips that safety net.
348    /// `default_true`, not `default`: a field-level `#[serde(default)]` made
349    /// every config.toml without this key read `false` (bool's default), so
350    /// the documented default `true` only held for users with no config file
351    /// (focus analysis 2026-10-15).
352    #[serde(default = "default_true")]
353    pub minimal_overhead: bool,
354    /// Opt-in: substitute long identifiers with short α-codes (+ a `§MAP` table)
355    /// in `aggressive` reads for projects with >50 source files. Off by default —
356    /// the abbreviated form is confusing for editing/refactoring, where the agent
357    /// needs the real package and symbol names. Enable for max exploration savings.
358    #[serde(default)]
359    pub symbol_map_auto: bool,
360    /// Opt-in: bias `auto` toward structure-first reads (`map`) for medium code
361    /// files on a cold read. Off by default — interactive sessions keep the
362    /// conservative `full` floor that avoids a follow-up body read. Enable for
363    /// phase-isolated harnesses (no warm-session cache payback), where a cold
364    /// `full` read is pure overhead and structure-first reads aid localization.
365    /// Override via the LEAN_CTX_STRUCTURE_FIRST env var.
366    #[serde(default)]
367    pub structure_first: bool,
368    /// Progressive disclosure for first-time reads (LCLM arXiv 2606.09659).
369    /// When true (default), large files default to compact overviews on first read:
370    ///     - Below progressive_threshold_lines: full content
371    ///     - Below progressive_signatures_max_lines: signatures mode
372    ///     - Above: map (manifest) mode
373    /// Models can always bypass with explicit mode= or lines= parameters.
374    /// Override via LEAN_CTX_PROGRESSIVE_DISCLOSURE env var.
375    #[serde(default = "serde_defaults::default_true")]
376    pub progressive_disclosure: bool,
377    /// Files with fewer lines than this threshold are always delivered in full.
378    /// Default: 100 lines.
379    #[serde(default = "serde_defaults::default_progressive_threshold_lines")]
380    pub progressive_threshold_lines: u32,
381    /// Files between threshold and this limit get signatures mode.
382    /// Files above get map (manifest) mode. Default: 500 lines.
383    #[serde(default = "serde_defaults::default_progressive_signatures_max")]
384    pub progressive_signatures_max: u32,
385    /// Retired, no effect (kept so existing configs load; `DEPRECATIONS.toml`).
386    /// The learned `auto` signals it switched on are gone: `auto` is always the
387    /// deterministic capability-guard and size/task cascade (#498). The
388    /// `LEAN_CTX_AUTO_MODE_LEARNING` env var is ignored as well.
389    #[serde(default)]
390    pub auto_mode_learning: bool,
391    /// Team content sharing (strategy 2026-10-14, acceptance T07). When false,
392    /// nothing whose body is content leaves for the hosted Team service: no bus
393    /// post, work-graph write, knowledge entry or artifact publication. Presence,
394    /// leases and reads (metadata) keep working. On by default.
395    #[serde(default = "serde_defaults::default_true")]
396    pub team_share_content: bool,
397    /// Enable human-readable activity journal (~/.lean-ctx/journal.md).
398    #[serde(default)]
399    pub journal_enabled: bool,
400    /// Opt-in: auto-persist interesting findings as knowledge facts.
401    #[serde(default)]
402    pub auto_capture: bool,
403    /// Hybrid search weights (BM25/dense/candidates).
404    #[serde(default)]
405    pub search: crate::core::hybrid_search::HybridConfig,
406    /// Index-time file filters (#735): include/exclude globs + gitignore
407    /// handling, applied by every index builder via `core::index_filter`.
408    #[serde(default)]
409    pub index: IndexConfig,
410    /// Skillify miner settings (#290): codify recurring patterns into rules.
411    #[serde(default)]
412    pub skillify: SkillifyConfig,
413    /// AI session-summary settings (#292): periodic, semantically-recallable summaries.
414    #[serde(default)]
415    pub summaries: SummariesConfig,
416    /// Retired, no effect (kept so existing configs load; `DEPRECATIONS.toml`
417    /// `config-llm`): LeanCTX calls no model itself.
418    #[serde(default)]
419    pub llm: LlmConfig,
420    /// Semantic-embedding engine settings (which local ONNX model to use).
421    #[serde(default)]
422    pub embedding: EmbeddingConfig,
423    /// Disable shell hook injection (the _lc() function that wraps CLI commands).
424    /// Override via LEAN_CTX_NO_HOOK env var.
425    #[serde(default)]
426    pub shell_hook_disabled: bool,
427    /// Shadow mode (default: true): lean-ctx steers agents to ctx_* MCP tools,
428    /// and an explicit `hook_mode = "replace"` may deny native Grep/Glob.
429    /// `shadow_mode = false` caps every agent at Hybrid, so native tools are
430    /// never denied.
431    /// Native Shell/Bash coverage is governed separately by `shell_hook_mode`.
432    #[serde(default = "serde_defaults::default_true")]
433    pub shadow_mode: bool,
434    /// Shell hook mode (#1278): how the PreToolUse shell hook treats native
435    /// Bash/Shell calls.
436    /// - `rewrite` (default): rewrite known read/search/list commands to
437    ///   lean-ctx equivalents; anything unrecognized passes through raw.
438    /// - `deny`: block native shell calls outright so every command goes
439    ///   through `ctx_shell` (same fail-opens as the deny hook: lean-ctx
440    ///   disabled, MCP daemon dead, explicit shadow-only tool surface).
441    /// - `auto`: currently equals `rewrite`; reserved for a future default flip.
442    ///
443    /// Env override: `LEAN_CTX_SHELL_HOOK_MODE`. Lives in config.toml, so the
444    /// choice survives reinstalls and updates.
445    #[serde(default)]
446    pub shell_hook_mode: Option<String>,
447    /// Per-turn tool-routing reinjection (#1288): the UserPromptSubmit hook
448    /// emits a one-line `additionalContext` reminder of the ctx_* routing, so it
449    /// post-dates any session-level harness instruction preferring native Bash.
450    /// `on` | `off` | `auto` (default: auto = off, since the host's rules channel
451    /// already carries the routing once, A3). Env override:
452    /// `LEAN_CTX_PROMPT_REINJECT`. Costs ~45 tokens per turn when active.
453    #[serde(default)]
454    pub prompt_reinject: Option<String>,
455    /// Global hook mode override. When set, overrides the per-agent auto-detection.
456    /// - `replace`: native Grep/Glob (and non-rewritable shell where the host
457    ///   supports it) denied, lean-ctx MCP is the only path — opt-in only
458    /// - `hybrid`: MCP + shell hooks; native tools keep working
459    /// - `mcp`: MCP server only, no hooks
460    /// - `auto`/unset: Hybrid for hook-capable agents, else MCP
461    ///   (`recommend_hook_mode`)
462    #[serde(default)]
463    pub hook_mode: Option<String>,
464    /// MCP tool surface mode. Controls how many tools `tools/list` advertises.
465    /// - `auto` (default): shadow-only (`ctx_call` only) for hook-covered
466    ///   clients, full lazy-core surface for all others.
467    /// - `mcp`: always advertise the full lazy-core/profile surface.
468    /// - `shadow`: always advertise only `ctx_call` (requires installed hooks).
469    #[serde(default)]
470    pub tool_surface: Option<String>,
471    /// Opt-in (#520): write a human-readable debug log of intercepted MCP tool
472    /// calls and hook routing decisions (lean-ctx vs native, with reasons) to
473    /// `<state_dir>/logs/debug.log`. Override via the LEAN_CTX_DEBUG_LOG env var.
474    #[serde(default)]
475    pub debug_log: bool,
476    /// Controls when the shell hook auto-activates aliases.
477    /// - `agents-only`: (Default since #699) Aliases only active when an AI
478    ///   agent env var is detected — transparent in plain human terminals.
479    /// - `always`: Aliases active in every interactive shell (pre-#699 default).
480    /// - `off`: Aliases never auto-activate (user must call `lean-ctx-on` manually).
481    ///
482    /// Override via `LEAN_CTX_SHELL_ACTIVATION` env var.
483    #[serde(default)]
484    pub shell_activation: ShellActivation,
485    /// Do not install agent CLI aliases (`claude`, `codex`, `gemini`,
486    /// `codebuddy`) into `~/.zshrc` / `~/.bashrc` during `onboard` / `setup`.
487    /// Existing alias blocks are removed when this is toggled on (#754).
488    /// Does NOT affect the shell compression hook (`_lc()`) — use
489    /// `shell_hook_disabled` for that. Orthogonal to `shell_activation` which
490    /// controls *when* aliases activate, not *whether* they are installed.
491    #[serde(default)]
492    pub skip_agent_aliases: bool,
493    /// Controls the native-Read → `ctx_read` redirect hook (#637).
494    /// - `auto`: (Default) redirect everywhere except hosts with a native
495    ///   read-before-write guard (Claude Code / CodeBuddy), where the path-swap
496    ///   would break native Write/Edit.
497    /// - `on`: always redirect (legacy behavior).
498    /// - `off`: never redirect native Read.
499    ///
500    /// Override via the `LEAN_CTX_READ_REDIRECT` env var.
501    #[serde(default)]
502    pub read_redirect: ReadRedirect,
503    /// Controls the PostToolUse native-Read re-read dedup (GL #1140).
504    /// - `auto`: (Default) replace only re-reads of unchanged files, and only on
505    ///   guard hosts (Claude Code / CodeBuddy) where the PreToolUse redirect is
506    ///   disabled — the guard-safe way to win the dedup savings back.
507    /// - `on`: dedup wherever the PostToolUse hook fires.
508    /// - `off`: never replace a Read result.
509    ///
510    /// Override via the `LEAN_CTX_READ_DEDUP` env var.
511    #[serde(default)]
512    pub read_dedup: ReadDedup,
513    /// Ask the user to approve a command the shell allowlist does not name,
514    /// when the MCP client supports elicitation (`ask`, default) or refuse
515    /// it without asking (`off`). Global only. Override via the
516    /// `LEAN_CTX_SHELL_APPROVAL` env var.
517    #[serde(default)]
518    pub shell_approval: ShellApproval,
519    /// Languages `ctx_execute` runs without asking (`python`, `javascript`,
520    /// …): what "Always allow" in its approval prompt writes. Global only.
521    #[serde(default)]
522    pub execute_allowed_languages: Vec<String>,
523    /// Disable the daily version check against
524    /// api.github.com/repos/yvgude/lean-ctx/releases/latest.
525    /// Override via LEAN_CTX_NO_UPDATE_CHECK env var.
526    #[serde(default)]
527    pub update_check_disabled: bool,
528    #[serde(default)]
529    pub updates: UpdatesConfig,
530    /// Fixed-context budget accounting for `doctor overhead` / `gain` (#964).
531    #[serde(default)]
532    pub context: ContextConfig,
533    /// Maximum BM25 cache file size in MB. Indexes exceeding this are quarantined on load
534    /// and refused on save. Override via LEAN_CTX_BM25_MAX_CACHE_MB env var.
535    #[serde(default = "serde_defaults::default_bm25_max_cache_mb")]
536    pub bm25_max_cache_mb: u64,
537    /// Maximum number of files scanned by the lightweight JSON graph index.
538    /// 0 = unlimited (default). Set >0 to cap for constrained systems.
539    #[serde(default = "serde_defaults::default_graph_index_max_files")]
540    pub graph_index_max_files: u64,
541    /// Maximum number of files indexed into the BM25 corpus; the semantic index
542    /// chunks the same corpus (#737), so this bounds semantic coverage too.
543    /// 0 = unlimited. Default 5000 (MAX_BM25_FILES).
544    #[serde(default = "serde_defaults::default_bm25_max_files")]
545    pub bm25_max_files: u64,
546    /// Controls RAM vs feature trade-off. Values: "low", "balanced" (default), "performance".
547    /// Override via LEAN_CTX_MEMORY_PROFILE env var.
548    #[serde(default)]
549    pub memory_profile: MemoryProfile,
550    /// Controls how aggressively memory is freed when idle.
551    /// Values: "shared" (default, 1h TTL), "aggressive" (5 min TTL for low-memory devices).
552    /// Override via LEAN_CTX_MEMORY_CLEANUP env var.
553    #[serde(default)]
554    pub memory_cleanup: MemoryCleanup,
555    /// How the code graph uses local semantic backends (language servers, a
556    /// live JetBrains IDE). Values: "off", "auto" (default: only backends that
557    /// are already running), "eager" (may start language servers).
558    /// Override via LEAN_CTX_SEMANTIC_MODE env var.
559    #[serde(default)]
560    pub semantic_mode: SemanticMode,
561    /// Soft process-RSS target as a percentage of system RAM (default: 5).
562    /// The guardian throttles and evicts above it, but this is not an OS hard cap.
563    /// Use a cgroup/container MemoryMax when strict isolation is required.
564    /// Override via LEAN_CTX_MAX_RAM_PERCENT env var.
565    #[serde(default = "serde_defaults::default_max_ram_percent")]
566    pub max_ram_percent: u8,
567    /// Per-process RSS target (MB) of each stdio MCP server; the guardian uses
568    /// the lower of this and `max_ram_percent` (default 512, 0 = percent only).
569    /// Hosts like the Codex app-server keep one server per loaded thread alive
570    /// for days, so a percentage of a large machine's RAM alone is too loose.
571    /// Override via LEAN_CTX_MCP_MAX_RSS_MB env var.
572    #[serde(default = "serde_defaults::default_mcp_max_rss_mb")]
573    pub mcp_max_rss_mb: u64,
574    /// Process RSS target (MB) of the background daemon; the guardian uses the
575    /// lower of this and `max_ram_percent` (default 2048, 0 = percent only).
576    /// One daemon serves every session and project root on the machine for
577    /// days, so a percentage of a large machine's RAM alone is too loose (#2075).
578    /// Override via LEAN_CTX_DAEMON_MAX_RSS_MB env var.
579    #[serde(default = "serde_defaults::default_daemon_max_rss_mb")]
580    pub daemon_max_rss_mb: u64,
581    /// Exit a stdio MCP server after this many minutes without a tool call
582    /// (0 = never, the default). Off by default because some hosts (Codex)
583    /// do not restart an exited server: the resumed thread then loses
584    /// lean-ctx until the host restarts. Idle servers release their caches
585    /// either way (`memory_cleanup`). Override via LEAN_CTX_MCP_IDLE_EXIT_MINUTES.
586    #[serde(default)]
587    pub mcp_idle_exit_minutes: u64,
588    /// Simplified disk budget (MB). When set and detail values are at defaults,
589    /// distributes proportionally: archive=25%, bm25=10%, remainder for stores.
590    /// 0 = disabled (use individual settings). Override via LEAN_CTX_MAX_DISK_MB.
591    #[serde(default)]
592    pub max_disk_mb: u64,
593    /// Auto-purge data older than this many days. 0 = disabled.
594    /// Flows into archive.max_age_hours and lifecycle idle TTL.
595    #[serde(default)]
596    pub max_staleness_days: u32,
597    /// Retention window for explicit session cleanup. This is independent from
598    /// archive retention and always preserves the newest session per project.
599    /// Override via LEAN_CTX_SESSION_RETENTION_DAYS.
600    #[serde(default = "serde_defaults::default_session_retention_days")]
601    pub session_retention_days: u32,
602    /// Cap on the rayon worker threads used by the CPU-heavy index build
603    /// (call graph etc.). 0 = rayon default (all cores). Set >0 to bound
604    /// per-instance CPU so a fleet of concurrent sessions can't saturate the
605    /// host on startup. Override via LEANCTX_INDEX_THREADS env var.
606    #[serde(default)]
607    pub max_index_threads: usize,
608    /// Controls visibility of token savings footers in tool output.
609    /// Values: "never" (default), "always" (show on every response), "auto" (legacy compatibility).
610    /// Override via LEAN_CTX_SAVINGS_FOOTER or LEAN_CTX_SHOW_SAVINGS=1|0 env var.
611    #[serde(default)]
612    pub savings_footer: SavingsFooter,
613    /// Controls compression annotation style in savings footers.
614    /// Values: "quantized" (default, round to 10% buckets), "full" (exact %), "none" (suppress all).
615    /// Override via LEAN_CTX_COMPRESSION_ANNOTATION env var.
616    #[serde(default)]
617    pub compression_annotation: CompressionAnnotation,
618    /// Minimum savings percentage to emit a footer annotation. Below this threshold,
619    /// annotations are suppressed (the savings are too small to be worth the token cost).
620    /// Default: 5 (suppress annotations for savings below 5%).
621    #[serde(default = "serde_defaults::default_annotation_threshold_pct")]
622    pub annotation_threshold_pct: u8,
623    /// In-band behavior hints (heavy full/raw reads, search→read→search
624    /// chains): "auto" (default, one hint per pattern per session) or "off".
625    /// Detections are always counted for `tools health`, hints or not.
626    /// "off" also drops the OUTPUT-HINT line from a task-filtered
627    /// `ctx_overview`: that line shapes the assistant's answer rather than
628    /// reporting a retrieval result, so it belongs to the same switch (#1763).
629    #[serde(default = "serde_defaults::default_behavior_nudges")]
630    pub behavior_nudges: String,
631    /// Maximum fresh tokens per single tool response (turn budget).
632    /// 0 = unlimited. Default: 4096. Prevents context bloat from oversized responses.
633    /// Override via LEAN_CTX_TURN_FRESH_LIMIT env var.
634    #[serde(default = "serde_defaults::default_turn_fresh_limit")]
635    pub turn_fresh_limit: usize,
636    /// Turn budget for a response the caller explicitly asked to be verbatim
637    /// (`ctx_read` with `raw=true` or `mode="raw"`). 0 = unlimited.
638    /// Default: 32768. Override via LEAN_CTX_TURN_FRESH_LIMIT_VERBATIM.
639    ///
640    /// The ordinary `turn_fresh_limit` is a compression backstop for output the
641    /// agent did not size itself. A verbatim read is the documented recovery
642    /// path out of compression (#1582); capping it at the same 4096 tokens made
643    /// that path unreachable for any file above ~16 KB.
644    #[serde(default = "serde_defaults::default_turn_fresh_limit_verbatim")]
645    pub turn_fresh_limit_verbatim: usize,
646    /// Explicit project root override. When set, lean-ctx uses this instead of auto-detection.
647    /// This prevents accidental home-directory scans when running from $HOME.
648    /// Override via LEAN_CTX_PROJECT_ROOT env var.
649    #[serde(default)]
650    pub project_root: Option<String>,
651    /// LSP server overrides. Map language name to custom binary path.
652    /// Example: `[lsp]\nrust = "/opt/rust-analyzer"\npython = "~/.venvs/main/bin/pylsp"`
653    #[serde(default)]
654    pub lsp: std::collections::HashMap<String, String>,
655    /// Per-IDE allowed paths. Restricts which directories lean-ctx will scan/index for each IDE.
656    /// Example: `[ide_paths]\ncursor = ["/home/user/projects/app1"]\ncodex = ["/home/user/codex"]`
657    /// When set, only these paths are indexed for the matching agent. Global `allow_paths` still applies.
658    #[serde(default)]
659    pub ide_paths: HashMap<String, Vec<String>>,
660    /// Custom model context window overrides.
661    /// Example: `[model_context_windows]\n"my-custom-model" = 500000`
662    #[serde(default)]
663    pub model_context_windows: HashMap<String, usize>,
664    /// Controls how much detail tool responses include.
665    ///
666    /// - `full` (default): complete compressed output
667    /// - `headers_only`: metadata line only (path, mode, token count)
668    ///
669    /// Override via `LEAN_CTX_RESPONSE_VERBOSITY` env var.
670    #[serde(default)]
671    pub response_verbosity: ResponseVerbosity,
672    /// Bypass hint mode. When agents use native Read/Grep instead of lean-ctx tools,
673    /// a hint is appended to the next tool response.
674    /// Values: "on" (default), "off", "aggressive" (hint on every call, no cooldown).
675    /// Override via LEAN_CTX_BYPASS_HINTS env var.
676    #[serde(default)]
677    pub bypass_hints: Option<String>,
678    /// Cache policy for ctx_read. Controls behavior on cache hits.
679    /// Values: "aggressive" (default, 13-tok stubs + compaction-aware reset),
680    /// "safe" (delivers map instead of stub), "off" (no caching, always disk read).
681    /// Override via LEAN_CTX_CACHE_POLICY env var.
682    #[serde(default)]
683    pub cache_policy: Option<String>,
684    /// Token budget for the in-memory `ctx_read` cache. When the cached total
685    /// plus an incoming read would exceed this, lean-ctx evicts the least-valuable
686    /// entries *immediately* (RRF: recency × frequency × size) so the read always
687    /// proceeds — eviction is never deferred to the staleness TTL. `0` uses the
688    /// built-in default (2M). `LEAN_CTX_CACHE_MAX_TOKENS` env var overrides this.
689    #[serde(default)]
690    pub cache_max_tokens: usize,
691    /// Cross-project boundary policy.
692    /// Controls whether cross-project search/import is allowed and whether access is audited.
693    #[serde(default)]
694    pub boundary_policy: crate::core::memory_boundary::BoundaryPolicy,
695    #[serde(default)]
696    pub secret_detection: SecretDetectionConfig,
697    /// Per-item sensitivity model with a uniform policy floor (#212).
698    /// Disabled by default → fully no-op until `sensitivity.enabled = true`.
699    #[serde(default)]
700    pub sensitivity: crate::core::sensitivity::SensitivityConfig,
701    /// Context Gateway admission (`[context_gateway]`): built-in detectors
702    /// that run on every governed source before caching or compression.
703    /// On by default. Global-only: a project-local file cannot weaken it.
704    #[serde(default)]
705    pub context_gateway: crate::core::context_admission::ContextGatewayConfig,
706    /// Set by the loader when the global `config.toml` exists but could not be
707    /// read or parsed, so this snapshot holds built-in defaults instead of the
708    /// operator's settings. It travels with exactly this `Config` value: an
709    /// earlier failed snapshot stays marked after a later successful load.
710    /// Never read from or written to a file.
711    #[serde(skip)]
712    pub load_failure: Option<super::ConfigLoadFailure>,
713    /// Accept A2A handoff bundles and context packages whose signature
714    /// verifies but whose signer key is not anchored in `trusted-signers.toml`
715    /// (GHSA-gvg8-285q-m62g). Off by default: anyone can mint a key, so an
716    /// untrusted signature proves nothing about the sender. Global-only
717    /// (never merged from project-local config).
718    #[serde(default)]
719    pub a2a_accept_untrusted_signers: bool,
720    /// Let MCP tools (`ctx_handoff import`, `ctx_pack import`) import handoff
721    /// bundles and context packages that are unsigned or signed by a key not
722    /// anchored in `trusted-signers.toml` (GHSA-gvg8-285q-m62g). A tool
723    /// argument cannot grant this — the model could set it under prompt
724    /// injection. Off by default; global-only. CLI imports keep their own
725    /// human opt-in (`--allow-unsigned`).
726    #[serde(default)]
727    pub mcp_import_allow_unverified: bool,
728    /// MCP Tool-Catalog Gateway (#210): aggregate + query-route downstream MCP
729    /// servers. Global-only (never merged from project-local config) and a full
730    /// no-op until `gateway.enabled = true`.
731    #[serde(default)]
732    pub gateway: crate::core::mcp_catalog::GatewayConfig,
733    /// Self-hosted org gateway server (`[gateway_server]`, enterprise#20):
734    /// deployment parameters for the usage cockpit — seat count for the
735    /// org-wide projection, display label, and the central admin API the local
736    /// cockpit may read from. All optional; absent = local-only behavior.
737    #[serde(default)]
738    pub gateway_server: GatewayServerConfig,
739    /// Enterprise Suite connection (`[enterprise]`): connects this Runtime to
740    /// a LeanCTX Enterprise Gateway for economics tracking and model routing.
741    #[serde(default)]
742    pub enterprise: EnterpriseConfig,
743    /// Verbatim binary path/expression for generated agent-hook commands
744    /// (#708). Users who sync agent settings (`~/.claude/settings.json`, …)
745    /// across machines with different usernames need an env-based form like
746    /// `$HOME/.local/bin/lean-ctx` — agent hosts run hook commands through a
747    /// shell, which expands it. When set (env `LEAN_CTX_HOOK_BINARY` wins,
748    /// then this key), every hook writer emits the value verbatim instead of
749    /// the machine-absolute exe path, so `init`/`doctor --fix`/`update` stop
750    /// rewriting synced files into sync ping-pong. Autostart plists/services
751    /// and daemon spawns are NOT affected — launchd/systemd do not expand
752    /// shell variables, so those keep the real absolute path. Empty (default)
753    /// = automatic absolute-path resolution (#367).
754    #[serde(default)]
755    pub hook_binary: Option<String>,
756    /// Disable PathJail entirely by setting `path_jail = false` in config.toml.
757    /// Useful in container/Docker environments where the sandbox is the boundary.
758    /// (The former `LEAN_CTX_NO_JAIL=1` env override was removed in v3.7.3.)
759    #[serde(default)]
760    pub path_jail: Option<bool>,
761    /// What the enforced jail admits besides the project + allow-lists:
762    /// `projects` (default — every project below `$HOME` readable, writes only
763    /// in the session's project), `home` (all of `$HOME` read and write, except
764    /// dot entries, `~/Library`, `~/AppData`) or `project`. See `core::pathjail_scope`.
765    #[serde(default)]
766    pub path_jail_scope: Option<String>,
767    /// Sandbox level for code execution (ctx_exec).
768    /// 0 = subprocess only (current), 1 = OS-level restriction (Seatbelt/Landlock).
769    /// Override via LEAN_CTX_SANDBOX_LEVEL env var.
770    #[serde(default)]
771    pub sandbox_level: u8,
772    /// When true, large tool outputs (>4000 chars) are stored as references
773    /// and a short URI is returned instead of the full content.
774    /// Override via LEAN_CTX_REFERENCE_RESULTS env var.
775    #[serde(default)]
776    pub reference_results: bool,
777    /// Default per-agent token budget. 0 means unlimited.
778    /// Override per-agent via ctx_session or programmatically.
779    #[serde(default)]
780    pub agent_token_budget: usize,
781    /// Optional shell command allowlist. When non-empty, only commands whose base binary
782    /// is in this list are permitted by ctx_shell. Empty = disable allowlist (allow all).
783    /// Default includes common dev tools. Set to `[]` to disable.
784    /// Override via LEAN_CTX_SHELL_ALLOWLIST env var (comma-separated).
785    #[serde(default = "default_shell_allowlist")]
786    pub shell_allowlist: Vec<String>,
787
788    /// Extra commands MERGED on top of the effective `shell_allowlist` without replacing
789    /// the defaults. Setting `shell_allowlist` replaces the whole built-in list (a common
790    /// footgun); entries here are purely additive, which is what `lean-ctx allow <cmd>`
791    /// writes. Only applied in restricted mode (when the base allowlist is non-empty).
792    #[serde(default)]
793    pub shell_allowlist_extra: Vec<String>,
794
795    /// When true, block command substitution ($(), backticks) and process substitution
796    /// (<(), >()) in shell arguments. When false (default), only warn via tracing.
797    /// Default false preserves backward compatibility — set true for maximum security.
798    #[serde(default)]
799    pub shell_strict_mode: bool,
800
801    /// GH #1419 follow-up: scope a multi-word `shell_allowlist`/
802    /// `shell_allowlist_extra` entry to the exact subcommand/prefix it names
803    /// (`"git status"` no longer grants the whole `git` binary). Default
804    /// `true` (the secure grammar); set `false` to fall back to the old
805    /// base-binary-only matching as a compat/rollback path. Trust-gated: an
806    /// untrusted workspace cannot weaken this via a local override. Override
807    /// via `LEAN_CTX_SHELL_ALLOWLIST_SUBCOMMAND_SCOPING`.
808    #[serde(default = "serde_defaults::default_true")]
809    pub shell_allowlist_subcommand_scoping: bool,
810
811    /// Shell-security mode for ctx_shell / `lean-ctx -c` command gating (GL #788):
812    /// `enforce` (default, secure), `warn` (run checks, log violations, never
813    /// block) or `off` (skip the allowlist + dangerous-pattern blocks entirely —
814    /// a deliberate opt-out; compression stays active). Override via
815    /// LEAN_CTX_SHELL_SECURITY. `None` resolves to `enforce`.
816    #[serde(default)]
817    pub shell_security: Option<String>,
818
819    /// Default shell-command timeout in seconds for *normal* commands. `None`
820    /// resolves to the built-in 2-minute default; heavy builds/tests use
821    /// [`Config::shell_heavy_timeout_secs`]. Override via
822    /// `LEAN_CTX_SHELL_TIMEOUT_SECS` (`LEAN_CTX_SHELL_TIMEOUT_MS` still wins over
823    /// both, in milliseconds).
824    #[serde(default)]
825    pub shell_timeout_secs: Option<u64>,
826
827    /// Shell-command timeout in seconds for *heavy* commands (cargo build/test,
828    /// make, docker build, git commit/push, …). `None` resolves to the built-in
829    /// 10-minute ceiling. Override via `LEAN_CTX_SHELL_HEAVY_TIMEOUT_SECS`.
830    #[serde(default)]
831    pub shell_heavy_timeout_secs: Option<u64>,
832
833    /// Extra command prefixes that get the heavy timeout ceiling. Merged with
834    /// the built-in list. Useful for project-specific long-running scripts.
835    /// Example: `shell_heavy_prefixes = ["python3 ", "./scripts/"]`
836    #[serde(default)]
837    pub shell_heavy_prefixes: Vec<String>,
838    /// When true, `ctx_shell` accepts shell file-write redirects (`>`, `>>`,
839    /// `tee`, heredoc-to-file, `curl -o`, `wget` default mode). Default false —
840    /// the native Write/Edit tool is preferred. Opt-in for power users who want
841    /// classic shell syntax; the real command gating (allowlist,
842    /// dangerous-pattern and interpreter-eval blocks) still applies. Override
843    /// via `LEAN_CTX_SHELL_ALLOW_WRITES=1`.
844    #[serde(default)]
845    pub shell_allow_writes: bool,
846    /// Absolute paths where shell redirects and `tee` may capture output.
847    /// Empty uses the operating system's temporary directories. Project files
848    /// remain denied even when a configured path overlaps the project root.
849    #[serde(default)]
850    pub write_allow_paths: Vec<String>,
851
852    /// #814: opt-in to allow `python3 -c`, `node -e`, etc. in ctx_shell.
853    /// Default `false` — inline code is blocked because it leaves no auditable
854    /// artifact. Override via `LEAN_CTX_SHELL_ALLOW_INLINE_SCRIPTS=1`.
855    #[serde(default)]
856    pub shell_allow_inline_scripts: bool,
857
858    /// Setup behavior: controls what gets injected during setup and updates.
859    #[serde(default)]
860    pub setup: SetupConfig,
861    #[serde(default)]
862    pub solution: super::solution::SolutionConfig,
863    /// Claude Code governor rule modes (`[governor]`; env overrides win).
864    #[serde(default)]
865    pub governor: super::governor::GovernorConfig,
866    #[serde(default)]
867    pub provenance: super::provenance::ProvenanceConfig,
868    pub cross_agent: super::provenance::CrossAgentConfig,
869}
870
871/// `[llm]`: no effect. Its only consumers, query expansion and observation
872/// synthesis, are retired (DEPRECATIONS.toml `config-llm`); the section stays
873/// accepted so existing configs keep loading.
874#[derive(Debug, Clone, Serialize, Deserialize)]
875#[serde(default)]
876pub struct LlmConfig {
877    pub enabled: bool,
878    pub backend: LlmBackend,
879    pub model: String,
880    pub timeout_secs: u64,
881    pub base_url: Option<String>,
882}
883
884impl Default for LlmConfig {
885    fn default() -> Self {
886        Self {
887            enabled: false,
888            backend: LlmBackend::Ollama,
889            model: "qwen2.5-coder:1.5b".to_string(),
890            timeout_secs: 10,
891            base_url: None,
892        }
893    }
894}
895
896/// The `[llm] backend` values an existing config may still name.
897#[derive(Debug, Clone, Default, Serialize, Deserialize)]
898#[serde(rename_all = "lowercase")]
899pub enum LlmBackend {
900    #[default]
901    Ollama,
902    OpenRouter,
903    Anthropic,
904}