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.pinis the upstream commit compost reflects, andfeedsmaps upstream paths to what they fed: a compost skill by name,canonfor the essays, or a mechanism,hooksfor compost's hooks as a whole orhooks/<mod>for one mod, sopile.py statusreports 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
- 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'spile.tomllags 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 nofeedsentry maps.--referencesadds each reference source, whose changed files all feed nothing; that is how a watched repo gets watched. It prints thegit diffcommand for reading each change. - 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
feedsentry now; a candidate add gets one once its issue lands, naming what it became. Its paths go into the sort below. - 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 (pilepoints it at the checkout's pins, the onesstatusread) and runuv run plugin/scripts/jev.py classify-change --input <file>. Record each change withneeds_reading: falseas ignored, with Jev's verdict as the reason, without reading it. Read the diff of every path into_readand 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-changejudges 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. - Before filing, list the open issues labelled
upstreamand 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 (therepositoryin${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json), labeledupstream, 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. - 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 hashstatusprinted for that source (a ref would resolve after a fresh fetch and could move the pin past changes nobody ruled on, soadvancerefuses one), thenuv 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. - 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
- Clone the repo and list what it ships by kind: skills, mods and hooks, agents, scripts. For the
skills, when
skill-atlasis installed, runskill-atlas --claude-dir <clone> judgethenreportto 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. - 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.
- Add a
[[source]]withrole = "reference", its license, andpinset to the commit you read. Laterstatus --referencesruns list what it adds after that commit. - A reference becomes an
inputthe first time compost takes text or a mod or hook from it: set the role, addfeeds(a skill name,hooks, orhooks/<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):
- Routing. The 30 labeled prompts live in the private compost-evals repo, as
$COMPOST_EVALS/route.json, whereCOMPOST_EVALSpoints at that checkout'sevals/directory. Frompluginin the source repo, runCOMPOST_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 markedclose, 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. - Rulings. Write
{"paths": [<the changed markdown files, relative to the plugin root>]}to a temp file and runuv 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:buildwhenturnfiled a single small adopt worth doing now.compost:specwhen filed issues are worth doing now: it turns them into a parent issue thatcompost:implementcan work, and gives an adapted idea the design it needs.