plugin-inventory counts hooks/README.md and hooks.json as executable LOC, inflating script_coverage #10
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found during the Phase 7 acceptance sweep (kotkan/claude-plugin-inference-arbitrage#7). Not fixed there deliberately — it shifts
script_coveragefor every target, and the S1–S8 numbers had just been stabilised. Small and self-contained.What
walk_hooksinbin/plugin-inventoryadds every regular file underhooks/to the hook list, andbuild_aggregatesums their line counts intoscript_loc:So documentation and configuration are counted as shipped executable volume:
memoryhooks/README.md,hooks/hooks.jsonanxioushooks/hooks.jsonworktree-disciplinehooks/hooks.jsonmemoryalso listsREADME.mdas a hook with"event": "unknown"in the JSON output, which is simply wrong — it is not a hook.Why it matters
FR-2.3 defines script coverage as prose-instruction volume vs shipped executable volume. A README is prose; counting it on the executable side of that ratio is a category error, and it biases the metric in the one direction that matters — it makes a prose-heavy plugin look better cut than it is.
script_coverageis one of the four static smells theoffload-auditskill andoffload-analystgather candidates from, so the error propagates into candidate selection.The effect is small on the targets audited so far (a few percent), but it scales with how much documentation a plugin keeps beside its hooks.
Fix
Filter
walk_hooksto actual hook scripts. The information needed is already parsed there —.claude-plugin/hooks.jsonis read to buildevents_by_file. Reasonable rule: count a file only if it is referenced byhooks.json, or is executable, or has a#!shebang; keephooks.jsonitself out ofscript_locregardless, since it is configuration rather than logic.Note
detect_langalready reads shebangs forbin/, so the same test is available.Test
tests/inventory.test.sh— add a fixture hook directory containing one real hook script, aREADME.mdand ahooks.json, and assertscript_loccounts only the hook script, and that the README does not appear inhooks[].Watch out
tests/calibration/mask-worktree-audit.pyrecomputesscript_locand must keep matchingbuild_aggregate— that consistency was itself the second bug fixed in105e9f5, and the regression test added there (script_loc still counts hooks) will catch a divergence.Confirmed real, fixed in
eb4b945.Root cause:
walk_hooksinbin/plugin-inventoryadded every regular file underhooks/to the hook list with no filter, andbuild_aggregatesummedhooks[].locstraight intoscript_loc.hooks/README.mdand.claude-plugin/hooks.jsonare prose/config, not shipped executable logic, so they inflatedscript_coveragein the direction that makes a plugin look better cut than it is — exactly as the issue described. Issue body was accurate as filed; nothing since (checkedgit log --oneline -- bin/plugin-inventory: last touches were07b5637/caa2a8c/bf88742, all Phase 2, well before this issue was opened 2026-07-29T18:16 — none of today's other fixes (6b634fd,e3b67e9,ba445cf) touched this file).Repro before fix (
~/projects/claude-plugins/memoryhooks dir has README.md + hooks.json alongside 3 real hooks):Matches the issue's table exactly: 75+40=115 wrongly-counted LOC for
memory. Same foranxious(hooks.json=51) andworktree-discipline(hooks.json=50).Fix:
walk_hooksnow counts a file only if it is referenced by.claude-plugin/hooks.json's command paths, is executable (os.access(X_OK)), ordetect_lang()recognizes a shebang/.pyextension — matching the rule the issue proposed.hooks/hooks.jsonis unconditionally excluded regardless of those checks, since it's configuration.README.mds with no shebang/exec bit are now dropped entirely (no more"event": "unknown"bogus hook entries for them).After fix, same repro:
1202 − 115 = 1087, exactly the delta the issue predicted. Checked
anxiousandworktree-disciplinetoo — hooks.json (51/50 LOC) no longer counted, real hook scripts unaffected (anxious'stest-issuer-ladder-guard.pystays counted since it's executable — that's a legitimate call under the issue's own proposed rule, not a new bug).Test: added
hooks/README.mdandhooks/hooks.jsontotests/fixtures/sample-plugin, plus three new assertions intests/inventory.test.sh(onlyguard.shappears inhooks[],script_locexcludes the README, LOC still matches). Ran the full suite:All green, no FAIL lines (verified via
grep -iE "fail|error"— only benign test-label matches like "T4 fails -> low").Watch-out from the issue (
mask-worktree-audit.pymust keep matchingbuild_aggregate): verified — it recomputesscript_locby summingd["hooks"][].locfrom the already-filtered JSON output, so it stayed consistent automatically with no code change needed there. Confirmed live:Bumped
plugin.jsonto 0.6.2 (patch, per this repo's convention that a shipped fix requires a version bump for cache invalidation). Committed aseb4b945, pushed tomain, working tree clean and in sync withorigin/main.No new bugs surfaced during this work. Nix/worktree/deploy standing rules don't apply here — no Nix build step, no deploy step, work done directly on
mainin the primary checkout (not a worktree).Fixed by
eb4b945(walk_hooks now excludes hooks.json unconditionally and only counts other hook files if referenced by hooks.json, executable, or shebang-detected). Verified: full test suite (bash tests/run-all.sh) passes clean on current origin/main (1a14456), including the 3 new regression assertions in tests/inventory.test.sh guarding this fix.