This repo is the source of truth for the clp@yscope plugin payload (the clp command, the skills, the manifests). The compiled installer and clp-s binary live in a separate private repository.
A useful mental model: the command is the security boundary, skills are the UX. The subcommands (and bin/lib/clp-common.sh) enforce the flag allowlist, validate paths, and unset dangerous env vars. Skills are prompt instructions that tell the agent which subcommand to call with which arguments. Keep these two layers cleanly separated and changes stay reviewable.
.claude-plugin/marketplace.json
Claude Code marketplace manifest.
.agents/plugins/marketplace.json
Codex marketplace manifest.
plugins/clp/.claude-plugin/plugin.json
plugins/clp/.codex-plugin/plugin.json
Product-specific plugin manifests. version must match — the
release workflow runs `scripts/validate-codex-plugin.sh` which
enforces this.
plugins/clp/bin/clp
The only command. Reads its first argument as a subcommand, or as the
TARGET of the analysis router. A tiny entry point; no logic of its own.
plugins/clp/bin/lib/
commands.py — the subcommand table: every name, its blurb, and the file
that implements it. The source of truth for the command's surface.
One implementation per subcommand (search.sh, compress-session.sh,
schema_tree_cli.py, ...), the shared clp-common.sh, and the internal
modules the implementations import (kql_build.py, log_shapes.py,
structurize.py, ...), which are never invoked directly.
plugins/clp/bin/clp-s
The clp-s engine binary, placed here by the installer and gitignored.
Not a subcommand; see resolve_clp_s below.
plugins/clp/skills-claude/
Claude Code skills: compress, compress-folder, search, analyze-logs,
decompress, clpp-compress, clpp-search, dev,
plus references/ (shared snippets included by the skills).
plugins/clp/skills-codex/
Codex skills: compress, compress-folder, search, log-insights,
decompress, codex-trajectory.
scripts/validate-codex-plugin.sh
Validates the Codex manifest and SKILL.md frontmatter.
.github/workflows/release.yml
Builds marketplace.tar.gz on tag push.
The repo root is the marketplace root for both products. Claude Code reads .claude-plugin/marketplace.json; Codex reads .agents/plugins/marketplace.json. Both point at the same plugins/clp/ directory, but each product reads its own plugin manifest and its own skills-* directory.
The loop is the same shape for both products — install the local marketplace once, then edit the source and reload — but the reload commands differ.
From the repo root:
claude plugin validate .
claude plugin validate ./plugins/clp
scripts/validate-codex-plugin.sh ./plugins/clp
claude plugin marketplace add "$PWD" --scope user
claude plugin install clp@yscope --scope user
codex plugin marketplace add "$PWD"
codex plugin add clp@yscopeThe local marketplace points directly at ./plugins/clp, so file edits in this checkout are the source the agent will read — there is no copy step.
For a single one-off session against the local plugin without registering a marketplace, use claude --plugin-dir ./plugins/clp. This is also the fastest loop for tweaking SKILL.md files — the plugin root is the checkout itself, so every reload is immediate.
See LOCAL_TESTING.md for the smoke tests (list sessions → compress → search → decompress, dry-run then real) and the clp-s resolution rules.
Make your change in the relevant file (a subcommand's implementation in bin/lib/, a SKILL.md, a manifest, or bin/lib/clp-common.sh). Then drive the change with the agent itself:
"Update
plugins/clp/skills-claude/search/SKILL.mdto also pass--tge 2025-01-01."
The agent edits the file. You observe the diff. Run the subcommand or skill manually to confirm the behavior matches what you asked for.
The local marketplace source points at the checkout on disk, so any edit under bin/, a skill, or a manifest is picked up on reload — there is no copy step. There are three reload paths, in order of cost:
-
Just restart the session — fastest, what you'll do 90% of the time.
-
Update in place while keeping the session:
claude plugin update clp@yscope
This re-reads the marketplace source on disk. Useful when you don't want to lose in-flight context.
3. Interactive UI: /plugin → pick Update for clp@yscope. Same effect as the CLI form.
For edits under bin/ only, you can also run the subcommand directly from the checkout without reloading the skill at all:
./plugins/clp/bin/clp search /path/to/archive 'level:error'Codex does not have an in-session reload for installed plugins. To see your changes:
- Start a new Codex session (new thread). The local marketplace source is re-read on launch, so
bin/, manifest, and skill edits are all picked up. - If the change does not appear, bump the
versionfield inplugins/clp/.codex-plugin/plugin.jsonby hand and re-runcodex plugin add clp@yscopeto force a refresh.
- Claude skills reach the command as
${CLAUDE_PLUGIN_ROOT}/bin/clp <subcommand>(resolved at agent startup). Codex skills hard-code the marketplace install path (~/.codex/marketplaces/yscope/plugins/clp/bin/clp). When you add a new subcommand, both skill trees need the matching reference. - Claude has in-session reload (
claude plugin updateor/pluginUI); Codex currently needs a new session and (for manifest changes) aversionbump in the Codex manifest. - The
versionfield must match betweenplugins/clp/.claude-plugin/plugin.jsonandplugins/clp/.codex-plugin/plugin.json.scripts/validate-codex-plugin.shenforces this locally and the release workflow runs it as part of CI, so a tag with version drift will fail the release build.
After your edit, run the preflight commands from step 1 again. They are fast and they catch the most common regressions (broken manifest JSON, missing name/description frontmatter, version drift between the two manifests).
For changes under bin/, also run the smoke tests in LOCAL_TESTING.md. For semantic-search changes, hit the health-check path by running clp search with a query that does not use semantic("...") first, then one that does.
If your change was only made in one product's skills tree, port the equivalent change to the other. A change under bin/ is product-agnostic but the corresponding skill docs in both skills-claude/ and skills-codex/ should still be reviewed for wording drift.
Push your branch and open a PR against main. The release flow is described in the Releases section of the README — this repo follows Semantic Versioning for the clp@yscope plugin.
The table in bin/lib/commands.py gives each subcommand a name and a one-line blurb; it says nothing about its role. These five tiers do, and a subcommand belongs in exactly one of them — if it seems to belong in two, it is doing two jobs. Above all five sits the router, clp <TARGET>: it identifies the application that wrote the logs and names the next step, and every step it names is one of the subcommands below.
| Tier | What it does | Subcommands |
|---|---|---|
| Passthrough | restricted calls into the clp-s binary, adding validation and nothing else |
clp search, clp compress session, clp compress folder, clp decompress, clp list-sessions, clp detect, clp compress status |
| Derived reading | computes something the records do not state, from one archive | clp schema (fields, type drift, record families), clp session turns (turn boundaries and the time split) |
| Session model | reconstructs a whole session from its many files, as archives plus a catalog of how they connect | clp bundle, clp bundle-review |
| Measurement | computes every figure a report may quote, so no model does arithmetic | clp session facts, clp facts |
| Policy and orchestration | applies thresholds, or drives a pipeline | clp session score + scoring-scale.json, clp bootstrap, clp baseline-plan, clp focus, clp run, clp extract, clp report, clp kql, clp shape-cache, clp shape-cluster |
Three things that follow from the tiers, and are easy to get wrong:
A passthrough may not compute. If a change would have clp search derive, summarise or interpret anything, it belongs in a derived-reading subcommand that calls it. Keeping that boundary is why clp search can be trusted as the single query path.
Measurement may not judge. clp session facts publishes values and never scores them; the thresholds live in scoring-scale.json because what counts as acceptable is the customer's policy, not a property of the data. A penalty, a weighting or a rung in Python is a bug, not a shortcut.
clp facts and clp session facts are deliberately not merged. They look alike and they are not: one reads a query-plan's results, the other a bundle's catalog. What they share is a contract — the report writer may quote no figure absent from the facts file — and that contract is worth stating in both places rather than abstracting into a base neither fits.
A subcommand name says what it does and nothing about where its code came from: the table in bin/lib/commands.py is the whole of the public surface, so the name in it is the only name a user ever sees. Keep them verbs (compress, search, extract) or the thing produced (schema, facts, shape-cache), and keep them free of /, . and ~ — those three characters are how the router tells a mistyped subcommand from a target.
"shape" survives in clp shape-cache and clp shape-cluster because the subject genuinely is log shapes — the dictionary, the clustering, the classification cache, and the baseline method documented in references/log-shape-baseline.md.
Files on disk are not renamed along with the subcommands. clp extract still writes /tmp/clp-insights-query-plan.txt and clp session facts still writes /tmp/clp-session-facts.md, because a path is a contract with whatever reads it next — including a run that started before your change. Rename one only if you are changing every reader in the same commit.
- New flag on an existing subcommand — add the flag to that implementation's explicit allowlist in
bin/lib/(and to the skill'sallowed-toolsfor Claude skills), then describe it inplugins/clp/README.mdand the matchingSKILL.md. No manifest bump unless behavior changed. - New subcommand — add the implementation under
plugins/clp/bin/lib/, then add its name, blurb and filename toSUBCOMMANDSinbin/lib/commands.py; nothing else registers it, and a name that is not in that table is anunknown subcommanderror. A shell implementation sourcesbin/lib/clp-common.shand follows the restricted-passthrough pattern (no--passthrough, explicit allowlist, validated paths, dangerous env vars unset). Group related steps under one name (Group(...), ascompressandsessiondo) rather than adding another top-level name. Updateplugins/clp/README.md, reference it from the relevantSKILL.mdin both product skill trees, and bump theversionfield in bothplugin.jsonfiles. - New internal module — a file in
bin/lib/that no subcommand table entry points at is an internal module, imported by an implementation and never invoked directly (structurize.py,log_shapes.py,kql_build.py). It needs no table entry and no doc beyond its own docstring. - New skill — create a new directory under
plugins/clp/skills-claude/and/orplugins/clp/skills-codex/with aSKILL.mdthat has YAML frontmatter (name,description). For product-specific use-cases (e.g. a vLLM debugging skill), one product's tree is fine; for the common compress/search/decompress workflow, add to both. Call onlybin/clpand its documented subcommands; never call bareclp-s, and never call a file inbin/lib/directly. - Skill wording / behavior — edit the
SKILL.mddirectly. Skills are prompt instructions; the subcommands do the actual work. Keep theallowed-toolslist (Claude) and the command reference (Codex) in sync with the subcommand's actual allowlist. - Manifest or marketplace change — bump the
versionin bothplugin.jsonfiles; updateplugins/clp/README.mdto reflect the new surface. Marketplace manifests (.claude-plugin/marketplace.jsonand.agents/plugins/marketplace.json) rarely need edits — the plugin entries are stable. Add a new plugin entry there only when shipping a genuinely new plugin, not for a new skill in an existing plugin.
bin/lib/clp-common.sh is shared; each subcommand enforces a different slice of the policy. Knowing which check lives where makes error messages actionable instead of mysterious.
| Check | Implemented in | Notes |
|---|---|---|
clp-s resolution (CLP_S_BIN → bin/clp-s → .clp-core/bin/clp-s → PATH) |
lib/clp-common.sh::resolve_clp_s |
Every subcommand that touches the engine calls this. Set CLP_S_BIN to override. |
Flag allowlist (every flag explicit; -- rejected) |
Each implementation's argument loop in bin/lib/ |
Adding a flag means editing that file, not the skill. |
| Selected session is under the agent's source root | clp compress session |
source_real containment check; rejects --session-file outside ~/.claude/projects or ~/.codex/sessions. |
Output directory is not broad (not /, $HOME, ~/.claude, ~/.codex) |
lib/clp-common.sh::is_broad_output_dir (called by clp compress session and clp decompress) |
The most common "refused" error — choose an output under /tmp or a project-local dir. |
archives-root config (saved default + env override) |
clp compress session |
--show-archives-root, --set-archives-root, --save-archives-root. |
| HTTPS-only semantic endpoint + health check | lib/clp-common.sh::require_secure_url, check_semantic_endpoint (called by clp search only when semantic("...") is in the KQL) |
The endpoint URL is not passed in plaintext; clp search derives a …/health URL and curls it before running the query. |
| Output parent directory exists | clp decompress |
Unlike compress, decompress writes to the chosen path rather than creating a timestamped subdir under a saved root. |
Metadata file (<archive>/.yscope-clp-archive.json) |
clp compress session |
Written on success; consumed by print_archive_metadata_summary so other subcommands can print agent, session.file, sourceRoot. |
If a subcommand refuses a path, the error message names the check (e.g. "refusing broad output directory", "selected session file must be under source root"). Match the error against the row above to find which knob to adjust or which bug to file.
Two judgement calls come up often enough to call out:
When a change belongs in the subcommand vs the skill. If the constraint is a security or correctness property (no arbitrary paths, fixed flag set, env var scrubbing), it belongs in the implementation under bin/lib/, enforced by code. If the constraint is about how the agent should phrase a query, what defaults to use, or which skill to call from another skill, it belongs in SKILL.md. Resist putting "use this flag" instructions in a subcommand's usage string — it should fail closed, not lecture.
When a new skill is a new directory vs an addition to an existing skill. A new use-case (e.g. "vLLM debugging", "Sentry error triage") deserves its own top-level directory under skills-claude/<name>/SKILL.md and skills-codex/<name>/SKILL.md. A new option or a refinement of an existing flow belongs inside the relevant compress/, search/, decompress/, or *-trajectory/ skill. Don't grow the existing skills indefinitely — if the description is no longer accurate, split.
Edit-both-products rule. Almost every skill change should land in both skills-claude/<name>/SKILL.md and skills-codex/<name>/SKILL.md. There are only three reasons to edit just one:
- The change is genuinely product-specific (e.g. adding a Codex-only KQL starter to
codex-trajectory). - The change is to a command reference that already diverges between products (e.g.
${CLAUDE_PLUGIN_ROOT}vs the Codex hard-coded path). - The other product's tree doesn't have the skill yet (you're adding the Claude-side skill first because Codex can't see it).
If none of these apply, your PR is incomplete until both trees match.
A few things to check before opening a PR, in addition to the preflight commands above:
shellcheckis clean on every shell file inbin/lib/you touched,clp-common.shincluded.- The two
plugin.jsonversionfields match. - New
SKILL.mdfiles have YAML frontmatter with bothnameanddescription. - New flags appear in the implementation's allowlist in
bin/lib/, the correspondingSKILL.mdin both product trees, andplugins/clp/README.md. - A new subcommand is in
SUBCOMMANDSinbin/lib/commands.py, andplugins/clp/bin/clpwith no arguments prints it in the table with a blurb that fits on one line. - A change that exposes new behavior is reflected in that subcommand's
--help(or equivalent) text. - A change to
plugins/clp/scoring-scale.jsonpassesplugins/clp/bin/clp session facts --check-scale(the release workflow runs it too). Every axis needs abasisand arationale: a threshold nobody can justify is not a threshold. Moving a rung is a behavior change — bumpscale_version, since scores from before and after are not comparable.
See the Releases section of the README for the version-bump → tag → release workflow and how the private installer consumes the release.
The release CI runs claude plugin validate and scripts/validate-codex-plugin.sh, which catches manifest drift and broken SKILL.md frontmatter. The smoke tests in LOCAL_TESTING.md are not run by CI — run them locally before tagging, especially if the change touched a subcommand's allowlist or added a new flag.
- "
clprefused my path" — check the error against the What the commands actually validate table above. The most common fix is to choose an output under/tmpor a project-local dir. - "error: unknown subcommand" — the name is not in
SUBCOMMANDSinbin/lib/commands.py. Either it is a typo (clpprints the whole table when run with no arguments), or you added an implementation tobin/lib/without registering it. A first argument that holds a/, a.or a~, names something that exists, or is a session-id UUID is read as the router's TARGET instead, soclp ./searhcis a missing file rather than a bad subcommand. - "skill changes don't show up in the agent" — for Claude, run
claude plugin update clp@yscope; for Codex, start a new session. If the manifest changed, bump the Codexversionand re-add the plugin. - "manifest validates locally but
claude plugin validatecomplains about my new skill" — confirm the skill directory has aSKILL.mdwith YAML frontmatter (name,description) and that the path matchesskills:inplugin.json. - "shellcheck fails on my edit" — most failures are unquoted variable expansions or missing
[[ -n "$x" ]]guards. The existing shell implementations inbin/lib/are the reference style. --help— every subcommand has one (clp search --help),clp --helpis the router's, and bareclpprints the table of all of them. Read them before guessing flag names; the allowlist is the truth, theSKILL.mdmay lag.
By contributing, you agree that your contributions will be licensed under the project's Apache-2.0 license.