think-short-setup

v2026.09.24

Installs or removes the think-short terse-mode hooks. Triggers: think-short, be terse, terse mode, инжект терс-режим.

GitHub
安装命令
npx skhub add kochetkov-ma/think-short-setup
Markdown
SKILL.md

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.md plus 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 the brewcode:hook-creator agent following the runbook.

What the hooks do (informational — skill does NOT implement)

HookBehavior
SessionStartinject the full terse prompt + reset the per-session counter
UserPromptSubmitinject the full prompt every 20th user prompt (20/40/60…, not the 1st)
SubagentStartinject 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.md is copied into the target at install — an existing install keeps its old text forever. After the prompt changes (incl. a brewtools update), run upgrade on 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.

  1. Strip nothing beyond mode/target words themselves — no other flags exist. An explicit mode token anywhere wins outright, no scoring.
  2. 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: status if anything is installed, else install.
  3. Empty arguments -> the same documented default; Step 1's STATUS FIRST always runs before anything else, so the answer is never guessed blind.
  4. Outcome-changing ambiguity (target unspecified, mode ambiguous between install/removal) -> ONE AskUserQuestion BEFORE any work — this is already Steps 2-3's own gate.
  5. 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.

<instructions>

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/Edit on ~/.claude/* — sensitive path: it prompts in default/acceptEdits, is auto-approved only under bypassPermissions, 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:

FieldValue
hook_fileshow many of the 3 scripts are present in that scope's hooks/; 3/3 = complete, 1/3-2/3 = half-installed → repair
settings_refscount of DISTINCT think-short-*.mjs scripts referenced in that scope's settings.json; 0/3 = not wired, 3/3 = fully wired
promptenabled = think-short-prompt.md present, disabled = only the .disabled rename is there, none = neither (the hooks would no-op even though they are wired)
injectsnode <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
markersnumber of tmp counter files; state only, never affects behavior
root_resolvedyes = 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-20th-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:

ScopeHook filessettings.json wiredPromptInjectsEffective

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.

ModeEN keywordsRU keywordsMutates?
status(no args + installed), statusстатус, проверь, что стоитno
install(no args + nothing installed), install, set upпоставь, установиyes
upgradeupgrade, update, refresh, after plugin updateобнови, перевыстави, подхвати новый промпт, после обновления плагинаyes
enableenableвключи обратно, верниyes
disabledisableвыключи, отключи, паузуyes
uninstalluninstallубери, сними, удали хукиyes
purgepurge, 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:

FieldContent
GOALthe overall task and why it exists — the point beyond the file edit
ROLEwhat this agent owns; what it must NOT touch
SCOPEexact paths/commands in bounds + explicit out-of-bounds
CONTEXTwhat is already done, by whom, what runs in parallel — trimmed to what THIS agent needs
CONSUMERwho or what uses the result next, and the shape it must fit
DONEacceptance 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. RUNBOOK written 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 literal export line 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-plugins is NOT needed, these are plain settings.json hooks; SessionStart fires on the next claude start / --resume;
  • enable/disable take 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,
  • injects for the touched scope — if it is still no, say plainly that the wiring succeeded but the prompt file is missing/empty, and offer upgrade.

Modes

ModeEffectHook filessettings.jsonPrompt filetmp markers
statusreport only————
installwire the 3 hookscopiedentries mergedcopied—
upgradere-emit from the current plugin versionre-copiedentries re-mergedre-copied, disabled state keptkept
enable.disabled → think-short-prompt.mdkeptkeptrenamed backkept
disablethink-short-prompt.md → .disabled; hooks stay wired, become no-opskeptkeptrenamed awaykept
uninstallunwiredeletedentries strippeddeletedkept
purgefull wipedeletedentries strippeddeleteddeleted

Re-install is a no-op. One target per run; "both" is two runs.

Guards

ConditionResponse
BT_ROOT resolves but $BT_ROOT/skills/think-short-setup/assets missingERROR: 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.jsonERROR: think-short: cannot locate plugin root — install/update brewtools first. STOP.
Status shows installed + vague intentPrint status, list available operations, STOP. Do not re-install.
Target unspecifiedAskUserQuestion: Project / Global. Never guess.
Mode ambiguous between install and removalAskUserQuestion. Never guess a destructive mode.
Global targetHook-creator MUST use Bash only (cp/node/mv/rm) — Write/Edit on ~/.claude/* prompts and fails headless.
root_resolved=no in the status blockThe 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 existsNot 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/3Nothing effective to disable; report the half-state and offer upgrade or uninstall.
Status shows injects=noWired and enabled, but the prompt file is missing/empty (broken install) — subagents get nothing. Offer upgrade for that scope.
Status shows injects=unknownThe 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 RUNNINGIt 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 requestedRestate exactly what gets deleted, confirm once, then delegate.
settings.json exists but is malformed JSONReport 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.

</instructions>
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

NOASSERTION

源路径

brewtools/skills/think-short-setup

默认分支

main

最新提交

7f5b5d8

Tree SHA

5bfd4fc