# ctx-tools A Claude Code **marketplace**: one repository, plugins under `plugins/`, each carrying its own skills, hooks and commands. Currently one plugin, `ctxguard`. This is the maintainer's document: handing the plugin to someone else, and releases. The usage guide — installation, daily use, the CLI, what to do when a tool call is denied — is the main [README.md](README.md), in Russian. ## Install ``` /plugin marketplace add /path/to/skills /plugin install ctxguard@ctx-tools ``` ## Handing this to someone else The plugin travels through the marketplace. The dictionary does not, and must not. ### 1. The code — the short way Copy the plugin folder into `~/.claude/skills/`: ``` cp -r plugins/ctxguard ~/.claude/skills/ctxguard ``` That is the whole step. It loads next session as `ctxguard@skills-dir` with **hooks included** — verified by a live run: `claude plugin list` shows it loaded, `${CLAUDE_PLUGIN_ROOT}` resolves, and enforcement fires. No marketplace, no install command, no restart beyond starting a new session. Two things to know: - **The name must be free.** An installed plugin of the same name wins, and *disabling* it is not enough — the name stays reserved until `claude plugin uninstall`. The listing says so explicitly when it happens. - Copy the directory, not a symlink into a repository you then move. Use this for one machine or one colleague. Use the marketplace below when several people need the same version and you want updates to be a command rather than a recopy. ### 2. The code — the maintainable way Push this repository somewhere the other person can reach, then they run: ``` /plugin marketplace add / # GitHub shorthand /plugin install ctxguard@ctx-tools ``` `claude plugin marketplace add` also takes a git URL or a filesystem path, and `--sparse .claude-plugin plugins` limits the checkout inside a monorepo. Nothing is fetched at runtime and there are no dependencies beyond `python3`. For a whole team, declare it at **project** scope and commit the result, so cloning the repo is enough: ``` claude plugin marketplace add / --scope project claude plugin install ctxguard@ctx-tools --scope project ``` ### Releases, and the trap in them Because `plugin.json` carries a semver version, `claude plugin update` compares **versions, not commits** — a release that is not version-bumped never reaches an installed copy, silently. So every release is: ``` # bump version in plugins/ctxguard/.claude-plugin/plugin.json AND the marketplace entry git commit && git push claude plugin tag plugins/ctxguard # validates the two agree, tags ctxguard--v1.0.1 git push origin refs/tags/ctxguard--v1.0.1 ``` The recipient then runs `claude plugin update ctxguard@ctx-tools` and restarts. Iterating locally is the same trap in miniature: hooks execute the **installed copy**, not the working tree, so `claude plugin marketplace update ctx-tools` followed by `claude plugin update ctxguard@ctx-tools` is required after every change — and a change without a version bump will not be picked up. Confirm with `diff -rq ~/.claude/plugins/cache/ctx-tools/ctxguard//scripts plugins/ctxguard/scripts`. ### 3. The dictionary — copied, never re-derived `~/.claude/ctx-guard/` is per-machine and deliberately outside every repository. It must be handed over explicitly, because **aliases cannot be reproduced**: - they are assigned in registration order, so whoever adds two companies in the other order gets the mapping swapped — two people would mean *different companies* by `CTXG_COMPANY_A`; - the salt is generated per store, so secret markers and PII aliases do not match either, and findings cannot be correlated across a team. There is a test asserting exactly this, so the guidance cannot quietly rot. Copy it: ``` ctxguard entity export --out ~/team-dict.json # refuses to write inside a git repo # transfer over a channel you would send the real values over -- it contains them ctxguard entity import team-dict.json # run from the project directory ``` After importing, both machines produce byte-identical output, markers and PII aliases included. ### 4. What the recipient still has to do ``` ctxguard init # create state, show what is active ctxguard scan . # what this repository actually holds ctxguard verify # 43 canary cases through the real hook entry points ctxguard scan-transcript # what has actually reached the model ``` Until entities are registered or imported, only credential and PII detection is live — company names, people and hostnames are not protected. And sanitize `CLAUDE.md` by hand: the harness loads it directly, so no hook ever sees it. ## Layout ``` .claude-plugin/marketplace.json marketplace manifest plugins/ctxguard/ context sanitization: skill + hooks + CLI docs/specs/ design document (historical) Makefile the single entry point for checks ``` ### Where does a new skill go? | The skill needs… | Put it in | |---|---| | hooks, slash commands, MCP servers, scripts | its own plugin under `plugins//`, plus a `marketplace.json` entry | | nothing but instructions | a bucket plugin: `plugins//skills//SKILL.md` | Hooks cannot ship inside a bare skill, which is why this repository is a marketplace rather than a flat directory of `SKILL.md` files. Instruction-only skills belong in one bucket plugin — a single install covers all of them, whereas plugins that install hooks are added deliberately, one at a time. There is no such bucket here yet; create it along with the first skill that needs it, not in advance. Conventions, following the installed plugin ecosystem: - `SKILL.md` frontmatter is `name` + `description`, third person, trigger-heavy - details go into `references/*.md`, not into `SKILL.md` - scripts are Python 3 **stdlib only**, invoked as `python3 "${CLAUDE_PLUGIN_ROOT}/..."` ## Plugins ### ctxguard — context sanitization Keeps credentials, client and company names, personal names and PII out of the model's context. Credentials are removed irreversibly; names and PII become stable aliases that are translated back on their way to disk, so the agent stays able to do real work. Enforcement is in hooks, not in the skill — the agent cannot switch it off, and its forgetting the rules is harmless. ``` make test # engine unit tests make verify # canary corpus through the real hook entry points make leaks # scan transcripts for values that actually reached the model ``` Start here: `plugins/ctxguard/skills/context-sanitization/SKILL.md`, then `references/threat-model.md` for what it deliberately does not cover. Optional, so the expectation survives the plugin being disabled — add to your `CLAUDE.md`: ```markdown Sensitive data in this project is aliased (`CTXG_*`) and enforced by ctxguard hooks. Use aliases verbatim, never reconstruct real values, and read a denial's reason instead of working around it. See the context-sanitization skill. ```