#3313 · playground · 2026-08-11

29 skills accused. 8 exonerated, 1 real, 20 for the owner.

The issue said 29 of 105 skills are unreachable by every activation path. The linters modeled three paths and the repo has four: #3221 deliberately converted 7 preload edges into explicit agent-body Read("${CLAUDE_PLUGIN_ROOT}/skills/<x>/SKILL.md") lines, and neither reachability gate ever learned that mechanism. So both gates accused 8 skills that have a live, ratcheted load path.

The arithmetic

29 accused
- 8 loaded via agent-body Read()   langgraph, llm-integration, monitoring-observability,
                                    multimodal-llm, product-frameworks, rag-retrieval,
                                    vite-advanced, web-research-workflow
= 21 genuinely without a load path
- 1 with a FALSE PROMISE to users  issue-progress-tracking advertised /ork:... — no such form
= 20                               delete-or-unflag: owner budget call, fenced off

Both gates, one path each learned

Gate A: test-skill-reachability.mjs

loadAgents kept only frontmatter and discarded the body, which is where the Read() lines live. It now extracts them, and the unreachable predicate exonerates the read-reached set. UNREACHABLE_BASELINE drops 29 to 21 in the same commit: the ratchet fails only on > baseline, so landing the linter fix without the drop would silently hand back 8 units of slack.

Gate B: test-unreachable-skills.sh, with its own vacuity floor

A second extractor collects Read() targets into its own file, deliberately NOT merged into wired.txt: wired_count feeds the parser self-check floor, and blending would make that floor measure a different population. The new extractor gets its own floor (MIN_READ_PATH_NAMES=6 against a live 8) because this gate argues from absence, and an extractor that silently returned 0 would re-accuse the 8 skills it exists to exonerate.

The second instance nobody had filed, 29x the first

scripts/_build-docs-generate.py, old:      if user_invocable: /ork:...
                                           else: "Auto-activated when Claude detects context"

but disable-model-invocation: true means   NOTHING auto-activates it.
ALL 29 generated pages carried that false claim.

new three-way branch:                      user_invocable -> /ork:...
                                           disable-model-invocation -> "Not directly
                                           invocable ... an agent loads it via Read()"
                                           else -> Auto-activated (now true by construction)

Regenerating changed exactly 29 mdx files, verified with git diff --stat rather than the build's exit code, because build-plugins.sh swallows build-docs failures to a discarded stderr.

What deliberately did NOT change

thingwhy
the 7 agent comments near the Read() linesthe prior "booby trap" was already defused by #3405; test-agent-read-path-truthful.mjs ratchets it
ORK_UNREACHABLE_SKILLS_ADVISORY mutethe bash gate exits 1 on ANY unreachable >0, no baseline concept; can only come out at 0, which needs the 20-skill decision
the 20 remaining skillsowner budget call; live price measured in the issue: unflagging them exceeds the skill-index cap by 5,334 bytes, 4.2x the total slack

Verification

gate A                        SUCCESS: unreachable held at 21/21, dead edges 0/0
read-path extraction          8 skills, byte-identical to the scoping report
gate B                        Read()-path sources: 8 · candidates 29 · advisory exit 0
docs drift gate               64 (baseline 64) — was 65/66 with one unit of untracked slack
mdx regeneration              exactly 29 files changed

Ratchet discipline note. The docs baseline was 66 with a live count of 65 — one unit of slack nobody recorded. Both drops (29→21, 66→64) land in the same commits as the changes that earn them, so neither ratchet ever holds unearned slack again.