Development
Hook
Hooks
What loop-kit runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
> /plugin marketplace add cbdreamer11/CB-loop-kit-claude-plugin > /plugin install loop-kit@loop-kit
Ships with loop-kit. Installing the plugin gets these hooks.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
sh "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
PreToolUse
- Matches
Bashsh "${CLAUDE_PLUGIN_ROOT}/hooks/guard-publish.sh"
Where it lives
- hooks/guard-publish.shRunsGitHub
Read the script
#!/bin/sh # PreToolUse hook (matcher: Bash) — the only two laws a hook can actually enforce. # # Blocks: pushing to the protected branch, and the commands this project declared # forbidden in .loop/VERIFY.md. Exit 2 blocks the call and shows stderr to the agent. # # What this canNOT do, and the README says so plainly: it cannot tell whether # something was really verified. That is not enforceable by any hook — only # deterministic facts are. Do not pretend otherwise. # # Bypassable by design (--safe-mode, disabled hooks, managed settings). This is a # guardrail against an honest mistake, not a security boundary. # # Known limitation, stated rather than hidden: matching is done on the payload text, so a # command that merely CONTAINS a forbidden string is blocked too — a test, a grep, or an # echo that quotes it. Distinguishing "runs a push" from "mentions a push" needs real # command parsing, which is not worth a fragile POSIX implementation. When it fires on a # false positive, rephrase the command or let the owner run it. ROOT=${CLAUDE_PROJECT_DIR:-.} VERIFY="$ROOT/.loop/VERIFY.md" PAYLOAD=$(cat) # Only enforce in a project that actually adopted the loop. Installing this plugin must # never change how git behaves in unrelated repositories — that is how a useful guard # becomes the reason someone uninstalls it. (Caught in real use: with no VERIFY.md the # guard fell back to protecting `main` everywhere and blocked a legitimate push in a # project that had never run setup.) # # Note the scope limit: the hook can only see the session's project directory. If you # work on repository A in a session rooted at repository B, A's rules are not the ones # being applied. Declared, not hidden. [ -f "$VERIFY" ] || exit 0 # Protected branch, as declared in VERIFY.md. If the field is still the template blank, # protect the usual suspects — the project adopted the loop, it just has not filled it in. BRANCH=$(sed -n 's/^- Protected branch[^`]*`\([^`]*\)`.*/\1/p' "$VERIFY" 2>/dev/null | head -1) case "$BRANCH" in ''|_____*) BRANCH="main master" ;; esac case "$PAYLOAD" in *"git push"*) for b in $BRANCH; do case "$PAYLOAD" in *"$b"*) echo "Blocked: pushing to the protected branch '$b' is the owner's action, not the agent's." >&2 echo "Commit on a working branch instead, and report that it is ready to publish." >&2 exit 2 ;; esac done # A bare 'git push' on the protected branch is the same thing. CUR=$(git -C "$ROOT" branch --show-current 2>/dev/null) for b in $BRANCH; do if [ "$CUR" = "$b" ]; then echo "Blocked: the current branch is the protected branch '$b'. Publishing is the owner's call." >&2 exit 2 fi done ;; esac # Commands this project declared forbidden, comma-separated inside backticks. # # Note for anyone editing this: do NOT loop with `... | while read`. A pipeline runs # in a subshell, so `exit 2` would only leave the subshell and the hook would print # "Blocked" while returning 0 — a guard that says it blocks and does not. (That bug # was in the first version of this file and was caught by actually running it.) if [ -f "$VERIFY" ]; then FORBIDDEN=$(sed -n 's/^- Forbidden commands[^`]*`\([^`]*\)`.*/\1/p' "$VERIFY" 2>/dev/null | tr ',' '\n') OLDIFS=$IFS IFS=' ' for cmd in $FORBIDDEN; do IFS=$OLDIFS cmd=$(printf '%s' "$cmd" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') case "$cmd" in ''|_____*) IFS=' '; continue ;; esac case "$PAYLOAD" in *"$cmd"*) echo "Blocked: '$cmd' is listed as a forbidden command in .loop/VERIFY.md." >&2 echo "If it genuinely needs to run, the owner runs it." >&2 exit 2 ;; esac IFS=' ' done IFS=$OLDIFS fi exit 0 - hooks/session-start.shRunsGitHub
Read the script
#!/bin/sh # SessionStart hook — mechanizes step 0 ("locate yourself"). # # Whatever this prints on stdout is added to the session's context before the first # token, so a session lands already oriented: what is next, what is parked, what # landed recently, and which files another session may be holding. # # Never fails the session: exits 0 no matter what. If it cannot find the loop, it # stays quiet rather than shouting at a project that does not use it. ROOT=${CLAUDE_PROJECT_DIR:-.} STATE="$ROOT/.loop/STATE.md" [ -f "$STATE" ] || exit 0 # Placeholder lines from the template (they contain <angle brackets>) are filtered # out everywhere: an unfilled template line read as a real gap sends a session # chasing work that does not exist. NOISE='<[^>]*>' echo "## Where this project stands (from .loop/STATE.md)" echo sed -n '/^## Epic:/,/^### Slices/p' "$STATE" | grep -vE "$NOISE" | sed -n '1,15p' echo echo "### Open items, gaps and blocks" OPEN=$(grep -E '^(\[ \]|GAP|PARKED|BLOCKED)' "$STATE" | grep -vE "$NOISE" | sed -n '1,20p') [ -n "$OPEN" ] && echo "$OPEN" || echo "(none recorded)" echo CLOSED=$(grep -E '^\[x\]' "$STATE" | grep -vE "$NOISE" | sed -n '1,10p') if [ -n "$CLOSED" ]; then echo "### Already done — do not rebuild these" echo "$CLOSED" echo fi if [ -f "$ROOT/.loop/GOTCHAS.md" ]; then echo "### Traps this project has already sprung (see .loop/GOTCHAS.md)" grep -E '^## ' "$ROOT/.loop/GOTCHAS.md" | sed -n '1,10p' echo fi echo "### Recent commits" git -C "$ROOT" log --oneline -15 2>/dev/null echo HOT=$(git -C "$ROOT" status --porcelain 2>/dev/null | sed -n '1,15p') if [ -n "$HOT" ]; then echo "### Uncommitted files — may belong to a parallel session, do not touch what is not yours" echo "$HOT" echo fi echo "Before building anything: read .loop/VERIFY.md and follow the loop skill." echo "Anything already committed is not rebuilt. Confirm against the code, not against a document." exit 0
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.
Ships withloop-kit
A working method for building real software with coding agents, across many sessions.
Get the whole plugin

