PLUGIN brewcode
Hooks
Hooks are Node.js scripts attached to Claude Code lifecycle events. Brewcode registers 2 hook commands in hooks.json, both on SessionStart, plus a shared lib/utils.mjs with their I/O helpers.
Summary table
| # | Hook | Event | Matcher | Channel | Timeout | Purpose |
|---|---|---|---|---|---|---|
| 1 | session-start | SessionStart | — | systemMessage | 3s | User-visible session/version info and permission_mode tag |
| 2 | compact-recall | SessionStart | compact | additionalContext | 2s | Re-anchor plan, intent and task graph after a compaction |
Both start through /bin/sh and hooks/run-node.sh, which finds Node.js (Node runtime). Without a usable Node.js 14.13.1+, the launcher skips them silently and logs one line per day. The systemMessage channel is user-visible; additionalContext is model context. Optional project search hooks come from semble-setup, not from the plugin registration.
brewcode mods: Prompt placeholders · Plan-mode effort · Reminders · all mods
Brewcode mods bundle their own command hooks and a hooks module; they are separate from the two hooks on this page.
Technical details
session-start.mjs #
Checks for a newer brewcode release (24 h cache), manages the plan symlink and appends the active permission_mode to the systemMessage when the payload has it. It emits no additionalContext, so version info never becomes model instructions.
| Source | Behavior |
|---|---|
startup, resume | Log session_id, append permission_mode tag when present |
clear | Create symlink LATEST.md -> newest plan |
LATEST.md: the plans dir resolves from local, project, then user plansDirectory (a value outside the project falls back to <config dir>/plans, following CLAUDE_CONFIG_DIR, else ~/.claude). The newest plan by mtime is linked as .claude/plans/LATEST.md only if it is under 60 seconds old; unrelated files and foreign links are preserved. If .claude/plans is itself a symlink out of the project, the hook logs a warning and creates nothing.
compact-recall.mjs: decision ladder #
Re-anchors plan, original intent and task graph after a compaction, so the session does not start a new task graph on top of the old one. It uses SessionStart additionalContext, because UserPromptSubmit does not fire on an auto-compaction. It reads only this session’s transcript_path (regular file, at most 64 MB) and scans the raw buffer for a few markers, no JSONL parsing.
First match wins:
| Branch | Condition | Injects |
|---|---|---|
plan-file | last recorded plan path still exists | [PLAN]: read that file first |
plan-latest | no usable planFilePath, but .claude/plans/LATEST.md is a symlink this plugin created, pointing at an existing plan | [PLAN]: read that link first |
plan-missing | plan path recorded, file gone (plans dir pruned) | [PLAN]: rebuild from the compact summary plus TaskList |
plan-in-summary | plan mode ran, no plan file recorded | [PLAN]: follow a plan if the summary holds one, else intent |
intent | none of the above | [INTENT]: re-read the original task from the summary |
plan-latest exists because a plan that predates the transcript (after --resume or /clear) leaves no marker to scan. A plain file or foreign symlink never qualifies, so this rung only points at this project’s own latest plan. [TASKS] is appended whenever the transcript contains a TaskCreate, ordered before the plan is acted on; TaskList is the authority, since the built-in reminder can lag.
On source === 'compact' it always injects something once Node.js runs it: every failure inside the hook degrades to [INTENT], and it never names a foreign plan. Without Node.js the launcher skips it silently. The trade-off: with two plans in one session the last recorded plan path wins.
Hook files and I/O protocol #
brewcode/hooks/
session-start.mjs SessionStart handler
compact-recall.mjs SessionStart handler, matcher "compact"
hooks.json Event bindings
lib/utils.mjs Shared I/O, configuration, logging
tests/run.sh Runs every suite (suite-session-start.mjs covers both hooks)Each hook reads JSON from stdin (readStdin()), writes JSON to stdout (output()) and logs to stderr. Optional permission_mode is a payload signal, not a value to infer. lib/utils.mjs also exports projectRoot(): CLAUDE_PROJECT_DIR when it points at a real path, else the nearest .git or .claude directory above the hook’s cwd, else cwd itself; it never throws.
Related
Brewcode overview
Plugin skills, agents and hooks.
Semble setup
Optional project search hooks, installed separately.
View on GitHub
Hook source and tests.
Update: /brewtools:plugin-update, /reload-plugins, then /brewcode:setup-status upgrade - FAQ