Fix dead slash-command refs and cookbook doc/YAML drift - #109
Fix dead slash-command refs and cookbook doc/YAML drift#109peterzhang12312 wants to merge 2 commits into
Conversation
The documented validation suite passes clean on main, but two defect classes
sit outside what any of it checks. Both are user-visible.
## Dead slash-command references
CLAUDE.md requires slash-command names in prose to be the actual
`skills/<name>/` directory name — short forms "look right in prose but are
dead commands." `claude plugin validate` treats `/foo` in prose as text, so
these shipped silently:
- `ip-legal/skills/cold-start-interview` — 11 refs across 6 lines used short
forms: `/cd` (→ `cease-desist`), `/fto` (→ `fto-triage`), `/infringe`
(→ `infringement-triage`), `/oss` (→ `oss-review`), `/clause`
(→ `ip-clause-review`). These are the lines that tell the user which skills
their answers light up, so every name in them is one the user may type.
- `legal-clinic/README.md` — `/legal-clinicgrations`, a mangled
find-and-replace. The intact form of the identical paragraph in
`product-legal/README.md` gives the correct text.
- `litigation-legal/skills/matter-intake` — `` `/matter` briefing `` →
`` `/matter-briefing` ``.
- `product-legal/skills/customize` — `/ai-governance-legal` addressed a plugin
as a command; reworded to name the plugin.
- Three command names were line-wrapped inside their backticks
(`` `/handbook-\nupdates` ``), so they render with a space and are not
typable as written. Reflowed.
## Cookbook README security tables contradicting their own YAML
Cookbook rule 2 requires the README security table and `agent.yaml` comments
to match what the YAML grants. `lint-tool-scope.py` only enforces rule 1 and
never opens `subagents/*.yaml` or `README.md`, so rule 2 was unenforced:
- `launch-radar` README claimed the orchestrator holds Linear/Jira/Asana/Drive.
It grants no `mcp_toolset` at all — and the adjacent `agent.yaml` comment
said so. A reader auditing blast radius from the README would overestimate
it; a reader "fixing" the YAML to match would break rule 1.
- `docket-watcher` and `reg-monitor` READMEs advertised a gdrive connector on
components that grant none. `docket-watcher`'s own README refuted its table
three lines later.
- `diligence-grid` claimed iManage read access without noting it ships
disabled.
- Five MCP servers were declared in `mcp_servers` but granted to no component
(`definely`, `gdrive`×3, `docusign`), with deploy instructions telling
operators to export URLs for connectors nothing could reach. Removed the
declarations and the env-var docs rather than widening any subagent's scope.
Nothing in `scripts/` or `.github/` referenced them.
- Two `agent.yaml` comments claimed leaves hold `web_fetch` that no leaf has
(copy-paste from `reg-monitor`, the one cookbook where it is true).
- Two adaptation notes pointed at `agent.yaml` for an iManage toggle that
lives in a subagent; following them literally would have added an MCP grant
to an orchestrator and broken rule 1.
- Split the combined `<leaf> / Orchestrator` rows. That row published a union
of two tiers, so it over-claimed `Glob` for three leaves and `Agent` for
every leaf (all have `callable_agents: []`), and made the row uncheckable
against either component.
No under-disclosure was found: every enabled tool and connector, including
`feed-reader`'s `web_fetch` and each Write-holder's exact toolset, was already
disclosed. All defects ran in the over-claim direction.
## Regression cover
Both classes now fail CI instead of shipping:
- `scripts/lint-skill-refs.py` — every `/foo` and `/plugin:skill` in plugin
prose resolves to a real skill directory; also catches names line-wrapped
inside backticks. Built-ins are exempt.
- `scripts/lint-cookbook-docs.py` — README security-table tools and connectors
match the YAML in both directions, declared MCP servers are granted
somewhere, `agent.yaml` comments are true, and combined rows are rejected.
Verified both scripts reproduce the full hand-found defect set against the
pre-fix tree and pass against this one.
Also corrected the JSON sanity command in CLAUDE.md: `glob('**/*.json')` does
not match dotfiles, so it was checking 15 files and skipping all 27 that live
under `.claude-plugin/` or are named `.mcp.json`.
Left alone per CLAUDE.md: the I1 curated sort order, the expected plugin-root
CLAUDE.md template warnings, per-plugin `.gitignore` differences, the two
missing `hooks/hooks.json`, the `references/` path gap, and vendor-authored
content under `external_plugins/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vz1DwCuBbxbqkG6fBGBZp9
|
Caution Pull Request opener is not an author or co-author of any commit in this PR.
This check is blocked to guard against commits being submitted under a trusted identity the submitter does not control. If this PR is a legitimate cherry-pick, release-engineering submission, or mailing-list-style patch delivery, the repository maintainer can opt out of this check by setting
I have read the CLA Document and I hereby sign the CLA 0 out of 2 committers have signed the CLA. Warning 1 commit in this PR was authored by an email address that is not linked to any GitHub user, so we cannot tell whether the author has signed the CLA. Unlinked author:
To unblock this PR, do one of the following:
|
Repo text is UTF-8 and the lint scripts print non-ASCII markers (✓, —), but Windows consoles default to cp1252, so print() raised UnicodeEncodeError and lint-cookbook-docs.py's bare read_text()/open() calls raised UnicodeDecodeError on the same files. Reconfigure stdout/stderr to UTF-8 in each script and pass encoding="utf-8" explicitly on every file read, instead of requiring every caller to remember PYTHONUTF8=1. Verified: all three scripts pass with no env override and under PYTHONIOENCODING=cp1252 forced.
Summary
The documented validation suite passes clean on main, but two defect classes sit outside what any of it checks. Both are user-visible.
Dead slash-command references
CLAUDE.md requires slash-command names in prose to be the actual
skills/<name>/directory name — short forms "look right in prose but are dead commands."claude plugin validatetreats/fooin prose as text, so these shipped silently:ip-legal/skills/cold-start-interview— 11 refs across 6 lines used short forms:/cd(→cease-desist),/fto(→fto-triage),/infringe(→infringement-triage),/oss(→oss-review),/clause(→ip-clause-review). These are the lines that tell the user which skills their answers light up, so every name in them is one the user may type.legal-clinic/README.md—/legal-clinicgrations, a mangled find-and-replace. The intact form of the identical paragraph inproduct-legal/README.mdgives the correct text.litigation-legal/skills/matter-intake—`/matter` briefing→`/matter-briefing`.product-legal/skills/customize—/ai-governance-legaladdressed a plugin as a command; reworded to name the plugin.`/handbook-\nupdates`), so they render with a space and are not typable as written. Reflowed.Cookbook README security tables contradicting their own YAML
Cookbook rule 2 requires the README security table and
agent.yamlcomments to match what the YAML grants.lint-tool-scope.pyonly enforces rule 1 and never openssubagents/*.yamlorREADME.md, so rule 2 was unenforced:launch-radarREADME claimed the orchestrator holds Linear/Jira/Asana/Drive. It grants nomcp_toolsetat all — and the adjacentagent.yamlcomment said so. A reader auditing blast radius from the README would overestimate it; a reader "fixing" the YAML to match would break rule 1.docket-watcherandreg-monitorREADMEs advertised a gdrive connector on components that grant none.docket-watcher's own README refuted its table three lines later.diligence-gridclaimed iManage read access without noting it ships disabled.mcp_serversbut granted to no component (definely,gdrive×3,docusign), with deploy instructions telling operators to export URLs for connectors nothing could reach. Removed the declarations and the env-var docs rather than widening any subagent's scope. Nothing inscripts/or.github/referenced them.agent.yamlcomments claimed leaves holdweb_fetchthat no leaf has (copy-paste fromreg-monitor, the one cookbook where it is true).agent.yamlfor an iManage toggle that lives in a subagent; following them literally would have added an MCP grant to an orchestrator and broken rule 1.<leaf> / Orchestratorrows. That row published a union of two tiers, so it over-claimedGlobfor three leaves andAgentfor every leaf (all havecallable_agents: []), and made the row uncheckable against either component.No under-disclosure was found: every enabled tool and connector, including
feed-reader'sweb_fetchand each Write-holder's exact toolset, was already disclosed. All defects ran in the over-claim direction.Regression cover
Both classes now fail CI instead of shipping:
scripts/lint-skill-refs.py— every/fooand/plugin:skillin plugin prose resolves to a real skill directory; also catches names line-wrapped inside backticks. Built-ins are exempt.scripts/lint-cookbook-docs.py— README security-table tools and connectors match the YAML in both directions, declared MCP servers are granted somewhere,agent.yamlcomments are true, and combined rows are rejected.Verified both scripts reproduce the full hand-found defect set against the pre-fix tree and pass against this one.
Also corrected the JSON sanity command in CLAUDE.md:
glob('**/*.json')does not match dotfiles, so it was checking 15 files and skipping all 27 that live under.claude-plugin/or are named.mcp.json.Left alone per CLAUDE.md: the I1 curated sort order, the expected plugin-root CLAUDE.md template warnings, per-plugin
.gitignoredifferences, the two missinghooks/hooks.json, thereferences/path gap, and vendor-authored content underexternal_plugins/.Test plan
python scripts/lint-skill-refs.py— passes on patched tree (13 plugins / 151 skills)python scripts/lint-cookbook-docs.py— passes on patched tree (5/5 cookbooks)python scripts/lint-tool-scope.py— passes on patched tree (5/5 cookbooks)claude plugin validate .claude-plugin/marketplace.json— passes