feat: make missing hook prerequisites skip visibly (10 plugins) - #328
Conversation
Land the shared visible-skip mechanism in lib/hook-utils.sh — jq-free
notice emitters (hook::json_escape, hook::emit_channels,
hook::emit_skip_notice, hook::emit_system_message), a once-per-session
dedup gate (hook::notice_once, markers under
${CLAUDE_PLUGIN_DATA}/skip-notices), and a jq gate (hook::require_jq) —
resync all 10 carrying plugins, and convert every silent
missing-prerequisite skip in the hook fleet into a once-per-session
notice on both the agent (additionalContext) and user (systemMessage)
channels. All 10 READMEs now declare the full hook runtime (Bash via
Git Bash on Windows, jq, plugin CLI) with each prerequisite's absence
classification.
Part of #317 (tranche A).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0aba999f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex review P2: the Write|Edit matcher is broader than each hook's file filter, so with jq missing the notice fired for out-of-scope edits (a README edit surfacing "workflow lint skipped"). New hook::raw_file_path extracts tool_input.file_path from the raw input without jq; the six extension-scoped hooks (actionlint, bash-format, biome-format, ruff-format, markdown-format, powershell-format) now run that pre-filter before hook::require_jq. eol-normalizer (all files) and desktop-notification (Notification event) stay unconditionally gated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
) Tranche C of the prerequisite-visibility wave: every dim-10 cross-platform FAIL from the conformance audit — undeclared shell/coreutils assumptions get a stated requirement with the Git Bash Windows path, and the two structural Windows hazards get real fixes. ## Per-plugin changes | Plugin | Version | Change | |---|---|---| | mcp-tools | 0.2.1 | README Requirements section added (Bash + coreutils, jq, Git Bash path). CHANGELOG created. | | ai-briefing | 0.5.1 | README states the POSIX-shell requirement of `setup --with-build-deps` (script's platform gate already accepts MINGW/MSYS/CYGWIN — the doc prong was the gap). | | education | 0.3.1 | README corrected from "none beyond Claude Code": declares `sha256sum`/`shasum`, `realpath`, `tr`, `sed` with the Git Bash path (bundles all of them). CHANGELOG created. | | prototype | 0.2.4 | README declares the detection-script Bash requirement and documents the no-Bash degrade honestly (pre-computed context echoes "none detected"; the skills read the host project directly — verified against `skills/logic/SKILL.md`, which never branches on the detection output). | | code-tidying | 0.4.3 | README declares Bash 4+ (`mapfile`, case-conversion expansions) with the Git Bash path; notes the scripts' existing CRLF/drive-letter handling. | | knowledge | 0.7.1 | README declares the Git Bash shell mechanics (`book-distill` hashes with `sha256sum` on every run) and the EPUB branch's `unzip` requirement (not bundled with Git Bash); PDF-only use needs neither. | | event-storming | 0.5.3 | Simulation teardown phrased shell-agnostically at both sites (`rm -rf` on POSIX/Git Bash, `Remove-Item -Recurse -Force` on PowerShell) instead of unconditional `rm -rf`. | | repo-hygiene | 0.2.1 | `clean`'s PreToolUse destructive-guard hook moves from a bare-`.sh` command to the interpreter-named exec form (`command: "bash"`, `args: […]`) — the doctrine-prescribed Windows-safe spawn shape, matching disk-hygiene's existing `command: "python"` precedent. | ## Verification - `scripts/validate-plugins.sh`, `node scripts/generate-catalog.mjs --check`, and `node scripts/validate-plugin-contracts.mjs` (1429 files) green. - `plugins/repo-hygiene/.../destructive-guard.test.sh`: 30/30 passing after the hook-form change. - The dim-9 work landed separately: hook channel in #328, skill channel in #329. With this tranche, every dim-9/dim-10 FAIL enumerated in #317 is addressed (actionlint's and work-items' dim-10 items were folded into those PRs). ## Related Closes #317 (together with #328 and #329, which carry tranches A and B). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…equisite-visibility wave) Second integration: main's prerequisite-visibility wave re-bumped the carrying plugins, so re-reconcile against the newer versions. - lib/hook-utils.sh: main's jq-free prerequisite-visibility helpers and this branch's argv parser (incl. heredoc handling) both retained. - Carrying plugin versions resolved above new main: guardrails stays 0.6.0 (> main 0.5.1); nine others -> main + patch (check-bump gate). - CHANGELOGs: stack this branch's hook-utils-refresh entry above main's new prerequisite-visibility entries; guardrails [0.6.0] over [0.5.1]. - Catalog regenerated. Contract suites 187 + 75 + lib 65 green; validate, check-bump, drift all pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 42s —— View job PR #328 Review
|
|
Claude finished @kyle-sexton's task in 2m 33s —— View job PR #328 Review
This is a well-executed PR. The shared mechanism is correctly designed under the jq-free constraint, fail-open behavior is consistently applied, and the test suite is thorough. The Codex P2 bug (early jq notice for out-of-scope edits) was correctly fixed in b8741b3 and is confirmed in the current state. All findings below are minor — no blocking issues. P2 Fix Verification (b8741b3)Confirmed correct. All six extension-scoped hooks (actionlint, bash-format, biome-format, ruff-format, markdown-format, powershell-format) now run a jq-free applicability pre-filter before
eol-normalizer correctly omits a pre-filter (it applies to every file), and desktop-notification uses The Correctness1. [[ -f "$marker" ]] && return 1
: >"$marker" 2>/dev/null
return 0When two hooks fire in parallel on a multi-file save, both can pass the 2. local marker="$dir/${key}.${session}"
3. Duplicate extension filters per plugin — e.g., Every extension-scoped hook now has two extension checks. The duplication is intentional (the pre-filter prevents false jq notices), but both Security
The ranges Session ID extraction is correctly sandboxed — The regex extracts raw session_id then sanitizes to desktop-notification C0 stripping on untrusted Correct: Test CoverageThe 61-test suite is solid. Test 16b ( Two minor gaps not blocking:
CLAUDE.md / Plugin-Philosophy Alignment
The deliberate asymmetry in powershell-format (jq-absent → visible; pwsh/PSScriptAnalyzer absent → silent) is correctly classified as N/A at SummaryNo blocking issues. Three actionable low-severity items, unchanged from the pre-b8741b3 review:
|

Tranche A of the prerequisite-visibility wave: land the shared visible-skip mechanism in
lib/hook-utils.sh, resync all 10 carrying plugins, and convert every silent missing-prerequisite skip in the hook fleet into a once-per-session notice on both channels.Shared mechanism (
lib/hook-utils.sh)Doctrine (PLUGIN-PHILOSOPHY § Prerequisites and failure behavior): absence must be surfaced to BOTH the agent and the user; a silently skipped feature is a defect. New helpers, all jq-free by design (the most common missing prerequisite is jq itself):
hook::json_escape— pure-bash JSON string escaper (backslash/quote/\n\r\tnamed, residual C0 dropped viatrwith a no-trfallback).hook::emit_channels <event> <ctx> <sysmsg>— composesadditionalContext(agent) andsystemMessage(user, verified against the current hooks doc as the user-visible universal field) into ONE JSON document — a hook's stdout is parsed as a single doc, so a run with lint findings AND a pending notice must not print twice.hook::emit_skip_notice/hook::emit_system_message— thin wrappers (same message both channels; systemMessage-only for events without an additionalContext channel, e.g. Notification).hook::notice_once <key> <input>— once-per-session dedup via a marker under${CLAUDE_PLUGIN_DATA}/skip-notices(session_id regex-extracted from the raw input, no jq; 7-day marker prune; fails open toward visibility when no data dir is available).hook::require_jq <event> <plugin> <input>— jq gate: visible once-per-session skip + exit 0.Per-plugin behavior changes
.editorconfig-opt-in notices (no opt-in stays quiet); jq gate; findings + notice compose into one JSON docAll 10 READMEs now declare the full hook runtime (Bash via Git Bash on native Windows, jq, plugin CLI) with each prerequisite's absence classification — the fleet-wide dim-10 README gap flagged on actionlint.
Verification
lib/hook-utils.test.sh: 61 passing (new: escaper, emitters incl. empty-PATH emission, combined-channel composition, notice_once dedup across sessions/keys/fail-open, require_jq gate with a coreutils-but-no-jq stub PATH — the real missing-jq shape).skippedstill emitted): actionlint 34, bash-format 38, biome-format 3 (binary-independent, runs even where Biome is absent), ruff-format 50, eol-normalizer 35, desktop-notification 54, markdown-format 65 — all green locally, plus fullscripts/run-plugin-tests.sh.scripts/sync-hook-utils.sh --checkand--check-bump origin/maingreen;scripts/validate-plugins.shgreen; ShellCheck clean on all changed shell files.systemMessage= user-visible universal output field;session_idin every hook input) and https://code.claude.com/docs/en/plugins-reference (CLAUDE_PLUGIN_DATAexported to hook processes, survives updates).Deliberately out of scope
catstdin readers ontohook::buffer_stdin(deferred hardening noted in the feat: migrate hook kill switches to native userConfig (10 plugins) #323 review) — orthogonal stdin-semantics change; keeping this PR single-purpose.Related
Part of #317. No linked issue is closed by this PR (tranches B and C remain before #317 can close).
🤖 Generated with Claude Code