gen-plugin-skills.cjs --check already guarantees skills/*/SKILL.md matches what commands/gsd/*.md generates, so the two trees cannot silently diverge FROM EACH OTHER. Nothing guarded the shape #3085 actually found: a command shipping Bash without Grep purely by omission, identical in both trees and therefore invisible to a parity check that only compares them to each other. That drift ran until 29 of 71 skills lacked a tool most of their siblings declared, and a manual audit — not a gate — is what surfaced it. Detection only: the lint never edits a command's allowed-tools. Two failure classes, not one. Violations are the rule itself. Stale exemptions are the other half: an entry whose command is gone, or which no longer declares Bash without Grep, fails just as loudly. An exemption list that can only grow becomes a list of things nobody re-examined, and a pre-forgiven command silently absorbs the next omission. That check earned its keep immediately. #4394 named eight exemptions from the #3085 review — the six ns-* dispatchers, help, and surface — and seven of them do not declare Bash at all, so the rule never reaches them. Listing them would have pre-forgiven seven commands for a condition none of them has. Only surface needs an entry. The tests drive synthetic fixtures rather than the live corpus: asserting "the real tree is clean" would say nothing about whether the rule can detect anything, which is the exact failure mode this lint exists to close. The one live-corpus arm asserts no stale exemptions — a property of this script's own list — and deliberately does not pin a violation count, which would make it a baseline every fix has to update. Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
11 KiB
11 KiB