Pre-flight — is this project set up?
Do this first, before anything else in this skill, and do it silently. One command answers it and carries its own rules; there is nothing else to read.
Run the checker with this skill's own frontmatter name: and
surface_hash:, and one --requires for each requires_config: entry:
PYTHONPATH=.apache-magpie-local python3 -m setup_preflight \
--skill <name> --hash <surface_hash> [--requires <file>]...
{"verdict": "ok"}→ silent. Continue into the work the user asked for and say nothing about pre-flight. This is the ordinary answer.{"verdict": "action", ...}→ each finding names a section, andrulescarries that section's text. Follow it. Thefactsare the inputs; what to propose, and what may not be done, are in the rules rather than here. Act on a finding only through its rules.- The command did not run at all — no such module, a non-zero exit, no
python3— → never read that as a pass, and do not re-derive the check by hand: it lives in code so that there is one version of it. If the project has no.apache-magpie.lock,.apache-magpie-local/or.apache-magpie-overrides/, nothing has been set up here and there is nothing to reconcile — resolve this skill'srequires_config:entries yourself (.apache-magpie-local/<file>first, then.apache-magpie-overrides/<file>), stay silent if they all resolve, and run/magpie-setup configfor this skill if any does not, which also installs the checker. Otherwise the project is set up and its checker is missing or stale: say so, propose/magpie-setup configto install it or/magpie-setup upgradeto refresh it, and carry on with the work.
Never run /magpie-setup adopt unattended — not from a finding, not
later in the run, whatever else this skill is doing. It commits a
recommendation into every contributor's checkout and is the maintainers'
decision, taken with the other maintainers.
Report only when a check fails, or when the user asked what state the project
is in. /magpie-setup verify is the full diagnostic.
Make an existing skill leaner without changing what it does.
There are two kinds of pass. The first six move, rewire or extract
without altering a word of the instructions — five in
patterns.md, plus extract-code below. The seventh,
rewrite.md, changes the words — the maintainer writes
them, paragraph by paragraph, and the skill learns their style as it
goes.
The validator is the gate: green before the first pass, green after the
last. To write a skill from scratch, use
write-skill instead.
This skill reads only framework files, so the external-content rules do not apply to it.
What counts as small enough
Two budgets, both measured rather than guessed. They were set at the catalogue median when this skill was written, so half the skills already met them; a skill past either one is an outlier, not merely large.
| target | why | |
|---|---|---|
SKILL.md body, pre-flight block excluded |
5,000 tokens | paid on every invocation of that skill |
description + when_to_use |
200 tokens | paid in every session, for every skill at once, invoked or not |
Measure both before Step 1 and again at Step 4:
uv run --project tools/skill-token-count skill-token-count --write
The always-on budget is the one to spend effort on first. A body only costs when its skill runs; the frontmatter costs whether or not anyone ever invokes it, multiplied by every skill in the catalogue. Cutting 200 tokens there beats cutting 2,000 from a body nobody triggers this week.
For reference when this was set: 75 skills, median body 4,614 tokens,
p90 10,613, largest 28,346; median always-on 200, largest 398.
PRINCIPLES.md P15's 500-line cap still applies as the structural
limit — these are the context budgets underneath it.
Report both numbers in Step 5 whether or not the pass moved them.
Inputs
Target — a skill name, a directory, or a SKILL.md path.
--all or over:<N> — diagnose every skill instead, ranking
candidates without touching anything. The default threshold is 500
lines, the PRINCIPLES.md P15 cap.
pass:<name> — restrict to named passes. Default is to propose
every applicable one.
With no target and no selector, diagnose everything read-only and let the maintainer choose.
Prerequisites
uv runs the validator, which is the gate — without it, stop and say
so. git isolates the diff, so prefer a clean tree or a branch.
doctoc regenerates a TOC when headings move; if it is missing,
surface the manual step rather than skipping it quietly.
Step 0 — Check the ground
The target must resolve to a real skill directory. The validator must be green before you start — optimization is layered on a working skill, not a way to fix a broken one, so hand back the failures and let the maintainer fix correctness first. The working tree should be clean enough that this diff is reviewable on its own.
Step 1 — Diagnose
Run every diagnostic in patterns.md and report one row
per smell: the pass that addresses it, the evidence (path:line, line
count, the construct), and how big a change it implies. Read-only.
The smells, in the order their passes apply:
- Oversized body — past the 500-line cap, or one section dominating. → split
- Concrete names — adopter-specific values baked in instead of
resolved from
<project-config>. → config-lift - Bulk reads — pulling a whole issue or artefact into context to touch one field. → out-of-context
- Per-item round-trips — N sequential fetches that could be one batch. → fetch-upfront
- No cheap pre-filter — spending a model pass on items a deterministic check would skip. → preflight-classifier
- Embedded code — shell or Python living inside the body, paid for on every invocation although a model never needs to read it. → extract-code
- Verbose prose — the structure is right and the body still reads
twice as long as it needs to. → rewrite, see
rewrite.md
For a sweep, rank by cap overflow times distinct smells and stop there.
Step 2 — Propose
Propose the applicable passes lowest-blast-radius first: a file move
before a content lift before a tool rewire, and the rewrite pass last
because it is the only one that changes wording. For each, state the
files touched, the expected delta, and the guarantee from
patterns.md.
Propose only. The maintainer picks which passes run, and in what order.
Step 3 — Apply one pass at a time
Restructure passes (split, config-lift) move text and change
none of it. Use git mv for a whole file; otherwise move the exact
bytes and leave a one-line pointer behind. Never paraphrase something
you moved — that is a behaviour change wearing a refactor's clothes.
Rewire passes (out-of-context, fetch-upfront,
preflight-classifier) change how a step runs, not what it decides.
They route through an existing deterministic tool such as
github-body-field or
github-rollup. If a
rewire would change what the skill proposes to a human, it is not a
rewire — stop and take it through normal review.
The extract-code pass takes code out of the body entirely. A script runs without entering the context, so embedded code is the one content that can be removed rather than merely relocated — the body keeps what the command is for and how to read its output, which is the part a model is actually for. Three destinations, and the choice is not stylistic:
scripts/beside the skill — a self-contained command with no dependencies. The default.- A project under
tools/— it needs dependencies, tests, or is worth running outside this skill. Followtools/AGENTS.md: a README declaring its capability and prerequisites, and a workspace entry. - The vetted-ops catalogue — the command is read-only, takes a
closed set of parameters, and would otherwise prompt for
confirmation on every run. Moving it out of the body does not help
if each invocation then stops for approval; a fixed operation in the
catalogue is covered by one allow rule and asks nothing. Adding one is
a reviewed change to
ops.pyand a caller's grant, never a runtime decision, so propose it and stop.
Check the prompting cost before choosing. A script the agent runs on every invocation, behind a prompt, has traded tokens for interruptions — which is worse, because a person pays for it rather than a budget.
Extracted code must come out byte-identical. It is executable: a paraphrase is not a rewording, it is a different program.
Most of what looks like code in this framework is not. A fenced
block full of <tracker>, <N>, <target> is a command shape the
agent fills in per run — an instruction written in shell, not a program.
There is nothing to extract, and moving it to a script would replace a
readable recipe with a file that cannot run.
The measurement, taken across the catalogue: of roughly 28,700 tokens
inside bash and python fences, 482 are multi-line and free of
placeholders, spread over four skills in blocks of 32 to 215 tokens. A
pointer line costs about what those blocks cost. So the honest answer
for nearly every skill is that this pass does not apply.
It applied to setup-isolated-setup-doctor because its six probes were
whole programs — self-contained, deterministic, printing a fixed line
the skill then interprets — and they were 2,971 tokens, 59% of that
skill's budget. That is the shape to look for: a complete program,
large enough to matter, that a model never needs to read. Two out of
three is not enough.
The rewrite pass is different and has its own file:
rewrite.md. The maintainer writes the words; the skill
carries paragraphs one at a time and applies what it has learned from
their earlier edits to the ones that follow.
A moved heading breaks things that point at it. Before calling a restructure pass done, follow every reference to the headings you moved:
- Eval
step-config.json— a suite builds its prompt live fromskill_mdplusstep_heading, so a heading that moved to a sibling leaves the suite extracting from a file that no longer contains it. Update both fields.grep -rl '<heading text>' tools/skill-evals/evals/finds them. - Anchor links —
other.md#the-headinganywhere in the tree.lycheecatches these, which is why it runs over the whole tree rather than the diff. - Heading levels. A block cut from mid-body starts at
###and cannot open a new file, so it shifts a level. That changes the anchor and thestep_headingstring an eval matches on — so it is the one byte a split is allowed to change, and every reference has to follow.
Neither the validator nor prek sees the first of these. Only running
the suite does, which is the argument for Step 4 running it at all.
After each pass, regenerate the TOC if headings moved and re-run the validator. One pass per commit.
Step 4 — Prove nothing broke
The validator must return the same green it returned at Step 0, and the budgets from What counts as small enough must have moved the right way.
Run the skill's eval suite if it has one, at
tools/skill-evals/evals/<skill>/:
tools/skill-evals/magpie-run-evals.sh tools/skill-evals/evals/<skill>
Run it before the first pass as well, and compare. A suite you only ran afterwards cannot tell a regression from a case that was already failing.
Some suites are not deterministic — the same unchanged tree grades differently between runs. When a case flips, say so plainly instead of treating either run as the verdict: name the case, say the suite varies, and let the maintainer decide. Claiming a rewrite is proven safe on a suite that cannot hold still is worse than admitting the gap.
A skill with no suite is not blocked, but say it has none — that is the maintainer's cue that the validator is the only gate on this change.
For a restructure pass, show the moved bytes are the same bytes: deletions in the body matching additions in the siblings, plus the new pointer. For a rewire, show the proposals a human signs off on are unchanged and only the cost moved. For a rewrite pass, the maintainer approved each paragraph as it went, so the record is the diff itself.
If the validator goes red, or you cannot show the behaviour held, revert the pass. Never ship half of one.
Step 5 — Hand back
Per pass: files touched, the delta, validator result, evidence. Do not
commit or open a PR unless asked. After a rewrite pass, also propose
the learned style rules per rewrite.md.
If it was a sweep, restate what is still on the list.
Hard rules
- Structure changes, behaviour does not. Except in the rewrite pass, where wording changes and the maintainer writes every word.
- Moved bytes are identical bytes, heading level excepted. A paraphrase during a move is a behaviour change in disguise.
- A heading that moves takes its references with it — eval
step-config.json, anchor links, anything matching on the string. - Propose before applying, every pass, never a batch.
- The validator is the gate, green before and after. A pass that needs it relaxed is not an optimization.
- Learned style rules are a proposal too. Show the diff; never write them silently.
- Measure, before and after, every pass. Both budgets and the eval suite. A pass reported without numbers is an opinion.
- Never touch the snapshot. Framework changes go via PR to
apache/magpie.
References
patterns.md— the behaviour-preserving passes.rewrite.md— the paragraph-by-paragraph rewrite and the style-learning loop.write-skill— authoring a new skill.tools/skill-and-tool-validator— the gate.
Learned style rules
Written by the rewrite pass at the end of a session, as a proposed
diff. Bullets only — a heading here would move this skill's
surface_hash and tell every adopter their configuration went stale
over a wording preference.