ClaudeMods
☰
JA
● 0 人がオンライン ・閲覧 0 回
スポンサー作品を投稿
GitHub リポジトリ · 投稿者 mickzijdel

dev-hooks

dev-hooks マーケットプレイスに属し、言語やプロジェクトを問わない開発ワークフロー用 hooks と、hooks が参照する関連スキルをまとめたプラグインです。

mickzijdel@mickzijdel

mickzijdel/dev-hooks/tree/main/plugins/dev-hooks

翻訳済み

この mod について

coding-onboarding、thinking-tools、writing とともに dev-hooks マーケットプレイスを構成します。Hooks は UserPromptSubmit(プロンプトの記録、意図のチェック、データを先に集めてから設計するためのリマインダー)、PreToolUse Bash(危険なコマンドと秘密情報の漏えい対策)、PostToolUse Bash(CI の監視、ワークツリーの用意)、PostToolUse Write/Edit(lint、依存関係の鮮度、スキャフォールディング、Dockerfile hadolint、秘密情報の平文、ポップオーバーの位置、GitHub Actions の pin チェック、インライン SVG、マイグレーションの安全性、a11y、SQL インジェクション、エラーの握りつぶし)、Stop hooks(検証、デバッグの残骸、テスト不足、レビュー、コメントの圧縮、メモリの取得、大きな変更の警告、変更概要、スクリプトの保存)、SessionStart hooks(開発環境のリマインダー、ドキュメント索引、スクリプトライブラリ索引)をカバーします。

インストール

まず作者の README で marketplace とプラグイン名を確認してください。コマンドはリポジトリの構成によって変わる場合があります。

claude plugin marketplace add mickzijdel/dev-hooks
claude plugin install dev-hooks
原文 / README

dev-hooks

A Claude Code plugin bundling polyglot, project-agnostic dev-workflow hooks plus the companion skills the hooks point at. Each hook script detects the project's own toolchain (Ruby/Rails, JavaScript/TypeScript, Python) — there is nothing to configure.

Part of the dev-hooks marketplace, alongside coding-onboarding, thinking-tools, and writing.

Contents

Hooks

| Event | Script | Purpose | |-------|--------|-------| | UserPromptSubmit | prompt-log.sh | Append one JSON line per user prompt (timestamp, repo cwd, session id, prompt length, first 500 chars) to ~/.claude/automation-review/prompts.jsonl (created 0600) — the cross-repo data source the thinking-tools weekly-automation-review skill clusters to spot repetitive requests worth automating. Credential-shaped values (vendor token prefixes, JWTs, private-key blocks, SECRET=-style assignments) are redacted to [REDACTED] before the line is written. Local-only, silent, never blocks the prompt. Opt out with DEV_HOOKS_PROMPT_LOG=false. | | UserPromptSubmit | intent-check-reminder.sh | When a prompt is a task (an imperative change request — "add…", "refactor…") but states neither a why (intent) nor non-goals (what's out of scope), inject one advisory nudging Claude to briefly restate its assumed goal + out-of-scope before substantial work, and to ask clarifying questions when the task is ambiguous or high-stakes. Deliberately conservative — skips questions, follow-ups, one-liners, and prompts that already carry intent or scope markers, so it fires only on a genuinely thin brief. Advisory, never blocks; pairs with the agent-brief skill. Opt out with DEV_HOOKS_INTENT_CHECK=false. | | UserPromptSubmit | data-before-design-reminder.sh | When a prompt asks to build a surface that displays stored data (a build verb plus a display surface — card, panel, dashboard, table, report, profile, badge, tile, widget, …), inject one advisory pointing at the data-before-design skill: profile every field the view will show (non-null count, distinct values, length min/mean/max, coverage, cardinality per parent, date presence) and state the numbers before writing markup, then mock several options from the busiest, the typical and the empty record and let the user pick. Conservative — stays silent for pure restyling (colour/padding/font/spacing/alignment) and fires once per session. Advisory, never blocks. Opt out with DEV_HOOKS_DATA_BEFORE_DESIGN=false. | | PreToolUse (Bash) | dangerous-command-guard.sh | Block the catastrophic, irreversible few before they run: wipe the disk/home (rm -rf /), fork bomb, format a filesystem (mkfs), overwrite a raw block device (dd of=/dev/…), chmod -R 777 /. Flags and targets are judged per simple command, split the way the shell would (quotes honoured; $(…), backticks, bash -c '…', eval and a heredoc fed to a shell split out), so cd ~ && rm -rf build/ isn't read as rm -rf ~ and rg "fnox get|bws secret get" isn't read as a secret read. Risky-but-legitimate commands (rm -rf a path, git reset --hard, force-push, curl … \| bash, sudo) are not gated — the normal permission flow already prompts on them, and Claude Code's auto-mode classifier catches them besides. Aimed at beginners not running auto mode, whose agents shouldn't be able to run a machine-wiping command on their say-so. The deny is configurable with DEV_HOOKS_GUARD_DENY (deny default / ask / allow) for advanced users. Opt-in extra (DEV_HOOKS_GUARD_MAIN=1, seeded by the coding-onboarding plugin's getting-started skill): ask before committing/pushing straight to main/master. Separately, it blocks a command that would print a secret value into the transcript — cat .env, cat config/master.key, bws secret get, op read, echo $GITHUB_TOKEN, and credential probes such as git credential fill, a helper's get, gh auth token, gh auth status -t, secret-tool lookup and cat ~/.git-credentials — since a leaked value can only really be fixed by rotating it; template files (.env.example), *.pub halves, inject-only wrappers (fnox run), source .env, counting greps, stdout sent to /dev/null or a file, and a value captured into a variable (v=$(fnox get X)) stay silent (2>/dev/null hides only stderr, so it doesn't). Configurable with DEV_HOOKS_GUARD_SECRETS (deny default / ask / allow) — it asked until 2.40.0, until an auto-mode classifier approved a ${VAR:-UNSET} presence check and leaked a live token. Opt out of the whole hook with DEV_HOOKS_BASH_GUARD=false. | | PostToolUse (Bash) | ci-watch-reminder.sh | After a git push in a repo with GitHub Actions workflows, remind Claude to watch the CI run so a red pipeline comes back to it instead of going unnoticed — watch to completion when idle, or background the watch (or hand it to a sub-agent) when there's more work, rather than pushing and forgetting. Reminder-only: the hook never runs gh; Claude does. Fires on real git push invocations (git push, git -C dir push, … && git push), not git config …pushurl/switch/pushall, and only when .github/workflows/ exists. Advisory, never blocks. Stands down where the mod's CI watch (below) watches the run itself (it sets DEV_HOOKS_CI_WATCH_SESSION to the session id). Opt out of both with DEV_HOOKS_CI_WATCH=false. | | PostToolUse (Bash) | worktree-provision-reminder.sh | After git worktree add, check whether the new worktree is missing gitignored state the main checkout has (Rails storage/ blobs, config/credentials/master.key, .env, service-account JSON, uploads) and point at [[worktree-setup]]'s setup-worktree.sh. Compares exact ignored paths, not top-level dirs — storage/ exists in a fresh worktree because storage/.keep is tracked, while every blob is absent, so the app boots and then 500s on each image as if the view were broken. Load-bearing entries lead the report; editor/tool caches are excluded. Silent when the worktree is correctly provisioned. Advisory, never blocks. Opt out with DEV_HOOKS_WORKTREE_PROVISION=false. | | PostToolUse (Write|Edit|MultiEdit) | lint-on-edit.sh | Auto-fix/format the file Claude just wrote using the linter this project configures (RuboCop/Standard, herb/erb_lint for ERB, Biome/Prettier/ESLint, Ruff/Black). Safe fixes only, never blocks. For Python it marks the code-deleting rules unfixable (F401 unused import, F841 unused variable) so a beat-early import/binding isn't stripped before the line that uses it lands — verify-work.sh/CI still flag them; override with DEV_HOOKS_RUFF_KEEP (set empty to restore full autofix). | | PostToolUse (Write|Edit|MultiEdit) | latest-deps-reminder.sh | When Claude writes a dependency manifest (requirements.txt, package.json, Gemfile, pyproject.toml, …) or hand-writes a lockfile, remind it to verify the versions are current (training data goes stale) with the right lookup command per ecosystem, or to regenerate lockfiles via the package manager. On manifest edits it also nudges Claude to keep the README/CLAUDE.md key-package versions in sync (creating those docs if missing). Advisory only, never blocks; fires once per session per ecosystem. | | PostToolUse (Write|Edit|MultiEdit) | scaffold-reminder.sh | When Claude creates a new project manifest or framework entrypoint by hand (Gemfile, package.json, pyproject.toml, Cargo.toml, go.mod, mix.exs, composer.json, build.gradle/pom.xml, manage.py, config/application.rb, …), remind it to run the framework's official generator (rails new, npm create vite@latest, django-admin startproject, cargo new, …) instead of scaffolding from memory — and to check the framework's current stable release and the generator's current flags first, unless the user pinned a version. Write-tool only; files git already tracks are skipped. Advisory only, never blocks; fires once per session. Opt out with DEV_HOOKS_SCAFFOLD=false. | | PostToolUse (Write|Edit|MultiEdit) | dockerfile-reminder.sh | When Claude writes a Dockerfile/Containerfile, run hadolint on it and feed the findings (or a clean pass) back to Claude, plus a layer-ordering nudge that points at the dockerfile skill. If hadolint isn't installed, falls back to a once-per-session ordering/gotchas reminder. Advisory only — reports every time, never blocks. | | PostToolUse (Write|Edit|MultiEdit) | secret-plaintext-reminder.sh | When Claude writes what looks like a plaintext secret value (a named API_KEY/SECRET/TOKEN/PASSWORD assignment to a real literal, a private-key block, or an AWS key id), nudge it to migrate to fnox via the env-to-fnox skill instead of committing the value. Fires at write time (before gitleaks would at commit). Env-var refs and obvious placeholders are ignored. Advisory only, never blocks; fires once per session. | | PostToolUse (Write|Edit|MultiEdit) | popover-reminder.sh | When Claude writes popover/tooltip/dropdown/menu UI (a frontend file — the shared extension list, see inline-svg-reminder.sh notes — that has a popover/tooltip/dropdown controller filename, role="tooltip"/the popover attribute, an @floating-ui/popper/tippy import, a data-controller naming one, or a tooltip/popover/dropdown class/data-attribute), nudge it to use a collision-aware positioner (flip + shift) rendered in the top layer/a portal instead of hand-rolled top/left math, and point at the popovers-tooltips skill. Advisory only, never blocks; fires once per session. Opt out with DEV_HOOKS_POPOVER=false. | | PostToolUse (Write|Edit|MultiEdit) | ci-action-ref-reminder.sh | When Claude writes/edits a GitHub Actions workflow (a *.yml/*.yaml pinning uses: owner/repo@ref), point it at the github-actions skill's supply-chain checklist (SHA-pin every action, read-only GITHUB_TOKEN, no untrusted input in run:) and have it verify the pins with the bundled check_action_refs.sh (which resolves each pin's # vX.Y.Z comment via git ls-remote and fails on a missing/mismatched tag); the hook itself never hits the network. Advisory only, never blocks; fires once per session per file. | | PostToolUse (Write|Edit|MultiEdit) | inline-svg-reminder.sh | When Claude hand-writes inline SVG into a frontend file (an <svg> blob with real drawing content — <path>/<circle>/<rect>/… or long d="M…" path data — or a data:image/svg+xml URI), feed a correction back (exit 2, every occurrence): use the project's icon library (named from package.json/Gemfile when found), else extract to a dedicated .svg file/sprite and reference it. Good patterns stay silent: <use href> sprite refs, writing .svg files, <img src="x.svg">, markdown, test files, data-driven chart markup (<rect x={…}>), and pre-existing SVG (Writes deduped against HEAD, Edits against old_string). Opt out with DEV_HOOKS_SVG_INLINE=false. | | PostToolUse (Write|Edit|MultiEdit) | migration-safety-reminder.sh | When Claude writes a database migration (Rails db/migrate/*.rb, Django <app>/migrations/*.py, Alembic …/versions/*.py), nudge it to check safe-migration practice — reversibility (change/up+down/downgrade), no data backfill inside a schema migration, lock-safe column adds (nullable → batched backfill → constraint), and concurrent index creation (algorithm: :concurrently + disable_ddl_transaction!, CREATE INDEX CONCURRENTLY, AddIndexConcurrently). Advisory only, never blocks; fires once per session. Opt out with DEV_HOOKS_MIGRATION=false. | | PostToolUse (Write|Edit|MultiEdit) | a11y-reminder.sh | When Claude writes frontend markup with common accessibility gaps — an <img> with no alt, an icon-only <button>/<a> with no accessible name, a click handler on a non-interactive <div>/<span>, or an unlabeled form <input> — flag them and point at the accessibility skill. Scans only what the call adds; heuristic. Advisory only, never blocks; fires once per session. Opt out with DEV_HOOKS_A11Y=false. | | PostToolUse (Write|Edit|MultiEdit) | sql-injection-reminder.sh | When Claude writes SQL with a value interpolated straight into the query string (a Python f-string, Ruby #{}, or string concatenation around SQL keywords), nudge it toward parameterized queries / ORM bind variables. The safe %s/:name/? placeholder styles are not flagged. Advisory only, never blocks; fires once per session. Opt out with DEV_HOOKS_SQL_INJECTION=false. | | PostToolUse (Write|Edit|MultiEdit) | error-swallow-reminder.sh | When Claude writes a handler that silently swallows the error (a Python bare except: or except …: pass, an empty JS/TS catch {}, or an empty Ruby rescue … end), nudge it to catch the specific exception and handle/log/re-raise instead. Scans only what the call adds. Advisory only, never blocks; fires once per session. Opt out with DEV_HOOKS_ERROR_SWALLOW=false. | | Stop | verify-work.sh | On stop, detect changed code files and run the project's linters/tests (RuboCop, herb + brakeman for Rails, Minitest/RSpec, Ruff/pytest, ESLint/JS tests). Blocks the stop with real failures so Claude fixes them before finishing — these re-block on every stop until fixed, including the continuation another Stop hook forced (capped at 3 blocks in a row there). When code changed but no tooling is recognised, it nudges Claude to check manually at most once per session, then lets it finish (no Stop loop). Turn the whole hook off with DEV_HOOKS_VERIFY=false (per-repo/user). | | Stop | debug-leftover-reminder.sh | On stop, flag debug statements Claude newly introduced this session (console.log/debugger, binding.pry/byebug/Ruby p, breakpoint()/pdb) — diffed against HEAD so pre-existing lines are ignored — and feed them back (blocks the stop) to strip before finishing. Test files excluded. Fires at most once per session. | | Stop | missing-test-reminder.sh | On stop, if Claude added a new source file this session with no matching test (*_spec.rb/*_test.rb, test_*.py/*_test.py, *.test.*/*.spec.*), nudge it (blocks the stop) to add one. Counts files added by commits made during the session as well as uncommitted ones, so committing as you go doesn't hide them. Skips test files, low-value targets (barrels, type defs, config, migrations, __init__/conftest), and vendored/generated code (dirs from the repo's .jscpd.json, plus minified *.min.*). Fires at most once per session. | | SessionStart | dev-env-reminder.sh | If the repo is yours and the dev-env standard applies but isn't met (missing mise/hk/CI/gitleaks, or behind the version stamp), nudge Claude to flag it and offer the dev-env-setup skill. Advisory only — never edits. Owner-gated (see env vars below); opt out per repo. | | SessionStart | docs-context.sh | If the project has a docs/ or doc/ directory containing Markdown files, emit a brief index (titles + optional descriptions from YAML frontmatter) so Claude knows where documentation lives and can consult the right files when working on related features. Docs whose frontmatter carries an opt-in stale_after: YYYY-MM-DD (past) or status: stale/deprecated/draft get their line flagged with ⚠ so Claude doesn't blindly trust an out-of-date doc; docs without the fields are unaffected. Advisory only — never blocks. Opt out with DEV_HOOKS_DOCS_CONTEXT=false. | | SessionStart | script-index.sh | List the custom CLI tools in your saved script library — each executable shebang script's path + its # short-description: line — so Claude knows what already exists and reaches for it instead of re-solving the problem, like a lightweight skill index. The library is DEV_HOOKS_SCRIPT_DIR, a colon-separated list of roots like PATH (default ~/.local/bin), each scanned recursively so a cloned scripts repo with subdirectories works. Scripts with no # short-description: are listed under a placeholder telling Claude to run <path> --help and ask you to add one. Hide scripts that aren't your own tools (installed/third-party CLIs, app launchers) with DEV_HOOKS_SCRIPT_IGNORE — a colon-separated list of globs matched against each script's basename or full path (e.g. *vocalinux*:gext). Never executes a script (so no --help side effects at startup). Paired with the script-library skill. Advisory only — never blocks. Opt out with DEV_HOOKS_SCRIPT_INDEX=false. | | Stop | plan-reminder.sh | If .claude/current_plan.md exists and is stale, remind Claude to update the multi-session plan before ending. Nudges once per version of the plan, not once per stop. | | Stop | review-reminder.sh | On stop, if this session touched code files but no code review ran since, remind Claude (blocks the stop) to run a review and keep iterating until it comes back clean. "This session's work" counts uncommitted changes and commits made since the session started, so the commit-as-you-go workflow's clean tree no longer silences it. "Already reviewed" is a tool-use scan for /code-review, a code-reviewer agent, requesting-code-review, or a dispatched agent whose description says "review" (how a subagent-driven session reviews) — not a bare name grep, which would match the transcript's skill listing in every session. Re-arms rather than firing once: while no review has run it asks at every stop (once only for a change under 10 added lines, up to 3 times above that, so a Claude that cannot review still terminates); once one has run, the session's added-code-line total becomes a baseline and the hook speaks again when it grows by ≥ 20 — code written after a review makes that review stale. Opt out with DEV_HOOKS_REVIEW=false. | | Stop | compress-comments-reminder.sh | On stop, if the session's work added a noticeable number of comment lines to code files (≥ 3, counting added lines in one diff from the last commit before the session started — the transcript's first timestamp — to the working tree, plus untracked code files, so commit-as-you-go sessions still trigger and rewriting this session's own comments is not growth; shebangs and directive comments like shellcheck/noqa/eslint don't count), remind Claude (blocks the stop) to run the compress-comments skill — delete comments that restate the code, compress the rest. Re-arms rather than firing once: the comment total at each reminder is stored per session, and the hook fires again whenever the total grows by ≥ 3 — one large commit or purely uncommitted edits alike. An unchanged total stays silent (no Stop loop), a dropped total (cleanup) rebases the baseline, and a skill run seeds it (detected by tool-use scan, not a bare name grep — the transcript's skill listing names every installed skill). Opt out with DEV_HOOKS_COMPRESS_COMMENTS=false. | | Stop | memory-reminder.sh | On stop of a substantial session (≥ 6 human turns), remind Claude (blocks the stop) to capture durable, non-obvious learnings into its file-based memory — memory dir only, never CLAUDE.md, with an explicit "nothing worth saving" escape hatch. Fires at most once per session, and not at all once a memory file was written this session (including by a subagent, when the mod runs the Stop hooks). Opt-in via DEV_HOOKS_MEMORY=1, or auto-enabled once you use Claude's memory feature anywhere. | | Stop | big-change-reminder.sh | On stop, if the working tree holds a very large uncommitted change (default: ≥ 25 files or ≥ 800 added lines), nudge Claude (blocks the stop) to slow down — commit the working pieces in small, focused commits, run tests, get a review, and consider plan mode for the next chunk. Stays silent when a multi-session plan is already in progress (.claude/current_plan.md). Aimed at beginners, for whom a giant uncommitted diff is hard to review and easy to lose. Fires once per session. Thresholds tunable via DEV_HOOKS_BIG_CHANGE_FILES/DEV_HOOKS_BIG_CHANGE_LINES; opt out with DEV_HOOKS_BIG_CHANGE=false. | | Stop | change-summary-reminder.sh | On stop, if the session changed a meaningful number of files (default: ≥ 3), nudge Claude (blocks the stop) to give a short, plain-language summary of what changed in each file — an aid for reviewing the session's work without re-reading the raw diff, for technical and non-technical readers alike. Fires once per session. Threshold tunable via DEV_HOOKS_CHANGE_SUMMARY_FILES; opt out with DEV_HOOKS_CHANGE_SUMMARY=false. | | Stop | save-script-reminder.sh | On stop, if Claude wrote a script this session (a Write of shebang-prefixed content, wherever it landed — scratchpad, /tmp, or inside a project repo; only scripts already in a library root are excluded), nudge it (blocks the stop) to decide per script: a broadly useful tool gets genericized to the saved-script standard (PEP 723 + uv run shebang + # short-description: + chmod +x) and added to a library root (or a subdirectory) so the script-index hook surfaces it next session — even one already committed to a repo can be worth promoting — while a genuinely task-specific or throwaway script is left where it is. Points at the script-library skill. Fires at most once per session. Library roots come from DEV_HOOKS_SCRIPT_DIR; opt out with DEV_HOOKS_SAVE_SCRIPT=false. |

Skills

The companion skills the hooks point at:

| Skill | Use when | |-------|----------| | dev-env-setup | Auditing/setting up a repo against an opinionated dev-env standard, version-tracked via DEV_ENV_VERSION: mise pinning the toolchain, an hk pre-commit hook running linters/tests + gitleaks + zizmor & actionlint (GitHub Actions security + correctness checks, v18) (Rails gets the v17 ERB + security + correctness suite — herb, brakeman, bundler-audit, importmap audit, strong_migrations, database_consistency, fasterer, rubocop plugins), a CI workflow mirroring those checks, a version-sync gate (v23) asserting that every file pinning a toolchain or service version — mise.toml, the .<lang>-version files, the Dockerfile ARGs, package.json's packageManager, and the compose/deploy/CI image: tags, with a floating mise spec resolved through mise.lock — names the same one, and that every CI setup step reads that pin rather than floating, hardcoding or omitting a version (v26) — a full release, with mise.lock always in the comparison (v27), a 4-day dependency cooldown (uv exclude-newer enforced on Python repos; Ruby/JS package managers documented), and project docs — a README.md and CLAUDE.md recording the project's pinned key-package versions, dispatching a subagent to create them when missing. Paired with the dev-env-reminder hook; trimmed from Nate Berkopec's dev-env-setup (kept/dropped rationale in the skill; per-version migration steps in references/upgrade-guide.md). Ships a fleet mode: after a standard bump, scripts/fleet_roster.sh discovers every DEV_ENV_VERSION-stamped repo live and the skill backfills them, canary first, one isolated agent per repo. | | github-actions | Writing, reviewing, or hardening a GitHub Actions workflow, or bumping a whole fleet's action pins. Carries the supply-chain security checklist (SHA-pin actions, read-only GITHUB_TOKEN, no pull_request_target/untrusted input in run:, OIDC for cloud creds, run actionlint + zizmor) in references/security-checklist.md, plus the fleet-wide SHA-pin/bump procedure (pinact run -u + check_action_refs.sh). Paired with the ci-action-ref-reminder hook; the dev-env CI templates ship pre-hardened to this standard. | | dependency-upgrade | Bringing a repo's dependencies up to the latest versions across JavaScript (npm/pnpm/yarn), Ruby (bundler), Python (uv/poetry/pip), and GitHub Actions. Reads changelogs/migration guides for major bumps, applies the code changes, and lands each step as its own commit — gating every commit on a green test suite and deferring any major it can't get green to a written report (plans/deferred-upgrades.md). Ships a read-only upgrade_inventory.sh preflight, a fleet mode (one isolated agent per repo), delegates the Actions part to github-actions, and respects dev-env-setup's 4-day cooldown. This is the upgrade counterpart to dev-env-setup (which only pins tooling + records versions). | | dockerfile | Writing/editing a Dockerfile — cache-friendly layer ordering (least→most frequently changed) and common gotchas (pinning, multi-stage, .dockerignore, non-root, exec-form CMD). Paired with the dockerfile-reminder hook; delegates linting to hadolint. | | popovers-tooltips | Building/fixing popovers, tooltips, dropdowns or menus that open off-screen or get clipped — Tailwind styles but doesn't position; use a collision-aware positioner (flip + shift) + top-layer/portal. Rails/Hotwire-first: Floating UI (@floating-ui/dom) in a Stimulus controller with autoUpdate and disconnect() cleanup (the Turbo gotcha), plus Tippy/Flowbite/Preline and the native Popover API. Paired with the popover-reminder hook. | | tailwind | Writing/fixing Tailwind CSS in any template (HTML, ERB/ViewComponent, JSX, Vue) — design tokens over magic numbers, taming class soup by extracting components (not @apply), dark mode (with WCAG-AA contrast), mobile-first responsive, and accessibility (focus-visible, sr-only, reduced motion). Framework-neutral; pairs with popovers-tooltips. Adapted from the MIT-licensed mattsears/rails-cto rails-cto-tailwind skill. | | design-process | Designing or restyling a UI — a landing page, app screen, dashboard, or "make it look less generic / more premium" — and any time the result looks AI-generated. The Discover → Define → Deliver process from Anshu Chimala's How to turn your AI into a world-class designer, kept close to verbatim: get variety from outside the model (a random seed string — String Seed of Thought — or a named wild inspiration), write a design brief before code and review it against the generic default, then a design-critic subagent in a fresh context that sees only screenshots (never the code or the target score) and scores against a studio bar or, better, a blind pick against real references — bounded to two rounds by default, stopping on bar met / a round that changed nothing / budget spent, since no published loop has ever exited on a score threshold alone. Then generated or real imagery in every visual slot by default — the route (key, MCP server, real assets, or the user's explicit type-only choice) is settled at the gate, not discovered when the critic stalls — plus video (chroma-keyed loops, keyframe-interpolated scroll transitions; keys in a gitignored env file or fnox), and a subtraction pass — remove glows, gradients, redundant labels, filler copy, custom controls worse than native — before verifying in a real browser, then a final voice pass over every user-visible string with the writing plugin's voice-profile skill (its baseline rules plus humanizer when no profile exists). references/anti-slop.md consolidates the recurring rules from the best-rated public design skills (Anthropic's frontend-design, impeccable, hallmark, Emil Kowalski's motion rules, interface-design, Refactoring UI, Vercel's guidelines) with severity ranked by who notices, plus typography, colour, layout, motion, and craft-floor rules; the brief always wins, and a tell is judged by intent, not appearance. Pairs with tailwind, accessibility, and popovers-tooltips. | | data-before-design | About to build, restyle or lay out anything that displays stored data — a card, panel, detail view, dashboard, table, report, profile page or summary strip. Three gates before any markup: name the decision the view serves (every field earns its place by changing it; prominence tracks decision weight, not which field was easiest to query), profile the field (non-null count, distinct values, length min/mean/p95/max, coverage across records, cardinality per parent, date presence, vocabulary, and what any displayed number can be compared against — a bare count with no median, target or prior period is unreadable) and state what you found in numbers, then mock several options from real records and let the person choose, drawing all six states (ideal, empty, partial, loading, error, done) plus the stress case — the record that is well-formed and painful, which volume extremes never surface. On a page with several cards, fixes one category table for the whole page before the first card: one colour, one name and one order per category, so a colour never means two things across cards. The check is a one-to-one (label, colour) mapping. Carries the rendering rules that fall out of the data: the empty state says what fills it, tabular figures and one precision per column, and a deliberate default sort. Paired with the data-before-design-reminder hook; complements design-process (aesthetics), dataviz (a single chart's colour and form) and popovers-tooltips (floating UI mechanics). | | accessibility | Auditing or fixing web UI against WCAG 2.2 AA / ARIA — semantic HTML over <div> soup, alt text, accessible names, form labels, keyboard focus order, contrast, and reduced motion, with Rails/Hotwire (Turbo focus management, form.label) and React (htmlFor, headless-library focus traps) patterns. Ships a heuristic a11y_audit.py (file:line: issue) and a full WCAG 2.2 review checklist. Paired with the a11y-reminder hook. | | env-to-fnox | Migrating a project's plaintext .env to fnox — references in a committed fnox.toml, real values in a vault (defaults to Bitwarden Secrets Manager). Ships migrate_env_to_fnox.py, an optional automation shortcut that creates the bws secrets and writes fnox.toml for the common case (--dry-run/--verify/--delete-source); the manual walkthrough still stands for categorising secrets vs. config and the judgement calls. Paired with the secret-plaintext-reminder hook. Adapted from Nate Berkopec's dotfiles with the provider switched from 1Password to Bitwarden. | | worktree-setup | Provisioning a freshly-created git worktree so it's actually ready to work in. A clean checkout is missing everything git doesn't track: an untrusted mise.toml, absent gitignored secrets/config (Rails config/master.key, .env, …), and shebang scripts the core.fileMode=false checkout dropped +x from. Ships setup-worktree.sh — run it inside the new worktree to trust mise, copy gitignored files from the main checkout (everything except heavy build/dependency dirs and the worktree itself), and re-mark scripts executable. Set worktree.baseref head first so the worktree branches from local HEAD. If the repo carries a committed .worktree-isolate.conf, also hands off to isolate-worktree.sh to give each worktree its own dev-server port and database (a stable, collision-free offset written into a gitignored mise.local.toml overlay), so parallel worktrees don't fight over :3000 or one shared DB. That config's WT_POST_SETUP key then runs the repo's seeding commands (bin/rails db:prepare, an asset build) inside the worktree — isolation allocates the port and database names, but nothing exists behind them until those run, and an unseeded worktree fails like a broken branch (fake deadlocks, every JS-dependent system test at once) rather than like a missing setup step. Complements using-git-worktrees (which creates the worktree; this provisions it). | | script-library | Keeping a one-off script you wrote — genericizing it to a standalone, self-contained CLI and saving it to the reusable library (~/.local/bin or a cloned, shareable scripts repo organised into subdirectories). The standard: a #!/usr/bin/env -S uv run --script shebang + PEP 723 inline deps (Python), a # short-description: line the script-index hook surfaces, a real argparse --help, and chmod +x; strip task-specific paths into flags and never bake in secrets. Library roots are a colon-separated DEV_HOOKS_SCRIPT_DIR (PATH-style), each scanned recursively. Ships a copy-paste references/template.py. Paired with the save-script-reminder (Stop) and script-index (SessionStart) hooks. | | compress-comments | Reviewing the comments a session's work added and cutting them down — after finishing a feature, before commit/merge, or when the compress-comments-reminder hook fires. AI-authored comments skew verbose (code-echo, change narration, planning forensics, reviewer justification); the skill is delete-biased around one survival rule — a comment survives only if it states something the code cannot show — with docstrings compressed but never deleted and directive comments (noqa, shellcheck, eslint-disable, …) never touched. Judges only the session's own diff (branch diff, or remembered commits on the default branch); ships the smell taxonomy with before/after examples in references/comment-smells.md. The survival rule is per-comment, so two file-level rules back it: a density budget (Django 4.0, Flask 2.0, requests 2.27 and git 2.34 all sit at ~10 comment lines per 100 code lines; over ~15% the skill ranks rather than filters, dropping comments that would each survive on their own) and a register rule (human comments are clipped fragments naming identifiers — 7–8 words, under 1% em dashes; AI comments are complete sentences about policy — 12 words, 19% em dashes, 39% parentheticals). Both measured in deslop's references/measurements.md. | | deslop | De-LLMifying the current diff — "deslop", "remove the AI slop", "make this look human-written", or any coding task about to be declared done. Slop is code that passes its tests today and becomes a liability later: verbosity, defensive scaffolding, fallbacks that never fire, any escapes, and a second way to do what the repo already does. Ships slop_scan.py, a dependency-free scanner with two passes — high-precision line rules (apology/deferral/placeholder/chat-artifact comments, swallowed errors, type escapes, dead branches, debug residue) and the file-level metrics no line rule can carry (comment density, comment register, god-file size). Tuned for precision against four pre-2023 codebases: 0.16 findings/file on 1402 Django/Flask/requests files and 0.24 on 1133 git shell scripts, against 2.38 on this repo's AI-written hooks. The pass always runs in a subagent — the agent that wrote the code knows why every comment is there, so every comment looks necessary to it — and the skill carries that subagent's brief verbatim. Catalogue in references/patterns.md (Tier A delete silently / Tier B fix and report / Tier C propose only), corpus work in references/measurements.md. Tier taxonomy adapted from Andrii Paslavskyi's anti-slop (MIT). | | api-scraping | Getting structured data off a website by reverse-engineering the private JSON/GraphQL API behind it instead of scraping rendered HTML — "scrape this site", "get all the X from", "there's no public API for this". Gates on robots.txt/ToS first, then runs a repeatable loop: capture a HAR, find the data-carrying request with the bundled har_scan.py --find "<value from the page>", replay it outside the browser and minimize the headers/auth to only what's required, then generate a client that follows the API's own pagination pointer, backs off on 429 (Retry-After), and writes JSONL incrementally so it resumes. Escalates only as far as it must (headers → cookies/token → curl_cffi TLS impersonation → headless browser) and stops at bot challenges — including Cloudflare's silent managed challenge, the one that presents as a JS problem rather than an access control — and anything needing the user's legal call. Where the data is for the user's own use, offers a sideways rung 5 instead of climbing: a paced, resumable fetch loop they paste into their own devtools console, which fails closed on a challenge and never takes a token out of the browser. References cover discovery (embedded JSON, GraphQL introspection, mobile-app APIs), the client skeleton, and the anti-bot ladder — including a table of the rationalizations that dress a bypass up as an escalation. Adapted from Jerome Paulos's All the data can be yours. | | repo-review | Reviewing/auditing a whole repository (not a diff) — an inherited or unfamiliar codebase, a "review this repo for 1. performance 2. code smells 3. structure" sweep. The broader stack-agnostic umbrella over rails-audit: a Rails app hands the Rails-shaped axes (correctness, security, perf, schema, tests, architecture, deps) to that deeper skill but still runs the cross-cutting axes rails-audit doesn't cover. Full axis set — correctness (/code-review), code smells (/simplify), performance, architecture, app security, test health (incl. does the suite run from a clean checkout), dev-env (dev-env-setup), dependencies/CVEs (dependency-upgrade), CI supply-chain (github-actions), secrets hygiene (env-to-fnox), plus accessibility (accessibility) and docs for web repos, and opt-in genericization. Detects monorepos (offers to review each sub-project separately). Ships a read-only detect_stack.sh preflight and ends in a severity-ranked plans/repo-review-YYYY-MM-DD.md. Report-only — it diagnoses, fixes are a separate scoped follow-up. The principle is delegate, don't re-derive. | | off-topic-improvements | Draining a repo's plans/off-topic-improvements.md backlog — the out-of-scope improvements parked mid-task by the global "Suggest Improvements" rule. Triggers on "drain the backlog", "address the off-topic improvements", or clearing accumulated items. Triages each item (do now / defer / drop, with a reason), isolates non-trivial ones in a worktree, lands each addressed item as its own atomic commit, and removes it from the file as it lands so the backlog stays a short live list — closing with a per-item ledger so nothing vanishes silently. The read/drain counterpart to the write side (repo-review and ad-hoc work surface the items). | | loop-oversight | Setting up an iterating loop so it stays reviewable — a /loop, a /schedule routine, or a Workflow fan-out that grinds through many items ("loop over every X and track status", "make a canonical tracker and work through every item"). Requires three artifacts before launch: a ledger (a status table, .claude/current_plan.md by default, that the loop reads/writes each turn so oversight is reading one file), an explicit bound (max iterations / token budget / until-N-consecutive-empty — no unbounded loops), and an independent verify pass (a separate agent prompted to refute, not the one that did the work). Picks the substrate by how closely you can watch (/loop present → Workflow for a hard cost cap → /schedule unattended, PR-per-unit, no auto-merge) and gives a per-turn contract + ready generate/verify prompts, plus a lighter poll-until-true scaffold for waiting on a single condition (CI green, main settled, DNS switched) before one bounded action. NOT for a one-off task or one-shot parallel fan-out (that's dispatching-parallel-agents). | | expose-service | Making a service on an internal or private host reachable — "expose X", "give me access to the admin panel", "set up a tunnel to", or a just-deployed service that 502s or is unreachable. Picks the narrowest exposure that fits the audience (one-off ssh -L/SOCKS → Tailscale Serve → VIP Services for per-service hostnames → Cloudflare Tunnel + Access → public reverse proxy), then covers the four things that actually bite: arming a detached systemd-run recovery before a change that can cut your own access, an outward reachability ladder (process → bind address → proxy→origin → DNS → TLS) that names the failing rung instead of guessing, reboot survival (persisted config, a watchdog that catches a hung daemon and won't undo a human, boot-order port races), and the audit for a second unintended way in. References cover Tailscale (Serve, VIP Services, set vs up, --bg port contention), the public edge (Access is an edge gate, not an origin gate; Authenticated Origin Pulls only work orange-clouded; origin-cert vs padlock), and reachability triage. | | agent-brief | Writing a task brief for an agent aimed at an outcome, not a method — dispatching a subagent, handing off work, or turning a fuzzy request into instructions. Carries a fill-in-order skeleton (GOAL / WHY / DONE WHEN / DON'T / AUTONOMY / PROVE IT) where the method box stays near-empty: spend the words on a checkable target and explicit non-goals, not on steps. Ships one before/after worked example and the compounding habit — patch the brief, not the one instance. Paired with the intent-check-reminder hook (which nudges toward exactly this when a prompt states what to do but not why or what's out of scope). |

Mods

dev-hooks also ships a Claude Code mod: TypeScript in hooks/register.tsx that hooks into Claude Code itself rather than running as a shell command. It needs Claude Code 2.1.287 or later; older versions ignore it and every hook above keeps working.

| Command | What it does | |---------|--------------| | /context-bar | Toggles a stacked bar above the prompt showing the context window, one color per /context category (system prompt, tools, memory, messages, …), with free space and the autocompact buffer shaded, a used% · tokens/window total, and a legend. Refreshes after every turn from the local estimate, so it costs no API calls. | | /session-facts | Prints what the mod has recorded about this session as a plain-text summary with relative times (/session-facts json for the raw record). The mod records it as it happens, rather than rebuilding it from git and the transcript at Stop: skills expanded (including inside subagents), subagents dispatched, and the files edited per git repository with their net line growth and how many edits came from subagents (edits outside any repository are kept under a null root). Only edits made with the Edit and Write tools are recorded; files changed by a Bash command are not. It also records the Stop verdict the command hooks returned. Kept for 30 days in the plugin's store, so it survives a reboot or a resumed session. The Stop hooks read it (below). | | (Stop orchestration, no command) | At the end of each turn the mod runs dev-hooks' own Stop hooks itself and gives Claude all their reasons as one message, instead of up to ten separate ones. The copies Claude Code would run directly stand down for that session (matched on the session id), so nothing runs twice; with mods off or on an older Claude Code, the Stop hooks run directly exactly as before. If the mod's Stop step ever fails, it hands Stop back to the hooks for the rest of the session and skips the takeover for that plugin version, so a broken release costs at most one turn's reminders. It also hands them the session facts (a file named in DEV_HOOKS_FACTS_FILE, set by the mod only), so review-reminder and compress-comments-reminder count a review or comment pass that ran inside a subagent, which the transcript alone never shows. The per-hook status lines ("Verifying tests and linters…") don't show while the mod runs them. | | (Guard dialog, no command) | When dangerous-command-guard.sh would ask (its opt-in ask modes: DEV_HOOKS_GUARD_DENY=ask, DEV_HOOKS_GUARD_SECRETS=ask, DEV_HOOKS_GUARD_MAIN=1), the mod puts the question to you in Claude Code's own dialog, with the reason and the command. A plain hook ask is answered by whoever answers permission prompts, which under auto mode is the classifier; this dialog reaches you. Allow only withdraws the guard's question (your permission rules still apply), Deny refuses, and anything typed under Other is passed to Claude as the reason. Dismissing it refuses; with no answer within the wait shown in the dialog it refuses and tells Claude the safer route — for a commit or push on main, to make the change on a branch in a worktree. A timed-out dialog can't be closed by the mod and may stay on screen; answering it later still counts — a late Allow is passed to Claude and lets that exact command through once within 10 minutes without asking again, and a late Deny or typed reply is passed to Claude as is. (Where the frontend draws the dialog through the mod, as the terminal may, it is also redrawn as a "timed out" note.) The wait is 2 minutes by default; set DEV_HOOKS_GUARD_DIALOG_TIMEOUT (seconds, 0 waits forever) in settings env to change it, or Claude can put it in front of a single command (DEV_HOOKS_GUARD_DIALOG_TIMEOUT=600 git push) when it knows you need longer — a timeout only ever refuses, so this can't loosen the guard. Hard denies (rm -rf /, …) are never asked; under claude -p, with no one to ask, the guard's ask stands as before. | | /session-facts nudges [days] | Whether Claude acted on each Stop nudge. Every reason the Stop orchestration returns is recorded per hook, and the next Stop (normally the continuation the nudge itself forced) resolves it from what the session did after it fired: review-reminder is acted on by a review skill (code-review, requesting-code-review, code-reviewer) or a subagent described as a review; compress-comments-reminder by the compress-comments skill; memory-reminder by an Edit/Write into a memory directory, or declined when Claude's reply says "nothing worth saving"; missing-test-reminder by a test file written for a listed source file; debug-leftover-reminder by an edit to a listed file (touching it, not proof the statement went); verify-work by passing at the next Stop (failures only; its once-only "no test suite detected" advisory is unknown). big-change, change-summary and save-script record unknown: nothing the facts hold shows whether they were heeded. Remedies done through Bash (a test from a generator, a memory written with cat) aren't seen and read as ignored. A nudge whose session ends before another Stop resolves as unknown unless the evidence already says acted. The command prints fired / acted / ignored / declined / unknown per hook over the last 7 days (or [days]) and an acted-on rate over the known outcomes; this session's nudges also appear in plain /session-facts. Outcomes are kept for 90 days in the plugin's store and exported to ~/.claude/automation-review/stop-nudges.json (when that directory exists) for the weekly automation review. | | (CI watch, no command) | After a git push that went through, in a repo with GitHub Actions workflows and a GitHub remote, the mod watches that push's runs itself, so Claude neither forgets the run nor blocks a turn on gh run watch. It reads the pushed commits from git's own push output (falling back to HEAD for a quiet push; a dry run or an up-to-date push starts nothing), finds their runs with gh run list --commit <sha>, selected by head sha and never by recency, and polls until every run has finished: progress in the status line under the prompt, then a toast (✓ passed, ✗ failed, ◌ cancelled). Claude hears the outcome too: the push's tool result says the watch is running, a pass or cancellation is added to the conversation as a note Claude reads with its next request without starting a turn, and a failure is submitted as a prompt of its own that wakes the session once it is idle, naming the failed run and its gh run view … --log-failed command. A run that hasn't appeared after two minutes, or a gh that can't answer, is reported the same way; a watch gives up after 90 minutes. Needs gh, logged in. Watches live in the module, so reloading it drops them. While the mod is active, ci-watch-reminder.sh stands down; DEV_HOOKS_CI_WATCH=false turns both off. |

The mod's tests live in tests/*.test.ts(x); run them with claude plugin test plugins/dev-hooks (the repo's tests/test_mods.py does this when a Claude CLI is on PATH).

Install

/plugin marketplace add mickzijdel/dev-hooks
/plugin install dev-hooks@dev-hooks

To receive the latest skills and hooks, enable auto-updates for the plugin in the plugin management overview.

Usage

Once installed the hooks fire automatically — there is nothing to invoke. They lint after edits, verify tests/linters before Claude stops, guard dangerous commands, and nudge on the patterns documented above. To use a companion skill, describe the task and Claude reaches for the matching one, or invoke it by name:

$ claude
> /dev-hooks:dev-env-setup audit this repo against the dev-env standard
> /dev-hooks:repo-review     whole-repo severity-ranked audit

Notes

  • prompt-log.sh is one of three UserPromptSubmit hooks (intent-check-reminder.sh and data-before-design-reminder.sh are the others; all three share the reminder_prompt_init lib preamble). It appends one JSON line per prompt (ts, cwd, session_id, len, prompt truncated to the first 500 chars) to ~/.claude/automation-review/prompts.jsonl, so the thinking-tools weekly-automation-review skill can cluster repeated requests across all your repos and suggest what to turn into a skill/hook/tool. It is silent (it never writes to stdout — a UserPromptSubmit hook's stdout would be injected into Claude's context — and always exits 0, so it can never block or pollute a prompt). Privacy: the log is plaintext and may capture whatever you type (including secrets pasted into a prompt). It is local-only under ~/.claude/, never transmitted, and capped at ~2×10 MiB (it rotates to prompts.jsonl.1 past the cap, default 10 MiB, DEV_HOOKS_PROMPT_LOG_MAX_BYTES). Disable logging entirely with DEV_HOOKS_PROMPT_LOG=false (in .claude/settings.local.json "env"); delete the ~/.claude/automation-review/ directory to purge history.
  • Fire telemetry (opt-in). Set DEV_HOOKS_FIRE_LOG=1 (in "env") to have every advisory reminder append one JSONL line (hook, session, ts) to ~/.claude/automation-review/hook-fires.jsonl. Off by default. It feeds thinking-tools:weekly-automation-review's Retire pass: which hooks actually fire (and which never do) is the evidence for pruning scaffolding whose capability bet the model has outgrown — see each hook's # bet:/# sunset: header for the bet it's making.
  • ci-watch-reminder.sh is one of two PostToolUse(Bash) hooks. It watches for a completed git push and, when the repo has GitHub Actions workflows, nudges Claude to watch that push's CI run (to completion if idle, or in the background if it has more work) rather than pushing and moving on while a red pipeline goes unnoticed. It never runs gh itself — it only reminds; Claude runs gh run watch. Where the mod is active it stands down (the mod's CI watch watches the run itself and marks the session with DEV_HOOKS_CI_WATCH_SESSION). Opt out with DEV_HOOKS_CI_WATCH=false.
  • worktree-provision-reminder.sh is the other PostToolUse(Bash) hook. After a git worktree add it diffs the gitignored state of the main checkout against the new worktree and, when something load-bearing is absent, points at setup-worktree.sh rather than letting the copy be hand-rolled. It reads the worktree path out of the command itself (an mtime "newest worktree" guess audits the wrong directory as soon as any older worktree is touched) and compares exact ignored paths, since storage/ and config/ exist in a fresh worktree while their contents do not. Opt out with DEV_HOOKS_WORKTREE_PROVISION=false.
  • verify-work.sh only runs inside a git repo and only when relevant code files have changed; it no-ops otherwise. Test-suite scope is set per repo (in .claude/settings.json "env", which hooks inherit) via DEV_HOOKS_VERIFY_TESTS: full (default — run the whole suite when code changed), changed (run only changed test files plus tests path-mapped from changed source, e.g. app/models/user.rb → test/models/user_test.rb; JS tests can't be targeted so changed skips them and linters still run), or off (skip the test run entirely; linters/scanners still run). Each test run is capped by a soft timeout (DEV_HOOKS_VERIFY_TEST_TIMEOUT, default 110 seconds, under the hook's 120s hard limit; 0 disables it): a run that exceeds it is stopped gracefully and the hook recommends switching to changed and/or adding a fast smoke-test subset, instead of being silently hard-killed.
  • dev-env-reminder.sh only nudges on repos it judges yours and only when the standard applies but isn't met; it's advisory and never edits anything. Ownership = origin remote owner listed in DEV_HOOKS_DEVENV_OWNERS or ≥80% of the last month's commits authored by your local git config user.email. Tune/override via env (set in .claude/settings.local.json "env"): DEV_HOOKS_DEVENV_OWNED (true/false — deterministic per-repo override, false silences it entirely), DEV_HOOKS_DEVENV_OWNERS (GitHub owners to treat as yours, e.g. your username or an org you own), DEV_HOOKS_DEVENV_EMAIL (commit-author email for the heuristic; defaults to your local git config user.email). Per-repo opt-out also via a dev-env: skip line in the project's CLAUDE.md.
  • docs-context.sh scans docs/ (falling back to doc/) at the project root for Markdown files up to two levels deep. Titles come from YAML frontmatter (title:) or the first # heading; an optional frontmatter description: field is included after an em-dash. Two more frontmatter fields are opt-in and off by default: stale_after: YYYY-MM-DD flags the line with ⚠ stale since <date> once that date has passed, and status: stale/deprecated/draft flags it with ⚠ status: <status> regardless of date. Neither field is required — docs without them render exactly as before. Hidden paths (.*) are ignored. Silence it with DEV_HOOKS_DOCS_CONTEXT=false (in .claude/settings.local.json "env").
  • memory-reminder.sh targets Claude Code's file-based memory feature. It stays a no-op unless you set DEV_HOOKS_MEMORY=1 or have already used memory somewhere (it detects any existing ~/.claude/projects/*/memory/ dir), so it's safe for installers who don't use memory. It only nudges Claude to capture learnings — it never writes memory or edits CLAUDE.md itself, and Claude is told to save nothing when there's nothing durable. "Substantial session" defaults to ≥ 6 human turns; tune with DEV_HOOKS_MEMORY_MIN_TURNS (in .claude/settings.local.json "env").
  • script-index.sh (SessionStart) and save-script-reminder.sh (Stop) are a pair that make a CLI-tool library self-stocking and self-advertising. Both read the library roots from DEV_HOOKS_SCRIPT_DIR — a colon-separated list like PATH (default ~/.local/bin, which is already on PATH), so you can keep personal scripts and a cloned, shareable scripts repo, e.g. ~/.local/bin:~/code/team-scripts. Each root is scanned recursively (hidden dirs like .git skipped, depth-capped), so a repo organised into subdirectories works; the index shows each script's path, since a script in a subdirectory or a non-PATH root is run by path or via uv run <path> rather than bare name. script-index reads only the first lines of each executable shebang file (for the # short-description:); it never runs a script. Hide entries that aren't your own tools — installed/third-party CLIs, app launchers — with DEV_HOOKS_SCRIPT_IGNORE, a colon-separated list of globs matched against each script's basename or full path (e.g. *vocalinux*:gext:gnome-extensions-cli). save-script-reminder detects a script Claude wrote via the Write tool whose content starts with a shebang, wherever it landed (only scripts already in a library root are excluded — in-repo scripts are listed too, so Claude can decide whether each is project- specific or a general tool worth promoting); a script created through a Bash heredoc isn't detected (the Write path is the common case). Disable independently with DEV_HOOKS_SCRIPT_INDEX=false / DEV_HOOKS_SAVE_SCRIPT=false (in .claude/settings.local.json "env"). See the script-library skill for the saved-script standard and how to share a scripts repo.
  • latest-deps-reminder.sh is reminder-only — it never queries a package registry, just nudges Claude to look up current versions itself (training data goes stale). On manifest edits (python/js/ruby — not lockfiles) it additionally reminds Claude to keep README.md and CLAUDE.md key-package versions in sync, creating those docs if they don't exist. It fires at most once per session per ecosystem (python/js/ruby/lockfile), tracked via a marker under ${TMPDIR:-/tmp}/dev-hooks-latest-deps/. Silence it with DEV_HOOKS_LATEST_DEPS=false (in .claude/settings.local.json "env").
  • scaffold-reminder.sh is reminder-only — it never blocks the write. It fires when the Write tool creates a project manifest or framework entrypoint that git doesn't already track (outside a git repo every file counts as new): hand-writing one of those is the signature of scaffolding a project from memory. Edits stay silent (the project already exists), as do tracked files. The nudge names the matching generator (rails new, npm create vite@latest, uv init/django-admin startproject, cargo new, go mod init, mix new/mix phx.new, composer create-project, gradle init, …) and tells Claude to first check the framework's current stable release — and the generator's current flags via --help/docs — rather than recalling either, unless the user asked for a specific version. Fires once per session, tracked via a marker under ${TMPDIR:-/tmp}/dev-hooks-scaffold/. Silence it with DEV_HOOKS_SCAFFOLD=false (in .claude/settings.local.json "env").
  • dockerfile-reminder.sh runs hadolint on each Dockerfile/Containerfile Claude writes and reports the findings (or a clean pass) back to Claude, plus a layer-ordering nudge pointing at the dockerfile skill. It's report-only: it surfaces results every time but never blocks the write — Claude decides whether to fix. If hadolint isn't installed it can't lint, so it falls back to a once-per-session ordering/gotchas reminder (tracked via a marker under ${TMPDIR:-/tmp}/dev-hooks-dockerfile/) and suggests installing hadolint. Silence the whole hook with DEV_HOOKS_DOCKERFILE=false (in .claude/settings.local.json "env").
  • popover-reminder.sh is reminder-only — it never blocks the write. It fires when Claude writes a frontend file (the shared extension list below, same as inline-svg-reminder.sh) carrying a popover/tooltip signal: a popover/tooltip/dropdown/popper/floating controller filename, role="tooltip"/the native popover attribute/popovertarget, an @floating-ui/popper/tippy import, a data-controller naming one, or a tooltip/popover/dropdown class/data-* attribute (matching is deliberately broad). It nudges Claude to use a collision-aware positioner (flip + shift) rendered in the top layer/a portal — not hand-rolled top/left math that opens off-screen — and points at the popovers-tooltips skill (Floating UI in a Stimulus controller for Rails/Hotwire). Fires once per session, tracked via a marker under ${TMPDIR:-/tmp}/dev-hooks-popover/. Silence it with DEV_HOOKS_POPOVER=false (in .claude/settings.local.json "env").
  • secret-plaintext-reminder.sh is reminder-only — it never blocks the write and never reads anything beyond the content Claude just wrote. Detection is deliberately conservative (named KEY/SECRET/TOKEN/PASSWORD-style assignments to a real literal, private-key blocks, AWS key ids); env-var references (process.env, os.environ, ${VAR}) and obvious placeholders are ignored, and *.example/*.sample/*.template/fnox.toml/lockfiles are skipped. It fires at write time (before gitleaks would at commit) and points at the env-to-fnox skill. Fires once per session, tracked via a marker under ${TMPDIR:-/tmp}/dev-hooks-secrets/. Silence it with DEV_HOOKS_SECRETS=false (in .claude/settings.local.json "env").
  • ci-action-ref-reminder.sh is reminder-only — it never hits the network. It fires when Claude writes a *.yml/*.yaml that pins a remote action (uses: owner/repo@ref), points Claude at the github-actions skill's supply-chain checklist (SHA-pin actions, read-only token, no untrusted input in run:), and at the bundled skills/dev-env-setup/scripts/check_action_refs.sh, which does the actual git ls-remote resolution and exits non-zero on any unresolved ref. That script classifies each ref as OK (a tag resolves, or a SHA pin's # vX.Y.Z comment matches the commit that tag points to) / FAIL (missing tag, or a SHA that doesn't match its comment) / PIN (a SHA with no version comment) / SKIP (remote unreachable — never a failure), so offline runs don't false-alarm; you can also run it directly over a workflow or .github/workflows. Fires once per session per file (marker under ${TMPDIR:-/tmp}/dev-hooks-ci-action-refs/). Silence it with DEV_HOOKS_CI_ACTION_REFS=false (in .claude/settings.local.json "env").
  • inline-svg-reminder.sh is the one enforcing PostToolUse hook: the write still lands, but it feeds a correction back (exit 2) on every occurrence rather than once per session — hand-written inline SVG is a habit worth breaking, not a one-time tip. It fires when Claude writes a frontend file (.js/.mjs/.cjs/.jsx/.ts/.tsx/.vue/.svelte/ .astro/.html/.htm/.erb/.haml/.slim/.php/.twig/.heex/.css/.scss) containing an <svg> block with real drawing content (<path>/<circle>/<rect>/… or substantial d="M…" path data), a partial drawing fragment, or a data:image/svg+xml URI. The feedback says, in order of preference: use the project's icon library (it greps package.json/Gemfile and names the one already installed — lucide, heroicons, tabler, font-awesome, …), else extract the markup to a dedicated .svg file/sprite and reference it; keep it inline only if the user explicitly asked. The good patterns never fire: <svg><use href="sprite.svg#id"> references, writing actual .svg files (the refactor target), <img src="x.svg">, markdown/docs, test files, and data-driven chart markup (drawing tags with expression attributes like <rect x={scale(d)}> — D3/visx charts aren't icons). Pre-existing, user-approved inline SVG doesn't re-trigger: full-file Writes are deduped against the file at HEAD, Edits/MultiEdits against their old_strings, keyed on the d="…" drawing data so attribute tweaks on an approved icon stay silent. Silence it with DEV_HOOKS_SVG_INLINE=false (in .claude/settings.local.json "env").
  • debug-leftover-reminder.sh only considers newly-introduced lines (added lines in git diff HEAD plus the full contents of untracked files), so committed/pre-existing debug statements are ignored — committing or removing the lines clears the nudge. It runs only in a git repo, excludes test files, and fires at most once per session (its sentinel in the transcript suppresses a re-fire). Silence it with DEV_HOOKS_DEBUG_LEFTOVER=false (in .claude/settings.local.json "env").
  • missing-test-reminder.sh only looks at files newly added this session — untracked, staged-added, or added by a commit made since the session started (so it still works when you commit as you go and the tree is clean by the time Stop fires; a file added and deleted again within the session doesn't count). It excludes test files, low-value targets (barrels, type defs, *.config.*, migrations, __init__.py/conftest.py), and vendored/generated code, and stays silent if a matching test already exists anywhere in the tree. Vendored/generated dirs come from the repo's own .jscpd.json ignore globs at run time (falling back to a built-in default — node_modules, vendor, dist, build, app/assets/builds — when the repo has no .jscpd.json); minified files (*.min.js etc.) are always skipped. Runs only in a git repo; fires once per session (transcript sentinel). It can false-positive on files that legitimately need no test — Claude is told to say so and move on. Silence it with DEV_HOOKS_MISSING_TEST=false (in .claude/settings.local.json "env").
  • dangerous-command-guard.sh is the only PreToolUse hook and the only one that can block a tool call. It reads the bash command from the hook payload (never runs or modifies it) and, on a catastrophic, irreversible command, emits a permissionDecision of deny; for everything else it stays silent and the normal permission flow proceeds. It never emits allow, so it can't widen your own allowlist. Scope is deliberately narrow — only the machine-wiping few (rm -rf /, fork bomb, mkfs, dd to a raw disk, chmod -R 777 /), not "anything that writes". Risky-but-legitimate commands (rm -rf a path, git reset --hard, force-push, curl … | bash, sudo) are left alone: the normal permission flow already prompts on them, and Claude Code's auto-mode classifier gates them too — a bespoke matcher there would just duplicate a built-in. It splits the command into the simple commands the shell would run (lib/shell-segments.awk: ;, &, |, newlines, honouring quotes) so flags and targets are only judged against the command they belong to (cd ~ && rm -rf build/ is not rm -rf ~; a commit message or search pattern that mentions mkfs or fnox get is not that command). What a command would run is split out and judged too: $(…) and backticks (also inside double quotes and unquoted heredocs), the string given to bash -c/sh -c/eval/ssh host, and a heredoc body fed to a shell; a quoted heredoc writing a test file is data. On an unbalanced quote it falls back to the old quote-blind split, which over-matches rather than hides. What happens on a match is configurable via DEV_HOOKS_GUARD_DENY: deny (default, block outright), ask (downgrade to a human confirmation), or allow/off (pass through silently — for advanced users who rely on auto mode or their own rules). The commit/push-on-main/master check is opt-in via DEV_HOOKS_GUARD_MAIN=1 (the coding-onboarding plugin's getting-started skill seeds it for beginners when installed — solo main-branch workflows aren't prompted on every commit; no built-in replaces this workflow-habit nudge); when on, it checks the current branch with git branch --show-current in the repo the command acts on (following cd dir && and git -C dir, not just the session's cwd). The secret-exfiltration check is the guard's other half, and it blocks by default (DEV_HOOKS_GUARD_SECRETS: deny default, ask, or allow/off). It asked until 2.40.0, on the reasoning that every match is a command you sometimes genuinely need — but ask does not select a human, it selects whoever answers prompts, and under "defaultMode": "auto" that is the auto-mode classifier. A presence check written ${VAR:+SET}${VAR:-UNSET} reads as safe to a classifier for the same reason it reads as safe to a person, so it was approved and a live token reached the transcript. Blocking is right here because the act is irreversible and a non-printing form always exists (${VAR:+SET}, ${#VAR}, fnox run/bws run, or reading it yourself outside the agent): secret-plaintext-reminder.sh catches a secret being written into a file, but nothing caught one being read into the transcript, where it is logged, summarised and pasted onward and the only real remedy is rotating the credential. Four families are matched, all per segment: a printing command (cat/head/tail/less/bat/jq/grep/…) reading a secret-bearing file (.env, .env.*, *.key, *.pem, id_ed25519, credentials.yml.enc, client_secret*.json, .netrc, .pgpass, …); a secret manager printing to stdout (bws secret get|list, fnox get|show, op read/op item get, vault kv get|read, gh auth token, aws secretsmanager get-secret-value, aws ssm get-parameter, doppler secrets get, kubectl get secret, pass show); a credential probe — git credential fill, a helper's get (git credential-<helper> get, git-credential-<helper> get), gh auth token, gh auth status -t/--show-token, gh auth git-credential get, secret-tool lookup|search, security find-*-password -w|-g, and reading ~/.git-credentials or gh's hosts.yml; and echo/printf/printenv of a secret-shaped variable name. The credential families came from a 2026-10-02 leak, where PATH=/usr/bin:/bin git credential fill, meant to test a "gh missing" case, reached the logged-in /usr/bin/gh and printed a live token. Their block message names the isolated way to test credential plumbing (temp HOME, GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1, temp GH_CONFIG_DIR, empty GH_TOKEN/SSH_AUTH_SOCK, a fake gh first on PATH, helper output counted rather than printed) inside a test script; the same variables set inline are not trusted, since a repo's own .git/config, a helper named by absolute path and the desktop keyring survive them. git credential approve|reject and a helper's store/erase read stdin and print nothing, and ssh-add -l|-L lists public keys, so those stay silent. False-positive discipline is the whole design, so template and example files (*.example, *.sample, *.template, *.dist), public key halves (*.pub), the inject-don't-print wrappers (fnox run, bws run, op run, doppler run), source .env, a grep that counts rather than prints (-c/-l/-q), stdout sent to /dev/null or a file, and a value captured into a variable (v=$(fnox get X), KEY="$(cat master.key)" cmd) or only compared by a test ([ "$(fnox get X)" = … ]) are all silent; 2>/dev/null, >&2 and >/dev/stderr are not, since the value still reaches the screen. Silence the whole guard with DEV_HOOKS_BASH_GUARD=false (in .claude/settings.local.json "env").
  • big-change-reminder.sh runs only in a git repo and sizes the uncommitted working tree (tracked changes from git status --porcelain plus untracked files enumerated via git ls-files --others — so files inside a brand-new directory are counted individually; added lines from git diff HEAD --numstat plus the line count of untracked files). It fires (blocks the stop, once per session via a transcript sentinel) only above the thresholds (DEV_HOOKS_BIG_CHANGE_FILES default 25, DEV_HOOKS_BIG_CHANGE_LINES default 800) and stays silent when .claude/current_plan.md exists — a plan already means the work is deliberate. Silence it with DEV_HOOKS_BIG_CHANGE=false.
  • data-before-design-reminder.sh is a UserPromptSubmit hook, so it fires on the prompt rather than on a tool call: a request to build a display surface is nudged to profile the real data first, before any markup exists to review. It fires once per session and only on a prompt that both names a view-shaped surface (card/view/panel/dashboard/table/chart/…) and carries no restyle-only marker (colour/padding/font/spacing/align/…), so a "make the padding bigger" is left alone. A missed prompt costs nothing; a false nudge costs one sentence. Silence it with DEV_HOOKS_DATA_BEFORE_DESIGN=false.
  • change-summary-reminder.sh counts changed files via the simpler git status --porcelain tally (an untracked directory collapses to one entry — unlike big-change-reminder.sh's line-count-accurate expansion, that's fine here since it's only a file-count threshold), and it doesn't inspect diff size at all — it's about getting a readable account of the changes, not their bulk. It doesn't check whether a summary was already given earlier in the session; the once-per-session sentinel is enough, and the reminder text itself tells Claude it's fine to skip repeating one.
  • Nearly all the hooks build on hooks/scripts/lib/reminder-common.sh, the shared library that owns payload extraction, opt-out handling, and advisory/blocking emit. Its content helpers understand Write content, Edit new_string/old_string, and MultiEdit edits[] alike, so the content-reading hooks (secret-plaintext-reminder.sh, inline-svg-reminder.sh, a11y-reminder.sh, …) all see the written text the same way.
  • Hooks require jq (used to parse hook input) and, for a11y-reminder.sh, debug-leftover-reminder.sh, error-swallow-reminder.sh, inline-svg-reminder.sh, memory-reminder.sh, missing-test-reminder.sh, review-reminder.sh, secret-plaintext-reminder.sh, sql-injection-reminder.sh, and verify-work.sh, python3.
  • The dev-env-setup skill applies a mise/hk standard, so the repos it sets up depend on mise, hk, pkl, gitleaks, zizmor, actionlint (and shellcheck/shfmt for shell repos) — all provisioned via the generated mise.toml. The dev-env-reminder hook itself only needs git, jq, and bash.

License

MIT

関連作品