memo-hooks
One skill, three branches. On every invocation, detect install state first, then route.
Step 1: Detect install state
STATE_SH="$(find .claude/skills -name state.sh -path '*/memo-hooks/*' 2>/dev/null | head -1)"
[ -n "$STATE_SH" ] || STATE_SH=".claude/skills/memo-hooks/modules/state.sh"
bash "$STATE_SH" \
detect \
"$(pwd)/.claude/memo-flow/config.json" \
"$HOME/.claude/memo-flow/registry.json" \
"$(pwd)" \
"$(pwd)/.claude/settings.json" \
"$HOME/.claude/settings.json"
The find covers nonstandard install locations; the direct path is the standard install location, used when the substitution comes back empty. If neither resolves to a file on disk, treat the state as not_installed.
The two trailing settings paths make detection honest: an enabled hook must have its script on disk and an entry in one of those settings files, or the install is not healthy.
Output is one of: not_installed | healthy | broken_no_config | broken_no_registry | broken_unwired
Route based on the output:
| State | Branch |
|---|---|
not_installed |
Fresh install |
healthy |
Day-two management |
broken_no_config |
Broken / repair |
broken_no_registry |
Broken / repair |
broken_unwired |
Broken / repair |
All-disabled is healthy. If the user has disabled every hook, state is still
healthy. Do not nag — go straight to the management menu. Disabled is a valid intentional choice.
Branch A: Fresh install
Fires when state is not_installed — the project has never had hooks installed.
A1. Narrative beat
Emit this orienting prose before doing anything. No AskUserQuestion here.
Hooks fire on Claude Code lifecycle events (UserPromptSubmit, PostToolUse, etc.).
The base tier just installed infrastructure — all hooks are disabled by default.
You opt in next.
There's no separate "are you sure?" prompt before the install — the
opt-in step where you pick which hooks to enable IS the consent moment.
Nothing runs until you turn it on.
This establishes the same onboarding tone as /memo-flow's narrative beat and names the asymmetry: the gate is skipped because the mutations are inert.
A2. Run the installer
bash .claude/skills/memo-hooks/install.sh --project-dir "$(pwd)"
The installer:
- Copies hook scripts to
.claude/memo-flow/hooks/ - Writes a fresh
config.jsonwith all hooks disabled (users opt in below) - Adds settings.json entries so hooks fire in Claude sessions
- Registers the project in
~/.claude/memo-flow/registry.json - Sets
FRESH_CONFIG=1in its output when it just createdconfig.jsonfor the first time
When the installer says all hooks are DISABLED by default, proceed to A3. Otherwise (re-run over a healthy install) config.json was untouched — jump to Branch B.
A3. Batched opt-in
One AskUserQuestion with 2 sub-questions rendered together. Do not split into separate calls.
Sub-question 1 — Which hooks to enable? (multiSelect)
Options:
context-monitor— Watches your session's token count on every UserPromptSubmit. When you're nearing the context limit, it injects a warning so you can run/handoffbefore reasoning degrades. Default threshold: 130 000 tokens.skill-leaderboard— Counts every skill invocation and writes totals to~/.claude/memo-flow/skill-usage.jsonafter each Skill tool call. Runmemo-hooks leaderboardto view.handoff-clipboard— When the/handoffskill writes its temp file (amktemppath likehandoff-A1B2C3.md), copies the absolute path to your system clipboard so you can paste it into the next session without scrolling for it. macOS + Linux only.none — I'll enable later— leaves everything disabled; you can flip individual hooks via/memo-hooksBranch B any time.
Sub-question 2 — If you enable context-monitor, what mode? (single-select)
Always asked, even if context-monitor was not selected in sub-question 1. Cost is zero — the mode value is idempotent and ignored when the hook is disabled. This future-proofs the flow if the user enables context-monitor later via Branch B.
Options:
notify(default) — soft reminder to run/handoffon every over-threshold turnnotify-once— same copy, first crossing only (uses a sentinel under~/.claude/memo-flow/state/)nag— sharper warning on every turnauto-handoff— stops the agent, calls/handoffwith inferred intent
Deprecated aliases still accepted (emit a stderr warning): inject-context → notify, remind-once → notify-once, remind-until → nag, auto → auto-handoff.
A4. Apply selections via CLI
Every mutation goes through memo-hooks --set. Never edit config.json directly.
Always run all three commands unconditionally — even when context-monitor is disabled, set the mode so it's ready when the user enables it later via Branch B.
.claude/memo-flow/bin/memo-hooks --set context-monitor=<true|false>
.claude/memo-flow/bin/memo-hooks --set skill-leaderboard=<true|false>
.claude/memo-flow/bin/memo-hooks --set context-monitor.mode=<picked-mode>
A5. Structured summary
Emit this block, substituting {var} placeholders:
**Done.** Hooks tier is set up in `{project}`.
**What just changed:**
- Enabled: {list of enabled hooks} [or 'No hooks enabled (default disabled).']
- Mode for context-monitor: {mode}
- Hook scripts installed at `.claude/memo-flow/hooks/`
- Config written to `.claude/memo-flow/config.json`
- Settings entry added to `.claude/settings.json` (fenced)
- Project registered in `~/.claude/memo-flow/registry.json` (hooks tier)
**Try this next:**
- `/memo-hooks` — management menu (enable/disable hooks, change modes, view leaderboard)
- `memo-hooks leaderboard` from a terminal once your skills get used
**Where to learn more:**
- `.claude/skills/memo-hooks/SKILL.md` — full skill reference
- `.claude/memo-flow/config.json` — config file (don't edit directly, use `--set`)
Re-run `/memo-hooks` any time to enable/disable hooks, change modes, or view the skill leaderboard.
Branch B: Day-two management
Fires when state is healthy — includes the all-disabled-on-purpose case.
B1. Read and summarise current state
.claude/memo-flow/bin/memo-hooks status
Display active (enabled) hooks grouped by the Claude Code event they fire on, in lifecycle order:
UserPromptSubmit
context-monitor: ENABLED — mode <mode>, threshold <N> tokens
PostToolUse
skill-leaderboard: ENABLED — writing to <output_file>
Rules:
- Only active (enabled) hooks appear. Disabled hooks are not listed.
- Enabled means verified, not assumed.
statuscross-checks each enabled hook against the runtime. A hook whose script is missing from.claude/memo-flow/hooks/or which has nosettings.jsonentry is flagged inline:<hook>: ENABLED (config) but NOT wired — <reason>; re-run install.sh to repair. Surface that line to the user verbatim and offer the Branch C repair. - Only event headers with at least one active hook are shown. No empty sections.
- Lifecycle order (top to bottom): SessionStart → UserPromptSubmit → PreToolUse → PostToolUse → Notification → PreCompact → Stop → SubagentStop → SessionEnd.
- If no hooks are active, output is
(no active hooks).
When context-monitor is disabled, also surface its settings as a reminder:
context-monitor: disabled — would warn at <N> tokens in <mode> mode if enabled
B1.5. Check for pending hooks (bundle-vs-installed diff)
Before presenting the management menu, detect hooks that shipped in the bundle but are not yet installed in this project.
A hook is pending when its script (<hook>.sh) is in the bundle (hooks/ under the skill folder) but absent from .claude/memo-flow/hooks/. Missing config.json keys are repaired by a separate idempotent loop later in the same installer run, so the pending-hook check is intentionally narrow.
Run the installer in non-interactive mode to collect pending hooks:
bash .claude/skills/memo-hooks/install.sh \
--project-dir "$(pwd)" \
--non-interactive \
2>/dev/null
The installer's _get_missing_hooks logic detects bundle-vs-hooks-dir gaps and installs them automatically (copies script, inserts config key with enabled: false, adds settings entry). Its _get_unwired_hooks companion rewires hooks whose script survived but whose settings entry was lost — reconciliation runs against the bundle's full hook set and the actual runtime state, never the install's historical manifest. After the installer exits, re-run status to get a fresh view.
This check is transparent to the user when there are no pending hooks. Only surface the install action when the installer actually copied something new (i.e., check if any hooks were mentioned in its output as "installed new hook: ...").
When the installer reports one or more newly installed hooks, emit a brief notice before the B2 menu:
New hook(s) installed from the latest bundle:
• handoff-clipboard — (disabled by default; enable below if you want it)
Then continue to B2 as normal. Do not auto-enable newly installed hooks — the user must opt in.
The state.sh detect output is unchanged by this step; no new state is emitted.
B2. Offer actions via AskUserQuestion
Tailor options to current state. Always include:
- Enable / disable a hook — one option per hook, labelled with its current state (e.g. "Enable context-monitor" or "Disable skill-leaderboard"). Lead with the enabling direction when a hook is disabled.
- Change context-monitor mode (only when context-monitor is enabled) — sub-options with one-line behaviour descriptions (see mode table in A3).
- Change context-monitor threshold (only when context-monitor is enabled) — ask for a token number as a follow-up.
- View skill leaderboard — runs
memo-hooks leaderboard. - Done — exit.
B3. Execute via CLI
.claude/memo-flow/bin/memo-hooks --set context-monitor=true
.claude/memo-flow/bin/memo-hooks --set context-monitor=false
.claude/memo-flow/bin/memo-hooks --set context-monitor.mode=nag
.claude/memo-flow/bin/memo-hooks --set context-monitor.threshold=80000
.claude/memo-flow/bin/memo-hooks --set skill-leaderboard=false
.claude/memo-flow/bin/memo-hooks leaderboard 10
--set value coercion: true/false → bool, all-digits → int, everything else → string.
After each action, confirm in one short line ("context-monitor.mode is now nag"). Hooks re-read config.json on every prompt — changes are live immediately, no restart needed.
B4. Loop or exit
Return to B1 with refreshed state after each action. Exit cleanly on Done or when the user signals they're finished.
Branch C: Broken / repair
Fires when state is broken_no_config, broken_no_registry, or broken_unwired. Print a diagnostic, then ask whether to repair.
C1. Diagnostic
broken_no_config — the user registry lists this project as hooks-installed, but .claude/memo-flow/config.json is missing or unreadable. Likely cause: manual deletion or a failed install. Hook scripts may still be in .claude/memo-flow/hooks/ but hooks cannot read their configuration.
broken_no_registry — .claude/memo-flow/config.json exists, but ~/.claude/memo-flow/registry.json does not list this project with the hooks tier. Likely cause: the registry was reset or the project was moved. The config survived but the install record is gone.
broken_unwired — config.json enables a hook that cannot fire: its script is missing from .claude/memo-flow/hooks/ or it has no settings.json entry. Likely cause: the bundle gained the hook after this project's first install, or the wiring was deleted by hand. Run memo-hooks status to see exactly which hook is dead and why. Config choices are intact — only the runtime wiring is gone, and re-running the installer restores it without changing any enabled/disabled decision.
C2. Ask the user
One AskUserQuestion (single-select):
- Re-run installer — the installer is idempotent. It re-copies any missing hook scripts, re-adds missing settings.json entries, and restores the registry entry. It never touches an existing
config.json— your enabled/mode/threshold choices are safe. - Cancel — leave things as-is.
C3. Repair
bash .claude/skills/memo-hooks/install.sh --project-dir "$(pwd)"
After the installer exits, re-run state detection (Step 1). If the result is healthy, continue to Branch B. If still broken, report the new state and stop — do not loop.
Terminal CLI reference
.claude/memo-flow/bin/memo-hooks is the day-two CLI. It behaves identically to this skill's management branch, but runs in a real terminal.
- With
guminstalled (brew install gumon macOS): no-args invocation shows an interactive checkbox toggle menu. - Without gum: falls back to
$EDITORon the raw JSON. --set <hook>=<value>: flip enabled flag or set a dotted field (context-monitor.mode=nag,context-monitor.threshold=80000).leaderboard [N]: print top-N skills by invocation count (default 10).
.claude/memo-flow/bin/memo-hooks # interactive TUI
.claude/memo-flow/bin/memo-hooks --set context-monitor=true
.claude/memo-flow/bin/memo-hooks --set context-monitor.mode=auto-handoff
.claude/memo-flow/bin/memo-hooks --set context-monitor.threshold=80000
.claude/memo-flow/bin/memo-hooks leaderboard
Implementation notes
- All config writes go through
modules/hook-config.sh(viamemo-hooks --set). Never editconfig.jsonfrom the skill flow. - The installer is idempotent: re-running over a healthy install is a no-op for
config.json. It re-copies missing hook scripts and re-adds missing settings.json entries by comparing the bundle's full hook set against the runtime (disk + settings.json), not against the manifest — a hook the bundle gained after first install is still picked up. Restored hooks keep whateverenabledvalue config.json already has (defaultfalsefor brand-new hooks); repair never flips consent. - If
.claude/memo-flow/bin/memo-hooksis missing, re-runinstall.sh— it restores the wrapper.