Think-Short
<instructions>Toggle terse-output mode. Writes state to
$CLAUDE_PLUGIN_DATA/think-short.json(global) or.claude/brewtools/think-short.json(project). Hooks (authored separately) read state and inject profile-specific directives into SessionStart + PreToolUse:Task. This skill ONLY parses intent and mutates state.
Robustness Rules
| Rule | Applies |
|---|---|
Every Bash call ends with && echo "OK ..." || echo "FAILED ..." | ALL |
Never use Write/Edit on ~/.claude/* or $CLAUDE_PLUGIN_DATA — use Bash + Node fs via helpers | ALL |
State writes go through writeState() in helpers/state.mjs (atomic, O_NOFOLLOW, 0600, merges defaults + timestamps) | P2 |
State reads go through resolveEffectiveState() in helpers/state.mjs (merges hardcoded → global → project → env) | P0, status |
NL-prompt resolution ALWAYS logged via log() exported from helpers/state.mjs at INFO level (auto-prefixed think-short), to .claude/logs/brewtools.log | P0 |
BT_ROOT Resolver
$CLAUDE_PLUGIN_ROOT is NOT inherited by the Bash tool in main-conversation slash invocations. Every Bash block MUST resolve BT_ROOT dynamically. Use the env var if present, else pick the newest cached version (survives version bumps — no hardcoded 3.7.4):
BT_ROOT="${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/claude-brewcode/brewtools/*/ 2>/dev/null | sort -V | tail -1 | sed 's:/*$::')}"
test -d "$BT_ROOT/skills/think-short/helpers" || { echo "❌ BT_ROOT invalid: $BT_ROOT"; exit 1; }
Paths (use $BT_ROOT literally in Bash):
- Global state:
$CLAUDE_PLUGIN_DATA/think-short.json(fallback:~/.claude/plugins/data/brewtools-claude-brewcode/think-short.json) — computed bygetPaths(cwd) - Project state:
$PWD/.claude/brewtools/think-short.json— computed bygetPaths(cwd) - State helper:
$BT_ROOT/skills/think-short/helpers/state.mjs— exportsgetPaths,readPluginDefaults,resolveEffectiveState,writeState,log - Safe-write helper:
$BT_ROOT/skills/think-short/helpers/safe-write.mjs— exportssafeReadJson,safeWriteJson - Log file:
$PWD/.claude/logs/brewtools.log(auto-created bylog())
State schema:
{"version":1, "enabled":false, "profile":"medium", "blacklist":["debate","docs-writer","architect"], "updated_at":"ISO"}
P0: Parse Intent
Parse $ARGUMENTS into structured form:
{ op: on|off|profile|status|blacklist, profile?: light|medium|aggressive, blacklistOp?: add|remove, agent?: string, scope?: global|project }
Structural match (exact)
| Input | Resolves to |
|---|---|
on [--scope global|project] | {op:on, scope} |
off | {op:off} |
profile <light|medium|aggressive> | {op:profile, profile} |
status | {op:status} |
blacklist add <agent> | {op:blacklist, blacklistOp:add, agent} |
blacklist remove <agent> | {op:blacklist, blacklistOp:remove, agent} |
NL-prompt fallback (MANDATORY)
If no structural match, treat argument as NL prompt. Algorithm:
- Trim + lowercase.
- Tokenize + apply synonym table:
| Regex / keyword | Resolves to |
|---|---|
включи|включись|enable|активируй|turn on|^on$ | on |
выключи|выключись|disable|отключи|turn off|^off$ | off |
light|лайт|лёгкий|легкий|уровень 1|level 1|\b1\b | profile light |
medium|мид|средний|уровень 2|level 2|\b2\b | profile medium |
aggressive|агрессив|агрессивный|макс|максимально|max|уровень 3|level 3|\b3\b | profile aggressive |
status|статус|как дела|что сейчас | status |
- Combos allowed — e.g.
включись максимально→on+profile aggressive. Execute BOTH ops in sequence. - Ambiguous (0 matches OR >1 mutually-exclusive match that isn't a combo) →
AskUserQuestionwith candidate operations as options. - After resolution, INFO log via utils.mjs:
think-short: NL-prompt "<input>" → resolved as <command>
EXECUTE using Bash tool (resolve + log):
BT_ROOT="${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/claude-brewcode/brewtools/*/ 2>/dev/null | sort -V | tail -1 | sed 's:/*$::')}"
test -d "$BT_ROOT/skills/think-short/helpers" || { echo "❌ BT_ROOT invalid: $BT_ROOT"; exit 1; }
node --input-type=module -e "
import {log} from '${BT_ROOT}/skills/think-short/helpers/state.mjs';
log('info', 'NL-prompt \"INPUT\" → resolved as RESOLVED', process.cwd(), process.env.CLAUDE_CODE_SESSION_ID || null);
" && echo "OK log" || echo "FAILED log"
Replace INPUT and RESOLVED literally. The log() from state.mjs auto-prefixes with think-short — do NOT add it again.
P1: Scope Selection
Default = project scope. Silent — no AskUserQuestion unless the user explicitly asks for disambiguation.
Rules:
| Signal | Scope |
|---|---|
--scope global or --scope=global present | global |
--scope project or --scope=project present | project |
User prompt contains explicit ambiguity ("для всех проектов или только здесь", "global or project?", --ask-scope) | Use AskUserQuestion — options: Project (default) / Global |
Otherwise (including --print / headless / no tty) | project (silent default) |
ALWAYS log the chosen scope at INFO using log() from state.mjs:
think-short: scope=<project|global> (<default|--scope|user-choice>, --scope <not specified|explicit>)
For status — no scope question (reads merged state). For blacklist — defaults to project scope silently.
If user is invoking from a combo (e.g. включись максимально) — determine scope ONCE via the rules above, apply to all ops in the combo.
P2: Mutate State
EXECUTE using Bash tool (generic pattern — substitute SCOPE, PATCH_JSON, OP):
BT_ROOT="${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/claude-brewcode/brewtools/*/ 2>/dev/null | sort -V | tail -1 | sed 's:/*$::')}"
test -d "$BT_ROOT/skills/think-short/helpers" || { echo "❌ BT_ROOT invalid: $BT_ROOT"; exit 1; }
node --input-type=module -e "
import {writeState, log} from '${BT_ROOT}/skills/think-short/helpers/state.mjs';
const patch = PATCH_JSON;
const r = await writeState('SCOPE', patch, process.cwd());
log('info', 'toggle OP applied (scope=SCOPE) → ' + JSON.stringify(patch), process.cwd(), process.env.CLAUDE_CODE_SESSION_ID || null);
console.log(JSON.stringify({scope:'SCOPE', file:r.path, state:r.after}));
" && echo "OK mutate" || echo "FAILED mutate"
Substitutions per op:
| Op | PATCH_JSON | OP |
|---|---|---|
on | {enabled:true} | on |
off | {enabled:false} | off |
profile light | {profile:'light'} | profile-light |
profile medium | {profile:'medium'} | profile-medium |
profile aggressive | {profile:'aggressive'} | profile-aggressive |
blacklist add X | {blacklist:[...current,'X']} (read via resolveEffectiveState first, dedupe) | blacklist-add-X |
blacklist remove X | {blacklist:current.filter(a=>a!=='X')} | blacklist-remove-X |
writeState handles: reading existing scope file, merging defaults, atomic write via safeWriteJson, stamping updated_at, enforcing version:1. No manual fs.existsSync / safeWrite calls needed.
Combo ops (e.g. on + profile aggressive): pass a single merged patch: {enabled:true, profile:'aggressive'} — one writeState call, atomic.
Blacklist mutation example (inline — single node invocation reads current state then writes):
BT_ROOT="${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/claude-brewcode/brewtools/*/ 2>/dev/null | sort -V | tail -1 | sed 's:/*$::')}"
node --input-type=module -e "
import {resolveEffectiveState, writeState, log} from '${BT_ROOT}/skills/think-short/helpers/state.mjs';
const s = await resolveEffectiveState(process.cwd());
const cur = Array.isArray(s.blacklist) ? s.blacklist : [];
const next = Array.from(new Set([...cur, 'AGENT'