Build1 publisher3 min readPublished Updated
Rulestack checks CLAUDE.md commands against package.json after shape tests missed a stale one for 24 days
Rulestack found a skill file that named a deleted pnpm script for 24 days while 28 commit-time shape tests passed. A 26-line check against package.json exits 1 on any missing name, so a hook can block the commit that deletes a referenced script.
The Engineer · Build desk
What happened
- Rulestack's CLAUDE.md, three rules files, nine skills and a CLI reference hold 707 command references to 190 names, all matching package.json's 199 scripts on the day of the run.
- The team's check pulls every pnpm, npm run and yarn reference out of Markdown and exits 1 when a name is missing from package.json.
- A first run that included Claude Code's agent worktrees scanned 12,800 Markdown files and reported 25 missing names, 19 of them only from those snapshots.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Format tests can pass for weeks on a rules file whose commands are gone, so the first thing to hit the drift is an agent partway through a task.
- cost Adoption is one 26-line bash-and-Node file, small enough to run against the whole rule set on every commit instead of on a review schedule.
- decision Claude Code users have to prune .claude/worktrees and scope the scan to files loaded into context, or copies of the repo dominate the report.
- constraint A clean run proves only that names exist; pnpm run targets, the exempt build, start, lint and test names, and changed script output all pass unchecked.
A shape test checks the file itself. It asks whether the frontmatter is right, the file is under its line limit and a description is present. The Rulestack post's example rule, `pnpm append-weekly-analysis`, passes all three while naming a script that was deleted three weeks earlier [4]. An agent that follows it gets a package manager error, then improvises or stops [5]. "Neither outcome is visible in a shape test," the authors wrote [6].
Their git history shows the same gap in a real file. For 24 days one skill file named a deleted command. Ten of the 142 rules-file commits in that window edited that same file, and the 28 tests passed every time [2].
Following the official advice produces more of these references. The memory page tells authors to write "Run npm test before committing" instead of "Test your changes" [3]. Every concrete instruction like that points into package.json and can go stale. Rulestack's rules carry 707 of them across 190 names, about 3.7 references per name [1][1]. The page's consistency advice asks authors to review "periodically" [15]. A pre-commit hook is also periodic, with a period of one commit.
The check is small and well made. It loads the scripts from package.json into a set, walks Markdown under CLAUDE.md, .claude and docs by default, and matches `pnpm x`, `npm run x` and `yarn x` with one regex [7][10]. Any name that is neither a script nor on a short builtin list is printed with its file and line, and the process exits 1 [7][9]. That exit code lets the same 26 lines run as a pre-commit hook or a CI job [7][8]. It rereads package.json and every target file on each run. Installed as a hook, it fails the commit that deletes a still-referenced script, before anyone edits the rule [5].
The default targets include .claude, and on Claude Code that directory holds more than rules. Agent worktrees live under .claude/worktrees/, and each one is a full copy of the repository [11]. The first unpruned run scanned 12,800 Markdown files and reported 25 missing names, 19 of them only from those snapshots and their fixtures [11]. The remaining six appeared in files outside the snapshots [2]. "Scope the input to files that actually load into the agent's context and the noise disappears," the authors wrote [12].
A clean run proves that each name exists. It does not run the script. A skill body is mostly "run this, read that field, then run this" [14], and a live script whose output lost that field still passes. The regex also captures only the first word after the command [10]. In `pnpm run deploy` that word is `run`, which is on the builtin list, so `deploy` is never compared with package.json [3]. The list also exempts `build`, `start`, `lint` and `test` [9]. A reference to any of those passes whether or not package.json defines it [4].
These figures come from one repository on 2026-09-22, run with Claude Code 2.1.278 [13]. They transfer to a repo whose rules name commands literally through pnpm, npm run or yarn. For that kind of repo I think this is the right first check to add after shape tests. I'd add a second capture group for `run` before trusting a result of zero.
What to watch
- Whether Rulestack extends the check from command names to output shape, testing the fields its skills tell the agent to read.
- Whether the regex gains a capture for pnpm run <x> and yarn run <x>, closing the builtin-list gap.
- Whether Claude Code's memory documentation moves its consistency advice from periodic review to a scripted reference check.