Skip to content
Development
Hook

Hooks

What fableclaudemdforopus runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
fableclaudemdforopus
718 skills5 hooks

Claude Code

$ npx -y skills add altafino/FableClaudeMDForOpus --agent claude-code

Where it lives

  • hooks/guard.pyGitHub
    Read the script
    #!/usr/bin/env python3
    """Guardrails companion — PreToolUse enforcement (roadmap Phase B1).
    
    Mechanically enforces: iron rule 1/C1 (no Edit of an un-Read file), iron rule 2
    (no Write over an existing file), C2 (no edits under generated/vendored paths),
    hard stop 2 (no git push without user authorization), hard stop 3 (no
    kill-by-image-name), SEC4 (no staged secrets at git commit).
    
    Deny = exit 2 with the reason on stderr (shown to the model).
    Bypass convention: GUARDRAILS_BYPASS=1 allows the call and appends one line to
    <cwd>/.claude/guardrails-bypass.log so the auditor can review overrides.
    """
    import json
    import os
    import re
    import subprocess
    import sys
    import tempfile
    import time
    
    GENERATED = re.compile(
        r"(^|/)(dist|build|out|gen|\.next|target|node_modules|vendor|coverage)(/|$)"
        r"|\.min\.|\.map$"
        r"|(^|/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|poetry\.lock|Cargo\.lock)$"
    )
    # Assignment-with-value shape, not bare words — kit docs legitimately contain
    # the words "password|secret|token" in rule text.
    SECRETS = re.compile(
        r"(?i)(password|secret|token|api[_-]?key)\s*[:=]\s*['\"][^'\"]{8,}"
        r"|BEGIN [A-Z ]*PRIVATE KEY"
    )
    KILL_BY_NAME = re.compile(r"(?i)\b(pkill|killall)\b|\btaskkill\b[^|;&]*/(im)\b")
    GIT_PUSH = re.compile(r"\bgit\b[^|;&]*\bpush\b")
    GIT_COMMIT = re.compile(r"\bgit\b[^|;&]*\bcommit\b")
    ALLOW_PUSH_TTL_S = 1800  # scripts/allow-push grants one push within 30 min
    
    
    def reads_state_path(session_id: str) -> str:
        return os.path.join(tempfile.gettempdir(), f"guardrails-reads-{session_id}.txt")
    
    
    def deny(msg: str) -> None:
        print(msg, file=sys.stderr)
        sys.exit(2)
    
    
    def log_bypass(cwd: str, tool: str, detail: str) -> None:
        try:
            d = os.path.join(cwd, ".claude")
            os.makedirs(d, exist_ok=True)
            with open(os.path.join(d, "guardrails-bypass.log"), "a") as f:
                f.write(f"{time.strftime('%Y-%m-%dT%H:%M:%S')} BYPASS {tool} {detail}\n")
        except OSError:
            pass
    
    
    def main() -> None:
        try:
            data = json.load(sys.stdin)
        except (json.JSONDecodeError, ValueError):
            sys.exit(0)  # unparseable input: never block on our own bug
        tool = data.get("tool_name", "")
        ti = data.get("tool_input") or {}
        sid = data.get("session_id", "unknown")
        cwd = data.get("cwd") or os.getcwd()
    
        if os.environ.get("GUARDRAILS_BYPASS") == "1":
            log_bypass(cwd, tool, json.dumps(ti)[:200])
            sys.exit(0)
    
        if tool in ("Edit", "Write"):
            path = ti.get("file_path", "")
            norm = path.replace("\\", "/")
            if GENERATED.search(norm):
                deny(
                    f"guardrails C2: {path} looks generated/vendored — edit the source or the "
                    "generator and re-run it instead (docs/guardrails/CODE.md C2). "
                    "(false positive? GUARDRAILS_BYPASS=1)"
                )
            exists = os.path.exists(path)
            known = False
            rf = reads_state_path(sid)
            if os.path.exists(rf):
                with open(rf) as f:
                    known = path in {line.strip() for line in f}
            if tool == "Edit" and exists and not known:
                deny(
                    f"guardrails iron rule 1 / C1: no Read of {path} recorded this session — "
                    "Read the enclosing scope first, then retry the Edit. "
                    "(state lost after crash/compaction? GUARDRAILS_BYPASS=1, logged)"
                )
            if tool == "Write" and exists:
                deny(
                    f"guardrails iron rule 2: {path} exists — modify with Edit, never Write. "
                    "Sole exception: the rewrite procedure in docs/guardrails/CODE.md "
                    "('You are rewriting instead of editing'); follow it, then GUARDRAILS_BYPASS=1."
                )
            sys.exit(0)
    
        if tool == "Bash":
            cmd = ti.get("command", "")
            if KILL_BY_NAME.search(cmd):
                deny(
                    "guardrails hard stop 3: never kill by image name -> find the PID via the "
                    "port (lsof -ti :PORT | netstat -ano | findstr :PORT) and kill that PID. "
                    "(bypass: GUARDRAILS_BYPASS=1)"
                )
            if GIT_PUSH.search(cmd):
                flag = os.path.join(cwd, ".claude", ".allow-push")
                if os.path.exists(flag) and time.time() - os.path.getmtime(flag) < ALLOW_PUSH_TTL_S:
                    try:
                        os.remove(flag)  # single-use grant
                    except OSError:
                        pass
                    sys.exit(0)
                deny(
                    "guardrails hard stop 2: git push blocked — needs the user's own action: "
                    "run scripts/allow-push (grants ONE push for 30 min), then retry. "
                    "(bypass: GUARDRAILS_BYPASS=1, logged)"
                )
            if GIT_COMMIT.search(cmd):
                try:
                    diff = subprocess.run(
                        ["git", "-C", cwd, "diff", "--cached"],
                        capture_output=True, text=True, timeout=15,
                    ).stdout
                except (OSError, subprocess.SubprocessError):
                    diff = ""
                hits = [l[:120] for l in diff.splitlines() if l.startswith("+") and SECRETS.search(l)]
                if hits:
                    deny(
                        "guardrails SEC4: staged diff matches secret patterns:\n"
                        + "\n".join(hits[:5])
                        + "\nRedact/unstage, or confirm false positive with the user. "
                        "(bypass: GUARDRAILS_BYPASS=1, logged)"
                    )
            sys.exit(0)
    
        sys.exit(0)
    
    
    if __name__ == "__main__":
        main()
    
  • hooks/rearm.pyGitHub
    Read the script
    #!/usr/bin/env python3
    """Guardrails companion — SessionStart re-arm hook (roadmap Phase C1 + C4).
    
    On session start after a compaction or /resume, deterministically re-inject the
    kit's re-arm instruction (routing row 6 / SESSION.md S1) instead of relying on
    the CLAUDE.md footer surviving the model's attention. On every start, report
    docs/STATE.md freshness (C4). Silent when the cwd has no guardrails kit.
    stdout from a SessionStart hook is added to the session's context.
    """
    import json
    import os
    import sys
    import time
    
    STALE_S = 24 * 3600
    
    
    def kit_installed(cwd: str) -> bool:
        p = os.path.join(cwd, "CLAUDE.md")
        try:
            with open(p) as f:
                return "guardrails-kit" in f.readline()
        except OSError:
            return False
    
    
    def main() -> None:
        try:
            data = json.load(sys.stdin)
        except (json.JSONDecodeError, ValueError):
            sys.exit(0)
        cwd = data.get("cwd") or os.getcwd()
        if not kit_installed(cwd):
            sys.exit(0)
        source = data.get("source", "startup")
        lines = []
        if source in ("compact", "resume"):
            lines.append(
                "guardrails re-arm: routing row 6 has fired — write its TRIGGER line and Read "
                "docs/guardrails/SESSION.md (S1 runs first: Read docs/STATE.md, run git status + "
                "git diff --stat HEAD, restate Goal/Next). Docs read before compaction no longer "
                "count as read; compaction-summary claims are UNVERIFIED until re-checked."
            )
        state = os.path.join(cwd, "docs", "STATE.md")
        if not os.path.exists(state):
            lines.append(
                "guardrails C4: docs/STATE.md is missing — create it per docs/guardrails/SESSION.md "
                "S2 before file-modifying work."
            )
        else:
            age = time.time() - os.path.getmtime(state)
            if age > STALE_S:
                lines.append(
                    f"guardrails C4: docs/STATE.md last updated {int(age // 3600)}h ago — treat its "
                    "Now/Next as stale until refreshed (SESSION.md S3)."
                )
        if lines:
            print("\n".join(lines))
        sys.exit(0)
    
    
    if __name__ == "__main__":
        main()
    
  • hooks/stop_verify.pyGitHub
    Read the script
    #!/usr/bin/env python3
    """Guardrails companion — Stop-hook done-claim verifier (roadmap Phase B2).
    
    At turn end: if the session edited files AND the final assistant message claims
    done/fixed/works/passing/complete/resolved/ready WITHOUT a legal VERIFY.md form
    (`Verified:` / `UNVERIFIED` / V-lines / canonical statuses), block the stop and
    point at docs/guardrails/VERIFY.md. Match scope: assistant prose only — fenced
    code blocks are stripped first. Heuristic v0; bypass: GUARDRAILS_BYPASS=1.
    """
    import json
    import os
    import re
    import sys
    
    CLAIM = re.compile(r"\b(done|fixed|works|passing|completed?|resolved|ready)\b", re.I)
    EVIDENCE = re.compile(
        r"Verified:|UNVERIFIED|EDITED-UNVERIFIED|NOT-DONE|CANNOT-REPRODUCE|V\d+:\s*(PASS|FAIL|N/A)"
    )
    EDIT_TOOLS = ("Edit", "Write", "NotebookEdit")
    
    
    def main() -> None:
        try:
            data = json.load(sys.stdin)
        except (json.JSONDecodeError, ValueError):
            sys.exit(0)
        if data.get("stop_hook_active"):  # loop guard: never block our own re-entry
            sys.exit(0)
        if os.environ.get("GUARDRAILS_BYPASS") == "1":
            sys.exit(0)
        tp = data.get("transcript_path")
        if not tp or not os.path.exists(tp):
            sys.exit(0)
    
        had_edit = False
        last_text = ""
        with open(tp, encoding="utf-8", errors="replace") as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    e = json.loads(line)
                except json.JSONDecodeError:
                    continue
                content = (e.get("message") or {}).get("content")
                if not isinstance(content, list):
                    continue
                for b in content:
                    if isinstance(b, dict) and b.get("type") == "tool_use" and b.get("name") in EDIT_TOOLS:
                        had_edit = True
                if e.get("type") == "assistant":
                    texts = [
                        b.get("text", "")
                        for b in content
                        if isinstance(b, dict) and b.get("type") == "text"
                    ]
                    if texts:
                        last_text = "\n".join(texts)
    
        if not had_edit:
            sys.exit(0)  # nothing was edited; completion claims need no run evidence
        prose = re.sub(r"```.*?```", "", last_text, flags=re.S)
        if CLAIM.search(prose) and not EVIDENCE.search(prose):
            print(
                "guardrails VERIFY: the final message claims completion without a legal status "
                "form. Use `Verified: <command> -> <result line>` or `UNVERIFIED — to confirm, "
                "run: <command>` per docs/guardrails/VERIFY.md, or run the verification now. "
                "(bypass: GUARDRAILS_BYPASS=1)",
                file=sys.stderr,
            )
            sys.exit(2)
        sys.exit(0)
    
    
    if __name__ == "__main__":
        main()
    
  • hooks/test_hooks.shGitHub
    Read the script
    #!/usr/bin/env bash
    # Guardrails companion test suite — every deny's trigger case + its bypass path.
    set -u
    HERE="$(cd "$(dirname "$0")" && pwd)"
    TMP="$(mktemp -d)"
    trap 'rm -rf "$TMP"' EXIT
    SID="testsess-$$"
    STATE="${TMPDIR:-/tmp}/guardrails-reads-$SID.txt"
    rm -f "$STATE"
    PASS=0; FAIL=0
    
    run_guard() { # $1=json  -> echoes exit code
      printf '%s' "$1" | python3 "$HERE/guard.py" >/dev/null 2>"$TMP/err"; echo $?
    }
    check() { # $1=name $2=expected_exit $3=actual_exit
      if [ "$2" = "$3" ]; then PASS=$((PASS+1)); echo "  ok  $1"
      else FAIL=$((FAIL+1)); echo "FAIL  $1 (expected exit $2, got $3)"; sed 's/^/      /' "$TMP/err"; fi
    }
    j() { python3 -c 'import json,sys; print(json.dumps({"session_id":sys.argv[1],"cwd":sys.argv[2],"tool_name":sys.argv[3],"tool_input":json.loads(sys.argv[4])}))' "$SID" "$1" "$2" "$3"; }
    
    echo "== guard.py: Edit/Write =="
    F="$TMP/app.py"; echo "x = 1" > "$F"
    check "Edit un-Read file -> deny"        2 "$(run_guard "$(j "$TMP" Edit "{\"file_path\":\"$F\"}")")"
    printf '%s' "$(j "$TMP" Read "{\"file_path\":\"$F\"}")" | python3 "$HERE/track.py"
    check "Edit after Read -> allow"         0 "$(run_guard "$(j "$TMP" Edit "{\"file_path\":\"$F\"}")")"
    check "Write existing file -> deny"      2 "$(run_guard "$(j "$TMP" Write "{\"file_path\":\"$F\"}")")"
    check "Write new file -> allow"          0 "$(run_guard "$(j "$TMP" Write "{\"file_path\":\"$TMP/new.py\"}")")"
    check "Edit under node_modules -> deny"  2 "$(run_guard "$(j "$TMP" Edit "{\"file_path\":\"$TMP/node_modules/a.js\"}")")"
    check "Write lockfile -> deny"           2 "$(run_guard "$(j "$TMP" Write "{\"file_path\":\"$TMP/package-lock.json\"}")")"
    
    echo "== guard.py: Bash =="
    check "pkill by name -> deny"            2 "$(run_guard "$(j "$TMP" Bash '{"command":"pkill node"}')")"
    check "taskkill /IM -> deny"             2 "$(run_guard "$(j "$TMP" Bash '{"command":"taskkill /IM node.exe /F"}')")"
    check "kill by PID -> allow"             0 "$(run_guard "$(j "$TMP" Bash '{"command":"kill 12345"}')")"
    check "git push w/o flag -> deny"        2 "$(run_guard "$(j "$TMP" Bash '{"command":"git push origin dev"}')")"
    mkdir -p "$TMP/.claude"; touch "$TMP/.claude/.allow-push"
    check "git push with flag -> allow"      0 "$(run_guard "$(j "$TMP" Bash '{"command":"git push origin dev"}')")"
    [ ! -e "$TMP/.claude/.allow-push" ] && { PASS=$((PASS+1)); echo "  ok  push flag consumed (single-use)"; } || { FAIL=$((FAIL+1)); echo "FAIL  push flag not consumed"; }
    
    echo "== guard.py: secret scan on commit =="
    REPO="$TMP/repo"; mkdir -p "$REPO"; git -C "$REPO" init -q
    echo 'api_key = "sk-supersecret-12345"' > "$REPO/cfg.py"; git -C "$REPO" add cfg.py
    check "commit with staged secret -> deny" 2 "$(run_guard "$(j "$REPO" Bash '{"command":"git commit -m x"}')")"
    git -C "$REPO" reset -q; echo 'name = "hello"' > "$REPO/cfg.py"; git -C "$REPO" add cfg.py
    check "commit clean staged -> allow"      0 "$(run_guard "$(j "$REPO" Bash '{"command":"git commit -m x"}')")"
    
    echo "== bypass + log =="
    EC=$(printf '%s' "$(j "$TMP" Write "{\"file_path\":\"$F\"}")" | GUARDRAILS_BYPASS=1 python3 "$HERE/guard.py" >/dev/null 2>&1; echo $?)
    check "bypass allows denied call"        0 "$EC"
    grep -q "BYPASS Write" "$TMP/.claude/guardrails-bypass.log" 2>/dev/null \
      && { PASS=$((PASS+1)); echo "  ok  bypass logged"; } || { FAIL=$((FAIL+1)); echo "FAIL  bypass not logged"; }
    
    echo "== stop_verify.py =="
    mk_transcript() { # $1=file $2=claim-text $3=with_edit(0/1)
      : > "$1"
      [ "$3" = "1" ] && printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"tool_use","name":"Edit","input":{"file_path":"a.py"}}]}}' >> "$1"
      python3 -c 'import json,sys; print(json.dumps({"type":"assistant","message":{"content":[{"type":"text","text":sys.argv[1]}]}}))' "$2" >> "$1"
    }
    run_stop() { printf '{"transcript_path":"%s","stop_hook_active":false}' "$1" | python3 "$HERE/stop_verify.py" >/dev/null 2>&1; echo $?; }
    T="$TMP/t1.jsonl"; mk_transcript "$T" "The bug is fixed now." 1
    check "edited + bare done-claim -> block" 2 "$(run_stop "$T")"
    T="$TMP/t2.jsonl"; mk_transcript "$T" "Fixed. Verified: pytest -> 3 passed" 1
    check "edited + Verified: -> allow"       0 "$(run_stop "$T")"
    T="$TMP/t3.jsonl"; mk_transcript "$T" "All done for today!" 0
    check "no edits + done-claim -> allow"    0 "$(run_stop "$T")"
    T="$TMP/t4.jsonl"; mk_transcript "$T" "Edited files. EDITED-UNVERIFIED: a.py" 1
    check "edited + EDITED-UNVERIFIED -> allow" 0 "$(run_stop "$T")"
    
    echo "== rearm.py =="
    KITDIR="$TMP/kitproj"; mkdir -p "$KITDIR/docs"
    echo '<!-- guardrails-kit: v1.2 -->' > "$KITDIR/CLAUDE.md"
    run_rearm() { printf '{"cwd":"%s","source":"%s"}' "$1" "$2" | python3 "$HERE/rearm.py" 2>/dev/null; }
    OUT=$(run_rearm "$KITDIR" compact)
    echo "$OUT" | grep -q "routing row 6" && echo "$OUT" | grep -q "STATE.md is missing" \
      && { PASS=$((PASS+1)); echo "  ok  compact -> re-arm + missing-STATE nudge"; } \
      || { FAIL=$((FAIL+1)); echo "FAIL  compact re-arm output: $OUT"; }
    mkdir -p "$KITDIR/docs"; echo x > "$KITDIR/docs/STATE.md"
    OUT=$(run_rearm "$KITDIR" startup)
    [ -z "$OUT" ] && { PASS=$((PASS+1)); echo "  ok  startup + fresh STATE -> silent"; } \
      || { FAIL=$((FAIL+1)); echo "FAIL  startup not silent: $OUT"; }
    NOKIT="$TMP/nokit"; mkdir -p "$NOKIT"
    OUT=$(run_rearm "$NOKIT" compact)
    [ -z "$OUT" ] && { PASS=$((PASS+1)); echo "  ok  non-kit project -> silent"; } \
      || { FAIL=$((FAIL+1)); echo "FAIL  non-kit not silent: $OUT"; }
    
    rm -f "$STATE"
    echo
    echo "RESULT: $PASS passed, $FAIL failed"
    [ "$FAIL" -eq 0 ]
    
  • hooks/track.pyGitHub
    Read the script
    #!/usr/bin/env python3
    """Guardrails companion — PostToolUse read/edit tracker (roadmap Phase B1).
    
    Records file paths the session has Read (or successfully Edited/Written) into a
    per-session state file; hooks/guard.py checks it to enforce iron rule 1 / C1.
    """
    import json
    import os
    import sys
    import tempfile
    
    
    def main() -> None:
        try:
            data = json.load(sys.stdin)
        except (json.JSONDecodeError, ValueError):
            sys.exit(0)
        if data.get("tool_name") not in ("Read", "Edit", "Write"):
            sys.exit(0)
        path = (data.get("tool_input") or {}).get("file_path")
        if not path:
            sys.exit(0)
        state = os.path.join(
            tempfile.gettempdir(), f"guardrails-reads-{data.get('session_id', 'unknown')}.txt"
        )
        try:
            existing = set()
            if os.path.exists(state):
                with open(state) as f:
                    existing = {line.strip() for line in f}
            if path not in existing:
                with open(state, "a") as f:
                    f.write(path + "\n")
        except OSError:
            pass
        sys.exit(0)
    
    
    if __name__ == "__main__":
        main()
    

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

@altafino
Ships withfableclaudemdforopus

A portable CLAUDE.md + documentation set that makes Claude Opus / Sonnet operate as close to frontier (Fable) level as possible inside Claude Code: fewer logic errors, fewer introduced bugs, fewer wasted tokens.

Stars on GitHub

Momentum

stars in total
7
Star history of altafino/FableClaudeMDForOpus on GitHub, a chart by star-history.com

More plugins that ship hooks.

superpowers

superpowers

A development methodology for coding agents that makes the agent brainstorm before building, follow plans, run parallel work and finish branches in a disciplined way. You get fewer skipped steps and more consistent results from your coding assistant.

15 skills1 hook311k
@obra@obra
Install Plugin
ponytail

ponytail

A Claude Code plugin with skills for auditing a whole repo, reviewing a change, listing deferred shortcut comments, and showing measured savings and a quick command reference.

6 skills10 hooks188k
@dietrichgebert@dietrichgebert
Install Plugin
agent-skills

agent-skills

Walks a software change from a written spec through design, testing, code review, CI/CD, and release, applying the matching engineering skill at each stage. Useful for developers using Claude Code on full development workflows.

25 skills+3112k
@addyosmani@addyosmani
Install Plugin
planning-with-files

planning-with-files

Persistent file-based planning for AI coding agents on long-running, multi-step tasks. It keeps task_plan.md, findings.md and progress.md on disk, so work can resume after /clear or context compaction.

6 skills+228k
@othmanadi@othmanadi
Install Plugin
gsd-core

gsd-core

A spec-driven development workflow for Claude Code that guides a project through planning, phases, milestone audits and fixes. Includes skills to generate tests from UAT criteria and to audit completed work.

72 skills+311k
@open-gsd@open-gsd
Install Plugin
superpowers-zh

superpowers-zh

Chinese-language skill pack for Claude Code that adds workflow guidance for brainstorming, TDD, debugging, and code review, plus Chinese conventions for commits, documentation, and domestic Git platforms like Gitee and Coding.net.

21 skills1 hook8.5k
@jnmetacode@jnmetacode
Install Plugin
socraticode

socraticode

Gives Claude Code semantic search, dependency graph and architecture context over a codebase, so an AI assistant can explore and understand a project's code. Includes skills for indexing a project and managing its file watching.

2 skills+33.4k
@giancarloerra@giancarloerra
Install Plugin
cocoindex-code

cocoindex-code

A lightweight code search engine that uses AST-based semantic search with local embeddings and incremental indexing. It helps Claude Code users find code faster and more efficiently.

1 skill1 MCP+13k
@cocoindex-io@cocoindex-io
Install Plugin

Questions about the hooks in fableclaudemdforopus.

Which coding agents can use the hooks in fableclaudemdforopus?
The hooks in fableclaudemdforopus install in Claude Code and Cursor: each runs Claude Code hooks from .claude/settings.json, so copy the hooks there.