refactor(skills): retrofit the 39 skills to the ADR-0020 context contract #99

Closed
opened 2026-08-14 19:39:27 +00:00 by Claude · 14 comments
Collaborator

ADR-0020 sets a blocking description/body contract for skills and agents. The gates ship hot with no baseline file, so any edit to a non-compliant skill is blocked at commit until that skill is retrofitted. This issue tracks doing it deliberately rather than lazily.

Figures below are measured at the tip of refactor/trim-skills-agents-context. They supersede the original body, which counted bodies whole-file where the gate counts body-only — the exact conflation ADR-0020 exists to stop. See the comments for what changed.

Non-compliant today

  • 26 of 39 descriptions exceed the 400-char FAIL. Worst: gitea-workflow 1012, agentsmd-author 960, agentsmd-audit 944, gitea-labels-milestones 835, provider-adapter-author 833, gitea-issues 827, apm-workflow 817.
  • 9 of 39 bodies exceed the 900-word FAIL, counted body-only (everything after the frontmatter's closing ---): git-remotes 1217, diagnose 1126, git-branches 1124, git-commits 1102, forge 1093, git-history 1044, git-submodules 1011, gitea-files 922, gitea-issues 902.
  • Preload tax: 21,005 chars (~5,250 tokens) across the 39 skills, down from 23,427 (~5,900) at the branch base.

Eight skills fail both gates and need a description cut and a dispatch split in one commit — partial improvement does not clear the gate.

kyberforge is itself 3 of 7 non-compliant

The plugin that owns the contract retrofitted only its four author/audit skills. These remain:

Skill Fails Note
forge desc 648, body 1093 The entry-point router. skill-author hands clean-context recheck to it.
apm-workflow desc 817 Cited as the dispatch-pattern exemplar by skill-author/references/contract.md while failing the gate.
apm-install desc 514 Description-only.

Retrofit apm-workflow and forge early — one is the model other retrofits imitate, the other is the router.

Per-skill work

  1. Rewrite the description to trigger + one capability + boundary. Target 250 chars.
  2. Compress boundaries to Not <thing> → <skill-name>; move composition notes to README.md.
  3. Delete trigger restatement (the same trigger in two registers) — gitea-files and gitea-issues are the worst.
  4. Move lookup tables, spec restatements, output schemas, templates and rationale to references/ behind "read X when Y".
  5. Apply the dispatch pattern where a skill has 2+ mutually exclusive flows.
  6. Cut Gotchas to ≤5 entries, each contradicting a reasonable default, none paraphrasing a body step.

Follow skill-author/references/retrofit.md — it carries the ordered cut procedure, the definition of "mutually exclusive flows", reference-file conventions, a mandatory collateral checklist, and an inline before/after worked example. It exists because four dry-run retrofits each invented six to ten different answers without it.

Also decide per skill whether it is hand-invoked (disable-model-invocation: true) — candidates: grill-me, grill-with-docs, caveman, prototype.

Traps

  • Validate in place. A skill audited outside its package prints INFO boundary-target resolution DID NOT RUN and exits 0. That reads as a pass and is not one.
  • A second gate is also hot. Kyberforge.CompositionNote fires 10 times across gitea-issues, gitea-labels-milestones, gitea-prs and gitea-workflow. pre-commit run --all-files is red on prose as well as size.
  • New SUGGESTION-tier checks (non-blocking, but they will appear): missing boundary clause, ## Gotchas over five entries, ## Gotchas over 25% of body words.
  • gitea-workflow needs a content decision, not just a cut. Its boundary clause routes to git-workflow in the git plugin. That resolves in this monorepo but not in a gitea-only install, and apm cannot express the dependency at plugin granularity without committing a ~192-file deployed tree. Agreed resolution: keep the exclusion, drop the explicit cross-plugin route. Fold it into that skill's retrofit.
  • Batch by plugin rather than fixing skills one at a time.

Success

Preload tax down from 21,005 chars (~5,250 tokens) to ~12,000 (~3,000), and pre-commit run --all-files green on both skill-size-check and vale-audit-prefilter-skill.

Starting command: pre-commit run skill-size-check --all-files reports every violation with its measured value.


Reference: docs/adr/0020-skill-description-and-body-context-contract.md. Related: #100 (dangling routing targets), #101 (merge skill-audit + agent-audit).

ADR-0020 sets a blocking description/body contract for skills and agents. The gates ship hot with no baseline file, so **any edit to a non-compliant skill is blocked at commit until that skill is retrofitted**. This issue tracks doing it deliberately rather than lazily. > **Figures below are measured at the tip of `refactor/trim-skills-agents-context`.** They supersede the original body, which counted bodies whole-file where the gate counts body-only — the exact conflation ADR-0020 exists to stop. See the comments for what changed. ## Non-compliant today - **26 of 39 descriptions** exceed the 400-char FAIL. Worst: `gitea-workflow` 1012, `agentsmd-author` 960, `agentsmd-audit` 944, `gitea-labels-milestones` 835, `provider-adapter-author` 833, `gitea-issues` 827, `apm-workflow` 817. - **9 of 39 bodies** exceed the 900-word FAIL, counted **body-only** (everything after the frontmatter's closing `---`): `git-remotes` 1217, `diagnose` 1126, `git-branches` 1124, `git-commits` 1102, `forge` 1093, `git-history` 1044, `git-submodules` 1011, `gitea-files` 922, `gitea-issues` 902. - **Preload tax: 21,005 chars (~5,250 tokens)** across the 39 skills, down from 23,427 (~5,900) at the branch base. Eight skills fail **both** gates and need a description cut *and* a dispatch split in one commit — partial improvement does not clear the gate. ## kyberforge is itself 3 of 7 non-compliant The plugin that owns the contract retrofitted only its four author/audit skills. These remain: | Skill | Fails | Note | |---|---|---| | `forge` | desc 648, body 1093 | The entry-point router. `skill-author` hands clean-context recheck to it. | | `apm-workflow` | desc 817 | **Cited as the dispatch-pattern exemplar** by `skill-author/references/contract.md` while failing the gate. | | `apm-install` | desc 514 | Description-only. | Retrofit `apm-workflow` and `forge` early — one is the model other retrofits imitate, the other is the router. ## Per-skill work 1. Rewrite the description to trigger + one capability + boundary. Target 250 chars. 2. Compress boundaries to `Not <thing> → <skill-name>`; move composition notes to `README.md`. 3. Delete trigger restatement (the same trigger in two registers) — `gitea-files` and `gitea-issues` are the worst. 4. Move lookup tables, spec restatements, output schemas, templates and rationale to `references/` behind "read X when Y". 5. Apply the dispatch pattern where a skill has 2+ mutually exclusive flows. 6. Cut Gotchas to ≤5 entries, each contradicting a reasonable default, none paraphrasing a body step. **Follow `skill-author/references/retrofit.md`** — it carries the ordered cut procedure, the definition of "mutually exclusive flows", reference-file conventions, a mandatory collateral checklist, and an inline before/after worked example. It exists because four dry-run retrofits each invented six to ten different answers without it. Also decide per skill whether it is hand-invoked (`disable-model-invocation: true`) — candidates: `grill-me`, `grill-with-docs`, `caveman`, `prototype`. ## Traps - **Validate in place.** A skill audited outside its package prints `INFO boundary-target resolution DID NOT RUN` and exits 0. That reads as a pass and is not one. - **A second gate is also hot.** `Kyberforge.CompositionNote` fires 10 times across `gitea-issues`, `gitea-labels-milestones`, `gitea-prs` and `gitea-workflow`. `pre-commit run --all-files` is red on prose as well as size. - **New SUGGESTION-tier checks** (non-blocking, but they will appear): missing boundary clause, `## Gotchas` over five entries, `## Gotchas` over 25% of body words. - **`gitea-workflow` needs a content decision, not just a cut.** Its boundary clause routes to `git-workflow` in the `git` plugin. That resolves in this monorepo but not in a gitea-only install, and apm cannot express the dependency at plugin granularity without committing a ~192-file deployed tree. Agreed resolution: keep the exclusion, drop the explicit cross-plugin route. Fold it into that skill's retrofit. - Batch by plugin rather than fixing skills one at a time. ## Success Preload tax down from 21,005 chars (~5,250 tokens) to ~12,000 (~3,000), and `pre-commit run --all-files` green on both `skill-size-check` and `vale-audit-prefilter-skill`. **Starting command**: `pre-commit run skill-size-check --all-files` reports every violation with its measured value. --- Reference: `docs/adr/0020-skill-description-and-body-context-contract.md`. Related: #100 (dangling routing targets), #101 (merge `skill-audit` + `agent-audit`).
Claude added this to the Skills & Agents milestone 2026-08-14 19:39:27 +00:00
Claude added the Kind/Enhancement
Priority
High
2
labels 2026-08-14 19:39:27 +00:00
Author
Collaborator

The ADR-0020 gates and the kyberforge-side retrofit have landed on refactor/trim-skills-agents-context, which changes the numbers in this issue's body.

Corrected scope. The body says 30 descriptions and 14 bodies are non-compliant. Measured against the branch as it stands, it is 26 descriptions over the 400-char FAIL and 9 bodies over the 900-word FAIL. Two reasons for the difference: the original body count was taken whole-file rather than body-only (the gate measures body-only, so the correct pre-change figure was 13, not 14), and the four kyberforge author/audit skills have since been retrofitted in the same change.

Already done, so out of this issue's scope:

Skill Description Body words
skill-author 614 → 225 2,623 → 599
agent-author 903 → 224 2,582 → 616
agent-audit 863 → 250 1,752 → 691
skill-audit 984 → 239 1,349 → 581

Remaining preload tax: 21,190 chars (~5,600 tokens), down from 23,612. The bulk of the remaining reduction is in the gitea-* and git-* families and in core.

Useful starting command: pre-commit run skill-size-check --all-files reports every current violation with its measured value.

Reference implementations to copy: skill-author and skill-audit are now the house examples of the dispatch pattern — body carries the dispatch table plus common gates, each branch self-contained in references/. apm-workflow remains the original exemplar.

Known trap: the gates are hot with no baseline file, so any edit to a non-compliant skill requires retrofitting it first. Batch by plugin rather than fixing skills one at a time.

The ADR-0020 gates and the kyberforge-side retrofit have landed on `refactor/trim-skills-agents-context`, which changes the numbers in this issue's body. **Corrected scope.** The body says 30 descriptions and 14 bodies are non-compliant. Measured against the branch as it stands, it is **26 descriptions over the 400-char FAIL** and **9 bodies over the 900-word FAIL**. Two reasons for the difference: the original body count was taken whole-file rather than body-only (the gate measures body-only, so the correct pre-change figure was 13, not 14), and the four kyberforge author/audit skills have since been retrofitted in the same change. **Already done, so out of this issue's scope:** | Skill | Description | Body words | |---|---|---| | `skill-author` | 614 → 225 | 2,623 → 599 | | `agent-author` | 903 → 224 | 2,582 → 616 | | `agent-audit` | 863 → 250 | 1,752 → 691 | | `skill-audit` | 984 → 239 | 1,349 → 581 | **Remaining preload tax**: 21,190 chars (~5,600 tokens), down from 23,612. The bulk of the remaining reduction is in the `gitea-*` and `git-*` families and in `core`. **Useful starting command**: `pre-commit run skill-size-check --all-files` reports every current violation with its measured value. **Reference implementations to copy**: `skill-author` and `skill-audit` are now the house examples of the dispatch pattern — body carries the dispatch table plus common gates, each branch self-contained in `references/`. `apm-workflow` remains the original exemplar. **Known trap**: the gates are hot with no baseline file, so any edit to a non-compliant skill requires retrofitting it first. Batch by plugin rather than fixing skills one at a time.
Author
Collaborator

Body rewritten against measured ground truth. What changed since the comment above, and why:

Numbers that were wrong in that comment. It reported the remaining preload tax as 21,190 chars (~5,600 tokens) down from 23,612. Both figures were carried over rather than re-measured. Measured now: 21,005 chars (~5,250 tokens), down from 23,427 at the branch base. The 23,612 headline appeared in the ADR too and has been corrected there; it was not reproducible under the method the ADR itself documents (~4 chars/token), which is how it was caught.

The retrofit table was also a few chars out per row (skill-author 613 not 614, agent-author 902 not 903, agent-audit 862 not 863, skill-audit 983 not 984) and its body figures have since moved — skill-author is now 595 and skill-audit 580, because both absorbed additional content during review and paid for it by cutting elsewhere rather than by borrowing headroom.

apm-workflow is cited as the exemplar and fails the gate. The comment above recommends it as the pattern to copy without noting its description is 817 chars. That is now called out in the body, along with forge (the router, failing both gates) and apm-install. kyberforge retrofitted four of its seven skills and left three — worth knowing before you copy from one of them.

New material in the body, none of which existed when this issue was written:

  • skill-author/references/retrofit.md is now the procedure to follow. Four dry-run retrofits established that the previous instruction mandated a retrofit and supplied no method, so each agent invented six to ten decisions. One dry run took git-history from 450 chars / 1,044 words to 189 / 247 with both validators green by following it literally.
  • The validate-in-place trap: auditing a copy outside its package prints INFO boundary-target resolution DID NOT RUN and exits 0, which reads as a pass.
  • Three new SUGGESTION-tier checks will show up during retrofits without blocking anything.
  • Kyberforge.CompositionNote is a second hot gate, red on four gitea-* skills.
  • gitea-workflow needs a content decision about its cross-plugin boundary target, not just a length cut.

The original body's "14 of 39 bodies" listed whole-file counts under a body-only heading — the precise conflation the ADR argues against. That is fixed rather than annotated, since this issue is the working document for the retrofit and the wrong numbers would be acted on.

Body rewritten against measured ground truth. What changed since the comment above, and why: **Numbers that were wrong in that comment.** It reported the remaining preload tax as 21,190 chars (~5,600 tokens) down from 23,612. Both figures were carried over rather than re-measured. Measured now: **21,005 chars (~5,250 tokens)**, down from **23,427** at the branch base. The 23,612 headline appeared in the ADR too and has been corrected there; it was not reproducible under the method the ADR itself documents (~4 chars/token), which is how it was caught. The retrofit table was also a few chars out per row (`skill-author` 613 not 614, `agent-author` 902 not 903, `agent-audit` 862 not 863, `skill-audit` 983 not 984) and its body figures have since moved — `skill-author` is now 595 and `skill-audit` 580, because both absorbed additional content during review and paid for it by cutting elsewhere rather than by borrowing headroom. **`apm-workflow` is cited as the exemplar and fails the gate.** The comment above recommends it as the pattern to copy without noting its description is 817 chars. That is now called out in the body, along with `forge` (the router, failing both gates) and `apm-install`. kyberforge retrofitted four of its seven skills and left three — worth knowing before you copy from one of them. **New material in the body**, none of which existed when this issue was written: - `skill-author/references/retrofit.md` is now the procedure to follow. Four dry-run retrofits established that the previous instruction mandated a retrofit and supplied no method, so each agent invented six to ten decisions. One dry run took `git-history` from 450 chars / 1,044 words to 189 / 247 with both validators green by following it literally. - The validate-in-place trap: auditing a copy outside its package prints `INFO boundary-target resolution DID NOT RUN` and exits 0, which reads as a pass. - Three new SUGGESTION-tier checks will show up during retrofits without blocking anything. - `Kyberforge.CompositionNote` is a second hot gate, red on four `gitea-*` skills. - `gitea-workflow` needs a content decision about its cross-plugin boundary target, not just a length cut. The original body's "14 of 39 bodies" listed whole-file counts under a body-only heading — the precise conflation the ADR argues against. That is fixed rather than annotated, since this issue is the working document for the retrofit and the wrong numbers would be acted on.
Author
Collaborator

Collateral for the gitea-prs retrofit: three stale claims in SKILL.md

PR #106 (fixing #104) corrected references/pull-requests.md on which pull_request_write parameters each method actually honours. It deliberately stopped at the reference file, because touching SKILL.md pulls in this retrofit. That leaves SKILL.md contradicting the reference it points at, with the stale version in the file that is preloaded every session.

Recording the three edits here so the retrofit does not land without them.

1. SKILL.md:6 — description advertises a capability that does not exist

The description says the skill covers "updating title/body/reviewers". editPullRequestFn never reads reviewers or team_reviewers; only add_reviewers/remove_reviewers do. This is in the always-loaded description, so it is both a factual error and preload tax — it should disappear during the 709 → ~250 char rewrite anyway. Just don't reintroduce "reviewers" as an update capability.

2. SKILL.md:41 — the instruction that walks an agent into the bug

"Before any pull_request_write call that includes a labels or milestone parameter, resolve names/titles to numeric IDs"

No method qualifier. An agent creating a PR from SKILL.md alone follows this, resolves a milestone ID, passes it on "create", and the server silently discards it — no error, and the response omits the key, so the drop is indistinguishable from never having passed it. labels does apply on create, so a caller who verifies the labels landed will reasonably assume the milestone did too.

Needs a clause: milestone is honoured on "update" only. The dispatch table's Create a PR row (SKILL.md:55) carries no warning either.

3. SKILL.md:32 — superseded draft workaround

"To un-draft, call update and pass the title without the WIP: prefix."

Still works, but unnecessary: applyDraftPrefix means "update" with draft: false and no title makes the server fetch the stored title and strip the prefix itself. Also strips [WIP], case-insensitively.

Gate status for this skill

Measured on plugins/gitea/.apm/skills/gitea-prs/SKILL.md:

  • skill-size-check: ERROR — description 709 chars (over the 400 ceiling). Body is 683 words, a SUGGESTION only, under the 900 hard fail — so this skill needs a description rewrite, not a dispatch split.
  • vale-audit-prefilter-skill: 2 errors — Kyberforge.CompositionNote at 4:310 (Composes) and 4:438 (rather than duplicating), both inside the description, both removed by the same rewrite.

So the blocking work here is one description rewrite; the three corrections above ride along with it. Cheaper than most entries on this issue — worth taking early, since the #2 gap is live and reachable by any agent that dispatches on SKILL.md without opening the reference.

Verified against gitea-mcp v1.6.0 source (operation/pull/pull.go), the version get_gitea_mcp_server_version reports as running. Independently confirmed in review of #106.

## Collateral for the `gitea-prs` retrofit: three stale claims in `SKILL.md` PR #106 (fixing #104) corrected `references/pull-requests.md` on which `pull_request_write` parameters each method actually honours. It deliberately stopped at the reference file, because touching `SKILL.md` pulls in this retrofit. That leaves `SKILL.md` contradicting the reference it points at, with the stale version in the file that is preloaded every session. Recording the three edits here so the retrofit does not land without them. ### 1. `SKILL.md:6` — description advertises a capability that does not exist The description says the skill covers *"updating title/body/**reviewers**"*. `editPullRequestFn` never reads `reviewers` or `team_reviewers`; only `add_reviewers`/`remove_reviewers` do. This is in the always-loaded description, so it is both a factual error and preload tax — it should disappear during the 709 → ~250 char rewrite anyway. Just don't reintroduce "reviewers" as an `update` capability. ### 2. `SKILL.md:41` — the instruction that walks an agent into the bug > "Before any `pull_request_write` call that includes a `labels` or `milestone` parameter, resolve names/titles to numeric IDs" No method qualifier. An agent creating a PR from `SKILL.md` alone follows this, resolves a milestone ID, passes it on `"create"`, and the server silently discards it — no error, and the response omits the key, so the drop is indistinguishable from never having passed it. `labels` *does* apply on create, so a caller who verifies the labels landed will reasonably assume the milestone did too. Needs a clause: `milestone` is honoured on `"update"` only. The dispatch table's `Create a PR` row (`SKILL.md:55`) carries no warning either. ### 3. `SKILL.md:32` — superseded draft workaround > "To un-draft, call `update` and pass the title without the `WIP:` prefix." Still works, but unnecessary: `applyDraftPrefix` means `"update"` with `draft: false` and no `title` makes the server fetch the stored title and strip the prefix itself. Also strips `[WIP]`, case-insensitively. ### Gate status for this skill Measured on `plugins/gitea/.apm/skills/gitea-prs/SKILL.md`: - `skill-size-check`: **ERROR** — description 709 chars (over the 400 ceiling). Body is 683 words, a SUGGESTION only, under the 900 hard fail — so this skill needs a description rewrite, not a dispatch split. - `vale-audit-prefilter-skill`: **2 errors** — `Kyberforge.CompositionNote` at 4:310 (`Composes`) and 4:438 (`rather than duplicating`), both inside the description, both removed by the same rewrite. So the blocking work here is one description rewrite; the three corrections above ride along with it. Cheaper than most entries on this issue — worth taking early, since the #2 gap is live and reachable by any agent that dispatches on `SKILL.md` without opening the reference. Verified against `gitea-mcp` v1.6.0 source (`operation/pull/pull.go`), the version `get_gitea_mcp_server_version` reports as running. Independently confirmed in review of #106.
Author
Collaborator

Folded in from #100 (closed): two dangling routing targets, both hostage to this retrofit

#100 tracked four skill descriptions whose boundary clauses named targets that do not resolve. Two are already fixed on main as forced consequences of earlier work (skill-audit → /skill-improve; agent-author → "examine agent files manually"). The remaining two are not separable bugs — both files are current skill-size-check FAILs on description length, and the hook is stages: ['pre-commit'] with pass_filenames: true, so it fires on exactly the files you touch. There is no edit to either file that does not pull the retrofit in. Closing #100 and recording the residue here.

1. gitea-issues → gitea-labels — no decision needed, the retrofit deletes it

Measured on plugins/gitea/.apm/skills/gitea-issues/SKILL.md: description 827 chars (ERROR), body 902 words (ERROR, 2 over the ceiling), Gotchas 391/902 = 43%, 7 entries.

The dangling target is a YAML fold artifact. The description contains:

Composes gitea-labels-
  milestones for all label inference/resolution and milestone lookup — …

The >-folded scalar joins those lines into gitea-labels- milestones, leaving gitea-labels- terminal and therefore danglable. The checker reports it as an unresolvable target named gitea-labels.

This needs no separate fix. The sentence is a composition note — exactly what step 2 of this issue says to move to README.md, and exactly what Kyberforge.CompositionNote already flags. Cut it during the 827 → ~250 rewrite and the dangling target goes with it. Do not "fix" it by re-hyphenating gitea-labels-milestones inline; that preserves a composition note the prose gate rejects.

Note this skill fails both size gates, so it needs a description cut and a dispatch split in one commit — it is one of the eight in this issue's body that cannot be partially improved.

2. research → neuledge-context — needs a content decision

Measured on plugins/bin/.apm/skills/research/SKILL.md: description 583 chars (ERROR), body 854 words (SUGGESTION only, under the 900 hard fail). So this one is a description rewrite, not a dispatch split.

The clause reads:

Do NOT use when the user wants to … install or manage the neuledge-context MCP server (use neuledge-context) …

neuledge-context is not a phantom — it is a corpse. Commit 6146120 (2026-06-23, "chore: remove neuledge-context skill and multiple kyberforge skills") deleted .agents/skills/neuledge-context/ — SKILL.md, META.md, and three references/ files — alongside marketplace-architect, plugin-create, promptfoo and write-agent. The description was never updated to follow. No skill in the repo has owned MCP-server installation since.

Three options, with the recommendation:

  1. Delete the clause outright. (Recommended.) No skill owns the capability, so there is no honest target. The point of the resolvable-target gate is that a boundary clause names somewhere the router can actually go; with no owner, the correct description says nothing.
  2. Keep it as a targetless negative trigger — "Not MCP server installation". The gate permits this (a clause naming no target cannot dangle), and it preserves the negative steer for the "install the neuledge context server" query class. Take this only if that query class is judged real; otherwise it is preload tax for a steer nobody needs.
  3. Retarget to a live skill. Rejected. The nearest candidate is apm-install, which explicitly scopes itself to the apm CLI and agent runtimes, not MCP servers. The gate would pass and the route would be a lie — strictly worse than a dangle, because it is undetectable.

Whichever is chosen, do not compress this boundary to Not MCP setup → neuledge-context per step 2 of this issue. That preserves the dead target in compressed form. The gate would catch it, but it wastes a retrofit cycle.

Collateral

The test pins are self-documenting — both files carry inline instructions to update together, so they will not be silently lost, but they must land in the same commit as the retrofit or the suite goes red:

  • tests/test-adr0020-targets.sh:424 — EXPECTED_DANGLING hard-codes exactly {gitea-labels, neuledge-context}. Update it; do not delete the assertion. An empty expected set is valid and still pins that no new dangling target appeared.
  • tests/test-skill-size-check.sh:557 — a two-element probe loop asserting each target is individually detected. Drop the probe whose fixture was retrofitted. The file's own comment explains why a pass "SKIP: …" branch is not acceptable here: it is an assertion-free result counted in the totals, which makes the suite look one test stronger than it is.

The one item no gate and no test covers:

  • plugins/bin/evals/research/research/eval.yaml:30 — eval case negative-neuledge, "Negative — MCP server setup goes to neuledge-context", query "Install the neuledge context server and set it up", should_trigger: false. It asserts a route that no longer exists. Under option 1 it should be deleted; leaving it makes the eval pass for the wrong reason (the skill does not trigger because the query is unrelated, not because it routes elsewhere). Under option 2, rename it so it no longer claims a destination.

Classification note

#100 carried Kind/Bug; this issue is Kind/Enhancement. Two live routing defects are no longer visible to a bug query. Accepted deliberately — both are detected automatically by the resolvable-target check, so pre-commit run skill-size-check --all-files remains the authority on whether they still exist.

## Folded in from #100 (closed): two dangling routing targets, both hostage to this retrofit #100 tracked four skill descriptions whose boundary clauses named targets that do not resolve. Two are already fixed on `main` as forced consequences of earlier work (`skill-audit` → `/skill-improve`; `agent-author` → "examine agent files manually"). The remaining two are **not separable bugs** — both files are current `skill-size-check` FAILs on description length, and the hook is `stages: ['pre-commit']` with `pass_filenames: true`, so it fires on exactly the files you touch. There is no edit to either file that does not pull the retrofit in. Closing #100 and recording the residue here. ### 1. `gitea-issues` → `gitea-labels` — no decision needed, the retrofit deletes it Measured on `plugins/gitea/.apm/skills/gitea-issues/SKILL.md`: description **827 chars** (ERROR), body **902 words** (ERROR, 2 over the ceiling), Gotchas 391/902 = 43%, 7 entries. The dangling target is a YAML fold artifact. The description contains: > Composes gitea-labels-<br>&nbsp;&nbsp;milestones for all label inference/resolution and milestone lookup — … The `>`-folded scalar joins those lines into `gitea-labels- milestones`, leaving `gitea-labels-` terminal and therefore danglable. The checker reports it as an unresolvable target named `gitea-labels`. **This needs no separate fix.** The sentence is a composition note — exactly what step 2 of this issue says to move to `README.md`, and exactly what `Kyberforge.CompositionNote` already flags. Cut it during the 827 → ~250 rewrite and the dangling target goes with it. Do **not** "fix" it by re-hyphenating `gitea-labels-milestones` inline; that preserves a composition note the prose gate rejects. Note this skill fails **both** size gates, so it needs a description cut *and* a dispatch split in one commit — it is one of the eight in this issue's body that cannot be partially improved. ### 2. `research` → `neuledge-context` — needs a content decision Measured on `plugins/bin/.apm/skills/research/SKILL.md`: description **583 chars** (ERROR), body **854 words** (SUGGESTION only, under the 900 hard fail). So this one is a description rewrite, not a dispatch split. The clause reads: > Do NOT use when the user wants to … install or manage the neuledge-context MCP server (use neuledge-context) … **`neuledge-context` is not a phantom — it is a corpse.** Commit `6146120` (2026-06-23, *"chore: remove neuledge-context skill and multiple kyberforge skills"*) deleted `.agents/skills/neuledge-context/` — `SKILL.md`, `META.md`, and three `references/` files — alongside `marketplace-architect`, `plugin-create`, `promptfoo` and `write-agent`. The description was never updated to follow. No skill in the repo has owned MCP-server installation since. Three options, with the recommendation: 1. **Delete the clause outright.** *(Recommended.)* No skill owns the capability, so there is no honest target. The point of the resolvable-target gate is that a boundary clause names somewhere the router can actually go; with no owner, the correct description says nothing. 2. **Keep it as a targetless negative trigger** — "Not MCP server installation". The gate permits this (a clause naming no target cannot dangle), and it preserves the negative steer for the "install the neuledge context server" query class. Take this only if that query class is judged real; otherwise it is preload tax for a steer nobody needs. 3. **Retarget to a live skill.** Rejected. The nearest candidate is `apm-install`, which explicitly scopes itself to the apm CLI and agent runtimes, not MCP servers. The gate would pass and the route would be a lie — strictly worse than a dangle, because it is undetectable. Whichever is chosen, do **not** compress this boundary to `Not MCP setup → neuledge-context` per step 2 of this issue. That preserves the dead target in compressed form. The gate would catch it, but it wastes a retrofit cycle. ### Collateral The test pins are self-documenting — both files carry inline instructions to update together, so they will not be silently lost, but they **must land in the same commit** as the retrofit or the suite goes red: - `tests/test-adr0020-targets.sh:424` — `EXPECTED_DANGLING` hard-codes exactly `{gitea-labels, neuledge-context}`. Update it; do not delete the assertion. An empty expected set is valid and still pins that no *new* dangling target appeared. - `tests/test-skill-size-check.sh:557` — a two-element probe loop asserting each target is individually detected. Drop the probe whose fixture was retrofitted. The file's own comment explains why a `pass "SKIP: …"` branch is not acceptable here: it is an assertion-free result counted in the totals, which makes the suite look one test stronger than it is. The one item **no gate and no test covers**: - `plugins/bin/evals/research/research/eval.yaml:30` — eval case `negative-neuledge`, *"Negative — MCP server setup goes to neuledge-context"*, query `"Install the neuledge context server and set it up"`, `should_trigger: false`. It asserts a route that no longer exists. Under option 1 it should be deleted; leaving it makes the eval pass for the wrong reason (the skill does not trigger because the query is unrelated, not because it routes elsewhere). Under option 2, rename it so it no longer claims a destination. ### Classification note #100 carried `Kind/Bug`; this issue is `Kind/Enhancement`. Two live routing defects are no longer visible to a bug query. Accepted deliberately — both are detected automatically by the resolvable-target check, so `pre-commit run skill-size-check --all-files` remains the authority on whether they still exist.
Author
Collaborator

Wave 1 complete — the gitea plugin (7 skills). Handoff for waves 2–4.

Branch refactor/adr0020-skill-retrofit, 13 commits, not pushed. Clean tree.

Measured state

at issue open now
description FAILs (>400) 26 19
body FAILs (>900, body-only) 9 7
dangling routing targets 2 1 (research → neuledge-context)
Kyberforge.CompositionNote 10 0 ✅
preload tax 21,033 chars / ~5,258 tok 17,334 / ~4,333 (−17%)
tests/run-tests.sh --strict 2 failing 24 passed, 0 failed ✅

Note the issue body's 21,005 baseline re-measured as 21,033 — 28 chars of since-landed drift, not a discrepancy worth chasing.

Per-skill result

skill desc body Gotchas
gitea-prs 709 → 161 683 → 353 8 → 2
gitea-issues 827 → 182 902 → 584 7 → 4
gitea-releases 500 → 211 676 → 462 5 → 3
gitea-labels-milestones 835 → 214 669 → 426 8 → 3 (63% → 20%)
gitea-files 787 → 263 922 → 302 9 → 3 (69% → 25%)
gitea-branches 688 → 282 435 → 293 5 → 3
gitea-workflow 1012 → 347 582 → 170 3 → 1
family 5,358 → 1,635

A 70% cut on the family the ADR predicted would sit at the FAIL tier permanently. Four of seven landed under the 250 SUGGESTION target.


The way of working — validated, and worth repeating verbatim

Orchestrator → parallel author agents (one per skill) → parallel clean-context audit agents (one per skill) → orchestrator triages and re-dispatches. This is forge Step 3's two-tier verification pattern, borrowed rather than invoked (forge's own description forbids using it when the artifact type is already known, and its Step 1 would recursively call grill-with-docs).

Author agent contract — every constraint here was load-bearing:

  • Handed its own measured skill-size-check and Vale lines as the improvement signal. skill-author's improve flow (references/improve.md Step 1) stops dead without one.
  • Invoke skill-author → improve flow → references/retrofit.md, then /skill-audit inline in its own context.
  • No git commands — parallel agents race on .git/index. This deliberately overrides skill-author Step 4's commit-verification; the orchestrator commits one per skill.
  • No worktree — skill-author/SKILL.md:23 names worktree teardown as a live hazard to uncommitted drafts.
  • No grandchild agents — same Gotcha. The clean-context recheck is the orchestrator's separate agent.
  • Validate in place. Auditing outside the package prints INFO boundary-target resolution DID NOT RUN and exits 0.
  • Composition notes go to the skill's own README.md (28 exist, one per skill), never the plugin-root one.
  • One arrow per boundary target — see #107.

Exemplars named in every prompt: skill-author (225/599), agent-author (224/616), skill-audit (239/581), agent-audit (250/691).

Cap: 3 rounds, then revert and park for a human decision.

Decisions taken

  • 250 chars soft, 400 hard. Aggregate reported after each wave to catch drift toward the ceiling. It held — family average 234.
  • Per-skill scope: all ERRORs + the Gotchas suggestions (>5 entries, >25% of body). Body-over-600 and desc-over-250 suggestions skipped.
  • caveman only for disable-model-invocation — see the finding below.
  • research: delete the neuledge-context clause outright; delete eval case negative-neuledge at plugins/bin/evals/research/research/eval.yaml:30 with it. (still to do, wave 3)
  • gitea-workflow: exclusion kept, cross-plugin route to git-workflow dropped. Done.

What the second audit layer was actually worth

Every skill passed both deterministic gates before its first audit. Five of seven then failed on auditor-tier defects. Not size — the gates were green for all of them:

  • gitea-issues: a label-application capability with no dispatch row, while gitea-labels-milestones routes label application to it and holds no issue_write. A closed loop with no executable branch.
  • gitea-labels-milestones: the org-label fallback named an org parameter the skill never derived, so the documented procedure stalled. Two rounds each fixed one half.
  • gitea-issues: one Gotcha rewritten three times — each rewrite correct on the branch it targeted and wrong on another.
  • gitea-workflow: the description's grammar orphaned a third of the skill's behaviour; a round-2 fix relocated a directive and left its fact behind, creating a fresh restatement FAIL.

This is direct evidence for the ADR's own claim that the deterministic tier is a backstop, not the mechanism. Budget for it: roughly 2 audit agents per skill, and expect a second round on the majority.

Audits going beyond brief paid off too — one queried the live gitea-mcp v1.7.0 server and found references/reviews.md forbidding review_comments (the field that works) in favour of review_scomments (which no longer exists), plus three undocumented pull_request_review_write methods. Landed as 0079f35.


Findings that changed scope

disable-model-invocation: true hard-blocks the Skill tool — tested, not inferred:

Skill(zoom-out) → Skill zoom-out cannot be used with Skill tool due to
disable-model-invocation. Ask the user to run /zoom-out themselves.

ADR-0020 verified only the listing-visibility and slash directions. No other skill can route to a hand-invoked skill. Of this issue's four named candidates, three have inbound routes: prototype ← diagnose; grill-me ← write-docs, skill-author/references/create.md; grill-with-docs ← improve-codebase-architecture, triage, write-docs, forge Step 1 (mandatory). Only caveman has none. Recorded in CONTEXT.md (ef3e981).

Three gate defects filed: #107 (resolver checks only the first target per arrow clause — confirmed 4×, once by executing the extracted resolver), #108 (boundary check fires on hand-invoked skills), #109 (body-discipline.md contradicts the ADR's cited exemplar).


Remaining work

  • wave 2 — git (9): git-commits git-branches git-worktrees git-remotes git-workflow git-submodules git-history pc-run pc-author
  • wave 3 — bin/core/lint (8): research prototype diagnose · agentsmd-author agentsmd-audit provider-adapter-author · vale-config vale-run
  • wave 4 — kyberforge (3): apm-workflow forge apm-install
  • orchestrator close-out: caveman flag · research eval case · EXPECTED_DANGLING → empty set once research lands · final mirror sync · full gate + --strict run

diagnose is a 27th skill in scope not visible in the "26 descriptions" figure — its description is compliant, its body is 1,126 words.

Traps confirmed the hard way

  • Run scripts/sync-plugin-content.sh --all once per wave, at the end. sync_dir does rm -rf before every copy; running it while agents edit the same plugin races them. Agents must be told not to sync.
  • The two test pins are the orchestrator's, never an agent's — parallel agents would conflict. tests/test-adr0020-targets.sh EXPECTED_DANGLING is now {neuledge-context}; both probe loops carry a shellcheck disable=SC2043 waiver because one entry is the expected steady state.
  • AGENTS.md:39 carries live gate counts and goes stale every wave. Updated to 19/7 in b8c36c3; update it again as waves land.
  • A .apm/ edit is not live until pushed. Filing these issues loaded the pre-retrofit gitea-issues from .claude/skills/.

Sequencing note

Resolve #109 before wave 4. apm-workflow is both the exemplar in that contradiction and an unretrofitted target — retrofit it first and the agent must guess which convention to follow, and its guess becomes the new de-facto exemplar. gitea-workflow currently carries the duplication knowingly, pending that decision.

## Wave 1 complete — the `gitea` plugin (7 skills). Handoff for waves 2–4. Branch `refactor/adr0020-skill-retrofit`, 13 commits, **not pushed**. Clean tree. ### Measured state | | at issue open | now | |---|---|---| | description FAILs (>400) | 26 | **19** | | body FAILs (>900, body-only) | 9 | **7** | | dangling routing targets | 2 | **1** (`research` → `neuledge-context`) | | `Kyberforge.CompositionNote` | 10 | **0** ✅ | | preload tax | 21,033 chars / ~5,258 tok | **17,334 / ~4,333** (−17%) | | `tests/run-tests.sh --strict` | 2 failing | **24 passed, 0 failed** ✅ | Note the issue body's 21,005 baseline re-measured as 21,033 — 28 chars of since-landed drift, not a discrepancy worth chasing. ### Per-skill result | skill | desc | body | Gotchas | |---|---|---|---| | `gitea-prs` | 709 → **161** | 683 → 353 | 8 → 2 | | `gitea-issues` | 827 → **182** | 902 → 584 | 7 → 4 | | `gitea-releases` | 500 → **211** | 676 → 462 | 5 → 3 | | `gitea-labels-milestones` | 835 → **214** | 669 → 426 | 8 → 3 (63% → 20%) | | `gitea-files` | 787 → **263** | 922 → 302 | 9 → 3 (69% → 25%) | | `gitea-branches` | 688 → **282** | 435 → 293 | 5 → 3 | | `gitea-workflow` | 1012 → **347** | 582 → 170 | 3 → 1 | | **family** | **5,358 → 1,635** | | | A 70% cut on the family the ADR predicted would sit at the FAIL tier permanently. Four of seven landed under the 250 SUGGESTION target. --- ## The way of working — validated, and worth repeating verbatim Orchestrator → **parallel author agents (one per skill)** → **parallel clean-context audit agents (one per skill)** → orchestrator triages and re-dispatches. This is `forge` Step 3's two-tier verification pattern, borrowed rather than invoked (`forge`'s own description forbids using it when the artifact type is already known, and its Step 1 would recursively call `grill-with-docs`). **Author agent contract** — every constraint here was load-bearing: - Handed **its own measured `skill-size-check` and Vale lines as the improvement signal**. `skill-author`'s improve flow (`references/improve.md` Step 1) stops dead without one. - Invoke `skill-author` → improve flow → `references/retrofit.md`, then `/skill-audit` **inline in its own context**. - **No git commands** — parallel agents race on `.git/index`. This deliberately overrides `skill-author` Step 4's commit-verification; the orchestrator commits one per skill. - **No worktree** — `skill-author/SKILL.md:23` names worktree teardown as a live hazard to uncommitted drafts. - **No grandchild agents** — same Gotcha. The clean-context recheck is the orchestrator's separate agent. - **Validate in place.** Auditing outside the package prints `INFO boundary-target resolution DID NOT RUN` and exits 0. - Composition notes go to the **skill's own `README.md`** (28 exist, one per skill), never the plugin-root one. - **One arrow per boundary target** — see #107. **Exemplars named in every prompt:** `skill-author` (225/599), `agent-author` (224/616), `skill-audit` (239/581), `agent-audit` (250/691). **Cap: 3 rounds**, then revert and park for a human decision. ### Decisions taken - **250 chars soft, 400 hard.** Aggregate reported after each wave to catch drift toward the ceiling. It held — family average 234. - **Per-skill scope: all ERRORs + the Gotchas suggestions** (>5 entries, >25% of body). Body-over-600 and desc-over-250 suggestions skipped. - **`caveman` only** for `disable-model-invocation` — see the finding below. - **`research`**: delete the `neuledge-context` clause outright; delete eval case `negative-neuledge` at `plugins/bin/evals/research/research/eval.yaml:30` with it. *(still to do, wave 3)* - **`gitea-workflow`**: exclusion kept, cross-plugin route to `git-workflow` dropped. Done. --- ## What the second audit layer was actually worth **Every skill passed both deterministic gates before its first audit. Five of seven then failed on auditor-tier defects.** Not size — the gates were green for all of them: - `gitea-issues`: a label-application capability with **no dispatch row**, while `gitea-labels-milestones` routes label application to it and holds no `issue_write`. A closed loop with no executable branch. - `gitea-labels-milestones`: the org-label fallback named an `org` parameter the skill never derived, so the documented procedure stalled. Two rounds each fixed one half. - `gitea-issues`: one Gotcha rewritten three times — each rewrite correct on the branch it targeted and wrong on another. - `gitea-workflow`: the description's grammar orphaned a third of the skill's behaviour; a round-2 fix relocated a directive and left its fact behind, creating a fresh restatement FAIL. This is direct evidence for the ADR's own claim that the deterministic tier is a backstop, not the mechanism. Budget for it: roughly **2 audit agents per skill**, and expect a second round on the majority. Audits going beyond brief paid off too — one queried the **live gitea-mcp v1.7.0** server and found `references/reviews.md` forbidding `review_comments` (the field that works) in favour of `review_scomments` (which no longer exists), plus three undocumented `pull_request_review_write` methods. Landed as `0079f35`. --- ## Findings that changed scope **`disable-model-invocation: true` hard-blocks the Skill tool** — tested, not inferred: ``` Skill(zoom-out) → Skill zoom-out cannot be used with Skill tool due to disable-model-invocation. Ask the user to run /zoom-out themselves. ``` ADR-0020 verified only the listing-visibility and slash directions. **No other skill can route to a hand-invoked skill.** Of this issue's four named candidates, three have inbound routes: `prototype` ← `diagnose`; `grill-me` ← `write-docs`, `skill-author/references/create.md`; `grill-with-docs` ← `improve-codebase-architecture`, `triage`, `write-docs`, **`forge` Step 1 (mandatory)**. Only `caveman` has none. Recorded in `CONTEXT.md` (`ef3e981`). **Three gate defects filed:** #107 (resolver checks only the first target per arrow clause — confirmed 4×, once by executing the extracted resolver), #108 (boundary check fires on hand-invoked skills), #109 (`body-discipline.md` contradicts the ADR's cited exemplar). --- ## Remaining work - **wave 2 — `git` (9):** `git-commits` `git-branches` `git-worktrees` `git-remotes` `git-workflow` `git-submodules` `git-history` `pc-run` `pc-author` - **wave 3 — `bin`/`core`/`lint` (8):** `research` `prototype` `diagnose` · `agentsmd-author` `agentsmd-audit` `provider-adapter-author` · `vale-config` `vale-run` - **wave 4 — `kyberforge` (3):** `apm-workflow` `forge` `apm-install` - **orchestrator close-out:** `caveman` flag · `research` eval case · `EXPECTED_DANGLING` → empty set once `research` lands · final mirror sync · full gate + `--strict` run `diagnose` is a **27th** skill in scope not visible in the "26 descriptions" figure — its description is compliant, its body is 1,126 words. ### Traps confirmed the hard way - **Run `scripts/sync-plugin-content.sh --all` once per wave, at the end.** `sync_dir` does `rm -rf` before every copy; running it while agents edit the same plugin races them. Agents must be told not to sync. - **The two test pins are the orchestrator's**, never an agent's — parallel agents would conflict. `tests/test-adr0020-targets.sh` `EXPECTED_DANGLING` is now `{neuledge-context}`; both probe loops carry a `shellcheck disable=SC2043` waiver because one entry is the expected steady state. - **`AGENTS.md:39` carries live gate counts** and goes stale every wave. Updated to 19/7 in `b8c36c3`; update it again as waves land. - **A `.apm/` edit is not live until pushed.** Filing these issues loaded the *pre-retrofit* `gitea-issues` from `.claude/skills/`. ### Sequencing note **Resolve #109 before wave 4.** `apm-workflow` is both the exemplar in that contradiction and an unretrofitted target — retrofit it first and the agent must guess which convention to follow, and its guess becomes the new de-facto exemplar. `gitea-workflow` currently carries the duplication knowingly, pending that decision.
Author
Collaborator

Wave 2 complete — the git plugin, 9 skills

Branch refactor/adr0020-skill-retrofit, 24 commits, clean tree, nothing pushed.

Measured state

Start After wave 1 After wave 2
Description FAILs (>400) 26 19 10
Body FAILs (>900, body-only) 9 7 2
Dangling routing targets 2 1 1 (research → neuledge-context)
Kyberforge.CompositionNote 10 0 0
Preload tax 21,033 17,334 14,507 chars (~3,626 tokens)
tests/run-tests.sh --strict — 24/0 24 passed, 0 failed

The git family went 5,057 → 2,258 chars of description, a 55% cut. Corpus preload is down 31% from the start.

Per skill

Skill Description Body Commit
git-commits 724 → 214 1102 → 343 f3b4860
git-branches 612 → 273 1124 → 457 0fde892
git-worktrees 592 → 248 756 → 515 3dd5387
git-remotes 582 → 237 1217 → 290 38eb074
git-workflow 566 → 249 644 → 398 3c74beb
git-submodules 480 → 248 1011 → 347 261e5b5
git-history 450 → 266 1044 → 462 7cb8e95
pc-run 477 → 211 736 → 367 15ff741
pc-author 475 → 213 680 → 212 3cd3f33

Three deliberate SUGGESTION-tier overages, all justified in their commit messages: git-branches 273 and git-history 266 (each the price of keeping a capability reachable by natural language), and git-remotes Gotchas at 27% of body (the ratio rose purely because the denominator shrank; padding the body to clear it would game the metric).

What the two-layer loop bought

All 9 passed the deterministic gates before their first audit. 8 of 9 then failed the clean-context audit — 16 must-fix findings. Six shared one shape: a safety gate surviving redistribution as text while losing its binding force. That is now the characteristic defect of this retrofit, and it is invisible to every script.

The three worth carrying forward:

  • git-worktrees had no dispatch row for a worktree on an existing local branch. That request fell to the adjacent -B row, which resets the branch to HEAD and discards its commits. The correct non-destructive command was one row away. This is the cost of the dispatch-table-as-flow shape: because the rows are the flows, a missing row is a missing capability.
  • git-commits moved the secret scan into the create flow only. Dispatch loads exactly one flow file, so the amend/squash path could commit a credential with no check in its loaded context — a grep for secret returned one hit on a path two of three branches never reach.
  • pc-run advertised diagnosing why a hook "never fires" but routed only from the failure path. When hooks never fire the manual run succeeds, so the request fell to the ambiguity default, reported green, and masked the cause.

Two author judgment calls were overturned by auditors using measurement rather than assertion (pc-run's "flows too small to extract" — the house already ships ~100-word branch files; git-remotes' weakest boundary target). One author claimed content was "already present verbatim" in a reference when the same pass had just added it there — outcome correct, reasoning not. Verify before deleting, not after.

Systematic hazard for waves 3–4

The Gotchas-ratio SUGGESTION fired three times purely from the denominator shrinking. Clearing two ERRORs routinely hands you a new SUGGESTION on a section you never touched. Two authors caught it mid-flight and rebalanced by relocating a branch-specific Gotcha to its point of use; one correctly declined to pad. Expect it, and do not resolve it by deleting a Gotcha that earns its place.

Collateral fixed this wave

skill-audit's own references/body-discipline.md used pre-retrofit git-commits as its worked negative example. Every figure was correct for v0.1.2 and none survived into v0.1.3 — so every wave-3 auditor would have read a rubric contradicted by the file it names. Worse, its row four called the secrets Gotcha a paraphrase FAIL, which is the exact reasoning that produced the git-commits credential hole.

Fixed in a2ebdaf: the example is pinned to commit 5e23250 and marked not to be refreshed against HEAD, and the paraphrase rule now carries its missing precondition — delete a restating Gotcha only when the surviving copy is reachable from every branch that needs it; relocate a multi-branch safety gate into the body rather than dropping it. A sweep of both audit skills found no other stale example (apm-workflow, git-orchestrate, gitea-orchestrate, apm-orchestrate citations all verified exact).

Issues filed from this wave

  • #110 — skill-size-check's BOUNDARY_ARROW regex cannot cross a ., so a boundary clause naming a dotted filename between "Not" and the arrow is invisible and misreported as missing.
  • #111 — validate-provenance.sh has no way to declare a reference file house-authored; source_keys: [] parses identically to a missing key, so the only alternatives to a permanent INFO are false provenance claims.
  • #112 — cherry-pick and revert have three claimants (git-branches delegates to git-history; git-commits claims it and owns references/cherry-pick.md; git-history executes it). Pre-existing, held out of scope. The rebase half of the same tangle was resolved in 0fde892.
  • #113 — rtk git vs bare git is inconsistent across the plugin. Pre-existing; needs one normalising pass, not nine per-skill calls.

Earlier gate defects remain open: #107, #108, #109.


Remaining work

Wave 3 — bin / core / lint (8): research (583), prototype (426), diagnose (body 1126 only), agentsmd-author (960), agentsmd-audit (944), provider-adapter-author (833), vale-config (657), vale-run (654)

Wave 4 — kyberforge (3): apm-workflow (817), forge (648/1093), apm-install (514)

Sequencing: resolve #109 before wave 4 — apm-workflow is both the exemplar in that contradiction and an unretrofitted target.

Orchestrator close-out: set disable-model-invocation: true on caveman; delete eval case negative-neuledge at plugins/bin/evals/research/research/eval.yaml:30; reduce EXPECTED_DANGLING to empty and drop the last probe from both tests/test-adr0020-targets.sh and tests/test-skill-size-check.sh once research lands; final scripts/sync-plugin-content.sh --all; pre-commit run --all-files; bash tests/run-tests.sh --strict; pre-commit run --hook-stage pre-push --all-files (prints 16); update the AGENTS.md gate counts again (currently "10 of 39 / 2 of 39", corrected in afbeaff).


The agent contract — reuse verbatim

Author agents. Hand each its own measured skill-size-check and Vale lines — skill-author's improve flow (references/improve.md Step 1) stops dead without an improvement signal. Then: invoke skill-author → improve flow → references/retrofit.md (the ordered cut procedure) → run /skill-audit inline, in its own context.

Audit agents. Fresh, not forked, one per skill. Tell them the deterministic gates already pass and their value is in what a script cannot check: behaviour loss, dispatch correctness, Gotcha paraphrase, description grammar as rendered, boundary-target quality, internal consistency. Give each the specific judgment call the author hedged on.

Repair agents. Fresh again, handed the audit findings split into must-fix and judgment call, with the reasoning — not just the diffs.

Hard constraints in every prompt:

  • No git commands at all. Parallel agents race on .git/index. The orchestrator commits. This deliberately overrides skill-author Step 4's commit-verification requirement.
  • No worktree isolation — skill-author/SKILL.md:23 names worktree teardown as a live hazard to uncommitted drafts.
  • No grandchild agents. Same Gotcha. The clean-context recheck is the orchestrator's, per forge Step 3.
  • No scripts/sync-plugin-content.sh — sync_dir runs rm -rf before every copy and will delete directories other agents are writing. Orchestrator syncs once at wave end.
  • Validate in place. A skill audited outside its package prints INFO boundary-target resolution DID NOT RUN and exits 0 — reads as a pass, is not one.
  • Edit only the target skill directory, never the flat mirror. Report sibling needs; do not act on them.

Content rules: one arrow per boundary target; never let a hyphenated skill name straddle a line in a >-folded scalar; composition notes go in the skill's own README.md. Any references/ file added, removed, or renamed requires updating the skill README.md file table, references/README.md, and references/sources.md "Contributing files".

Exemplars: skill-author (225/599), agent-author (224/616), skill-audit (239/581), agent-audit (250/691).

Budget: ~1 audit agent per skill, and expect a repair round on most. Wave 2 ran 9 + 9 + 8 = 26 agents for 9 skills.

Accidental benefit worth keeping

Because the flat mirror is synced only at wave end, it still holds the pre-retrofit content while the audits run. Three auditors used it to diff real before/after rather than inferring what was lost — which is how the git-submodules regressions and the git-remotes redistribution audit were both settled on evidence. Keep syncing at wave end, not per skill.

## Wave 2 complete — the `git` plugin, 9 skills Branch `refactor/adr0020-skill-retrofit`, 24 commits, clean tree, **nothing pushed**. ### Measured state | | Start | After wave 1 | After wave 2 | |---|---|---|---| | Description FAILs (>400) | 26 | 19 | **10** | | Body FAILs (>900, body-only) | 9 | 7 | **2** | | Dangling routing targets | 2 | 1 | **1** (`research` → `neuledge-context`) | | `Kyberforge.CompositionNote` | 10 | 0 | **0** | | Preload tax | 21,033 | 17,334 | **14,507 chars** (~3,626 tokens) | | `tests/run-tests.sh --strict` | — | 24/0 | **24 passed, 0 failed** | The `git` family went **5,057 → 2,258 chars** of description, a 55% cut. Corpus preload is down 31% from the start. ### Per skill | Skill | Description | Body | Commit | |---|---|---|---| | `git-commits` | 724 → 214 | 1102 → 343 | `f3b4860` | | `git-branches` | 612 → 273 | 1124 → 457 | `0fde892` | | `git-worktrees` | 592 → 248 | 756 → 515 | `3dd5387` | | `git-remotes` | 582 → 237 | 1217 → 290 | `38eb074` | | `git-workflow` | 566 → 249 | 644 → 398 | `3c74beb` | | `git-submodules` | 480 → 248 | 1011 → 347 | `261e5b5` | | `git-history` | 450 → 266 | 1044 → 462 | `7cb8e95` | | `pc-run` | 477 → 211 | 736 → 367 | `15ff741` | | `pc-author` | 475 → 213 | 680 → 212 | `3cd3f33` | Three deliberate SUGGESTION-tier overages, all justified in their commit messages: `git-branches` 273 and `git-history` 266 (each the price of keeping a capability reachable by natural language), and `git-remotes` Gotchas at 27% of body (the ratio rose purely because the denominator shrank; padding the body to clear it would game the metric). ### What the two-layer loop bought **All 9 passed the deterministic gates before their first audit. 8 of 9 then failed the clean-context audit** — 16 must-fix findings. Six shared one shape: **a safety gate surviving redistribution as text while losing its binding force.** That is now the characteristic defect of this retrofit, and it is invisible to every script. The three worth carrying forward: - **`git-worktrees`** had no dispatch row for a worktree on an existing local branch. That request fell to the adjacent `-B` row, which **resets the branch to HEAD and discards its commits**. The correct non-destructive command was one row away. This is the cost of the dispatch-table-as-flow shape: because the rows *are* the flows, a missing row is a missing capability. - **`git-commits`** moved the secret scan into the create flow only. Dispatch loads exactly one flow file, so the amend/squash path could commit a credential with no check in its loaded context — a grep for `secret` returned one hit on a path two of three branches never reach. - **`pc-run`** advertised diagnosing why a hook "never fires" but routed only from the *failure* path. When hooks never fire the manual run **succeeds**, so the request fell to the ambiguity default, reported green, and masked the cause. Two author judgment calls were overturned by auditors using measurement rather than assertion (`pc-run`'s "flows too small to extract" — the house already ships ~100-word branch files; `git-remotes`' weakest boundary target). One author claimed content was "already present verbatim" in a reference when the same pass had **just added it there** — outcome correct, reasoning not. Verify before deleting, not after. ### Systematic hazard for waves 3–4 **The Gotchas-ratio SUGGESTION fired three times purely from the denominator shrinking.** Clearing two ERRORs routinely hands you a new SUGGESTION on a section you never touched. Two authors caught it mid-flight and rebalanced by relocating a branch-specific Gotcha to its point of use; one correctly declined to pad. Expect it, and do not resolve it by deleting a Gotcha that earns its place. ### Collateral fixed this wave `skill-audit`'s own `references/body-discipline.md` used pre-retrofit `git-commits` as its worked negative example. Every figure was correct for v0.1.2 and none survived into v0.1.3 — so every wave-3 auditor would have read a rubric contradicted by the file it names. Worse, its row four called the secrets Gotcha a paraphrase FAIL, which is the exact reasoning that produced the `git-commits` credential hole. Fixed in `a2ebdaf`: the example is pinned to commit `5e23250` and marked not to be refreshed against `HEAD`, and the paraphrase rule now carries its missing precondition — *delete a restating Gotcha only when the surviving copy is reachable from every branch that needs it; relocate a multi-branch safety gate into the body rather than dropping it.* A sweep of both audit skills found no other stale example (`apm-workflow`, `git-orchestrate`, `gitea-orchestrate`, `apm-orchestrate` citations all verified exact). ### Issues filed from this wave - **#110** — `skill-size-check`'s `BOUNDARY_ARROW` regex cannot cross a `.`, so a boundary clause naming a dotted filename between "Not" and the arrow is invisible and misreported as *missing*. - **#111** — `validate-provenance.sh` has no way to declare a reference file house-authored; `source_keys: []` parses identically to a missing key, so the only alternatives to a permanent INFO are false provenance claims. - **#112** — cherry-pick and revert have three claimants (`git-branches` delegates to `git-history`; `git-commits` claims it and owns `references/cherry-pick.md`; `git-history` executes it). **Pre-existing**, held out of scope. The rebase half of the same tangle *was* resolved in `0fde892`. - **#113** — `rtk git` vs bare `git` is inconsistent across the plugin. Pre-existing; needs one normalising pass, not nine per-skill calls. Earlier gate defects remain open: #107, #108, #109. --- ## Remaining work **Wave 3 — `bin` / `core` / `lint` (8):** `research` (583), `prototype` (426), `diagnose` (body 1126 only), `agentsmd-author` (960), `agentsmd-audit` (944), `provider-adapter-author` (833), `vale-config` (657), `vale-run` (654) **Wave 4 — `kyberforge` (3):** `apm-workflow` (817), `forge` (648/1093), `apm-install` (514) **Sequencing:** resolve **#109 before wave 4** — `apm-workflow` is both the exemplar in that contradiction and an unretrofitted target. **Orchestrator close-out:** set `disable-model-invocation: true` on `caveman`; delete eval case `negative-neuledge` at `plugins/bin/evals/research/research/eval.yaml:30`; reduce `EXPECTED_DANGLING` to empty and drop the last probe from both `tests/test-adr0020-targets.sh` and `tests/test-skill-size-check.sh` once `research` lands; final `scripts/sync-plugin-content.sh --all`; `pre-commit run --all-files`; `bash tests/run-tests.sh --strict`; `pre-commit run --hook-stage pre-push --all-files` (prints 16); update the `AGENTS.md` gate counts again (currently "10 of 39 / 2 of 39", corrected in `afbeaff`). --- ## The agent contract — reuse verbatim **Author agents.** Hand each its own **measured** `skill-size-check` and Vale lines — `skill-author`'s improve flow (`references/improve.md` Step 1) stops dead without an improvement signal. Then: invoke `skill-author` → improve flow → `references/retrofit.md` (the ordered cut procedure) → run `/skill-audit` **inline, in its own context**. **Audit agents.** Fresh, not forked, one per skill. Tell them the deterministic gates already pass and their value is in what a script cannot check: behaviour loss, dispatch correctness, Gotcha paraphrase, description grammar as *rendered*, boundary-target quality, internal consistency. Give each the specific judgment call the author hedged on. **Repair agents.** Fresh again, handed the audit findings split into must-fix and judgment call, with the reasoning — not just the diffs. **Hard constraints in every prompt:** - **No git commands at all.** Parallel agents race on `.git/index`. The orchestrator commits. This deliberately overrides `skill-author` Step 4's commit-verification requirement. - **No worktree isolation** — `skill-author/SKILL.md:23` names worktree teardown as a live hazard to uncommitted drafts. - **No grandchild agents.** Same Gotcha. The clean-context recheck is the orchestrator's, per `forge` Step 3. - **No `scripts/sync-plugin-content.sh`** — `sync_dir` runs `rm -rf` before every copy and will delete directories other agents are writing. Orchestrator syncs once at wave end. - **Validate in place.** A skill audited outside its package prints `INFO boundary-target resolution DID NOT RUN` and exits 0 — reads as a pass, is not one. - **Edit only the target skill directory**, never the flat mirror. Report sibling needs; do not act on them. **Content rules:** one arrow per boundary target; never let a hyphenated skill name straddle a line in a `>`-folded scalar; composition notes go in the skill's **own** `README.md`. Any `references/` file added, removed, or renamed requires updating the skill `README.md` file table, `references/README.md`, and `references/sources.md` "Contributing files". **Exemplars:** `skill-author` (225/599), `agent-author` (224/616), `skill-audit` (239/581), `agent-audit` (250/691). **Budget:** ~1 audit agent per skill, and expect a repair round on most. Wave 2 ran 9 + 9 + 8 = 26 agents for 9 skills. ### Accidental benefit worth keeping Because the flat mirror is synced only at wave end, it still holds the **pre-retrofit** content while the audits run. Three auditors used it to diff real before/after rather than inferring what was lost — which is how the `git-submodules` regressions and the `git-remotes` redistribution audit were both settled on evidence. Keep syncing at wave end, not per skill.
Author
Collaborator

Wave 3 complete — bin / core / lint, 8 skills

Branch refactor/adr0020-skill-retrofit, 34 commits, clean tree, nothing pushed.

Measured state

Start After wave 1 After wave 2 After wave 3
Description FAILs (>400) 26 19 10 3
Body FAILs (>900, body-only) 9 7 2 1
Dangling routing targets 2 1 1 0
Kyberforge.CompositionNote 10 0 0 0
Preload tax 21,033 17,334 14,507 11,256 chars (~2,814 tokens)
tests/run-tests.sh --strict — 24/0 24/0 24 passed, 0 failed
pre-commit run --hook-stage pre-push --all-files — — — 16/16 pass

The preload tax is already under the 12,000-char success criterion, with wave 4 still outstanding — the three remaining FAILs are 1,979 chars between them and should land near 750.

Every remaining ERROR is in kyberforge: apm-workflow (817), forge (648 + 1,093-word body), apm-install (514). That is wave 4 exactly.

Per skill

Skill Description Body Commit
agentsmd-author 960 → 244 470 → 452 e42c055
agentsmd-audit 944 → 322 345 → 341 f0526b3
provider-adapter-author 833 → 239 370 → 387 c59e4bf
vale-config 657 → 244 504 → 342 45cd260
vale-run 654 → 294 698 → 642 ff187ef
research 583 → 247 854 → 593 ee6b040
prototype 426 → 286 467 (untouched) f03bfa8
diagnose 290 → 220 1,126 → 808 00c1e6b

Plus 7e80c09 (resolves #109 and its collateral in gitea-workflow) and d8dfba9 (AGENTS.md counts).

What the two-layer loop bought, again

8 of 8 skills passed every deterministic gate and then failed the clean-context audit, on roughly 20 must-fix findings. Wave 2 was 8 of 9. The ratio is not improving, which is the point: the audit layer is not catching sloppiness, it is catching a defect class the scripts structurally cannot see.

Wave 2's characteristic defect repeated exactly — a rule surviving redistribution as text while losing its binding force — but wave 3 sharpened it into two distinguishable shapes:

  1. Demotion. The rule survives on a path that is always loaded, but as description rather than imperative. agentsmd-author's provider-file prohibition became a justification clause on the false branch of a conditional, so the common path never read a sentence binding it. With Write and Edit granted and the bundled validator's own remediation text saying "Move provider-agnostic content into AGENTS.md", a size FAIL actively invited the prohibited edit.
  2. Stranding. The rule keeps its imperative force but moves behind a trigger the needing path never fires. diagnose put its loop-quality bar behind "if you do not already have a signal" — inverted, so the most common entry skipped it. vale-run's CI-failure answer moved into a file no CI-entered invocation loads.

Three findings were worse than quality defects:

  • research silently broke the provenance chain. The references/file-format.md pointer was dropped at the point of use, and validate-provenance.sh short-circuits when the Status field is absent — rd_status != "\extracted`"→continue, reporting nothing. A sources.md` written from the retrofitted step 6 was unparseable and check 8 passed anyway.
  • vale-config shipped a false statement. Gotcha 1's kernel lost the qualifier that only package styles need fetching, contradicting the same file twice. An agent adding a custom style would have added a spurious Packages entry and broken vale sync outright.
  • provider-adapter-author asserted behaviour its validator does not have — see #115.

On scoping: two of my own briefs were wrong

Worth recording, because both were orchestrator errors that the agents surfaced rather than absorbed.

diagnose. I scoped the description out while mandating a boundary clause. Adding one to a 290-char description cannot land under 400 — it landed at 379, twenty-one characters from a hard FAIL. The auditor also showed the capability chain was inaccurate, naming "minimise" as a phase that does not exist while omitting the phase the body itself calls "This is the skill". Final: 220.

provider-adapter-author. I claimed a false statement had propagated into references/provider-matrix.md. The repair agent checked, found it had not, and declined to manufacture an edit to satisfy the instruction. Correct call.

A third correction went the other way: agentsmd-audit's author dropped a governance trigger against a measured budget of "~9 spare chars". The real cost was ~49 — and 250 is the SUGGESTION tier, not a ceiling. The trade was never "restore it or bust the budget."

The .claude/skills/ A/B went vacuous the moment the corpus got healthy

tests/test-adr0020-targets.sh asserts that a deployed tree does not leak into the resolver's universe, and built its bait by deriving it from the live dangling set. Fixing the last dangling target left nothing to deploy, and the test said so out loud rather than passing — it had been written with that failure message in place.

Rebuilt on an explicit synthetic target, so the A/B distinguishes something on every run regardless of corpus health. The old form depended on the corpus staying broken to prove anything.

Both probe loops are removed, not emptied — a loop over an empty list is an assertion-free result counted in the totals, which is the vacuous-pass shape those files explicitly reject.

Issues filed this wave

  • #114 — prototype's LOGIC.md/UI.md sit at the skill root instead of references/; held out of the wave deliberately.
  • #115 — validate-adapter.sh's --no-import-syntax is a no-op. Both branches reduce to the same expression, so the flag defends nothing and omitting it costs nothing.
  • #116 — research mandated subagent fan-out its allowed-tools never granted. Resolved conservatively (serial), but the real question is whether fan-out belongs to an agent rather than a skill.
  • #109 closed — with two corrections to it, below.
  • #110 updated — two more independent hits this wave, three total, two different workarounds.

#109's premise was half wrong

There is no Vale conflict. PaddingPhrase.yml matches only see references?/? for (more )?(info|information|details) and never fired on apm-workflow's closing line. The contradiction was purely the prose rule that LLM auditors apply literally. One file changed, no Vale rule touched.

Relatedly, one agent hypothesised that the Vale prefilter never fires on folded-scalar descriptions and is a vacuous green. Tested and false — bare vale does miss them, but vale-wrap.sh exists to fix exactly that and flattens the scalar in a scratch copy first. Planted violation: bare vale 0 errors, wrapper 1 error. The gate is real.

Contract additions for wave 4

Everything from the wave-2 contract still holds. Four things to add:

  1. pre-commit run --files shells out to git and can stash — it races parallel agents on .git/index just as git itself does. One wave-3 agent noticed and ran validate.sh / validate-provenance.sh / vale-wrap.sh directly instead. Put this in every prompt; only one of eight worked it out unprompted.
  2. Verify the fold programmatically. Every agent that checked yaml.safe_load + re.findall(r'[a-z]- [a-z]', desc) reported it; none found a break. Cheap, and it retires the gitea-labels- milestones failure mode by construction.
  3. State that 250 is advisory and 400 blocks. Two agents made real trades against a misunderstood ceiling.
  4. gotcha_stats() slices from after the ## Gotchas heading. A naive count includes the heading's two words and reads ~0.6pp tighter than the gate is.

Wave 4 — kyberforge (3)

apm-workflow (817), forge (648 / 1,093), apm-install (514 / Gotchas 47%).

#109 is resolved, so apm-workflow no longer has to guess which convention to follow — that was the sequencing reason to do it first, and it is done.

Note forge and apm-workflow are both used by this retrofit process. Retrofitting the tooling that runs the retrofit is the one place worth pausing before dispatching.

Orchestrator close-out still outstanding

  • caveman — set disable-model-invocation: true (zero inbound routes; still shows a 263-char description and no boundary clause)
  • skill-size-check.sh:458-460 — the ARROW_BOUNDARY carve-out is live (sole extractor for three real targets in kyberforge's audit skills) but its stated justification is retired, since diagnose's process chain is gone. Update the comment; do not delete the branch.
  • Final scripts/sync-plugin-content.sh --all, pre-commit run --all-files, bash tests/run-tests.sh --strict, pre-commit run --hook-stage pre-push --all-files
  • Update AGENTS.md counts once more
## Wave 3 complete — `bin` / `core` / `lint`, 8 skills Branch `refactor/adr0020-skill-retrofit`, 34 commits, clean tree, **nothing pushed**. ### Measured state | | Start | After wave 1 | After wave 2 | **After wave 3** | |---|---|---|---|---| | Description FAILs (>400) | 26 | 19 | 10 | **3** | | Body FAILs (>900, body-only) | 9 | 7 | 2 | **1** | | Dangling routing targets | 2 | 1 | 1 | **0** | | `Kyberforge.CompositionNote` | 10 | 0 | 0 | **0** | | Preload tax | 21,033 | 17,334 | 14,507 | **11,256 chars** (~2,814 tokens) | | `tests/run-tests.sh --strict` | — | 24/0 | 24/0 | **24 passed, 0 failed** | | `pre-commit run --hook-stage pre-push --all-files` | — | — | — | **16/16 pass** | **The preload tax is already under the 12,000-char success criterion**, with wave 4 still outstanding — the three remaining FAILs are 1,979 chars between them and should land near 750. **Every remaining ERROR is in `kyberforge`:** `apm-workflow` (817), `forge` (648 + 1,093-word body), `apm-install` (514). That is wave 4 exactly. ### Per skill | Skill | Description | Body | Commit | |---|---|---|---| | `agentsmd-author` | 960 → **244** | 470 → 452 | `e42c055` | | `agentsmd-audit` | 944 → **322** | 345 → 341 | `f0526b3` | | `provider-adapter-author` | 833 → **239** | 370 → 387 | `c59e4bf` | | `vale-config` | 657 → **244** | 504 → 342 | `45cd260` | | `vale-run` | 654 → **294** | 698 → 642 | `ff187ef` | | `research` | 583 → **247** | 854 → 593 | `ee6b040` | | `prototype` | 426 → **286** | 467 (untouched) | `f03bfa8` | | `diagnose` | 290 → **220** | 1,126 → 808 | `00c1e6b` | Plus `7e80c09` (resolves #109 and its collateral in `gitea-workflow`) and `d8dfba9` (AGENTS.md counts). ### What the two-layer loop bought, again **8 of 8 skills passed every deterministic gate and then failed the clean-context audit**, on roughly 20 must-fix findings. Wave 2 was 8 of 9. The ratio is not improving, which is the point: the audit layer is not catching sloppiness, it is catching a defect class the scripts structurally cannot see. Wave 2's characteristic defect repeated exactly — **a rule surviving redistribution as text while losing its binding force** — but wave 3 sharpened it into two distinguishable shapes: 1. **Demotion.** The rule survives on a path that is always loaded, but as description rather than imperative. `agentsmd-author`'s provider-file prohibition became a justification clause on the *false* branch of a conditional, so the common path never read a sentence binding it. With `Write` and `Edit` granted and the bundled validator's own remediation text saying *"Move provider-agnostic content into AGENTS.md"*, a size FAIL actively invited the prohibited edit. 2. **Stranding.** The rule keeps its imperative force but moves behind a trigger the needing path never fires. `diagnose` put its loop-quality bar behind *"if you do **not** already have a signal"* — inverted, so the most common entry skipped it. `vale-run`'s CI-failure answer moved into a file no CI-entered invocation loads. Three findings were worse than quality defects: - **`research` silently broke the provenance chain.** The `references/file-format.md` pointer was dropped at the point of use, and `validate-provenance.sh` **short-circuits when the `Status` field is absent** — `rd_status != "\`extracted\`"` → `continue`, reporting nothing. A `sources.md` written from the retrofitted step 6 was unparseable and check 8 passed anyway. - **`vale-config` shipped a false statement.** Gotcha 1's kernel lost the qualifier that only *package* styles need fetching, contradicting the same file twice. An agent adding a custom style would have added a spurious `Packages` entry and broken `vale sync` outright. - **`provider-adapter-author` asserted behaviour its validator does not have** — see #115. ### On scoping: two of my own briefs were wrong Worth recording, because both were orchestrator errors that the agents surfaced rather than absorbed. **`diagnose`.** I scoped the description out while mandating a boundary clause. Adding one to a 290-char description cannot land under 400 — it landed at 379, twenty-one characters from a hard FAIL. The auditor also showed the capability chain was *inaccurate*, naming "minimise" as a phase that does not exist while omitting the phase the body itself calls "This is the skill". Final: 220. **`provider-adapter-author`.** I claimed a false statement had propagated into `references/provider-matrix.md`. The repair agent checked, found it had not, and declined to manufacture an edit to satisfy the instruction. Correct call. A third correction went the other way: `agentsmd-audit`'s author dropped a governance trigger against a measured budget of "~9 spare chars". The real cost was ~49 — and 250 is the SUGGESTION tier, not a ceiling. The trade was never "restore it or bust the budget." ### The `.claude/skills/` A/B went vacuous the moment the corpus got healthy `tests/test-adr0020-targets.sh` asserts that a deployed tree does not leak into the resolver's universe, and built its bait by **deriving** it from the live dangling set. Fixing the last dangling target left nothing to deploy, and the test said so out loud rather than passing — it had been written with that failure message in place. Rebuilt on an explicit synthetic target, so the A/B distinguishes something on every run regardless of corpus health. The old form depended on the corpus staying broken to prove anything. Both probe loops are **removed, not emptied** — a loop over an empty list is an assertion-free result counted in the totals, which is the vacuous-pass shape those files explicitly reject. ### Issues filed this wave - **#114** — `prototype`'s `LOGIC.md`/`UI.md` sit at the skill root instead of `references/`; held out of the wave deliberately. - **#115** — `validate-adapter.sh`'s `--no-import-syntax` is a no-op. Both branches reduce to the same expression, so the flag defends nothing *and* omitting it costs nothing. - **#116** — `research` mandated subagent fan-out its `allowed-tools` never granted. Resolved conservatively (serial), but the real question is whether fan-out belongs to an *agent* rather than a skill. - **#109 closed** — with two corrections to it, below. - **#110 updated** — two more independent hits this wave, three total, two different workarounds. ### #109's premise was half wrong There is **no Vale conflict**. `PaddingPhrase.yml` matches only `see references?/? for (more )?(info|information|details)` and never fired on `apm-workflow`'s closing line. The contradiction was purely the prose rule that LLM auditors apply literally. One file changed, no Vale rule touched. Relatedly, one agent hypothesised that the Vale prefilter never fires on folded-scalar descriptions and is a vacuous green. **Tested and false** — bare `vale` does miss them, but `vale-wrap.sh` exists to fix exactly that and flattens the scalar in a scratch copy first. Planted violation: bare `vale` 0 errors, wrapper 1 error. The gate is real. ### Contract additions for wave 4 Everything from the wave-2 contract still holds. Four things to add: 1. **`pre-commit run --files` shells out to git and can stash** — it races parallel agents on `.git/index` just as `git` itself does. One wave-3 agent noticed and ran `validate.sh` / `validate-provenance.sh` / `vale-wrap.sh` directly instead. Put this in every prompt; only one of eight worked it out unprompted. 2. **Verify the fold programmatically.** Every agent that checked `yaml.safe_load` + `re.findall(r'[a-z]- [a-z]', desc)` reported it; none found a break. Cheap, and it retires the `gitea-labels- milestones` failure mode by construction. 3. **State that 250 is advisory and 400 blocks.** Two agents made real trades against a misunderstood ceiling. 4. **`gotcha_stats()` slices from *after* the `## Gotchas` heading.** A naive count includes the heading's two words and reads ~0.6pp tighter than the gate is. ### Wave 4 — `kyberforge` (3) `apm-workflow` (817), `forge` (648 / 1,093), `apm-install` (514 / Gotchas 47%). **#109 is resolved, so `apm-workflow` no longer has to guess** which convention to follow — that was the sequencing reason to do it first, and it is done. Note `forge` and `apm-workflow` are both *used by* this retrofit process. Retrofitting the tooling that runs the retrofit is the one place worth pausing before dispatching. ### Orchestrator close-out still outstanding - `caveman` — set `disable-model-invocation: true` (zero inbound routes; still shows a 263-char description and no boundary clause) - `skill-size-check.sh:458-460` — the `ARROW_BOUNDARY` carve-out is **live** (sole extractor for three real targets in kyberforge's audit skills) but its *stated justification* is retired, since `diagnose`'s process chain is gone. Update the comment; do not delete the branch. - Final `scripts/sync-plugin-content.sh --all`, `pre-commit run --all-files`, `bash tests/run-tests.sh --strict`, `pre-commit run --hook-stage pre-push --all-files` - Update `AGENTS.md` counts once more
Author
Collaborator

Wave 4 pre-flight — two findings that change the brief

1. apm-workflow is cited by hard numbers in live guidance, and wave 4 invalidates all of them

Every figure below is exact as of now — verified, not recalled:

Location Citation Currently
skill-audit/references/body-discipline.md:83 "the reference shape in this repo is apm-workflow: a 421-word body dispatching to roughly 3,000 words… whole-file count is 554" body 421, whole-file 554, references 3,006 ✅
skill-author/references/contract.md:123 "Exemplar: the apm-workflow skill — a 421-word body dispatching to 3,006 words of references" ✅

Wave 4 breaks both. The description goes 817 → ~250, which moves the whole-file count directly; and the Gotchas ratio (253/421 = 60%, the worst in the corpus) can only be fixed by moving content out of the body, which moves the 421.

This is the same rot this retrofit already hit once — body-discipline.md's worked negative example cited a git-commits that the wave-2 retrofit had rewritten, and every figure in it was correct for a version that no longer existed. Fixed in a2ebdaf by pinning to a commit SHA.

The fix here is different, and the difference matters. The git-commits case was a negative example: it had to stay pinned to the broken snapshot, because the whole point was the defect. apm-workflow is a positive exemplar — and after retrofit it will be a better one, not a worse one. So these two citations must be refreshed to the new measured figures, not pinned to a stale snapshot.

Mandatory collateral for the apm-workflow retrofit: re-measure body words, whole-file words, and the references/ total, then update both citations in the same commit. Measure; do not estimate.

2. ADR-0020 needs no action — and must not be touched

ADR-0020 also cites apm-workflow by number (:75) and by line range (:168 cites SKILL.md:33-41), and :48 uses its description as a defect example ("re-quoting the same six as user phrasings") that the retrofit will make false.

None of that needs fixing, because the ADR pins itself in its own opening paragraph:

Every file:line citation in this ADR is against the base commit the decision was taken on, f9b919d7e3bd5e6b51fbdf88b32ace0438b313e0, not against current HEAD… Use git show f9b919d:<path> to follow one.

An ADR records evidence as it stood when the decision was taken. Tell the wave-4 agents explicitly not to "helpfully" update it — refreshing an ADR's figures to match later reality destroys the record of why the decision was made. This is the one file in the blast radius where the correct action is to leave stale-looking numbers alone.

3. The "retrofitting the tooling that runs the retrofit" concern was overstated

I flagged this at the end of wave 3. Checked, and it does not hold: skill-author and skill-audit only mention wave-4 skills in prose — a hazard note about /forge's outer loop (skill-author/SKILL.md:23), a /apm-workflow configure pointer in the create flow (references/create.md:42, which retrofits do not take), and the exemplar citation above. No wave-4 skill is invoked by the retrofit loop. Wave 4 can be dispatched exactly like waves 2 and 3.

Also done in this pass

scripts/skill-size-check.sh's bare-arrow carve-out comment justified itself with diagnose's fix -> regression-test process chain — which wave 3 cut. The gate is now unexercised corpus-wide. The comment is corrected rather than the branch deleted: it guards prose no one has written yet, and the bare-arrow rule it sits on is the sole extractor for three real targets in kyberforge's audit skills, all written unbackticked. Unexercised is not the same as unnecessary.

## Wave 4 pre-flight — two findings that change the brief ### 1. `apm-workflow` is cited by hard numbers in live guidance, and wave 4 invalidates all of them Every figure below is **exact as of now** — verified, not recalled: | Location | Citation | Currently | |---|---|---| | `skill-audit/references/body-discipline.md:83` | "the reference shape in this repo is `apm-workflow`: a **421-word body** dispatching to roughly 3,000 words… whole-file count is 554" | body 421, whole-file 554, references 3,006 ✅ | | `skill-author/references/contract.md:123` | "Exemplar: the `apm-workflow` skill — a **421-word body** dispatching to 3,006 words of references" | ✅ | Wave 4 breaks both. The description goes 817 → ~250, which moves the whole-file count directly; and the Gotchas ratio (253/421 = **60%**, the worst in the corpus) can only be fixed by moving content *out of the body*, which moves the 421. This is the same rot this retrofit already hit once — `body-discipline.md`'s worked negative example cited a `git-commits` that the wave-2 retrofit had rewritten, and every figure in it was correct for a version that no longer existed. Fixed in `a2ebdaf` by pinning to a commit SHA. **The fix here is different, and the difference matters.** The `git-commits` case was a *negative* example: it had to stay pinned to the broken snapshot, because the whole point was the defect. `apm-workflow` is a *positive* exemplar — and after retrofit it will be a **better** one, not a worse one. So these two citations must be **refreshed to the new measured figures**, not pinned to a stale snapshot. **Mandatory collateral for the `apm-workflow` retrofit:** re-measure body words, whole-file words, and the `references/` total, then update both citations in the same commit. Measure; do not estimate. ### 2. ADR-0020 needs no action — and must not be touched ADR-0020 also cites `apm-workflow` by number (`:75`) and by line range (`:168` cites `SKILL.md:33-41`), and `:48` uses its description as a *defect* example ("re-quoting the same six as user phrasings") that the retrofit will make false. None of that needs fixing, because the ADR pins itself in its own opening paragraph: > Every `file:line` citation in this ADR is against the base commit the decision was taken on, `f9b919d7e3bd5e6b51fbdf88b32ace0438b313e0`, not against current `HEAD`… Use `git show f9b919d:<path>` to follow one. An ADR records evidence as it stood when the decision was taken. **Tell the wave-4 agents explicitly not to "helpfully" update it** — refreshing an ADR's figures to match later reality destroys the record of why the decision was made. This is the one file in the blast radius where the correct action is to leave stale-looking numbers alone. ### 3. The "retrofitting the tooling that runs the retrofit" concern was overstated I flagged this at the end of wave 3. Checked, and it does not hold: `skill-author` and `skill-audit` only *mention* wave-4 skills in prose — a hazard note about `/forge`'s outer loop (`skill-author/SKILL.md:23`), a `/apm-workflow configure` pointer in the **create** flow (`references/create.md:42`, which retrofits do not take), and the exemplar citation above. **No wave-4 skill is invoked by the retrofit loop.** Wave 4 can be dispatched exactly like waves 2 and 3. ### Also done in this pass `scripts/skill-size-check.sh`'s bare-arrow carve-out comment justified itself with `diagnose`'s `fix -> regression-test` process chain — which wave 3 cut. The gate is now unexercised corpus-wide. The comment is corrected rather than the branch deleted: it guards prose no one has written yet, and the bare-arrow rule it sits on is the sole extractor for three real targets in kyberforge's audit skills, all written unbackticked. Unexercised is not the same as unnecessary.
Author
Collaborator

Wave 4 complete — kyberforge, and the corpus is clean

Skill Description Body Gotchas Commit
apm-workflow 817 → 324 421 → 237 6 → 2 1e4aab5
forge 648 → 387 1093 → 541 2 → 2 4aab9d3
apm-install 514 → 213 350 → 397 5 → 2 aa982b9

Corpus state, measured

Start W1 W2 W3 W4
Description FAILs (>400) 26 19 10 3 0
Body FAILs (>900, body-only) 9 7 2 1 0
Dangling routing targets 2 1 1 0 0
Kyberforge.CompositionNote 10 0 0 0 0
Preload tax 21,033 17,334 14,507 11,256 10,201 chars (~2,550 tokens)

51% cut, under the 12,000-char success criterion. pre-commit run --all-files green on both skill-size-check and vale-audit-prefilter-skill; tests/run-tests.sh --strict 24/0; pre-commit run --hook-stage pre-push --all-files 16/16.

forge was the last body FAIL in the corpus. It was not trimmed to fit — it routes four artifact types a single invocation classifies between, so it got a dispatch table plus common gates, with routes in references/author-routes.md, apm-routes.md and version-bump.md.

The two-layer loop again justified itself, and the defect class changed

All three skills passed every deterministic gate. All three then failed the clean-context audit — 13 must-fix findings total. Waves 1–3 ran 8/9, 8/8, 8/8; wave 4 is 3/3. The ratio has never improved, which is the finding: the audit catches something scripts structurally cannot.

What changed this wave is the shape. Waves 2–3 were dominated by lost content — rules demoted or stranded when they moved. Wave 4 was dominated by invented content:

  • apm-install claimed the piped installer "ignores" VERSION and GITHUB_URL. The installer reads both from the environment and its own usage header documents VERSION working through the pipe.
  • apm-workflow claimed apm checks type: "going forward". No source supports it; agent-primitive-schema.md:54 explicitly calls undocumented constraints "authoring guidance rather than enforced checks".
  • forge claimed apm audit "already runs inside apm-workflow's own flow" and told the agent to confirm it ran clean. It never runs on those routes — and that was the routes' only completion check.
  • forge's provenance asserted a source contributed to a file it had no hand in.

Cutting text is safe. Rewriting text to be shorter is where invention enters, and every gate here is a counter — none reads for truth. One agent also reported folding a Gotcha into "restructured Step 1 / Step 2 headings" that already existed pre-retrofit; only the audit's diff against the unsynced mirror caught that the compensating structure was never built.

Stranding still appeared once, and instructively: apm-workflow moved the experimental enable registries precondition into configure.md while keeping a configure-only rule in the body — the placement rule applied exactly backwards. compile.md publishes to a registry and install.md resolves through one; neither points at configure.md, and declaring a registry without the precondition is a silent no-op.

Every repair got a fresh verification pass. Two rewrote factual claims, so both were re-checked against primary sources — one auditor fetched the 782-line installer and confirmed the new text line by line.

Collateral landed

  • body-discipline.md:83 and contract.md:123 cite apm-workflow by hard number as the exemplar dispatch shape. Refreshed to measured figures (body 421→237, whole-file 554→304, references 3,006→3,222) in 264a5db. The quoted closing line had also changed and is now quoted with an ellipsis. These are positive exemplars in live guidance, so they are refreshed — the opposite treatment from the git-commits negative example in the same file, pinned to 5e23250 precisely so it keeps describing pre-retrofit state.
  • ADR-0020 deliberately untouched. It self-pins every citation to base commit f9b919d; refreshing its figures would destroy the record of what the decision was taken against.
  • AGENTS.md session rule rewritten (b25412b): nothing is grandfathered any more, so the gates bite on first commit.
  • 79c6071 — I broke the shared-resolver byte-identity invariant in 915eb09 by editing the carve-out comment in one of three copies, and reported the suite green without re-running it. tests/test-adr0020-contract.sh caught it. Restored; comment-only, no behaviour change.

Filed this wave

  • #117 — the Kyberforge Vale style is scoped [**/SKILL.md], so every references/ file corpus-wide is unlinted. This compounds with ADR-0020: the contract's remedy is to move prose into references/, i.e. out of the prose gate. forge moved ~900 words that way and the moved prose carried a real SentenceOpenerThereIs error. vale-wrap.sh reports in 0 files, which reads as clean.
  • #118 — validate-provenance.sh checks slug existence and back-references, never whether a stated contribution is true. forge's retrofit asserted one that never happened and the script exited 0.
  • #119 — installation.md omits apm runtime status and apm runtime remove, which exist in apm 0.28.0. The skill is right and the doc is stale; flagged so nobody "corrects" the skill to match it.

Not done, deliberately

No plugin version bump. configure.md's repo policy says a substantive .apm/ change bumps the package version, but no wave on this branch has bumped one across 41 commits. Bumping kyberforge alone would be inconsistent; a single release bump covering all six plugins belongs at wave-5 close-out. Flagging rather than deciding unilaterally.

Remaining — wave 5, orchestrator only

  • caveman: set disable-model-invocation: true (zero inbound routes; still 263 chars, no boundary clause).
  • Decide the release bump above.
  • The branch has 41 commits and has never been pushed.
## Wave 4 complete — kyberforge, and the corpus is clean | Skill | Description | Body | Gotchas | Commit | |---|---|---|---|---| | `apm-workflow` | 817 → **324** | 421 → 237 | 6 → 2 | `1e4aab5` | | `forge` | 648 → **387** | 1093 → **541** | 2 → 2 | `4aab9d3` | | `apm-install` | 514 → **213** | 350 → 397 | 5 → 2 | `aa982b9` | ### Corpus state, measured | | Start | W1 | W2 | W3 | **W4** | |---|---|---|---|---|---| | Description FAILs (>400) | 26 | 19 | 10 | 3 | **0** | | Body FAILs (>900, body-only) | 9 | 7 | 2 | 1 | **0** | | Dangling routing targets | 2 | 1 | 1 | 0 | **0** | | `Kyberforge.CompositionNote` | 10 | 0 | 0 | 0 | **0** | | Preload tax | 21,033 | 17,334 | 14,507 | 11,256 | **10,201 chars** (~2,550 tokens) | **51% cut, under the 12,000-char success criterion.** `pre-commit run --all-files` green on both `skill-size-check` and `vale-audit-prefilter-skill`; `tests/run-tests.sh --strict` 24/0; `pre-commit run --hook-stage pre-push --all-files` 16/16. `forge` was the last body FAIL in the corpus. It was not trimmed to fit — it routes four artifact types a single invocation classifies between, so it got a dispatch table plus common gates, with routes in `references/author-routes.md`, `apm-routes.md` and `version-bump.md`. ### The two-layer loop again justified itself, and the defect class changed All three skills passed every deterministic gate. **All three then failed the clean-context audit** — 13 must-fix findings total. Waves 1–3 ran 8/9, 8/8, 8/8; wave 4 is 3/3. The ratio has never improved, which is the finding: the audit catches something scripts structurally cannot. What changed this wave is the *shape*. Waves 2–3 were dominated by **lost** content — rules demoted or stranded when they moved. Wave 4 was dominated by **invented** content: - `apm-install` claimed the piped installer "ignores" `VERSION` and `GITHUB_URL`. The installer reads both from the environment and its own usage header documents `VERSION` working through the pipe. - `apm-workflow` claimed apm checks `type:` "going forward". No source supports it; `agent-primitive-schema.md:54` explicitly calls undocumented constraints "authoring guidance rather than enforced checks". - `forge` claimed `apm audit` "already runs inside apm-workflow's own flow" and told the agent to confirm it ran clean. It never runs on those routes — and that was the routes' only completion check. - `forge`'s provenance asserted a source contributed to a file it had no hand in. Cutting text is safe. **Rewriting text to be shorter is where invention enters**, and every gate here is a counter — none reads for truth. One agent also reported folding a Gotcha into "restructured Step 1 / Step 2 headings" that already existed pre-retrofit; only the audit's diff against the unsynced mirror caught that the compensating structure was never built. Stranding still appeared once, and instructively: `apm-workflow` moved the `experimental enable registries` precondition into `configure.md` while keeping a configure-only rule in the body — the placement rule applied exactly backwards. `compile.md` publishes to a registry and `install.md` resolves through one; neither points at `configure.md`, and declaring a registry without the precondition is a silent no-op. Every repair got a fresh verification pass. Two rewrote factual claims, so both were re-checked against primary sources — one auditor fetched the 782-line installer and confirmed the new text line by line. ### Collateral landed - `body-discipline.md:83` and `contract.md:123` cite `apm-workflow` by hard number as the exemplar dispatch shape. Refreshed to measured figures (body 421→237, whole-file 554→304, references 3,006→3,222) in `264a5db`. The quoted closing line had also changed and is now quoted with an ellipsis. These are *positive* exemplars in live guidance, so they are refreshed — the opposite treatment from the `git-commits` negative example in the same file, pinned to `5e23250` precisely so it keeps describing pre-retrofit state. - **ADR-0020 deliberately untouched.** It self-pins every citation to base commit `f9b919d`; refreshing its figures would destroy the record of what the decision was taken against. - `AGENTS.md` session rule rewritten (`b25412b`): nothing is grandfathered any more, so the gates bite on first commit. - `79c6071` — I broke the shared-resolver byte-identity invariant in `915eb09` by editing the carve-out comment in one of three copies, and reported the suite green without re-running it. `tests/test-adr0020-contract.sh` caught it. Restored; comment-only, no behaviour change. ### Filed this wave - **#117** — the `Kyberforge` Vale style is scoped `[**/SKILL.md]`, so every `references/` file corpus-wide is unlinted. This compounds with ADR-0020: the contract's remedy is to move prose *into* `references/`, i.e. out of the prose gate. `forge` moved ~900 words that way and the moved prose carried a real `SentenceOpenerThereIs` error. `vale-wrap.sh` reports `in 0 files`, which reads as clean. - **#118** — `validate-provenance.sh` checks slug existence and back-references, never whether a stated contribution is true. `forge`'s retrofit asserted one that never happened and the script exited 0. - **#119** — `installation.md` omits `apm runtime status` and `apm runtime remove`, which exist in apm 0.28.0. The skill is right and the doc is stale; flagged so nobody "corrects" the skill to match it. ### Not done, deliberately **No plugin version bump.** `configure.md`'s repo policy says a substantive `.apm/` change bumps the package version, but no wave on this branch has bumped one across 41 commits. Bumping `kyberforge` alone would be inconsistent; a single release bump covering all six plugins belongs at wave-5 close-out. Flagging rather than deciding unilaterally. ### Remaining — wave 5, orchestrator only - `caveman`: set `disable-model-invocation: true` (zero inbound routes; still 263 chars, no boundary clause). - Decide the release bump above. - The branch has **41 commits and has never been pushed.**
Author
Collaborator

Wave 5 — caveman flagged, and a correction to the wave-4 figure

Correction

The wave-4 comment reported the preload tax as 10,201 chars. That measurement summed all 39 skills, but zoom-out already carries disable-model-invocation: true and is therefore withheld from the model-visible listing — it was never being preloaded. The correct post-wave-4 figure is 10,002 chars across 38 model-visible skills. The conclusion is unchanged (well under the 12,000 criterion), but the number was wrong.

caveman

bdff6fd sets disable-model-invocation: true on caveman.

The plan chose it on the criterion "zero inbound skill routes", which is necessary — the flag hard-blocks the Skill tool, so any Call \caveman`step elsewhere would break the moment it lands. Re-verified across all 39 descriptions and bodies after four waves of rewrites: every mention is documentation, research notes orapm.lock.yaml`. Nothing routes to it.

But that criterion is not the interesting one. caveman's description was almost entirely a trigger list — "caveman mode", "talk like caveman", "use caveman", "less tokens", "be brief" — and "be brief" is a false-positive magnet. A user asking for brevity wants short answers, not dropped articles and Respond terse like smart caveman. Withholding the skill from the router deletes that trigger. The 287 characters saved are a side effect, not the reason. Decision taken explicitly by the repo owner: caveman is to be user-invoked only.

The description is rewritten as human-facing text — it now states how to turn the mode on and off, rather than listing phrases for a reader that can no longer see it. That is what a hand-invoked skill's description is for once the router cannot read it.

Two SUGGESTIONs remain on it and are not to be fixed: the 280-char length and "description has no boundary clause". Both are #108 — the boundary check fires on hand-invoked skills, which ADR-0020 contractually exempts. Neither the length target nor the boundary clause means anything for a skill that is not in the routing surface.

Final state

Start Now
Description FAILs (>400) 26 0
Body FAILs (>900, body-only) 9 0
Dangling routing targets 2 0
Kyberforge.CompositionNote 10 0
Preload tax 21,033 9,732 chars (~2,433 tokens), 37 model-visible skills

54% cut. tests/run-tests.sh --strict 24/0; pre-commit run --all-files no failures; pre-commit run --hook-stage pre-push --all-files 16/16.

Still open on this issue

  1. The release bump. No wave has bumped a plugin version across 42 commits, though configure.md's repo policy says a substantive .apm/ change should. Recommendation stands: one bump covering all six plugins, not kyberforge alone. Needs a decision.
  2. The branch has never been pushed. 42 commits, five waves, existing only on one machine.

Gate defects found along the way and filed separately: #107, #108, #110, #111, #117, #118, #119. Content debt: #112, #113, #114, #115, #116, #119. None blocks closing this issue; all were held out of the waves deliberately rather than improvised mid-flight.

## Wave 5 — caveman flagged, and a correction to the wave-4 figure ### Correction The wave-4 comment reported the preload tax as **10,201 chars**. That measurement summed all 39 skills, but `zoom-out` already carries `disable-model-invocation: true` and is therefore withheld from the model-visible listing — it was never being preloaded. The correct post-wave-4 figure is **10,002 chars across 38 model-visible skills**. The conclusion is unchanged (well under the 12,000 criterion), but the number was wrong. ### caveman `bdff6fd` sets `disable-model-invocation: true` on `caveman`. The plan chose it on the criterion "zero inbound skill routes", which is necessary — the flag hard-blocks the Skill tool, so any `Call \`caveman\`` step elsewhere would break the moment it lands. Re-verified across all 39 descriptions and bodies after four waves of rewrites: every mention is documentation, research notes or `apm.lock.yaml`. Nothing routes to it. But that criterion is not the interesting one. `caveman`'s description was almost entirely a trigger list — `"caveman mode"`, `"talk like caveman"`, `"use caveman"`, `"less tokens"`, `"be brief"` — and **`"be brief"` is a false-positive magnet**. A user asking for brevity wants short answers, not dropped articles and `Respond terse like smart caveman`. Withholding the skill from the router deletes that trigger. The 287 characters saved are a side effect, not the reason. Decision taken explicitly by the repo owner: caveman is to be user-invoked only. The description is rewritten as human-facing text — it now states how to turn the mode on and off, rather than listing phrases for a reader that can no longer see it. That is what a hand-invoked skill's description is for once the router cannot read it. **Two SUGGESTIONs remain on it and are not to be fixed**: the 280-char length and "description has no boundary clause". Both are #108 — the boundary check fires on hand-invoked skills, which ADR-0020 contractually exempts. Neither the length target nor the boundary clause means anything for a skill that is not in the routing surface. ### Final state | | Start | **Now** | |---|---|---| | Description FAILs (>400) | 26 | **0** | | Body FAILs (>900, body-only) | 9 | **0** | | Dangling routing targets | 2 | **0** | | `Kyberforge.CompositionNote` | 10 | **0** | | Preload tax | 21,033 | **9,732 chars** (~2,433 tokens), 37 model-visible skills | **54% cut.** `tests/run-tests.sh --strict` 24/0; `pre-commit run --all-files` no failures; `pre-commit run --hook-stage pre-push --all-files` 16/16. ### Still open on this issue 1. **The release bump.** No wave has bumped a plugin version across 42 commits, though `configure.md`'s repo policy says a substantive `.apm/` change should. Recommendation stands: one bump covering all six plugins, not `kyberforge` alone. Needs a decision. 2. **The branch has never been pushed.** 42 commits, five waves, existing only on one machine. Gate defects found along the way and filed separately: #107, #108, #110, #111, #117, #118, #119. Content debt: #112, #113, #114, #115, #116, #119. None blocks closing this issue; all were held out of the waves deliberately rather than improvised mid-flight.
Author
Collaborator

The hand-invocation decision, for all four candidates

The issue body asks for a per-skill decision on disable-model-invocation: true across grill-me, grill-with-docs, caveman and prototype. Only caveman was flagged. Here is the reasoning for the other three, re-verified against the corpus after all five waves of description rewrites rather than against the pre-retrofit state.

Candidate Inbound routes Decision
grill-me 4 — incl. prototype's boundary clause -> \grill-me`` No
grill-with-docs 3 — incl. forge/SKILL.md:25 Call \bin:grill-with-docs`` No
prototype 0 No — but for a different reason
caveman 0 Yes — bdff6fd

Why the two with inbound routes are excluded

The flag hard-blocks the Skill tool (CONTEXT.md:36-40). Note the failure mode is behavioural, not gated: flagging a skill does not delete its file, so skill-size-check's boundary-target resolver still resolves a clause pointing at it and tests/test-adr0020-targets.sh stays green. prototype's boundary clause would keep passing every check while routing to something unreachable. Nothing would catch it.

forge's case is blunter — Step 1 mandates the call, so flagging grill-with-docs breaks the entry-point router outright.

Why prototype is excluded despite qualifying

prototype is the only candidate besides caveman with zero inbound routes, so flagging it would break nothing. It should still not be flagged, and the distinction is worth writing down:

Zero inbound routes makes flagging safe. It does not make it right.

prototype's description triggers on ordinary work requests — "Use when the user wants a throwaway prototype to answer a design question about a data model, state machine or business logic, or to mock up a UI in several variations, as in 'try a few designs'". That is exactly what a router should catch. Hand-invoking it hides a task skill behind a slash command the user has to already know exists, trading discoverability for 286 characters.

caveman is the opposite shape. It is a mode, requested by name, and its trigger list contained "be brief" and "less tokens" — phrasings that mean "give me short answers", not "drop articles and write in fragments". Auto-invocation there was actively wrong, and removing the skill from the router deletes the misfire. The preload saving was the side effect.

So the criterion is not "does anything route to it" — that is only the safety precondition. The criterion is whether model invocation is desirable at all: modes and meta-commands, yes; task skills, no.

Remaining scope check

  • Agents. The issue opens "a blocking description/body contract for skills and agents". All four agents pass the description gate (apm-orchestrate 386, lint-runner 343, gitea-orchestrate 284, git-orchestrate 254 — none over 400). Their bodies are 1,085 / 333 / 1,199 / 933 words, and three exceed 900 — but ADR-0020:181 exempts agents from the body gate deliberately, because an agent body runs in its own context rather than the caller's. The ADR records those same figures and explicitly warns against widening the file pattern to cover them. No agent work is outstanding.
  • All six per-skill work items in the issue body are complete across the 27 in-scope skills.
  • Both success criteria are met: preload 9,732 chars against the ~12,000 target, and pre-commit run --all-files clean on skill-size-check and vale-audit-prefilter-skill.
  • All four traps addressed: validated in place throughout; Kyberforge.CompositionNote at 0; gitea-workflow's cross-plugin route resolved as agreed in wave 1; batched by plugin.

This issue is functionally complete

Two things stand between here and closing it, both decisions rather than work:

  1. The release bump — no wave bumped a plugin version across 43 commits, though repo policy says a substantive .apm/ change should. Recommendation: one bump across all six plugins.
  2. The branch has never been pushed. Closing #99 while the work exists only on one machine would be recording a completion that is not durable anywhere.
## The hand-invocation decision, for all four candidates The issue body asks for a per-skill decision on `disable-model-invocation: true` across `grill-me`, `grill-with-docs`, `caveman` and `prototype`. Only `caveman` was flagged. Here is the reasoning for the other three, re-verified against the corpus **after** all five waves of description rewrites rather than against the pre-retrofit state. | Candidate | Inbound routes | Decision | |---|---|---| | `grill-me` | 4 — incl. `prototype`'s boundary clause `-> \`grill-me\`` | **No** | | `grill-with-docs` | 3 — incl. `forge/SKILL.md:25` `Call \`bin:grill-with-docs\`` | **No** | | `prototype` | **0** | **No** — but for a different reason | | `caveman` | 0 | **Yes** — `bdff6fd` | ### Why the two with inbound routes are excluded The flag hard-blocks the Skill tool (`CONTEXT.md:36-40`). Note the failure mode is *behavioural, not gated*: flagging a skill does not delete its file, so `skill-size-check`'s boundary-target resolver still resolves a clause pointing at it and `tests/test-adr0020-targets.sh` stays green. `prototype`'s boundary clause would keep passing every check while routing to something unreachable. Nothing would catch it. `forge`'s case is blunter — Step 1 mandates the call, so flagging `grill-with-docs` breaks the entry-point router outright. ### Why `prototype` is excluded despite qualifying `prototype` is the only candidate besides `caveman` with zero inbound routes, so flagging it would break nothing. It should still not be flagged, and the distinction is worth writing down: **Zero inbound routes makes flagging *safe*. It does not make it *right*.** `prototype`'s description triggers on ordinary work requests — *"Use when the user wants a throwaway prototype to answer a design question about a data model, state machine or business logic, or to mock up a UI in several variations, as in 'try a few designs'"*. That is exactly what a router should catch. Hand-invoking it hides a task skill behind a slash command the user has to already know exists, trading discoverability for 286 characters. `caveman` is the opposite shape. It is a **mode**, requested by name, and its trigger list contained `"be brief"` and `"less tokens"` — phrasings that mean "give me short answers", not "drop articles and write in fragments". Auto-invocation there was actively wrong, and removing the skill from the router deletes the misfire. The preload saving was the side effect. So the criterion is not "does anything route to it" — that is only the safety precondition. The criterion is **whether model invocation is desirable at all**: modes and meta-commands, yes; task skills, no. ### Remaining scope check - **Agents.** The issue opens "a blocking description/body contract for skills **and agents**". All four agents pass the description gate (`apm-orchestrate` 386, `lint-runner` 343, `gitea-orchestrate` 284, `git-orchestrate` 254 — none over 400). Their bodies are 1,085 / 333 / 1,199 / 933 words, and three exceed 900 — but ADR-0020:181 exempts agents from the body gate deliberately, because an agent body runs in its own context rather than the caller's. The ADR records those same figures and explicitly warns against widening the file pattern to cover them. **No agent work is outstanding.** - All six per-skill work items in the issue body are complete across the 27 in-scope skills. - Both success criteria are met: preload **9,732 chars** against the ~12,000 target, and `pre-commit run --all-files` clean on `skill-size-check` and `vale-audit-prefilter-skill`. - All four traps addressed: validated in place throughout; `Kyberforge.CompositionNote` at 0; `gitea-workflow`'s cross-plugin route resolved as agreed in wave 1; batched by plugin. ### This issue is functionally complete Two things stand between here and closing it, both decisions rather than work: 1. **The release bump** — no wave bumped a plugin version across 43 commits, though repo policy says a substantive `.apm/` change should. Recommendation: one bump across all six plugins. 2. **The branch has never been pushed.** Closing #99 while the work exists only on one machine would be recording a completion that is not durable anywhere.
Author
Collaborator

An independent post-closure audit (agent-audit + skill-audit run against every skill/agent changed on refactor/adr0020-skill-retrofit, cross-checked against this issue's scope and against the commit history of each flagged file) found 3 in-scope description-quality misses this branch's own retrofit commits introduced but never caught: git-submodules (261e5b5), git-worktrees (3dd5387), and gitea-files (d5954d3) each collapsed length correctly but left a verb-enumerated capability clause instead of ADR-0020's required single clause. It also found one regression: 00c1e6b (diagnose retrofit) moved a script-path bullet into references/feedback-loops.md and flipped a correct skill-root-relative path into an incorrect parent-relative one, contradicting that commit's own claim to have fixed a non-resolving path.

All four are now fixed and validated clean via skill-audit and skill-size-check (commits on refactor/adr0020-skill-retrofit).

The same pass also re-confirmed two pre-existing, out-of-scope items already covered elsewhere: prototype's LOGIC.md/UI.md placement is correctly deferred to #114, and apm-orchestrate.agent.md's description/dispatch issues (including a real add-package mapping bug vs. apm-workflow's marketplace.md) all predate this branch (authored in 5e23250, before #99 started) and are outside this issue's skill-scoped gate — worth a follow-up issue in the #107-119 style, not folded in here.

An independent post-closure audit (agent-audit + skill-audit run against every skill/agent changed on refactor/adr0020-skill-retrofit, cross-checked against this issue's scope and against the commit history of each flagged file) found 3 in-scope description-quality misses this branch's own retrofit commits introduced but never caught: `git-submodules` (261e5b5), `git-worktrees` (3dd5387), and `gitea-files` (d5954d3) each collapsed length correctly but left a verb-enumerated capability clause instead of ADR-0020's required single clause. It also found one regression: `00c1e6b` (diagnose retrofit) moved a script-path bullet into `references/feedback-loops.md` and flipped a correct skill-root-relative path into an incorrect parent-relative one, contradicting that commit's own claim to have fixed a non-resolving path. All four are now fixed and validated clean via skill-audit and skill-size-check (commits on refactor/adr0020-skill-retrofit). The same pass also re-confirmed two pre-existing, out-of-scope items already covered elsewhere: `prototype`'s LOGIC.md/UI.md placement is correctly deferred to #114, and `apm-orchestrate.agent.md`'s description/dispatch issues (including a real `add-package` mapping bug vs. apm-workflow's marketplace.md) all predate this branch (authored in 5e23250, before #99 started) and are outside this issue's skill-scoped gate — worth a follow-up issue in the #107-119 style, not folded in here.
Author
Collaborator

Second independent audit — 29 agents, all prior fixes confirmed, 3 new defects found and fixed

Ran a fresh, independent audit (skill-audit/agent-audit, one fresh agent per skill + one for apm-orchestrate.agent.md — 29 total) against every skill and agent changed on this branch, cross-checked against this issue's full comment history rather than trusting the previous post-closure audit's claims at face value.

Every previously-documented fix holds up on independent re-verification — not just re-asserted, checked against source: the git-worktrees destructive dispatch gap, the git-commits secret-scan reachability, pc-run's false-green, the gitea-issues/gitea-labels-milestones closed loop and org-parameter derivation, gitea-prs' three stale claims (live-verified against gitea-mcp v1.7.0), agentsmd-author's provider-file prohibition, vale-config/vale-run's false statement and stranded content, apm-install/apm-workflow/forge's wave-4 invented-content defects, research's provenance-chain break, and both apm-orchestrate citation updates — all confirmed correct at their cited locations.

3 new, previously-untracked defects were found and are now fixed (commits 59aaec4, 0c0df46, 6cb47f8, pushed):

  1. gitea-branches — references/commits.md cited a nonexistent overview.md as authority for a scope-gating claim (the only file by that name is an external research doc, never shipped with the skill). Repointed to branches.md's own Token scope section, which actually states the principle.
  2. gitea-releases — the retrofit (dfacf05) collapsed two Gotchas into a bidirectional claim ("deleting a tag never deletes the release wrapping it") that references/call-signatures.md never confirms — that file explicitly marks the reverse direction unconfirmed and "the more dangerous direction to get wrong." Now states only the confirmed direction and hedges the reverse with a verification step, since this is a destructive, irreversible operation.
  3. apm-workflow — the registries precondition was only documented in references/configure.md, despite SKILL.md claiming it applies "anywhere — configure, install or publish." references/compile.md (Publish) and references/install.md (dependency resolution) — the two files that actually hit a named registry — were silent on it. Both now cross-reference the precondition.

All three re-validated clean via skill-audit; pre-commit run --all-files and bash tests/run-tests.sh --strict (24/0) both green after the fixes; pushed.

1 pre-existing bug independently confirmed rather than just alleged: apm-orchestrate.agent.md's add-package mapping really does contradict apm-workflow/references/marketplace.md's remote-only constraint, plus 2 new agent-audit FAILs (description quality, an uncited delegation restatement of configure.md's Gotcha). Filed as #120, in the #107-#119 style, per this issue's own precedent for pre-existing agent-file defects being out of ADR-0020's skill-scoped gate.

Nothing else changed — this issue's own "functionally complete" status and both success criteria from the previous comment stand.

## Second independent audit — 29 agents, all prior fixes confirmed, 3 new defects found and fixed Ran a fresh, independent audit (skill-audit/agent-audit, one fresh agent per skill + one for `apm-orchestrate.agent.md` — 29 total) against every skill and agent changed on this branch, cross-checked against this issue's full comment history rather than trusting the previous post-closure audit's claims at face value. **Every previously-documented fix holds up on independent re-verification** — not just re-asserted, checked against source: the `git-worktrees` destructive dispatch gap, the `git-commits` secret-scan reachability, `pc-run`'s false-green, the `gitea-issues`/`gitea-labels-milestones` closed loop and org-parameter derivation, `gitea-prs`' three stale claims (live-verified against gitea-mcp v1.7.0), `agentsmd-author`'s provider-file prohibition, `vale-config`/`vale-run`'s false statement and stranded content, `apm-install`/`apm-workflow`/`forge`'s wave-4 invented-content defects, `research`'s provenance-chain break, and both `apm-orchestrate` citation updates — all confirmed correct at their cited locations. **3 new, previously-untracked defects were found and are now fixed** (commits `59aaec4`, `0c0df46`, `6cb47f8`, pushed): 1. **`gitea-branches`** — `references/commits.md` cited a nonexistent `overview.md` as authority for a scope-gating claim (the only file by that name is an external research doc, never shipped with the skill). Repointed to `branches.md`'s own Token scope section, which actually states the principle. 2. **`gitea-releases`** — the retrofit (`dfacf05`) collapsed two Gotchas into a bidirectional claim ("deleting a tag never deletes the release wrapping it") that `references/call-signatures.md` never confirms — that file explicitly marks the reverse direction unconfirmed and "the more dangerous direction to get wrong." Now states only the confirmed direction and hedges the reverse with a verification step, since this is a destructive, irreversible operation. 3. **`apm-workflow`** — the registries precondition was only documented in `references/configure.md`, despite SKILL.md claiming it applies "anywhere — configure, install or publish." `references/compile.md` (Publish) and `references/install.md` (dependency resolution) — the two files that actually hit a named registry — were silent on it. Both now cross-reference the precondition. All three re-validated clean via skill-audit; `pre-commit run --all-files` and `bash tests/run-tests.sh --strict` (24/0) both green after the fixes; pushed. **1 pre-existing bug independently confirmed** rather than just alleged: `apm-orchestrate.agent.md`'s `add-package` mapping really does contradict `apm-workflow/references/marketplace.md`'s remote-only constraint, plus 2 new `agent-audit` FAILs (description quality, an uncited delegation restatement of `configure.md`'s Gotcha). Filed as **#120**, in the #107-#119 style, per this issue's own precedent for pre-existing agent-file defects being out of ADR-0020's skill-scoped gate. Nothing else changed — this issue's own "functionally complete" status and both success criteria from the previous comment stand.
Author
Collaborator

Third independent review — 8 review agents, 6 fix agents, 10 commits pushed

Fresh read-only review of all 47 commits, dispatched across 8 agents (one per plugin family, plus tests/gate-scripts, commit-claim accuracy, collateral integrity, and repo-doc accuracy), then 6 fix agents. Every finding below was re-verified independently before acting on it. Pushed as 6cb47f8..0959291.

The enforcement machinery was not weakened. Verified by mutation, not assertion: the removed test probes were genuine deletions with honest totals (39→37, 43→41, no vacuous placeholders), the rebuilt .claude/skills/ A/B fails under two separate mutations, and all five copies of the shared resolver hash identically. The prior audits' claims hold up on re-verification.

Six content defects, four safety-relevant

  1. git-worktrees — the "Create tracking a remote branch" row prescribed git worktree add <path> <remote>/<branch>, which produces a detached HEAD, not a tracking branch. The DWIM fires only on a bare branch name not found locally. Commits made there are unreachable once the worktree goes. Branch-introduced — the word "tracking" was new. git worktree repair's semantics were also inverted, with an invented sentence the manual does not support.
  2. pc-run — the pre-commit clean confirmation gate moved into clean.md, while failure-patterns.md (the diagnosis route) still prescribed the command ungated, under a body that says read one file "and no other". "Why is this hook failing" could wipe the machine-wide cache without asking. The fixer-hook rule was stranded behind "if the cause is not obvious", which is false exactly when pre-commit prints - files were modified by this hook.
  3. gitea-issues — stated in bold that list_issues has no type and no milestones parameter. Both exist on the deployed v1.7.0 and work. gitea-workflow's no-args check-in therefore reported PRs under "Open Issues" while the skill forbade the one-parameter fix.
  4. gitea-labels-milestones — called exclusive "an org-labels-only flag" and mandated replace-don't-stack. Live list_repo_labels returns exclusive on every repo label: all seven Kind/* are false, Priority/*/Reviewed/*/Status/* are true. The rule would strip a valid Kind/* label. The org-label fallback also read a required=[read:organization] scope error as proof of absence.
  5. gitea-releases — the is_pre_release imperative was stranded behind a trigger that does not fire for "cut a v2.0.0-beta.1". Severe because there is no update/edit-release tool — the only repair is delete-and-recreate, and nothing said so.
  6. apm-workflow — type: described as constraining .apm/ contents. Read against apm-cli 0.28.0: it is a processing selector, no mismatch check exists anywhere, so the real hazard is silent omission — install exits 0 having shipped none of the package's primitives.

vale-config: a fix this issue recorded as landed, which never did

Wave 3 lists "vale-config shipped a false statement. Gotcha 1's kernel lost the qualifier that only package styles need fetching" among "three findings worse than quality defects". The file was byte-identical to 45cd260, the commit that introduced it. No repair ever landed, and the two later audits both missed it.

The claim is also empirically false: a missing style is a hard E100, exit 2 — not "finds nothing … a clean run". Worse, the same commit deleted the Packages-vs-BasedOnStyles Gotcha that was the actual diagnostic, so the surviving rule sends you to vale sync, which reports Synced 0 package(s) against a BasedOnStyles-only name. The remediation loop does not terminate. Reproduced end to end.

A third gate blind spot, unfiled

validate-provenance.sh matched Contributing files only in the inline form. Seven skills — git-branches, git-remotes, git-submodules, git-workflow, git-worktrees, gitea-files, gitea-releases — use the bullet form, so checks 4 and 5 silently verified nothing on them. Those are among the skills this branch changed most, and the retrofit's mandatory sources.md collateral went in unchecked.

Demonstrated rather than argued: planting a nonexistent contributing path in git-remotes yields 0 findings under the old parser and 1 FAIL under the new one. Fixing the parser immediately surfaced one real defect (git-branches/references/merging.md missing a slug sources.md credits it with), now fixed. This sits alongside #117 and #118 and was not filed anywhere.

docs/spec/gates.md was stale and contradicted AGENTS.md

It still read "Two independent hot gates are currently red" with the full pre-retrofit table — 26/39, 9/39, 2 dangling, 58 SUGGESTIONs, 10 CompositionNote errors. Measured: 0, 0, 0, 33, 0. The branch correctly left ADR-0020 untouched (self-pinned to f9b919d), but gates.md carries no such pin and AGENTS.md names it three times as the authoritative reasoning layer — so the stale doc told readers a one-line fix to gitea-prs was blocked pending a retrofit already complete.

Commit-message accuracy

Headers are 100% Conventional Commits compliant. Two numeric claims are not, and both were written into always-loaded AGENTS.md:

  • ee6b040 claims research → 231 chars / 519 words. Actual 247 / 593. (This issue's own wave-3 table has the correct figures — the commit was measured on a pre-repair draft.)
  • b8c36c3 claims 19 descriptions over 400 and that CompositionNote "now fires nowhere". Actual 20, and gitea-workflow still sat at 1013 chars carrying four CompositionNote tokens; both became true one commit later at bedbd1d.

Also: 24 commits typed refactor: document behaviour fixes (restoring a force-push confirm: true gate, adding the dispatch row whose absence discarded commits) — those are fix:. Trailers appear on only 7 of 47. All immutable now, recorded here rather than rewritten.

The release bump — done

No wave bumped a version across 56 commits. All six local packages had substantive .apm/ edits, so all six are bumped minor (the retrofit rewrote every routing surface): kyberforge 1.7.0, git 1.4.0, gitea 1.4.0, bin/core/lint 1.2.0. Per configure.md, versions are per package, so this is six independent bumps landing together, not one release number — the earlier "one bump covering all six" recommendation reaches the right outcome by the wrong reasoning.

executables.allow is version-pinned to kyberforge#<version> by apm's design, and the first bump orphaned it — the SessionStart hook would have silently stopped deploying (ADR-0019). The comment above that block predicted exactly this, and test-check-executables-allow-sync.sh caught it.

Overlap check against #107–#120

No duplicates filed. Mapping: the conjoined-arrow recall hole is #107; the dotted-filename hole is #110, but worse than filed — for a bare target the name is not extracted at all, so a dangling route ships green and silent rather than being misreported as missing (worth adding to #110). provider-adapter-author building a firmer rule on the dead --no-import-syntax flag is #115's flag. git-submodules' reference files dropping the rtk git prefix is #113. git-branches routing cherry-pick to the weaker handler is #112. The unlinted references/ files are #117. The false provenance assertion class is #118. apm-orchestrate's add-package mapping is #120 and was left untouched. The new provenance-parser defect is the only genuinely new gate finding and is fixed here rather than filed.

State

pre-commit run --all-files clean (33 SUGGESTIONs, all advisory); tests/run-tests.sh --strict 24/0; pre-commit run --hook-stage pre-push --all-files all 14 real hooks pass. Pushed.

Residual, not fixed: gitea-prs' update-side labels set semantics are unverified, so nothing was asserted about them; and two new SUGGESTIONs were accepted deliberately (vale-config Gotchas at 37%, git-worktrees body at 664) rather than deleting the safety content that earns the space.

## Third independent review — 8 review agents, 6 fix agents, 10 commits pushed Fresh read-only review of all 47 commits, dispatched across 8 agents (one per plugin family, plus tests/gate-scripts, commit-claim accuracy, collateral integrity, and repo-doc accuracy), then 6 fix agents. Every finding below was re-verified independently before acting on it. Pushed as `6cb47f8..0959291`. **The enforcement machinery was not weakened.** Verified by mutation, not assertion: the removed test probes were genuine deletions with honest totals (39→37, 43→41, no vacuous placeholders), the rebuilt `.claude/skills/` A/B fails under two separate mutations, and all five copies of the shared resolver hash identically. The prior audits' claims hold up on re-verification. ### Six content defects, four safety-relevant 1. **`git-worktrees`** — the "Create tracking a remote branch" row prescribed `git worktree add <path> <remote>/<branch>`, which produces a **detached HEAD**, not a tracking branch. The DWIM fires only on a bare branch name *not found locally*. Commits made there are unreachable once the worktree goes. Branch-introduced — the word "tracking" was new. `git worktree repair`'s semantics were also inverted, with an invented sentence the manual does not support. 2. **`pc-run`** — the `pre-commit clean` confirmation gate moved into `clean.md`, while `failure-patterns.md` (the *diagnosis* route) still prescribed the command ungated, under a body that says read one file "and no other". "Why is this hook failing" could wipe the machine-wide cache without asking. The fixer-hook rule was stranded behind "if the cause is not obvious", which is false exactly when pre-commit prints `- files were modified by this hook`. 3. **`gitea-issues`** — stated in bold that `list_issues` has no `type` and no `milestones` parameter. Both exist on the deployed v1.7.0 and work. `gitea-workflow`'s no-args check-in therefore reported PRs under "Open Issues" while the skill forbade the one-parameter fix. 4. **`gitea-labels-milestones`** — called `exclusive` "an org-labels-only flag" and mandated replace-don't-stack. Live `list_repo_labels` returns `exclusive` on every **repo** label: all seven `Kind/*` are `false`, `Priority/*`/`Reviewed/*`/`Status/*` are `true`. The rule would strip a valid `Kind/*` label. The org-label fallback also read a `required=[read:organization]` scope error as proof of absence. 5. **`gitea-releases`** — the `is_pre_release` imperative was stranded behind a trigger that does not fire for "cut a v2.0.0-beta.1". Severe because there is **no update/edit-release tool** — the only repair is delete-and-recreate, and nothing said so. 6. **`apm-workflow`** — `type:` described as constraining `.apm/` contents. Read against apm-cli 0.28.0: it is a processing selector, no mismatch check exists anywhere, so the real hazard is **silent omission** — install exits 0 having shipped none of the package's primitives. ### `vale-config`: a fix this issue recorded as landed, which never did Wave 3 lists *"`vale-config` shipped a false statement. Gotcha 1's kernel lost the qualifier that only package styles need fetching"* among "three findings worse than quality defects". **The file was byte-identical to `45cd260`, the commit that introduced it.** No repair ever landed, and the two later audits both missed it. The claim is also empirically false: a missing style is a hard `E100`, exit 2 — not "finds nothing … a clean run". Worse, the same commit deleted the `Packages`-vs-`BasedOnStyles` Gotcha that was the actual diagnostic, so the surviving rule sends you to `vale sync`, which reports `Synced 0 package(s)` against a `BasedOnStyles`-only name. **The remediation loop does not terminate.** Reproduced end to end. ### A third gate blind spot, unfiled `validate-provenance.sh` matched Contributing files only in the inline form. Seven skills — `git-branches`, `git-remotes`, `git-submodules`, `git-workflow`, `git-worktrees`, `gitea-files`, `gitea-releases` — use the bullet form, so checks 4 and 5 **silently verified nothing** on them. Those are among the skills this branch changed most, and the retrofit's mandatory `sources.md` collateral went in unchecked. Demonstrated rather than argued: planting a nonexistent contributing path in `git-remotes` yields 0 findings under the old parser and 1 FAIL under the new one. Fixing the parser immediately surfaced one real defect (`git-branches/references/merging.md` missing a slug `sources.md` credits it with), now fixed. This sits alongside #117 and #118 and was not filed anywhere. ### `docs/spec/gates.md` was stale and contradicted `AGENTS.md` It still read "**Two independent hot gates are currently red**" with the full pre-retrofit table — 26/39, 9/39, 2 dangling, 58 SUGGESTIONs, 10 CompositionNote errors. Measured: 0, 0, 0, 33, 0. The branch correctly left ADR-0020 untouched (self-pinned to `f9b919d`), but `gates.md` carries no such pin and `AGENTS.md` names it three times as the authoritative reasoning layer — so the stale doc told readers a one-line fix to `gitea-prs` was blocked pending a retrofit already complete. ### Commit-message accuracy Headers are 100% Conventional Commits compliant. Two numeric claims are not, and both were written into always-loaded `AGENTS.md`: - `ee6b040` claims research → 231 chars / 519 words. Actual **247 / 593**. (This issue's own wave-3 table has the correct figures — the commit was measured on a pre-repair draft.) - `b8c36c3` claims 19 descriptions over 400 and that `CompositionNote` "now fires nowhere". Actual **20**, and `gitea-workflow` still sat at 1013 chars carrying four CompositionNote tokens; both became true one commit later at `bedbd1d`. Also: 24 commits typed `refactor:` document behaviour fixes (restoring a force-push `confirm: true` gate, adding the dispatch row whose absence discarded commits) — those are `fix:`. Trailers appear on only 7 of 47. All immutable now, recorded here rather than rewritten. ### The release bump — done No wave bumped a version across 56 commits. All six local packages had substantive `.apm/` edits, so all six are bumped **minor** (the retrofit rewrote every routing surface): kyberforge 1.7.0, git 1.4.0, gitea 1.4.0, bin/core/lint 1.2.0. Per `configure.md`, versions are per package, so this is six independent bumps landing together, not one release number — the earlier "one bump covering all six" recommendation reaches the right outcome by the wrong reasoning. `executables.allow` is version-pinned to `kyberforge#<version>` by apm's design, and the first bump orphaned it — the `SessionStart` hook would have silently stopped deploying (ADR-0019). The comment above that block predicted exactly this, and `test-check-executables-allow-sync.sh` caught it. ### Overlap check against #107–#120 No duplicates filed. Mapping: the conjoined-arrow recall hole **is** #107; the dotted-filename hole is #110, but **worse than filed** — for a *bare* target the name is not extracted at all, so a dangling route ships green and silent rather than being misreported as missing (worth adding to #110). `provider-adapter-author` building a firmer rule on the dead `--no-import-syntax` flag is #115's flag. `git-submodules`' reference files dropping the `rtk git` prefix is #113. `git-branches` routing cherry-pick to the weaker handler is #112. The unlinted `references/` files are #117. The false provenance assertion class is #118. `apm-orchestrate`'s `add-package` mapping is #120 and was left untouched. The new provenance-parser defect is the only genuinely new gate finding and is fixed here rather than filed. ### State `pre-commit run --all-files` clean (33 SUGGESTIONs, all advisory); `tests/run-tests.sh --strict` **24/0**; `pre-commit run --hook-stage pre-push --all-files` all 14 real hooks pass. Pushed. Residual, not fixed: `gitea-prs`' update-side `labels` set semantics are unverified, so nothing was asserted about them; and two new SUGGESTIONs were accepted deliberately (`vale-config` Gotchas at 37%, `git-worktrees` body at 664) rather than deleting the safety content that earns the space.
Sign in to join this conversation.