A Claude Code plugin that makes each session's context-window usage observable to any session or tool that needs it, so long-running workflows can route heavy work away from a degraded context before quality slips, instead of guessing. Five parts:
- Statusline shim (
scripts/statusline-shim.sh), the durable wiring target. Installed once to~/.claude/context-guard/bin/, it resolves whichever tee version is installed at run time, so a plugin update never requires re-wiring and an uninstall degrades to your statusline running alone. Pure Bash builtins: it adds no measurable time to a refresh. - Statusline tee (
scripts/statusline-tee.sh), a transparent wrapper around your statusline command. Each refresh it atomically writescaptured_at,session_id, and the session'scontext_windowobject (copied verbatim from the statusline stdin) to the per-session path~/.claude/context-guard/context/<session_id>.json, then passes your statusline through byte-for-byte. With no statusline configured it doubles as a minimal standalone statusline. - Zone resolver (
scripts/context-zone.sh).context-zone.sh <session_id>prints exactly one word:smart/acceptable/dumb/unknown. Two band shapes, combined conservatively (the worse computable zone wins): percentage bands overused_percentage(shipped defaults smart ≤ 50 < acceptable ≤ 75 < dumb) and window-class token bands over occupancy (total_input_tokens + total_output_tokens; shipped defaults 100k/160k on a 200k window, 200k/400k on a 1M window). Bands come from the machine-scope~/.claude/context-guard/zones.jsonwhen present and valid, else from the shipped defaults. Zones say where you are; consumers decide what to do. - Zone-crossing hooks (
hooks/), the first shipped consumer. Once per transition into a worse zone, a PostToolBatch/UserPromptSubmit hook reports the crossing (advisory; silent on unchanged, improving, orunknownzones), splitting the report by audience: the continuation menu (continue,/clear, handoff-then-/clear,/compact) renders to the operator onsystemMessage, because choosing among them is the human's call; the model's channel carries the zone determination plus the counter-steer that a zone word is a measurement and not a decay signal, and never an exit menu. An exit menu injected into model context manufactures the model's own initiative to stop, summarize, or hand off, which the instruction-audit catalog flags as check I23. A PostCompact hook writes an evidence-degraded marker next to the session's snapshot, and both zone consumers honor it: a compacted session's effective zone is dumb regardless of its post-compaction numbers. An optional blocking mode (zone_hook_modeuserConfig) adds a PreToolUse gate that denies new Write/Edit/NotebookEdit/Agent/Workflow calls on a fresh dumb-zone snapshot past a grace budget, fail-open onunknown, with handoff-path writes, reads, Bash, and Skill invocations never gated, so a durable handoff is always writable. All four hook rows, including the two advisoryzone-crossing-inject.shrows (PostToolBatch and UserPromptSubmit), carry a 60-second timeout. The 0.4.8 measurement sized that cap: on Windows 11 / Git Bash with Defender real-time protection enabled,zone-crossing-inject.shreached 22.0 s on a small payload. A timeout only caps a hook that has already stalled and saves nothing on a normal fire, so a lower cap would not speed up the prompt path; on that profile it would cancel the advisory on essentially every fire rather than only on a stuck one. The rows stay at 60 until a re-measurement on that profile shows headroom. A hook blocked on stdin is bounded separately bycg::read_payloadinhooks/payload.sh, which reads to EOF under a 5-secondread -tand which both rows use. - Reader contract (
reference/reader-contract.md), the authoritative consumer contract: the snapshot path pattern, file shape, the 10-minute staleness rule, fail-open capability detection, the zones.json shape, session-id discovery via${CLAUDE_SESSION_ID}, and the zone-is-not-a-compaction-indicator rule. Its companionreference/cloud-headless-capture.mdis the writer-side channel inventory: why the statusline is the only capture channel, which other channels were checked and rejected (with sources and dates), including the two that do carry live occupancy and still cannot supply a snapshot, and whyunknownin a session that runs no statusline, a cloud or headless session by default, is structural rather than a defect.
- Transparent by contract. No tee outcome (missing
jq, unwritable path, or a rename blocked by a concurrent reader) ever changes the wrapped statusline's output or exit code. Missingjqis surfaced as a visible one-line notice, never a silent skip. - Per-session, atomic snapshots. One file per session id (no cross-session last-writer-wins); readers never see torn JSON (temp file + rename, with a brief retry for the Windows rename-over-open-target case). Stale sibling files are pruned on write with a 14-day cutoff, far above the staleness window, so live-but-idle sessions always survive.
- Cheap on every render. A render whose context-window fields match the last write, made less
than 60 seconds earlier (
CG_TEE_NOCHANGE_FLOOR), writes nothing and starts no process; a render that writes starts one, themv. The prune runs at most once an hour. A payload the built-in reader cannot prove byte-for-byte goes tojq, as before. - Path containment.
session_idbecomes a filename, so the tee accepts only[A-Za-z0-9_-]and skips the snapshot for anything else, the wrapped statusline is unaffected. - Fail-open zone resolution. Absent, stale, or unparsable snapshots, null or out-of-range
used_percentage, nullcurrent_usage(early-session and post-/compactstatusline states), a non-ISOcaptured_at, a snapshot whose embeddedsession_iddiffers from the requested one, or missingjqall resolveunknown. Consumers take their conservative path on data they cannot trust, never a fabricated zone. The shipped bands are declared judgment defaults.zones.jsonis the tuning path. The reader contract owns the published exception list, including the native 1M window compacting before the window fills (about 967K tokens on the model-config page re-read 2026-09-28). The trigger itself is operator-tunable:autoCompactWindow,CLAUDE_CODE_AUTO_COMPACT_WINDOW,CLAUDE_AUTOCOMPACT_PCT_OVERRIDE, andautoCompactEnabled/DISABLE_AUTO_COMPACT. Bands belong below whatever it resolves to, normalized into the percentage shape, so the session reaches a boundary decision before the harness compacts for it. Note thatused_percentagealways measures against the model's full window, so a lowered auto-compact window no longer shows up in the percentage. The reader contract owns those surfaces, their verification dates, and the rationale. - Integrity boundary (stated honestly). The snapshot directory is owner-only where POSIX
modes work; on Windows ACL volumes the
chmodis a no-op and other local users could forge snapshots. Zones are routing hints. Consumers must never attach security or egress decisions to a zone word. See the reader contract's untrusted-data section.
zone-crossing-inject.sh runs on PostToolBatch, which fires once per tool batch, so every process
it starts is paid on the critical path of every batch. Measured 2026-09-02 on Windows 11 under Git
Bash: 12 trials per row, each preceded by a bash -c : spawn floor so the floor and the hook see
the same machine load, medians reported. Cost is given in spawn-equivalents (hook wall time divided
by that run's floor) because the absolute figure moves with load: the measured floor ranged 36 to
94 ms across the before rows and 46 to 56 ms across the after rows, against a 33 ms program
baseline. Process counts are of commands in command position in the hook process, so a shell
builtin such as command -v jq is correctly not counted.
Counts below are processes started by the hook itself. The zone resolver is one of them, and it starts its own; the whole-fire total is in the paragraph after the table.
| Path | Processes before | Processes after | Spawn-equivalents before | After |
|---|---|---|---|---|
| PostToolBatch, steady zone (the common case) | 11 | 2 | 18.9 | 10.8 |
| PostToolBatch, no snapshot yet | 6 | 2 | 8.6 | 4.8 |
| PostToolBatch, crossing into a worse zone | 11 | 2 | 16.5 | 11.8 |
| UserPromptSubmit, steady zone | 11 | 2 | 18.4 | 11.8 |
| PreToolUse gate, default advisory posture | 2 | 0 | 2.5 | 1.4 |
| PostCompact marker | 9 | 4 | 9.4 | 5.7 |
scripts/context-zone.sh, the band authority the hooks call once per resolve, was cut in the same
pass. It spent six processes: one jq for the snapshot, two date for the staleness arithmetic,
and three awk for the two band comparisons and the version gate. It now spends one, and a second
only when a zones.json override is present. The band resolution moved ahead of the snapshot pass
so the resolved bands are handed to that one jq as data, and the two zones.json passes became
one over the same file. Measured with old and new interleaved in a single loop against the same
floor, so machine load cannot skew the comparison:
| Resolver, realistic snapshot | Processes | Spawn-equivalents |
|---|---|---|
| Before | 6 | 9.5 |
| After | 1 | 2.3 |
Whole steady PostToolBatch fire, end to end: 15 processes before this pass (17 when the snapshot
carries the token fields), 3 after. Those three are one jq in the hook, the resolver's own
process, and one jq inside it.
The figures above count commands in command position, which counts jq and bash invocations.
That is not the number of processes the operating system creates, and the two came apart here.
Bash normally elides the extra fork inside $(...) and execs the command in the substitution's own
subshell, but only when that command carries no redirection of its own. A 2>/dev/null, a <<<,
or a pipeline written inside the substitution defeats the elision, so bash forks the subshell and
then forks again to run the command. A fork that never execs never reaches a command position, so
the invocation count sees one process where the kernel made two.
Re-measured under strace -f (counting clone/fork/vfork), the steady fire that this section
reported as 3 was creating 8 processes. Each of the three call sites carried its redirection
inside the substitution and so cost double, and the payload pass cost triple because it was fed by
a printf | jq pipeline. Moving every redirection onto an enclosing { ...; } group, and reading
the payload into a variable in-process rather than through a command substitution, brings the real
count to 3, the figure this section always claimed:
| Steady PostToolBatch fire | Process creations | Program launches (execve) |
|---|---|---|
| Before (#3520) | 8 | 4 |
| After | 3 | 4 |
The program launches are unchanged, which is the point: the same jq, bash and jq still run
over the same inputs, and only the fork overhead around them is gone. On the hosts in
#3508 a process creation
costs 180 to 2,841 ms (median 1,108 ms at 501 concurrent processes) against about 1% user CPU, so
removing five of eight is the whole of the available saving on that class of host. This hook draws
on the per-turn ceiling as well as the per-tool-call one, because it fires on UserPromptSubmit
too.
Which hooks this reaches: the zone-crossing hook on both of its routes (PostToolBatch and
UserPromptSubmit), and the PreToolUse zone gate, which calls the same resolver and so inherits
its share. Not the PostCompact marker: post-compact-mark.sh never calls the resolver and still
captures its payload through a command substitution, so its count is untouched by this pass.
The contract test asserts the process-creation count under strace as an exact figure, alongside
the older command-position budget, so a redirection moved back inside a substitution fails a test
rather than quietly doubling a call site. Where strace is unavailable that assertion skips and
the command-position budget still runs.
Three files outside the hook decide everything it does: the per-session snapshot, the optional
zones.json, and the compaction marker. When none is newer than the .seen mark the last
completed resolve left, and the two optional ones still exist or are still absent exactly as that
mark's own line records them, the fire cannot reach a different answer, and the hook exits through
builtins alone. The existence line is what an mtime comparison cannot supply: a removed file is
never newer than anything, so without it, deleting zones.json or the compaction marker read as
nothing having moved. A mark carrying no readable line never takes the skip. The envelope parse had
to become free for any of this to mean anything, so a payload
within hook::jq_fields' proof ceiling is parsed by the library's builtin JSON parser, and one
above it keeps the single here-string jq described below.
Measured as process creations under a Windows job object, which counts every descendant; 5 reps per
cell, identical across reps. The subject is the hooks.json row run through usr/bin/bash.exe -c,
whose own floor is 3: the -c shell, env, and the shell the script's shebang starts.
| Fire | Payload | Creations before | After |
|---|---|---|---|
| First, resolves | small envelope | 11 | 9 |
| Repeat, nothing moved | small envelope | 9 | 3 |
| Snapshot rewritten | small envelope | 9 | 7 |
| First, resolves | 150 KB batch | 11 | 11 |
| Repeat, nothing moved | 150 KB batch | 9 | 5 |
| Snapshot rewritten | 150 KB batch | 9 | 9 |
No cell is worse than before, which is what the size test on the envelope parse buys: the helper's
fallback reads through a process substitution and costs four creations on an oversize payload
against two for the here-string jq, so only the small arm goes through the helper. Wall clock on
this host is bimodal and is reported only for the row it dominates: the small repeat fire's median
fell from 1,448 ms to 237 ms.
The one failure mode is a snapshot written DURING a resolve. The mark is stamped after the resolve completes, so that write counts as seen and its crossing waits for the next statusline render; the window is the resolve, not an mtime tick. A missed crossing is therefore late, never lost, and the converse cannot happen: skipping only ever chooses silence, so no arrangement of timestamps can manufacture an injection the full path would not have made.
The 150 KB repeat row above paid a here-string jq before the unchanged-input skip could run, because the skip needs session_id. On a PostToolBatch envelope those ids are top-level strings and tool_calls is the nested value that makes the payload large (hooks reference: common fields include session_id and hook_event_name; PostToolBatch adds tool_calls). The hook now closes a leading scalar object at the comma before that first nested value and parses the two ids with the builtin parser. The skip then exits with no external command, the same budget as a small envelope. Ids that follow the nested value, or a header the scan cannot prove, still use the here-string jq. A rewrite whose zone inputs changed still resolves, so a crossing is not dropped. A rewrite that only refreshes captured_at, or that keeps used_percentage inside the same shipped band with every other field unchanged and no zones.json, reuses the last zone and starts no resolver. zone-crossing-inject.test.sh pins both arms by xtrace.
The saving is not free, and the charge is disk rather than CPU. Two of the five removed process
creations come from replacing printf '%s' "$INPUT" | jq with jq fed by <<<"$INPUT", and a
here-string is not a pipe. Bash 5.1+ delivers one through the pipe buffer only while it fits; at or
above 64KiB it writes the string to a temp file (/tmp/sh-thd.*) and hands jq that descriptor.
Measured on bash 5.2.21: a 60,000-byte here-string opens no file, a 65,536-byte one opens
/tmp/sh-thd.* twice (create, then read). The pipeline this replaced never touched disk at any
size.
The extracted fields are byte-identical either way, so this changes no output. But a
PostToolBatch payload carries every serialized tool result and routinely clears 64KiB, so a large
fire now performs a temp-file write and read it did not perform before. That lands on the platform
this work is for: the #3508 hosts run Defender real-time protection, which scans temp-file writes,
and the 0.4.8 measurement below already attributes 22.0 s on that platform to it. The trade taken
is one process creation saved on every fire against disk I/O on the fires that exceed the
buffer, on hosts where a process creation costs 180 to 2,841 ms. Handing the hook's stdin straight
to jq would avoid both, and is declined for a separate reason: it would give up payload.sh's
bounded read -t 5 drain loop, which caps a stalled pipe at five seconds instead of letting it
block to the harness timeout. That loop is builtins only and costs no process, so keeping it is not
what the here-string pays for.
What went: every dirname call, replaced by parameter expansion (three in the zone-crossing hook,
two in each of the others); a second jq, by reading both envelope fields in one pass;
tr -cd | head -c on each of the two state markers, by $(<file) plus parameter expansion; and
mkdir and rm calls that the steady path had already made unnecessary, behind existence guards.
date -u in the PostCompact marker became printf's %()T format under a TZ=UTC prefix, verified
to produce the same string on a host whose local zone is not UTC, so the trailing Z stays honest.
%()T arrived in bash 4.2, so on an older shell (stock macOS 3.2) that site and the resolver's
clock fall back to the exact date invocation they replaced; the counts above are for 4.2 and
later, where the fallback is never reached.
No decision, no emitted text and no state file changed: an eleven-scenario capture covering
both events, both crossing directions, hostile and absent session ids and an empty payload diffs
byte-identical on stdout, exit code and every state file, and the contract tests assert the
remaining process budget by trace so a regression fails a test rather than slowing a session.
The resolver's own equivalence was proven the same way, at more depth, because its rewrite moved
security-relevant gates. A differential harness runs the old and new resolver over 103 inputs and
compares stdout, stderr and exit code byte for byte: both band shapes at every boundary, the
window-class selection, the plausibility guard, the version gate either side of 2.1.132, the
combination rule, the staleness window either side, the whole calendar-invalid captured_at class,
all four trust gates, and every zones.json variant including both malformed-notice paths. Zero
differences, stable across three runs.
One subtlety is worth stating, because it would have been a silent widening. jq's
fromdateiso8601 is not date -u -d: it NORMALIZES a structurally well-formed but
calendar-invalid timestamp rather than refusing it, so February 30th would have become March 2nd
and second 60 would have rolled into the next minute. The strict ISO-8601 format test exists to
stop a lenient parser accepting a forged captured_at, and normalizing there would have undone it.
The parsed epoch is therefore formatted back and required to equal the input byte for byte, which
restores date's answer on every such value.
What stays, and why: one jq pass in the hook, because a PostToolBatch payload carries every
serialized tool result, so a regex for the envelope fields would be matching against tool output;
one jq in the resolver, carrying every gate and both comparisons; and the resolver's own process.
Running it rather than sourcing it is deliberate: it is a documented seam that zone-gate.sh and
its test suite invoke as an executable, and it signals through exit. Making it sourceable would
change a public interface to save one process, and that is the only structural cut left here.
/plugin marketplace add melodic-software/claude-code-plugins
/plugin install context-guard@<marketplace>The tee needs two operator steps, both one-time:
/context-guard:setup apply. Installs the statusline shim to~/.claude/context-guard/bin/statusline-shim.sh(and seeds/refresheszones.json). The shim is inert until step 2./context-guard:setup check. Verifies prerequisites and prints the exactsettings.jsonstatusline edit (wrapping your existing command, or standalone) for you to apply. The plugin never edits your settings itself.
You wire the shim, not the tee, and that wiring is permanent: ${CLAUDE_PLUGIN_ROOT} is
version-pinned and the old version directory is pruned ~14 days after an update, so a statusline
wired straight to <plugin-root>/scripts/statusline-tee.sh silently stops teeing on the next
version bump and then takes the whole statusline down when the path disappears. The shim resolves
the newest installed tee at run time, so plugin updates need no re-wiring, and it passes your
statusline through unchanged when no tee is installed (including after uninstall). check still
flags legacy version-pinned wiring if you have it.
The scripts run on Bash (Git Bash on native Windows, so install
Git for Windows; the statusline wiring
invokes bash explicitly) and need jq on PATH for the tee, the
zone resolver, and the standalone statusline. Every hook row runs through node hooks/exec-bash.mjs,
so the hooks need Node.js on PATH: Claude Code's native binary
neither ships nor uses Node (setup), and without it the
hooks do not launch and are not enforced. The statusline tee does not use Node.
/context-guard:setup check reports both prerequisites. The snapshot updates only while an interactive
session refreshes the statusline; context_window fields can be null early in a session and
right after /compact, per the
statusline reference. Readers own null handling.
Cost: the tee adds roughly 0.6–0.9 s per statusline refresh on Windows under Git Bash, where the
cost is process-spawn bound on jq and date, and correspondingly less on native POSIX shells. The statusline is not on
the input path, so this is display latency, not typing latency; refreshInterval in your settings
governs how often it runs.
Per docs/conventions/hook-budget/README.md,
the PostToolBatch and UserPromptSubmit rows are per-turn hooks and the PreToolUse row is
per-tool-call, all always-on. Measured on Windows 11 under Git Bash, twelve trials against an
interleaved bash -c : floor, old and new interleaved in one loop (2026-09-02):
| Event | Fires | Spawn-equivalents | What changed |
|---|---|---|---|
PostToolBatch, steady zone (zone-crossing-inject.sh) |
1 | 18.9 before, 10.8 after (0.7.34) | 11 processes to 2: one jq reading both payload fields, dirname and tr pipelines replaced by expansions, mkdir -p behind a -d guard |
UserPromptSubmit, steady zone (the same zone-crossing-inject.sh) |
1 | 18.4 before, 11.8 after (0.7.34) | same script, same cuts; measured separately because the payload differs |
PreToolUse Write/Edit, advisory mode (zone-gate.sh) |
1 | 2.5 before, 1.4 after (0.7.34) | no process spawned in the default posture |
PostCompact (post-compact-mark.sh) |
1 | 9.4 before, 5.7 after (0.7.34) | 9 processes to 4: date replaced by printf's clock with a date fallback; mkdir and rm behind existence guards |
Zone resolver (scripts/context-zone.sh, called by the rows above) |
per resolve | 9.5 before, 2.3 after (0.7.34) | six processes to one jq; a whole steady PostToolBatch fire is 3 processes, down from 15 |
0.7.45, advisory zone-gate.sh sources nothing. 2026-09-06, Linux CI host.
The default zone_hook_mode is advisory, so the gate is inert. It still parsed
hook-utils.sh (and payload.sh) to discover that. The MODE check now sits
above every source, the same shape as the kill-switch hoist. Spawn census
stays at 0 PATH-visible execs; bash -x sources of hook-utils.sh go 1 → 0.
Wall clock, n=20 after 2 warmup, host spawn_probe measurable (min 0.5 ms,
spread 1.78×): p50 4.8 → 1.4 ms, p95 5.0 → 1.5 ms. Blocking mode still sources
the library after the MODE check and is unchanged.
The spawn-equivalents above are command-position counts. Re-measured as process creations under
strace -f (0.7.49), the steady PostToolBatch and UserPromptSubmit fire was creating 8 processes
where those rows report 3; moving every redirection off the inside of a command substitution
brings it to 3 with the program launches unchanged. See "Counting invocations is not counting
processes" above for why the two counts differ and what it costs on a slow-spawn host.
The two advisory rows keep their 60-second timeout: the 0.4.8 measurement put this script at 22.0 s on Windows with Defender real-time protection, and a timeout caps a stalled hook without speeding a normal one.
Three userConfig options, all hook-scoped: context_guard_hooks_enabled (kill switch, default
true), zone_hook_mode (advisory default | blocking), and zone_gate_grace_calls (blocking
mode's grace budget, in-script default 20). The snapshot path and the 10-minute staleness rule are
deliberately not configurable: they are contract constants that cross-plugin consumers inline
from the reader contract; a per-user override would silently split
the writer from its readers. Band numbers are the one tunable, via
~/.claude/context-guard/zones.json (shape in the reader contract), which the operator's own
statusline display may read too, so display and consumers never drift. Disabling the tee is the
operator's edit (remove or unwrap the statusline command); disabling everything is
enabledPlugins / uninstall.
Generated from this plugin's .claude-plugin/plugin.json. Every option Claude Code
will prompt for when the plugin is enabled, with the environment variable each hook
reads it from.
| Option | Type | Default | Environment variable | Description |
|---|---|---|---|---|
context_guard_hooks_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_CONTEXT_GUARD_HOOKS_ENABLED |
Master switch for the zone-crossing injection, blocking gate, and PostCompact marker hooks |
zone_hook_mode |
string | "advisory" |
CLAUDE_PLUGIN_OPTION_ZONE_HOOK_MODE |
advisory (default) injects guidance only; blocking additionally denies new Write/Edit/NotebookEdit/Agent/Workflow calls on a fresh dumb-zone snapshot past the grace budget (fail-open on unknown; handoff-path writes, reads, Bash, and Skill stay allowed) |
zone_gate_grace_calls |
string | "20" |
CLAUDE_PLUGIN_OPTION_ZONE_GATE_GRACE_CALLS |
Blocking mode only: number of matched tool calls allowed after the session first resolves dumb before the gate denies (in-script default 20) |
Three supported routes, in the order most people want them:
-
Interactively. Claude Code prompts for declared options when you enable the plugin. To change them later:
/plugin configure context-guard@<marketplace>. -
Headless. Repeat
--configfor each option. Replace<marketplace>with the marketplace you installed this plugin from:claude plugin install context-guard@<marketplace> -s <scope> --config context_guard_hooks_enabled=<value>
The same command reconfigures a plugin that is already installed: it prints
already installedand still writes the value. The short-circuit message is about the install, not the config write. Do notclaude plugin uninstallto reconfigure: uninstalling drops this plugin's whole storedpluginConfigsentry, resetting every option in the table above to its default.-sdefaults touser, so pass the scopeclaude plugin listreports for this plugin. The verified-version record lives in the plugin-reconfiguration convention.The value is stored immediately; the session you are in does not change. Hooks are handed their
CLAUDE_PLUGIN_OPTION_*when the session starts, so start a fresh Claude Code session before expecting new behavior. A check run in the old session still reports the old value, and that is not a failed write. -
By hand, in settings. Add the value under
pluginConfigsin your user settings (~/.claude/settings.json):{ "pluginConfigs": { "context-guard@<marketplace>": { "options": { "context_guard_hooks_enabled": <value> } } } }Plugin option values are read from user,
--settings, and managed settings only, not from a project's.claude/settings.json. To vary behavior per repository, enable or disable the plugin in that project'senabledPluginsinstead of setting an option there.
Do not set the CLAUDE_PLUGIN_OPTION_* variables yourself. They are how Claude Code
hands a configured value to a hook process; the value comes from the routes above.
- User configuration: the
userConfigschema and theCLAUDE_PLUGIN_OPTION_<KEY>export - Plugin install options: the
--configflag's reference entry - Plugins and skills settings:
enabledPlugins,extraKnownMarketplaces,pluginConfigs - Settings files and who they affect: user vs project vs local precedence
- Manage installed plugins: enabling, disabling,
/plugin list
The plugin's own zone-crossing hooks are the first shipped consumer. Next: the plugin-quality
audit skill (zone-informed dispatch and evidence-flush decisions, conservative on unknown). Any
session or tool on the machine may read the same files under the same contract.
MIT (SPDX-License-Identifier: MIT).