Retrofits all 39 skills to ADR-0020's description/body context contract, then fixes what six rounds of independent review found in that retrofit — including four ways the hot gate itself failed open. Closes #99, #107, #108, #110, #111, #114, #115, #120. ## The retrofit (waves 1-5) | | Start | Now | |---|---|---| | Description FAILs (>400 chars) | 26 | **0** | | Body FAILs (>900 words, body-only) | 9 | **0** | | Dangling routing targets | 2 | **0** | | `Kyberforge.CompositionNote` | 10 | **0** | | Preload tax | 21,005 chars | **~10,500** | Under the 12,000-char success criterion. Per-wave detail is on #99. ## The review fixes **The gate failed open four ways, three of them found after the retrofit shipped.** An unrecognised follower token made a dangling target vanish. A skill directory with no `SKILL.md` resolved as a valid target, so a commit could be green locally and red in a fresh clone — three existing fixtures were relying on that, one of which made the install-leak A/B pass vacuously. Then the free-standing `/name` sweep turned out to be gated on the sentence carrying a boundary marker, so route notation in any other sentence was invisible — not an ERROR, not a SUGGESTION, not an INFO — which left the documented "`/name` always blocks" promise false from a second direction. All four fixed and pinned. **Two checks were silently not running.** `validate-provenance.sh` checks 7-8 were dead across nine skills. Waking them exposed a deeper problem: they assume `Research doc:` names a source index, but 30 of 121 entries point at topic content documents, so every new check-7 INFO was a false positive and check 8 was saved from a false-FAIL flood only by an *unannounced* skip. Checks 7/8 are now scoped to source indexes and every skip announces itself (#121). **The retrofit's own anti-goal, four times.** ADR-0020 warns that a blunt gate gets satisfied by deleting content rather than relocating it. `diagnose` and `skill-audit` relocated prose and then read it unconditionally; `prototype` and `vale-config` deleted rules outright that survived nowhere. All four addressed. ## Verification - `bash tests/run-tests.sh --strict` — 24 suites, 0 skipped, 0 failed - `bash tests/run-bats.sh` — 325 tests, 0 failures - `pre-commit run --all-files` — 17/17 - `pre-commit run --hook-stage pre-push --all-files` — 16/16, with `apm marketplace check` and `apm pack --check-clean` run against the remote, not skipped - `scripts/skill-size-check.sh` over all 39 skills — rc 0, 0 ERROR/FAIL, SUGGESTION-only - Preload tax measured at **10,498 chars**, max description 390 — both inside budget - Every new test proven non-vacuous by a deliberate mutation of the behaviour it covers **Per-commit sync, stated accurately:** the ten commits from the latest review round each pass `check-plugin-content-sync` in isolation, verified by checking each out in a detached worktree with a clean between. The earlier gitea window (`dfacf05..bedbd1d`, nine commits) does **not** — its mirror was regenerated in one batch at `bbc7300`. An earlier revision of this description claimed the property held for every commit; it does not, and a bisect through that window lands on a red commit. **Squash-merge** to collapse it, or accept that this range is not bisectable. ## Version bump Six plugins and the catalog take a **patch**, not a minor. The branch is **89 commits — 40 `fix` / 30 `refactor` / 12 `docs` / 5 `chore` / 2 `test` — zero `feat`, zero `!`, zero `BREAKING CHANGE`** — and adds no skill, agent, command or hook. (Two earlier revisions of this section cited a stale histogram, most recently 78 commits; the figures above are measured at HEAD.) Both rules this repo ships (`forge/references/version-bump.md`, landing in this PR, and `git-commits/references/conventional-commits-spec.md`) make that a patch, and the catalog set is unchanged at 7 entries. Not settled by that: four published files were removed from the installed tree, three moved, and `caveman` gained `disable-model-invocation`, retiring its old triggers. Under a strict reading those are major-class and currently ship under `refactor:` with no marker. Whether the deployed skill surface is a public contract is written down nowhere — worth deciding, but it outlives this PR. ## Deliberately not in scope #112 (cherry-pick ownership, now resolved in favour of `git-commits`), #113 (`rtk git` normalisation), #116 (research fan-out), #101 (audit-skill merge), #122 (non-spec skill-root files), #123 (no PRD producer) stay open. #117 is the one worth reading: the contract's remedy is to move prose into `references/`, which is exactly where neither the size gate nor Vale looks — and the blind spot is wider than #117 currently records, since there is no root `.vale.ini` at all, so every ADR, `CONTEXT.md` and `README.md` is unlinted too. That blind spot let this branch carry two `level: error` `Kyberforge.SentenceOpenerThereIs` violations into `references/` files it created — `provider-adapter-author/references/provider-matrix.md:31` and `agent-audit/references/finding-criteria.md:95`. Both are reworded in `afadaae`, confirmed by routing each file through the audit's own `vale-wrap.sh` (1 error each before, 0 after). Five further occurrences sit in `references/` files already on `main`; those are the pre-existing corpus and stay with #117, which is the real fix. Also unfixed and not this PR's: `apm install` appends a duplicate `SessionStart` entry to `.claude/settings.json`, so a fresh clone cannot get pre-push green without an edit AGENTS.md warns against. Reproduces identically on `main`. Co-authored-by: Defame1297 <gitea@rkdr.net> Reviewed-on: https://git.dev.rkdr.net/Defame1297/holocron/pulls/129 Co-authored-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net> Co-committed-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net>
10 KiB
source_keys
| source_keys | ||
|---|---|---|
|
Body Discipline Reference
Upstream source: agentskills.io — skill-authoring, best-practices. House contract: ADR-0020, the context budget.
The core test
For every sentence in the body, ask: "Would the agent get this wrong without this instruction?"
If no — cut it. The agent already knows it from general training. Adding it wastes tokens and dilutes the signal of what matters.
What the body is for
The body carries the decision procedure only: ordered steps, decision branches, gates, and which reference to load when.
Include content the agent lacks:
- Project-specific conventions and domain procedures it cannot infer
- Non-obvious edge cases and environment-specific gotchas
- The specific tools or sequences to use — not the full range of options
- One default per decision point with one escape hatch
Move to references/, behind an explicit "If X, read references/<file>.md" trigger — the literal
conditional form, never a generic pointer. Write the real filename in the skill under audit; the
angle brackets are a placeholder here, and a literal references/file.md in a body is an ERROR
from the ADR-0020 gate because no such file exists on disk.
A dispatch table satisfies this requirement on its own. A table row already pairs a condition with a target, which is exactly what the literal form encodes; restating each row underneath as a prose conditional duplicates the routing in the one body whose whole purpose is to be short. Where a body dispatches, audit the table for condition/target completeness and stop there — do not require the conditional form as well. The literal form is what a body needs when it loads a reference without a dispatch table: a single mid-procedure deepening, an escape hatch, an error path.
Move:
- Lookup tables and spec restatements
- Output schemas, templates and example blocks
- Rationale and justification prose
- Anything only one branch of the procedure ever reaches
Do not include at all:
- Concepts the agent already knows (what JSON is, how HTTP works, what a CSV is)
- Exhaustive option lists — pick a default; the agent does not benefit from choosing
- Steps the agent handles independently — over-specifying leads to unproductive paths
- Restatements of the description, which is already in context
Two length families, measured differently
Do not conflate these, and do not report them as one finding.
| Gate | SUGGESTION | FAIL | Counts |
|---|---|---|---|
| Body budget (house, ADR-0020) | 600 words | 900 words | the body only — everything after the frontmatter's closing --- |
| Spec conformance (agentskills.io) | — | 2,770 words / 500 lines | the whole file, frontmatter included |
The 2,770-word ceiling is a token-conformance backstop calibrated to the densest prose in the
corpus; it says nothing about quality and a file can sit a thousand words inside it while failing
the body budget. The 900-word ceiling is the quality gate: a body is loaded into the caller's live
context and competes with the conversation already there. validate.sh reports both. Cite whichever
one actually fired.
A word count cannot detect the defect it stands in for. Treat both numbers as backstops to the dispatch rule and the Gotchas constraint below, never as a substitute for them.
Dispatch is mandatory at two or more mutually exclusive flows
If a skill handles two or more flows that a single invocation cannot both take — separate
subcommands, separate input types, separate lifecycle stages — the body carries a dispatch
table plus the gates common to every branch, and each flow lives in its own self-contained
references/ file. Inlining all of them is a FAIL regardless of word count, because every
invocation then pays for every branch it did not take.
The reference shape in this repo is apm-workflow: a 294-word body dispatching to 3,154 words
of references across five mutually exclusive flows. Its whole-file count is 348 words — cite 294
when calibrating a body, or the conflation this section warns against reappears in the finding
itself. The 3,154 counts the five flow files only; references/sources.md is a provenance record
and is never loaded at runtime, so counting it inflates the dispatched total.
What earns the wiring exemption
A dispatch table earns the exemption above on its properties, not on which skill it appears in. Audit any dispatching body against these four:
- Every flow the skill handles has a row, and every row names a target file that exists on disk.
- Each row pairs a condition the agent can evaluate from the request with exactly one target. A row keyed on a literal slash invocation fails this: a model-invoked activation never produces that string, so the routing silently falls to whatever else the row carries.
- One line after the table tells the agent to read the file its row matched, and only that one.
- The gates every branch needs sit in the body, not inside one flow's file — see the reachability precondition below.
A table missing any of the four is not exempt, and the literal-conditional requirement applies to it as written. The exemption covers the wiring form only: every other rule in this file applies to a dispatching skill exactly as it applies to any other.
Gotchas sections
The highest-value construct in a body, and the easiest to fill with noise. A Gotcha must state a fact that contradicts a reasonable default — something the agent gets wrong precisely by acting sensibly.
## Gotchas
- The `users` table uses soft deletes. Always include `WHERE deleted_at IS NULL`.
- User ID is `user_id` in the database, `uid` in auth, `accountId` in billing. Same value.
Constraints:
- More than five entries is a SUGGESTION — five is the guideline, not a ceiling. Past five, the
section is usually a summary of the body rather than a set of traps, and the agent stops reading
it as a warning. It stays advisory because whether a given gotcha earns its place is judgment;
validate.shemits it throughsuggest()and the run still exits 0. - A Gotcha that paraphrases a step in the body below it is a FAIL. It has no independent content, and it teaches the agent that Gotchas can be skimmed because the real instruction is coming. This one is the auditor's call — no script detects it. The Fix is conditional: delete the Gotcha only if the surviving copy is reachable from every branch that needs it — see the reachability precondition below.
- A Gotchas section exceeding 25% of the body is a SUGGESTION — the body has been inverted into a preamble. Same tier and same reasoning as the entry count, and independent of it: either can fire without the other.
- Place the section near the top. A gotcha read after the mistake is worthless, which is also why
Gotchas is the one construct exempt from moving to
references/.
Worked negative example — git-commits v0.1.2 at commit 5e23250, a fixed pre-retrofit
snapshot, not the current file. The live skill is v0.1.3 and matches none of the citations below;
they are quoted as they stood before the ADR-0020 retrofit, and are not to be refreshed against
HEAD. The snapshot is reachable only from a checkout of the authoring repo — an installed plugin
cache holds no git history and no such path — so read the citations below as quoted rather than
going to look for the file. From a checkout:
git show 5e23250:<the git plugin>/.apm/skills/git-commits/SKILL.md
That body carried twelve Gotchas, four of which restated content already below them or already in the description:
| Gotcha | Restates |
|---|---|
:31 "SemVer mapping is not optional" |
the description |
:32 "Confirmation gates are mandatory for destructive operations" |
step 9 at :52 |
:33 "Never skip hooks with --no-verify" |
step 9 at :52 |
:36 "Never commit secrets" |
step 2 at :45 |
All four are FAILs under the paraphrase rule. The entry count and the section's share of the body (387 of 1,102 words, 35%) are two further SUGGESTIONs on top — the script reports both, and neither fails the run on its own. What makes this worth auditing directly is that the four paraphrase FAILs pass every word gate there is; only reading the construct finds them.
The paraphrase rule has a reachability precondition
A Gotcha that restates a step may be deleted only when the surviving copy is reachable from every branch that needs it. In a dispatch body it usually is not: each flow file is loaded alone, so a step in one is invisible to an invocation that took another branch. When the restated rule is a safety gate more than one flow needs, the Fix is to move it into the body's common-gates section, never to drop it in favour of the per-flow copy.
Row four is the case that proves it. Following the rule literally, the retrofit deleted the
always-loaded secrets Gotcha and kept step 2 of references/create-commit.md — but git-commits
dispatches to exactly one flow file, and references/rewrite-history.md stages changes and runs
--amend, which commits newly staged content exactly as a fresh commit does. A grep for secret
across the skill in that state returned one hit, on a path two of three branches never reach: that
branch could commit a credential with no check anywhere in its loaded context, against this repo's
governance hard prohibition. v0.1.3 carries the rule as gate 2 of "Gates on every flow" instead.
So check reachability before writing the Fix. Rows one to three are unaffected — the description is loaded on every invocation, and confirmation is likewise a common gate rather than a per-flow step.
Calibrating control
Be prescriptive when operations are fragile, consistency matters, or a specific sequence must be followed:
Run exactly:
\`\`\`bash
python scripts/migrate.py --verify --backup
\`\`\`
Do not modify the command or add additional flags.
Give freedom when multiple approaches are valid. Explaining why outperforms rigid directives — agents make better decisions when they understand the purpose.
Defaults not menus
Never present a list of equivalent options — pick one and mention the alternative briefly:
# Too many options
Use pypdf, pdfplumber, PyMuPDF, or pdf2image...
# Default with escape hatch
Use pdfplumber for text extraction. For scanned PDFs requiring OCR, use pdf2image instead.
The FAIL and SUGGESTION criteria for this dimension live in references/finding-criteria.md,
which Step 3 loads on every run.