turn

Checks the skill repos compost was made from for upstream changes worth working in, and adds skill repos to watch as references.

npx skills

npx skills add JoshuaOliphant/compost --skill turn

Works for Codex, Cursor and other harnesses. Use -a <agent> to target one, e.g. -a codex.

Claude Code

/plugin marketplace add JoshuaOliphant/compost
/plugin install compost@compost

turn

Turning a compost pile mixes new material in. compost was made by breaking other people's skills down and rebuilding them; those sources keep improving. turn finds what changed upstream since compost last took from each source, decides what is worth working in, and files that as issues. It never edits compost's skills itself: an adopted change goes through compost:spec, or compost:build for a single small adopt, like any other work, so it gets built, verified, and reviewed.

pile.toml at the plugin root lists every source:

  • input: a source compost takes text from. pin is the upstream commit compost reflects, and feeds maps upstream paths to what they fed: a compost skill by name, canon for the essays, or a mechanism, hooks for compost's hooks as a whole or hooks/<mod> for one mod, so pile.py status reports an adopted mod's upstream drift as it does a skill's.
  • frozen: an input that will not change again (a retired plugin). Listed for attribution only.
  • reference: a repo watched for ideas and not drawn from yet.

${CLAUDE_PLUGIN_ROOT}/scripts/pile.py does the bookkeeping (Python 3.11+ via uv, stdlib only).

Kinds and rulings

Both procedures below sort each upstream item by kind before judging it: a skill (a SKILL.md and its references), a mod or hook (a hooks module, a hook entry, a hook script), an agent, or a script. Judge a skill, agent, or script by its text. Judge a mod or hook as a mechanism: name the event it fires on (a prompt submitted, a tool call, Stop, session start) and the compost rule it would enforce that a skill now only states. Each item gets one ruling:

  • adopt: better than compost's counterpart, fits as is (a sharper step, a fixed bug, a clearer example)
  • adapt: the idea is good, the form conflicts with compost's rulings (a new approval gate, a TDD ritual, beads, a size rule); take the idea, not the text
  • add: compost has no counterpart, and it serves a step in compost's workflow (spec, slice, build, verify, review, finish, pause, diagnose). Name the compost step it serves and what it makes cheaper or harder to skip. If no step fits, the ruling is ignore: it belongs in a standalone plugin, not in compost.
  • ignore: churn, tooling specific to the source, or already covered

Adopt and adapt are overlaps, judged against the compost skill or mechanism the item feeds; add is judged against the step it serves. Report the two apart.

Check for upstream changes

  1. In a checkout of the compost repo on its default branch, freshly pulled, run uv run plugin/scripts/pile.py status --references; the installed plugin's pile.toml lags until a release ships, so it would list changes an unreleased pin move already ruled on. For each input it keeps a blobless clone under ~/.cache/compost/upstream/, lists every file changed since the pin, and groups them by the compost skill or mechanism they feed, then lists the names of the changed files no feeds entry maps. --references adds each reference source, whose changed files all feed nothing; that is how a watched repo gets watched. It prints the git diff command for reading each change.
  2. Skim the unmapped file names for an item that did not exist when the pin was set: a skill, mod or hook, agent, or script. Note its kind. One that overlaps a compost skill or mechanism becomes a feeds entry now; a candidate add gets one once its issue lands, naming what it became. Its paths go into the sort below.
  3. Sort the changes before reading them. Ask Jev first: per source, write {"source": "<name>", "pile": "plugin/pile.toml", "paths": [<every path listed under a compost skill, plus promising unmapped skills>]} to a temp file (pile points it at the checkout's pins, the ones status read) and run uv run plugin/scripts/jev.py classify-change --input <file>. Record each change with needs_reading: false as ignored, with Jev's verdict as the reason, without reading it. Read the diff of every path in to_read and judge it yourself; Jev's verdict there is a hint, since it over-calls adopt and adapt on long diffs (Jev tools). Exit 3 means Jev is unavailable: read every listed diff. classify-change judges skill text against compost's skills and has no add verdict, so read every mod or hook and candidate add yourself. Give each change you read a ruling from kinds and rulings, against the canon. When dozens of changes remain to read, split them across parallel subagents, one per compost skill or mechanism, each returning a ruling with a one-line reason; then read the adopt, adapt, and add candidates yourself.
  4. Before filing, list the open issues labelled upstream and skip any change one already carries (match on the upstream diff link): a run that died between filing and moving the pin leaves them behind. File one issue per remaining adopt, adapt, or add on the compost repo (the repository in ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json), labeled upstream, with the upstream diff link, its kind, and what to take: for an overlap, the compost skill or mechanism it touches; for an add, the step it serves and what it makes cheaper or harder to skip.
  5. Once every change since a pin has a ruling, move the pin. Pins live in the source repo, not the installed plugin: in a worktree of that repo run uv run plugin/scripts/pile.py advance <source> <commit>, with <commit> the full hash status printed for that source (a ref would resolve after a fresh fetch and could move the pin past changes nobody ruled on, so advance refuses one), then uv run plugin/scripts/pile.py notice, and commit both with the ignored changes and their reasons in the commit message. That is the record that keeps them from resurfacing.
  6. Report per source, in two parts: overlaps, with how many changes were adopted, adapted, and ignored and the issue links; then additions, each add with its kind, the step it serves, and its issue link.

Add a reference

  1. Clone the repo and list what it ships by kind: skills, mods and hooks, agents, scripts. For the skills, when skill-atlas is installed, run skill-atlas --claude-dir <clone> judge then report to get each skill's category, craft, and the techniques it uses; otherwise read the SKILL.md files directly. Read each mod and hook for the event it fires on and what it does.
  2. Give each item a ruling from kinds and rulings and report overlaps and additions apart. For each overlap, name the compost skill or mechanism it overlaps and what it would bring that compost lacks: worked examples, a verification step, a decision procedure, a sharper trigger, a rule enforced instead of stated. For each add, name the step it serves and what it makes cheaper or harder to skip.
  3. Add a [[source]] with role = "reference", its license, and pin set to the commit you read. Later status --references runs list what it adds after that commit.
  4. A reference becomes an input the first time compost takes text or a mod or hook from it: set the role, add feeds (a skill name, hooks, or hooks/<mod>), and regenerate NOTICE.

Check compost's own text after a change

An adopted change rewrites a skill, and a description edit changes which skill Claude loads. After any change to a skill's text or description, run the plugin's two regression evals before the change merges (Jev tools):

  1. Routing. The 30 labeled prompts live in the private compost-evals repo, as $COMPOST_EVALS/route.json, where COMPOST_EVALS points at that checkout's evals/ directory. From plugin in the source repo, run COMPOST_EVALS=<checkout>/evals uv run --group dev pytest -m jev tests/test_evals_meta.py -k route -s. It routes every prompt through every skill's description and fails below 27 of 30. A new miss, or a prompt now marked close, points at the description that moved; fix the description, not the eval. Without a checkout the test skips rather than fails, so a skip is not a pass: say in the turn report that the routing eval did not run, and read the changed descriptions against the skills they compete with yourself.
  2. Rulings. Write {"paths": [<the changed markdown files, relative to the plugin root>]} to a temp file and run uv run ${CLAUDE_PLUGIN_ROOT}/scripts/jev.py rulings-lint --input <file> ({} lints the whole plugin). Read each flagged passage against the rulings it names: reword the ones that drifted, and leave the false flags with a line in the commit message.

Exit 3 from either means Jev is unavailable: re-read the changed text against the rulings yourself.

Run it on a schedule

A monthly run needs no one watching: it only reads upstream and files issues. Set it up with a scheduled routine or a cron-driven claude -p "/compost:turn" in the compost repo.

Next moves

  • compost:build when turn filed a single small adopt worth doing now.
  • compost:spec when filed issues are worth doing now: it turns them into a parent issue that compost:implement can work, and gives an adapted idea the design it needs.

← compost