Think-Short
Installer/configurator skill. It wires three self-contained hooks (SessionStart, UserPromptSubmit, SubagentStart) that inject a terse-output prompt — or configures/removes them. No profiles and no project-level config: the only state is the copied
think-short-prompt.mdplus an ephemeral per-session counter in the OS temp dir (os.tmpdir()/brewtools-think-short/<session_id>.think-short-counter), auto-pruned. The hooks own all runtime behavior. This skill only decides mode and project vs global, then delegates the file work to thebrewcode:hook-creatoragent following the runbook.
What the hooks do (informational — skill does NOT implement)
| Hook | Behavior |
|---|---|
| SessionStart | inject the full terse prompt + reset the per-session counter |
| UserPromptSubmit | inject the full prompt every 10th user prompt (10/20/30…, not the 1st) |
| SubagentStart | inject the full terse prompt into spawned subagents (additionalContext accumulates across hooks — no coexistence/yield logic needed) |
All three read think-short-prompt.md from their OWN directory and emit {} when it cannot be read. There is no enabled flag and no config file to add one to — so disable renames the copied prompt to think-short-prompt.md.disabled: the hooks stay wired, find no prompt, and every event becomes a genuine no-op. enable renames it back. This is the hooks' existing fail-open path, not new machinery.
think-short-prompt.mdis copied into the target at install — an existing install keeps its old text forever. After the prompt changes (incl. a brewtools update), runupgradeon that target to pick it up.
Prompt contract
Position 1 of $ARGUMENTS is a free-form prompt (RU/EN) — modes and the project/global target
are optional and may follow in any order. Nobody types keys: resolve mode + target FROM the
prompt.
- Strip nothing beyond mode/target words themselves — no other flags exist. An explicit mode token anywhere wins outright, no scoring.
- Else score modes by distinct whole-word keyword hits (Step 2 table below). Highest unique
score wins. Tie between install and a removal verb ->
AskUserQuestion; never guess destructive. All zero -> the documented default:statusif anything is installed, elseinstall. - Empty arguments -> the same documented default; Step 1's STATUS FIRST always runs before anything else, so the answer is never guessed blind.
- Outcome-changing ambiguity (target unspecified, mode ambiguous between install/removal) ->
ONE
AskUserQuestionBEFORE any work — this is already Steps 2-3's own gate. - Prose that is not a mode/target keyword is still input: extract intent from it (e.g. "подхвати
новый промпт" ->
upgrade), never treat the first word as a positional id.
Then print this block ONCE, before the first action:
PLAN — brewtools:think-short-setup
INPUT: <arguments verbatim, or "(empty)">
MODE: <resolved> — <explicit | matched keyword: X | default>
SCOPE: <resolved target: project|global, resolved paths>
DO: <2-5 imperative bullets>
RESULT: <what the user ends up holding>
Labels are literal; values follow the conversation language. status (read-only) prints it at
the end of Step 1, right before its state table. Every mutating mode prints it in Step 4, once
target is resolved, before the hook-creator delegation.
BT_ROOT Resolver (use in EVERY bash block)
The plugin root is resolved from the skill's OWN directory (the CLAUDE_SKILL_DIR prompt substitution), never from CLAUDE_PLUGIN_ROOT -- that env var is not exported to a skill's Bash tool:
SD="${CLAUDE_SKILL_DIR}"
if [ -n "$SD" ] && [ -f "$SD/../../.claude-plugin/plugin.json" ]; then BT_ROOT=$(cd "$SD/../.." && pwd); else BT_ROOT=$(ls -d ~/.claude/plugins/cache/claude-brewcode/brewtools/*/ 2>/dev/null | sort -V | tail -1 | sed 's:/*$::'); fi
[ -n "$BT_ROOT" ] || { echo "ERROR: cannot locate brewtools plugin root -- install/update brewtools first."; exit 1; }
test -d "$BT_ROOT/skills/think-short-setup/assets" || { echo "❌ BT_ROOT invalid: $BT_ROOT"; exit 1; }
Asset paths (all under $BT_ROOT/skills/think-short-setup/assets/):
INSTALL.md— the runbook: install project/global, upgrade, disable/enable, uninstall, purge. Single source of truth — follow it, never re-derive its commands here.think-short-session.mjs,think-short-prompt-counter.mjs,think-short-subagent.mjs,think-short-prompt.md— the hook files that travel together
Never use
Write/Editon~/.claude/*— sensitive path: it prompts indefault/acceptEdits, is auto-approved only underbypassPermissions, and FAILS headless. Global operations run through the Bash tool only (cp+node+mv+rm). The hook-creator agent handles this per the runbook.
Project root (use in EVERY bash block and pass it to the agent)
The project scope is $ROOT/.claude/, never $PWD/.claude/: the shell cwd moves with cd and persists across calls, so $PWD can be a subdirectory and status would report the root install missing while install would build a second, nested .claude/.
# Project root: CLAUDE_PROJECT_DIR -> git toplevel -> upward walk -> PWD.
claude_project_root() {
if [ -n "$CLAUDE_PROJECT_DIR" ] && [ -d "$CLAUDE_PROJECT_DIR" ]; then
printf '%s\n' "$CLAUDE_PROJECT_DIR"; return 0
fi
if r=$(git rev-parse --show-toplevel 2>/dev/null) && [ -n "$r" ]; then
printf '%s\n' "$r"; return 0
fi
d=$PWD
while [ "$d" != "/" ]; do
if [ -d "$d/.git" ] || [ -d "$d/.claude" ]; then printf '%s\n' "$d"; return 0; fi
d=$(dirname "$d")
done
printf '%s\n' "$PWD"; return 1 # nonzero: caller decides
}
Status may report on the fallback root; a mutating mode may NOT — if the resolver returns non-zero, report what was looked for (CLAUDE_PROJECT_DIR, a git toplevel, a .git/.claude marker above $PWD) and stop instead of writing. CLAUDE_PROJECT_DIR is exported to hook child processes, not to this shell, so it is normally empty here and the git toplevel does the work.
Step 1 — STATUS FIRST, always
Run this before anything else, in EVERY mode. Never install, re-install or remove blind.
EXECUTE using Bash tool:
SD="${CLAUDE_SKILL_DIR}"
if [ -n "$SD" ] && [ -f "$SD/../../.claude-plugin/plugin.json" ]; then BT_ROOT=$(cd "$SD/../.." && pwd); else BT_ROOT=$(ls -d ~/.claude/plugins/cache/claude-brewcode/brewtools/*/ 2>/dev/null | sort -V | tail -1 | sed 's:/*$::'); fi
[ -n "$BT_ROOT" ] || { echo "ERROR: cannot locate brewtools plugin root -- install/update brewtools first."; exit 1; }
A="$BT_ROOT/skills/think-short-setup/assets"
for f in INSTALL.md think-short-session.mjs think-short-prompt-counter.mjs think-short-subagent.mjs think-short-prompt.md; do
test -f "$A/$f" || { echo "❌ FAILED — assets incomplete under BT_ROOT=$BT_ROOT (missing $f)"; exit 1; }
done
echo "ASSETS_DIR=$A"
echo "RUNBOOK=$A/INSTALL.md"
claude_project_root() {
if [ -n "$CLAUDE_PROJECT_DIR" ] && [ -d "$CLAUDE_PROJECT_DIR" ]; then
printf '%s\n' "$CLAUDE_PROJECT_DIR"; return 0
fi
if r=$(git rev-parse --show-toplevel 2>/dev/null) && [ -n "$r" ]; then
printf '%s\n' "$r"; return 0
fi
d=$PWD
while [ "$d" != "/" ]; do
if [ -d "$d/.git" ] || [ -d "$d/.claude" ]; then printf '%s\n' "$d"; return 0; fi
d=$(dirname "$d")
done
printf '%s\n' "$PWD"; return 1
}
if ROOT=$(claude_project_root); then ROOT_OK=yes; else ROOT_OK=no; fi
export ROOT
echo "ROOT=$ROOT root_resolved=$ROOT_OK"
for S in "$ROOT/.claude:project" "$HOME/.claude:global"; do
D="${S%%:*}"; N="${S##*:}"
F=0
for f in think-short-session.mjs think-short-prompt-counter.mjs think-short-subagent.mjs; do
[ -f "$D/hooks/$f" ] && F=$((F+1))
done
W=$({ grep -o 'think-short-[a-z-]*\.mjs' "$D/settings.json" 2>/dev/null || true; } | sort -u | wc -l | tr -d ' '); W=${W:-0}
P=none
[ -f "$D/hooks/think-short-prompt.md.disabled" ] && P=disabled
[ -f "$D/hooks/think-short-prompt.md" ] && P=enabled
I=n/a
if [ -f "$D/hooks/think-short-subagent.mjs" ]; then
C=$(node "$D/hooks/think-short-subagent.mjs" --check "$ROOT" </dev/null 2>/dev/null || true)
case "$C" in *'"injects":true'*) I=yes ;; *'"injects":false'*) I=no ;; *) I=unknown ;; esac
fi
echo "$N: hook_files=$F/3 settings_refs=$W/3 prompt=$P injects=$I"
done
M="${TMPDIR:-/tmp}/brewtools-think-short"
echo "markers=$({ ls "$M" 2>/dev/null || true; } | wc -l | tr -d ' ') dir=$M"
echo "✅ status"
STOP if ❌ — plugin cache incomplete; reinstall/update brewtools first.
Field meanings — do not paraphrase them into something stronger:
| Field | Value |
|---|---|
hook_files |
how many of the 3 scripts are present in that scope's hooks/; 3/3 = complete, 1/3-2/3 = half-installed → repair |
settings_refs |
count of DISTINCT think-short-*.mjs scripts referenced in that scope's settings.json; 0/3 = not wired, 3/3 = fully wired |
prompt |
enabled = think-short-prompt.md present, disabled = only the .disabled rename is there, none = neither (the hooks would no-op even though they are wired) |
injects |
node <that scope's copy>/think-short-subagent.mjs --check "$PWD" → injects. yes = the prompt file is present and readable, so the SubagentStart hook really delivers it. no = wired but the prompt is missing/empty — a broken install, not a yield: additionalContext accumulates across hooks, so nothing here ever loses to another hook. n/a = no subagent hook in that scope. unknown = the installed copy answered nothing parseable — it predates --check, so run upgrade on that scope |
markers |
number of tmp counter files; state only, never affects behavior |
root_resolved |
yes = ROOT came from CLAUDE_PROJECT_DIR, a git toplevel or a .git/.claude marker; no = it fell back to $PWD. On no, report status only and refuse every mutating mode |
settings_refs is a textual count, not a JSON validation — it does not prove the entries are well-formed or attached to the right events.
injects covers ONLY think-short-subagent.mjs (the subagent injection). SessionStart and the every-10th-prompt injection are separate paths and are not measured by it — injects=no means subagents get nothing while the main session still gets the directive.
Read the output into a state table. If MODE resolves to status, print the Prompt contract PLAN
block now, right before this table. Then PRINT the table to the user:
| Scope | Hook files | settings.json wired | Prompt | Injects | Effective |
|---|
Effective = hook_files=3/3 settings_refs=3/3 prompt=enabled injects=yes. Anything else is NOT effective — say so plainly instead of reporting a half-state as installed. In particular: prompt=enabled injects=no means the prompt file is empty or corrupt — fully wired but injecting nothing into subagents. Offer upgrade to re-copy it; do not report it as installed.
Early exit
If it is already installed the way the user could want it and the intent is not explicit (no argument, or vague like "терс-режим"), PRINT the status, list the operations available (upgrade, enable, disable, uninstall, purge, install for the other scope) and STOP. Do not re-install, do not ask a chain of questions.
Step 2 — Decide MODE
Read $ARGUMENTS. Default when there are NO arguments at all = status if anything is installed, install if nothing is.
| Mode | EN keywords | RU keywords | Mutates? |
|---|---|---|---|
status |
(no args + installed), status | статус, проверь, что стоит | no |
install |
(no args + nothing installed), install, set up | поставь, установи | yes |
upgrade |
upgrade, update, refresh, after plugin update | обнови, перевыстави, подхвати новый промпт, после обновления плагина | yes |
enable |
enable | включи обратно, верни | yes |
disable |
disable | выключи, отключи, паузу | yes |
uninstall |
uninstall | убери, сними, удали хуки | yes |
purge |
purge, wipe, remove everything | вычисти всё, удали полностью, снеси | yes, destructive |
Ambiguous between install and a removal verb → AskUserQuestion. Use AskUserQuestion ONLY for genuinely destructive ambiguity, never to guess a mode.
Step 3 — Decide TARGET (project | global)
- Explicit global →
global:global,глобально,for all projects,всех проектах. - Explicit project →
project:project,this repo,локально,здесь,этот проект. - NOT specified →
AskUserQuestion: "think-short for this Project or Globally?" (options: Project / Global). Do not auto-guess. - Settled by the status table when only ONE scope is installed and the mode is not
install— do not ask then.
For uninstall/purge with an unspecified target, ask the same Project/Global question (or offer to clean both if the user says "everywhere").
Step 4 — State the plan, then delegate
For every mutating mode, print the Prompt contract PLAN block now — target is resolved (Step 3) — before the delegation below. Tell the user plainly what will happen, e.g.:
Installing think-short hooks (SessionStart + UserPromptSubmit + SubagentStart) into
<repo>/.claude/and merging<repo>/.claude/settings.json.
For uninstall/purge list exactly which files are deleted and confirm once.
Delegation
A big task handed to one agent = an agent gone for an hour: you cannot observe it, cannot correct it, and it usually drifts off-target. One mode for ONE target is ONE bounded unit (4 asset files + one settings.json, well under 10 steps) — a single hook-creator spawn. A wider request ("install here AND globally AND clean three other repos") MUST be split into N tasks, one per target, all spawned in ONE message.
Every spawn prompt MUST carry:
| Field | Content |
|---|---|
| GOAL | the overall task and why it exists — the point beyond the file edit |
| ROLE | what this agent owns; what it must NOT touch |
| SCOPE | exact paths/commands in bounds + explicit out-of-bounds |
| CONTEXT | what is already done, by whom, what runs in parallel — trimmed to what THIS agent needs |
| CONSUMER | who or what uses the result next, and the shape it must fit |
| DONE | acceptance criteria + the exact report shape you want back |
A bare one-line task is never enough. The prompt below is that shape.
The runbook path only survives if it reaches the SHELL.
RUNBOOKwritten as prose in the prompt is just text — the runbook's blocks derive their source dir from$RUNBOOK, so an un-exported value copies from nowhere. The spawn prompt below therefore carries the literalexportline the agent must run FIRST, in the same Bash invocation as every runbook block.
Spawn the agent (substitute MODE, TARGET, RUNBOOK, ASSETS_DIR from Steps 1-3 — into BOTH the CONTEXT block and the export line):
Task(subagent_type="brewcode:hook-creator", prompt="
GOAL: the user wants think-short terse-mode hooks MODE-ed for TARGET. Three hooks
(SessionStart, UserPromptSubmit, SubagentStart) inject a terse-output prompt; runtime
behavior lives entirely in the hook files, so this task is pure file + settings wiring.
ROLE: you own the file copy/rename/strip and the settings.json merge. Do NOT edit hook
logic, do NOT touch unrelated hooks or settings keys, do NOT act on the other target.
SCOPE: in — the 4 assets under ASSETS_DIR, the target .claude/ dir, its settings.json.
Out — everything else. Project target: everything under ROOT/.claude, never a .claude
under some other cwd. Global target (~/.claude/*): BASH ONLY (cp + node merge + mv + rm),
never Write/Edit — a sensitive path that prompts and fails headless.
CONTEXT:
Step 1 already verified the plugin cache and resolved every path below; nothing has been
copied or merged yet, and no sibling agent is running — you are the only writer.
MODE = MODE (install|upgrade|enable|disable|uninstall|purge)
TARGET = TARGET (project|global)
ROOT = ROOT (absolute project root, already resolved — never re-derive it from $PWD)
RUNBOOK = RUNBOOK (absolute path to assets/INSTALL.md)
ASSETS_DIR = ASSETS_DIR (absolute path to the assets source dir — copy the 4 hook files FROM here)
MANDATORY FIRST BASH COMMAND — the runbook's blocks read this from the ENVIRONMENT,
not from this prompt. Run this VERBATIM as the first line of EVERY Bash call that executes
a runbook block (a new Bash call does NOT inherit exports from the previous one):
export RUNBOOK='RUNBOOK' ROOT='ROOT'
Then verify before writing anything:
echo \"RUNBOOK=\$RUNBOOK ROOT=\$ROOT\"
Follow the runbook at RUNBOOK exactly — it self-locates its source via SRC=\$(dirname \"\$RUNBOOK\").
Sections map 1:1 to MODE: 'PROJECT target'/'GLOBAL target' for install, 'UPGRADE',
'DISABLE / ENABLE', 'UNINSTALL', 'PURGE'.
Merge = append + dedupe by think-short-*.mjs script path (idempotent).
Upgrade = re-copy the 4 files + re-merge, PRESERVING a disabled prompt as .disabled.
Disable/enable = rename think-short-prompt.md <-> think-short-prompt.md.disabled ONLY;
never touch settings.json or the 3 scripts.
Uninstall = strip entries by the 3 basenames, drop empty event arrays, delete the 4 files,
KEEP the tmp markers. Purge = uninstall + delete the tmp marker dir.
CONSUMER: Step 5 reports your result to the user; the settings.json you write is then loaded
by the NEXT Claude Code session, so a malformed merge breaks that session instead of
failing here — report the exact path you touched so it can be checked.
DONE: report which hooks were installed/removed/renamed, the exact settings.json path, and
the final state of think-short-prompt.md (present | .disabled | absent).
")
Step 5 — Final status
Re-run the Step 1 status block and print the refreshed table, plus:
- what changed (files, settings.json, prompt state),
- a NEW session is required for hook WIRING changes (install / upgrade / uninstall / purge) —
/reload-pluginsis NOT needed, these are plain settings.json hooks; SessionStart fires on the nextclaudestart /--resume; enable/disabletake effect immediately — the hooks re-read the prompt file on every call, no restart;- for
upgrade: that the prompt text is what actually changed, since the 3 scripts are usually identical between versions, injectsfor the touched scope — if it is stillno, say plainly that the wiring succeeded but the prompt file is missing/empty, and offerupgrade.
Modes
| Mode | Effect | Hook files | settings.json | Prompt file | tmp markers |
|---|---|---|---|---|---|
status |
report only | — | — | — | — |
install |
wire the 3 hooks | copied | entries merged | copied | — |
upgrade |
re-emit from the current plugin version | re-copied | entries re-merged | re-copied, disabled state kept | kept |
enable |
.disabled → think-short-prompt.md |
kept | kept | renamed back | kept |
disable |
think-short-prompt.md → .disabled; hooks stay wired, become no-ops |
kept | kept | renamed away | kept |
uninstall |
unwire | deleted | entries stripped | deleted | kept |
purge |
full wipe | deleted | entries stripped | deleted | deleted |
Re-install is a no-op. One target per run; "both" is two runs.
Guards
| Condition | Response |
|---|---|
BT_ROOT resolves but $BT_ROOT/skills/think-short-setup/assets missing |
ERROR: think-short: assets not found under $BT_ROOT — plugin cache incomplete. STOP. |
Neither the skill dir nor any cached plugin dir yields .claude-plugin/plugin.json |
ERROR: think-short: cannot locate plugin root — install/update brewtools first. STOP. |
| Status shows installed + vague intent | Print status, list available operations, STOP. Do not re-install. |
| Target unspecified | AskUserQuestion: Project / Global. Never guess. |
| Mode ambiguous between install and removal | AskUserQuestion. Never guess a destructive mode. |
| Global target | Hook-creator MUST use Bash only (cp/node/mv/rm) — Write/Edit on ~/.claude/* prompts and fails headless. |
root_resolved=no in the status block |
The project root fell back to $PWD. Report status and STOP: name CLAUDE_PROJECT_DIR, the git toplevel and the .git/.claude marker as what was looked for. Never install, upgrade, uninstall or purge a guessed root. |
enable asked for but no .disabled file exists |
Not disabled — say so and stop. If prompt=none the install is BROKEN, not disabled: offer upgrade to re-copy the prompt. |
disable asked for but hook_files is not 3/3 |
Nothing effective to disable; report the half-state and offer upgrade or uninstall. |
Status shows injects=no |
Wired and enabled, but the prompt file is missing/empty (broken install) — subagents get nothing. Offer upgrade for that scope. |
Status shows injects=unknown |
The installed think-short-subagent.mjs is older than the --check diagnostic. Offer upgrade for that scope, then re-run status. |
User expects disable to stop the hooks from RUNNING |
It does not: the 3 processes still spawn per event and exit with {}. It removes the injection, not the ~50 ms. Say this; offer uninstall if the cost is the complaint. |
uninstall/purge requested |
Restate exactly what gets deleted, confirm once, then delegate. |
settings.json exists but is malformed JSON |
Report it: every runbook block ABORTS rather than overwriting it blind, and the rm is skipped too so files and settings stay consistent. Offer to fix. |
Smoke Test
Verify the 5 assets exist and the scripts parse before delegating.
EXECUTE using Bash tool:
SD="${CLAUDE_SKILL_DIR}"
if [ -n "$SD" ] && [ -f "$SD/../../.claude-plugin/plugin.json" ]; then BT_ROOT=$(cd "$SD/../.." && pwd); else BT_ROOT=$(ls -d ~/.claude/plugins/cache/claude-brewcode/brewtools/*/ 2>/dev/null | sort -V | tail -1 | sed 's:/*$::'); fi
[ -n "$BT_ROOT" ] || { echo "ERROR: cannot locate brewtools plugin root -- install/update brewtools first."; exit 1; }
A="$BT_ROOT/skills/think-short-setup/assets"
test -d "$A" || { echo "❌ assets dir missing"; exit 1; }
for f in think-short-session.mjs think-short-prompt-counter.mjs think-short-subagent.mjs think-short-prompt.md INSTALL.md; do
test -f "$A/$f" || { echo "❌ missing $f"; exit 1; }
done
test -s "$A/think-short-prompt.md" || { echo "❌ smoke FAILED — think-short-prompt.md is empty (all 3 hooks would no-op)"; exit 1; }
node --check "$A/think-short-session.mjs" && \
node --check "$A/think-short-prompt-counter.mjs" && \
node --check "$A/think-short-subagent.mjs" && \
echo "✅ smoke" || echo "❌ smoke FAILED"
STOP if ❌ — do NOT delegate; reinstall/update brewtools first.
node --check proves the files parse, nothing more. The full behavioral suite is tests/run.sh in the skill dir and is NOT run here.