diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 12dffe5..95cfce9 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,13 +6,13 @@ }, "metadata": { "description": "Claude Code plugin suite: brewcode for infinite task execution, brewdoc for documentation tools, brewtools for text utilities, brewui for UI/visual/creative tools", - "version": "6.1.4" + "version": "6.2.0" }, "plugins": [ { "name": "brewcode", "description": "Brewcode - full-featured development platform for Claude Code: infinite focus tasks, prompt optimization, skill/agent creation, quorum reviews, rules management", - "version": "6.1.4", + "version": "6.2.0", "category": "productivity", "keywords": [ "brewcode", @@ -46,7 +46,7 @@ { "name": "brewdoc", "description": "Brewdoc - Claude Code documentation tools: my-claude installation docs, memory sync, md-to-pdf conversion", - "version": "6.1.4", + "version": "6.2.0", "category": "productivity", "keywords": [ "brewdoc", @@ -72,7 +72,7 @@ { "name": "brewtools", "description": "Brewtools - universal utilities for Claude Code: text optimization, humanization, secrets scanning", - "version": "6.1.4", + "version": "6.2.0", "category": "productivity", "keywords": [ "brewtools", @@ -100,7 +100,7 @@ { "name": "brewui", "description": "Placeholder for future UI/visual/creative tools (currently empty, installable)", - "version": "6.1.4", + "version": "6.2.0", "category": "productivity", "keywords": [ "ui", diff --git a/.codex/plugins/brewcode/skills/agents/references/agent-frontmatter-fields.md b/.codex/plugins/brewcode/skills/agents/references/agent-frontmatter-fields.md new file mode 100644 index 0000000..372166a --- /dev/null +++ b/.codex/plugins/brewcode/skills/agents/references/agent-frontmatter-fields.md @@ -0,0 +1,100 @@ +# AG Frontmatter Field Reference + +## AG File Format + +```markdown +--- +name: agent-name # REQ: lowercase/hyphens; !=leading `-`, !=`:` (rejected v2.1.218+, file skipped+logged) +description: "Short description" # REQ: TRG terms, when to delegate +effort: high # OPT: low|medium|high|xhigh|max (local + PLG) +maxTurns: 20 # OPT: positive int, max turns (local + PLG) +disallowedTools: Write, Edit # OPT: deny specific TLs (local + PLG) +skills: skill1, skill2 # OPT: injected into ctx at startup +color: cyan # OPT: 8 UI colors, see Color Semantics (agent-template.md) +memory: project # OPT: user|project|local +background: true # OPT: `true` keeps it BG even when Codex wants the result -- no `false` semantics +isolation: worktree # OPT: FM accepts `worktree` only; `remote` is invocation-level (Agent TL), gated +mcpServers: [server1, server2] # OPT: ignored for PLG AGs +initialPrompt: "Analyze this code" # OPT: fires only when this definition runs as the MAIN session (`--agent` / `agent` setting) +observer: "reviewer" # OPT: absent from the 2.1.233 field table -- !=emit +observerMessage: "watch for X" # OPT: absent from the 2.1.233 field table -- !=emit +observeSubagents: false # OPT: absent from the 2.1.233 field table -- !=emit +hooks: {PreToolUse: [{matcher: "Bash", hooks: [{type: command, command: "./validate.sh"}]}]} # OPT: any hook event, flow-style shown for brevity (also valid as block YAML); ignored for PLG AGs +experimental: {cacheTtl: "5m"} # OPT: "5m"|"1h" per-agent prompt-cache TTL (2.1.248+); local-only, ignored for PLG AGs +--- + +# SP + +Detailed instructions for the AG... +``` + +## FM Reference + +### REQ Fields + +| Field | Format | Description | +|-------|--------|-------------| +| `name` | lowercase, hyphens; !=leading `-`, !=`:` (rejected v2.1.218+ -- file skipped, logged; `:` reserved for PLG namespacing) | Unique identifier. PLG AGs auto-namespaced `::` | +| `description` | per Description Budget in `agent-template.md` -- single line + role + 3-7 TRGs by DEF, `` blocks only under the stated exception | When Codex delegates to this AG. Aliases: `when_to_use`, `when-to-use`. Some registries truncate long descriptions | + +### OPT Fields + +Verified against CC 2.1.233 (`docs/sub-agents.md:279-300` field table), re-checked through the 2.1.269 delta (see Changes 2.1.234-2.1.269 in `agent-known-issues.md`). Two parsers exist -- **local** (`.codex/agents/`, `~/.codex/agents/`, `--agents` JSON) and **PLG** (`/agents/**.md`). `Scope` column = where the key is honored: PLG AGs ignore `hooks`, `mcpServers`, `sandbox_mode` (`docs/sub-agents.md:228`), plus `experimental.cacheTtl` (2.1.248+, local-only) -- four keys total; every other key is honored in both. + +| Field | Values | DEF | Scope | Description | +|-------|--------|-----|-------|-------------| +| `model` | `fast model`, `balanced model`, `high-reasoning model`, `fable` (`claude-fable-5`, Mythos-class, v2.1.170), `inherit` | `inherit` | both | MDL selection | +| `effort` | `low`, `medium`, `high`, `xhigh`, `max` (MDL-dependent) | `inherit` | both | Override effort; no `auto`, no bare integer. Pre-2.1.267 this was a no-op on pinned-effort models (high-reasoning model 4.7/4.8, Fable 5) -- honored since | +| `maxTurns` | positive integer | unlimited | both | Max turns before abort | +| `tools` | comma-separated | All inherited | both | Allowed TLs | +| `disallowedTools` | comma-separated | None | both | Denied TLs (removed from inherited) | +| `skills` | comma-separated / list | None | both | Full SK content injected into ctx at startup. Preload only -- an unlisted SK stays reachable at runtime via the `Skill` TL (`docs/sub-agents.md:292`); list `Skill` in `tools:`, !=the SK name | +| `color` | 8 values, see Color Semantics in `agent-template.md` | None | both | UI color; `magenta` is NOT valid | +| `memory` | `user`, `project`, `local` | None | both | AG memory scope; with explicit `tools` list parser force-adds memory TLs | +| `background` | `true` | unset | both | `true` keeps the SA in BG even when Codex asks for the foreground (`docs/sub-agents.md:296`). One value only -- `false` is not a force-foreground switch; mode is picked by the four-case precedence, see Execution Modes in `agent-context-and-execution.md`. Since 2.1.269, a teammate-spawned SA whose definition carries `background: true` hard-errors instead of forcing foreground -- drop the field on a definition that may run as a teammate | +| `isolation` | `worktree` | None | both | LOW PRIORITY -- omit unless AGs write files in parallel. FM documents `worktree` alone (`docs/sub-agents.md:298`); `remote` is invocation-level, not FM, see the note below | +| `sandbox_mode` | see Permission Modes below | `default` | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | +| `mcpServers` | server name or inline definition | All inherited | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | +| `hooks` | YAML structure, any hook event | None | local | Ignored for PLG AGs; a PROJECT AG's FM hooks need the workspace-trust dialog accepted for the exact folder holding the file (`docs/sub-agents.md:648`, v2.1.218+). `~/.codex/agents/` and `--agents` need no trust step | +| `initialPrompt` | non-empty string | None | both | Auto-submitted as the first user turn when THIS definition runs as the MAIN session -- `--agent ` or the `agent` setting; commands + SKs are processed, prepended to any user prompt (`docs/sub-agents.md:300`). Irrelevant on ordinary SA spawn. `--agent` resolves a PLG AG by its scoped name, so origin is not the boundary; execution context is | +| `observer`* | non-empty string | None | local | Observing AG | +| `observerMessage`* | non-empty string | None | local | Brief for observer | +| `observeSubagents`* | `false` disables | enabled | local | -- | +| `experimental.cacheTtl` | `"5m"`, `"1h"` | none | local | 2.1.248+: per-agent prompt-cache TTL override; pairs with session-level `promptCacheTtl`/`subagentPromptCacheTtl` settings (2.1.243) | + +> *`observer`/`observerMessage`/`observeSubagents` are absent from the 2.1.233 field table (`docs/sub-agents.md:279-300`) -- treat as internal/older until confirmed, !=emit into a generated AG. +> Need `sandbox_mode`/`hooks`/`mcpServers` -> put the AG in `.codex/agents/` or `~/.codex/agents/`, or grant `permissions.allow` rules in `config.toml` (session-wide, !=PLG-AG-scoped) (`docs/sub-agents.md:228`). +> PLG AG files above the byte limit are skipped entirely (`Skipping plugin agent : ... exceeds N byte limit`). +> `isolation` = LOW PRIORITY: !=add by DEF. Costs worktree setup + disk per spawn, and known data-loss combo (see Known Bugs in `agent-known-issues.md`, #29110). Use ONLY when several AGs mutate the same files concurrently. `remote` is **invocation-level only**: the Agent TL schema carries `isolation?: "worktree" | "remote"` and `remote` launches the AG in a remote cloud environment, always backgrounded, availability-gated (`npm/package-2.1.233/sdk-tools.d.ts:526-527`). Never valid in FM; reachable only from an `Agent(...)` call where the gate is on. + +## Permission Modes + +| Mode | Behavior | +|------|----------| +| `default` | Standard permission prompts | +| `manual` | Alias of `default` (v2.1.200+) | +| `acceptEdits` | Auto-accept file edits | +| `auto` | CC picks per-call (2.1.233 value set, `docs/sub-agents.md:289`) | +| `dontAsk` | Auto-deny prompts (allowed TLs still work) | +| `bypassPermissions` | Skip all checks (use with caution) | +| `plan` | Read-only exploration mode | + +## Hook Events + +**All hook events are supported in AG FM** (`docs/sub-agents.md:652`). These three are the common ones: + +| Event | Matcher | When | Note | +|-------|---------|------|------| +| `PreToolUse` | TL name | Before the SA uses a TL | -- | +| `PostToolUse` | TL name | After the SA uses a TL | -- | +| `Stop` | (none) | The SA finishes | Converted to `SubagentStop` at runtime when the definition is spawned AS a SA (`docs/sub-agents.md:658,680`) | + +Configured in `config.toml` / `PLG/hooks/hooks.json`, never AG FM: `SubagentStart`, `SubagentStop`, +`PreToolUse:Agent`/`PostToolUse:Agent`, `TaskCreated`/`TeammateIdle`/`TaskCompleted` -- full event +table + stdin fields: `hooks-events.md`. + +> Matcher value = the FM `name` for local/user AGs, the scoped `plugin:agent` id for PLG AGs. A scoped name contains `:` and is matched as an UNANCHORED regex -- anchor it `^brewcode:agent-creator$` to hit one AG only. +> The SAME file can run as a SA or as the MAIN session (`--agent`). In the main-session case FM hooks run alongside `config.toml` hooks and `Stop` stays `Stop`. +> **Trust:** a PROJECT AG's FM hooks run only after the workspace-trust dialog is accepted for the EXACT folder holding the AG file -- a trusted parent is not enough and a `-p` session never counts. Until then the SA still runs, hooks are skipped, an error goes to the debug log. `~/.codex/agents/` and `--agents` definitions need no trust step; an `--add-dir` folder must be trusted separately (`docs/sub-agents.md:648`). +> PLG AG FM `hooks` are ignored (`docs/sub-agents.md:228`) -- ship hooks in `PLG/hooks/hooks.json` instead. +> Settings-level hooks affect ALL SAs, incl. hooks from managed policy settings and PLGs. diff --git a/.codex/plugins/brewcode/skills/agents/references/agent-template.md b/.codex/plugins/brewcode/skills/agents/references/agent-template.md new file mode 100644 index 0000000..8c7ba59 --- /dev/null +++ b/.codex/plugins/brewcode/skills/agents/references/agent-template.md @@ -0,0 +1,163 @@ +# AG Emit Template -- Description Budget, SP Structure, Guardrails, Validation + +## Description Budget (NORMATIVE) + +The single description policy. The `description` row in FM Reference (`agent-frontmatter-fields.md`), the Description Patterns section below, and the Validation Checklist below all defer here -- no other numbers apply. + +| Constraint | Value | +|------------|-------| +| Total | <= 150 tokens (~600 chars) | +| Lead sentence | <= 160 chars, plain EN prose | +| TRGs | comma-list, EN only, 3-7 keywords | +| EXs | at most 1, commentary <= 15 words | +| Language | EN only in FM | + +> Exceed only if user explicitly asks. Frequent-use AGs: up to ~200 tokens + 1-2 EXs. +> **Example-block exception:** an AG whose domain overlaps another AG's may carry `` blocks in `description` as a YAML block scalar (`description: |`), up to the ceilings above -- see Description Patterns. Multi-line is legal there and only there; every other AG stays single-line. + +## Description Patterns + +**Format:** Action verb phrase -> `Triggers:` keyword list -> optional inline EXs, inside the **Description Budget** ceilings above. Front-load keywords. + +| AG clarity | Format | EXs | +|------------|--------|-----| +| Clear domain (developer, tester) | Single-line: action + TRGs | 0 | +| Some overlap with other AGs | Single-line + detailed `Triggers:` list | 0-1 | +| Ambiguous (creator AGs) | Block scalar (`description: \|`) + `` with `` -- the example-block exception | 1-2 | + +EX (ambiguous case -- clear-domain and some-overlap cases use the same one-line lead, without `` blocks): + +```yaml +description: | + Creates CC AGs. Triggers: create agent, new agent, improve agent, agent description. + + + user: "Create an agent for code review" + Explicit AG creation request TRGs this AG + +``` +(add a 2nd `` with different phrasing per Rule 6, e.g. "My reviewer agent doesn't trigger reliably") + +### Rules + +| # | Rule | Why | +|---|------|-----| +| 1 | Lead with action verb, not "Use this agent when" | Denser signal per token, matches user intent | +| 2 | Add `Triggers:` with exact user phrases | Semantic match on natural language | +| 3 | Dash-separated capabilities beat prose | `"SDET/QA - runs tests, debugs flaky"` > sentence | +| 4 | `` explains WHY this TRGs | Helps Codex distinguish similar AGs | +| 5 | 1 `` block by DEF, 2 at most (Description Budget) | More = token waste, diminishing returns | +| 6 | Vary phrasing across EXs | Codex generalizes rather than matching one phrase | +| 7 | No "proactively" or "MUST" language | No special weight -- write clear descriptions | +| 8 | Quote description if contains YAML special chars | Prevents parse failures | + +## EX Format (minimal) + +```yaml + +user: "exact phrase user would say" +Why THIS AG, not another + +``` + +No `Context:` line, no `assistant:` response -- `` is the selection signal (phrasing/commentary rules: see Description Patterns > Rules above). + +## SP Structure + +Order: role -> Return Contract -> Scope/Never (Scope Fit, Delegation) -> Ctx/Patterns/Cmds -> Checklist. Concrete EX already shipping this order: `brewcode/agents/bash-expert.md` (`# Bash Expert` -> `## Return Contract` -> `## Scope & Checkpoints` -> numbered body -> `## Checklist`). + +> Target SP body (excluding FM): ~800-1,500 words for a generic AG; teams-setup profiles instead cap at <=3200 bytes body-only (see the compact exception below). + +| # | Section header | Content | Format | +|---|-----------------|---------|--------| +| 1 | `# AG Name` | `**Role:**` one sentence; `**Scope:**` READ-ONLY / Write access / Full access | 2 bold lines | +| 2 | `## Return Contract` | From Guardrails below, placed right after role/scope -- not at the end | verbatim block | +| 3 | `## Scope Fit` (code-writing AGs) / `## Delegation` (AGs with `Agent` in `tools:`) | From Guardrails below, whichever applies | verbatim block(s) | +| 4 | `## Ctx` | Stack/Auth/Build facts, EX: `**Stack:** React 17 \| TypeScript 5.7 \| MUI v5` | table + one `>` constraint line | +| 5 | `## Patterns` | Avoid vs Prefer code idioms | 2-col table | +| 6 | `## Cmds` | sub-agent task -> Cmd reference | 2-col table | +| 7 | `## Checklist` | DoD, placed at end of SP | `- [ ]` list | + +### teams-setup compact exception + +A brief citing `brewcode/skills/teams-setup/references/agent-template.md` overrides the generic SP structure and guardrails. Generate one domain profile <=3200 bytes (~800 est-tokens) with exactly these ordered body headings and no others: `## Mission`, `## Owned surfaces`, `## Exclusions`, `## Must-load references`, `## Unique invariants`, `## Unique verification`. Load `.codex/teams/{TEAM_NAME}/team.md` first. Keep acceptance, routing, tracing, return, colleague, scope-fit, and etalon rules only in that shared file; !=restore `sub-agent task Acceptance Protocol`, `Return Contract`, `Trace Instructions`, `Colleagues`, or `Scope Fit` sections. Preserve frontmatter metadata specified by the teams brief. `intent-guard` remains exempt and only its three emitted seeded blocks may be adapted. + +### 6. Guardrails (non-team AGs; emit verbatim) + +For AGs outside `teams-setup`, `Return Contract` = unconditional. `Scope Fit` = only when the domain writes code/scripts/SQL/schemas/infra/config; drop it for pure-research/docs/review-only AGs. `Delegation` = only when `tools:` includes `Agent`; drop it for AGs that never spawn subagents. + +```markdown +## Scope Fit +Build for the actual scale and the problems that exist today; !=imagined load, !=speculative abstraction (EX: 10-user app !=hardened against lock contention). After finishing, one pass: can this be simpler -- fewer files, less config, less indirection? +Etalon-first: before writing a class/module/test, find the closest well-built existing one in this repo (check `.codex/convention/*` first) and take its principles. ADDITIVE to conventions/rules/docs, !=a replacement. + +## Delegation +Delegate only large, independent, parallelizable work; finish anything doable in a handful of tool calls yourself. != spawn a subagent to verify your own output. Keep spawn counts low -- fan out once, do not nest. + +## Return Contract +Verdict first, <=30 lines, `path:line`. !=bodies/output/log/preamble. Unconditional -- spend one step on what the MAIN SESSION needs and return only that. +Bulk material (long logs, full diffs, dumps, long reports) -> file under `.codex/reports/_/`; return the PATH, !=the content. AGs that dump everything burn the main session's context. +If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. +``` + +> agent-creator obeys this same contract for its own report -- see `agent-creator.md`'s own `## Return Contract`. + +## LLM Text Rules + +| Rule | Details | +|------|---------| +| Tables over prose, bullets over numbered | Multi-column ~66% savings, bullets when order irrelevant | +| `code` over text, inline over blocks | Identifiers, paths, short vals; blocks only if >3 lines | +| Comma-separated inline lists | `a, b, c` not bullet per item when saving space | +| One-liner rules, arrows for flow | `old` -> `new`, conditions with `->` (~40% savings) | +| No filler, no water | Cut "please note", "it's important", "only", "exactly", "basically" | +| Positive framing, no aggressive lang | "Do Y" not "Don't X"; "Use when..." not "CRITICAL: MUST..." | +| Imperative form | "Do X" not "You should do X"; 3rd person for descriptions | +| Bold for key terms, no extra formatting | `**term**` for emphasis; no decorative lines, headers, dividers | +| No emojis except status markers | Only 3 allowed: checkmark, cross, warning | +| Merge duplicates, abbreviate in tables | Single source of truth; REQ, impl, cfg, args, ret, err | + +## Color Semantics + +8 valid values (`magenta` is NOT one -- drop it if seen in old AGs). No official semantic +mapping beyond these repo conventions; the other 4 are free to assign per team. + +| Color | Use for | EXs | +|-------|---------|-----| +| cyan | Analysis, review | code-reviewer, security-analyzer | +| green | Generation, creation | test-generator, doc-generator | +| yellow | Validation, warning | PLG-validator, schema-checker | +| red | Security, critical | security-scanner, vuln-finder | +| blue, purple, orange, pink | Unassigned -- pick per project convention | -- | + +## Common AG Types + +| Type | MDL | TLs | Focus | +|------|-----|-----|-------| +| `developer-*` | high-reasoning model | Read, Write, Edit, Bash, Agent | Implementation | +| `reviewer` | high-reasoning model | Read, Glob, Grep | Code review | +| `tester` | balanced model | Read, Bash | Test exec | +| `arch-*` | high-reasoning model | Read, Glob, Grep, web fetch | Architecture (read-only) | +| `docs-*` | balanced model | Read, Write, Edit | Documentation | +| `explorer` | fast model | Read, Glob, Grep | Quick search | + +## Validation Checklist + +- [ ] `name`: lowercase-hyphens only (`[a-z0-9-]+`), no `:` +- [ ] `description`: within the **Description Budget** above -- single line + role + `Triggers:` keywords by DEF; `` blocks only for an ambiguous AG, under the example-block exception +- [ ] Placement: file sits in a `.codex/agents/` dir on the walk-up path from the intended launch cwd -- warn if placed under a module subfolder while sessions launch from repo root +- [ ] `tools`: minimal REQ set (least privilege), every entry survives the filters for the pool this AG runs in -- none of the nine filter-1 TLs, and `Skill` listed only when the AG invokes SKs at runtime +- [ ] Body carries no "ask/confirm with the user" instruction -- a SA cannot prompt; it returns the decision request to its caller +- [ ] Body's task-graph steps have a no-sub-agent task-TL fallback, or the AG is documented as foreground/teammate-only +- [ ] `isolation`: `worktree` or absent -- `remote` is invocation-level, never FM +- [ ] `disallowedTools`: no conflict with `tools` if both specified +- [ ] `model`: matches task complexity (fable=mythos/hardest, high-reasoning reasoning_tier=complex, balanced reasoning_tier=standard, fast reasoning_tier=light) +- [ ] SP: tables over prose, code over text +- [ ] Project-specific knowledge included (stack, conventions, cmds) +- [ ] SP contract: generic AG -> Checklist at end + one `## Return Contract`; teams-setup domain AG -> exact six ordered headings, <=3200 bytes (~800 est-tokens), shared `team.md` loaded first, no duplicated shared-contract section +- [ ] Generic code-writing AG -> `## Scope Fit` incl. etalon-first; teams-setup keeps both only in `team.md` +- [ ] AG with `Agent` in `tools:` -> `## Delegation` states an explicit large/independent/parallelizable criterion and caps spawn counts (R7); absent for AGs that never delegate +- [ ] READ-ONLY AGs have no Write/Edit TLs +- [ ] No CD rules duplicated in AG body (already injected) +- [ ] Unique name in scope (no conflict with existing AGs) +- [ ] Optimized by the `text-optimizer` AG (or skipped -- brewtools absent, noted in report) diff --git a/.codex/plugins/brewcode/skills/agents/references/hooks-io-contract.md b/.codex/plugins/brewcode/skills/agents/references/hooks-io-contract.md new file mode 100644 index 0000000..4727358 --- /dev/null +++ b/.codex/plugins/brewcode/skills/agents/references/hooks-io-contract.md @@ -0,0 +1,182 @@ +# Hook I/O Contract Reference + +Common stdin fields, the message-routing matrix (which channel Codex actually sees), exit codes, and every output schema. + +### Common stdin (ALL events) + +Same fields on every event, JSON shape + `permission_mode`'s 6 values: "Common stdin" in `hooks-events.md`. + +## Message Routing Matrix + +Consult BEFORE choosing output -- wrong channel = silently ignored (no error). `UI`=`updatedInput`. + +| Event | `AC` (Codex sees) | `decision`/reason | IGNORED (do not use) | +|-------|---------------------|--------------------|------------------------| +| SS | YES, ``, stable | -- | `UI` | +| UserPromptSubmit | YES, appended; **cannot rewrite prompt** | `decision:"block"` -> UI only, Codex does NOT see reason | **`UI` -- IGNORED** (root cause of the `forced-eval.mjs` bug: emitted `UI.prompt` here, silently dropped) | +| PTU | YES, stable | `permissionDecision`: allow/deny/ask/defer; `permissionDecisionReason` on deny; `"defer"` pauses headless, resume `-p --resume` (v2.1.89+) | `updatedToolOutput` | +| POT | YES, stable (#15345) | **AFTER THE FACT** -- runs after the tool, cannot prevent the call: `decision:"block"`+`reason` appends the reason next to the tool result; `updatedToolOutput` replaces what Codex sees. Side effects stand; also carries `updatedMCPToolOutput` (MCP tools) and `classifierContext` (auto-mode classifier only, not shown to Codex, 2.1.236) | -- | +| PostToolUseFailure | YES, limited data -- verify against docs before depending on it | `additionalContext` | -- | +| PostToolBatch | YES, injected once before the next model call | `decision:"block"` / `continue:false` stops the agentic loop | `UI` | +| SubagentStart | YES, into SA (not parent) | -- | -- | +| PreModelSwitch | NO -- gate only, no context injection | `permissionDecision`: allow/deny/ask (no `defer`); priority `deny > ask > allow`; timeout BLOCKS the switch (opposite of PTU, where a timeout lets the call through) | `updatedInput`, `additionalContext` | +| PostModelSwitch | YES, delivered on the NEXT turn | -- (cannot block, the switch already happened) | `decision`, `permissionDecision` | +| Notification | YES, stable | -- | -- | +| Stop | YES, feedback + keeps turn going, not hook-error label (v2.1.163+); or `decision:"block"`+`reason` -> Codex continues, sees reason | -- | `AC` for blocking (use `decision` instead) | +| SubagentStop | same as Stop, scoped to SA | -- | same | +| PreCompact | N/A, not supported | **BLOCKING**: exit 2 or `decision:"block"` blocks compaction | `systemMessage`, `continue` -- both discarded | +| PostCompact | N/A, not supported | -- | `systemMessage`, `continue` -- both discarded | +| SessionEnd | N/A, not supported | -- | -- (informational only) | +| Setup | YES, `AC` (plain stdout -> debug log only) | -- cannot block, any exit code continues | -- | +| UserPromptExpansion | YES, alongside the expanded prompt | `decision:"block"` prevents the command from expanding; `reason` -> USER | -- | +| DirectoryAdded | via `systemMessage` on the NEXT turn (matcher `slash_command` only) | -- cannot block, the dir is already added | `continue` -- discarded | +| PR | N/A | `decision.behavior`: `allow\|deny` (this file's tested value); reason via `decision.message` -- **field name unresolved, see the PR note in Output Schemas below, verify before shipping**; exit 2 is NOT honored | exit 2 | +| PermissionDenied | via `hookSpecificOutput` only | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -> model may retry; auto-mode denials only (v2.1.89+) | exit code, stderr, top-level `retry` | +| TeammateIdle, TaskCompleted, TaskCreated | N/A | JSON `{continue, stopReason}` (v2.1.52+) | -- | + +### stdout (exit 0, JSON) + +| Event | Codex sees? | +|-------|:---:| +| SS, UserPromptSubmit, PTU | YES -- parsed, context injected | +| All others | NO -- verbose mode only (Ctrl+O) | + +### systemMessage + +Goes to user UI only -- Codex does NOT see it. Exception: async hooks deliver on next turn. + +### stderr (exit 2) + +| Type | Codex sees? | Events | +|------|:---:|--------| +| Blocking (exit 2 stops the action) | YES | PTU, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange (except `policy_settings`), PostToolBatch, **PreCompact**, **PreModelSwitch**, Elicitation, ElicitationResult, WorktreeCreate, **WorktreeRemove** (ANY non-zero aborts, both Worktree events) | +| Non-blocking, stderr still reaches Codex | YES | POT, PostToolUseFailure | +| Non-blocking | NO (UI/debug log only) | SS, Setup, SubagentStart, PCD, Notification, SessionEnd, InstructionsLoaded, CwdChanged, FileChanged, DirectoryAdded (debug log), **PostModelSwitch**, MD | +| exit 2 IGNORED entirely | NO | **PR** (use `decision`), **PermissionDenied** (use `hookSpecificOutput.retry`), StopFailure (except `terminalSequence`) | + +### UI (PTU only) + +Silently modifies tool params. Codex unaware of change. `UI` also rewrites on PR. `UI` is single-writer/last-wins -- every hook on the event sees the same original input, runner keeps only the last edit -- reserve for ONE owning hook; for SA prompt injection prefer SubagentStart `AC` instead (accumulates across hooks, no clobbering). + +### Exit codes + +| Code | Meaning | stdout | stderr | +|------|---------|--------|--------| +| 0 | Success | parsed as JSON; TeammateIdle/TaskCompleted: teammate terminates | verbose mode | +| 1 | Error (non-fatal) | TeammateIdle/TaskCompleted: teammate continues; others: error | verbose mode | +| 2 | Critical error | IGNORED | -> Codex (blocking) or user (non-blocking) | + +| Event | exit 0 | exit 1 | exit 2 | +|-------|--------|--------|--------| +| PTU | JSON processed | non-blocking error, tool call proceeds | stderr -> Codex, blocks the call | +| Stop | JSON processed | non-blocking error | stderr -> Codex | +| SubagentStop | JSON processed | non-blocking error | stderr -> Codex | +| SS | JSON processed | warning in UI | stderr -> UI | +| PreCompact | JSON processed | compact continues | **blocks compaction**, stderr -> UI on manual `/compact` | +| PreModelSwitch | JSON processed (`permissionDecision`) | non-blocking error, switch proceeds | stderr -> Codex, **blocks the switch**; a TIMEOUT also blocks it -- the one event where a hook timeout is not fail-open | +| PostModelSwitch | JSON processed (`additionalContext`, delivered next turn) | non-blocking error | stderr -> debug log only, event is non-blocking | +| TeammateIdle | teammate terminates | teammate continues | stderr -> UI | +| TaskCompleted | task accepted | task re-assigned | stderr -> UI | +| POT | JSON processed | non-blocking error | stderr -> Codex; tool already ran, call not prevented | +| PR | JSON `decision` processed | non-blocking error | **IGNORED** -- permission flow proceeds unchanged | +| PermissionDenied | `hookSpecificOutput.retry` processed | ignored | **IGNORED** -- the denial already happened | +| Setup | JSON processed | JSON honored if schema-valid, else UI notice | stderr -> UI notice; session continues | +| UserPromptExpansion | JSON processed | non-blocking error | **blocks the expansion**, stderr -> user as `reason` | +| PostToolBatch | JSON processed | non-blocking error | **stops the agentic loop**, stderr -> Codex | +| DirectoryAdded | JSON processed (`continue` dropped) | debug log | stderr -> debug log; the dir is already added | +| WorktreeCreate | JSON processed | **creation FAILS** | **creation FAILS** (ANY non-zero) | +| WorktreeRemove | JSON processed | **removal FAILS** (path still exists) | **removal FAILS** (ANY non-zero) | +| `http`/`mcp_tool` type (any event) | N/A -- no OS exit code | N/A | N/A | + +> Sample, not exhaustive (33 events total). Exit 1 is a NON-blocking error EVERYWHERE except `WorktreeCreate`/`WorktreeRemove` (ANY non-zero fails the operation) -- to enforce a policy use `exit 2` or JSON, never `exit 1`. `PreModelSwitch` is the one event where even a TIMEOUT blocks; every other blocking event's timeout is fail-open (lets the action through). +> `http`/`mcp_tool` convey success/failure via response JSON (`decision`/`AC`) or HTTP/tool-call failure, not exit code; 2xx + empty body = pass-through, 2xx + non-JSON body = non-blocking error. + +## Output Schemas + +Single-field schemas (compact): + +| Event -- purpose | Schema | +|---|---| +| PTU -- allow w/ context | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"..."}}` | +| PTU -- deny | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}` | +| Stop -- block | `{"decision":"block","reason":"sub-agent task not complete. Continue with phase 3."}` | +| SubagentStop -- block | `{"decision":"block","reason":"Review not finished. Check remaining files."}` | +| SubagentStart -- inject into SA | `{"hookSpecificOutput":{"hookEventName":"SubagentStart","additionalContext":"Context injected into SUBAGENT (not parent)"}}` | +| UserPromptSubmit -- block | `{"decision":"block","reason":"Reason shown to USER only (Codex does NOT see this)"}` | +| POT -- feedback | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"Post-tool feedback for Codex"}}` | +| POT -- block (feedback next to the result) | `{"decision":"block","reason":"Lint failed; fix before continuing."}` -- top-level, NOT `hookSpecificOutput` | +| POT -- replace what Codex sees | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","updatedToolOutput":{"stdout":"[redacted]","stderr":"","interrupted":false,"isImage":false}}}` -- value MUST match the tool's output shape or it is ignored | +| PostToolBatch -- inject once | `{"hookSpecificOutput":{"hookEventName":"PostToolBatch","additionalContext":"..."}}`; `{"decision":"block","reason":"..."}` stops the agentic loop | +| Setup -- context | `{"hookSpecificOutput":{"hookEventName":"Setup","additionalContext":"Dependencies installed"}}` | +| UserPromptExpansion -- block | `{"decision":"block","reason":"Shown to the USER","hookSpecificOutput":{"hookEventName":"UserPromptExpansion","additionalContext":"..."}}` | +| PreModelSwitch -- gate a switch | `{"hookSpecificOutput":{"hookEventName":"PreModelSwitch","permissionDecision":"ask","permissionDecisionReason":"Switching now re-sends ~180k tokens. Continue?"}}` | +| PostModelSwitch -- context after switch | `{"hookSpecificOutput":{"hookEventName":"PostModelSwitch","additionalContext":"Now running claude-high-reasoning model-5"}}` | +| TeammateIdle/TaskCompleted/TaskCreated -- control (v2.1.52+) | `{"continue":false,"stopReason":"sub-agent task limit reached."}` | +| PermissionDenied -- retry (v2.1.89+) | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -- top-level `retry` is NOT read. Tells the model it MAY retry; does not reverse the denial. Ignored for no-verdict denials | +| WorktreeCreate -- return path (v2.1.84+, http hooks) | `{"hookSpecificOutput":{"hookEventName":"WorktreeCreate","worktreePath":"/path/to/worktree"}}` | +| Empty pass-through | `{}` | + +> PostToolUse also carries `updatedMCPToolOutput` (same idea as `updatedToolOutput`, MCP tools only -- +> prefer `updatedToolOutput` when both apply) and `classifierContext` (<=2000 chars, auto-mode classifier +> only, v2.1.236 -- never shown to Codex, do not use it for feedback). + +### PTU -- Modify input + +`updatedInput` REPLACES the entire `tool_input` object -- always spread the original, never send a partial: +```json +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"prompt":"Modified prompt text","other_field":"preserved"}}} +``` +```js +output({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow', + updatedInput:{...input.tool_input, prompt:'Modified prompt text'}}}); +``` + +### PTU -- Answer request_user_input (v2.1.85+) + +Echo back the original `questions` array and add an `answers` object mapping question text -> chosen label: +```json +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"questions":[{"question":"Which database?","header":"DB","options":[{"label":"PostgreSQL"},{"label":"MySQL"}],"multiSelect":false}],"answers":{"Which database?":"PostgreSQL"}}}} +``` +> `"allow"` ALONE is not sufficient for `request_user_input`/`ExitPlanMode` -- it must carry `updatedInput`. Multi-select labels join with commas. PTU precedence across hooks: `deny` > `defer` > `ask` > `allow`. + +### SS -- Context injection +```json +{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"Injected context for Codex","sessionTitle":"My session title","reloadSkills":true},"systemMessage":"Status shown to user only"} +``` +> `reloadSkills:true` re-scans skill + command dirs after the SS hooks finish, so a skill the hook installed is usable in the SAME session. `sessionTitle` applies on `startup`/`resume`/`fork`, ignored on `clear`/`compact`. `initialUserMessage` creates the first turn in `-p` mode (`AC` only attaches to an existing one). + +### PR -- Allow/Deny +```json +{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}} +``` + +| `behavior` | Effect | +|------------|--------| +| `allow` | auto-allow | +| `deny` | reject without prompting | + +> Unresolved -- flagged, not asserted: 2.1.233 testing found `decision.behavior` limited to `allow\|deny` +> with the deny reason on `decision.message`; current docs disagree with themselves across fetches, +> sometimes adding `ask` or naming the reason field `permissionDecisionReason` instead. Verify with a +> live `claude --debug` log (`Hook JSON output had unrecognized keys` names the real field) before +> depending on either form. `ask` otherwise exists only as PTU's `permissionDecision` value. + +### PR -- Allow with permission mutation +```json +{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","updatedInput":{"command":"npm test"},"updatedPermissions":[{"type":"addRules","rules":[{"toolName":"Bash","ruleContent":"npm *"}],"behavior":"allow","destination":"session"}]}}} +``` + +### Elicitation -- MCP form response (v2.1.76+) +```json +{"hookSpecificOutput":{"hookEventName":"Elicitation","action":"accept","content":{"field_name":"value"}}} +``` + +| `action` | Effect | +|----------|--------| +| `accept` | auto-fill MCP form with `content` | +| `decline` | decline elicitation | +| `cancel` | cancel elicitation | + +## Output size cap + +`additionalContext`, `systemMessage` and plain stdout are capped at 10,000 chars (v2.1.89); over that the value is written to a file and replaced by a preview + path, so a gate's reason can arrive truncated. Fail-safe design + templates: `hooks-templates.md`. diff --git a/.codex/plugins/brewcode/skills/superreview-setup/SKILL.md b/.codex/plugins/brewcode/skills/superreview-setup/SKILL.md index 8ab31f9..d4c8160 100644 --- a/.codex/plugins/brewcode/skills/superreview-setup/SKILL.md +++ b/.codex/plugins/brewcode/skills/superreview-setup/SKILL.md @@ -11,7 +11,7 @@ Inspect repository instructions, architecture, tests, and recent changes, then c Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve ``, ``, ``, and `` before running commands. - + # Super Review Generator (brewcode:superreview-setup) diff --git a/.codex/plugins/brewcode/skills/teams-setup/SKILL.md b/.codex/plugins/brewcode/skills/teams-setup/SKILL.md index 6db29c3..b43f5de 100644 --- a/.codex/plugins/brewcode/skills/teams-setup/SKILL.md +++ b/.codex/plugins/brewcode/skills/teams-setup/SKILL.md @@ -7,7 +7,7 @@ description: "Creates and manages dynamic teams of domain agents. Triggers: crea Use collaboration agents only when the user or project instructions explicitly request a team. Split work into bounded independent tasks, keep one owner per file or surface, exchange evidence through collaboration messages, and synthesize results in the parent session. Do not invent unsupported agent parameters or create persistent team configuration unless requested. - + ## Native authority diff --git a/.codex/plugins/brewcode/skills/teams-setup/scripts/trace-ops.sh b/.codex/plugins/brewcode/skills/teams-setup/scripts/trace-ops.sh index 6a8e1e6..bcf707c 100755 --- a/.codex/plugins/brewcode/skills/teams-setup/scripts/trace-ops.sh +++ b/.codex/plugins/brewcode/skills/teams-setup/scripts/trace-ops.sh @@ -1,5 +1,5 @@ #!/bin/sh -# brewcode-meta: version=6.1.4 content_version=5.6.0 generated_by=brewcode:teams-setup +# brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewcode:teams-setup set -eu USAGE="Usage: trace-ops.sh [args...]" diff --git a/.codex/plugins/brewdoc/skills/md-to-pdf/SKILL.md b/.codex/plugins/brewdoc/skills/md-to-pdf/SKILL.md index 8f83907..82348aa 100644 --- a/.codex/plugins/brewdoc/skills/md-to-pdf/SKILL.md +++ b/.codex/plugins/brewdoc/skills/md-to-pdf/SKILL.md @@ -11,7 +11,7 @@ Convert a local Markdown file with `scripts/md_to_pdf.py`. Check dependencies wi Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve ``, ``, ``, and `` before running commands. - + # MD to PDF diff --git a/.codex/plugins/brewtools/skills/task-board-setup/references/07-agents-md-optimize.md b/.codex/plugins/brewtools/skills/task-board-setup/references/07-agents-md-optimize.md index 4ba97cd..8672c4e 100644 --- a/.codex/plugins/brewtools/skills/task-board-setup/references/07-agents-md-optimize.md +++ b/.codex/plugins/brewtools/skills/task-board-setup/references/07-agents-md-optimize.md @@ -6,7 +6,7 @@ OPTIONAL, opt-in phase. PROPOSE-ONLY: every change is gated behind request_user_ > **Verified lazy-loading mechanic (source: developers.openai.com/codex/guides/agents-md, fetched 2026-06-14).** Bake this into every proposal rationale: > - Root CMD + all ancestor AGENTS.md/AGENTS.local.md: **loaded in full AT LAUNCH**, every session, regardless of length. -> - Subdirectory (nested) AGENTS.md: **NOT loaded at launch -- loaded ON-DEMAND when Codex reads a file in that subtree.** +> - Subdirectory (nested) AGENTS.md: **NOT on-demand -- concatenated ONCE at session start.** Codex walks git root -> CWD and concatenates every AGENTS.md it finds into one instruction chain, capped by `project_doc_max_bytes` (32 KiB default; later/nested files are dropped first over the cap); a nested AGENTS.md wins for its own subtree only because it sits later in that one concatenation (verified: https://developers.openai.com/codex/guides/agents-md, 2026-09-12). > - `@path` imports: **EAGER -- expanded into context at launch.** They help organization but do NOT reduce root context. > - `.codex/rules/*.md` with `paths:` FM: on-demand when matching files are touched; without `paths:`: at launch. > CONSEQUENCE: to shrink always-on context, push MOD detail into a NESTED MODCMD. NEVER use `@import` for that goal (eager = no savings). This is the justification stated to the user in the module-split proposal. @@ -119,7 +119,7 @@ If NOT OVER: state it's within budget; offer optional tidy (markup pass 5g) but If OVER: assemble a concrete decomposition PLAN combining 5d (module split), 5e (rules dedup), 5f (compress), then AskUser ONCE with the whole plan before applying any of it: > **AGENTS.md is lines (over the ceiling; optimal ~).** Proposed decomposition to get back under budget: -> 1. Move detail for modules `` into per-module AGENTS.md (loaded on-demand, shrinks always-on context). Root keeps a 2-line module index. [est -X lines] +> 1. Move detail for modules `` into per-module AGENTS.md (it overrides root for that subtree and keeps root short so `project_doc_max_bytes` never truncates it). Root keeps a 2-line module index. [est -X lines] > 2. Move topic blocks `<...>` into path-scoped `.codex/rules/*.md` (load only when matching files are touched). [est -Y lines] > 3. Dedup overlap with existing rules `<...>`; delete duplicated spans. [est -Z lines] > 4. Deep-compress the remainder via brewtools:text-optimize. [est -W lines] @@ -136,17 +136,17 @@ Apply ONLY approved steps. Each sub-step (5d/5e/5f) below still narrates what it For each approved MOD in MODULES: 1. Gather the CMD content that is module-specific (build/test cmds, layout, conventions for that subtree). -2. Write/extend `/AGENTS.md` (a NESTED file -- this is what gives on-demand loading). If `has_own_cmd`, MERGE (Edit), do not clobber. Improve markup (headers, tables, bullets). +2. Write/extend `/AGENTS.md` (a NESTED file -- this is what makes it override the root for that subtree). If `has_own_cmd`, MERGE (Edit), do not clobber. Improve markup (headers, tables, bullets). 3. In the ROOT CMD, REPLACE the moved block with a MAX-COMPRESSED index: a couple of lines, e.g.: ``` - ## Modules (each has its own AGENTS.md, loaded on-demand when you work in it) + ## Modules (each has its own AGENTS.md, which overrides this file for that subtree) | Module | Path | Owns | |--------|------|------| | api | services/api/ | handlers, OpenAPI, db migrations | | web | apps/web/ | UI, build, e2e | ``` Keep ONLY the index in root; the detail lives in the MODCMD. -> Rationale to state in the proposal: nested AGENTS.md loads ONLY when Codex touches that subtree, so module detail leaves the always-on root context. Do NOT use `@import` here -- imports are eager and would not save context. +> Rationale to state in the proposal: a nested AGENTS.md overrides root for its own subtree (both are concatenated at session start, nested last, so nested wins) and keeps root short so `project_doc_max_bytes` never truncates it. Codex has no `@import`-style eager-include mechanism at all, so that concern does not apply here. > Do NOT move CROSS-cutting / repo-wide rules into a single module; those stay in root or go to a `.codex/rules/*.md`. --- diff --git a/.codex/plugins/brewtools/skills/text-optimize/SKILL.md b/.codex/plugins/brewtools/skills/text-optimize/SKILL.md index de5c0eb..d51f5b6 100644 --- a/.codex/plugins/brewtools/skills/text-optimize/SKILL.md +++ b/.codex/plugins/brewtools/skills/text-optimize/SKILL.md @@ -120,6 +120,7 @@ When no flag provided AND input suggests compression (not just optimization): | Perception | P.1-P.6 | Examples near rules, hierarchy, bold keywords, standard symbols, instruction order, default over options | | LLM Comprehension | L.1-L.8 | Critical info position, documents-first, conciseness, quote-first, add WHY, reiterate constraint, prompt repetition, preserve scope qualifiers | | Aggressive lossy | A.1-A.4 | Line fusion, word drop, paraphrase, known-fact elision (deep/max) | +| Prompt quality | PQ.1-PQ.13 | Role-first return contract, dedupe repeats, positive imperative (incident-tied `!=` kept), one hard-stop cap, drop step-by-step/verify filler, explicit scope, table-vs-procedure shape, example over adjective, DICT threshold gate — prompt-shaped content (system prompt/AGENTS.md/agent def/skill doc), Medium+ only | > Full per-ID definitions live in `references/rules-review.md` (loaded at Step 0) — do not restate them here. @@ -128,12 +129,13 @@ When no flag provided AND input suggests compression (not just optimization): | Mode | Applies | Notes | |------|---------|-------| | Light | C.1-C.8, T.6, D.1, R.1-R.3, P.1-P.4, L.1-L.8 | Text cleanup + exact-dup removal — no restructuring | -| Medium | All rules (C + T + S + D + R + P + L) | Balanced transformations | -| Standard | All rules (C + T + S + D + R + P + L) + `references/standard-compression.md` | 30-50% compression, human-readable, 1 verification round | -| Deep | All rules (C + T + S + D + R + P + L) + A.1-A.4 + `references/deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds (conditional) | -| Max | All rules (C + T + S + D + R + P + L) + A.1-A.4 + `references/deep-compression.md` + `references/max-compression.md` | Atomic fact-lines, ASCII operators, format-aware tables, 4 mandatory guardrails, 2 verification rounds | +| Medium | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) | Balanced transformations | +| Standard | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + `references/standard-compression.md` | 30-50% compression, human-readable, 1 verification round | +| Deep | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + A.1-A.4 + `references/deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds (conditional) | +| Max | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + A.1-A.4 + `references/deep-compression.md` + `references/max-compression.md` | Atomic fact-lines, ASCII operators, format-aware tables, 4 mandatory guardrails, 2 verification rounds | > D.5 (cross-file dedup) applies in ANY mode when processing multiple files or a folder. D.6 wrong-merge guard is mandatory wherever D.2/D.3/D.5 run. +> PQ (prompt-quality rewrite) applies at Medium mode and above, only when content type is a prompt-shaped target (system prompt/AGENTS.md/agent def/skill doc) — never Light, never generic docs/README. ### D.5 is decided by the orchestrator, never by a per-file agent @@ -424,6 +426,7 @@ the user can diff or delete it. | Snapshot first | No edit without a Phase 0 snapshot on disk and a clean tree over the targets. `!=` editing straight from the prompt | | Refuse, don't warn | A failed sub-gate restores the original bytes. A lossy file is never left in place with a warning attached | | Preserve | Names, numbers, dates, URLs, file paths, versions, ports, sizes | +| Preserve | CLI flags/options verbatim; model IDs byte-exact; thresholds/gates/percentages exactly as stated | | Preserve | Negative rule semantics (`!=` notation in deep mode) | | Preserve | At least one example per rule with examples | | Preserve | Scope qualifiers ("every section, not just the first") — high-reasoning model 4.8 literalism (Max/Deep) | diff --git a/.codex/plugins/brewtools/skills/text-optimize/references/deep-compression.md b/.codex/plugins/brewtools/skills/text-optimize/references/deep-compression.md index 7fc8670..7b48d65 100644 --- a/.codex/plugins/brewtools/skills/text-optimize/references/deep-compression.md +++ b/.codex/plugins/brewtools/skills/text-optimize/references/deep-compression.md @@ -65,48 +65,31 @@ Status emoji cost 2-4 tokens each (measured): `✅`/`❌` = 2-3 tok, `ℹ️` = ## Dictionary Format -Place DICT header at document start when terms appear 3+ times: - -``` -[DICT: CC=Codex, KB=knowledge base, SP=system prompt, ...] -``` - -Rules: -- Terms appearing 3+ times → dictionary entry -- Max 20 entries -- Sort alphabetically -- Place before first content line -- Use abbreviation from DICT throughout document +Place `[DICT: CC=Codex, KB=knowledge base, SP=system prompt, ...]` at document start when +terms appear 3+ times. Rules: max 20 entries, sort alphabetically, place before the first content +line, use the abbreviation from DICT throughout — a term used <3x stays inline (rules-review.md R13: +DICT pays only on a long, repetition-heavy file, not a short one). ## Filler Words & Phrases to Remove -Apply filler removal from `rules-review.md` rule T.6. Additional deep-mode removals: - -| Pattern | Action | -|---------|--------| -| Articles (the/a/an) | Remove when meaning clear without them | -| Relative clauses ("which is", "that are") | Remove or restructure | -| Hedging ("might", "possibly", "could potentially") | Remove — use direct statements | +Beyond `rules-review.md` T.6: drop articles (the/a/an) when meaning survives without them, relative +clauses ("which is", "that are"), and hedging ("might", "possibly", "could potentially") — state +direct facts instead. ## Structural Compression Patterns -- Conditionals: `if X → Y` or `X ? Y : Z` -- Prohibitions: `!=X bc Y` (must not X because Y) -- Lists: inline comma-separated when items are short -- Tables: for multi-attribute data -- Merge related one-liners into single line with `|` separator +- Conditionals: `if X -> Y` or `X ? Y : Z` | prohibitions: `!=X bc Y` (must not X because Y) +- Lists: inline comma-separated when items are short | tables: for multi-attribute data +- Merge related one-liners into a single line with `|` separators - Remove markdown formatting that doesn't aid parsing (bold, italic in tables) -- Headers: flatten to 2 levels max -- Remove blank lines between items in lists/tables +- Headers: flatten to 2 levels max | remove blank lines between list/table items ## Redundancy Factoring Run dedup pass (D.1-D.6, rules-review.md) BEFORE symbol substitution — merging first shrinks the text remaining passes must process and keeps verification cheap. Record merges in a dedup ledger (kept <- dropped). -- Phrase-DICT: recurring phrase >= 3 words appearing 2+ times -> DICT entry (counts toward the 20-entry cap). Source: CompactPrompt arXiv:2510.18043 -- Path-prefix hoisting: repeated path/URL prefixes -> single DICT entry (e.g. `[DICT: SR=src/main/resources]`) -- Header echo removal: subsection headers repeating parent header words -> drop the echo ("## Server Config / ### Server Config Ports" -> "### Ports") -- Number/unit normalization: "approximately 30 percent" -> `~30%`; "greater than or equal to 21" -> `>=21` +- Phrase-DICT (recurring phrase >=3 words, 2+ times -> DICT entry, counts toward the 20-cap; CompactPrompt arXiv:2510.18043) | path-prefix hoisting (repeated path/URL prefix -> one DICT entry, e.g. `[DICT: SR=src/main/resources]`) +- Header-echo removal (drop repeated parent words: "## Server Config / ### Server Config Ports" -> "### Ports") | number/unit normalization ("approximately 30 percent" -> `~30%`, "greater than or equal to 21" -> `>=21`) ## Token-Class Keep/Drop Heuristics @@ -120,58 +103,56 @@ Never drop negations or scope qualifiers (L.8; max-mode guardrail C2). ## Aggressive Lossy Techniques (A.1-A.4) -Deep/max only. Rule definitions: rules-review.md category A. Application order: - -dedup (D.1-D.6) -> line fusion (A.1) -> paraphrase (A.3) -> word drop (A.2) -> knowledge elision (A.4) -> symbol substitution - -Loss ledger REQUIRED: every A.2/A.4 drop recorded as `dropped -> reason`, listed in the report. A.4 elisions count against the fact-level loss budget (deep gate >= 95%) as `elided-known`; A.2 is word-level and gate-neutral — ledgered for transparency, no direct gate impact, but if a drop degrades a fact's meaning the verifier labels that fact `distorted` (normal gate impact). A.1/A.3 results count as preserved (kept/merged), no ledger entry. Guards: never drop negations, numbers, named entities, scope qualifiers (L.8, C2); D.6 wrong-merge guard applies before A.1 fusion; unsure whether A.4 knowledge is generic -> keep. +Deep/max only. Full rule + ledger semantics live in `rules-review.md` category A (Step 0, always +loaded) — do not restate here. Application order: dedup (D.1-D.6) -> A.1 fusion -> A.3 paraphrase -> +A.2 word drop -> A.4 elision -> symbol substitution. ### Example: A.1 fusion + A.3 paraphrase (loss-free) - -**Original**: -> The deployment script should be executed from the project root directory. In the event that the script fails, you can check the log file which is located at `logs/deploy.log`. - -**Compressed**: -> run deploy script from project root | fail -> check `logs/deploy.log` +> "The deployment script should be executed from the project root directory. In the event that the script fails, you can check the log file which is located at `logs/deploy.log`." -> "run deploy script from project root | fail -> check `logs/deploy.log`" ### Example: A.4 elision, project delta kept +> "Always write unit tests for new code, since testing catches regressions early. Keep functions small and readable. The project coverage gate is 85% (jacoco); builds fail below it." -> "coverage gate 85% (jacoco), build fails below" -**Original**: -> Always write unit tests for new code, since testing catches regressions early. Keep functions small and readable. The project coverage gate is 85% (jacoco); builds fail below it. - -**Compressed**: -> coverage gate 85% (jacoco), build fails below - -Ledger: dropped "write unit tests / catches regressions" -> generic LLM knowledge; dropped "keep functions small" -> generic. Kept: 85%, jacoco, build-fail behavior (project-specific). +Ledger: dropped "write unit tests / catches regressions" + "keep functions small" -> generic LLM knowledge. Kept: 85%, jacoco, build-fail behavior (project-specific). ## Iron Rules -Preserve in ALL cases regardless of compression level: +Preserve in ALL cases regardless of compression level — the lossless guard, never paraphrased, +rounded, or dropped: - Names, numbers, dates, URLs, file paths, versions, ports, sizes -- Negative rule semantics (use `!=` notation) -- At least one example per rule that originally has examples +- CLI flags/options verbatim (`-x`, `--max`); model IDs byte-exact (`claude-balanced model-5`, never "balanced model 5") +- Thresholds, gates, percentages exactly as stated (`>=95%`, `~20%` ceiling) — never rounded +- Negative rule semantics (use `!=` notation) | >=1 example per rule that originally had examples - DICT header at document start - Dedup ledger: every merged pair recorded (kept <- dropped); merged facts count as preserved in verification - Loss ledger: every A.2/A.4 drop recorded (dropped -> reason); never elide project-specific facts (names, numbers, paths, versions, prohibitions) +## Stop Condition + +Stop the A.1-A.4 pass the instant one of these trips — patch back, never push further: +- A.2 would touch a noun, numeral, negation or named entity (Token-Class Heuristics above already forbid it — this is the enforcement trigger) +- An A.4 candidate is not clearly generic training-knowledge (unsure -> keep, per the A.4 rule itself) +- DICT header would exceed 20 entries, or would cover a term used <3x +- The dedup/loss ledger can no longer account for every merge and drop 1:1 + ## Before/After Examples ### Example 1 — Prose Instruction -**Original** (~60 words): +**Original** (46 words): > Please note that when you are working with the database connection, it is important to make sure that you close the connection after you are done with it. Failure to do so can result in connection pool exhaustion, which may lead to the application becoming unresponsive. -**Compressed** (~15 words): +**Compressed** (13 words): > DB conn: close after use bc unclosed -> pool exhaustion -> app unresponsive ### Example 2 — Rule Block with DICT -**Original** (~90 words): +**Original** (82 words): > ## File Handling Rules > > When working with temporary files in the build directory, you should always use the project's file utility library. It is important to note that temporary files must be cleaned up after the build process completes. You must not write temporary files to the source directory because it can corrupt the version control state. The file utility library provides a `cleanup()` method that should be called in the finally block. All temporary files should use the `.tmp` extension. -**Compressed** (~35 words): +**Compressed** (35 words incl. DICT header, 27 body): > [DICT: TF=temporary files, FUL=file utility lib, BD=build dir] > > ## File Handling @@ -180,12 +161,12 @@ Preserve in ALL cases regardless of compression level: ### Example 3 — Configuration Section -**Original** (~70 words): +**Original** (56 words): > ## Server Configuration > > The application server runs on port 8443 with TLS enabled. The configuration file is located at `/etc/myapp/server.yml`. The minimum required version is Java 21. The maximum heap size should be set to 4096MB for production environments. Health check endpoint is available at `https://localhost:8443/health`. The connection timeout is 30 seconds and the read timeout is 60 seconds. -**Compressed** (~40 words): +**Compressed** (25 words): > ## Server Config > Port: 8443 (TLS) | cfg: `/etc/myapp/server.yml` | Java >= 21 > Heap max: 4096MB (prod) | health: `https://localhost:8443/health` @@ -193,14 +174,27 @@ Preserve in ALL cases regardless of compression level: ### Example 4 — Negative Rules -**Original** (~80 words): +**Original** (64 words): > ## Security Rules > > You must never store passwords in plain text in the configuration files. API keys should not be committed to the repository under any circumstances. It is important to make sure that you do not log sensitive information such as tokens or credentials at any log level. You should not disable TLS certificate verification in production environments because it exposes the application to man-in-the-middle attacks. -**Compressed** (~30 words): +**Compressed** (29 words): > ## Security > !=plaintext passwords in cfg files > !=API keys in repo > !=log sensitive data (tokens, credentials) @ any log level > !=disable TLS cert verification in prod bc MITM exposure + +### Measured (this file's own examples, `wc -w`) + +| Example | Original | Compressed | Reduction | Ratio | +|---------|----------|------------|-----------|-------| +| 1 — Prose Instruction | 46 | 13 | -71.7% | 3.54x | +| 2 — Rule Block + DICT | 82 | 27 (35 w/ DICT) | -67.1% | 3.04x | +| 3 — Config Section | 56 | 25 | -55.4% | 2.24x | +| 4 — Negative Rules | 64 | 29 | -54.7% | 2.21x | +| Total | 248 | 94 | -62.1% | 2.64x | + +Deep mode's "2-3x" target above is this file's own measured spread (2.2x-3.5x, combined 2.64x) on +dense rule prose, not an invented number — re-measure with `wc -w` before claiming a new ratio. diff --git a/.codex/plugins/brewtools/skills/text-optimize/references/max-compression.md b/.codex/plugins/brewtools/skills/text-optimize/references/max-compression.md index 1ea84eb..38603b2 100644 --- a/.codex/plugins/brewtools/skills/text-optimize/references/max-compression.md +++ b/.codex/plugins/brewtools/skills/text-optimize/references/max-compression.md @@ -22,6 +22,11 @@ Rules: > build runs unit tests after compile > test fail -> pipeline stops + artifact !=published +**Measured** (`wc -w`): before 31 words, after 23 words = -25.8% by WORD count only — atomic-fact +style forbids pronouns, so "build" repeats 3x. The paper's ~50% figure above is TOKEN reduction: a +repeated short noun costs less than the pronoun+clause structure it replaces. Word count and token +count diverge here — judge B1 by an actual token estimate, not `wc -w`, whenever a noun repeats. + ## ASCII Operator Dialect (A1 — CRITICAL) Prefer ASCII digraphs over unicode glyphs. Measured token cost (tiktoken cl100k/o200k, live): @@ -69,6 +74,9 @@ CONDITIONAL: > 1 ann admin > 2 bob user +**Measured** (`wc -w`): before 26 words / 5 lines, after 12 words / 3 lines = -53.8% — a real win on +both words and tokens (pipe alignment and separator rows carry no data). + ## Chain-of-Density Final Pass (B4) Source: arXiv:2309.04269. After all compression passes, run 1-3 rewrite iterations at FIXED length: each pass fuses 1-3 missing entities from the original back in WITHOUT growing the text (~3 iterations reach human-preferred density). Use to repair entity loss found by verification instead of re-inflating. @@ -86,17 +94,22 @@ These CAP the aggression. Sources: Anthropic context-engineering blog; Anthropic ## Iron Rules (inherited + max-specific) -Inherits ALL `deep-compression.md` iron rules: -- Preserve names, numbers, dates, URLs, file paths, versions, ports, sizes -- DICT header @ document start (terms 3+ times) -- >= 1 example per rule that originally had examples - -Max adds: +Inherits ALL of `deep-compression.md` Iron Rules (the lossless guard) unchanged — do not restate the +list here, re-read it there. Max adds: - Scope qualifiers preserved verbatim (C2) - 2 mandatory verification rounds, independent methods: claim inventory + self-QA probe (never optional) - Semantic match must be >= 95% -> else warn user with loss list - 100% sub-gate: numbers, names, negations, scope qualifiers +## Stop Condition + +Stop pushing max-mode density the instant one of these trips — patch via Chain-of-Density (B4) +instead of deleting further: +- The 20% deletion ceiling (C3) would be crossed +- A B1 atomic line would need a cross-line pronoun to stay readable (the decomposition is now lossy) +- Round 2 self-QA misses a number, name, negation or scope qualifier (100% sub-gate) +- The next fusion pass has no missing entity left to restore (B4 has converged) — ship, do not chase a fixed multiplier past this point + ## Verification (2 rounds, mandatory, INDEPENDENT methods) Never silently ship lossy max output. Two rounds use DIFFERENT methods — they catch different losses. diff --git a/.codex/plugins/brewtools/skills/text-optimize/references/rules-review.md b/.codex/plugins/brewtools/skills/text-optimize/references/rules-review.md index d1ddb1b..c1174e0 100644 --- a/.codex/plugins/brewtools/skills/text-optimize/references/rules-review.md +++ b/.codex/plugins/brewtools/skills/text-optimize/references/rules-review.md @@ -108,6 +108,35 @@ Examples (before -> after): - A.3: "In the event that the build process does not complete successfully, notify the team" -> "build fails -> notify team" - A.4: "Write unit tests for new code, tests catch regressions. Coverage gate is 85% (jacoco); build fails below." -> "coverage gate 85% (jacoco), build fails below" (generic "write tests" elided -> ledger; project delta kept) +**Lossless guard (any mode, A.2/A.4 never target these):** numbers, dates, versions, model IDs +byte-exact (`claude-balanced model-5`, never "balanced model 5"), CLI flags/options verbatim (`-x`, `--max`), +thresholds/gates/percentages exactly as stated (`>=95%`, `~20%` ceiling), URLs, file paths, ports, +sizes, named entities, negations (`!=`/NEVER/MUST NOT), scope qualifiers (L.8). A drop that touches +any of these is not A.2/A.4 — it is a defect, caught by the 100% sub-gate. + +## PQ - Prompt-Quality Rewrite (digest of `.codex/reports/20260912-173000_agents-refresh/prompting-rules.md` R1-R16) + +Separate from the 52 numbered rules above (still 8 categories, unchanged count) — a rewrite pass for +prompt-shaped targets (system prompt, agent `.md`, skill `SKILL.md`, hook prompt text, AGENTS.md), +applied Medium mode and above (never Light — Light stays wording-only, no restructuring). Stays +lossless per the guard above: R1-R16 govern SHAPE and emphasis, never facts. + +| ID | Source | Transformation | Bad -> Good | +|----|--------|-----------------|-------------| +| PQ.1 | R1, R15 | Role in one sentence, Return contract next, Scope/Never after — before procedure detail | Role buried after 3 paragraphs of scope -> "You are a code reviewer. Return: findings list, `path:line`, verdict first." then Scope/Never as its own heading | +| PQ.2 | R2 | Same instruction stated once; delete a cross-section repeat | "Never invent scope" stated, then restated 2 sentences later in other words -> keep the sharper phrasing once | +| PQ.3 | R3 | Prohibition -> positive imperative, UNLESS the `!=`/NEVER guards a named, previously-observed failure | "Do not use markdown" -> "Write in flowing prose". Keep verbatim: `!=re-run text-guard.sh` (BT-F15 regression) | +| PQ.4 | R4 | Drop scattered ALL-CAPS; keep exactly one true hard-stop (irreversible action) in caps, lower the rest | 4x MUST/CRITICAL in one file -> 1 STOP on the irreversible action (e.g. edit-without-snapshot), 3 become plain imperative | +| PQ.5 | R5 | No "think step by step" or scripted step-lists for thinking-enabled models; no bare "verify"/"be careful" filler | "Think step by step and double-check" -> delete, or state the goal only | +| PQ.6 | R6 (high-reasoning model-5-specific; apply cautiously to balanced model/Fable) | Drop carried-over generic self-verification instructions; keep verification that is a specific, gated protocol with real thresholds | "Double-check your output before returning" (generic) -> delete. A named gate (`>=95% match, 100% sub-gate`) is not this pattern — keep it | +| PQ.7 | R7 | An agent that itself delegates states an explicit delegate-only-when criterion, low spawn count | "delegate as needed" -> "delegate only for large independent parallelizable work; never to verify your own output" | +| PQ.8 | R8 | State scope explicitly; never rely on the model generalizing a rule to similar items | "apply this rule" -> "apply this rule to every file matching X, not just the first" | +| PQ.9 | R10 | Reference data (fields/flags/thresholds/model IDs) -> table. Real-dependency procedure -> numbered steps. Never mix the two shapes | A flag/target matrix written as prose -> table; a create-in-order procedure kept as numbered prose, not flattened into a table | +| PQ.10 | R11 | A concrete example or named reference file beats an adjective ("clean", "thorough", "professional") | "write clean code" -> "follow the pattern in `skill-creator.md`" or a 2-line before/after | +| PQ.11 | R13 | `[DICT: ...]` header only when it pays: >=5 distinct abbreviations, each reused >=3x, file itself hundreds of lines | A 150-line agent body with 3 abbreviations used twice each -> no DICT header, inline the 3 terms | +| PQ.12 | R14 (recommendation, not a mandate — verify against the specific hook's own miss-rate first) | Recurring reminder text: once at session-start/compaction, throttle or drop the per-turn copy | A reminder injected on every `UserPromptSubmit` AND at session-start/after-compaction -> keep the structural-checkpoint copies, narrow the per-turn one | +| PQ.13 | R16 | State "run independent tool calls in parallel" once per artifact, never per section | 3 sections each repeating the parallel-call instruction -> state it once, delete the other 2 | + ## Rules NOT Recommended | Avoid | Reality | diff --git a/.codex/plugins/brewtools/skills/text-optimize/references/standard-compression.md b/.codex/plugins/brewtools/skills/text-optimize/references/standard-compression.md index 4a4d67f..81444a2 100644 --- a/.codex/plugins/brewtools/skills/text-optimize/references/standard-compression.md +++ b/.codex/plugins/brewtools/skills/text-optimize/references/standard-compression.md @@ -68,8 +68,8 @@ After compression, verify: - API signatures and parameters - Error messages (exact text matters) - Legal/compliance text -- Version numbers, dates, URLs -- Command-line examples +- Version numbers, dates, URLs, model IDs (byte-exact) +- Command-line examples, CLI flags/options, thresholds and gates (`>=98%`, `30-50%`) verbatim > **Note:** Never convert config blocks to TOML for "efficiency" — TOML is the most token-heavy structured format (more overhead than YAML/JSON). For uniform tabular data prefer markdown tables or TSV/CSV; for nested data prefer compact JSON. @@ -77,12 +77,15 @@ After compression, verify: ### Example 1: README Intro -**Before** (~80 words): +**Before** (59 words): > This project is a command-line tool that is able to help developers in order to automate the process of deploying their applications. It is important to note that the tool supports a large number of cloud providers. Due to the fact that deployment can be complex, this tool simplifies it for the purpose of reducing errors and saving time. -**After** (~35 words): +**After** (19 words): > CLI tool that automates application deployment. Supports many cloud providers. Simplifies complex deployments to reduce errors and save time. +Measured (`wc -w`): 59 -> 19 = -67.8%, above the 30-50% default target — short, filler-heavy prose +can legitimately land higher. Treat 30-50% as the safe default for typical docs, not a ceiling. + ### Example 2: Installation Instructions **Before**: @@ -107,3 +110,11 @@ After compression, verify: | Projects | 3 | Unlimited | Unlimited | | Storage | 1 GB | 50 GB | 500 GB | | Support | Community | Email | Dedicated | + +## 8. Stop Condition + +Stop compressing the moment: the next cut would touch a name/number/path/version/flag (lossless +guard, `rules-review.md`); a paragraph-to-table conversion would need to invent a category the +source never stated; or a further sentence merge would combine facts with different scope/numbers/ +conditions (D.6). The 30-50% target is done at that point even if the actual ratio lands outside it +either way — report the real number, never force one to fit the range. diff --git a/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs b/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs index 33993c0..f09db95 100644 --- a/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs +++ b/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs @@ -1,12 +1,12 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:think-short-setup +// brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewtools:think-short-setup /** * think-short — UserPromptSubmit hook (self-contained, no plugin-root deps). * * Maintains a per-session prompt counter (marker keyed by session_id, inside a * private 0700 tmp dir we own — a planted symlink is rejected, never followed). * Increments on every user prompt; re-injects the full think-short prompt - * ONLY every 10th prompt (10, 20, 30, ...). Never on the 1st prompt + * ONLY every 20th prompt (20, 40, 60, ...). Never on the 1st prompt * (SessionStart already injected the prompt at session open). * * Inject channel: hookSpecificOutput.additionalContext (UserPromptSubmit). @@ -24,7 +24,7 @@ const HERE = path.dirname(fileURLToPath(import.meta.url)); const PROMPT_PATH = path.join(HERE, 'think-short-prompt.md'); const MARKER_DIR = path.join(os.tmpdir(), 'brewtools-think-short'); const UID = typeof process.getuid === 'function' ? process.getuid() : null; -const INTERVAL = 5; +const INTERVAL = 20; let markerDirOk; diff --git a/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-session.mjs b/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-session.mjs index 9aaa7dd..e969a47 100644 --- a/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-session.mjs +++ b/.codex/plugins/brewtools/skills/think-short-setup/assets/think-short-session.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:think-short-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewtools:think-short-setup /** * think-short — SessionStart hook (self-contained, no plugin-root deps). * diff --git a/.codex/scripts/generate-compat.mjs b/.codex/scripts/generate-compat.mjs index fe49d30..26fc4a1 100644 --- a/.codex/scripts/generate-compat.mjs +++ b/.codex/scripts/generate-compat.mjs @@ -32,6 +32,15 @@ const MANUAL_NATIVE_SKILLS = new Set([ 'brewtools/think-short-setup' ]); +// brewcode/agents references that are pure Claude Code runtime documentation (env vars, hook +// event tables, version changelogs) with no Codex counterpart. Excluded from the mirror here; +// validate-compat.mjs's resourceTarget carries the matching exclusion. +const CLAUDE_ONLY_AGENT_REFERENCES = [ + 'references/agent-context-and-execution.md', 'references/agent-known-issues.md', 'references/agent-scope-and-tools.md', + 'references/hooks-changes.md', 'references/hooks-env.md', 'references/hooks-events.md', 'references/hooks-templates.md', + 'references/hooks-types-config.md' +]; + // Etalon-first wording mirrored into the Codex variants. Sources of truth: // brewcode/skills/teams-setup/references/agent-template.md and // brewtools/skills/manager-setup/references/architect.md. Edit here only, never at the call sites. @@ -278,6 +287,49 @@ function writeFile(file, content, mode) { if (mode !== undefined) fs.chmodSync(file, mode); } +// Per-file, exact-text overrides applied AFTER the generic substitution chain. Reserved for the rare +// passage where the corpus-wide word substitution turns a fact that is true for Claude Code into one +// that is false for Codex, and no single global rule can fix it without also mangling unrelated text. +// Keyed by the file's path relative to REPO_ROOT, taken BEFORE the claude-md -> agents-md rename. +// Each `exactFrom` must still be present in the text nativeWorkflowText already produced; if a source +// edit moves the passage, the mismatch throws immediately instead of silently shipping a stale claim. +const TEXT_OVERRIDES = { + 'brewtools/skills/task-board-setup/references/07-claude-md-optimize.md': [ + [ + '> - Subdirectory (nested) AGENTS.md: **NOT loaded at launch -- loaded ON-DEMAND when Codex reads a file in that subtree.**', + '> - Subdirectory (nested) AGENTS.md: **NOT on-demand -- concatenated ONCE at session start.** Codex walks git root -> CWD and concatenates every AGENTS.md it finds into one instruction chain, capped by `project_doc_max_bytes` (32 KiB default; later/nested files are dropped first over the cap); a nested AGENTS.md wins for its own subtree only because it sits later in that one concatenation (verified: https://developers.openai.com/codex/guides/agents-md, 2026-09-12).' + ], + [ + '> 1. Move detail for modules `` into per-module AGENTS.md (loaded on-demand, shrinks always-on context). Root keeps a 2-line module index. [est -X lines]', + '> 1. Move detail for modules `` into per-module AGENTS.md (it overrides root for that subtree and keeps root short so `project_doc_max_bytes` never truncates it). Root keeps a 2-line module index. [est -X lines]' + ], + [ + '2. Write/extend `/AGENTS.md` (a NESTED file -- this is what gives on-demand loading). If `has_own_cmd`, MERGE (Edit), do not clobber. Improve markup (headers, tables, bullets).', + '2. Write/extend `/AGENTS.md` (a NESTED file -- this is what makes it override the root for that subtree). If `has_own_cmd`, MERGE (Edit), do not clobber. Improve markup (headers, tables, bullets).' + ], + [ + ' ## Modules (each has its own AGENTS.md, loaded on-demand when you work in it)', + ' ## Modules (each has its own AGENTS.md, which overrides this file for that subtree)' + ], + [ + '> Rationale to state in the proposal: nested AGENTS.md loads ONLY when Codex touches that subtree, so module detail leaves the always-on root context. Do NOT use `@import` here -- imports are eager and would not save context.', + '> Rationale to state in the proposal: a nested AGENTS.md overrides root for its own subtree (both are concatenated at session start, nested last, so nested wins) and keeps root short so `project_doc_max_bytes` never truncates it. Codex has no `@import`-style eager-include mechanism at all, so that concern does not apply here.' + ] + ] +}; + +function applyTextOverrides(relativeSourcePath, text) { + const pairs = TEXT_OVERRIDES[relativeSourcePath]; + if (!pairs) return text; + for (const [exactFrom, to] of pairs) { + if (!text.includes(exactFrom)) { + throw new Error(`TEXT_OVERRIDES entry for ${relativeSourcePath} no longer matches the generated text: ${JSON.stringify(exactFrom)}`); + } + text = text.split(exactFrom).join(to); + } + return text; +} + function copyTransformedTree(sourceDir, targetDir) { const entries = fs.readdirSync(sourceDir, { withFileTypes: true }) .sort((left, right) => left.name.localeCompare(right.name)); @@ -296,7 +348,8 @@ function copyTransformedTree(sourceDir, targetDir) { if (data.includes(0)) { fs.writeFileSync(target, data); } else { - fs.writeFileSync(target, nativeWorkflowText(data.toString('utf8'), { shell: isShellAsset(target) }), 'utf8'); + const transformed = nativeWorkflowText(data.toString('utf8'), { shell: isShellAsset(target) }); + fs.writeFileSync(target, applyTextOverrides(path.relative(REPO_ROOT, source), transformed), 'utf8'); } fs.chmodSync(target, fs.statSync(source).mode & 0o777); } @@ -619,7 +672,10 @@ function copySelected(source, target) { const data = fs.readFileSync(source); fs.mkdirSync(path.dirname(target), { recursive: true }); if (data.includes(0)) fs.writeFileSync(target, data); - else fs.writeFileSync(target, nativeWorkflowText(data.toString('utf8'), { shell: isShellAsset(target) }), 'utf8'); + else { + const transformed = nativeWorkflowText(data.toString('utf8'), { shell: isShellAsset(target) }); + fs.writeFileSync(target, applyTextOverrides(path.relative(REPO_ROOT, source), transformed), 'utf8'); + } fs.chmodSync(target, fs.statSync(source).mode & 0o777); } @@ -1487,6 +1543,14 @@ esac } } + if (plugin === 'brewcode' && skill === 'agents') { + // These document Claude Code's own env vars, hook event catalog, and SA runtime -- literal + // facts (`CLAUDE_CODE_*` names, version changelogs) with no Codex equivalent, since Codex's + // native agent/hook model is unrelated. Drop them rather than mistranslate; the + // frontmatter/template/io-contract references stay mirrored as genuinely portable guidance. + for (const name of CLAUDE_ONLY_AGENT_REFERENCES) fs.rmSync(path.join(targetDir, name), { force: true }); + } + if (plugin === 'brewtools' && skill === 'manager-setup') { fs.rmSync(path.join(targetDir, 'references', 'hard.md'), { force: true }); fs.rmSync(path.join(targetDir, 'references', 'intent-routing.md'), { force: true }); diff --git a/.codex/scripts/validate-compat.mjs b/.codex/scripts/validate-compat.mjs index 2333022..037009a 100644 --- a/.codex/scripts/validate-compat.mjs +++ b/.codex/scripts/validate-compat.mjs @@ -120,10 +120,19 @@ function checkHookCommand(plugin, distRoot, hook) { } } +// brewcode/agents references documenting Claude Code's own env vars, hook events, and SA +// runtime -- no Codex equivalent exists, so generate-compat.mjs never mirrors them. +const CLAUDE_ONLY_AGENT_REFERENCES = [ + 'references/agent-context-and-execution.md', 'references/agent-known-issues.md', 'references/agent-scope-and-tools.md', + 'references/hooks-changes.md', 'references/hooks-env.md', 'references/hooks-events.md', 'references/hooks-templates.md', + 'references/hooks-types-config.md' +]; + function resourceTarget(plugin, skill, relative) { if (relative === 'SKILL.md' || relative.startsWith('.claude/') || relative.includes('/__pycache__/') || relative.endsWith('.pyc')) return null; if (plugin === 'brewtools' && skill === 'manager-setup' && ['references/hard.md', 'references/intent-routing.md'].includes(relative)) return null; if (plugin === 'brewtools' && skill === 'think-short-setup' && (relative === 'assets/think-short-subagent.mjs' || relative.startsWith('tests/'))) return null; + if (plugin === 'brewcode' && skill === 'agents' && CLAUDE_ONLY_AGENT_REFERENCES.includes(relative)) return null; return relative.replaceAll('claude-md', 'agents-md').replaceAll('claude-local', 'codex-local'); } diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index cdd57de..f9520fb 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -2,6 +2,55 @@ --- +## v6.2.0 (2026-09-12) + +> Docs: [agent-creator](https://doc-claude.brewcode.app/brewcode/agents/agent-creator/) | [skill-creator](https://doc-claude.brewcode.app/brewcode/agents/skill-creator/) | [hook-creator](https://doc-claude.brewcode.app/brewcode/agents/hook-creator/) | [bc-rules-organizer](https://doc-claude.brewcode.app/brewcode/agents/bc-rules-organizer/) | [bash-expert](https://doc-claude.brewcode.app/brewcode/agents/bash-expert/) | [skills](https://doc-claude.brewcode.app/brewcode/skills/skills/) | [agents](https://doc-claude.brewcode.app/brewcode/skills/agents/) | [hooks](https://doc-claude.brewcode.app/brewcode/hooks/) | [text-optimizer](https://doc-claude.brewcode.app/brewtools/agents/text-optimizer/) | [ssh-admin](https://doc-claude.brewcode.app/brewtools/agents/ssh-admin/) | [deploy-admin](https://doc-claude.brewcode.app/brewtools/agents/deploy-admin/) | [text-optimize](https://doc-claude.brewcode.app/brewtools/skills/text-optimize/) | [think-short-setup](https://doc-claude.brewcode.app/brewtools/skills/think-short-setup/) | [manager-setup](https://doc-claude.brewcode.app/brewtools/skills/manager-setup/) | [memory-sync-setup](https://doc-claude.brewcode.app/brewdoc/skills/memory-sync-setup/) + +> All 8 plugin agents rewritten for Claude Code 2.1.269 and the Claude 5 family prompting guidance (role -> Return contract -> Scope/Never -> procedure; short bodies, reference catalogs on disk); creators teach the current formats (20 skill fields, 17 agent fields incl. `experimental.cacheTtl`, 33 hook events incl. `PreModelSwitch`/`PostModelSwitch`, corrected exit-code semantics); text-optimizer gained a prompt-quality (PQ) pass; memory-sync-setup ships `prompting-guide.md` (Claude + OpenAI/Codex rules) and a prompt-quality step; hook reminders trimmed and throttled. + +### brewcode + +#### Added + +- **`skills/agents/references/`** — 11 on-demand reference files backing `agent-creator`/`hook-creator` (context/execution, frontmatter fields, known issues, scope/tools, template, hooks changes/env/events/io-contract/templates/types-config) +- **4 new `skills/skills/references/*.md`** — `activation-and-troubleshooting`, `design-patterns`, `execution-model`, `frontmatter-fields` +- **`validate-skill.sh` checks 11-14** — unknown frontmatter key (warn), top-level `once:` (fail), non-builtin `agent:` (warn), UTF-8 BOM (fail) +- **`hooks/tests/suite-forced-eval.mjs`** — new contract suite for the `forced-eval.mjs` hook + +#### Changed + +- **Agent bodies cut to the Claude 5 prompting shape**: `agent-creator` 633→128 lines, `hook-creator` 851→107, `skill-creator` 897→147, `bc-rules-organizer` 305→184 (all with Delegation/Scope Fit guardrails); `bash-expert` tightened to 188 lines +- **`forced-eval.mjs`** now fires on the 1st prompt then every 10th (session-keyed counter, meta-replies skipped); `[ROLE]`/`[SPLIT]`/`[BRANCH]` text shrunk 636→472 chars + +#### Fixed + +- **`validate-skill.sh`** frontmatter extraction reopened on body `---` dividers +- Agent contract suite now covers 44 checks + +### brewtools + +#### Changed + +- **`text-optimizer` + `text-optimize` references** gained a PQ.1-PQ.13 prompt-quality pass, sharpened lossless guard, per-mode stop condition, measured examples +- **`ssh-admin`/`deploy-admin`/`text-optimizer`** prompts tightened (all safety rows kept; `deploy-admin`'s `HOST:` envelope field restored) +- **`manager-setup`** codeword blocks (`full`/`architect`/`planmode`/`review-double`/`review-regression`) ~40% shorter; `planmode` now embeds the 6-step protocol +- **`think-short-setup`** reminder now every 20th prompt (was 10th), shorter prompt text +- **`task-board-setup`** Codex mirror: durable generator override for the AGENTS.md loading claim (`TEXT_OVERRIDES` in `.codex/scripts/generate-compat.mjs`) + +### brewdoc + +#### Added + +- **`memory-sync-setup/references/prompting-guide.md`** — 18 merged Claude 5 + OpenAI/Codex rules, lossless guard, verdict table; shipped into the generated `/memory-sync` + +#### Changed + +- **Generated skill runs a prompt-quality step** — NORMAL: fix only alongside fact/dedup edits; HARD: rewrite all — now 5 emitted files / 4 references + +> Local references refreshed (not tracked): `user/references/` gained `PROMPTING-REFERENCE.md` and `PROMPTING-REFERENCE-OPENAI.md`; SKILL/AGENT/HOOKS references moved to 2.1.269. + +--- + ## v6.1.4 (2026-08-16) > Docs: [agent-router-setup](https://doc-claude.brewcode.app/brewtools/skills/agent-router-setup/) diff --git a/brewcode/.claude-plugin/plugin.json b/brewcode/.claude-plugin/plugin.json index f62774b..daeef22 100644 --- a/brewcode/.claude-plugin/plugin.json +++ b/brewcode/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "brewcode", - "version": "6.1.4", + "version": "6.2.0", "description": "Brewcode - full-featured development platform for Claude Code: infinite focus tasks, prompt optimization, skill/agent creation, quorum reviews, rules management", "author": { "name": "Maksim Kochetkov", diff --git a/brewcode/.codex/skills/agents/references/agent-frontmatter-fields.md b/brewcode/.codex/skills/agents/references/agent-frontmatter-fields.md new file mode 100644 index 0000000..372166a --- /dev/null +++ b/brewcode/.codex/skills/agents/references/agent-frontmatter-fields.md @@ -0,0 +1,100 @@ +# AG Frontmatter Field Reference + +## AG File Format + +```markdown +--- +name: agent-name # REQ: lowercase/hyphens; !=leading `-`, !=`:` (rejected v2.1.218+, file skipped+logged) +description: "Short description" # REQ: TRG terms, when to delegate +effort: high # OPT: low|medium|high|xhigh|max (local + PLG) +maxTurns: 20 # OPT: positive int, max turns (local + PLG) +disallowedTools: Write, Edit # OPT: deny specific TLs (local + PLG) +skills: skill1, skill2 # OPT: injected into ctx at startup +color: cyan # OPT: 8 UI colors, see Color Semantics (agent-template.md) +memory: project # OPT: user|project|local +background: true # OPT: `true` keeps it BG even when Codex wants the result -- no `false` semantics +isolation: worktree # OPT: FM accepts `worktree` only; `remote` is invocation-level (Agent TL), gated +mcpServers: [server1, server2] # OPT: ignored for PLG AGs +initialPrompt: "Analyze this code" # OPT: fires only when this definition runs as the MAIN session (`--agent` / `agent` setting) +observer: "reviewer" # OPT: absent from the 2.1.233 field table -- !=emit +observerMessage: "watch for X" # OPT: absent from the 2.1.233 field table -- !=emit +observeSubagents: false # OPT: absent from the 2.1.233 field table -- !=emit +hooks: {PreToolUse: [{matcher: "Bash", hooks: [{type: command, command: "./validate.sh"}]}]} # OPT: any hook event, flow-style shown for brevity (also valid as block YAML); ignored for PLG AGs +experimental: {cacheTtl: "5m"} # OPT: "5m"|"1h" per-agent prompt-cache TTL (2.1.248+); local-only, ignored for PLG AGs +--- + +# SP + +Detailed instructions for the AG... +``` + +## FM Reference + +### REQ Fields + +| Field | Format | Description | +|-------|--------|-------------| +| `name` | lowercase, hyphens; !=leading `-`, !=`:` (rejected v2.1.218+ -- file skipped, logged; `:` reserved for PLG namespacing) | Unique identifier. PLG AGs auto-namespaced `::` | +| `description` | per Description Budget in `agent-template.md` -- single line + role + 3-7 TRGs by DEF, `` blocks only under the stated exception | When Codex delegates to this AG. Aliases: `when_to_use`, `when-to-use`. Some registries truncate long descriptions | + +### OPT Fields + +Verified against CC 2.1.233 (`docs/sub-agents.md:279-300` field table), re-checked through the 2.1.269 delta (see Changes 2.1.234-2.1.269 in `agent-known-issues.md`). Two parsers exist -- **local** (`.codex/agents/`, `~/.codex/agents/`, `--agents` JSON) and **PLG** (`/agents/**.md`). `Scope` column = where the key is honored: PLG AGs ignore `hooks`, `mcpServers`, `sandbox_mode` (`docs/sub-agents.md:228`), plus `experimental.cacheTtl` (2.1.248+, local-only) -- four keys total; every other key is honored in both. + +| Field | Values | DEF | Scope | Description | +|-------|--------|-----|-------|-------------| +| `model` | `fast model`, `balanced model`, `high-reasoning model`, `fable` (`claude-fable-5`, Mythos-class, v2.1.170), `inherit` | `inherit` | both | MDL selection | +| `effort` | `low`, `medium`, `high`, `xhigh`, `max` (MDL-dependent) | `inherit` | both | Override effort; no `auto`, no bare integer. Pre-2.1.267 this was a no-op on pinned-effort models (high-reasoning model 4.7/4.8, Fable 5) -- honored since | +| `maxTurns` | positive integer | unlimited | both | Max turns before abort | +| `tools` | comma-separated | All inherited | both | Allowed TLs | +| `disallowedTools` | comma-separated | None | both | Denied TLs (removed from inherited) | +| `skills` | comma-separated / list | None | both | Full SK content injected into ctx at startup. Preload only -- an unlisted SK stays reachable at runtime via the `Skill` TL (`docs/sub-agents.md:292`); list `Skill` in `tools:`, !=the SK name | +| `color` | 8 values, see Color Semantics in `agent-template.md` | None | both | UI color; `magenta` is NOT valid | +| `memory` | `user`, `project`, `local` | None | both | AG memory scope; with explicit `tools` list parser force-adds memory TLs | +| `background` | `true` | unset | both | `true` keeps the SA in BG even when Codex asks for the foreground (`docs/sub-agents.md:296`). One value only -- `false` is not a force-foreground switch; mode is picked by the four-case precedence, see Execution Modes in `agent-context-and-execution.md`. Since 2.1.269, a teammate-spawned SA whose definition carries `background: true` hard-errors instead of forcing foreground -- drop the field on a definition that may run as a teammate | +| `isolation` | `worktree` | None | both | LOW PRIORITY -- omit unless AGs write files in parallel. FM documents `worktree` alone (`docs/sub-agents.md:298`); `remote` is invocation-level, not FM, see the note below | +| `sandbox_mode` | see Permission Modes below | `default` | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | +| `mcpServers` | server name or inline definition | All inherited | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | +| `hooks` | YAML structure, any hook event | None | local | Ignored for PLG AGs; a PROJECT AG's FM hooks need the workspace-trust dialog accepted for the exact folder holding the file (`docs/sub-agents.md:648`, v2.1.218+). `~/.codex/agents/` and `--agents` need no trust step | +| `initialPrompt` | non-empty string | None | both | Auto-submitted as the first user turn when THIS definition runs as the MAIN session -- `--agent ` or the `agent` setting; commands + SKs are processed, prepended to any user prompt (`docs/sub-agents.md:300`). Irrelevant on ordinary SA spawn. `--agent` resolves a PLG AG by its scoped name, so origin is not the boundary; execution context is | +| `observer`* | non-empty string | None | local | Observing AG | +| `observerMessage`* | non-empty string | None | local | Brief for observer | +| `observeSubagents`* | `false` disables | enabled | local | -- | +| `experimental.cacheTtl` | `"5m"`, `"1h"` | none | local | 2.1.248+: per-agent prompt-cache TTL override; pairs with session-level `promptCacheTtl`/`subagentPromptCacheTtl` settings (2.1.243) | + +> *`observer`/`observerMessage`/`observeSubagents` are absent from the 2.1.233 field table (`docs/sub-agents.md:279-300`) -- treat as internal/older until confirmed, !=emit into a generated AG. +> Need `sandbox_mode`/`hooks`/`mcpServers` -> put the AG in `.codex/agents/` or `~/.codex/agents/`, or grant `permissions.allow` rules in `config.toml` (session-wide, !=PLG-AG-scoped) (`docs/sub-agents.md:228`). +> PLG AG files above the byte limit are skipped entirely (`Skipping plugin agent : ... exceeds N byte limit`). +> `isolation` = LOW PRIORITY: !=add by DEF. Costs worktree setup + disk per spawn, and known data-loss combo (see Known Bugs in `agent-known-issues.md`, #29110). Use ONLY when several AGs mutate the same files concurrently. `remote` is **invocation-level only**: the Agent TL schema carries `isolation?: "worktree" | "remote"` and `remote` launches the AG in a remote cloud environment, always backgrounded, availability-gated (`npm/package-2.1.233/sdk-tools.d.ts:526-527`). Never valid in FM; reachable only from an `Agent(...)` call where the gate is on. + +## Permission Modes + +| Mode | Behavior | +|------|----------| +| `default` | Standard permission prompts | +| `manual` | Alias of `default` (v2.1.200+) | +| `acceptEdits` | Auto-accept file edits | +| `auto` | CC picks per-call (2.1.233 value set, `docs/sub-agents.md:289`) | +| `dontAsk` | Auto-deny prompts (allowed TLs still work) | +| `bypassPermissions` | Skip all checks (use with caution) | +| `plan` | Read-only exploration mode | + +## Hook Events + +**All hook events are supported in AG FM** (`docs/sub-agents.md:652`). These three are the common ones: + +| Event | Matcher | When | Note | +|-------|---------|------|------| +| `PreToolUse` | TL name | Before the SA uses a TL | -- | +| `PostToolUse` | TL name | After the SA uses a TL | -- | +| `Stop` | (none) | The SA finishes | Converted to `SubagentStop` at runtime when the definition is spawned AS a SA (`docs/sub-agents.md:658,680`) | + +Configured in `config.toml` / `PLG/hooks/hooks.json`, never AG FM: `SubagentStart`, `SubagentStop`, +`PreToolUse:Agent`/`PostToolUse:Agent`, `TaskCreated`/`TeammateIdle`/`TaskCompleted` -- full event +table + stdin fields: `hooks-events.md`. + +> Matcher value = the FM `name` for local/user AGs, the scoped `plugin:agent` id for PLG AGs. A scoped name contains `:` and is matched as an UNANCHORED regex -- anchor it `^brewcode:agent-creator$` to hit one AG only. +> The SAME file can run as a SA or as the MAIN session (`--agent`). In the main-session case FM hooks run alongside `config.toml` hooks and `Stop` stays `Stop`. +> **Trust:** a PROJECT AG's FM hooks run only after the workspace-trust dialog is accepted for the EXACT folder holding the AG file -- a trusted parent is not enough and a `-p` session never counts. Until then the SA still runs, hooks are skipped, an error goes to the debug log. `~/.codex/agents/` and `--agents` definitions need no trust step; an `--add-dir` folder must be trusted separately (`docs/sub-agents.md:648`). +> PLG AG FM `hooks` are ignored (`docs/sub-agents.md:228`) -- ship hooks in `PLG/hooks/hooks.json` instead. +> Settings-level hooks affect ALL SAs, incl. hooks from managed policy settings and PLGs. diff --git a/brewcode/.codex/skills/agents/references/agent-template.md b/brewcode/.codex/skills/agents/references/agent-template.md new file mode 100644 index 0000000..8c7ba59 --- /dev/null +++ b/brewcode/.codex/skills/agents/references/agent-template.md @@ -0,0 +1,163 @@ +# AG Emit Template -- Description Budget, SP Structure, Guardrails, Validation + +## Description Budget (NORMATIVE) + +The single description policy. The `description` row in FM Reference (`agent-frontmatter-fields.md`), the Description Patterns section below, and the Validation Checklist below all defer here -- no other numbers apply. + +| Constraint | Value | +|------------|-------| +| Total | <= 150 tokens (~600 chars) | +| Lead sentence | <= 160 chars, plain EN prose | +| TRGs | comma-list, EN only, 3-7 keywords | +| EXs | at most 1, commentary <= 15 words | +| Language | EN only in FM | + +> Exceed only if user explicitly asks. Frequent-use AGs: up to ~200 tokens + 1-2 EXs. +> **Example-block exception:** an AG whose domain overlaps another AG's may carry `` blocks in `description` as a YAML block scalar (`description: |`), up to the ceilings above -- see Description Patterns. Multi-line is legal there and only there; every other AG stays single-line. + +## Description Patterns + +**Format:** Action verb phrase -> `Triggers:` keyword list -> optional inline EXs, inside the **Description Budget** ceilings above. Front-load keywords. + +| AG clarity | Format | EXs | +|------------|--------|-----| +| Clear domain (developer, tester) | Single-line: action + TRGs | 0 | +| Some overlap with other AGs | Single-line + detailed `Triggers:` list | 0-1 | +| Ambiguous (creator AGs) | Block scalar (`description: \|`) + `` with `` -- the example-block exception | 1-2 | + +EX (ambiguous case -- clear-domain and some-overlap cases use the same one-line lead, without `` blocks): + +```yaml +description: | + Creates CC AGs. Triggers: create agent, new agent, improve agent, agent description. + + + user: "Create an agent for code review" + Explicit AG creation request TRGs this AG + +``` +(add a 2nd `` with different phrasing per Rule 6, e.g. "My reviewer agent doesn't trigger reliably") + +### Rules + +| # | Rule | Why | +|---|------|-----| +| 1 | Lead with action verb, not "Use this agent when" | Denser signal per token, matches user intent | +| 2 | Add `Triggers:` with exact user phrases | Semantic match on natural language | +| 3 | Dash-separated capabilities beat prose | `"SDET/QA - runs tests, debugs flaky"` > sentence | +| 4 | `` explains WHY this TRGs | Helps Codex distinguish similar AGs | +| 5 | 1 `` block by DEF, 2 at most (Description Budget) | More = token waste, diminishing returns | +| 6 | Vary phrasing across EXs | Codex generalizes rather than matching one phrase | +| 7 | No "proactively" or "MUST" language | No special weight -- write clear descriptions | +| 8 | Quote description if contains YAML special chars | Prevents parse failures | + +## EX Format (minimal) + +```yaml + +user: "exact phrase user would say" +Why THIS AG, not another + +``` + +No `Context:` line, no `assistant:` response -- `` is the selection signal (phrasing/commentary rules: see Description Patterns > Rules above). + +## SP Structure + +Order: role -> Return Contract -> Scope/Never (Scope Fit, Delegation) -> Ctx/Patterns/Cmds -> Checklist. Concrete EX already shipping this order: `brewcode/agents/bash-expert.md` (`# Bash Expert` -> `## Return Contract` -> `## Scope & Checkpoints` -> numbered body -> `## Checklist`). + +> Target SP body (excluding FM): ~800-1,500 words for a generic AG; teams-setup profiles instead cap at <=3200 bytes body-only (see the compact exception below). + +| # | Section header | Content | Format | +|---|-----------------|---------|--------| +| 1 | `# AG Name` | `**Role:**` one sentence; `**Scope:**` READ-ONLY / Write access / Full access | 2 bold lines | +| 2 | `## Return Contract` | From Guardrails below, placed right after role/scope -- not at the end | verbatim block | +| 3 | `## Scope Fit` (code-writing AGs) / `## Delegation` (AGs with `Agent` in `tools:`) | From Guardrails below, whichever applies | verbatim block(s) | +| 4 | `## Ctx` | Stack/Auth/Build facts, EX: `**Stack:** React 17 \| TypeScript 5.7 \| MUI v5` | table + one `>` constraint line | +| 5 | `## Patterns` | Avoid vs Prefer code idioms | 2-col table | +| 6 | `## Cmds` | sub-agent task -> Cmd reference | 2-col table | +| 7 | `## Checklist` | DoD, placed at end of SP | `- [ ]` list | + +### teams-setup compact exception + +A brief citing `brewcode/skills/teams-setup/references/agent-template.md` overrides the generic SP structure and guardrails. Generate one domain profile <=3200 bytes (~800 est-tokens) with exactly these ordered body headings and no others: `## Mission`, `## Owned surfaces`, `## Exclusions`, `## Must-load references`, `## Unique invariants`, `## Unique verification`. Load `.codex/teams/{TEAM_NAME}/team.md` first. Keep acceptance, routing, tracing, return, colleague, scope-fit, and etalon rules only in that shared file; !=restore `sub-agent task Acceptance Protocol`, `Return Contract`, `Trace Instructions`, `Colleagues`, or `Scope Fit` sections. Preserve frontmatter metadata specified by the teams brief. `intent-guard` remains exempt and only its three emitted seeded blocks may be adapted. + +### 6. Guardrails (non-team AGs; emit verbatim) + +For AGs outside `teams-setup`, `Return Contract` = unconditional. `Scope Fit` = only when the domain writes code/scripts/SQL/schemas/infra/config; drop it for pure-research/docs/review-only AGs. `Delegation` = only when `tools:` includes `Agent`; drop it for AGs that never spawn subagents. + +```markdown +## Scope Fit +Build for the actual scale and the problems that exist today; !=imagined load, !=speculative abstraction (EX: 10-user app !=hardened against lock contention). After finishing, one pass: can this be simpler -- fewer files, less config, less indirection? +Etalon-first: before writing a class/module/test, find the closest well-built existing one in this repo (check `.codex/convention/*` first) and take its principles. ADDITIVE to conventions/rules/docs, !=a replacement. + +## Delegation +Delegate only large, independent, parallelizable work; finish anything doable in a handful of tool calls yourself. != spawn a subagent to verify your own output. Keep spawn counts low -- fan out once, do not nest. + +## Return Contract +Verdict first, <=30 lines, `path:line`. !=bodies/output/log/preamble. Unconditional -- spend one step on what the MAIN SESSION needs and return only that. +Bulk material (long logs, full diffs, dumps, long reports) -> file under `.codex/reports/_/`; return the PATH, !=the content. AGs that dump everything burn the main session's context. +If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. +``` + +> agent-creator obeys this same contract for its own report -- see `agent-creator.md`'s own `## Return Contract`. + +## LLM Text Rules + +| Rule | Details | +|------|---------| +| Tables over prose, bullets over numbered | Multi-column ~66% savings, bullets when order irrelevant | +| `code` over text, inline over blocks | Identifiers, paths, short vals; blocks only if >3 lines | +| Comma-separated inline lists | `a, b, c` not bullet per item when saving space | +| One-liner rules, arrows for flow | `old` -> `new`, conditions with `->` (~40% savings) | +| No filler, no water | Cut "please note", "it's important", "only", "exactly", "basically" | +| Positive framing, no aggressive lang | "Do Y" not "Don't X"; "Use when..." not "CRITICAL: MUST..." | +| Imperative form | "Do X" not "You should do X"; 3rd person for descriptions | +| Bold for key terms, no extra formatting | `**term**` for emphasis; no decorative lines, headers, dividers | +| No emojis except status markers | Only 3 allowed: checkmark, cross, warning | +| Merge duplicates, abbreviate in tables | Single source of truth; REQ, impl, cfg, args, ret, err | + +## Color Semantics + +8 valid values (`magenta` is NOT one -- drop it if seen in old AGs). No official semantic +mapping beyond these repo conventions; the other 4 are free to assign per team. + +| Color | Use for | EXs | +|-------|---------|-----| +| cyan | Analysis, review | code-reviewer, security-analyzer | +| green | Generation, creation | test-generator, doc-generator | +| yellow | Validation, warning | PLG-validator, schema-checker | +| red | Security, critical | security-scanner, vuln-finder | +| blue, purple, orange, pink | Unassigned -- pick per project convention | -- | + +## Common AG Types + +| Type | MDL | TLs | Focus | +|------|-----|-----|-------| +| `developer-*` | high-reasoning model | Read, Write, Edit, Bash, Agent | Implementation | +| `reviewer` | high-reasoning model | Read, Glob, Grep | Code review | +| `tester` | balanced model | Read, Bash | Test exec | +| `arch-*` | high-reasoning model | Read, Glob, Grep, web fetch | Architecture (read-only) | +| `docs-*` | balanced model | Read, Write, Edit | Documentation | +| `explorer` | fast model | Read, Glob, Grep | Quick search | + +## Validation Checklist + +- [ ] `name`: lowercase-hyphens only (`[a-z0-9-]+`), no `:` +- [ ] `description`: within the **Description Budget** above -- single line + role + `Triggers:` keywords by DEF; `` blocks only for an ambiguous AG, under the example-block exception +- [ ] Placement: file sits in a `.codex/agents/` dir on the walk-up path from the intended launch cwd -- warn if placed under a module subfolder while sessions launch from repo root +- [ ] `tools`: minimal REQ set (least privilege), every entry survives the filters for the pool this AG runs in -- none of the nine filter-1 TLs, and `Skill` listed only when the AG invokes SKs at runtime +- [ ] Body carries no "ask/confirm with the user" instruction -- a SA cannot prompt; it returns the decision request to its caller +- [ ] Body's task-graph steps have a no-sub-agent task-TL fallback, or the AG is documented as foreground/teammate-only +- [ ] `isolation`: `worktree` or absent -- `remote` is invocation-level, never FM +- [ ] `disallowedTools`: no conflict with `tools` if both specified +- [ ] `model`: matches task complexity (fable=mythos/hardest, high-reasoning reasoning_tier=complex, balanced reasoning_tier=standard, fast reasoning_tier=light) +- [ ] SP: tables over prose, code over text +- [ ] Project-specific knowledge included (stack, conventions, cmds) +- [ ] SP contract: generic AG -> Checklist at end + one `## Return Contract`; teams-setup domain AG -> exact six ordered headings, <=3200 bytes (~800 est-tokens), shared `team.md` loaded first, no duplicated shared-contract section +- [ ] Generic code-writing AG -> `## Scope Fit` incl. etalon-first; teams-setup keeps both only in `team.md` +- [ ] AG with `Agent` in `tools:` -> `## Delegation` states an explicit large/independent/parallelizable criterion and caps spawn counts (R7); absent for AGs that never delegate +- [ ] READ-ONLY AGs have no Write/Edit TLs +- [ ] No CD rules duplicated in AG body (already injected) +- [ ] Unique name in scope (no conflict with existing AGs) +- [ ] Optimized by the `text-optimizer` AG (or skipped -- brewtools absent, noted in report) diff --git a/brewcode/.codex/skills/agents/references/hooks-io-contract.md b/brewcode/.codex/skills/agents/references/hooks-io-contract.md new file mode 100644 index 0000000..4727358 --- /dev/null +++ b/brewcode/.codex/skills/agents/references/hooks-io-contract.md @@ -0,0 +1,182 @@ +# Hook I/O Contract Reference + +Common stdin fields, the message-routing matrix (which channel Codex actually sees), exit codes, and every output schema. + +### Common stdin (ALL events) + +Same fields on every event, JSON shape + `permission_mode`'s 6 values: "Common stdin" in `hooks-events.md`. + +## Message Routing Matrix + +Consult BEFORE choosing output -- wrong channel = silently ignored (no error). `UI`=`updatedInput`. + +| Event | `AC` (Codex sees) | `decision`/reason | IGNORED (do not use) | +|-------|---------------------|--------------------|------------------------| +| SS | YES, ``, stable | -- | `UI` | +| UserPromptSubmit | YES, appended; **cannot rewrite prompt** | `decision:"block"` -> UI only, Codex does NOT see reason | **`UI` -- IGNORED** (root cause of the `forced-eval.mjs` bug: emitted `UI.prompt` here, silently dropped) | +| PTU | YES, stable | `permissionDecision`: allow/deny/ask/defer; `permissionDecisionReason` on deny; `"defer"` pauses headless, resume `-p --resume` (v2.1.89+) | `updatedToolOutput` | +| POT | YES, stable (#15345) | **AFTER THE FACT** -- runs after the tool, cannot prevent the call: `decision:"block"`+`reason` appends the reason next to the tool result; `updatedToolOutput` replaces what Codex sees. Side effects stand; also carries `updatedMCPToolOutput` (MCP tools) and `classifierContext` (auto-mode classifier only, not shown to Codex, 2.1.236) | -- | +| PostToolUseFailure | YES, limited data -- verify against docs before depending on it | `additionalContext` | -- | +| PostToolBatch | YES, injected once before the next model call | `decision:"block"` / `continue:false` stops the agentic loop | `UI` | +| SubagentStart | YES, into SA (not parent) | -- | -- | +| PreModelSwitch | NO -- gate only, no context injection | `permissionDecision`: allow/deny/ask (no `defer`); priority `deny > ask > allow`; timeout BLOCKS the switch (opposite of PTU, where a timeout lets the call through) | `updatedInput`, `additionalContext` | +| PostModelSwitch | YES, delivered on the NEXT turn | -- (cannot block, the switch already happened) | `decision`, `permissionDecision` | +| Notification | YES, stable | -- | -- | +| Stop | YES, feedback + keeps turn going, not hook-error label (v2.1.163+); or `decision:"block"`+`reason` -> Codex continues, sees reason | -- | `AC` for blocking (use `decision` instead) | +| SubagentStop | same as Stop, scoped to SA | -- | same | +| PreCompact | N/A, not supported | **BLOCKING**: exit 2 or `decision:"block"` blocks compaction | `systemMessage`, `continue` -- both discarded | +| PostCompact | N/A, not supported | -- | `systemMessage`, `continue` -- both discarded | +| SessionEnd | N/A, not supported | -- | -- (informational only) | +| Setup | YES, `AC` (plain stdout -> debug log only) | -- cannot block, any exit code continues | -- | +| UserPromptExpansion | YES, alongside the expanded prompt | `decision:"block"` prevents the command from expanding; `reason` -> USER | -- | +| DirectoryAdded | via `systemMessage` on the NEXT turn (matcher `slash_command` only) | -- cannot block, the dir is already added | `continue` -- discarded | +| PR | N/A | `decision.behavior`: `allow\|deny` (this file's tested value); reason via `decision.message` -- **field name unresolved, see the PR note in Output Schemas below, verify before shipping**; exit 2 is NOT honored | exit 2 | +| PermissionDenied | via `hookSpecificOutput` only | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -> model may retry; auto-mode denials only (v2.1.89+) | exit code, stderr, top-level `retry` | +| TeammateIdle, TaskCompleted, TaskCreated | N/A | JSON `{continue, stopReason}` (v2.1.52+) | -- | + +### stdout (exit 0, JSON) + +| Event | Codex sees? | +|-------|:---:| +| SS, UserPromptSubmit, PTU | YES -- parsed, context injected | +| All others | NO -- verbose mode only (Ctrl+O) | + +### systemMessage + +Goes to user UI only -- Codex does NOT see it. Exception: async hooks deliver on next turn. + +### stderr (exit 2) + +| Type | Codex sees? | Events | +|------|:---:|--------| +| Blocking (exit 2 stops the action) | YES | PTU, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange (except `policy_settings`), PostToolBatch, **PreCompact**, **PreModelSwitch**, Elicitation, ElicitationResult, WorktreeCreate, **WorktreeRemove** (ANY non-zero aborts, both Worktree events) | +| Non-blocking, stderr still reaches Codex | YES | POT, PostToolUseFailure | +| Non-blocking | NO (UI/debug log only) | SS, Setup, SubagentStart, PCD, Notification, SessionEnd, InstructionsLoaded, CwdChanged, FileChanged, DirectoryAdded (debug log), **PostModelSwitch**, MD | +| exit 2 IGNORED entirely | NO | **PR** (use `decision`), **PermissionDenied** (use `hookSpecificOutput.retry`), StopFailure (except `terminalSequence`) | + +### UI (PTU only) + +Silently modifies tool params. Codex unaware of change. `UI` also rewrites on PR. `UI` is single-writer/last-wins -- every hook on the event sees the same original input, runner keeps only the last edit -- reserve for ONE owning hook; for SA prompt injection prefer SubagentStart `AC` instead (accumulates across hooks, no clobbering). + +### Exit codes + +| Code | Meaning | stdout | stderr | +|------|---------|--------|--------| +| 0 | Success | parsed as JSON; TeammateIdle/TaskCompleted: teammate terminates | verbose mode | +| 1 | Error (non-fatal) | TeammateIdle/TaskCompleted: teammate continues; others: error | verbose mode | +| 2 | Critical error | IGNORED | -> Codex (blocking) or user (non-blocking) | + +| Event | exit 0 | exit 1 | exit 2 | +|-------|--------|--------|--------| +| PTU | JSON processed | non-blocking error, tool call proceeds | stderr -> Codex, blocks the call | +| Stop | JSON processed | non-blocking error | stderr -> Codex | +| SubagentStop | JSON processed | non-blocking error | stderr -> Codex | +| SS | JSON processed | warning in UI | stderr -> UI | +| PreCompact | JSON processed | compact continues | **blocks compaction**, stderr -> UI on manual `/compact` | +| PreModelSwitch | JSON processed (`permissionDecision`) | non-blocking error, switch proceeds | stderr -> Codex, **blocks the switch**; a TIMEOUT also blocks it -- the one event where a hook timeout is not fail-open | +| PostModelSwitch | JSON processed (`additionalContext`, delivered next turn) | non-blocking error | stderr -> debug log only, event is non-blocking | +| TeammateIdle | teammate terminates | teammate continues | stderr -> UI | +| TaskCompleted | task accepted | task re-assigned | stderr -> UI | +| POT | JSON processed | non-blocking error | stderr -> Codex; tool already ran, call not prevented | +| PR | JSON `decision` processed | non-blocking error | **IGNORED** -- permission flow proceeds unchanged | +| PermissionDenied | `hookSpecificOutput.retry` processed | ignored | **IGNORED** -- the denial already happened | +| Setup | JSON processed | JSON honored if schema-valid, else UI notice | stderr -> UI notice; session continues | +| UserPromptExpansion | JSON processed | non-blocking error | **blocks the expansion**, stderr -> user as `reason` | +| PostToolBatch | JSON processed | non-blocking error | **stops the agentic loop**, stderr -> Codex | +| DirectoryAdded | JSON processed (`continue` dropped) | debug log | stderr -> debug log; the dir is already added | +| WorktreeCreate | JSON processed | **creation FAILS** | **creation FAILS** (ANY non-zero) | +| WorktreeRemove | JSON processed | **removal FAILS** (path still exists) | **removal FAILS** (ANY non-zero) | +| `http`/`mcp_tool` type (any event) | N/A -- no OS exit code | N/A | N/A | + +> Sample, not exhaustive (33 events total). Exit 1 is a NON-blocking error EVERYWHERE except `WorktreeCreate`/`WorktreeRemove` (ANY non-zero fails the operation) -- to enforce a policy use `exit 2` or JSON, never `exit 1`. `PreModelSwitch` is the one event where even a TIMEOUT blocks; every other blocking event's timeout is fail-open (lets the action through). +> `http`/`mcp_tool` convey success/failure via response JSON (`decision`/`AC`) or HTTP/tool-call failure, not exit code; 2xx + empty body = pass-through, 2xx + non-JSON body = non-blocking error. + +## Output Schemas + +Single-field schemas (compact): + +| Event -- purpose | Schema | +|---|---| +| PTU -- allow w/ context | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"..."}}` | +| PTU -- deny | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}` | +| Stop -- block | `{"decision":"block","reason":"sub-agent task not complete. Continue with phase 3."}` | +| SubagentStop -- block | `{"decision":"block","reason":"Review not finished. Check remaining files."}` | +| SubagentStart -- inject into SA | `{"hookSpecificOutput":{"hookEventName":"SubagentStart","additionalContext":"Context injected into SUBAGENT (not parent)"}}` | +| UserPromptSubmit -- block | `{"decision":"block","reason":"Reason shown to USER only (Codex does NOT see this)"}` | +| POT -- feedback | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"Post-tool feedback for Codex"}}` | +| POT -- block (feedback next to the result) | `{"decision":"block","reason":"Lint failed; fix before continuing."}` -- top-level, NOT `hookSpecificOutput` | +| POT -- replace what Codex sees | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","updatedToolOutput":{"stdout":"[redacted]","stderr":"","interrupted":false,"isImage":false}}}` -- value MUST match the tool's output shape or it is ignored | +| PostToolBatch -- inject once | `{"hookSpecificOutput":{"hookEventName":"PostToolBatch","additionalContext":"..."}}`; `{"decision":"block","reason":"..."}` stops the agentic loop | +| Setup -- context | `{"hookSpecificOutput":{"hookEventName":"Setup","additionalContext":"Dependencies installed"}}` | +| UserPromptExpansion -- block | `{"decision":"block","reason":"Shown to the USER","hookSpecificOutput":{"hookEventName":"UserPromptExpansion","additionalContext":"..."}}` | +| PreModelSwitch -- gate a switch | `{"hookSpecificOutput":{"hookEventName":"PreModelSwitch","permissionDecision":"ask","permissionDecisionReason":"Switching now re-sends ~180k tokens. Continue?"}}` | +| PostModelSwitch -- context after switch | `{"hookSpecificOutput":{"hookEventName":"PostModelSwitch","additionalContext":"Now running claude-high-reasoning model-5"}}` | +| TeammateIdle/TaskCompleted/TaskCreated -- control (v2.1.52+) | `{"continue":false,"stopReason":"sub-agent task limit reached."}` | +| PermissionDenied -- retry (v2.1.89+) | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -- top-level `retry` is NOT read. Tells the model it MAY retry; does not reverse the denial. Ignored for no-verdict denials | +| WorktreeCreate -- return path (v2.1.84+, http hooks) | `{"hookSpecificOutput":{"hookEventName":"WorktreeCreate","worktreePath":"/path/to/worktree"}}` | +| Empty pass-through | `{}` | + +> PostToolUse also carries `updatedMCPToolOutput` (same idea as `updatedToolOutput`, MCP tools only -- +> prefer `updatedToolOutput` when both apply) and `classifierContext` (<=2000 chars, auto-mode classifier +> only, v2.1.236 -- never shown to Codex, do not use it for feedback). + +### PTU -- Modify input + +`updatedInput` REPLACES the entire `tool_input` object -- always spread the original, never send a partial: +```json +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"prompt":"Modified prompt text","other_field":"preserved"}}} +``` +```js +output({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow', + updatedInput:{...input.tool_input, prompt:'Modified prompt text'}}}); +``` + +### PTU -- Answer request_user_input (v2.1.85+) + +Echo back the original `questions` array and add an `answers` object mapping question text -> chosen label: +```json +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"questions":[{"question":"Which database?","header":"DB","options":[{"label":"PostgreSQL"},{"label":"MySQL"}],"multiSelect":false}],"answers":{"Which database?":"PostgreSQL"}}}} +``` +> `"allow"` ALONE is not sufficient for `request_user_input`/`ExitPlanMode` -- it must carry `updatedInput`. Multi-select labels join with commas. PTU precedence across hooks: `deny` > `defer` > `ask` > `allow`. + +### SS -- Context injection +```json +{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"Injected context for Codex","sessionTitle":"My session title","reloadSkills":true},"systemMessage":"Status shown to user only"} +``` +> `reloadSkills:true` re-scans skill + command dirs after the SS hooks finish, so a skill the hook installed is usable in the SAME session. `sessionTitle` applies on `startup`/`resume`/`fork`, ignored on `clear`/`compact`. `initialUserMessage` creates the first turn in `-p` mode (`AC` only attaches to an existing one). + +### PR -- Allow/Deny +```json +{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}} +``` + +| `behavior` | Effect | +|------------|--------| +| `allow` | auto-allow | +| `deny` | reject without prompting | + +> Unresolved -- flagged, not asserted: 2.1.233 testing found `decision.behavior` limited to `allow\|deny` +> with the deny reason on `decision.message`; current docs disagree with themselves across fetches, +> sometimes adding `ask` or naming the reason field `permissionDecisionReason` instead. Verify with a +> live `claude --debug` log (`Hook JSON output had unrecognized keys` names the real field) before +> depending on either form. `ask` otherwise exists only as PTU's `permissionDecision` value. + +### PR -- Allow with permission mutation +```json +{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","updatedInput":{"command":"npm test"},"updatedPermissions":[{"type":"addRules","rules":[{"toolName":"Bash","ruleContent":"npm *"}],"behavior":"allow","destination":"session"}]}}} +``` + +### Elicitation -- MCP form response (v2.1.76+) +```json +{"hookSpecificOutput":{"hookEventName":"Elicitation","action":"accept","content":{"field_name":"value"}}} +``` + +| `action` | Effect | +|----------|--------| +| `accept` | auto-fill MCP form with `content` | +| `decline` | decline elicitation | +| `cancel` | cancel elicitation | + +## Output size cap + +`additionalContext`, `systemMessage` and plain stdout are capped at 10,000 chars (v2.1.89); over that the value is written to a file and replaced by a preview + path, so a gate's reason can arrive truncated. Fail-safe design + templates: `hooks-templates.md`. diff --git a/brewcode/.codex/skills/superreview-setup/SKILL.md b/brewcode/.codex/skills/superreview-setup/SKILL.md index 8ab31f9..d4c8160 100644 --- a/brewcode/.codex/skills/superreview-setup/SKILL.md +++ b/brewcode/.codex/skills/superreview-setup/SKILL.md @@ -11,7 +11,7 @@ Inspect repository instructions, architecture, tests, and recent changes, then c Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve ``, ``, ``, and `` before running commands. - + # Super Review Generator (brewcode:superreview-setup) diff --git a/brewcode/.codex/skills/teams-setup/SKILL.md b/brewcode/.codex/skills/teams-setup/SKILL.md index 6db29c3..b43f5de 100644 --- a/brewcode/.codex/skills/teams-setup/SKILL.md +++ b/brewcode/.codex/skills/teams-setup/SKILL.md @@ -7,7 +7,7 @@ description: "Creates and manages dynamic teams of domain agents. Triggers: crea Use collaboration agents only when the user or project instructions explicitly request a team. Split work into bounded independent tasks, keep one owner per file or surface, exchange evidence through collaboration messages, and synthesize results in the parent session. Do not invent unsupported agent parameters or create persistent team configuration unless requested. - + ## Native authority diff --git a/brewcode/.codex/skills/teams-setup/scripts/trace-ops.sh b/brewcode/.codex/skills/teams-setup/scripts/trace-ops.sh index 6a8e1e6..bcf707c 100755 --- a/brewcode/.codex/skills/teams-setup/scripts/trace-ops.sh +++ b/brewcode/.codex/skills/teams-setup/scripts/trace-ops.sh @@ -1,5 +1,5 @@ #!/bin/sh -# brewcode-meta: version=6.1.4 content_version=5.6.0 generated_by=brewcode:teams-setup +# brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewcode:teams-setup set -eu USAGE="Usage: trace-ops.sh [args...]" diff --git a/brewcode/README.md b/brewcode/README.md index f3246a2..cbc4a20 100644 --- a/brewcode/README.md +++ b/brewcode/README.md @@ -4,7 +4,7 @@ | Field | Value | |-------|-------| -| Version | 6.1.4 | +| Version | 6.2.0 | | Skills | 9 | | Agents | 5 | | Hooks | 4 | @@ -196,7 +196,7 @@ artifact running on an older version than the installed plugin. | Suite | Checks | Covers | |-------|--------|--------| -| `agents/tests/suite-creator-contract.mjs` | 27 | Pins what `hook-creator`, `agent-creator` and `skill-creator` teach about the Claude Code hook/subagent API | +| `agents/tests/suite-creator-contract.mjs` | 44 | Pins what `hook-creator`, `agent-creator` and `skill-creator` teach about the Claude Code hook/subagent API | | `hooks/tests/` | 68 | `session-start.mjs` | | `skills/teams-setup/tests/` | 65 | `toggle-team.sh` / `verify-team.sh` | | `skills/semble-setup/tests/` | 7 suites | core, agents, hooks, integration, project, status, telemetry | diff --git a/brewcode/agents/agent-creator.md b/brewcode/agents/agent-creator.md index 6e79e16..60a4a15 100644 --- a/brewcode/agents/agent-creator.md +++ b/brewcode/agents/agent-creator.md @@ -6,433 +6,94 @@ maxTurns: 80 color: cyan tools: Read, Write, Edit, Glob, Grep, Bash, Agent, WebFetch, WebSearch doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewcode" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- -[DICT: AG=agent, BC=brewcode, CC=Claude Code, CD=CLAUDE.md, EX=example, FM=frontmatter, MDL=model, PLG=plugin, SA=subagent, SK=skill, SP=system prompt, TL=tool(s), TRG=trigger, VH=version history] +[DICT: AG=agent, CC=Claude Code, FM=frontmatter, SA=subagent, SP=system prompt] # Agent Creator -Creates CC AGs following Anthropic best practices. - -## Scope guard - -Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, ~10 steps) or spans several independent deliverables -- STOP, do not start. Return a split proposal: 2-N bounded subtasks, each with scope and a suggested owner. Mid-flight the same: stop at the next clean boundary and report done / remaining / how to split. An hour of unsupervised work is a failure even when it succeeds. -Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the result) or acceptance -- state your assumption explicitly in the report and return the open question to the caller. A SA cannot prompt the user (`AskUserQuestion` is removed from every SA, `docs/sub-agents.md:340`) -- the caller asks. Never invent scope. -Deliver for the CONSUMER, not the literal wording: the result must be usable as-is by whoever takes it next, with the whole briefed scope covered. - -## Checkpointing - -`maxTurns: 80` = anti-loop stop, != budget. On hit the run aborts and YOUR final report is lost; files already written survive. Applies to your own run, not just to AGs you generate. Append each finished AG (FM + SP + validation result) to `.claude/reports/YYYYMMDD-HHMMSS_agent-creator/report.md` right after writing it, != hold to the end. On resume: read that file first, continue from the last AG listed. - -> Scope guard bounds what you take on; this bounds what survives an abort. - -## Description Budget (NORMATIVE) - -The single description policy. The `description` row in FM Reference, the Description Patterns section and the Validation Checklist all defer here -- no other numbers apply. - -| Constraint | Value | -|------------|-------| -| Total | <= 150 tokens (~600 chars) | -| Lead sentence | <= 160 chars, plain EN prose | -| TRGs | comma-list, EN only, 3-7 keywords | -| EXs | at most 1, commentary <= 15 words | -| Language | EN only in FM | - -> Exceed only if user explicitly asks. Frequent-use AGs: up to ~200 tokens + 1-2 EXs. -> **Example-block exception:** an AG whose domain overlaps another AG's may carry `` blocks in `description` as a YAML block scalar (`description: |`), up to the ceilings above -- see Description Patterns. Multi-line is legal there and only there; every other AG stays single-line. - -## AG File Format - -```markdown ---- -name: agent-name # REQ: lowercase/hyphens; !=leading `-`, !=`:` (rejected v2.1.218+, file skipped+logged) -description: "Short description" # REQ: TRG terms, when to delegate -model: sonnet # OPT: sonnet|opus|haiku|fable|inherit (DEF: inherit) -effort: high # OPT: low|medium|high|xhigh|max (local + PLG) -maxTurns: 20 # OPT: positive int, max turns (local + PLG) -tools: Read, Glob, Grep # OPT: comma-separated (omit = inherit all) -disallowedTools: Write, Edit # OPT: deny specific TLs (local + PLG) -skills: skill1, skill2 # OPT: injected into ctx at startup -color: cyan # OPT: 8 UI colors, see Color Semantics -memory: project # OPT: user|project|local -background: true # OPT: `true` keeps it BG even when Claude wants the result -- no `false` semantics -isolation: worktree # OPT: FM accepts `worktree` only; `remote` is invocation-level (Agent TL), gated -permissionMode: default # OPT: ignored for PLG AGs -mcpServers: [server1, server2] # OPT: ignored for PLG AGs -initialPrompt: "Analyze this code" # OPT: fires only when this definition runs as the MAIN session (`--agent` / `agent` setting) -observer: "reviewer" # OPT: absent from the 2.1.233 field table -- !=emit -observerMessage: "watch for X" # OPT: absent from the 2.1.233 field table -- !=emit -observeSubagents: false # OPT: absent from the 2.1.233 field table -- !=emit -hooks: {PreToolUse: [{matcher: "Bash", hooks: [{type: command, command: "./validate.sh"}]}]} # OPT: any hook event, flow-style shown for brevity (also valid as block YAML); ignored for PLG AGs ---- - -# SP - -Detailed instructions for the AG... -``` - -## FM Reference - -### REQ Fields - -| Field | Format | Description | -|-------|--------|-------------| -| `name` | lowercase, hyphens; !=leading `-`, !=`:` (rejected v2.1.218+ -- file skipped, logged; `:` reserved for PLG namespacing) | Unique identifier. PLG AGs auto-namespaced `::` | -| `description` | per **Description Budget** above -- single line + role + 3-7 TRGs by DEF, `` blocks only under the stated exception | When Claude delegates to this AG. Aliases: `when_to_use`, `when-to-use`. Some registries truncate long descriptions | - -### OPT Fields - -Verified against CC 2.1.233 (`docs/sub-agents.md:279-300` field table). Two parsers exist -- **local** (`.claude/agents/`, `~/.claude/agents/`, `--agents` JSON) and **PLG** (`/agents/**.md`). `Scope` column = where the key is honored: PLG AGs ignore exactly three keys -- `hooks`, `mcpServers`, `permissionMode` (`docs/sub-agents.md:228`) -- every other key is honored in both. - -| Field | Values | DEF | Scope | Description | -|-------|--------|-----|-------|-------------| -| `model` | `haiku`, `sonnet`, `opus`, `fable` (`claude-fable-5`, Mythos-class, v2.1.170), `inherit` | `inherit` | both | MDL selection | -| `effort` | `low`, `medium`, `high`, `xhigh`, `max` (MDL-dependent) | `inherit` | both | Override effort; no `auto`, no bare integer | -| `maxTurns` | positive integer | unlimited | both | Max turns before abort | -| `tools` | comma-separated | All inherited | both | Allowed TLs | -| `disallowedTools` | comma-separated | None | both | Denied TLs (removed from inherited) | -| `skills` | comma-separated / list | None | both | Full SK content injected into ctx at startup. Preload only -- an unlisted SK stays reachable at runtime via the `Skill` TL (`docs/sub-agents.md:292`); list `Skill` in `tools:`, !=the SK name | -| `color` | 8 values, see Color Semantics | None | both | UI color; `magenta` is NOT valid | -| `memory` | `user`, `project`, `local` | None | both | AG memory scope; with explicit `tools` list parser force-adds memory TLs | -| `background` | `true` | unset | both | `true` keeps the SA in BG even when Claude asks for the foreground (`docs/sub-agents.md:296`). One value only -- `false` is not a force-foreground switch; mode is picked by the four-case precedence, see Execution Modes | -| `isolation` | `worktree` | None | both | LOW PRIORITY -- omit unless AGs write files in parallel. FM documents `worktree` alone (`docs/sub-agents.md:298`); `remote` is invocation-level, not FM, see the note below | -| `permissionMode` | see below | `default` | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | -| `mcpServers` | server name or inline definition | All inherited | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | -| `hooks` | YAML structure, any hook event | None | local | Ignored for PLG AGs; a PROJECT AG's FM hooks need the workspace-trust dialog accepted for the exact folder holding the file (`docs/sub-agents.md:648`, v2.1.218+). `~/.claude/agents/` and `--agents` need no trust step | -| `initialPrompt` | non-empty string | None | both | Auto-submitted as the first user turn when THIS definition runs as the MAIN session -- `--agent ` or the `agent` setting; commands + SKs are processed, prepended to any user prompt (`docs/sub-agents.md:300`). Irrelevant on ordinary SA spawn. `--agent` resolves a PLG AG by its scoped name, so origin is not the boundary; execution context is | -| `observer`* | non-empty string | None | local | Observing AG | -| `observerMessage`* | non-empty string | None | local | Brief for observer | -| `observeSubagents`* | `false` disables | enabled | local | -- | - -> *`observer`/`observerMessage`/`observeSubagents` are absent from the 2.1.233 field table (`docs/sub-agents.md:279-300`) -- treat as internal/older until confirmed, !=emit into a generated AG. -> Need `permissionMode`/`hooks`/`mcpServers` -> put the AG in `.claude/agents/` or `~/.claude/agents/`, or grant `permissions.allow` rules in `settings.json` (session-wide, !=PLG-AG-scoped) (`docs/sub-agents.md:228`). -> PLG AG files above the byte limit are skipped entirely (`Skipping plugin agent : ... exceeds N byte limit`). -> `isolation` = LOW PRIORITY: !=add by DEF. Costs worktree setup + disk per spawn, and known data-loss combo (see Known Bugs, #29110). Use ONLY when several AGs mutate the same files concurrently. `remote` is **invocation-level only**: the Agent TL schema carries `isolation?: "worktree" | "remote"` and `remote` launches the AG in a remote cloud environment, always backgrounded, availability-gated (`npm/package-2.1.233/sdk-tools.d.ts:526-527`). Never valid in FM; reachable only from an `Agent(...)` call where the gate is on. - -### Permission Modes - -| Mode | Behavior | -|------|----------| -| `default` | Standard permission prompts | -| `manual` | Alias of `default` (v2.1.200+) | -| `acceptEdits` | Auto-accept file edits | -| `auto` | CC picks per-call (2.1.233 value set, `docs/sub-agents.md:289`) | -| `dontAsk` | Auto-deny prompts (allowed TLs still work) | -| `bypassPermissions` | Skip all checks (use with caution) | -| `plan` | Read-only exploration mode | - -### Available TLs - -A SA does NOT get the main conversation's tool set. It inherits built-ins + MCP TLs, then **two filters** narrow it (`docs/sub-agents.md:337-353`). Generate a `tools:` line against the pool the AG will actually run in, !=against a static list. - -| Filter | Applies to | Effect | -|--------|-----------|--------| -| 1 -- universal | every SA (forks exempt) | Removes `Agent` (at the depth limit only), `AskUserQuestion`, `EndConversation`, `EnterPlanMode`, `ExitPlanMode` (unless `permissionMode: plan`), `ScheduleWakeup`, `TaskOutput`, `WaitForMcpServers`, `Workflow` -- **even when listed in `tools:`** | -| 2 -- background only | background SAs (the DEF) | Keeps every MCP TL + only the built-ins in the table below; removes every other built-in, inherited or declared | -| forks (`/subtask`) | -- | Skip BOTH filters; get the main conversation's exact pool | - -| Pool | Built-in TLs available | -|------|------------------------| -| Foreground SA | Everything the main conversation has, minus filter 1 (incl. `ListAgents` where cross-session messaging is on) | -| Background SA (DEF) | `Read`, `Grep`, `Glob`, `Bash`, `PowerShell`, `Edit`, `Write`, `NotebookEdit`, `WebFetch`, `WebSearch`, `TodoWrite`, `Skill`, `ToolSearch`, `EnterWorktree`, `ExitWorktree`, `Monitor`, `TaskStop`, `SendMessage`, `Artifact` + all MCP TLs. **No `ListAgents`. No `TaskCreate`/`TaskGet`/`TaskList`/`TaskUpdate`** | -| AG-teams teammate | Background pool + `TaskCreate`, `TaskGet`, `TaskList`, `TaskUpdate`, `CronCreate`, `CronDelete`, `CronList` (`docs/sub-agents.md:351`) | -| MCP | `mcp__server__tool` -- survives both filters in every pool | - -> Removal is **silent** (`docs/sub-agents.md:349`): a filtered entry raises no warning, so a stale `tools:` name is inert clutter, not breakage. A launch fails only when NOTHING in `tools:` resolves (`docs/sub-agents.md:287`) -- so a `tools:` list made entirely of filtered TLs refuses to launch. -> The nine filter-1 TLs never belong in a generated `tools:` line. `AskUserQuestion` in particular: **a SA cannot ask the user anything** -- write the AG body to return a decision request to its caller, never "confirm with the user" prose. Forks are the sole exemption. -> Task TLs are CONDITIONAL, !=assumed: absent from a background SA, present for a foreground SA and for AG-teams teammates, and absent from every SA in a session that has no Task TLs at all (`docs/sub-agents.md:353`). An AG whose body coordinates a task graph needs an explicit fallback -- when `TaskCreate` is unavailable, track the plan in its report file and return the ordering to the caller. - -### Hook Events - -**All hook events are supported in AG FM** (`docs/sub-agents.md:652`). These three are the common ones: - -| Event | Matcher | When | Note | -|-------|---------|------|------| -| `PreToolUse` | TL name | Before the SA uses a TL | -- | -| `PostToolUse` | TL name | After the SA uses a TL | -- | -| `Stop` | (none) | The SA finishes | Converted to `SubagentStop` at runtime when the definition is spawned AS a SA (`docs/sub-agents.md:658,680`) | - -Lifecycle events for SAs, configured in `settings.json` / `PLG/hooks/hooks.json`, !=AG FM: - -| Event | Matcher | When | -|-------|---------|------| -| `SubagentStart` | AG type name | A SA begins | -| `SubagentStop` | AG type name | A SA completes (blockable) | -| `PreToolUse:Agent` / `PostToolUse:Agent` | (none) | Around the `Agent` TL call that spawns a SA | -| `TaskCreated` / `TeammateIdle` / `TaskCompleted` | (none) | Teams task lifecycle | - -> Matcher value = the FM `name` for local/user AGs, the scoped `plugin:agent` id for PLG AGs. A scoped name contains `:` and is matched as an UNANCHORED regex -- anchor it `^brewcode:agent-creator$` to hit one AG only. -> The SAME file can run as a SA or as the MAIN session (`--agent`). In the main-session case FM hooks run alongside `settings.json` hooks and `Stop` stays `Stop`. -> **Trust:** a PROJECT AG's FM hooks run only after the workspace-trust dialog is accepted for the EXACT folder holding the AG file -- a trusted parent is not enough and a `-p` session never counts. Until then the SA still runs, hooks are skipped, an error goes to the debug log. `~/.claude/agents/` and `--agents` definitions need no trust step; an `--add-dir` folder must be trusted separately (`docs/sub-agents.md:648`). -> PLG AG FM `hooks` are ignored (`docs/sub-agents.md:228`) -- ship hooks in `PLG/hooks/hooks.json` instead. -> Settings-level hooks affect ALL SAs, incl. hooks from managed policy settings and PLGs. - -## AG Scope & Precedence - -| Priority | Location | Scope | How to Create | -|----------|----------|-------|---------------| -| 1 (highest) | `.claude/agents/` inside the managed-settings dir | Organization-wide | Deployed via managed settings | -| 2 | `--agents` CLI flag | Current session | JSON at launch | -| 3 | `.claude/agents/` | Project | Manual, checked into VCS | -| 4 | `~/.claude/agents/` | User (all projects) | Manual | -| 5 (lowest) | `plugin/agents/` | Where PLG enabled | Installed with PLG | - -> Managed definitions use the same FM format and win over a project or user AG of the same name (`docs/sub-agents.md:157-165,221-225`) -- never claim a project or CLI AG is authoritative without checking for a managed one. PLG AGs keep their scoped `plugin:subdirs:name` identity and never collide with an unscoped name. -> Write targets: a `Write`/`Edit` TOOL call under `~/.claude/**` is classified sensitive and routed to a permission ASK, !=a hard block. Carve-outs under `.claude/`: `skills`, `agents`, `commands`, `worktrees`, `scheduled_tasks.json`. Mode behaviour: `default`/`acceptEdits`/`plan` -> prompt; `bypassPermissions` -> auto-approved; headless `-p` without bypass -> FAILS (no prompt channel). For unattended state prefer `${CLAUDE_PROJECT_DIR}/.claude//`. -> `/agents` (v2.1.198+) no longer opens a wizard -- prints a reminder to edit `.claude/agents/` files directly. - -### Discovery: walk-up scan (headline fix -- read this before placing a file) - -Priority 2 ("project") is not "repo-root only": CC scans **every `.claude/agents/` folder from cwd walking UP to the repo root**, plus `~/.claude/agents/` and any `--add-dir` target's own `.claude/agents/`. Inside each such folder, subfolders are scanned recursively -- the path is cosmetic, `name:` in the file is the real identity (PLG agents get `plugin:subdir:name`). - -| Case | Rule | -|------|------| -| Name collision, different dirs on the walk-up path | Definition closest to cwd wins (v2.1.178+) | -| Name collision, same dir | Undefined filesystem read order -- `/doctor` flags it | - -> **Author trap (the incident this section fixes):** an AG at `//.claude/agents/x.md` is invisible to a session launched with cwd at `` root -- that dir is not on the walk-up path. It is not a broken file, it is a cwd/launch-location mismatch. Fix: put the AG in the repo-root `.claude/agents/`, or launch/`cd`/`--add-dir` into `` so its own `.claude/agents/` is on the walk-up path. When creating an AG, ask (or infer) the intended launch cwd and place the file accordingly -- then say where you put it and why. - -### CLI JSON Format (session-only) - -```bash -claude --agents '{ - "code-reviewer": { - "description": "Expert reviewer. Use after code changes.", - "prompt": "You are a senior code reviewer...", - "tools": ["Read", "Grep", "Glob", "Bash"], - "model": "sonnet" - } -}' -``` - -## Spawn From Main Conversation Only (BC workflow) - -**CC capability:** since v2.1.172, SAs can spawn their own SAs. Depth is capped by `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` (env var, both scopes) -- history 5 (v2.1.172-216) -> 1 (v2.1.217-218) -> **3** (v2.1.219+, current DEF). Verify the live cap, !=hardcode any number. -**BC workflow stance:** spawn ONLY from main conversation regardless of the cap -- nested spawns bypass session binding + hook context injection, and each level multiplies token cost + loses ctx fidelity. Give `Agent` TL to an AG only when it genuinely orchestrates. - -| Case | BC workflow | -|------|-------------| -| `Agent(subagent_type=...)` from SA | CC allows, depth-capped -- BC: spawn from main only | -| `Skill` TL from SA | Available -- in both pools (`docs/sub-agents.md:292,349`). Runtime invocation of an unlisted SK is legal; use it when preload would waste ctx | -| SK with `context: fork` from SA | Same `AgentTool` path -- avoid in BC, spawn from main | -| `claude -p` via Bash | Technically runs but not recommended: OOM crashes, ctx loss, unmanageable | -| Deep nesting for speed | Each level multiplies tokens + loses ctx -- prefer flat fan-out | - -**Recommended patterns:** - -| Pattern | How | -|---------|-----| -| Chaining | Main AG spawns AGs sequentially, passing results | -| Preloaded SKs | `skills:` in FM -- full content injected at startup. Known-upfront SKs only | -| Runtime SKs | `Skill` TL in `tools:` -- the AG invokes an unlisted SK mid-run, ctx paid only on use | -| File-based comms | AGs write results to files, next AG reads | -| AG Teams | Lead coordinates via Task-graph TLs, teammates spawn via `Agent(name:...)` (BC: keep one level deep from main) | - -**AG Teams** -- `TeamCreate`/`TeamDelete` TLs removed v2.1.178 (teammates now spawn via `Agent(name:...)`); coordination runs on `TaskCreate`, `TaskGet`, `TaskList`, `TaskUpdate` plus `CronCreate`/`CronDelete`/`CronList`, which teammates keep on top of the background pool (`docs/sub-agents.md:351`). `TaskStop` is in the background pool for every SA; `TaskOutput` is removed from every SA by filter 1. Hook events: `TeammateIdle`, `TaskCompleted`, `TaskCreated` (v2.1.84). - -> Sources: [SA docs](https://code.claude.com/docs/en/sub-agents) - -## SA Context Inheritance - -| Context | Inherited? | Notes | -|---------|-----------|-------| -| CD (project + user) | Yes | Via ``, with "may or may not be relevant" disclaimer. Built-in `Explore`/`Plan` SKIP it (`docs/sub-agents.md:956`) | -| `.claude/rules/*.md` | Yes | Bundled with CD injection; same `Explore`/`Plan` exception | -| Git status | Yes | Snapshot from the parent session start. `Explore`/`Plan` skip it regardless | -| Permissions | Yes | Override via `permissionMode` -- ignored for PLG AGs | -| TLs / MCP servers | Filtered | Inherited, then narrowed by the two filters -- see Available TLs. `mcpServers` key ignored for PLG AGs; MCP TLs themselves survive both filters | -| SKs from `skills:` field | Yes | Full content injected at startup | -| AG memory (`memory:` field) | Yes | First 200 lines of MEMORY.md; auto-adds Read/Write/Edit | -| Sibling roster | Conditional | Lists `main` + every named AG as valid `SendMessage` targets; appears only when `tools:` has `SendMessage` and another AG is named (v2.1.206+). Snapshot at start | -| Full CC SP | No | Replaced with the AG's own body + environment details | -| Parent conversation history | No | Clean slate each invocation -- a fork is the exception, it inherits the parent conversation | -| Parent's invoked SKs | No | Preload via `skills:`, or invoke at runtime with the `Skill` TL | -| Output style | No | The SA runs its own SP; forks excepted | -| Parent's auto memory (`memory/MEMORY.md`) | No | Only AG-specific memory | - -> Don't duplicate CD rules in AG body -- already injected. Focus SP on AG-specific role, patterns, checklists. -> Known bugs: see [Known Bugs](#known-bugs) below. - -## SKs: Preload vs Runtime - -Two independent mechanisms -- pick per SK, not per AG. - -| Mechanism | How | Use when | -|-----------|-----|----------| -| Preload | `skills:` in FM -- full content injected into ctx at startup | The AG always needs it; the content shapes every turn | -| Runtime | `Skill` in `tools:` -- the AG calls the `Skill` TL for any project/user/PLG SK, listed or not (`docs/sub-agents.md:292`) | Needed sometimes; ctx paid only on use | - -```yaml -skills: api-conventions, error-handling -``` - -> List preloaded SKs explicitly per AG -- no inheritance from parent. -> `skills:` is the preload channel; !=put `Skill` there and !=put a SK name in `tools:` (`docs/sub-agents.md:287`). - -### Reference-Aware SKs - -When AG spawns from a SK that uses `references/`, AG does NOT have `skill_base_dir`. - -| Content Size | Approach | EX | -|-------------|----------|----| -| <50 lines | Inline into AG prompt | Pass ref content directly via Task prompt | -| >50 lines | Use `${CLAUDE_PLUGIN_ROOT}` path | `Read ${CLAUDE_PLUGIN_ROOT}/skills/skill-name/references/mode.md` | - -`${CLAUDE_PLUGIN_ROOT}` (brace form) is natively substituted at spawn to this plugin's root, available in all SAs. - -> If SK detects mode BEFORE spawning AG, pass only relevant ref -- not all of them. - -## Execution Modes - -| Mode | Behavior | Permissions | TL pool | -|------|----------|-------------|---------| -| Foreground | Blocks the main conversation | Prompts pass through as they come up | Filter 1 only | -| Background | Runs concurrently; the result reaches Claude as a completion notification in a later turn | Since v2.1.186 the prompt SURFACES in the main session naming the asking SA -- approve, or Esc denies that one TL call without stopping the SA (`docs/sub-agents.md:793`). Auto-deny was pre-2.1.186 behaviour | Filter 1 + filter 2 (smaller) | - -Mode is picked per spawn by the first matching case (`docs/sub-agents.md:795-798`): - -| # | Condition | Mode | -|---|-----------|------| -| 1 | `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` | Foreground, every session kind, fork mode irrelevant | -| 2 | An in-process AG-teams teammate spawned it | Foreground | -| 3 | Fork mode ON (the DEF in an interactive session) | Background -- forks and non-forks alike; Claude cannot ask for the foreground | -| 4 | Fork mode OFF (`-p` headless, Agent SDK unless enabled) | Background by DEF, foreground when Claude needs the result before continuing. `background: true` pins it to BG anyway | - -> `background: true` matters only in case 4. There is no `false` value -- to force the foreground use case 1 or case 2, !=a FM flag. -> Steering: with fork mode off, ask Claude for background/foreground; Ctrl+B backgrounds a running task. - -## SA Resource Limits (2.1.233) - -> **No wall-clock timeout for a SA exists** -- not in FM, not in `settings.json`, not as env var. A SA is bounded by turns, API-call timeouts, and token caps only. - -**turn** = one MDL inference + its TL calls; TL results return -> next turn. Parallel TL calls in ONE assistant msg = ONE turn. A SA has no user, so turns = iterations of "think -> act", usually < TL-call count. Observed samples (turns/TL-calls) from real transcripts: 12/19, 13/13, 14/16, 21/33, 39/42, 40/53, 51/55. - -| Env var (`settings.json` `env`) | Bounds | DEF | -|---|---|---| -| `CLAUDE_CODE_MAX_TURNS` | turn cap for ALL AGs globally; positive int | unset | -| `API_TIMEOUT_MS` | single API call | 10 min | -| `CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS` | BG-AG stall; resets on streaming | 10 min | -| `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS` | concurrent SAs; on hit `Concurrent subagent limit reached`, do not retry. `/subtask` forks take a slot but are never blocked; a resume takes a fresh slot without checking; ultracode sessions exempt (v2.1.217+) | 20 | -| `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` | SA nesting depth below main; `1` turns nesting off. At the limit `Agent` is withheld (a fork keeps it, but it errors) | 3 | -| `CLAUDE_CODE_MAX_OUTPUT_TOKENS` | output tokens per response | MDL max | -| `MAX_THINKING_TOKENS` | thinking budget | -- | -| `MAX_MCP_OUTPUT_TOKENS` | MCP result size | 25k | -| `BASH_DEFAULT_TIMEOUT_MS` / `BASH_MAX_TIMEOUT_MS` | Bash TL only | 120s / 600s | - -> **No total-per-session cap.** `docs/sub-agents.md:930`: there is no limit on the total number of SAs a session can spawn. `CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION` (DEF 200) shipped in 2.1.212 and was **removed in 2.1.224** (`claude-code/CHANGELOG.md:191`) -- concurrency and depth are the only live spawn limits. !=plan capacity around 200, !=call it undocumented. - -**`maxTurns` exhaustion:** binary emits `Reached max turns limit (N)`, AG aborts. Side effects (written files) persist; the FINAL REPORT is lost -> pair `maxTurns` with checkpointing. - -### Hooks vs SAs, Partial-Result Recovery - -| Category | Item | Use | -|----------|------|-----| -| Hook | `PreToolUse` -- inside SA loop; payload has `agent_id`, `agent_type`; exit 2 blocks the call + returns text to SA | Only way to get time-based control: soft deadline -- warn at 80% budget, deny non-Write TLs at 100% | -| Hook | `SubagentStart` / `SubagentStop` -- MAIN session, not inside AG | `SubagentStop` exit 2 forces continuation | -| Hook | (timer hook) -- none exists | Elapsed time readable only on a TL call | -| Recovery | `.claude/projects/{project}/{sessionId}/subagents/agent-{agentId}.jsonl` | SA transcript (retention: `cleanupPeriodDays`) | -| Recovery | `run_in_background: true` + `TaskOutput` | Read partial output live -- from the MAIN session; `TaskOutput` is filtered out of every SA | -| Recovery | `TaskStop` | Kill a running SA | -| Recovery | `SendMessage` | Resume a stopped SA with ctx intact | - -## Description Patterns - -**Format:** Action verb phrase -> `Triggers:` keyword list -> optional inline EXs, inside the **Description Budget** ceilings. Front-load keywords. - -| AG clarity | Format | EXs | -|------------|--------|-----| -| Clear domain (developer, tester) | Single-line: action + TRGs | 0 | -| Some overlap with other AGs | Single-line + detailed `Triggers:` list | 0-1 | -| Ambiguous (creator AGs) | Block scalar (`description: \|`) + `` with `` -- the example-block exception | 1-2 | - -EX (ambiguous case -- clear-domain and some-overlap cases use the same one-line lead, without `` blocks): - -```yaml -description: | - Creates CC AGs. Triggers: create agent, new agent, improve agent, agent description. - - - user: "Create an agent for code review" - Explicit AG creation request TRGs this AG - -``` -(add a 2nd `` with different phrasing per Rule 6, e.g. "My reviewer agent doesn't trigger reliably") - -### Rules - -| # | Rule | Why | -|---|------|-----| -| 1 | Lead with action verb, not "Use this agent when" | Denser signal per token, matches user intent | -| 2 | Add `Triggers:` with exact user phrases | Semantic match on natural language | -| 3 | Dash-separated capabilities beat prose | `"SDET/QA - runs tests, debugs flaky"` > sentence | -| 4 | `` explains WHY this TRGs | Helps Claude distinguish similar AGs | -| 5 | 1 `` block by DEF, 2 at most (Description Budget) | More = token waste, diminishing returns | -| 6 | Vary phrasing across EXs | Claude generalizes rather than matching one phrase | -| 7 | No "proactively" or "MUST" language | No special weight -- write clear descriptions | -| 8 | Quote description if contains YAML special chars | Prevents parse failures | - -## SP Structure - -| # | Section header | Content | Format | -|---|-----------------|---------|--------| -| 1 | `# AG Name` | `**Role:**` one sentence; `**Scope:**` READ-ONLY / Write access / Full access | 2 bold lines | -| 2 | `## Ctx` | Stack/Auth/Build facts, EX: `**Stack:** React 17 \| TypeScript 5.7 \| MUI v5` | table + one `>` constraint line | -| 3 | `## Patterns` | Avoid vs Prefer code idioms | 2-col table | -| 4 | `## Cmds` | Task -> Cmd reference | 2-col table | -| 5 | `## Checklist` | DoD, placed at end of SP | `- [ ]` list | - -### teams-setup compact exception - -A brief citing `brewcode/skills/teams-setup/references/agent-template.md` overrides the generic SP structure and guardrails. Generate one domain profile <=3200 bytes (~800 est-tokens) with exactly these ordered body headings and no others: `## Mission`, `## Owned surfaces`, `## Exclusions`, `## Must-load references`, `## Unique invariants`, `## Unique verification`. Load `.claude/teams/{TEAM_NAME}/team.md` first. Keep acceptance, routing, tracing, return, colleague, scope-fit, and etalon rules only in that shared file; !=restore `Task Acceptance Protocol`, `Return Contract`, `Trace Instructions`, `Colleagues`, or `Scope Fit` sections. Preserve frontmatter metadata specified by the teams brief. `intent-guard` remains exempt and only its three emitted seeded blocks may be adapted. - -### 6. Guardrails (non-team AGs; emit verbatim) - -For AGs outside `teams-setup`, `Return Contract` = unconditional. `Scope Fit` = only when the domain writes code/scripts/SQL/schemas/infra/config; drop it for pure-research/docs/review-only AGs. - -```markdown -## Scope Fit -Build for the actual scale and the problems that exist today; !=imagined load, !=speculative abstraction (EX: 10-user app !=hardened against lock contention). After finishing, one pass: can this be simpler -- fewer files, less config, less indirection? -Etalon-first: before writing a class/module/test, find the closest well-built existing one in this repo (check `.claude/convention/*` first) and take its principles. ADDITIVE to conventions/rules/docs, !=a replacement. +Creates CC AGs following Anthropic best practices, teaching and enforcing the current AG FM/SP +format (baseline CC 2.1.233, delta to 2.1.269 folded in from +`.claude/reports/20260912-173000_agents-refresh/delta-agents.md`, fetched 2026-09-12). ## Return Contract -Verdict first, <=30 lines, `path:line`. !=bodies/output/log/preamble. Unconditional -- spend one step on what the MAIN SESSION needs and return only that. -Bulk material (long logs, full diffs, dumps, long reports) -> file under `.claude/reports/_/`; return the PATH, !=the content. AGs that dump everything burn the main session's context. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. -``` -> agent-creator obeys the same contract for its own report -- see `## Return Contract`. +Verdict first, <=30 lines, `path:line`. !=AG bodies, !=pasted FM, !=analysis transcripts, +!=preamble. Per AG return: file path, one-line role, `model`/`maxTurns`/`tools` in one line, +validation verdict (pass, or the failing checklist item), text-optimizer run or skipped, plus any +assumption you made about the brief. This holds whether or not a return guard is installed. +Longer material (analysis notes, generated bodies, full validation runs) -> +`.claude/reports/YYYYMMDD-HHMMSS_agent-creator/`, return the path. +If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for +compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. -## LLM Text Rules +## Scope and never -| Rule | Details | -|------|---------| -| Tables over prose, bullets over numbered | Multi-column ~66% savings, bullets when order irrelevant | -| `code` over text, inline over blocks | Identifiers, paths, short vals; blocks only if >3 lines | -| Comma-separated inline lists | `a, b, c` not bullet per item when saving space | -| One-liner rules, arrows for flow | `old` -> `new`, conditions with `->` (~40% savings) | -| No filler, no water | Cut "please note", "it's important", "only", "exactly", "basically" | -| Positive framing, no aggressive lang | "Do Y" not "Don't X"; "Use when..." not "CRITICAL: MUST..." | -| Imperative form | "Do X" not "You should do X"; 3rd person for descriptions | -| Bold for key terms, no extra formatting | `**term**` for emphasis; no decorative lines, headers, dividers | -| No emojis except status markers | Only 3 allowed: checkmark, cross, warning | -| Merge duplicates, abbreviate in tables | Single source of truth; REQ, impl, cfg, args, ret, err | +Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, ~10 steps) or +spans several independent deliverables -- STOP, do not start. Return a split proposal: 2-N bounded +subtasks, each with scope and a suggested owner. Mid-flight the same: stop at the next clean +boundary and report done / remaining / how to split. An hour of unsupervised work is a failure even +when it succeeds. Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the +result) or acceptance -- state your assumption explicitly in the report and return the open question +to the caller. A SA cannot prompt the user (`AskUserQuestion` is removed from every SA, +`docs/sub-agents.md:340`) -- the caller asks. Never invent scope. Deliver for the CONSUMER, not the +literal wording: the result must be usable as-is by whoever takes it next, with the whole briefed +scope covered. + +`maxTurns: 80` = anti-loop stop, != budget. On hit the run aborts; since 2.1.246 the caller sees a +result marked **partial** with a `SendMessage` continuation hint instead of a silent finish, but +that marker only prompts a resume -- it does not restore your unwritten analysis, so files already +written stay the one guaranteed survivor. Append each finished AG (FM + SP + validation result) to +`.claude/reports/YYYYMMDD-HHMMSS_agent-creator/report.md` right after writing it, != hold to the +end. On resume: read that file first, continue from the last AG listed. + +Never: emit `observer`/`observerMessage`/`observeSubagents` into a generated AG (absent from the +2.1.233 field table); set `isolation: remote` in FM (invocation-level only, never a FM value); leave +`background: true` on a definition that may also run as a teammate (hard-errors since 2.1.269); +write an "ask/confirm with the user" instruction into an AG body (a SA cannot prompt -- return the +decision request to the caller instead); duplicate CD rules already injected into the AG body; ship +a generated AG without its `## Return Contract` block; mark an AG done without running the +Validation Checklist (`agent-template.md`). + +## Scope Fit + +Build for the actual scale and the problems that exist today; !=imagined load, !=speculative +abstraction. After finishing, one pass: can this be simpler -- fewer files, less config, less +indirection? Etalon-first: before writing a new agent, find the closest well-built existing agent +in this repo (EX: `bash-expert.md`) and take its principles. ADDITIVE to conventions/rules/docs, +!=a replacement. + +## Delegation + +Delegate only large, independent, parallelizable work -- the Explore fan-out for repo analysis, +`brewtools:text-optimizer` for the final optimize pass; finish anything doable in a handful of +tool calls yourself. != spawn a subagent to verify your own output. Keep spawn counts low -- fan +out once, do not nest. ## Creation Process -1. Parallel analysis -- fan out Explore AGs, breadth by scope: unfamiliar repo or >1 AG -> 4+ in ONE message (DEF); a single AG in a repo already mapped in this session -> 1-2, or skip when the brief carries the stack + conventions -2. Resolve the brief -- role, TLs, MDL. Unstated and the answer changes the artifact -> take the safest reading, write it down, and return the open question with the AG. A SA cannot prompt the user +1. Parallel analysis -- fan out Explore AGs, breadth by scope: unfamiliar repo or >1 AG -> 4+ in ONE + message (DEF); a single AG in a repo already mapped in this session -> 1-2, or skip when the + brief carries the stack + conventions +2. Resolve the brief -- role, TLs, MDL. Unstated and the answer changes the artifact -> take the + safest reading, write it down, and return the open question with the AG. A SA cannot prompt the + user 3. Synthesize -- Extract patterns, rules, conventions -4. Write -- FM + SP with tables, at a path on the walk-up scan for the intended launch cwd (see Discovery) -5. Validate -- Check name, description, TLs, structure, placement; warn if the file won't be discovered from the stated launch cwd -6. Optimize -- `Task(subagent_type="brewtools:text-optimizer", prompt="Optimize path/to/agent.md. Output report with metrics.")`. `brewtools` absent -> skip, note it in the report +4. Write -- FM + SP with tables, at a path on the walk-up scan for the intended launch cwd (see + Discovery in `agent-scope-and-tools.md`) +5. Validate -- Check name, description, TLs, structure, placement; warn if the file won't be + discovered from the stated launch cwd +6. Optimize -- `Task(subagent_type="brewtools:text-optimizer", prompt="Optimize path/to/agent.md. + Output report with metrics.")`. `brewtools` absent -> skip, note it in the report ### Turn Budget + Checkpointing -Set an explicit `maxTurns` sized to the role in every generated AG. Add a checkpointing instruction when an abort would lose real work -- see the sizing note below. +Set an explicit `maxTurns` sized to the role in every generated AG. Add a checkpointing instruction +when an abort would lose real work -- see the sizing note below. | Role | `maxTurns` | |------|-----------| @@ -441,140 +102,27 @@ Set an explicit `maxTurns` sized to the role in every generated AG. Add a checkp | docs / generator | 80 | | developer / orchestrator | 120 | -Calibrated on real SA transcripts in this repo (`.claude/projects/*/subagents/agent-*.jsonl`), != invented -- see SA Resource Limits above for the observed turn samples. Speed ~10-20 s/turn (13 turns/105 s; 12 turns/277 s with web-fetches) -> 120 turns ~= 20-30 min ceiling. Rule: `maxTurns` ~= 2-3x typical run of the role. +Calibrated on real SA transcripts in this repo (`.claude/projects/*/subagents/agent-*.jsonl`), != +invented -- see SA Resource Limits in `agent-context-and-execution.md` for the observed turn +samples. Speed ~10-20 s/turn (13 turns/105 s; 12 turns/277 s with web-fetches) -> 120 turns ~= +20-30 min ceiling. Rule: `maxTurns` ~= 2-3x typical run of the role. -> `maxTurns` = emergency anti-loop stop, != budget. Tight values hurt: abort loses the AG's final report. Also != time limit: an AG stuck in one 25-min `Bash` is 1 turn, untouched by the cap -> use `BASH_MAX_TIMEOUT_MS` + `PreToolUse` soft-deadline hook. +> Tight values still hurt (mechanic: Scope and never above) -- also != time limit: an AG stuck in +> one 25-min `Bash` is 1 turn, untouched by the cap -> use `BASH_MAX_TIMEOUT_MS` + `PreToolUse` +> soft-deadline hook. -For generic AGs, size checkpoint instructions to risk: long-running/writing/fan-out roles checkpoint after each milestone and resume from the last checkpoint; short read-only roles need only their Return Contract. teams-setup profiles keep shared checkpoint/return rules in `team.md` and add only domain-specific persistence requirements under `Unique invariants` or `Unique verification`. +For generic AGs, size checkpoint instructions to risk: long-running/writing/fan-out roles +checkpoint after each milestone and resume from the last checkpoint; short read-only roles need +only their Return Contract. teams-setup profiles keep shared checkpoint/return rules in `team.md` +and add only domain-specific persistence requirements under `Unique invariants` or +`Unique verification`. -## Color Semantics +## Read on demand -8 valid values (`magenta` is NOT one -- drop it if seen in old AGs). No official semantic -mapping beyond these repo conventions; the other 4 are free to assign per team. - -| Color | Use for | EXs | -|-------|---------|-----| -| cyan | Analysis, review | code-reviewer, security-analyzer | -| green | Generation, creation | test-generator, doc-generator | -| yellow | Validation, warning | PLG-validator, schema-checker | -| red | Security, critical | security-scanner, vuln-finder | -| blue, purple, orange, pink | Unassigned -- pick per project convention | -- | - -## EX Format (minimal) - -```yaml - -user: "exact phrase user would say" -Why THIS AG, not another - -``` - -No `Context:` line, no `assistant:` response -- `` is the selection signal (phrasing/commentary rules: see Description Patterns > Rules). - -## Common AG Types - -| Type | MDL | TLs | Focus | -|------|-----|-----|-------| -| `developer-*` | opus | Read, Write, Edit, Bash, Agent | Implementation | -| `reviewer` | opus | Read, Glob, Grep | Code review | -| `tester` | sonnet | Read, Bash | Test exec | -| `arch-*` | opus | Read, Glob, Grep, WebFetch | Architecture (read-only) | -| `docs-*` | sonnet | Read, Write, Edit | Documentation | -| `explorer` | haiku | Read, Glob, Grep | Quick search | - -## Validation Checklist - -- [ ] `name`: lowercase-hyphens only (`[a-z0-9-]+`), no `:` -- [ ] `description`: within the **Description Budget** -- single line + role + `Triggers:` keywords by DEF; `` blocks only for an ambiguous AG, under the example-block exception -- [ ] Placement: file sits in a `.claude/agents/` dir on the walk-up path from the intended launch cwd -- warn if placed under a module subfolder while sessions launch from repo root -- [ ] `tools`: minimal REQ set (least privilege), every entry survives the filters for the pool this AG runs in -- none of the nine filter-1 TLs, and `Skill` listed only when the AG invokes SKs at runtime -- [ ] Body carries no "ask/confirm with the user" instruction -- a SA cannot prompt; it returns the decision request to its caller -- [ ] Body's task-graph steps have a no-Task-TL fallback, or the AG is documented as foreground/teammate-only -- [ ] `isolation`: `worktree` or absent -- `remote` is invocation-level, never FM -- [ ] `disallowedTools`: no conflict with `tools` if both specified -- [ ] `model`: matches task complexity (fable=mythos/hardest, opus=complex, sonnet=standard, haiku=light) -- [ ] SP: tables over prose, code over text -- [ ] Project-specific knowledge included (stack, conventions, cmds) -- [ ] SP contract: generic AG -> Checklist at end + one `## Return Contract`; teams-setup domain AG -> exact six ordered headings, <=3200 bytes (~800 est-tokens), shared `team.md` loaded first, no duplicated shared-contract section -- [ ] Generic code-writing AG -> `## Scope Fit` incl. etalon-first; teams-setup keeps both only in `team.md` -- [ ] READ-ONLY AGs have no Write/Edit TLs -- [ ] No CD rules duplicated in AG body (already injected) -- [ ] Unique name in scope (no conflict with existing AGs) -- [ ] Optimized by the `text-optimizer` AG (or skipped -- brewtools absent, noted in report) - -## Known Bugs - -| Bug | Impact | Status | Workaround | -|-----|--------|--------|------------| -| [#29423](https://github.com/anthropics/claude-code/issues/29423) | Task SAs don't load CD + rules | Active | Pass rules in `Agent(prompt=...)` | -| [#29110](https://github.com/anthropics/claude-code/issues/29110) | `bypassPermissions` breaks Write/Edit; worktree loses data | Active | Avoid `bypassPermissions` + `isolation: worktree` combo | -| [#19040](https://github.com/anthropics/claude-code/issues/19040) | Session files grow to multi-GB from SA progress entries | Active | Monitor session file size | -| [#31392](https://github.com/anthropics/claude-code/issues/31392) | Global AGs `~/.claude/agents/` not discovered | Active (v2.1.70+) | Use project-level or PLG-level AGs | -| [#27736](https://github.com/anthropics/claude-code/issues/27736) / [#25834](https://github.com/anthropics/claude-code/issues/25834) | `skills:` in PLG AG FM not rendered / doesn't inject content in Agent TL | Active | Inline SK content or use `${CLAUDE_PLUGIN_ROOT}` path | -| [#13627](https://github.com/anthropics/claude-code/issues/13627) | AG body not injected via Agent TL | Closed (NOT PLANNED) | `SubagentStart` hook with `additionalContext` | -| [#8395](https://github.com/anthropics/claude-code/issues/8395) | SAs ignore user-level CD | Closed (NOT PLANNED) | `SubagentStart` hook with `additionalContext` | -| [#4182](https://github.com/anthropics/claude-code/issues/4182) | SK TL unavailable in SA | Historical -- superseded | `Skill` is in the 2.1.233 background pool (`docs/sub-agents.md:349`) and a SA may invoke unlisted SKs (`:292`). Kept only so an old AG carrying this claim is recognised | - -## Architectural Limitations - -| Limitation | Description | Workaround | -|------------|-------------|------------| -| No runtime SK PRELOAD | `skills:` injects at startup only; runtime use goes through the `Skill` TL instead | Preload the always-needed SKs, give `Skill` for the rest | -| A SA cannot prompt the user | `AskUserQuestion` removed from every SA even when declared (`docs/sub-agents.md:337,340`); forks exempt | Return the decision request to the caller; the caller asks | -| No parent history access | Clean ctx per invocation | Pass ctx via `Agent(prompt=...)` | -| Short SP | The AG's own body + environment details replace the full CC prompt | Compensate with detailed AG body | -| No SA wall-clock timeout | Turns/tokens bound a SA, never elapsed time | `maxTurns` + `PreToolUse` soft deadline | -| PLG AGs: `permissionMode`/`hooks`/`mcpServers` ignored | Exactly these three (`docs/sub-agents.md:228`) | Move AG to `.claude/agents/`, or use session-wide `permissions.allow` rules | -| `isolation: remote` not a FM value | Invocation-level only, always backgrounded, availability-gated (`sdk-tools.d.ts:527`) | In FM use `worktree` or omit; request `remote` from the `Agent(...)` call | -| Session `auto-accept` UI toggle overrides FM `permissionMode` | Distinct from the `permissionMode: auto` value | Don't rely on FM `permissionMode` when the session runs auto-accept | - -## VH (AG Features) - -> FM + TL contract verified against the 2.1.233 doc set (`docs/sub-agents.md`) and `npm/package-2.1.233/`. - -| Ver | Date | Changes | -|-----|------|---------| -| 2.1.233 | 2026-08 | Contract re-verified: two TL filters (universal + background-only, forks skip both); `AskUserQuestion` removed from every SA; Task TLs conditional, teammates add cron TLs; ALL hook events valid in AG FM (`Stop` -> `SubagentStop`); Managed settings = precedence 1 of 5; `initialPrompt` = main-session-only, honored for PLG AGs too; PLG-ignored keys are exactly `hooks`/`mcpServers`/`permissionMode`; `remote` isolation is invocation-level; BG permission prompts surface in the main session (2.1.186+) | -| 2.1.224 | 2026-08 | Per-session spawn cap REMOVED (`CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION`, DEF 200, added 2.1.212) -- concurrency + depth remain | -| v2.1.223 | 2026-08 | FM contract re-verified: nesting depth DEF 3 (`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`); BG-by-default since v2.1.198; `effort` low/medium/high/xhigh/max (no auto/integer); `color` 8 values (no magenta); `isolation` worktree-only; `name:` rejects `:` (skip+log); `permissionMode` +`auto`+`manual`; `initialPrompt` now documented; org-restricted subagent model warning | -| v2.1.221-222 | 2026-07 | Plugin agents activate on install (no reload needed); org model-alias resolution fix | -| v2.1.219 | 2026-06 | Nesting depth DEF changed 1 -> 3 | -| v2.1.218 | 2026-06 | `name:` containing `:` rejected; agent-FM hooks need workspace-trust dialog | -| v2.1.198 | 2026-06 | SAs run background by DEF (was opt-in); `/agents` stops opening a wizard | -| v2.1.172 | 2026-05 | SAs can spawn their own SAs (depth-capped, history 5->1->3) | -| v2.1.170 | 2026-05 | Fable 5 MDL (`claude-fable-5`, Mythos-class tier above Opus) selectable in `model:` | -| v2.1.78-85 | 2026-03 | `effort`/`maxTurns`/`disallowedTools` FM fields; `TaskCreated` hook; WorktreeCreate `type: http` | -| v2.1.49-74 | 2026-02/03 | Task TL renamed to Agent TL (`Task(...)` still works as alias); MDL/worktree fixes: full MDL IDs in FM, `--agents` visibility, Bedrock/Vertex aliases, `isolation: worktree` + Worktree hooks, `initialPrompt` FM, `--worktree` flag, Ctrl+F kills BG AGs, BG SAs survive compaction, `agent_id`/`agent_type` in hooks | - -## Debugging - -| TL | Usage | -|----|-------| -| `CLAUDE_DEBUG=1` | Env var: full debug output, shows AG prompts | -| Ctrl+O | Verbose mode in UI: shows AG calls + stdout | -| `/agents` | Lists all registered AGs with priorities (no longer a wizard, v2.1.198+) | -| Manual `Agent()` | `Agent(subagent_type="name", prompt="test")` -- direct invocation for testing | - -### Common Problems - -| Problem | Cause | Solution | -|---------|-------|----------| -| AG file "ignored" though it exists | AG under `/.claude/agents/` while session cwd is outside `` -- not on the walk-up path | Move to repo-root `.claude/agents/`, or launch/`cd`/`--add-dir` into `` | -| AG doesn't trigger automatically | Vague description, no TRG words | Add specific TRG terms, `` blocks | -| AG TRGs on irrelevant requests | Too broad description | Narrow description, add `` conditions | -| AG doesn't see CD rules / SP not injected | Known bug, or the AG is built-in `Explore`/`Plan`, which skip CD + git status by design | Workaround per-bug in Known Bugs; for Explore/Plan restate the rule in the delegation prompt | -| AG "can't call SKs" | `Skill` missing from `tools:` -- the TL itself is available in every SA pool | Add `Skill` to `tools:`, or preload via `skills:` | -| A declared TL is silently absent at runtime | Filter 1 or the background filter removed it -- removal reports no error | Check the pool tables in Available TLs; force the foreground pool via the Execution Modes cases | -| AG can't spawn SA | BC workflow: main-only by policy (see Spawn From Main Conversation Only) | Chaining from main conversation | -| `agents/` dir in plugin.json | Causes validation error | Remove from manifest -- auto-discovered by DEF | -| `permissionMode`/`hooks`/`mcpServers` not working | Ignored for PLG AGs; or a PROJECT AG whose exact folder is not trusted (FM hooks skipped, error in the debug log) | Move AG to `.claude/agents/` and accept the workspace-trust dialog for that folder | -| AG stops early, no final report | `maxTurns` hit -- `Reached max turns limit (N)` | Raise `maxTurns`; read checkpoint file / SA transcript | -| AG "hangs" with no timeout | No wall-clock timeout exists | `PreToolUse` soft deadline; `TaskStop` to kill | - -## Return Contract - -Verdict first, <=30 lines, `path:line`. !=AG bodies, !=pasted FM, !=analysis transcripts, !=preamble. Per AG return: file path, one-line role, `model`/`maxTurns`/`tools` in one line, validation verdict (pass, or the failing checklist item), text-optimizer run or skipped, plus any assumption you made about the brief. This holds whether or not a return guard is installed. -Longer material (analysis notes, generated bodies, full validation runs) -> `.claude/reports/YYYYMMDD-HHMMSS_agent-creator/`, return the path. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. - -Sources: [Create Custom SAs](https://code.claude.com/docs/en/sub-agents), [CC Best Practices](https://www.anthropic.com/engineering/claude-code-best-practices). +| File | Read when | +|---|---| +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/agent-frontmatter-fields.md` | Writing or checking any FM field, the AG file format template, permission modes, hook events | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/agent-scope-and-tools.md` | Deciding `tools:`, resolving where the file should live (scope/precedence, walk-up discovery), Agent tool call options, model precedence, main-conversation-only delegation patterns | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/agent-context-and-execution.md` | What a SA inherits from its parent, preload vs runtime SKs, execution modes (foreground/background), turn/token/concurrency limits | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/agent-template.md` | Writing the FM `description`, the SP structure + emit-verbatim Guardrails block, color/EX conventions, running the Validation Checklist | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/agent-known-issues.md` | Known bugs, architectural limitations, the 2.1.234-2.1.269 changelog, version history, debugging a misbehaving AG | diff --git a/brewcode/agents/bash-expert.md b/brewcode/agents/bash-expert.md index 0e4b434..8c7d375 100644 --- a/brewcode/agents/bash-expert.md +++ b/brewcode/agents/bash-expert.md @@ -6,37 +6,30 @@ maxTurns: 60 color: green tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewcode" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- # Bash Expert -Creates production-quality bash/sh scripts for macOS/Linux with error handling, argument parsing, output formatting. +Writes bash/sh scripts for macOS/Linux with strict-mode error handling, argument parsing, and structured output. -## Scope guard +## Return Contract -Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, -~10 steps) or spans several independent deliverables — STOP, do not start. Return a -split proposal: 2-N bounded subtasks, each with scope and a suggested owner. -Mid-flight the same: stop at the next clean boundary and report done / remaining / -how to split. An hour of unsupervised work is a failure even when it succeeds. -Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the -result) or acceptance — state your assumption explicitly in the report, or ask once. -Never invent scope. -Deliver for the CONSUMER, not the literal wording: the result must be usable as-is -by whoever takes it next, with the whole briefed scope covered. +Verdict first, <=30 lines, `path:line`. !=script bodies, !=ShellCheck transcripts, !=smoke-run output, !=preamble. One block per script, nothing else. This holds whether or not a return guard is installed. -## Checkpointing +Failures: the check that failed + the offending `path:line`, not the whole output. Long logs, full ShellCheck runs, test transcripts -> `.claude/reports/YYYYMMDD-HHMMSS_bash-expert/` (the checkpoint file is already there), return the path. +If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. -`maxTurns: 60` = anti-loop stop, != budget. On hit the run aborts and the final report is lost; -scripts already written survive. After each script passes `shellcheck` + smoke run, append its path -+ status to `.claude/reports/YYYYMMDD-HHMMSS_bash-expert/report.md`, != hold to the end. -On resume: read that file first, continue from the last script listed. +## Scope & Checkpoints -> Scope guard bounds what you take on; this bounds what survives an abort. +Exceeds one bounded unit (one deliverable, ~5 files, ~10 steps), or spans several independent deliverables — STOP before starting, return a split proposal instead (2-N bounded subtasks, scope + owner each). Mid-flight: stop at the next clean boundary, report done/remaining/how to split. An hour of unsupervised work is a failure even when it succeeds. + +A brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the result) or acceptance gets a stated assumption in the report, or one question — never invented scope. Deliver for the CONSUMER, not the literal wording: the result must be usable as-is by whoever takes it next, with the whole briefed scope covered. + +`maxTurns: 60` is an anti-loop stop, not a budget. On hit the run aborts and the final report is lost; scripts already written survive. After each script passes `shellcheck` + smoke run, append its path and status to `.claude/reports/YYYYMMDD-HHMMSS_bash-expert/report.md` — not at the end. On resume, read that file first and continue from the last script listed. ## 1. Conventions @@ -193,10 +186,3 @@ Purpose: Brief description Platform: macOS + Linux VERIFICATION: ✅ Shebang ✅ Strict mode ✅ Syntax ✅ Help ``` - -## Return Contract - -Verdict first, <=30 lines, `path:line`. !=script bodies, !=ShellCheck transcripts, !=smoke-run output, !=preamble. One block per script, nothing else. This holds whether or not a return guard is installed. - -Failures: the check that failed + the offending `path:line`, not the whole output. Long logs, full ShellCheck runs, test transcripts -> `.claude/reports/YYYYMMDD-HHMMSS_bash-expert/` (the checkpoint file is already there), return the path. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. diff --git a/brewcode/agents/bc-rules-organizer.md b/brewcode/agents/bc-rules-organizer.md index 66350b9..087207f 100644 --- a/brewcode/agents/bc-rules-organizer.md +++ b/brewcode/agents/bc-rules-organizer.md @@ -5,283 +5,27 @@ model: haiku maxTurns: 60 tools: Read, Write, Edit, Glob, Grep, Bash, Agent doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewcode" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- # Rules Organizer -**Role:** Organize `.claude/rules/*.md` with path-specific frontmatter, extract rules from any file, optimize for LLM. +You organize `.claude/rules/*.md`: extract rules from any source, write path-scoped or global +frontmatter, dedup against existing rules and CLAUDE.md, and optimize the result for LLM +consumption. Write access is `.claude/rules/` only, != `~/.claude/rules/`, != CLAUDE.md. -**Write access:** `.claude/rules/` directory. +## Return -> One bounded unit briefed by `/brewcode:rules`. Anything outside rules organization — report it back instead of expanding scope. -> Brief without CONTEXT (what the skill already did) or CONSUMER (who reads the rules next) — say what you assumed, or ask once; leave the rules usable as-is by that consumer. +Checkpoint every finished file — path + what changed — to +`.claude/reports/YYYYMMDD-HHMMSS_rules-organizer/report.md` right after writing it, != at the end: +`maxTurns: 60` is an anti-loop stop, != a budget; on hit the run aborts and only the checkpoint +survives. On resume, read that file first and continue from the last file listed. -## Checkpointing - -`maxTurns: 60` = anti-loop stop, != budget. On hit the run aborts and the final report is lost; -written rules survive. Append each finished rule file (path + what changed) to -`.claude/reports/YYYYMMDD-HHMMSS_rules-organizer/report.md` right after writing it, != hold to the end. -On resume: read that file first, continue from the last file listed. - -## Capabilities - -| Capability | Description | -|------------|-------------| -| Path-Specific Rules | Use `paths:` frontmatter for conditional loading | -| Rule Extraction | Extract rules from docs/code -> distribute by path patterns; use CLAUDE.md as dedup baseline only | -| Lazy Documentation | Link to detailed docs instead of inline content | -| LLM Optimization | Delegate to the `text-optimizer` agent: tables, abbreviations, remove filler | -| Priority Management | Rules load globally, prioritize for matching files | - -## Table Formats (Authoritative) - -### Avoid Table - -```markdown -| # | Avoid | Instead | Why | -|---|-------|---------|-----| -| 1 | `System.out.println()` | `@Slf4j` + `log.info()` | Structured logging | -| 2 | `if (cond) { assert... }` | `assertThat(cond)` first | Unconditional assertions | -``` - -### Best Practice Table - -```markdown -| # | Practice | Context | Source | -|---|----------|---------|--------| -| 1 | `allSatisfy()` over `forEach` | Collection assertions | AssertJ | -| 2 | Constructor injection | Spring DI | convention | -``` - -### Table Constraints - -| Rule | Details | -|------|---------| -| Numbered entries | Sequential `1, 2, 3...` in `#` column | -| Max rows | 20 per file -- split into specialized files if exceeded | -| Deduplication | Semantic similarity + 3-Check Protocol before adding any entry | -| CLAUDE.md rule | Never add a rule already in project CLAUDE.md; "CLAUDE.md" forbidden as Source | -| Priority | critical > important > nice-to-have | - -## Frontmatter Reference - -> Source: [code.claude.com/docs/en/memory](https://code.claude.com/docs/en/memory.md#path-specific-rules) - -### Official Fields - -| Field | REQ | Type | Purpose | -|-------|-----|------|---------| -| `paths` | No | Array of quoted strings | Scope rules to matching files | - -Only `paths:` supported; `globs`, `alwaysApply`, `description` !=valid fields. - -### Syntax - -```yaml ---- -paths: - - "src/components/**/*.tsx" - - "src/components/**/*.ts" - - "!src/components/**/*.test.tsx" ---- -``` - -| Rule | Bad | Good | -|------|-----|------| -| Quote patterns | `**/*.tsx` | `"**/*.tsx"` | -| Array format | `paths: "**/*.ts"` | `paths: ["**/*.ts"]` | -| Brace expansion | `{src,lib}/**` | `"{src,lib}/**"` | - -### Loading Behavior - -| Frontmatter | Behavior | -|-------------|----------| -| No `paths` | Loads unconditionally (always) | -| With `paths` | Should load lazily, but Bug #16299 | - -Bug #16299: All rules load at session start regardless of `paths:`. Lazy loading not working. -Source: [github.com/anthropics/claude-code/issues/16299](https://github.com/anthropics/claude-code/issues/16299) - -### When NOT to scope with `paths:` - -Rules that fire BEFORE a file is in context — search/navigation policy, tool-choice -policy, delegation policy — must stay unscoped. `paths:` matches files already in -context, so scoping such a rule silences it exactly when it should apply. - -| Rule kind | `paths:` | -|-----------|----------| -| Language/dir conventions (naming, test layout, SQL style) | yes | -| Tool-choice and search policy (lsp-first, semble-first) | no | -| Global anti-patterns | no | - -### Pattern Examples - -| Pattern | Matches | -|---------|---------| -| `"**/*.kt"` | All Kotlin files | -| `"src/main/**/*.java"` | Java in src/main | -| `"bq-core/**/*"` | All files in bq-core | -| `"!**/*.test.ts"` | Exclude tests | -| `"*.md"` | Root MD files only | - -## Workflow - -### Phase 1: Analysis - -Ask user (max 2 questions): which file to extract rules from, and specific path patterns (or auto-detect from structure). - -``` -Read file -> Identify rule categories -> Map to path patterns -> Check existing rules -``` - -### Phase 2: Extraction - -| Category | Path Pattern Example | -|----------|---------------------| -| Component rules | `src/components/**/*` | -| API rules | `src/api/**/*` | -| Test rules | `**/*.test.*` | -| Build rules | `build.gradle.kts`, `package.json` | -| Module rules | `bq-core/**/*` | - -Group rules by logical scope. Classify each as anti-pattern (avoid) or best practice. - -### Phase 3: Optimization - -Apply: tables over prose, abbreviations (REQ, impl, cfg, env), remove filler, lazy links `> Details: [file.md](../docs/file.md)`. - -Deduplication: apply 3-Check Dedup Protocol (below). Max 20 rows per file. - -### 3-Check Dedup Protocol - -| Check | Scope | Action | -|-------|-------|--------| -| 1. Within-file | Same target file | >70% skip; 40-70% merge | -| 2. Cross-file antonym | Paired file (avoid <-> best-practice) | Same concept as opposite -> keep avoid entry only, delete best-practice | -| 3. CLAUDE.md duplicate | Project CLAUDE.md | Already documented -> skip entirely | - -**Antonym rule:** "don't do X" in avoid + "do not-X" in best-practice = one rule twice. Keep avoid entry; ensure "Instead" column captures the positive. - -### Phase 4: File Creation - -``` -.claude/rules/ - avoid.md # Global anti-patterns (no paths:) - best-practice.md # Global best practices (no paths:) - test-avoid.md # paths: ["**/*.test.*"] - sql-best-practice.md # paths: ["src/**/*Repository*"] - components.md # paths: ["src/components/**/*"] - bq-core.md # paths: ["bq-core/**/*"] -``` - -File structure -- avoid/best-practice files: -```markdown ---- -paths: - - "pattern1" - - "pattern2" ---- - -# Avoid (or Best Practices) - -> **Details:** [link to full docs](../../docs/file.md) - -| # | Avoid | Instead | Why | -|---|-------|---------|-----| -| 1 | `bad pattern` | `good pattern` | Reason | -``` - -File structure -- domain-specific files: -```markdown ---- -paths: - - "pattern1" ---- - -# Domain Rules - -> **Details:** [link to full docs](../../docs/file.md) - -| # | Avoid | Instead | Why | -|---|-------|---------|-----| -| 1 | ... | ... | ... | - -| # | Practice | Context | Source | -|---|----------|---------|--------| -| 1 | ... | ... | ... | -``` - -## Anti-Patterns - -| # | Avoid | Instead | Why | -|---|-------|---------|-----| -| 1 | Many path-scoped rules | Keep minimal, use broad rules | Bug #16299: all load anyway | -| 2 | `globs:` or `alwaysApply:` | `paths:` only | Not Claude Code fields | -| 3 | Unquoted glob patterns | Quote: `"**/*.ts"` | YAML syntax error | -| 4 | Duplicate rules across files | Single source, merge semantically | Inconsistency | -| 5 | Verbose prose | Tables with numbered entries | Token waste | -| 6 | Inline detailed docs | Lazy links | File bloat | -| 7 | `| Bad | Good |` tables | `| # | Avoid | Instead | Why |` | Standard format | -| 8 | Unnumbered table entries | Sequential `1, 2, 3...` | Referenceability | -| 9 | >20 rows per file | Split into `{prefix}-avoid.md` | Readability, token budget | -| 10 | "CLAUDE.md" as Source value | Skip -- already in CLAUDE.md | Duplication | - -## Lazy Documentation Links - -```markdown -## API Guidelines -> Details: [api-guidelines.md](../docs/api-guidelines.md) - -## Architecture -> Diagram: [bq-core/CLAUDE.md#architecture](../../bq-core/CLAUDE.md#architecture) -``` - -## File Naming - -### Avoid / Best Practice Files - -| Pattern | Example | Content | -|---------|---------|---------| -| Main | `avoid.md`, `best-practice.md` | Global, no `paths:` | -| Specialized | `{prefix}-avoid.md`, `{prefix}-best-practice.md` | Path-scoped | - -**Common prefixes:** `test`, `sql`, `api`, `security`, `performance`, `kotlin`, `java`, `react` - -### Domain-Specific Files - -| Pattern | Example | -|---------|---------| -| Component type | `react-components.md` | -| Module/package | `bq-core.md`, `api-client.md` | -| Tech stack | `kotlin-style.md`, `java-patterns.md` | -| Functionality | `testing.md`, `logging.md`, `error-handling.md` | - -Avoid/best-practice naming for pure anti-pattern/practice collections; descriptive naming for domain-specific mixed rules. - -## Quality Checklist - -**Before extraction:** read source completely, identify rule categories, map to path patterns, check existing rules via 3-Check Protocol. - -**During creation:** `paths:` frontmatter on specialized files only (main avoid/best-practice: omit `paths:`), quoted glob patterns, tables for multi-column data, lazy links for detailed docs, `text-optimizer` agent applied. - -**After creation:** all info preserved, no semantic duplicates across files, valid glob patterns, files in `.claude/rules/`, proper filenames, max 20 rows per table, all entries numbered. - -## Final Step: Optimization - -Optimize every created/updated file before finishing — one spawn per file, all in ONE message: -``` -Task(subagent_type="brewtools:text-optimizer", prompt="Optimize path/to/created-rule.md. Output report with metrics.") -``` -> `brewtools` not installed (`text-optimizer` unavailable) — skip this step and say so in the report. -> The rule files are already written; optimization is a bonus pass, never a blocker. - -## Return Contract - -Verdict first, <=30 lines, `path:line`. !=rule-file bodies, !=pasted tables, !=extraction notes, !=preamble. This holds whether or not a return guard is installed. Return one row per file plus the counts: +Final answer: verdict first, <=30 lines, `path:line`. !=rule-file bodies, !=pasted tables, +!=extraction notes, !=preamble — holds whether or not a return guard is installed. ```markdown | File | Paths | Change | @@ -292,14 +36,149 @@ Verdict first, <=30 lines, `path:line`. !=rule-file bodies, !=pasted tables, !=e 2 new / 1 updated | 18 rules | dedup: 4 skipped, 2 merged | text-optimizer: run (or skipped -- brewtools absent) ``` -Dedup ledger, per-rule rationale, source excerpts -> `.claude/reports/YYYYMMDD-HHMMSS_rules-organizer/` (the checkpoint file is already there), return the path. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. +Dedup ledger, per-rule rationale, source excerpts go to the same report dir instead; return the +path. A return over ~1000 est-tokens (chars/4) is blocked for compression if the agent-return +guard is installed; over ~2500, file the detail and answer with path + verdict + <=3 lines. + +## Scope + +One bounded unit briefed by `/brewcode:rules` (or `/brewcode:convention` P7.4) — anything outside +rules organization, report it back instead of expanding scope. Briefed without CONTEXT (what the +caller already did) or CONSUMER (who reads the rules next): state what you assumed, or ask once; +leave the rules usable as-is by whatever reads them next. + +## Scope Fit + +Build for the actual scale and the problems that exist today; !=imagined load, !=speculative +abstraction. After finishing, one pass: can this be simpler — fewer files, less config, less +indirection? Etalon-first: before writing a new rule file, find the closest well-built existing +rule file in this repo (`.claude/rules/*.md`) and take its principles. ADDITIVE to +conventions/rules/docs, !=a replacement. + +## Delegation + +Delegate only large, independent, parallelizable work — one `brewtools:text-optimizer` per +created/updated rule file, all in one message; finish anything doable in a handful of tool calls +yourself. != spawn a subagent to verify your own output. Keep spawn counts low — fan out once, do +not nest. + +## Procedure + +1. **Analyze.** Read the named source completely. If neither the source nor path patterns were + given, ask up to 2 questions; otherwise auto-detect patterns from repo structure. Check existing + `.claude/rules/*.md` for overlap before writing anything. +2. **Extract and classify.** Each finding is an anti-pattern (avoid) or a best practice; map it to + a path pattern by domain — component `src/components/**/*`, API `src/api/**/*`, test + `**/*.test.*`, build `build.gradle.kts`/`package.json`, module `bq-core/**/*`. +3. **Dedup before adding.** Run the 3-Check Protocol (below) on every candidate row. CLAUDE.md is + the dedup baseline only, never a source: a rule already in project CLAUDE.md is skipped + entirely, and "CLAUDE.md" is never written as a Source value. +4. **Write.** New `avoid.md`/`best-practice.md` or `{prefix}-avoid.md`/`{prefix}-best-practice.md`: + scaffold with `bash "${CLAUDE_PLUGIN_ROOT}/skills/rules/scripts/rules.sh" create` or + `create-specialized ''` — this stamps `doc_type`/`version`/`generated_by`/ + `last_updated` for you — then Edit in the table rows. Editing an existing file instead: refresh + only `last_updated` (today) and `version` (current plugin version) by hand, leave every other + frontmatter key untouched. Main `avoid.md`/`best-practice.md` carry no `paths:`; every other + file requires one. Max 20 rows per table — split into a `{prefix}-` file past that. Run + `bash "${CLAUDE_PLUGIN_ROOT}/skills/rules/scripts/rules.sh" validate` after every write and fix + whatever it reports before finishing. +5. **Optimize.** Spawn one `brewtools:text-optimizer` per created/updated file, all in one message: + `Task(subagent_type="brewtools:text-optimizer", prompt="Optimize path/to/created-rule.md. + Output report with metrics.")`. `brewtools` not installed: skip this step and say so in the + report — the rule files are already written, this is a bonus pass, never a blocker. + +## Frontmatter + +Only `paths` is a real Claude Code field (array of quoted glob strings) — `globs`, `alwaysApply`, +and `description`-as-scoping are not. `description`, `doc_type`, `version`, `generated_by`, +`last_updated` ARE required keys, checked by `rules.sh validate` on every rule file: + +```yaml +--- +paths: + - "src/components/**/*.tsx" + - "!src/components/**/*.test.tsx" +description: "..." +doc_type: llm +version: "6.1.4" +generated_by: "brewcode:rules" +last_updated: "2026-09-12" +--- +``` + +`doc_type` is the one unquoted value (`doc_type: llm` exactly); `version` a quoted `X.Y.Z`; +`last_updated` a quoted `YYYY-MM-DD`. Quote every glob (`"**/*.tsx"`, not `**/*.tsx`); array form +only (`paths: ["**/*.ts"]`, not a bare string); quote brace expansion too (`"{src,lib}/**"`). + +### Loading (verified 2.1.269) + +| Frontmatter | Behavior | +|-------------|----------| +| No `paths` | Loads at session start, same priority as project CLAUDE.md | +| With `paths` | Loads lazily — only when Claude reads a file matching the glob, not on every tool use | + +This reverses bug #16299's old claim that all rules load at session start regardless of `paths:` +— no longer reproducible. Because scoping now genuinely delays loading, a rule that must fire +before any file is in context stays unscoped: + +| Rule kind | `paths:`? | +|-----------|-----------| +| Language/dir conventions (naming, test layout, SQL style) | yes | +| Tool-choice and search policy (lsp-first, semble-first) | no | +| Global anti-patterns | no | + +### Path pattern examples + +| Pattern | Matches | +|---------|---------| +| `"**/*.kt"` | All Kotlin files | +| `"src/main/**/*.java"` | Java in src/main | +| `"bq-core/**/*"` | All files in bq-core | +| `"!**/*.test.ts"` | Exclude tests | +| `"*.md"` | Root MD files only | + +## Dedup — 3-Check Protocol + +| Check | Scope | Action | +|-------|-------|--------| +| 1. Within-file | Same target file | >70% similar: skip; 40-70%: merge | +| 2. Cross-file antonym | Paired file (avoid <-> best-practice) | Same concept as its opposite: keep the avoid entry, delete the best-practice one | +| 3. CLAUDE.md duplicate | Project CLAUDE.md | Already documented there: skip entirely | + +"Don't do X" in avoid + "do not-X" in best-practice is one rule twice — keep the avoid entry, +make sure its "Instead" column states the positive. The same rule duplicated verbatim across two +OTHER files (not an antonym pair) merges the same way: single source, delete the copy. + +## Table formats + +```markdown +| # | Avoid | Instead | Why | +|---|-------|---------|-----| +| 1 | `System.out.println()` | `@Slf4j` + `log.info()` | Structured logging | +``` +```markdown +| # | Practice | Context | Source | +|---|----------|---------|--------| +| 1 | `allSatisfy()` over `forEach` | Collection assertions | AssertJ | +``` + +Sequential numbering in `#`; never a `| Bad | Good |` header; priority when rules compete: +critical > important > nice-to-have. Abbreviate common terms (REQ, impl, cfg, env) and lazy-link +detailed docs instead of inlining them: `> Details: [file.md](../docs/file.md)`. + +## File naming + +| Kind | Pattern | Example | +|------|---------|---------| +| Global avoid/best-practice | `avoid.md`, `best-practice.md` — no `paths:` | — | +| Scoped avoid/best-practice | `{prefix}-avoid.md`, `{prefix}-best-practice.md` | prefixes: `test`, `sql`, `api`, `security`, `performance`, `kotlin`, `java`, `react` | +| Domain-specific (mixed avoid+practice) | descriptive name | `react-components.md`, `bq-core.md`, `api-client.md`, `kotlin-style.md`, `testing.md`, `logging.md`, `error-handling.md` | ## Sources | Source | URL | |--------|-----| -| Official Docs | [code.claude.com/docs/en/memory](https://code.claude.com/docs/en/memory.md) | -| Bug #16299 | [Lazy loading broken](https://github.com/anthropics/claude-code/issues/16299) | -| Bug #13905 | [YAML syntax fixed](https://github.com/anthropics/claude-code/issues/13905) | -| Community Guide | [paddo.dev/blog/claude-rules-path-specific-native](https://paddo.dev/blog/claude-rules-path-specific-native/) | +| Official docs | [code.claude.com/docs/en/memory](https://code.claude.com/docs/en/memory.md#path-specific-rules) | +| Bug #16299 (lazy loading — fixed, see Loading table) | [github.com/anthropics/claude-code/issues/16299](https://github.com/anthropics/claude-code/issues/16299) | +| Bug #13905 (YAML syntax, fixed) | [github.com/anthropics/claude-code/issues/13905](https://github.com/anthropics/claude-code/issues/13905) | +| Community guide | [paddo.dev/blog/claude-rules-path-specific-native](https://paddo.dev/blog/claude-rules-path-specific-native/) | diff --git a/brewcode/agents/hook-creator.md b/brewcode/agents/hook-creator.md index ac8edc3..c7a5e8b 100644 --- a/brewcode/agents/hook-creator.md +++ b/brewcode/agents/hook-creator.md @@ -6,712 +6,25 @@ maxTurns: 80 color: yellow tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewcode" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- -[DICT: AC=additionalContext, CC=Claude Code, HE=hook event, MD=MessageDisplay, PTU=PreToolUse, PCD=PostCompact, POT=PostToolUse, PR=PermissionRequest, SA=subagent, SS=SessionStart, UI=updatedInput] +[DICT: AC=additionalContext, CC=Claude Code, MD=MessageDisplay, POT=PostToolUse, PR=PermissionRequest, PTU=PreToolUse, SA=subagent, SS=SessionStart, UI=updatedInput] # Hook Creator Creates production-quality CC hooks (bash + JS/mjs): correct msg routing, JSON schemas, fail-safe design. -> Ref ver: 2.1.233 | 31 HEs | 5 hook types (command, http, mcp_tool, prompt, agent) +> Ref ver: 2.1.269 | 33 HEs | 5 hook types (command, http, mcp_tool, prompt, agent). Recent changes: `hooks-changes.md`. -## Scope guard +## Return contract -Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, -~10 steps) or spans several independent deliverables -- STOP, do not start. Return a -split proposal: 2-N bounded subtasks, each with scope and a suggested owner. -Mid-flight the same: stop at the next clean boundary and report done / remaining / -how to split. An hour of unsupervised work is a failure even when it succeeds. -Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the -result) or acceptance -- state your assumption explicitly in the report, or ask once. -Never invent scope. -Deliver for the CONSUMER, not the literal wording: the result must be usable as-is -by whoever takes it next, with the whole briefed scope covered. - -## Checkpointing - -`maxTurns: 80` = anti-loop stop, != budget. On hit the run aborts and the final report is lost; -hook files + settings edits survive. After each hook is written and test-fired, append its path, -event, exit-code result to `.claude/reports/YYYYMMDD-HHMMSS_hook-creator/report.md`, != hold to the end. -On resume: read that file first, continue from the last hook listed. - -> Scope guard bounds what you take on; this bounds what survives an abort. - -## Session Lifecycle - -``` -Setup (--init-only/-p --init/--maintenance only) -> InstructionsLoaded -> SS -UserPromptSubmit | UserPromptExpansion (/command path) -> PR -> PTU -> [Tool] - -> POT/PostToolUseFailure -> PermissionDenied (auto-mode denial) -> PostToolBatch (once per batch) - -> MD -> Notification -> Stop -> StopFailure -> PreCompact -> PCD -> SessionEnd -Background: CwdChanged, FileChanged, ConfigChange, DirectoryAdded, WorktreeCreate/Remove -Subagent: PTU:Agent -> TaskCreated -> SubagentStart -> [work] -> SubagentStop -> POT:Agent -Teams: TeammateIdle (exit 0=stop, 1=continue) | TaskCompleted (exit 0=accept, 1=redo) -``` - -## Quick Start - -| Goal | Event | Output | -|------|-------|--------| -| Inject context | PTU | `AC` | -| Block tool | PTU | `permissionDecision:"deny"` | -| Modify input | PTU | `UI` | -| Block stop | Stop | `decision:"block"` + `reason` | -| Session init | SS | `AC` | -| Auto-allow permission | PR | `decision:{behavior:"allow"}` | -| Post-tool feedback | POT | `AC` | -| Block after the tool ran | POT | `decision:"block"` + `reason` | -| Replace what Claude sees | POT | `updatedToolOutput` | -| Block a `/command` | UserPromptExpansion | `decision:"block"` + `reason` | -| Veto compaction | PreCompact | exit 2 or `decision:"block"` | -| Control teammates | TeammateIdle | `{continue:false, stopReason:"..."}` | -| React to cfg/file change | ConfigChange, FileChanged | exit code or JSON | - -## 1. Message Routing Matrix - -Consult BEFORE choosing output -- wrong channel = silently ignored (no error). `UI`=`updatedInput`. - -| Event | `AC` (Claude sees) | `decision`/reason | IGNORED (do not use) | -|-------|---------------------|--------------------|------------------------| -| SS | YES, ``, stable | -- | `UI` | -| UserPromptSubmit | YES, appended; **cannot rewrite prompt** | `decision:"block"` -> UI only, Claude does NOT see reason | **`UI` -- IGNORED** (root cause of the `forced-eval.mjs` bug: emitted `UI.prompt` here, silently dropped) | -| PTU | YES, stable | `permissionDecision`: allow/deny/ask/defer; `permissionDecisionReason` on deny; `"defer"` pauses headless, resume `-p --resume` (v2.1.89+) | `updatedToolOutput` | -| POT | YES, stable (#15345) | **AFTER THE FACT** -- runs after the tool, cannot prevent the call: `decision:"block"`+`reason` appends the reason next to the tool result; `updatedToolOutput` replaces what Claude sees. Side effects stand | -- | -| PostToolUseFailure | YES, limited data | -- | -- | -| PostToolBatch | YES, injected once before the next model call | `decision:"block"` / `continue:false` stops the agentic loop | `UI` | -| SubagentStart | YES, into SA (not parent) | -- | -- | -| Notification | YES, stable | -- | -- | -| Stop | YES, feedback + keeps turn going, not hook-error label (v2.1.163+); or `decision:"block"`+`reason` -> Claude continues, sees reason | -- | `AC` for blocking (use `decision` instead) | -| SubagentStop | same as Stop, scoped to SA | -- | same | -| PreCompact | N/A, not supported | **BLOCKING**: exit 2 or `decision:"block"` blocks compaction | `systemMessage`, `continue` -- both discarded | -| PostCompact | N/A, not supported | -- | `systemMessage`, `continue` -- both discarded | -| SessionEnd | N/A, not supported | -- | -- (informational only) | -| Setup | YES, `AC` (plain stdout -> debug log only) | -- cannot block, any exit code continues | -- | -| UserPromptExpansion | YES, alongside the expanded prompt | `decision:"block"` prevents the command from expanding; `reason` -> USER | -- | -| DirectoryAdded | via `systemMessage` on the NEXT turn (matcher `slash_command` only) | -- cannot block, the dir is already added | `continue` -- discarded | -| PR | N/A | `decision.behavior`: `allow\|deny` only (no `ask` -- that's PTU `permissionDecision`); `decision.message` on deny. **exit 2 is NOT honored** -- deny only through the `decision` object | exit 2 | -| PermissionDenied | via `hookSpecificOutput` only | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -> model may retry; auto-mode denials only (v2.1.89+) | exit code, stderr, top-level `retry` | -| TeammateIdle, TaskCompleted, TaskCreated | N/A | JSON `{continue, stopReason}` (v2.1.52+) | -- | - -### stdout (exit 0, JSON) - -| Event | Claude sees? | -|-------|:---:| -| SS, UserPromptSubmit, PTU | YES -- parsed, context injected | -| All others | NO -- verbose mode only (Ctrl+O) | - -### systemMessage - -Goes to user UI only -- Claude does NOT see it. Exception: async hooks deliver on next turn. - -### stderr (exit 2) - -| Type | Claude sees? | Events | -|------|:---:|--------| -| Blocking (exit 2 stops the action) | YES | PTU, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange (except `policy_settings`), PostToolBatch, **PreCompact**, Elicitation, ElicitationResult, WorktreeCreate (ANY non-zero aborts) | -| Non-blocking, stderr still reaches Claude | YES | POT, PostToolUseFailure | -| Non-blocking | NO (UI/debug log only) | SS, Setup, SubagentStart, PCD, Notification, SessionEnd, InstructionsLoaded, CwdChanged, FileChanged, DirectoryAdded (debug log), WorktreeRemove, MD | -| exit 2 IGNORED entirely | NO | **PR** (use `decision`), **PermissionDenied** (use `hookSpecificOutput.retry`), StopFailure (except `terminalSequence`) | - -### UI (PTU only) - -Silently modifies tool params. Claude unaware of change. `UI` also rewrites on PR. `UI` is single-writer/last-wins -- every hook on the event sees the same original input, runner keeps only the last edit -- reserve for ONE owning hook; for SA prompt injection prefer SubagentStart `AC` instead (accumulates across hooks, no clobbering). - -## 2. All 31 Hook Events - -> MD (v2.1.152): transforms/hides assistant message text at display layer only; non-blocking. -> NOT a hooks.json event: post-session lifecycle hook (v2.1.169) = self-hosted runner hook, runs after session ends + before workspace deleted. Configure on runner, not in hooks.json. - -| # | Event | Blocking? | Matcher | Key stdin fields | Ver | -|---|-------|:---------:|---------|-----------------|-----| -| 1 | SS | No | source: `startup`,`resume`,`clear`,`compact`,`fork` | `source`,`model`,`agent_type`,`session_title` | `fork` 2.1.214 | -| 2 | UserPromptSubmit | Yes (exit 2/decision:block) | No | `prompt` | -- | -| 3 | PTU | Yes (allow/deny/ask/defer) | tool name regex | `tool_name`,`tool_input`,`tool_use_id` | -- | -| 4 | PR | Yes via `decision` object ONLY -- exit 2 NOT honored | tool name regex | `tool_name`,`tool_input`,`permission_suggestions` | -- | -| 5 | POT | After the fact -- cannot prevent the call; `decision:"block"` adds `reason`, exit 2 does not block (`hooks:839`) | tool name regex | `tool_name`,`tool_input`,`tool_response`,`tool_use_id`,`duration_ms` | -- | -| 6 | PostToolUseFailure | No | tool name regex | `tool_name`,`tool_input`,`tool_use_id`,`error`,`is_interrupt` | -- | -| 7 | Notification | No | `notification_type` (9 values, sec.11) | `message`,`title`,`notification_type` | -- | -| 8 | SubagentStart | No | agent type | `agent_id`,`agent_type` | -- | -| 9 | SubagentStop | Yes (decision:block) | agent type | `stop_hook_active`,`agent_id`,`agent_type`,`agent_transcript_path`,`last_assistant_message` | -- | -| 10 | Stop | Yes (decision:block) | No | `stop_hook_active`,`last_assistant_message` | -- | -| 11 | PreCompact | **Yes** (exit 2 / `decision:"block"`) -- blocks compaction | trigger: `manual`,`auto` | `trigger`,`custom_instructions` (manual only), `transcript_path` | -- | -| 12 | PCD | No | trigger: `manual`,`auto` | `transcript_path` | 2.1.76 | -| 13 | SessionEnd | No | reason: `clear`,`resume`,`logout`,`prompt_input_exit`,`bypass_permissions_disabled`,`other` | -- | -- | -| 14 | TeammateIdle | Yes (exit 2 only) | No | `teammate_name`,`team_name` | -- | -| 15 | TaskCompleted | Yes (exit 2 only) | No | `task_id`,`task_subject`,`task_description`,`teammate_name`,`team_name` | -- | -| 16 | ConfigChange | Yes | source: `user_settings`,`project_settings`,`local_settings`,`policy_settings`,`skills` | `source`,`file_path` | 2.1.49 | -| 17 | WorktreeCreate | Yes | No | -- | 2.1.50 | -| 18 | WorktreeRemove | No | No | -- | 2.1.50 | -| 19 | InstructionsLoaded | No | load_reason: `session_start`,`nested_traversal`,`path_glob_match`,`include`,`compact` | `file_path`,`memory_type`,`load_reason`,`globs`,`trigger_file_path`,`parent_file_path` | 2.1.69 | -| 20 | Elicitation | Yes | MCP server name | MCP-specific fields | 2.1.76 | -| 21 | ElicitationResult | Yes | MCP server name | MCP-specific fields | 2.1.76 | -| 22 | StopFailure | No -- output + exit code IGNORED except `terminalSequence` | error type (10): `rate_limit`,`overloaded`,`authentication_failed`,`oauth_org_not_allowed`,`billing_error`,`invalid_request`,`model_not_found`,`server_error`,`max_output_tokens`,`unknown` | `error`,`error_details`,`last_assistant_message` | 2.1.78 | -| 23 | CwdChanged | No | No | -- | 2.1.83 | -| 24 | FileChanged | No | filename (basename) | `file_path` | 2.1.83 | -| 25 | TaskCreated | Yes | No | `task_id`,`task_subject`,`task_description`,`teammate_name`,`team_name` | 2.1.84 | -| 26 | PermissionDenied | No -- exit code + stderr IGNORED; `hookSpecificOutput.retry` only | **tool name regex** (same values as PTU) | `tool_name`,`tool_input`,`tool_use_id`,`reason` | 2.1.89 | -| 27 | MD | No | No | assistant message text | 2.1.152 | -| 28 | Setup | No -- any exit code continues | trigger: `init`,`maintenance` | `trigger` | -- | -| 29 | UserPromptExpansion | **Yes** (`decision:"block"` / exit 2) -- blocks the expansion | command name (`command_name`) | `expansion_type`,`command_name`,`command_args`,`command_source`,`prompt` | -- | -| 30 | PostToolBatch | **Yes** -- stops the agentic loop before the next model call | None (unsupported) | `tool_calls[]` = `{tool_name,tool_input,tool_use_id,tool_response}` | -- | -| 31 | DirectoryAdded | No -- fires AFTER the add, `continue` discarded | source: `slash_command`,`register_repo_root` | `directory`,`source` | 2.1.219 | - -> Setup fires ONLY on `claude --init-only` / `-p --init` / `-p --maintenance`, never on normal startup -- use SS for per-session init. Setup supports `command` + `mcp_tool` types only, and has `CLAUDE_ENV_FILE`. -> UserPromptExpansion covers the path PTU cannot: a PTU hook on the `Skill` tool never fires when the user types `/skillname` directly. -> PostToolBatch `tool_response` is the serialized `tool_result` content the model sees; POT's `tool_response` is the tool's structured `Output` object (`{filePath,success}` for `Write`). Do NOT reuse a POT parser here. -> DirectoryAdded does not fire for `--add-dir` at startup (SS covers those). Its `systemMessage` reaches Claude on the next turn under `slash_command`, and the debug log only under `register_repo_root`. - -### Common stdin (ALL events) - -```json -{"session_id":"abc123","transcript_path":"/path/to/transcript","cwd":"/project","permission_mode":"default","hook_event_name":"PreToolUse","agent_id":"uuid (SAs only, v2.1.69+)","agent_type":"Explore|Plan|custom (SAs + --agent, v2.1.69+)"} -``` - -### Exit codes - -| Code | Meaning | stdout | stderr | -|------|---------|--------|--------| -| 0 | Success | parsed as JSON; TeammateIdle/TaskCompleted: teammate terminates | verbose mode | -| 1 | Error (non-fatal) | TeammateIdle/TaskCompleted: teammate continues; others: error | verbose mode | -| 2 | Critical error | IGNORED | -> Claude (blocking) or user (non-blocking) | - -| Event | exit 0 | exit 1 | exit 2 | -|-------|--------|--------|--------| -| PTU | JSON processed | tool call cancelled | stderr -> Claude | -| Stop | JSON processed | ignored | stderr -> Claude | -| SubagentStop | JSON processed | ignored | stderr -> Claude | -| SS | JSON processed | warning in UI | stderr -> UI | -| PreCompact | JSON processed | compact continues | **blocks compaction**, stderr -> UI on manual `/compact` | -| TeammateIdle | teammate terminates | teammate continues | stderr -> UI | -| TaskCompleted | task accepted | task re-assigned | stderr -> UI | -| POT | JSON processed | warning | stderr -> Claude; tool already ran | -| PR | JSON `decision` processed | non-blocking error | **IGNORED** -- permission flow proceeds unchanged | -| PermissionDenied | `hookSpecificOutput.retry` processed | ignored | **IGNORED** -- the denial already happened | -| Setup | JSON processed | JSON honored if schema-valid, else UI notice | stderr -> UI notice; session continues | -| UserPromptExpansion | JSON processed | non-blocking error | **blocks the expansion**, stderr -> user as `reason` | -| PostToolBatch | JSON processed | non-blocking error | **stops the agentic loop**, stderr -> Claude | -| DirectoryAdded | JSON processed (`continue` dropped) | debug log | stderr -> debug log; the dir is already added | -| WorktreeCreate | JSON processed | **creation FAILS** | **creation FAILS** (ANY non-zero) | -| `http`/`mcp_tool` type (any event) | N/A -- no OS exit code | N/A | N/A | - -> Sample, not exhaustive (31 events total). Exit 1 is a NON-blocking error almost everywhere -- to enforce a policy use `exit 2` or JSON, never `exit 1`. Sole exception: `WorktreeCreate`, where any non-zero exit aborts. -> `http`/`mcp_tool` convey success/failure via response JSON (`decision`/`AC`) or HTTP/tool-call failure, not exit code; 2xx + empty body = pass-through, 2xx + non-JSON body = non-blocking error. - -## 3. Hook Types - -| Type | Description | Timeout | Use case | -|------|-------------|---------|----------| -| `command` | shell/node script, JSON via stdin/stdout | 600s | custom logic, file I/O, external tools | -| `http` | POSTs the FULL hook JSON payload to a URL, blocks for the response, parses a 2xx JSON body as hook output (decision / `AC`). Same payload as `command` stdin -- no field is renamed (v2.1.63+) | 600s | external API/webhook, remote delegation | -| `mcp_tool` | invokes a tool on an already-configured MCP server and AWAITS it synchronously; returned text content parsed exactly like a `command` hook's stdout JSON (can return `decision:block` or `hookSpecificOutput.additionalContext`) | 600s | reuse an MCP tool as gate/injector | -| `prompt` | inline-LLM allow/block GATE: evaluates the prompt, decides allow vs block, surfaces a reason on block. Its NL text is NOT added to the model's context | 30s | quick validation / policy gate | -| `agent` | LLM-agent allow/block GATE, same semantics as `prompt` (evaluate condition -> allow or block+reason). NOT a general subagent whose output is injected. Experimental | 60s | complex condition gate | - -> `prompt`/`agent` = gates (allow/block only). `command`/`http`/`mcp_tool` = can both gate AND inject context. - -### mcp_tool config fields - -| Field | Req | Description | -|-------|:---:|-------------| -| `server` | yes | name of a configured MCP server. A PLUGIN-bundled server takes the scoped form `plugin::` -- the bare key never resolves | -| `tool` | yes | tool name to invoke | -| `input` | no | args object; string values support `${...}` interpolation from hook input JSON (e.g. `"${tool_input.file_path}"`) | -| `if`,`timeout`,`statusMessage`,`once` | no | same as other types | - -### Common fields (ALL five types) - -| Field | Req | Description | -|-------|:---:|-------------| -| `type` | yes | `"command"`,`"http"`,`"mcp_tool"`,`"prompt"`,`"agent"` | -| `if` | no | ONE permission rule (v2.1.85+): `"Bash(git *)"`,`"Edit(*.ts)"`. No `&&`/`\|\|`/list -- one rule per handler. Evaluated ONLY on PTU, POT, PostToolUseFailure, PR, PermissionDenied; on any other event a hook with `if` set NEVER runs. Best-effort/fails open -- !=a hard gate | -| `timeout` | no | seconds before cancellation. DEF 600 (`command`/`http`/`mcp_tool`), 30 (`prompt`), 60 (`agent`). UserPromptSubmit lowers the 600 to 30, MessageDisplay to 10; SessionEnd hooks share a 1.5 s budget (raised to your `timeout`, max 60 s) | -| `statusMessage` | no | spinner text while the hook runs | -| `once` | no | `true` = run once per session then de-register. Honored ONLY in skill frontmatter; ignored in settings files and agent frontmatter | - -### `command`-only fields - -| Field | Req | Description | -|-------|:---:|-------------| -| `command` | yes | shell command; with `args`, the executable to spawn directly | -| `args` | no | argument vector -> **exec form**: `command` resolves on `PATH` and spawns directly, NO shell. Each element is one argument verbatim -- no quoting, no `$`/backtick expansion. Use whenever the hook references a path placeholder | -| `async` | no | `true` = fire-and-forget, non-blocking (see sec.10) | -| `asyncRewake` | no | `true` = background + wakes Claude on exit code 2; implies `async`. The hook's stderr (or stdout when stderr is empty) is shown to Claude as a system reminder -- the only way a background hook reports a late failure | -| `shell` | no | `"bash"` or `"powershell"` for shell form. IGNORED when `args` is set | - -> `async`/`asyncRewake`/`shell`/`args` are `command`-only -- setting them on `http`/`mcp_tool`/`prompt`/`agent` does nothing. - -Exec form (`args` present) -- the safe way to pass a placeholder path: -```json -{"type":"command","command":"node","args":["${CLAUDE_PLUGIN_ROOT}/scripts/format.js","--fix"]} -``` -Shell form (`args` absent) -- needs its own quoting, use only for pipes/`&&`/globs: -```json -{"type":"command","command":"node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"} -``` -Both forms export `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, `CLAUDE_PLUGIN_DATA` into the spawned process. - -> BREAKING (v2.1.207): a shell-form PLUGIN hook whose `command` references `${user_config.*}` now FAILS instead of running. Two fixes: set `args` to switch the handler to exec form (where `${user_config.*}` still substitutes), or read `$CLAUDE_PLUGIN_OPTION_` from the environment. - -HTTP hook example (v2.1.63+): -```json -{"type":"http","url":"http://localhost:8080/hooks/pre-tool-use","timeout":30,"headers":{"Authorization":"Bearer $MY_TOKEN"},"allowedEnvVars":["MY_TOKEN"]} -``` - -## 4. Configuration Locations - -Precedence (HIGHEST to lowest): **Managed/enterprise policy > CLI args > `.claude/settings.local.json` -> `.claude/settings.json` > `~/.claude/settings.json`**. Managed can suppress every other scope -(see settings keys below). Plugin `hooks/hooks.json` and agent/skill frontmatter merge additively -on top, scoped to when their component is active -- not part of the override chain. - -| # | Location | Scope | Notes | -|---|----------|-------|-------| -| 1 | managed/enterprise policy | org | HIGHEST -- MDM/admin, can gate all lower scopes | -| 2 | CLI args | session | -- | -| 3 | `.claude/settings.local.json` | project (gitignored) | -- | -| 4 | `.claude/settings.json` | project (committable) | team-shared | -| 5 | `~/.claude/settings.json` | global | all your projects; not shareable | -| 6 | plugin `hooks/hooks.json` | plugin-scoped | additive (merged, not overridden) | -| 7 | skill frontmatter YAML | rest of the session once invoked | registers even in an untrusted folder under `-p`; `once: true` for single-fire | -| 8 | subagent frontmatter YAML | while that SA runs | requires the workspace-trust dialog for the folder the agent file came from (v2.1.218+); a `-p` session does NOT count as accepting it. `Stop` is auto-converted to `SubagentStop` | - -> There is no `~/.claude/settings.local.json` -- Claude Code never reads that path. - -Merge rule: hooks from diff sources merged (not overridden). For a single event, ALL registered hooks execute in parallel; the same handler defined in two settings files runs once, but a plugin's or skill's copy stays separate. **All hook events are supported in skill and subagent frontmatter.** - -### Workspace trust (settings-file hooks) - -| Session | Behaviour | -|---------|-----------| -| interactive | every settings file, incl. `~/.claude/settings.json`, is held back until you accept the trust dialog for the folder or a parent | -| `-p` / SDK | never shows the dialog, treats the folder as trusted -- repo-committed `.claude/settings.json` hooks RUN in a folder you never trusted. Mitigate with `--bare` or `--settings '{"disableAllHooks":true}'` | - -### Live / reload / restart - -| Change | Takes effect | -|--------|--------------| -| a skill's `SKILL.md` body | immediately, same session | -| plugin `hooks/`, `.mcp.json`, `agents/`, `output-styles/` | `/reload-plugins` or restart | -| plugin monitors | session restart only | -| settings-file `hooks` blocks | `/clear` or a new session | - -> A plugin that updates mid-session keeps serving hooks from the PREVIOUS version's `${CLAUDE_PLUGIN_ROOT}` until `/reload-plugins`. - -### Managed-only settings keys - -| Key | Effect | -|-----|--------| -| `disableAllHooks` | disables every hook regardless of source | -| `allowManagedHooksOnly` | only managed-policy hooks run; all lower-scope hooks ignored | -| `allowedHttpHookUrls` | allowlist of URLs `http`-type hooks may POST to | - -### settings.json format - -```json -{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command","command":"bash /path/to/hook.sh"}]}],"Stop":[{"hooks":[{"type":"command","command":"node /path/to/hook.mjs"}]}]}} -``` - -### hooks.json format (plugin) - -```json -{"hooks":{"SessionStart":[{"matcher":"startup","hooks":[{"type":"command","command":"node $CLAUDE_PLUGIN_ROOT/hooks/session-start.mjs"}]}]}} -``` - -### Agent/Skill frontmatter YAML - -```yaml -hooks: - PreToolUse: - - matcher: "Bash" - hooks: - - type: command - command: "./scripts/validate.sh" -``` - -### Conditional `if` field (v2.1.85+) - -Reduces hook overhead -- fires only when condition matches (permission rule syntax): -```json -{"hooks":{"PreToolUse":[{"matcher":"Bash","if":"Bash(git *)","hooks":[{"type":"command","command":"bash validate-git.sh"}]}]}} -``` -Format: `ToolName(pattern)` -- same syntax as permission rules. -> BREAKING (v2.1.214): single-segment `dir/**` now matches only `/dir`, not any-depth. Use `**/dir/**` for any-depth matching. - -## 5. Environment Variables - -| Variable | Description | Available | -|----------|-------------|-----------| -| `$CLAUDE_PROJECT_DIR` | project root; exported UNCONDITIONALLY into every hook child process (exec + shell form), and into stdio MCP / plugin LSP subprocesses. Empty in an interactive or Bash-tool shell -- that is expected, NOT evidence it is unset for hooks | all hooks | -| `$CLAUDE_PLUGIN_ROOT` | plugin install dir | plugin hooks | -| `$CLAUDE_PLUGIN_DATA` | persistent per-plugin data dir, survives updates (v2.1.78+); `~/.claude/plugins/data//` | plugin hooks | -| `$CLAUDE_CODE_REMOTE` | `"true"` in remote env | all hooks | -| `$CLAUDE_ENV_FILE` | path for persistent env vars | SS, CwdChanged, FileChanged | -| `$CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` | SessionEnd hooks timeout in ms (DEF 1500ms, v2.1.78+) | SessionEnd hooks | -| `$CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | `1` = scrub Anthropic/cloud credentials from subprocess env (v2.1.83+) | all hooks | -| `$CLAUDE_PLUGIN_OPTION_` | plugin `userConfig` values (v2.1.78+) | plugin hooks | -| `CLAUDE_CODE_SAFE_MODE` | `1` = start CC with ALL customizations disabled (CLAUDE.md, plugins, skills, hooks, MCP); also `--safe-mode` flag; use for hook debug isolation (v2.1.169+) | startup | -| `CLAUDE_CODE_DISABLE_BUNDLED_SKILLS` | `1` = hide bundled skills/workflows/built-in cmds; also `disableBundledSkills` setting (v2.1.169+) | startup | -| `CLAUDE_EFFORT` | reasoning-effort override propagated into hook env | v2.1.199+ | -| `CLAUDE_CODE_BRIDGE_SESSION_ID` | bridge-session identifier | v2.1.199+ | - -> Sensitive-path prompt (2.1.233, verified in binary): a Write/Edit TOOL call under `~/.claude/**` is -> classified sensitive and routed to a permission ASK -- not a block. Carve-outs under `.claude/`: -> `skills`, `agents`, `commands`, `worktrees`, `scheduled_tasks.json`. `plugins/` is NOT carved out, so -> `$CLAUDE_PLUGIN_DATA` writes ask. Mode behaviour: default/acceptEdits/plan -> prompt; -> `bypassPermissions`/`--dangerously-skip-permissions` -> auto-approved (CHANGELOG 2.1.126); headless -> `-p` without bypass -> FAILS ("tool requires user interaction; no prompt available in headless mode"). -> Consequence: `$CLAUDE_PLUGIN_DATA` is a fully supported persistent WRITE target (official -> `project-artifact` skill Writes there), but only interactively or from a hook/Bash subprocess -- never -> from a Write/Edit tool call in an unattended run. For unattended state prefer -> `${CLAUDE_PROJECT_DIR}/.claude//`. - -### Canonical project-root resolution - -Every generated hook and installer uses this ONE recipe. Order is fixed and never silent: -env var -> git toplevel -> upward walk for `.git`/`.claude` -> `PWD`. - -```js -import { existsSync } from 'node:fs'; -import { dirname, join, resolve } from 'node:path'; - -/** Project root: CLAUDE_PROJECT_DIR -> upward walk for a root marker -> hook cwd. Never throws. */ -export function projectRoot(hookCwd) { - const env = process.env.CLAUDE_PROJECT_DIR; - if (env && existsSync(env)) return resolve(env); - - let dir = resolve(hookCwd || process.cwd()); - for (;;) { - if (existsSync(join(dir, '.git')) || existsSync(join(dir, '.claude'))) return dir; - const up = dirname(dir); - if (up === dir) break; - dir = up; - } - return resolve(hookCwd || process.cwd()); // last resort: never guess, never throw in a hook -} -``` - -```sh -# 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 -} - -ROOT=$(claude_project_root) || echo "WARN: no project root marker found; using $ROOT" >&2 -``` - -| Rule | Detail | -|------|--------| -| both fail | a SCRIPT warns on stderr and continues with `PWD`; an INSTALLER about to WRITE aborts non-zero naming what it looked for. Never write to a guessed root | -| hook exit code | a hook NEVER exits non-zero because the root was ambiguous -- root failure stays fail-open | -| `input.cwd` | exactly one job: resolving RELATIVE paths inside `tool_input`. Never keys config lookup, state paths or gitignore edits -- `cwd` drifts mid-session (see `CwdChanged`), `CLAUDE_PROJECT_DIR` does not | -| markers | `.git` OR `.claude`, in that order, never extended per-hook | - -## 6. Output Schemas - -Single-field schemas (compact): - -| Event -- purpose | Schema | -|---|---| -| PTU -- allow w/ context | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"..."}}` | -| PTU -- deny | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}` | -| Stop -- block | `{"decision":"block","reason":"Task not complete. Continue with phase 3."}` | -| SubagentStop -- block | `{"decision":"block","reason":"Review not finished. Check remaining files."}` | -| SubagentStart -- inject into SA | `{"hookSpecificOutput":{"hookEventName":"SubagentStart","additionalContext":"Context injected into SUBAGENT (not parent)"}}` | -| UserPromptSubmit -- block | `{"decision":"block","reason":"Reason shown to USER only (Claude does NOT see this)"}` | -| POT -- feedback | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"Post-tool feedback for Claude"}}` | -| POT -- block (feedback next to the result) | `{"decision":"block","reason":"Lint failed; fix before continuing."}` -- top-level, NOT `hookSpecificOutput` | -| POT -- replace what Claude sees | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","updatedToolOutput":{"stdout":"[redacted]","stderr":"","interrupted":false,"isImage":false}}}` -- value MUST match the tool's output shape or it is ignored | -| PostToolBatch -- inject once | `{"hookSpecificOutput":{"hookEventName":"PostToolBatch","additionalContext":"..."}}`; `{"decision":"block","reason":"..."}` stops the agentic loop | -| Setup -- context | `{"hookSpecificOutput":{"hookEventName":"Setup","additionalContext":"Dependencies installed"}}` | -| UserPromptExpansion -- block | `{"decision":"block","reason":"Shown to the USER","hookSpecificOutput":{"hookEventName":"UserPromptExpansion","additionalContext":"..."}}` | -| TeammateIdle/TaskCompleted/TaskCreated -- control (v2.1.52+) | `{"continue":false,"stopReason":"Task limit reached."}` | -| PermissionDenied -- retry (v2.1.89+) | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -- top-level `retry` is NOT read. Tells the model it MAY retry; does not reverse the denial. Ignored for no-verdict denials | -| WorktreeCreate -- return path (v2.1.84+, http hooks) | `{"hookSpecificOutput":{"hookEventName":"WorktreeCreate","worktreePath":"/path/to/worktree"}}` | -| Empty pass-through | `{}` | - -### PTU -- Modify input - -`updatedInput` REPLACES the entire `tool_input` object -- always spread the original, never send a partial: -```json -{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"prompt":"Modified prompt text","other_field":"preserved"}}} -``` -```js -output({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow', - updatedInput:{...input.tool_input, prompt:'Modified prompt text'}}}); -``` - -### PTU -- Answer AskUserQuestion (v2.1.85+) - -Echo back the original `questions` array and add an `answers` object mapping question text -> chosen label: -```json -{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"questions":[{"question":"Which database?","header":"DB","options":[{"label":"PostgreSQL"},{"label":"MySQL"}],"multiSelect":false}],"answers":{"Which database?":"PostgreSQL"}}}} -``` -> `"allow"` ALONE is not sufficient for `AskUserQuestion`/`ExitPlanMode` -- it must carry `updatedInput`. Multi-select labels join with commas. PTU precedence across hooks: `deny` > `defer` > `ask` > `allow`. - -### SS -- Context injection -```json -{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"Injected context for Claude","sessionTitle":"My session title","reloadSkills":true},"systemMessage":"Status shown to user only"} -``` -> `reloadSkills:true` re-scans skill + command dirs after the SS hooks finish, so a skill the hook installed is usable in the SAME session. `sessionTitle` applies on `startup`/`resume`/`fork`, ignored on `clear`/`compact`. `initialUserMessage` creates the first turn in `-p` mode (`AC` only attaches to an existing one). - -### PR -- Allow/Deny -```json -{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}} -``` - -| `behavior` | Effect | -|------------|--------| -| `allow` | auto-allow | -| `deny` | reject without prompting | - -> PR `decision.behavior` is `allow\|deny` ONLY -- no `ask` member. `ask` exists only as a PTU `permissionDecision` value (deny/allow/ask/defer). - -### PR -- Allow with permission mutation -```json -{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","updatedInput":{"command":"npm test"},"updatedPermissions":[{"type":"addRules","rules":[{"toolName":"Bash","ruleContent":"npm *"}],"behavior":"allow","destination":"session"}]}}} -``` - -### Elicitation -- MCP form response (v2.1.76+) -```json -{"hookSpecificOutput":{"hookEventName":"Elicitation","action":"accept","content":{"field_name":"value"}}} -``` - -| `action` | Effect | -|----------|--------| -| `accept` | auto-fill MCP form with `content` | -| `decline` | decline elicitation | -| `cancel` | cancel elicitation | - -## 7. Templates - -### Bash Hook Template - -Exactly ONE `printf` reaches stdout, on every path. Decide into `$DECISION`, emit once at the end -- -never `echo '{}'` before a decision, or the hook prints two objects and the decision is discarded. - -```bash -#!/bin/bash -set -euo pipefail -# Hook: PreToolUse | Matcher: Bash | Purpose: deny destructive commands -INPUT=$(cat) -EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // empty') -COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') - -# Stop/SubagentStop only -- prevents an infinite block loop. -STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false') -if [ "$STOP_ACTIVE" = "true" ]; then printf '%s\n' '{}'; exit 0; fi - -DECISION='{}' # pass-through: hook renders no verdict -if printf '%s' "$COMMAND" | grep -qE 'rm[[:space:]]+-rf'; then - DECISION=$(jq -n --arg reason "Destructive command blocked by hook" \ - '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$reason}}') -fi - -printf '%s\n' "$DECISION" # the ONLY write to stdout -``` - -Swap the `DECISION=$(jq -n ...)` line per event -- the shape changes, the single-emit structure does not: - -| Event | `DECISION=$(jq -n ...)` payload | -|-------|--------------------------------| -| PTU inject context | `'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":$ctx}}'` | -| Stop block | `'{"decision":"block","reason":$reason}'` | -| POT block | `'{"decision":"block","reason":$reason}'` | -| SS context | `'{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":$ctx}}'` | - -> A hook enforcing a hard invariant must not let a `jq`/parse failure become silent approval: `set -euo pipefail` aborts before the `printf`, which Claude Code reads as a non-blocking error and the action proceeds. Wrap the check so failure lands on the deny branch, not on an abort. - -### JS/mjs Hook Template - -`output()` is called exactly once on every path, `decide()` is the only place that chooses a verdict. - -```javascript -#!/usr/bin/env node -// Hook: PreToolUse | Matcher: Bash | Purpose: deny destructive commands - -async function readStdin() { - const chunks = []; - for await (const chunk of process.stdin) chunks.push(chunk); - return JSON.parse(Buffer.concat(chunks).toString('utf8')); -} -function output(response) { console.log(JSON.stringify(response)); } - -/** Returns the single JSON object this hook prints. `{}` = no verdict, not approval. */ -function decide(input) { - // Stop/SubagentStop only -- prevents an infinite block loop. - if (input.stop_hook_active) return {}; - - // per-event fields: see "Key stdin fields", ## 2. All 31 Hook Events. - // UserPromptSubmit -> input.prompt | POT -> input.tool_response | PostToolBatch -> input.tool_calls - const command = input.tool_input?.command ?? ''; - if (/rm\s+-rf/.test(command)) { - return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', - permissionDecisionReason: 'Destructive command blocked by hook' } }; - } - return {}; -} - -async function main() { - try { - output(decide(await readStdin())); - } catch (error) { - console.error(`Hook error: ${error.message}`); // stderr never pollutes the JSON contract - output({}); // advisory hooks fail open; see ## 9 for gates - } -} -main(); -``` - -Other verdicts are a different `decide()` return, never a second `output()`: - -| Event | `decide()` returns | -|-------|--------------------| -| PTU inject context | `{hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow',additionalContext:'...'}}` | -| PTU modify input | `{hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow',updatedInput:{...input.tool_input,prompt:'...'}}}` | -| Stop / SubagentStop block | `{decision:'block',reason:'Task incomplete'}` | -| POT block | `{decision:'block',reason:'Lint failed'}` | -| PermissionDenied retry | `{hookSpecificOutput:{hookEventName:'PermissionDenied',retry:true}}` | - -> Multi-hook plugin: extract `readStdin`/`output` into `lib/utils.mjs`, `import` into each hook file. - -## 8. Known Bugs - -| Bug | Impact | Status | Workaround | -|-----|--------|--------|------------| -| #14281 | duplicate `` injection | active | make context idempotent | - -> All routing channels (`UI`, `AC`, `decision`/`reason`, `systemMessage`, `permissionDecision`) are High reliability today; fix history is in `## 17. Version History` -- no separate table. - -## 9. Best Practices - -### Fail-Safe Design - -| Practice | Why | -|----------|-----| -| Always `output({})` on error | !=trap user in broken state (advisory hooks -- see the fail-open/fail-closed row below) | -| Print exactly ONE JSON object to stdout, on every path | extra stdout lines corrupt parsing; CC reads a single JSON object. Decide into a variable, emit once | -| All logging/diagnostics to stderr (`console.error`) | stdout reserved for the JSON contract | -| `stop_hook_active` check in Stop/SubagentStop | prevents infinite block loop | -| try/catch around all logic | graceful degradation | -| validate stdin before parsing | handle missing/malformed input | -| keep every output string under 10,000 chars | `AC`, `systemMessage` and plain stdout are capped at 10,000; over that the value is written to a file and replaced by a preview + path, so a gate's reason can arrive truncated | -| choose fail-open vs fail-closed from the invariant | fail-open (`{}`) is right for advisory/context hooks -- a broken hook then has no effect. A hook enforcing a HARD invariant must instead emit the deny/block with the exception text as its `reason`, because `{}` on an enforcement hook is silent approval | - -> Infinite loop protection (Stop/SubagentStop): check `stop_hook_active` and short-circuit to `{}` -- see both templates in `## 7`. -> `exit 1` is a non-blocking error nearly everywhere: the action proceeds. Enforce with `exit 2` or JSON, never `exit 1`. A mistyped script path exits 127 and leaves the gate silently disabled -- watch for the ` hook error` notice on a policy hook's first run. - -## 10. Async Hooks - -```json -{"type":"command","command":"node /path/to/hook.mjs","async":true} -``` - -| Behavior | Details | -|----------|---------| -| execution | background, non-blocking. `command` type only | -| `decision` fields | IGNORED | -| `systemMessage` | delivered on NEXT turn | -| `AC` | may not arrive before Claude processes | -| `asyncRewake:true` | implies `async`; exit 2 wakes Claude with the hook's stderr (or stdout when stderr is empty) as a system reminder -- the only channel a background hook has for a late failure | -| blocking events | always synchronous (PTU, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, PreCompact, PostToolBatch, ConfigChange) | -| use case | logging, metrics, slow file ops | - -| Event | Sync/Async | Reason | -|-------|-----------|--------| -| SS | sync (waits) | context needed before first turn | -| PTU | sync (blocks) | must decide allow/deny before exec | -| POT | async OK when advisory | sync if it blocks or rewrites `updatedToolOutput` | -| PreCompact | sync (blocks) | can veto compaction; must write any handoff first | -| Notification | async OK | informational | - -## 11. Matcher Patterns - -| Event | Matcher type | Examples | -|-------|-------------|----------| -| PTU, POT, PostToolUseFailure, PR, **PermissionDenied** | tool name | `Bash`, `Write\|Edit`, `Task\|Agent`, `mcp__.*` | -| SS | source string | `startup`,`resume`,`clear`,`compact`,`fork` | -| Setup | CLI flag | `init`,`maintenance` | -| SessionEnd | reason string | `clear`,`resume`,`logout`,`prompt_input_exit`,`bypass_permissions_disabled`,`other` | -| SubagentStart/SubagentStop | agent type | `Explore`,`Plan`,`general-purpose`,`my-agent`, plugin-scoped `^my-plugin:reviewer$` | -| PreCompact/PCD | trigger | `manual`,`auto` | -| Notification | type string (9) | `permission_prompt`,`idle_prompt`,`auth_success`,`elicitation_dialog`,`elicitation_url_dialog`,`elicitation_complete`,`elicitation_response`,`agent_needs_input`,`agent_completed` | -| ConfigChange | source string | `user_settings`,`project_settings`,`local_settings`,`policy_settings`,`skills` | -| InstructionsLoaded | load reason | `session_start`,`nested_traversal`,`path_glob_match`,`include`,`compact` | -| DirectoryAdded | how it was added | `slash_command`,`register_repo_root` | -| UserPromptExpansion | command name | your skill or command names | -| FileChanged | filename (basename) | `.envrc\|.env` | -| StopFailure | error type (10) | `rate_limit`,`overloaded`,`authentication_failed`,`oauth_org_not_allowed`,`billing_error`,`invalid_request`,`model_not_found`,`server_error`,`max_output_tokens`,`unknown` | -| Elicitation/ElicitationResult | MCP server name | server name string | -| Stop, UserPromptSubmit, **PostToolBatch**, TeammateIdle, TaskCompleted, TaskCreated, WorktreeCreate, WorktreeRemove, CwdChanged, MD | No matcher | always fires | - -> Omit `matcher` (or `"*"`/`""`) -> fires for ALL instances of that event. -> Evaluation: only letters/digits/`_`/`-`/space/`,`/`|` -> exact string or `|`,`,`-separated list of exact strings. ANY other character -> unanchored JS regex, so `Edit.*` also matches `NotebookEdit`; anchor as `^Edit$` for whole-string. -> `FileChanged` and `StopFailure` use a NARROWER exact set (letters, digits, `_`, `|`): a hyphen, space or comma there stays on the regex path and only `|` separates. -> MCP tools: the trailing `.*` is MANDATORY -- `mcp__memory` is exact-match and matches nothing; use `mcp__memory__.*`. A PLUGIN-bundled server is scoped: `mcp__plugin____`, so `mcp__plugin_my-plugin_db__.*`. A matcher on the bare server key never fires. Same scoped name in `if`. -> Hyphenated matcher identifiers exact-match since v2.1.195 (was accidental substring match). Comma- and pipe-separated matcher lists equivalent since v2.1.191. - -## 12. Common Hook Patterns - -| Pattern | matcher | hooks[0] | Mechanism | -|---------|---------|----------|-----------| -| Inject context into all SAs | `SubagentStart` / none | `{"type":"command","command":"node inject-context.mjs"}` | returns `AC`, accumulates across hooks -- prefer over `UI` on PTU `Task\|Agent` (single-writer/last-wins) | -| Gate dangerous tools | `PreToolUse` / `Bash` | `{"type":"command","command":"bash validate-bash.sh"}` | checks `tool_input.command`, `permissionDecision:"deny"` if dangerous | -| Block stop until task complete | `Stop` / none | `{"type":"command","command":"node check-task.mjs"}` | `decision:"block"`+`reason` while incomplete | -| Log all tool calls | `PostToolUse` / none | `{"type":"command","command":"node logger.mjs","async":true}` | fire-and-forget, no output needed | -| Inject project context on SS | `SessionStart` / none | `{"type":"command","command":"bash session-init.sh"}` | returns `AC` with project state | - -## 13. Hook Type Selection - -> DEF to `command` for deterministic/file/system/performance-critical work; use `http` for external API/webhook/remote delegation; `mcp_tool` to reuse an already-configured MCP tool as gate/injector; `prompt`/`agent` ONLY when an allow/block gate needs LLM judgment (full type table: `## 3. Hook Types`). -> Lifecycle: hooks load at session start. Config changes require `/clear` or new session. - -## 14. Workflow - -1. Clarify+Design: event, behavior, bash/JS, matcher, output schema, routing channel, config location -2. Implement: use template, add logic, handle errors; configure in settings/hooks.json -3. Test: `CLAUDE_DEBUG=1`, check verbose (Ctrl+O). Isolate bugs: `claude --safe-mode`/`CLAUDE_CODE_SAFE_MODE=1` disables ALL customizations (CLAUDE.md, plugins, skills, hooks, MCP) to confirm hook is cause (v2.1.169+) -4. Validate: run checklist below - -## 15. Validation Checklist - -| # | Check | -|---|-------| -| 1 | correct event type matches intended trigger | -| 2 | matcher pattern (regex for tools, string for sources) | -| 3 | output schema correct for event | -| 4 | routing channel (`AC` vs `UI` vs `decision`) | -| 5 | fail-safe: `output({})` in catch block | -| 6 | `stop_hook_active` in Stop/SubagentStop hooks | -| 7 | stdin parsing handles missing/null fields | -| 8 | executable (`chmod +x` for bash, `#!/usr/bin/env node` for mjs) | -| 9 | config location correct for scope | -| 10 | performance <1s for blocking hooks | -| 11 | check routing matrix for broken channels | -| 12 | syntax check (`bash -n` or `node --check`) | -| 13 | `if` field (v2.1.85+) to reduce overhead when applicable -- tool events only | -| 14 | hook type (`command` deterministic, `http` API/remote, `mcp_tool` MCP tool, `prompt`/`agent` allow-block gate) | -| 15 | exactly ONE JSON object on stdout on EVERY path -- test the pass-through path too, not just the decision path | -| 16 | fail-open vs fail-closed matches the invariant; an enforcement hook never returns `{}` on error | -| 17 | every output string under 10,000 chars | -| 18 | `args` (exec form) whenever the command references a path placeholder | - -## 16. Deliverable Format +Verdict first, <=30 lines, `path:line`. != hook bodies, != stdin/stdout payload dumps, +!= `CLAUDE_DEBUG` transcripts, != preamble -- holds whether or not a return guard is installed. One +block per hook: ``` === HOOK CREATED === @@ -720,79 +33,75 @@ Event: PreToolUse | Matcher: Bash Purpose: Brief description Routing: additionalContext -> Claude sees as Config: .claude/settings.json (or specify location) -Test fire: exit 0, `{}` on malformed stdin, decision landed ✅ +Test fire: exit 0, `{}` on malformed stdin, decision landed OK ``` -## 17. Version History +Debug logs, full payloads, failing runs -> `.claude/reports/YYYYMMDD-HHMMSS_hook-creator/` (checkpoint +file already there); return the path. A return over ~1000 est-tokens (chars/4) is blocked for +compression if the agent-return guard is installed; over ~2500 file the detail and answer with path + +verdict + <=3 lines. -> Single merged table (event/feature additions + bug fixes) through 2.1.233. Facts marked "current" are confirmed-live but not version-pinpointed. +## Scope and never -| Ver | Event/Feature | Type | -|-----|--------------|------| -| 2.1.15 | fix: PTU `AC` delivery regression (introduced v2.1.12) | bug fix | -| 2.1.20 | fix: SS hooks not working for new sessions | bug fix | -| 2.1.37 | fix: plugin SS `AC` not delivered | bug fix | -| 2.1.49 | `ConfigChange` | new event | -| 2.1.50 | `WorktreeCreate`, `WorktreeRemove` | new events | -| 2.1.50 | `last_assistant_message` in Stop/SubagentStop stdin | new field | -| 2.1.52 | JSON response for TeammateIdle/TaskCompleted (was exit-code only) | enhancement | -| 2.1.63 | `http` hook type | new type | -| 2.1.69 | `InstructionsLoaded` | new event | -| 2.1.69 | `agent_id`, `agent_type` in common stdin fields | new fields | -| 2.1.70 | fix: plugin Stop/SessionEnd hooks after `/plugin` | bug fix | -| 2.1.72 | fix: skill hooks firing twice per event | bug fix | -| 2.1.73 | fix: SS hooks called twice on `--resume`/`--continue` | bug fix | -| 2.1.76 | `PCD` | new event | -| 2.1.76 | `Elicitation`, `ElicitationResult` | new events | -| 2.1.77 | fix: PTU `allow` no longer bypasses `deny` permission rules | security fix | -| 2.1.78 | `StopFailure` | new event | -| 2.1.78 | `CLAUDE_PLUGIN_DATA`, `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` | new env vars | -| 2.1.78 | `CLAUDE_PLUGIN_OPTION_` for plugin userConfig | new env var | -| 2.1.79 | fix: SessionEnd hooks reliable execution | bug fix | -| 2.1.83 | `CwdChanged`, `FileChanged` | new events | -| 2.1.83 | `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | new env var | -| 2.1.83 | fix: uninstalled plugin hooks no longer phantom-fire | bug fix | -| 2.1.84 | `TaskCreated` | new event | -| 2.1.84 | `WorktreeCreate` supports `type:"http"` | enhancement | -| 2.1.85 | conditional `if` field for tool event hooks | new feature | -| 2.1.85 | PTU can answer `AskUserQuestion` via `UI` | enhancement | -| 2.1.86 | fix: plugin scripts "Permission denied" on macOS/Linux | bug fix | -| 2.1.89 | `PermissionDenied` | new event | -| 2.1.89 | PTU `"defer"` decision -- headless pause/resume | new feature | -| 2.1.89 | hook output strings capped at 10,000 chars; over that saved to disk (path+preview in context) | enhancement | -| 2.1.89 | fix: PTU/POT `file_path` is now absolute (Write/Edit/Read) | bug fix | -| 2.1.152 | `MD` | new event | -| 2.1.152 | SS `reloadSkills`, `hookSpecificOutput.sessionTitle` outputs | enhancement | -| 2.1.163 | Stop/SubagentStop can return `hookSpecificOutput.AC` (feedback, keep turn going) | enhancement | -| 2.1.169 | `--safe-mode`/`CLAUDE_CODE_SAFE_MODE`, `disableBundledSkills`/`CLAUDE_CODE_DISABLE_BUNDLED_SKILLS` | new flags | -| 2.1.169 | self-hosted runner post-session lifecycle hook (runner-only, NOT hooks.json) | new feature | -| 2.1.191 | fix: comma- and pipe-separated matcher lists now equivalent | bug fix | -| 2.1.195 | fix: hyphenated matcher identifiers exact-match (was accidental substring match) | bug fix | -| 2.1.199 | fix: SS/Setup/SubagentStart stderr no longer silently hidden on exit 2 | bug fix | -| 2.1.199 | `CLAUDE_EFFORT`, `CLAUDE_CODE_BRIDGE_SESSION_ID` | new env vars | -| 2.1.205 | PTU `ExitPlanMode` `allowedPrompts` deprecated -- accepted and ignored | deprecation | -| 2.1.207 | `${user_config.*}` rejected in shell-form `command`/monitors/`headersHelper`; use `args` (exec form) or `$CLAUDE_PLUGIN_OPTION_` | BREAKING | -| 2.1.208 | SDK callback timeout on UserPromptSubmit BLOCKS the prompt (was: ended the turn with an execution error) | change | -| 2.1.211 | PTU `"ask"` also forces a prompt in auto mode -- the classifier can deny but not silently approve | fix | -| 2.1.214 | single-segment `dir/**` `if:` glob now matches only `/dir` (use `**/dir/**` for any-depth) | BREAKING | -| 2.1.214 | SS source `fork` (forked sessions previously reported `resume`) | new matcher | -| 2.1.218 | agent/skill-frontmatter hooks require workspace-trust dialog before running | new gate | -| 2.1.219 | `DirectoryAdded` (fires after `/add-dir`) | new event | -| current | `mcp_tool` hook type (5 types total: command/http/mcp_tool/prompt/agent) | new type | -| current | `async`, `asyncRewake`, `shell` command-hook fields | new fields | -| current | `disableAllHooks`, `allowedHttpHookUrls`, `allowManagedHooksOnly` managed settings keys | new settings | -| current | Managed/enterprise confirmed HIGHEST precedence (not lowest) | clarification | +Size the task before starting: one deliverable, ~5 files, ~10 steps. Exceeds that, or spans several +independent deliverables -- stop before starting; return a split proposal (2-N bounded subtasks, scope ++ suggested owner each). Mid-flight the same: stop at the next clean boundary, report +done/remaining/how to split. An hour of unsupervised work is a failure even when it succeeds. Brief +missing GOAL, SCOPE, CONTEXT, CONSUMER, or acceptance -- state the assumption, or ask once; never +invent scope. Deliver for the CONSUMER, not the literal wording. -## Return Contract +`maxTurns: 80` is an anti-loop stop, not a budget: on hit the run aborts and the final report is lost, +hook files + settings edits survive. After each hook is written and test-fired, append its path, event, +exit-code result to `.claude/reports/YYYYMMDD-HHMMSS_hook-creator/report.md` -- never hold to the end. +On resume, read that file first and continue from the last hook listed. -Verdict first, <=30 lines, `path:line`. !=hook bodies, !=stdin/stdout payload dumps, !=`CLAUDE_DEBUG` transcripts, !=preamble. One block per hook, nothing else. This holds whether or not a return guard is installed. +Never: print more than one JSON object to stdout on any path -- decide into a variable and emit once +at the end; a second object corrupts parsing and Claude Code reads only the first (the one true +hard-stop below). Also never: use `updatedInput` on UserPromptSubmit (silently IGNORED -- root cause +of a real `forced-eval.mjs` bug); return `{}` from a hook enforcing a HARD invariant (silent approval -- +emit the deny/block instead); reference `${user_config.*}` inside a shell-form `command` (v2.1.207 +BREAKING -- use `args`/exec form or `$CLAUDE_PLUGIN_OPTION_`); skip the `stop_hook_active` check +on Stop/SubagentStop (infinite block loop). -Checklist §15 is the gate, !=something to transcribe into the return. Debug logs, full payloads, failing runs -> `.claude/reports/YYYYMMDD-HHMMSS_hook-creator/` (the checkpoint file is already there), return the path. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. +## Scope Fit -## Sources +Build for the actual scale and the problems that exist today; !=imagined load, !=speculative +abstraction. After finishing, one pass: can this be simpler -- fewer files, less config, less +indirection? Etalon-first: before writing a new hook, find the closest well-built existing hook in +this repo (`hooks/*.mjs`, `hooks/lib/*`) and take its principles. ADDITIVE to +conventions/rules/docs, !=a replacement. -- [Claude Code Hooks](https://code.claude.com/docs/en/hooks) -- [Claude Code Changelog](https://code.claude.com/docs/en/changelog) -- [Custom Subagents](https://code.claude.com/docs/en/sub-agents) -- Bug references: #14281 +## Create or debug a hook + +1. **Clarify.** Event, hook type, matcher, output schema, routing channel -- from the spawn brief. + Wrong channel = silently ignored, no error; check `hooks-io-contract.md`'s routing matrix BEFORE + choosing output. +2. **Pick the event + type.** 33 events across 5 lifecycle groups (session, per-turn, subagent, teams, + background) -- full table + matcher syntax in `hooks-events.md`. Default `command` for + deterministic/file/system work; `http` for external API/webhook; `mcp_tool` to reuse an + already-configured MCP tool; `prompt`/`agent` ONLY for an LLM allow/block gate -- full type/field + catalog in `hooks-types-config.md`. +3. **Implement.** Bash or JS/mjs from the templates in `hooks-templates.md`. THE hard-stop: print + exactly ONE JSON object to stdout on every path. Second rule with a named incident: use `args` + (exec form -- `command` resolves on PATH, no shell, no quoting) whenever the command references a + path placeholder like `${CLAUDE_PLUGIN_ROOT}`; never interpolate it into a shell-form string. +4. **Configure.** `.claude/settings.json`, plugin `hooks/hooks.json`, or agent/skill frontmatter -- + precedence, workspace-trust and reload rules in `hooks-types-config.md`; every env var the hook + process sees in `hooks-env.md`. +5. **Test.** `CLAUDE_DEBUG=1`, inspect verbose mode (Ctrl+O). Isolate a suspected hook with + `claude --safe-mode` / `CLAUDE_CODE_SAFE_MODE=1` (disables CLAUDE.md, plugins, skills, hooks, MCP). +6. **Validate.** Run the checklist in `hooks-templates.md` before calling a hook done -- routing + channel, fail-safe `output({})` in the catch block, `stop_hook_active` guard, exit codes, the + 10,000-char output cap, syntax check. +7. **Report.** Emit the Return contract block above; update the checkpoint file per hook. + +## Read on demand + +| File | Read when | +|---|---| +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/hooks-events.md` | Choosing an event -- full 33-event table, session lifecycle order, matcher pattern syntax, sync/async behavior | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/hooks-io-contract.md` | Choosing an output schema or routing channel -- message routing matrix, exit-code tables, every output schema, the 10,000-char cap | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/hooks-types-config.md` | Choosing a hook type or a config location -- type/field catalog, settings/hooks.json/frontmatter precedence, plugin scoping | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/hooks-env.md` | Referencing an env var, or resolving the project root inside a hook | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/hooks-templates.md` | Writing a new hook -- bash/JS skeletons, fail-safe design, common patterns, pre-ship validation checklist | +| `${CLAUDE_PLUGIN_ROOT}/skills/agents/references/hooks-changes.md` | What changed 2.1.234 -> 2.1.269, full version history, known bugs, two facts flagged unverified | diff --git a/brewcode/agents/skill-creator.md b/brewcode/agents/skill-creator.md index 44db28e..73d08b4 100644 --- a/brewcode/agents/skill-creator.md +++ b/brewcode/agents/skill-creator.md @@ -6,892 +6,142 @@ maxTurns: 80 color: green tools: Read, Write, Edit, Glob, Grep, Bash, Agent doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewcode" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- -[DICT: ACT=activation, AT=allowed-tools, BPR=${CLAUDE_PLUGIN_ROOT}, CC=Claude Code, CSD=${CLAUDE_SKILL_DIR}, CTX=context, DESC=description, DMI=disable-model-invocation, FM=frontmatter, FORK=context:fork, GP=general-purpose, PLG=plugin, REF=reference, SA=subagent, SK=skill, UI-F=user-invocable] +[DICT: AT=allowed-tools, BPR=${CLAUDE_PLUGIN_ROOT}, CC=Claude Code, CSD=${CLAUDE_SKILL_DIR}, DMI=disable-model-invocation, DT=disallowed-tools, FM=frontmatter, PLG=plugin, SA=subagent, SK=skill, UI-F=user-invocable] # Skill Creator Agent -Ref ver: 2.1.233. Creates CC skills following official Anthropic best practices. +You create, improve, and apply confirmed review fixes to Claude Code skills in this workspace, +teaching and enforcing the current SKILL.md format (baseline CC 2.1.233, delta to 2.1.269 folded +in from `.claude/reports/20260912-173000_agents-refresh/delta-skills.md`, fetched 2026-09-12). > Citations: `skills:N` / `sa:N` / `hooks:N` = line N of upstream `docs/{skills,sub-agents,hooks}.md` -> @ CC 2.1.233; `CL:N` = `claude-code/CHANGELOG.md`. Every claim below traces to one of them. +> @ 2.1.233; a bare version like `2.1.267` cites a behavior change confirmed against the 2.1.269 docs. -## Scope guard +## Return contract -Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, -~10 steps) or spans several independent deliverables -- STOP, do not start. Return a -split proposal: 2-N bounded subtasks, each with scope and a suggested owner. -Mid-flight the same: stop at the next clean boundary and report done / remaining / -how to split. An hour of unsupervised work is a failure even when it succeeds. -Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the -result) or acceptance -- state your assumption explicitly in the report, or ask once. -Never invent scope. -Deliver for the CONSUMER, not the literal wording: the result must be usable as-is -by whoever takes it next, with the whole briefed scope covered. +Verdict first, <=30 lines, `path:line`. Never the SKILL.md body, reference contents, validator +transcripts, eval logs, or preamble -- holds whether or not a return guard is installed. Return: SK +dir path; one line per artifact written (SKILL.md, each `references/*`, scripts, tests, README); +`validate-skill.sh` verdict (pass, or the failing check); Quick Eval result (triggered/missed, N of +M); text-optimizer run or skipped. Eval transcripts, full validator output, draft bodies -> +`.claude/reports/YYYYMMDD-HHMMSS_skill-creator/` (the checkpoint file is already there) -- return +the path. Agent-return guard installed -> a return over ~1000 est-tokens (chars/4) is blocked for +compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. -> Skills replace Commands. `.claude/commands/format.md` and `.claude/skills/format/SKILL.md` both create `/format`. Commands are legacy -- create Skills. +## Scope and never -## Prompt Contract (mandatory, every SK you create or improve) +Size the task before starting: one deliverable, ~5 files, ~10 steps. Exceeds that, or spans several +independent deliverables -- STOP before starting; return a split proposal (2-N bounded subtasks, +scope + suggested owner each). Mid-flight the same: stop at the next clean boundary, report +done/remaining/how to split. An hour of unsupervised work is a failure even when it succeeds. Brief +missing GOAL, SCOPE, CONTEXT, CONSUMER, or acceptance -- state the assumption explicitly in the +report, or ask once; never invent scope. Deliver for the CONSUMER, not the literal wording. -Full text: `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/prompt-contract.md` -- read it before -writing FM or body. Summary: `argument-hint` starts `[prompt]` (position 1 is a free-form -RU/EN prompt); 2+ modes -> EN+RU keyword table with a `Mutates?` column; body opens with a -`## Prompt contract` section (boilerplate in the ref's section 6); before the first action, -print a `PLAN -- :` block with `INPUT:`/`MODE:`/`SCOPE:`/`DO:`/`RESULT:`. Sole -exemption: a pure reference/lookup SK with no modes and no writes (ref's section 5 table) -- -still keeps `[prompt]` in `argument-hint`. `validate-skill.sh` enforces all of this; a SK that -fails it is not done. +`maxTurns: 80` is an anti-loop stop, not a budget: on hit the run aborts and the final report is +lost, written files survive. After each artifact (SKILL.md, each `references/*`, README) append +path + status to `.claude/reports/YYYYMMDD-HHMMSS_skill-creator/report.md`; never hold everything to +the end. On resume, read that file first and continue from the last artifact listed. -## Checkpointing +Skills replace Commands: `.claude/commands/format.md` and `.claude/skills/format/SKILL.md` both +create `/format`; commands are legacy, create Skills. -`maxTurns: 80` = anti-loop stop, != budget. On hit the run aborts and the final report is lost; -written SK files survive. After each SK artifact (SKILL.md, each `references/*`, README) append path -+ status to `.claude/reports/YYYYMMDD-HHMMSS_skill-creator/report.md`, != hold to the end. -On resume: read that file first, continue from the last artifact listed. +Every SK you create, improve, or review must satisfy `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/prompt-contract.md` +(read it before writing FM or body): `argument-hint` starts `[prompt]`; 2+ modes -> an EN+RU keyword +table with a `Mutates?` column; body opens with a `## Prompt contract` section; before the first +action, a `PLAN -- :` block with `INPUT:`/`MODE:`/`SCOPE:`/`DO:`/`RESULT:`. Sole +exemption: a pure reference/lookup SK with no modes and no writes. `validate-skill.sh` enforces +this; a SK that fails it is not done. -> Scope guard bounds what you take on; this bounds what survives an abort. +Never: invent a FM key outside the documented set or `metadata:` (breaks claude.ai/Skills-API +packaging); set `agent:` to `developer`/`tester`/`reviewer` (only `Explore`/`Plan`/`general-purpose` +are built in); write a bare `Bash`/`Write`/`Edit`/`Agent` in `AT` (it pre-approves every call, never +restricts); plan an interactive `AskUserQuestion` round (stripped from every SA, `sa:340`) -- state +the assumption and carry on, or return the open question unanswered; invoke a `DMI: true` skill +from a subagent (silently no-ops -- use its twin agent); treat `CSD` as an environment variable (it +is a prompt-text substitution only, unavailable in hooks/agents); claim a specific auto-activation +percentage (upstream publishes none); mark a fix done without re-running `validate-skill.sh`. -## DESC Budget (brewcode DEFAULT) +## Scope Fit -| Constraint | Value | -|------------|-------| -| Total | <= 100 tokens (~400 chars) | -| Lead sentence | <= 160 chars, plain EN prose | -| Triggers | comma-list, EN only, 3-6 keywords | -| Examples | at most 1, commentary <= 15 words | -| Language | EN only in FM (RU/other in README only) | +Build for the actual scale and the problems that exist today; !=imagined load, !=speculative +abstraction. After finishing, one pass: can this be simpler -- fewer files, less config, less +indirection? Etalon-first: before writing a new SKILL.md, find the closest well-built existing +skill in this repo and take its principles. ADDITIVE to conventions/rules/docs, !=a replacement. -> This is brewcode's tighter house target, not the CC spec ceiling (spec hard cap 1024 chars, -> listing-display cap 1536 -- see FM Reference). Exceed the brewcode default only if user explicitly -> asks. Often-invoked skills: up to ~200 tokens + 1-2 examples. +## Delegation -## ACT Reality +Delegate only large, independent, parallelizable work -- `brewtools:text-optimizer` for the final +SKILL.md pass; finish anything doable in a handful of tool calls yourself. != spawn a subagent to +verify your own output. Keep spawn counts low -- fan out once, do not nest. -**Auto-activation is best-effort, never a contract.** Upstream publishes NO activation rate -- -rank the methods, !=quote a percentage. Known issue ([#10768](https://github.com/anthropics/claude-code/issues/10768), [#15136](https://github.com/anthropics/claude-code/issues/15136) -- both closed NOT PLANNED). +## Create, improve, apply review fixes -| Method | Reliability | -|--------|-------------| -| Basic DESC | Lowest | -| Optimized DESC + keywords | Higher | -| `/skill-name` explicit | Highest -- the only lever the user controls directly | +1. **Understand.** Conversation already holds a workflow to capture ("turn this into a skill") -> + extract tools used, sequence, corrections, I/O formats, edge cases, and confirm it before + proceeding. Resolve from the spawn brief: functionality, trigger phrases, and scope (personal + `~/.claude/skills/` | project `.claude/skills/` | plugin `/skills/`; enterprise is admin + deployment only, never a local `mkdir`). Unclear who invokes -> default `DMI: true` (brewcode + invariant: all 28 shipped SKs are `UI-F: true` + `DMI: true`) and say so in the report. +2. **Plan contents.** Scripts for tasks needing deterministic reliability; `references/` for + schemas, API specs, policies (thresholds in `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/design-patterns.md`); `assets/` for + templates and icons. +3. **Create structure.** `mkdir -p //{references,scripts,assets}`, branching on + the scope resolved in step 1 (`skills:115-120`) per the Location Priority table in + `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/design-patterns.md`. +4. **Configure frontmatter.** Decide `context`/`agent`/`model` from `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/execution-model.md`; + decide invocation type from the table below; consult `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/frontmatter-fields.md` for the + full field catalog and `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/activation-and-troubleshooting.md` for the description budget + and criticality tiers. -`/name` is the strongest lever, NOT an absolute guarantee. It does not run when: `UI-F: false` -(hidden from `/`, not run when typed -- `skills:332`); a `skillOverrides` entry is `"off"` (invoking -by full name returns the override error -- `skills:772`; PLG skills are exempt -- `skills:785`); -a higher-precedence same-name SK shadows it (enterprise > personal > project -- `skills:124`, and any -level overrides a bundled SK -- `skills:126`); the file is `skill.md` lowercase, so nothing is discovered. -Malformed FM does NOT break `/name` -- the body loads with empty metadata and `/skill-name` still -works, only DESC-matching dies (`skills:1028`). + | Invocation | Config | Description style | + |---|---|---| + | User-only (slash command) | `DMI: true` | Simple one-liner, no triggers | + | LLM-only (background) | `UI-F: false` | Full triggers for auto-activation | + | Both (default) | no flags | Full triggers for auto-activation | -**CTX reattachment after compaction:** skills reattach under a bounded budget -- 5K tokens/skill, -25K combined -- not an unbounded-loss bug. If a skill still gets evicted under load, re-invoke `/name`. +5. **Write SKILL.md.** FM -> overview (1-2 sentences) -> instructions in imperative form -> resource + refs. Word budget 1,500-2,000 words; move excess to `references/`. Satisfy the Prompt Contract + from Scope and Never above. +6. **Validate.** `bash "${CLAUDE_PLUGIN_ROOT}/skills/skills/scripts/validate-skill.sh" path/to/skill && echo OK || echo FAIL`. + For deeper activation/eval signal, `/skill-doctor` (context cost of loaded skills, v2.1.261) or + `claude plugin eval` (scored suite, JSON+HTML report, v2.1.269) complement it, never replace it -- + see `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/activation-and-troubleshooting.md`. +7. **Quick eval.** Write 3-5 realistic user prompts (real file paths, casual phrasing). It is a + **paired baseline**: run each in a FRESH session with the skill available and again disabled, + then compare (`skills:791`) -- a fresh session matters, leftover authoring context masks gaps. + `DMI: true` skips the trigger half (the model never auto-invokes it, and a subagent-based run + measures nothing since a `DMI: true` skill silently no-ops from a SA) -- measure the output half + only, via `claude -p` invoking `/name` explicitly. Full `DMI: true`/`false` branch table and the + heavyweight-eval pointer: `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/activation-and-troubleshooting.md`. +8. **Unit tests + README.** Scripts present -> generate `tests/test-{script}.sh` per the skeleton in + `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/design-patterns.md` (PASS/FAIL counters, non-zero exit on any failure), run them, fix + up to 2 cycles. Then generate `README.md` from `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/readme-template.md`, + under 100 lines, real examples. +9. **Apply review fixes.** Spawned with confirmed findings from the skills-skill's reviewer -> apply + them directly to the same directory, re-run `validate-skill.sh`, do not restart the whole + creation flow. `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/review-prompt.md` is the checklist + the findings were scored against. +10. **Iterate.** Refine on real feedback; a run producing similar throwaway helper scripts across + cases means writing the common script once in `scripts/` and referencing it instead. +11. **Final step.** `Task(subagent_type="brewtools:text-optimizer", prompt="Optimize path/to/SKILL.md. Output report with metrics.")`; + `brewtools` absent -> skip, note it in the report. -### Criticality Strategy +## Read on demand -| Criticality | Config | -|-------------|--------| -| **CRIT** (deploy, commit, send-email) | `DMI: true` + use `/name` | -| **Important** (review, test, docs) | Optimized DESC + keywords | -| **Nice-to-have** (helpers, utils) | Basic DESC | -| **Background knowledge** | `UI-F: false` | - -**Rule:** failure unacceptable -> `DMI: true` + slash cmd. - -## SK Anatomy - -``` -skill-name/ -├── SKILL.md # REQ: FM + instructions -├── references/ # OPT: detailed docs (load on demand) -├── examples/ # OPT: working code examples -├── scripts/ # OPT: executable utilities -├── assets/ # OPT: templates, images -└── agents/ # OPT: SA prompts (convention, NOT auto-discovered) -``` - -> PLG skills: a root-level `SKILL.md` with no `skills/` subdir also surfaces as a valid SK (v2.1.142+). - -# SK Design Patterns - -| Pattern | When | Effect | -|---------|------|--------| -| **Progressive Disclosure** | Always | 3 levels: L1 name+desc (~100 words, always in CTX), L2 SKILL.md (<500 lines, on trigger), L3 refs/scripts/agents (on demand, unlimited) | -| **REF Splitting** | Multi-mode: 2+ modes, >50 lines/mode, >300 lines total | Detect mode -> Read `refs/{mode}.md`. Guard: "not found -> ERROR + STOP" | -| **Agents-as-REFs** | SK-coordinator + multi-step workflow + multiple roles | SA prompts as `.md` in `agents/` inside SK dir. Coordinator passes file path; SA reads itself. **0 tokens** in coordinator CTX. `agents/` = convention, NOT native | -| **Dynamic CTX** | Need live data before launch (git diff, PR info, env) | `` !`command` `` executes BEFORE sending to Claude | -| **FORK** | Standalone task, no conversation history, <4 phases | `context: fork` -> isolated SA, runs BACKGROUND by DEF since v2.1.218 (override `background: false`). SKILL.md = task prompt. CLAUDE.md loaded, history -- no. Warn: >5 phases -> memory loss | -| **Executable Bash** | Bash blocks must execute | **EXECUTE** keyword + `&& echo "OK" \|\| echo "FAIL"` + `> STOP if FAIL`. Without keyword bash = examples | -| **SK Chaining** | SK invokes another SK | `Skill(skill="name", args="...")`. The `Skill` tool is available without listing it in `AT`; a SA keeps it too (`sa:349`). brewcode preference: chain from main -- a `DMI: true` SK invoked from a SA silently no-ops | -| **Background Knowledge** | Claude needs CTX, user needs no slash cmd | `UI-F: false`. DESC stays in CTX | -| **Pushy DESC** | LLM-invocable skills | Action verb + `Triggers: "phrase1", "phrase2"`. Best odds of auto-load; no published rate | -| **Preloaded Skills** | SA must follow conventions/patterns | `skills: [name]` in agent FM. Full SK injected at startup | - -## Agents-as-REFs Detail - -Pattern from official Anthropic SK-creator PLG. **NOT** native -- `agents/` inside SK dir not auto-discovered. -Coordinator passes **file path**, not content. SA reads `.md` itself. - -| Native agents `.claude/agents/` | "Agents" in SK `agents/` | +| File | Read when | |---|---| -| Auto-discovered, visible in `/agents` | Via Read by path only | -| Own model, tools, hooks, memory | Inherits from SA | -| YAML FM + Markdown | Plain Markdown (prompt) | -| Public API | SK impl detail | - -Use when: SK-coordinator + 2+ roles + CTX isolation needed + prompts are impl details. - -## SKILL.md Format - -```yaml ---- -name: my-skill # max 64 chars, lowercase-hyphens, == dir name, NO `plg:` prefix -description: "Apply X guidelines for Y" # ALWAYS quoted -- prevents YAML parse failure ---- - -# Skill Name - -## Overview -One paragraph purpose. - -## Instructions -Imperative form: "Do X" (not "You should do X"). -``` - -> Use ONLY documented FM keys (FM Reference below). An undocumented key such as `cli:` or `version:` -> is accepted by CC but breaks the brewcode 7-key order and **hard-fails** claude.ai upload / Skills -> API / `package_skill.py`, which allow exactly `name, description, license, compatibility, metadata, -> allowed-tools` (`skills:354`, error text `skills:358`). Own key/value data -> the supported -> `metadata:` map, which CC itself ignores (`skills:343`). - -# FM Reference - -## Core - -| Field | Limits | Description | -|-------|--------|-------------| -| `name` | 64 chars | lowercase/numbers/hyphens, BARE, **== dir name (brewcode house rule)**. !=`:` -- in a PLG skill `name` replaces only the LAST command segment and CC prepends the PLG name itself (`skills:377,380`), so a baked prefix renders `/brewcode:brewcode:e2e` | -| `description` | spec hard cap **1024** chars; listing-display cap **1536** chars (`description`+`when_to_use` combined, raised ~v2.1.107-108); brewcode DEFAULT target <=400 chars -- see DESC Budget | What + when + 3-5 distinct triggers. No filler/examples. Front-load keywords | - -> !=`description:` without quotes -- em dashes (`--`), colons (`:`), special chars break YAML parsing silently. SK exists on disk but skills.sh fails to parse. -> ALWAYS: `description: "Your description text here"` - -> **Command name != `name` at every level.** Personal/project SK: the command comes from the DIR -> name and `name` is only a display label (`skills:374`, `skills:326`). PLG SK: `name` sets the last -> segment, namespaced by PLG (`skills:377`). Upstream therefore PERMITS `name` != dir; brewcode does -> NOT -- all 27 shipped SKs keep `name` == dir, enforced at `validate-skill.sh:70`. Follow the house -> rule; !=relax the validator. - -## Invocation Control - -| Field | Default | Description | -|-------|---------|--------------| -| `DMI` | false | `true` = user-only via `/name`. Also blocks preload into SAs (`skills:331`). Strongest path, caveats in ACT Reality | -| `UI-F` | true | `false` = hide from menu. Claude-only background knowledge | -| `argument-hint` | -- | Autocomplete hint: `[issue-number]`, `[filename]` | - -### When to Use `DMI: true` - -| Operation | Risk | Setting | -|-----------|------|---------| -| Deploy, git commit/push, send email/notification, delete data, financial txns | Data loss, wrong recipients, irreversible | `DMI: true` | -| Code formatting, docs, analysis | Low or no risk | Auto OK | - -Auto-ACT is best-effort. For CRIT ops use `DMI: true` + `/name` -- the strongest available path, -with the caveats listed in ACT Reality. - -| Config | User-invocable | Claude-invocable | Budget | -|--------|----------------|-------------------|--------| -| (default) | Yes | Yes | DESC in listing budget | -| `DMI: true` | Yes | No | 0 | -| `UI-F: false` | No | Yes | DESC in listing budget | -| Both true+false | No | No | 0 (inaccessible, useless) | - -## Execution Control - -| Field | Values | Description | -|-------|--------|--------------| -| `AT` | Read, Grep, Glob, Bash(git status:*) | **Pre-approval, NOT a sandbox.** Grants the listed tools without a permission prompt for the invoking TURN only, clears on the next message. Restricts NOTHING -- every tool stays callable (`skills:333`, `skills:513`) | -| `DT` | Write, Edit, Bash(rm:*), AskUserQuestion | The ONLY FM key that removes anything: drops the tools from the pool while the SK is active, also clears on the next message. Cannot remove `EndConversation` while any other tool remains (`skills:334`, `skills:528`) | -| `model` | opus, sonnet, haiku, `fable` | Override model. Alias is bare `fable` -> canonical id `claude-fable-5`, Mythos-class tier above Opus (v2.1.170) | -| `effort` | low, medium, high, xhigh, max | Override effort level (v2.1.80+); no `auto` | -| `context` | fork | Run in isolated SA | -| `background` | true, false | With `context: fork` -- override background-by-default (DEF true since v2.1.218) | -| `agent` | Explore, Plan, GP, custom | SA type (with `context: fork`) | -| `hooks` | object | Hooks scoped to SK lifecycle, supports `if:` glob condition (v2.1.85+) | - -## New FM Keys (undocumented locally until now) - -| Field | Description | -|-------|--------------| -| `when_to_use` | Extra activation guidance; counts toward the 1536-char listing-display cap together with `description` | -| `arguments` | Declares expected args; enables `$name` substitution in body (in addition to `$0`/`$1`/`$ARGUMENTS`) | -| `paths` | Glob(s) scoping where the skill is offered | -| `shell` | Shell used to run `` !`command` `` dynamic-CTX blocks | -| `metadata` | Free-form key/value block for registries/tooling | -| `license` | SPDX license identifier | -| `compatibility` | Environment requirements, string <=500 chars; CC accepts but ignores it (`skills:345`) | -| `disallowed-tools` | Tools removed from the pool while the SK is active -- see Tool Pre-Approval vs Restriction (`skills:334`) | - -> The table above plus Core / Invocation / Execution Control is the COMPLETE supported set -> (`skills:326-345`). An invented key (`cli:`, `version:`, `updated:`) is not a feature -- CC ignores it -> and claude.ai packaging hard-fails on it (`skills:358`). Anything else -> `metadata:`. - -# CTX Modes - -## Inline (Default) - -Omit `context`. Runs in main conversation with full history, FM shape as in SKILL.md Format above. -DESC loaded at start, full body on invoke. Best for REF material, guidelines, background knowledge. - -## FORK (`context: fork`) - -Isolated SA, fresh CTX, no conversation access. Runs BACKGROUND by default since v2.1.218 -(`background: false` waits for the result in the invoking turn). SKILL.md body = task prompt. -CLAUDE.md loaded, EXCEPT with `agent: Explore` or `agent: Plan` (`skills:692`). -Best for standalone tasks, research, side effects. A fork with guidelines but no actionable task -returns nothing useful (`skills:685`). - -### Fork/background caveats -- decide `background` on these, not on phase count - -| Caveat | Consequence | -|--------|-------------| -| Background forks run with the **narrower background tool set** (`skills:680`, pool at `sa:349`). The SK's SA is a regular agent type, so the fork exemption does NOT cover it | A step needing a tool outside that pool silently has no tool -> set `background: false` | -| A backgrounded fork's edits land **outside session checkpoints**: `/rewind` does not undo them, only git does (`skills:682`) | Fork that WRITES -> either `background: false`, or the SK states git is the only undo | -| CC waits anyway, whatever `background` says, in 4 cases (`skills:673-678`): `-p`/Agent SDK; `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`; a second invocation while the first still runs; a scheduled task firing the SK | !=design a SK around "it returns immediately" | - -```yaml ---- -name: deep-research -description: Research a topic thoroughly -context: fork -agent: Explore ---- - -Research $ARGUMENTS: -1. Find and read relevant files using Glob/Grep -2. Summarize with file references -``` - -## Memory Behavior - -| Mode | Phases | Behavior | -|------|--------|----------| -| Inline | Any | Full conversation access | -| `fork` | 1-4 | Works well, CTX isolated | -| `fork` | 5+ | Memory loss -- forgets task structure, skips phases | - -`context: fork` -> CTX fades over extended execution. Multi-phase: use inline | hooks/external state files (e.g. TASK.md, a progress log). - -## Decision Matrix - -| Question | Answer | Mode | -|----------|--------|------| -| Needs conversation history? | Yes | Inline (omit `context`) | -| Standalone quick task (<4 phases)? | Yes | `context: fork` | -| Multi-phase orchestration (4+ phases)? | Yes | Inline + hooks/external state | -| Simple research/analysis? | Yes | `context: fork` + `agent: Explore` | -| Fork needs a tool outside the background pool (`sa:349`)? | Yes | `background: false` | -| Fork writes files and `/rewind` must work? | Yes | `background: false` -- else git is the only undo | - -# SA Spawning Constraints - -A SA CAN spawn SAs and CAN invoke skills. Default depth is **3** layers below the main conversation -(`sa:901`; env `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` overrides, `sa:905`; `1` turns nesting off). -Only AT the depth limit is `Agent` withheld -- a fork keeps it listed but it errors instead of -spawning (`sa:901`). There is NO per-session cap on total SAs (`sa:930`); the 200-spawn cap added in -2.1.212 was removed in 2.1.224 (`CL:191`). Concurrency is the live limit. - -Two filters narrow a SA's pool (`sa:337`); a fork skips both. **`AskUserQuestion` is removed from -EVERY SA, even when listed in `tools:`** (`sa:340`) -- so a SK must never instruct a spawned SA to -ask the user anything, and a SA authoring a SK cannot clarify interactively: put open questions in -its return instead. A background SA additionally keeps only the reduced built-in set, which still -includes `Skill` and `Agent` (`sa:349`). - -brewcode workflow still prefers spawns from the main conversation -- an explicit house preference, -not a platform limit: nested spawns bypass session binding + hook context injection. - -| Scenario | brewcode workflow | Why | -|----------|------------------|-----| -| SK with FORK from **main conversation** | **Use this** | Lock binding + hook context injection intact | -| SK with FORK from **SA** | **Avoid** | Bypasses session binding + coordinator loop | -| Agent tool from **SA** | **Avoid** | Nested spawn bypasses session binding + hook context injection | -| Skill tool from **SA** | **Never** | Bypasses hook context injection, and `DMI: true` SKs (all distributed brewcode/brewtools/brewdoc SKs) silently no-op — use the SK's twin AG instead | -| Inline SK (no `context`) from SA | **Avoid** | Same binding/injection bypass | - -Design: spawn from main only. For SAs use `skills:` FM (preload at startup). Multi-agent orchestration -- chain from main, not nested. - -> Sources: [Sub-agents docs](https://code.claude.com/docs/en/sub-agents) - -# Agent Field - -With `context: fork`, `agent` selects SA. - -| Agent | Model | Tools | Use for | -|-------|-------|-------|---------| -| `Explore` | Haiku | Read-only | Read-only analysis, file discovery -- fast, safe | -| `Plan` | Inherit | Read-only | Planning, structured research | -| `general-purpose` | Inherit | All | Multi-step tasks (default), code changes | - -> Only these three are built in. `developer`/`tester`/`reviewer` do NOT exist -- a generated SK naming one fails to resolve its SA on first run. - -Custom agents: `.claude/agents/` | `~/.claude/agents/` via `agent: my-custom-agent`. - -# Model Selection - -| Model | Use Case | Examples | -|-------|----------|----------| -| `fable` | Mythos-class tier above Opus (alias -> canonical `claude-fable-5`, v2.1.170) | Hardest reasoning/orchestration | -| opus | Complex orchestration, multi-phase | setup, create, review | -| sonnet | Medium complexity, optimization | rules, convention | -| haiku | Simple, fast, cleanup | teardown, clean-cache | - -# Tool Pre-Approval vs Restriction - -`AT` is a PERMISSION GRANT, not a tool allowlist. Upstream is categorical: it "does not restrict -which tools are available: every tool remains callable", and the listed tools are used "without -prompting you for approval" -- for the invoking turn only (`skills:513`; field row `skills:333`). -Workspace trust does not gate it: a project SK's grant applies even in a `-p` run inside a folder -never trusted, so "a skill can grant itself broad tool access" (`skills:515`). - -| Goal | Mechanism | -|------|-----------| -| Skip the prompt for the exact cmds the SK runs | `AT`, scoped as narrowly as the cmd allows: `Bash(git status:*)`, `Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *)` -- CC substitutes `CSD`/`${CLAUDE_PROJECT_DIR}`/`BPR`/`${CLAUDE_PLUGIN_DATA}` inside `AT` Bash rules too, so the rule matches the exact cmd the body tells Claude to run (`skills:403`, example `skills:409`) | -| Stop the SK from calling a tool at all | `DT` -- the only FM key that removes anything (`skills:334`, `skills:528`) | -| Restrict for the whole session, or across all SKs | permission settings: allow rules for a session-wide grant, deny rules to block (`skills:513`, `skills:528`) | - -Rules for every SK written: -- !=bare `Bash`, `Write`, `Edit`, `Agent` in `AT`. A bare token pre-approves EVERY invocation of that - tool, unprompted, for the turn -- the opposite of narrowing. Write the narrowest Bash pattern, or - omit the key entirely. -- `AT` is never needed to make a tool callable. `Skill`, `Agent`, `Read` and the rest are callable - with or without it; listing them only removes the prompt. -- Autonomous / background SK that must never stall on input -> `DT: AskUserQuestion` (`skills:334`). -- An injected `` !`cmd` `` whose permission check is anything but allow ABORTS the invocation; - pre-approve that exact cmd with `AT` (`skills:663-665`). A matching ask or deny rule aborts anyway. - -# Dynamic CTX Injection - -Shell cmds execute before content reaches Claude via `` !`command` ``, e.g. inside a FORK -SKILL.md body: `` - Diff: !`gh pr diff` `` -- resolves to the actual diff text before the model -ever sees the skill. Multi-line -> a fenced block opened with ` ```! ` (`skills:614`). - -| Rule | Detail | -|------|--------| -| **Failure ABORTS the whole invocation** | Not just the placeholder -- Claude never sees the SK content. Shows `Shell command failed for pattern "..."` (`skills:652`) | -| Non-zero = failure | One carveout: exit 1 from search/comparison cmds is normal and its output is injected; exit >=2 fails even for those (`skills:654`) | -| Remedy | Append `\|\| true` to any other cmd expected to exit non-zero -- e.g. a check script that exits 1 on findings (`skills:661`) | -| Permission | Injected cmds never prompt. Any check result other than allow ABORTS, including a rule that would normally ask; pre-approve the exact cmd with `AT` (`skills:663-665`) | -| CWD | The session shell's cwd, which moves with `cd`. Use `CSD` / `${CLAUDE_PROJECT_DIR}` in any path that must resolve identically every time (`skills:643`) | -| Timeout | Bash tool default 2 min; a kill at timeout aborts the invocation (`skills:645`) | -| Inline form | `` ! `` is recognized only at line start or right after whitespace -- `` KEY=!`cmd` `` stays literal (`skills:612`) | -| Single pass | Substitution runs ONCE; injected output is not re-scanned, so a cmd cannot emit a placeholder (`skills:610`) | - -# String Substitutions - -Complete set, `skills:392-401`. Nothing else is substituted. - -| Variable | Description | Since | -|----------|-------------|-------| -| `$ARGUMENTS` | All args passed on invoke. Absent from the body -> args appended as `ARGUMENTS: ` | -- | -| `$ARGUMENTS[N]` | Arg by 0-based index | -- | -| `$0`, `$1`, `$2` | Shorthand for `$ARGUMENTS[N]` | -- | -| `$name` | Named arg declared via `arguments` FM key | -- | -| `${CLAUDE_SESSION_ID}` | Current session ID | -- | -| `${CLAUDE_EFFORT}` | Active effort: `low\|medium\|high\|xhigh\|max` (ultracode reports `xhigh`) | -- | -| `CSD` | Dir containing SKILL.md. PLG SK -> the SK subdir, not the PLG root | v2.1.69 | -| `${CLAUDE_PROJECT_DIR}` | Project root -- same path hooks/MCP get as `CLAUDE_PROJECT_DIR` | v2.1.196 | -| `BPR` | PLG install dir. Substituted in PLG skills only | -- | -| `${CLAUDE_PLUGIN_DATA}` | PLG persistent data dir, survives PLG updates. PLG skills only | -- | - -> `CSD`, `${CLAUDE_PROJECT_DIR}`, `BPR`, `${CLAUDE_PLUGIN_DATA}` are substituted in TWO places: the -> SK's markdown AND Bash rules in `AT` (`skills:403`). Same variable in both = a bundled script runs -> with no prompt (`skills:409`). - -> Unfilled `$2` with only one arg stays literal; an unfilled `$name` expands to empty (`skills:421`). -> Literal `$` before a digit / `ARGUMENTS` / a declared name -> escape with one backslash: `\$1.00`. -> The escape covers ONLY those placeholders -- a backslash never blocks a `${CLAUDE_*}` var (`skills:423`). - -> `CSD` -- string substitution (NOT env var). Replaced in SKILL.md before sending to model. PLG skills -> SK subdir, not PLG root. NOT available in hooks/agents -- use `${CLAUDE_PLUGIN_ROOT}` (brace form, natively substituted) in agents, `$CLAUDE_PLUGIN_ROOT` env var in hooks. - -> `$ARGUMENTS` inside ` ```bash ``` ` = shell variable (empty/undefined), NOT CC substitution. CC replaces `$ARGUMENTS` in markdown text only. Fix: put `$ARGUMENTS` in text, use placeholder in bash block. - -# Skill Tool - -Native CC tool implementing [agentskills.io](https://agentskills.io) standard. Compatible with CC, OpenAI Codex, ChatGPT. - -``` -Skill(skill="skill-name", args="arguments") -Skill(skill="plugin:skill", args="...") -``` - -`Skill` needs no `AT` entry to be callable -- listing it only pre-approves it for the turn. -It survives both SA tool filters, so a SA can chain skills too (`sa:349`). - -# Task Tool - -Delegates work to SAs (renamed `Agent` in CC v2.1.49-74; `Task(...)` still resolves as alias). -Available in the main conversation AND in a SA, up to the depth limit -- `Agent` is withheld only at -that limit (`sa:901`). Listing `Agent` in a SA's `tools:` genuinely lets it spawn; only a type list -inside the parentheses is ignored (`sa:413`). - -| Param | REQ | Description | -|-------|-----|--------------| -| `description` | Yes | 3-5 words | -| `prompt` | Yes | Task details | -| `subagent_type` | Yes | Agent type | -| `model` | No | Override: opus, sonnet, haiku | -| `run_in_background` | No | Async execution | -| `resume` | No | Agent ID to resume | - -> Use `subagent_type`, not `agent`. `agent` does not exist in this tool. -> To keep a generated SA read-only, omit `Agent` from its `tools:` or add it to `disallowedTools` -> (`sa:917`) -- do NOT assume nesting is off by default. - -Parallel execution -- launch multiple calls in one message rather than serially. - -# Hooks Field - -```yaml -hooks: - PreToolUse: - - matcher: "Bash" - hooks: - - type: command - command: "./scripts/validate.sh" -``` - -**All hook events are supported** (`hooks:652`) -- `PreToolUse`, `PostToolUse`, `Stop` are just the -common ones. CC registers a SK's hooks when the SK is invoked and keeps running them for the REST OF -THE SESSION, including turns after the SK's own (`hooks:650`). To fire once and unregister, set -`once: true` on the hook -- honored only in SK frontmatter, ignored in settings and agent FM -(`hooks:424`). - -`PostToolUse` runs AFTER the tool, so it cannot prevent the call (`hooks:839`) -- but it is not inert: -`decision: "block"` adds a `reason` next to the tool result, and `updatedToolOutput` replaces what Claude sees (`hooks:1923`). - -> PLG caveat: SK-frontmatter hooks do not fire for PLG skills ([#17688](https://github.com/anthropics/claude-code/issues/17688)) -- use the PLG `hooks.json`. - -# DESC Optimization - -Claude uses DESC to decide when to invoke. **DESC quality is the only lever on auto-load** -- upstream publishes no rate, so compare variants against your own eval set (Step 5.5). - -| Invocation | DESC style | Note | -|------------|-----------|------| -| User-only (`DMI: true`) | Simple one-liner, NO triggers needed | LLM never auto-invokes DMI skills | -| LLM-invocable | Action verb + `Triggers:` line, third-person | Best odds of auto-load | - -Template: `description: "[Action verb sentence]. Triggers: [exact user phrases]."` - -```yaml -# BAD -- first-person, no triggers, multiline -description: | - I can help you create presentations with company colors. - Use this skill when creating slides. - -# GOOD -- third person, single line, action verb + Triggers -description: "Creates presentations with company branding and animations. Triggers: create presentation, make slides, build deck." -``` - -Rules: action verb, not "Use this skill when" - ONE line, no `|` multiline - front-load -keywords - `Triggers:` with exact user phrases - "proactively" has NO effect - cap per FM -Reference (brewcode default <=400 chars). - -Listing budget = dynamic **1% of context window** (`skillListingBudgetFraction`, default -`0.01`), not a fixed 2%/16K -- exceeding it means some skills never appear in the listing. - -### Trigger Eval Queries (OPT but REC) - -Only meaningful for a `DMI: false` SK -- a `DMI: true` SK is never model-invoked, so there is -nothing to measure. Generate 5 queries that SHOULD trigger and 5 tricky near-misses that should -NOT (share keywords, need a different tool), run them, iterate 2-3 times on misses, and report -the hit rate. `AskUserQuestion` is unavailable in a SA (`sa:340`) -- report, do not poll. - -# Body Style - -Imperative form: "Configure authentication before making requests", not "You should configure authentication". - -# REF Splitting Strategy - -## Content Organization - -| Location | Content | -|----------|---------| -| SKILL.md | Overview, instructions, examples, resource refs | -| references/ | Patterns, API docs, policies | -| scripts/ | Python, JS, Bash (pre-installed packages only) | -| assets/ | Templates, images (not loaded into CTX) | - -## When to Split - -| Criteria | Threshold | -|----------|-----------| -| Independent modes | 2+ modes with different knowledge | -| Per-mode instructions | >50 lines per mode | -| Total REF content | >300 lines combined | -| Shared vs. specific ratio | <30% shared content | - -All criteria met -> split into `references/{mode}.md`. - -## Loading Patterns - -| Pattern | When | Example | -|---------|------|---------| -| Conditional (lazy) | Multi-mode, >50 lines/mode | `superreview-setup`: detect stack -> Read `references/{stack}.md` | -| Unconditional single | Single REF, <200 lines | `brewtools:text-optimize`: always Read `references/rules-review.md` | - -## 3-Step Pattern - -DETECT mode from `$ARGUMENTS`/project analysis -> READ matching `${CLAUDE_SKILL_DIR}/references/{mode}.md` -(read at runtime -> `CSD`) -> VALIDATE: not found -> ERROR "Missing REF for {mode}", STOP. - -## Anti-Patterns - -| Anti-Pattern | Fix | -|--------------|-----| -| Load ALL refs regardless of mode | Detect mode -> load only matching | -| Inline all mode-specific content in SKILL.md | Split to `references/{mode}.md` when >50 lines | -| No validation after Read | Add "not found -> ERROR + STOP" guard | -| Generic REF names | Use mode name: `references/jvm.md`, not `references/ref1.md` | - -# Resource Path Resolution - -ONE rule, three cases -- no exceptions, no second prescription: - -| Case | Form | Why | -|------|------|-----| -| Prose pointer to a bundled doc ("see X for details") | Markdown link, relative: `[reference.md](reference.md)` | Upstream's own shape (`skills:451-457`); tells Claude what the file holds and when to load it | -| Anything EXECUTED or Read at runtime -- scripts, templates, refs the SK opens | `${CLAUDE_SKILL_DIR}/...` | CWD is the session shell's, which moves with `cd` (`skills:643`); `CSD` resolves identically every time and is substituted in `AT` Bash rules too (`skills:403`) | -| Resource in the PLG but OUTSIDE this SK's dir (shared across skills), or a path handed to an agent | `${CLAUDE_PLUGIN_ROOT}/...` | `CSD` is the SK subdir, not the PLG root (`skills:398`); an agent gets no `CSD` | - -Never a hardcoded absolute path -- it breaks on every other install. - -```yaml -# Executed -> CSD -bash "${CLAUDE_SKILL_DIR}/scripts/validate.sh" $ARGUMENTS - -# Read at runtime -> CSD -Read `${CLAUDE_SKILL_DIR}/references/api-spec.md` before generating the client. - -# Prose pointer -> markdown link -For complete API details, see [references/api-spec.md](references/api-spec.md). -``` - -**Path handed to an agent:** `BPR` (the agent has no `CSD`): - -```markdown -Agent(subagent_type="general-purpose", prompt="Read ${CLAUDE_PLUGIN_ROOT}/skills/my-skill/references/rules.md then...") -``` - -# Executable Bash - -Bash blocks = examples unless marked for execution. Template: `**EXECUTE** using Bash tool:` -label, then a fenced bash block ending `&& echo "OK" || echo "FAIL"`, then -`> **STOP if FAIL**` with recovery instructions. - -| Rule | Bad | Good | -|------|--------|---------| -| Label | ` ```bash` | `**EXECUTE**:` ` ```bash` | -| Validate | `command` | `command && echo "OK" \|\| echo "FAIL"` | -| Paths | `${CLAUDE_PLUGIN_ROOT}/skills/x/scripts/y.sh`, or a bare relative `scripts/y.sh` | `${CLAUDE_SKILL_DIR}/scripts/y.sh` -- executed, so `CSD` (see Resource Path Resolution) | - -# Location Priority - -| Scope | Path | Git | -|-------|------|-----| -| Enterprise | Managed settings | N/A | -| Personal | `~/.claude/skills/` | No | -| Project | `.claude/skills/` | Yes | -| PLG | `/skills/` | Yes | - -Priority: Enterprise > Personal > Project. PLG skills: `/plugin-name:skill-name`. Hide bundled -skills: `disableBundledSkills` setting or `CLAUDE_CODE_DISABLE_BUNDLED_SKILLS` env (v2.1.169+). - -> **Output path (v3.4.70):** SK outputs -> `.claude//` (project-relative). !=Write to `~/.claude/*` (protected-path blocks ALL modes). Exceptions: `commands|agents|skills|worktrees`. - -# Creation Process - -## Step 1: Understand - -If conversation has a workflow the user wants captured ("turn this into a SK"), extract first: -tools used + sequence, steps + corrections, input/output formats, edge cases. Confirm the -extracted workflow before proceeding. - -Resolve from the spawn brief: functionality, usage examples, trigger phrases, and **scope** -- -personal (`~/.claude/skills/`), project (`.claude/skills/`), or PLG (`/skills/`). Scope -decides Step 3's target dir and whether `BPR` is even substituted; enterprise/managed is an admin -deployment, never a local `mkdir`. - -> `AskUserQuestion` is stripped from EVERY SA even when listed in `tools:` (`sa:340`), and this -> agent runs as a SA. Do NOT plan an interactive clarification round. Missing value -> state the -> assumption explicitly and carry on, or return the question to the orchestrator unanswered. - -### Invocation Type (CRIT) - -**Unclear who will invoke -> state the assumption in the report and default to `DMI: true` -(brewcode invariant: all 27 shipped SKs are `UI-F: true` + `DMI: true`):** - -| Invocation Type | Config | DESC Style | -|-----------------|--------|------------| -| **User-only** (slash cmd) | `DMI: true` | Simple one-liner, NO triggers | -| **LLM-only** (background) | `UI-F: false` | Full triggers for auto-ACT | -| **Both** (default) | (no flags) | Full triggers for auto-ACT | - -User says "only I will call it" | "slash cmd only" -> `DMI: true` + simple DESC. - -## Step 2: Plan Contents - -**Scripts** -- tasks needing deterministic reliability. **REF docs** -- schemas, API specs, -policies (see REF Splitting Strategy for multi-mode skills). **Assets** -- templates, icons. - -## Step 3: Create Structure - -Branch on the scope resolved in Step 1 (`skills:115-120`): - -| Scope | Target | -|-------|--------| -| Project | `mkdir -p .claude/skills//{references,scripts,assets}` | -| Personal | `mkdir -p ~/.claude/skills//{references,scripts,assets}` -- one of the few `~/.claude/*` paths not protected-path blocked | -| PLG | `mkdir -p /skills//{references,scripts,assets}` -- the brewcode default; only here are `BPR`/`${CLAUDE_PLUGIN_DATA}` substituted (`skills:400-401`) | -| Enterprise/managed | Not created here -- admin deployment via managed settings | - -## Step 4: Configure - -| Question | Answer | Action | -|----------|--------|--------| -| Needs history? | Yes | Inline (omit `context`) | -| Standalone task? | Yes | `context: fork` | -| Side effects? | Yes | `DMI: true` | -| Background only? | Yes | `UI-F: false` | - -| Complexity | Model | Agent | -|------------|-------|-------| -| Complex orchestration | opus | GP | -| Optimization/analysis | sonnet | Explore (read-only) | -| Simple/fast | haiku | -- | - -Write SKILL.md: FM -> overview (1-2 sentences) -> instructions (imperative) -> resource refs. -**Word budget:** 1,500-2,000 words. Move excess to `references/`. - -## Step 5: Validate - -**EXECUTE** validate-skill.sh: -```bash -bash "${CLAUDE_PLUGIN_ROOT}/skills/skills/scripts/validate-skill.sh" path/to/skill && echo "OK" || echo "FAIL" -``` - -### Structure Checklist - -| Check | Details | -|-------|---------| -| Structure | SKILL.md with valid YAML FM | -| `name` | <=64 chars, lowercase-hyphens, == dir name, no `:` prefix | -| `description` | Per FM Reference caps, third-person, what+when + 3-5 distinct triggers, no filler | -| FM keys | Every key is in the supported set (`skills:326-345`). No `cli:`/`version:`/`updated:` -- undocumented keys hard-fail claude.ai packaging (`skills:358`) | -| `argument-hint` | Prompt-first: starts `[prompt]`. Exempt SKs (prompt-contract.md section 5) still keep it | -| Prompt contract | Body has `## Prompt contract` section + a `PLAN --` block with all 5 labels (`INPUT:`/`MODE:`/`SCOPE:`/`DO:`/`RESULT:`); 2+ modes -> keyword table has `Mutates?` col + >=1 Cyrillic keyword. Exempt SKs skip this row -- see prompt-contract.md section 5 | -| Body | <500 lines, imperative form | -| `context` | `fork` if standalone | -| `agent` | Appropriate type | -| `model` | Based on complexity | -| `AT` | Pre-approval only. No bare `Bash`/`Write`/`Edit`/`Agent`; narrowest Bash patterns, or key omitted | -| `DT` | Present when the SK must never call a tool (autonomous SK -> `AskUserQuestion`) | -| Examples | Working | -| Secrets | None hardcoded | -| Bash | EXECUTE keyword, `&& OK \|\| FAIL`, dynamic paths | - -### ACT Checklist (CRIT) - -| Check | Details | -|-------|---------| -| Action verb + Triggers | DESC starts with action verb + includes `Triggers:` line | -| Triggers present | `Triggers: deploy, release, ship to prod` | -| Single line | No multiline `\|`, within FM Reference caps | -| Third-person | "Deploys..." not "I deploy..." or "Use this to..." | -| CRIT -> slash | `DMI: true` for risky ops | - -Test: say the trigger phrase (should auto-load), say "Use [skill-name] skill to..." (higher ACT), -say `/skill-name` (works unless an ACT Reality caveat applies). Test 1 fails but `/name` works -> optimize DESC or -switch to `DMI: true`. - -## Step 5.5: Quick Eval - -After validation, write 3-5 realistic prompts a real user would say (file paths, casual speech, -abbreviations -- not "Format this data" but "ok I have this csv in ~/Downloads/sales_q4.csv and -need to add a profit margin column"). - -The check is a **paired baseline**: run each prompt in a FRESH session with the SK available and -again with it disabled, then compare (`skills:791`). A fresh session matters -- leftover authoring -context masks gaps in the written instructions. Two questions, measured separately, and which ones -apply depends on `DMI`: - -| SK | Trigger question | Output question | How to run | -|----|------------------|-----------------|------------| -| `DMI: true` (every shipped brewcode SK) | **Skip** -- the model never auto-invokes it (`skills:331`), and it is not preloaded into SAs either | Measure | Fresh `claude -p` session invoking `/name` explicitly. !=spawn a SA "with the SK": a SA invoking a `DMI: true` SK silently no-ops, so a SA-based run measures nothing | -| `DMI: false` | Measure -- did the prompt alone load it? | Measure | Fresh session per prompt; disable via `skillOverrides: "off"` for the baseline half (`skills:759`) | - -Wasted steps? All runs writing similar helper scripts -> bundle into `scripts/`. Issues found -> -fix + re-run; all good -> Step 6. Heavyweight version of this loop (evals.json, per-case isolation, -grading, A/B): `skill-creator@claude-plugins-official` (`skills:793-812`). - -## Step 5.7: Unit Tests - -Generate unit tests for `scripts/`. Skip if no scripts exist. Replace `SKILL_DIR` with the -actual SK dir path from Step 3. - -**EXECUTE** detect scripts: `ls "${SKILL_DIR}/scripts/"*.{sh,mjs,py} 2>/dev/null | head -20` - -If scripts found: `mkdir -p "${SKILL_DIR}/tests"`, then for each script generate -`tests/test-{script-name}.sh` from this skeleton (PASS/FAIL counters, exit non-zero on any -FAIL) covering: script exists, script executable, runs without error (`--help`), plus -script-specific assertions: - -```bash -#!/bin/bash -pass=0; fail=0 -check() { - local name="$1"; shift - if "$@" >/dev/null 2>&1; then - echo "PASS: $name"; pass=$((pass+1)) - else - echo "FAIL: $name"; fail=$((fail+1)) - fi -} -check "script exists" test -f "${SKILL_DIR}/scripts/foo.sh" -check "script executable" test -x "${SKILL_DIR}/scripts/foo.sh" -check "runs --help" "${SKILL_DIR}/scripts/foo.sh" --help -echo "pass=$pass fail=$fail"; [ "$fail" -eq 0 ] -``` - -**EXECUTE** run all tests, fix failures (max 2 cycles): -```bash -for t in "${SKILL_DIR}/tests"/test-*.sh; do - bash "$t" && echo "OK $(basename "$t")" || echo "FAIL $(basename "$t")" -done -``` - -> **STOP if after 2 fix cycles** -- document failures, proceed to next step. - -## Step 5.8: README Generation - -Generate `README.md` in SK dir using template. - -1. Read template: `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/readme-template.md` -2. Fill placeholders from SK metadata: `{SKILL_NAME}` (FM `name`), `{ONE_LINE_DESCRIPTION}` - (FM `description` first sentence), `{ARGUMENT_HINT}` (FM `argument-hint` | empty), - `{TODAY}` (ISO date), Modes/args/examples from SKILL.md body -3. Remove inapplicable sections (single-mode -> drop Modes table; no scripts -> drop from Files) -4. Write `README.md` to SK dir, under 100 lines, actual examples not generic placeholders - -## Step 6: Iterate - -Refine based on real-world usage feedback. Check Claude's thinking to verify triggering. After -running test cases, read transcripts -- repeated similar helper scripts across runs means write -the common script once in `scripts/` and REF it from SKILL.md. - -# Common Mistakes - -## Structure & Syntax - -| Mistake | Fix | -|---------|-----| -| Colon in DESC | Remove `:` -- breaks YAML | -| >500 lines | Move to references/ | -| Missing FORK for tasks | Add `context: fork` | -| Wrong agent | Explore=read-only, GP=full | -| Hardcoded secrets | Use MCP | -| Multipurpose | Split into focused skills | -| Unmarked bash | Add EXECUTE keyword | -| `$ARGUMENTS` in bash block | Move to text, use placeholder | -| All refs loaded unconditionally in multi-mode | Detect mode -> load matching `references/{mode}.md` only | -| Using `BPR` for own scripts in SKILL.md | Use `CSD` -- it's the SK's own dir | -| Treating `CSD` as env var | It's string substitution in SKILL.md only, not available in hooks/agents | -| `skill.md` (lowercase) | Must be `SKILL.md` (uppercase) -- lowercase silently ignored ([#17417](https://github.com/anthropics/claude-code/issues/17417)) | -| `context: fork` with 5+ phases | Memory loss -- use inline + external state | -| Reserved SK names (`anthropic`, `claude`) | SK won't load -- avoid these two reserved words | -| DESC over spec/listing caps | May be truncated -- front-load keywords, cut filler | -| Invented FM key (`cli:`, `version:`, `updated:`) | Use a supported key or `metadata:` -- CC ignores the rest and claude.ai packaging hard-fails (`skills:358`) | -| Bare `Bash`/`Write`/`Edit` in `AT` "to restrict" | `AT` pre-approves, never restricts (`skills:513`). Narrowest Bash pattern, or drop the key; restrict via `DT` | -| Expecting `AT` to keep a tool out of the SK's reach | Every tool stays callable regardless (`skills:513`) -- use `DT` or a deny rule | -| `argument-hint` starts with a mode token, not `[prompt]` | Prompt is always position 1 -- `[prompt] [mode1\|mode2]`, never `` alone | -| No `## Prompt contract` section / no `PLAN --` block before the first action | Paste the boilerplate from prompt-contract.md section 6, substitute `:` and `` | -| Mode table with EN keywords only, no RU column / no `Mutates?` col | Every mode row needs EN + RU keywords and a `Mutates?` value -- copy the shape from `semble-setup/references/intent-routing.md` | - -## ACT Mistakes (kill auto-load) - -| Mistake | Fix | -|---------|-----| -| Summary WITHOUT triggers | Include BOTH action verb sentence AND `Triggers:` line | -| No `Triggers:` line | Add `Triggers: deploy, release, ship to prod` | -| Starts with "Use this skill when" | Start with action verb: "Deploys..." not "Use this skill when deploying" | -| Vague DESC | Specific: "Deploy to k8s" not "Helps with deployment" | -| First-person DESC | Third-person: "Deploys..." not "I deploy..." | -| Second-person body | Imperative: "Do X" not "You should do X" | -| CRIT without slash | `DMI: true` for CRIT ops | -| Too many skills | Beyond the dynamic listing budget -> some invisible | -| PLG skills: `DMI` ignored | PLG skills always in CTX ([#22345](https://github.com/anthropics/claude-code/issues/22345), unconfirmed against 2.1.233) -- copy to `.claude/skills/` if parity needed | - -# Final Step - -Run optimization: `Task(subagent_type="brewtools:text-optimizer", prompt="Optimize path/to/SKILL.md. Output report with metrics.")` -`brewtools` absent (`text-optimizer` unavailable) -> skip, note it in the report. - -# Return Contract - -Verdict first, <=30 lines, `path:line`. !=SKILL.md body, !=REF contents, !=validator transcripts, !=eval logs, !=preamble. This holds whether or not a return guard is installed. Return: SK dir path, one line per artifact written (SKILL.md, each `references/*`, scripts, tests, README), `validate-skill.sh` verdict (pass, or the failing check), Quick Eval result (triggered / missed, N of M), text-optimizer run or skipped. -Eval transcripts, full validator output, draft bodies -> `.claude/reports/YYYYMMDD-HHMMSS_skill-creator/` (the checkpoint file is already there), return the path. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. - -# Troubleshooting ACT - -## SK Not Auto-Activating - -| Symptom | Cause | Fix | -|---------|-------|-----| -| Never ACTs | Beyond listing budget | Check `/skills` listing; trim skill count or DESC length | -| Never ACTs | DESC is summary | Rewrite with triggers only | -| Sometimes ACTs | Weak keywords | Add explicit "Trigger keywords:" | -| Was working, stopped | CTX compaction | Reattaches under 5K/skill, 25K combined budget; re-invoke `/name` if evicted | -| Claude ignores instruction | Attention competition | Fewer skills | explicit `/name` | - -## Debug Steps - -1. Ask "What skills do you have?" -- not listed -> budget exceeded -2. Check thinking (if visible) for the SK name -- absent -> DESC not matching -3. Test explicit `/skill-name` -- works -> ACT issue; fails -> SK broken -4. Force test: "Use skill-name skill to do X" -- naming the SK is the strongest hint short of `/name` - -# Known Bugs - -| # | Bug | Impact | Status | Workaround | -|---|-----|--------|--------|------------| -| [#39686](https://github.com/anthropics/claude-code/issues/39686) | claude.ai skills silently injected (~6000 tokens) | 37% of SK budget consumed; no opt-out | Open | No workaround | -| [#22345](https://github.com/anthropics/claude-code/issues/22345) | PLG skills ignore `DMI` | PLG skills always in CTX (~4400 tokens) | Open, unconfirmed against 2.1.233 | No workaround | -| [#17688](https://github.com/anthropics/claude-code/issues/17688) | SK-scoped hooks don't fire in PLGs | Hooks from SKILL.md FM not working for PLG skills | Open | Use PLG hooks.json | -| [#35641](https://github.com/anthropics/claude-code/issues/35641) | `/reload-plugins` doesn't load skills from new PLGs | Skills emitter not called on reload | Open | `/reload-skills` (v2.1.152) re-scans SK dirs without restart | -| [#33080](https://github.com/anthropics/claude-code/issues/33080) | Same-name skill resolution surprises users | A non-bundled (project/personal) skill overrides a same-name bundled skill, no notification | Open | Namespace prefix (e.g., `my-`) if collision unwanted | -| [#17417](https://github.com/anthropics/claude-code/issues/17417) | `skill.md` (lowercase) silently ignored | SK not discovered | Open | Use `SKILL.md` (uppercase) | -| [#36031](https://github.com/anthropics/claude-code/issues/36031) | User-level skills listed in Desktop autocomplete but not invoked | SKILL.md not loaded in Desktop app | Open, unconfirmed against 2.1.233 | Use CLI | -| [#10768](https://github.com/anthropics/claude-code/issues/10768) / [#15136](https://github.com/anthropics/claude-code/issues/15136) | Auto-ACT unreliable, sometimes skipped despite instructions | SK not invoked on relevant request | Closed (NOT PLANNED) | Optimize DESC, then `/name` | - -# Version History (earlier fixes, no inline home) - -| Version | Change | -|---------|--------| -| v2.1.76 | `/effort` slash command | -| v2.1.74 | Fix: `ask` rules bypassed via AT | -| v2.1.73 | Fix: deadlock on mass SK file changes | -| v2.1.72 | Fix: built-in slash cmds hidden; SK hooks dropped | -| v2.1.69 | Security: nested discovery skips gitignored dirs | -| v2.1.47 | Fix: crash on numeric `name`/`description`; `argument-hint` YAML sequence | -| v2.1.45 | PLG skills available immediately after install (no restart) | - -# Sources - -[CC Skills](https://code.claude.com/docs/en/skills) | [Custom Subagents](https://code.claude.com/docs/en/sub-agents) | [Skill Best Practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) | [agentskills.io](https://agentskills.io) -- [GitHub #12541](https://github.com/anthropics/claude-code/issues/12541) -- feature request that led to `CSD` +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/frontmatter-fields.md` | Deciding or checking any frontmatter field; fixing a validator "unknown key" warning | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/design-patterns.md` | Choosing a structure pattern (REF splitting, agents-as-refs, FORK), resource paths, executable bash, generating the unit-test skeleton | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/execution-model.md` | Configuring `context`/`agent`/`model`/tools, dynamic CTX injection, string substitutions, `Skill`/`Task` params | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/activation-and-troubleshooting.md` | Writing or debugging description + triggers, activation not working, known bugs | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/prompt-contract.md` | Every SK you create or improve -- mandatory | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/review-prompt.md` | Spawned to review, or applying review findings | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/readme-template.md` | Step 8 README generation | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/summary-template.md` | Populating the final report handed back to the orchestrator | +| `${CLAUDE_PLUGIN_ROOT}/skills/skills/references/e2e-template.md` | Deep testing depth E2E scenarios | diff --git a/brewcode/agents/tests/suite-creator-contract.mjs b/brewcode/agents/tests/suite-creator-contract.mjs index 30281c9..1cb9390 100644 --- a/brewcode/agents/tests/suite-creator-contract.mjs +++ b/brewcode/agents/tests/suite-creator-contract.mjs @@ -1,14 +1,19 @@ #!/usr/bin/env node /** - * suite-creator-contract.mjs - pins the Claude Code 2.1.233 facts that the three + * suite-creator-contract.mjs - pins the Claude Code 2.1.269 facts that the three * creator agents (hook-creator, skill-creator, agent-creator) teach, so a future * drift fails a test instead of shipping silently. * - * Regression suite, not a style linter: every check maps to a High/destructive - * finding from the v6.0.0 review. Evidence of record for the expected values is - * the upstream snapshot `docs/hooks.md` / `docs/sub-agents.md` of 2026-08-15 - - * the fixtures below ARE the transcription of it, deliberately hardcoded so the - * suite runs standalone (no network, no MCP, no evidence dir). + * The 2026-09-12 restructure split each agent into a short body + on-demand + * `references/*.md` files. Most facts this suite pins now live in a reference, + * not the agent body -- every such check is TWO asserts: (1) the reference still + * states the fact, (2) the agent body still cites that reference's path. A check + * that only verified "agent body contains X" before must never collapse back to + * one assert just because the text moved. + * + * Evidence of record: `.claude/reports/20260912-173000_agents-refresh/delta-{hooks,agents,skills}.md` + * (fetched 2026-09-12, upstream docs as of CC 2.1.269). Fixtures below are the + * transcription of it, hardcoded so the suite runs standalone (no network, no MCP). * * Assertion policy: unconditional exact-equality / exact-set checks with a * description. No branching decides which asserts run. Every failure names the @@ -17,33 +22,50 @@ * Usage: node brewcode/agents/tests/suite-creator-contract.mjs */ import { execFileSync } from 'node:child_process'; -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const HERE = join(fileURLToPath(import.meta.url), '..'); // agents/tests/ const AGENTS = join(HERE, '..'); // brewcode/agents/ +const PLUGIN_ROOT = join(AGENTS, '..'); // brewcode/ const FILES = ['hook-creator.md', 'skill-creator.md', 'agent-creator.md']; const text = Object.fromEntries( FILES.map((f) => [f, readFileSync(join(AGENTS, f), 'utf8')]), ); +// Every `${CLAUDE_PLUGIN_ROOT}/.../*.md` reference token the three bodies cite +// (their "Read on demand" tables) -- loaded once, keyed by basename. A missing +// file is NOT skipped: it is recorded and reported as a defect (refs.exist below). +const CITED = [...new Set(FILES.flatMap((f) => + [...text[f].matchAll(/\$\{CLAUDE_PLUGIN_ROOT\}\/[^\s`|]+\.md/g)].map((m) => m[0]), +))]; +const REF_PATH = {}; +for (const token of CITED) { + REF_PATH[token.split('/').pop()] = token.replace('${CLAUDE_PLUGIN_ROOT}', PLUGIN_ROOT); +} +const REFS = Object.keys(REF_PATH); +for (const base of REFS) { + text[base] = existsSync(REF_PATH[base]) ? readFileSync(REF_PATH[base], 'utf8') : ''; +} +const ALL_KEYS = [...FILES, ...REFS]; + // ---------------------------------------------------------------- fixtures -/** The 31 hook events, verbatim from docs/hooks.md `### ` section headings. */ -const EVENTS_31 = [ +/** The 33 hook events, verbatim from `hooks-events.md` "## All 33 Hook Events". */ +const EVENTS_33 = [ 'ConfigChange', 'CwdChanged', 'DirectoryAdded', 'Elicitation', 'ElicitationResult', 'FileChanged', 'InstructionsLoaded', 'MessageDisplay', 'Notification', 'PermissionDenied', - 'PermissionRequest', 'PostCompact', 'PostToolBatch', 'PostToolUse', 'PostToolUseFailure', - 'PreCompact', 'PreToolUse', 'SessionEnd', 'SessionStart', 'Setup', - 'Stop', 'StopFailure', 'SubagentStart', 'SubagentStop', 'TaskCompleted', - 'TaskCreated', 'TeammateIdle', 'UserPromptExpansion', 'UserPromptSubmit', 'WorktreeCreate', - 'WorktreeRemove', + 'PermissionRequest', 'PostCompact', 'PostModelSwitch', 'PostToolBatch', 'PostToolUse', + 'PostToolUseFailure', 'PreCompact', 'PreModelSwitch', 'PreToolUse', 'SessionEnd', + 'SessionStart', 'Setup', 'Stop', 'StopFailure', 'SubagentStart', 'SubagentStop', + 'TaskCompleted', 'TaskCreated', 'TeammateIdle', 'UserPromptExpansion', 'UserPromptSubmit', + 'WorktreeCreate', 'WorktreeRemove', ]; -/** hook-creator.md writes events in its own DICT abbreviations. */ +/** hooks-events.md writes some events in the house DICT abbreviations. */ const ABBREV = { SS: 'SessionStart', PTU: 'PreToolUse', POT: 'PostToolUse', PR: 'PermissionRequest', PCD: 'PostCompact', MD: 'MessageDisplay', @@ -53,19 +75,22 @@ const HANDLER_TYPES = ['agent', 'command', 'http', 'mcp_tool', 'prompt']; const SESSIONSTART_SOURCES = ['clear', 'compact', 'fork', 'resume', 'startup']; +/** 12 values -- 2.1.267 added `account_on_hold` + `cloud_credential_error` (was 10). */ const STOPFAILURE_TYPES = [ - 'authentication_failed', 'billing_error', 'invalid_request', 'max_output_tokens', - 'model_not_found', 'oauth_org_not_allowed', 'overloaded', 'rate_limit', - 'server_error', 'unknown', + 'account_on_hold', 'authentication_failed', 'billing_error', 'cloud_credential_error', + 'invalid_request', 'max_output_tokens', 'model_not_found', 'oauth_org_not_allowed', + 'overloaded', 'rate_limit', 'server_error', 'unknown', ]; +/** 12 values -- 2.1.234 added the 3 `quota_auto_resume_*` values (was 9). */ const NOTIFICATION_TYPES = [ 'agent_completed', 'agent_needs_input', 'auth_success', 'elicitation_complete', - 'elicitation_dialog', 'elicitation_response', 'elicitation_url_dialog', - 'idle_prompt', 'permission_prompt', + 'elicitation_dialog', 'elicitation_response', 'elicitation_url_dialog', 'idle_prompt', + 'permission_prompt', 'quota_auto_resume_disabled', 'quota_auto_resume_fired', + 'quota_auto_resume_stale', ]; -const REF_VER = '2.1.233'; +const REF_VER = '2.1.269'; /** Claims that were true once and are now wrong; a hit, in any casing, is a regression. */ const BANNED = [ @@ -75,6 +100,16 @@ const BANNED = [ ['denial_reason', 'the PermissionDenied stdin field is `reason`'], ]; +/** Reference each agent body must cite because the fact-checks below read it there. */ +const REQUIRED_CITES = { + 'hook-creator.md': [ + 'hooks-events.md', 'hooks-types-config.md', 'hooks-env.md', + 'hooks-templates.md', 'hooks-io-contract.md', + ], + 'skill-creator.md': ['frontmatter-fields.md'], + 'agent-creator.md': ['agent-frontmatter-fields.md', 'agent-scope-and-tools.md', 'agent-template.md'], +}; + // ---------------------------------------------------------------- helpers let passed = 0; @@ -97,7 +132,7 @@ function check(name, actual, expected, message) { } } -/** Lines of `file` between the two anchors, end-exclusive. */ +/** Lines of `file` between the two anchors, end-exclusive. `endAnchor` may be absent (runs to EOF). */ function section(file, startAnchor, endAnchor) { const lines = text[file].split('\n'); const from = lines.findIndex((l) => l.startsWith(startAnchor)); @@ -128,10 +163,9 @@ function lineAt(file, index) { } /** - * Text windows in which a file talks about PostToolUse: every `PostToolUse` mention - * (never the distinct PostToolUseFailure event) plus the sec.1 routing row `| POT |`. - * A claim about POT counts only if it lives inside one of these - a claim about Stop - * or UserPromptExpansion elsewhere in the file must not stand in for it. + * Text windows in which `file` talks about PostToolUse: every `PostToolUse` mention + * (never the distinct PostToolUseFailure event) plus a `| POT |` row start. A claim + * about POT counts only if it lives inside one of these. */ function potWindows(file) { const t = text[file]; @@ -139,6 +173,11 @@ function potWindows(file) { .map((m) => t.slice(m.index, m.index + 260)); } +/** Whether `file`'s body cites `refBase` (its Read-on-demand table names the file). */ +function cites(file, refBase) { + return new RegExp(esc(refBase)).test(text[file]); +} + /** Runs a hook template with `stdin` and returns its non-empty stdout lines. */ function runHookTemplate(source, stdin) { const dir = mkdtempSync(join(tmpdir(), 'creator-contract-')); @@ -154,32 +193,78 @@ function runHookTemplate(source, stdin) { } } +// -------------------------------------------------- 0. restructure integrity + +{ + const missing = REFS.filter((base) => !existsSync(REF_PATH[base])); + check('refs.exist', missing, [], + 'every path cited in a "Read on demand" table must exist on disk'); + + const tooLong = REFS + .filter((base) => text[base] && text[base].split('\n').length > 200) + .map((base) => `${base}:${text[base].split('\n').length}`); + check('refs.maxLines200', tooLong, [], + 'every cited reference file must stay <=200 lines (on-demand loading budget)'); + + function bodyWords(file) { + const parts = text[file].split(/^---$/m); + return parts.slice(2).join('---').trim().split(/\s+/).filter(Boolean).length; + } + const overWords = FILES + .filter((f) => bodyWords(f) > 1500) + .map((f) => `${f}:${bodyWords(f)}`); + check('body.maxWords1500', overWords, [], + 'each agent body (measured after frontmatter) must stay <=1500 words'); + + function frontmatter(file) { + return text[file].split(/^---$/m)[1] || ''; + } + const noName = FILES.filter((f) => !/^name:\s*\S+/m.test(frontmatter(f))); + check('fm.name', noName, [], 'every creator agent frontmatter must carry `name`'); + + const noModelInherit = FILES.filter((f) => !/^model:\s*inherit\s*$/m.test(frontmatter(f))); + check('fm.modelInherit', noModelInherit, [], + 'every creator agent frontmatter must carry `model: inherit`'); + + const noBash = FILES.filter((f) => { + const toolsLine = frontmatter(f).split('\n').find((l) => l.startsWith('tools:')) || ''; + return !/\bBash\b/.test(toolsLine); + }); + check('fm.toolsBash', noBash, [], 'every creator agent frontmatter `tools:` must include `Bash`'); + + for (const [file, refs] of Object.entries(REQUIRED_CITES)) { + const missingCite = refs.filter((r) => !cites(file, r)); + check(`cite.${file}`, missingCite, [], + `${file} must cite every reference that now teaches a fact this suite checks there`); + } +} + // ---------------------------------------------------------------- 1. roster { - const rows = section('hook-creator.md', '## 2. All 31 Hook Events', '### Common stdin') + const rows = section('hooks-events.md', '## All 33 Hook Events', '### Common stdin') .filter((l) => /^\| \d+ \|/.test(l)) .map((l) => l.split('|')[2].trim()); const expanded = rows.map((e) => ABBREV[e] || e).sort(); - check('roster.count', rows.length, 31, - 'hook-creator.md sec.2 must list every hook event of docs/hooks.md exactly once'); - check('roster.set', expanded, EVENTS_31, - 'hook-creator.md sec.2 event names must equal the 31 headings of docs/hooks.md'); - check('roster.heading', hits('hook-creator.md', /^## 2\. All 31 Hook Events$/).length, 1, - 'hook-creator.md sec.2 heading must state the same count it lists'); + check('roster.count', rows.length, 33, + 'hooks-events.md must list every hook event exactly once'); + check('roster.set', expanded, EVENTS_33, + 'hooks-events.md event names must equal the 33 current events'); + check('roster.heading', hits('hooks-events.md', /^## All 33 Hook Events$/).length, 1, + 'hooks-events.md heading must state the same count it lists'); } // ------------------------------------------------------------ 2. handler types { - const types = section('hook-creator.md', '## 3. Hook Types', '> `prompt`/`agent` = gates') + const types = section('hooks-types-config.md', '## Hook Types', '> `prompt`/`agent` = gates') .filter((l) => /^\| `/.test(l)) .map((l) => l.split('|')[1].trim().replace(/`/g, '')) .sort(); check('types.set', types, HANDLER_TYPES, - 'hook-creator.md sec.3 must document exactly the 5 handler types'); + 'hooks-types-config.md must document exactly the 5 handler types'); const header = hits('hook-creator.md', - /^> Ref ver: .* \| 5 hook types \(command, http, mcp_tool, prompt, agent\)$/); + /^> Ref ver: .* \| 5 hook types \(command, http, mcp_tool, prompt, agent\)/); check('types.header', header.length, 1, 'hook-creator.md header line must name the same 5 handler types'); } @@ -187,51 +272,48 @@ function runHookTemplate(source, stdin) { // ------------------------------------------------- 3. matcher rows (N1/N2/N3/N5) { - const sec11 = section('hook-creator.md', '## 11. Matcher Patterns', '## 12.'); - const row = (re) => sec11.find((l) => re.test(l)) || ''; + const matcherSec = section('hooks-events.md', '## Matcher Patterns', '\u0000-never-matches'); + const row = (re) => matcherSec.find((l) => re.test(l)) || ''; check('matcher.noMatcher.PostToolBatch', /PostToolBatch/.test(row(/\| No matcher \|/)), true, - 'hook-creator.md sec.11 no-matcher row must list PostToolBatch (N5)'); + 'hooks-events.md no-matcher row must list PostToolBatch (N5)'); check('matcher.noMatcher.PermissionDenied', /PermissionDenied/.test(row(/\| No matcher \|/)), false, - 'hook-creator.md sec.11 PermissionDenied takes a tool-name matcher, not the no-matcher row (H02)'); + 'hooks-events.md PermissionDenied takes a tool-name matcher, not the no-matcher row (H02)'); check('matcher.toolName.PermissionDenied', /PermissionDenied/.test(row(/\| tool name \|/)), true, - 'hook-creator.md sec.11 must put PermissionDenied on the tool-name matcher row (H02)'); + 'hooks-events.md must put PermissionDenied on the tool-name matcher row (H02)'); check('matcher.SessionStart.sources', tokens(row(/^\| SS \| source string \|/)), SESSIONSTART_SOURCES, - 'hook-creator.md sec.11 SessionStart sources must include `fork` (N1)'); + 'hooks-events.md SessionStart sources must include `fork` (N1)'); check('matcher.StopFailure.types', tokens(row(/^\| StopFailure \|/)), STOPFAILURE_TYPES, - 'hook-creator.md sec.11 StopFailure must carry all 10 error types (N2)'); + 'hooks-events.md StopFailure must carry all 12 error types (N2, 2.1.267)'); check('matcher.Notification.types', tokens(row(/^\| Notification \|/)), NOTIFICATION_TYPES, - 'hook-creator.md sec.11 Notification must carry all 9 notification types (N3)'); + 'hooks-events.md Notification must carry all 12 notification types (N3, 2.1.234)'); - // N2 second location: the sec.2 event table row for StopFailure. - const sec2Row = section('hook-creator.md', '## 2. All 31 Hook Events', '### Common stdin') - .find((l) => /^\| 22 \| StopFailure \|/.test(l)) || ''; + // N2/N1 second location: the events-table row for StopFailure/SessionStart, same file. + const eventsSec = section('hooks-events.md', '## All 33 Hook Events', '### Common stdin'); + const sec2Row = eventsSec.find((l) => /^\| 22 \| StopFailure \|/.test(l)) || ''; check('matcher.StopFailure.types.sec2', tokens(sec2Row.split('|')[4] || ''), STOPFAILURE_TYPES, - 'hook-creator.md sec.2 StopFailure row must carry the same 10 error types as sec.11 (N2)'); + 'hooks-events.md events-table StopFailure row must carry the same 12 error types as Matcher Patterns (N2)'); - const ssRow = section('hook-creator.md', '## 2. All 31 Hook Events', '### Common stdin') - .find((l) => /^\| 1 \| SS \|/.test(l)) || ''; + const ssRow = eventsSec.find((l) => /^\| 1 \| SS \|/.test(l)) || ''; check('matcher.SessionStart.sources.sec2', tokens(ssRow.split('|')[4] || ''), SESSIONSTART_SOURCES, - 'hook-creator.md sec.2 SessionStart row must carry the same 5 sources as sec.11 (N1)'); + 'hooks-events.md events-table SessionStart row must carry the same 5 sources as Matcher Patterns (N1)'); } // --------------------------------------------- 4. no live 200-subagent cap { - // Any 200 near subagent talk, however phrased - `200-spawn cap`, `(default 200)`, - // `MAX_SUBAGENTS ... 200`. The sentence wraps, so the window is the number +/- 160 - // chars, not one line; inside it the removal must be stated or the number is a claim. + // Any 200 near subagent talk, however phrased, across every agent body + cited reference. const offenders = []; - for (const f of FILES) { + for (const f of ALL_KEYS) { for (const m of text[f].matchAll(/\b200\b/g)) { const around = text[f].slice(Math.max(0, m.index - 160), m.index + 160); const aboutSubagents = /subagents?\b|\bSAs?\b|spawn|MAX_SUBAGENTS/i.test(around); @@ -242,14 +324,14 @@ function runHookTemplate(source, stdin) { } } check('cap.no200', offenders, [], - 'no creator file may assert a live 200-subagent-per-session cap; it was removed in 2.1.224'); + 'no creator file or reference may assert a live 200-subagent-per-session cap; it was removed in 2.1.224'); } // -------------------------------------- 5. CLAUDE_PLUGIN_DATA is writable { const offenders = []; - for (const f of FILES) { + for (const f of ALL_KEYS) { for (const n of hits(f, /CLAUDE_PLUGIN_DATA/)) { const line = text[f].split('\n')[n - 1]; if (/block|forbidden|protected[- ]path|never a Write target|read-only/i.test(line)) { @@ -258,18 +340,18 @@ function runHookTemplate(source, stdin) { } } check('pluginData.writable', offenders, [], - 'no creator file may claim ${CLAUDE_PLUGIN_DATA} writes are blocked; it is the official writable plugin data dir (D1 Q1/H16)'); + 'no creator file or reference may claim ${CLAUDE_PLUGIN_DATA} writes are blocked; it is the official writable plugin data dir (D1 Q1/H16)'); - const asked = hits('hook-creator.md', /Sensitive-path prompt \(2\.1\.233, verified in binary\)/); + const asked = hits('hooks-env.md', /Sensitive-path prompt \(2\.1\.233, verified in binary\)/); check('pluginData.sensitivePathBlock', asked.length, 1, - 'hook-creator.md must carry D1\'s sensitive-path ASK text exactly once (H16)'); + 'hooks-env.md must carry D1\'s sensitive-path ASK text exactly once (H16)'); } // ---------------------------- 6. PostToolUse blockability, stated identically { const offenders = []; - for (const f of FILES) { + for (const f of ALL_KEYS) { for (const [needle, why] of BANNED) { for (const n of hits(f, new RegExp(esc(needle), 'i'))) { offenders.push(`${f}:${n} "${needle}" (${why})`); @@ -277,36 +359,40 @@ function runHookTemplate(source, stdin) { } } check('pot.noBannedClaims', offenders, [], - 'a banned stale claim reappeared in a creator file (N6/H03/H02/H13)'); + 'a banned stale claim reappeared in a creator file or reference (N6/H03/H02/H13)'); // Naming the field is not stating the fact: the meaning is asserted, not the token. - const stating = FILES.filter( - (f) => /`updatedToolOutput` (?:replaces|rewrites) what Claude sees/.test(text[f]), - ).sort(); - check('pot.updatedToolOutput.files', stating, ['hook-creator.md', 'skill-creator.md'], - 'the two creator files that teach hook output schemas must both state that `updatedToolOutput` replaces what Claude sees (N6)'); + // \s+ tolerates hard-wrapped prose (frontmatter-fields.md wraps mid-sentence). + const UTO_RE = /`updatedToolOutput`\s+(?:replaces|rewrites)\s+what Claude sees/; + check('pot.updatedToolOutput.hooksIoContract', UTO_RE.test(text['hooks-io-contract.md']), true, + 'hooks-io-contract.md (cited by hook-creator.md) must state `updatedToolOutput` replaces what Claude sees (N6)'); + check('pot.updatedToolOutput.skillsFrontmatterFields', UTO_RE.test(text['frontmatter-fields.md']), true, + 'frontmatter-fields.md (cited by skill-creator.md) must state `updatedToolOutput` replaces what Claude sees (N6)'); - const blockable = FILES - .filter((f) => potWindows(f).some((w) => /`decision: ?"block"`[^\n]*reason/.test(w))) - .sort(); - check('pot.blockable.files', blockable, ['hook-creator.md', 'skill-creator.md'], - 'both files that state PostToolUse blockability must state it the same way, in the POT row/paragraph itself: decision:"block" + reason (N6)'); + const BLOCKABLE_RE = /`decision: ?"block"`[^\n]*reason/; + check('pot.blockable.hooksIoContract', + potWindows('hooks-io-contract.md').some((w) => BLOCKABLE_RE.test(w)), true, + 'hooks-io-contract.md must state PostToolUse blockability in the POT row itself: decision:"block" + reason (N6)'); + check('pot.blockable.skillsFrontmatterFields', BLOCKABLE_RE.test(text['frontmatter-fields.md']), true, + 'frontmatter-fields.md must state PostToolUse blockability the same way: decision:"block" + reason (N6)'); } // ------------------------------------------------------ 7. reference version { - const refs = []; - for (const f of FILES) { - for (const m of text[f].matchAll(/Ref ver:\s*([0-9]+\.[0-9]+\.[0-9]+)/g)) { - refs.push(`${f}=${m[1]}`); - } - } - check('refver.values', refs.map((r) => r.split('=')[1]), [REF_VER, REF_VER], - `every creator "Ref ver:" must read ${REF_VER} (N4)`); - check('refver.files', refs.map((r) => r.split('=')[0]), - ['hook-creator.md', 'skill-creator.md'], - 'hook-creator.md and skill-creator.md are the two creators carrying a "Ref ver:" header'); + // Convention change from the restructure: only hook-creator.md still carries a + // "Ref ver:" header; skill-creator.md/agent-creator.md cite the 2.1.269 delta in + // prose instead (their own intro sentence). Guard both forms so neither can drift. + const refVerFiles = FILES.filter((f) => /Ref ver:/.test(text[f])).sort(); + check('refver.files', refVerFiles, ['hook-creator.md'], + 'only hook-creator.md carries the "Ref ver:" header post-restructure'); + + const refVerValues = refVerFiles.map((f) => (text[f].match(/Ref ver:\s*([0-9]+\.[0-9]+\.[0-9]+)/) || [])[1]); + check('refver.values', refVerValues, [REF_VER], `hook-creator.md's "Ref ver:" must read ${REF_VER} (N4)`); + + const mention = FILES.filter((f) => text[f].includes(REF_VER)).sort(); + check('refver.allMention', mention, FILES.slice().sort(), + `every creator body must mention its ${REF_VER} baseline somewhere in prose`); } // ------------------------------- 8. AskUserQuestion is not promised to a SA @@ -318,8 +404,7 @@ function runHookTemplate(source, stdin) { return /AskUserQuestion/.test(fm); }); // Ruling D1-Q3: the tool is stripped from every SA, so a declaration is inert - and - // documenting the removal does not license keeping the dead entry (agent-creator.md - // dropped it, skill-creator.md followed in v6.0.0). + // documenting the removal does not license keeping the dead entry. check('auq.declared', declaring, [], 'no creator may declare the inert AskUserQuestion in `tools:`; it is stripped from every SA (Q3)'); @@ -336,20 +421,47 @@ function runHookTemplate(source, stdin) { // ------------------------------------- 9. BC-A01: templates are fail-closed { - const tpl = section('hook-creator.md', '## 7. Templates', '## 8. Known Bugs').join('\n'); + const tpl = section('hooks-templates.md', '## Templates', '## Best Practices').join('\n'); // Behavioural, not textual: the shipped bash template is run on the stop-hook path, // the one that tempted an early `echo '{}'`. Two objects on stdout = discarded verdict. const bashTpl = (tpl.match(/```bash\n([\s\S]*?)```/) || ['', ''])[1]; check('template.bash.singleStdoutWrite', runHookTemplate(bashTpl, '{"stop_hook_active":true}'), ['{}'], - 'hook-creator.md sec.7 bash template must print exactly one JSON object on every path (BC-A01)'); + 'hooks-templates.md bash template must print exactly one JSON object on every path (BC-A01)'); check('template.decideFn', (tpl.match(/output\(decide\(await readStdin\(\)\)\)/g) || []).length, 1, - 'hook-creator.md sec.7 JS template must emit one object from one decide() call (BC-A01)'); + 'hooks-templates.md JS template must emit one object from one decide() call (BC-A01)'); check('template.noCommentedDecisions', /^\s*(#|\/\/) (?:Deny tool|Block stop|Inject context)/m.test(tpl), false, - 'hook-creator.md sec.7 templates must not park the real decision outputs in comments (BC-A01)'); + 'hooks-templates.md templates must not park the real decision outputs in comments (BC-A01)'); +} + +// ------------------------- 10. agent-creator 2.1.234-2.1.269 delta fixtures +// The facts moved to agent-{frontmatter-fields,scope-and-tools,template}.md; +// citation back to agent-creator.md is asserted once, up front, in cite.agent-creator.md. + +{ + check('experimentalCacheTtl.fieldTable', + /\|\s*`experimental\.cacheTtl`\s*\|/.test(text['agent-frontmatter-fields.md']), true, + 'agent-frontmatter-fields.md OPT Fields table must carry the experimental.cacheTtl field (2.1.248)'); + + const precedence = section('agent-scope-and-tools.md', '## Model Precedence', '## Spawn From Main Conversation Only').join('\n'); + check('modelPrecedence.forceVar', + /CLAUDE_CODE_SUBAGENT_MODEL_FORCE/.test(precedence), true, + 'agent-scope-and-tools.md Model Precedence table must mention CLAUDE_CODE_SUBAGENT_MODEL_FORCE (2.1.257)'); + + const colorSec = section('agent-template.md', '## Color Semantics', '## Common AG Types') + .filter((l) => /^\| /.test(l) && !/^\| Color \|/.test(l) && !/^\|-{2,}/.test(l)); + const colors = colorSec.flatMap((l) => l.split('|')[1].split(',').map((c) => c.trim().replace(/`/g, ''))); + check('color.noMagenta', colors.includes('magenta'), false, + 'agent-template.md Color Semantics table must not list magenta as a valid color value'); + check('color.count8', new Set(colors).size, 8, + 'agent-template.md Color Semantics table must enumerate exactly 8 valid color values'); + + const tpl = section('agent-template.md', '### 6. Guardrails', '## LLM Text Rules').join('\n'); + check('template.returnContractHeading', /^## Return Contract$/m.test(tpl), true, + 'agent-template.md generated-agent template (Guardrails) must carry a literal "## Return Contract" heading'); } // ---------------------------------------------------------------- report diff --git a/brewcode/docs/commands.md b/brewcode/docs/commands.md index 7d51aaa..f2ab3dd 100644 --- a/brewcode/docs/commands.md +++ b/brewcode/docs/commands.md @@ -6,7 +6,7 @@ description: Detailed description of all brewcode plugin commands # BC Plugin Commands -> **ver:** 6.1.4 | **Author:** Maksim Kochetkov | **License:** MIT +> **ver:** 6.2.0 | **Author:** Maksim Kochetkov | **License:** MIT ## Naming diff --git a/brewcode/docs/file-tree.md b/brewcode/docs/file-tree.md index 0dbebcf..865ca3e 100644 --- a/brewcode/docs/file-tree.md +++ b/brewcode/docs/file-tree.md @@ -4,7 +4,7 @@ description: Complete file tree of the brewcode plugin with descriptions # Brewcode Plugin - File Tree -> Version: 6.1.4 | Files: 161 | Directories: 47 (excludes the generated `.codex/` mirror; no dotfiles, `__pycache__`, or `node_modules` exist under `brewcode/`) +> Version: 6.2.0 | Files: 161 | Directories: 47 (excludes the generated `.codex/` mirror; no dotfiles, `__pycache__`, or `node_modules` exist under `brewcode/`) ## Plugin Structure @@ -12,7 +12,7 @@ description: Complete file tree of the brewcode plugin with descriptions brewcode/ # Plugin root directory │ ├── .claude-plugin/ # Claude Code plugin configuration -│ └── plugin.json # Manifest (name, version 6.1.4, skills/ reference) +│ └── plugin.json # Manifest (name, version 6.2.0, skills/ reference) │ ├── hooks/ # Node.js scripts for Claude Code events (4 hooks) │ ├── hooks.json # Binds 2 events (UserPromptSubmit, SessionStart); SessionStart has 2 groups: unmatched + matcher "compact" @@ -113,7 +113,7 @@ brewcode/ # Plugin root directory │ ├── README.md # Components, commands, agents, hooks, architecture, flow diagrams ├── INSTALL.md # Installation: plugin-dir, marketplace, embedding, troubleshooting -└── package.json # npm: claude-plugin-brewcode@6.1.4, build/publish scripts +└── package.json # npm: claude-plugin-brewcode@6.2.0, build/publish scripts ``` ## Target Project Structure diff --git a/brewcode/hooks/forced-eval.mjs b/brewcode/hooks/forced-eval.mjs index 6079d3e..64e93ab 100644 --- a/brewcode/hooks/forced-eval.mjs +++ b/brewcode/hooks/forced-eval.mjs @@ -5,20 +5,108 @@ * Event: UserPromptSubmit * Channel: hookSpecificOutput.additionalContext — updatedInput is IGNORED on * UserPromptSubmit in CC 2.1.x (silently dropped, no error). - * Payload: 3 short lines, injected on EVERY prompt — keep it tiny. + * Cadence: fires on the 1st real prompt, then every 10th (10, 20, 30, ...) — + * same session-keyed marker pattern as think-short-prompt-counter.mjs + * (private 0700 tmp dir we own, atomic write, lstat-only, planted + * symlink rejected). Meta-replies are skipped before the counter is + * touched, so they never consume or land on an inject slot. + * role-recall.mjs re-injects after compaction, so coverage stays. * Cap: 9000 chars, under the 2.1.174 10K disk-spill threshold. */ +import { lstatSync, mkdirSync, chmodSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; import { readStdin, output, capText } from './lib/utils.mjs'; // Shared with role-recall.mjs (SessionStart/compact) — one normative copy. import { REMINDER_TEXT } from './lib/reminder.mjs'; +const MARKER_DIR = path.join(os.tmpdir(), 'brewcode-forced-eval'); +const UID = typeof process.getuid === 'function' ? process.getuid() : null; +const INTERVAL = 10; + +let markerDirOk; + +/** os.tmpdir() is world-writable; accept the root only as a real dir we own, mode 0700. */ +function ensureMarkerDir() { + if (markerDirOk !== undefined) return markerDirOk; + markerDirOk = false; + try { + mkdirSync(MARKER_DIR, { recursive: true, mode: 0o700 }); + } catch { + // may already exist; validated below either way + } + try { + let st = lstatSync(MARKER_DIR); + if (!st.isDirectory() || (UID !== null && st.uid !== UID)) return markerDirOk; + if ((st.mode & 0o077) !== 0) { + chmodSync(MARKER_DIR, 0o700); + st = lstatSync(MARKER_DIR); + } + markerDirOk = (st.mode & 0o077) === 0; + } catch { + markerDirOk = false; + } + return markerDirOk; +} + +/** Counter path for a session id, or null when the id cannot name a plain file. */ +function markerPathFor(session_id) { + if (!session_id || typeof session_id !== 'string') return null; + if (!/^[A-Za-z0-9._-]{1,128}$/.test(session_id) || session_id === '.' || session_id === '..') return null; + return path.join(MARKER_DIR, `${session_id}.forced-eval-counter`); +} + +/** Previous count, or null when the marker is unreadable or not a file we own. */ +function readCount(markerPath) { + let st; + try { + st = lstatSync(markerPath); // lstat, never stat: do not follow a planted symlink + } catch { + return 0; // no marker yet -> start from 0 + } + if (!st.isFile() || (UID !== null && st.uid !== UID)) return null; + try { + const parsed = parseInt(readFileSync(markerPath, 'utf8').trim(), 10); + return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0; + } catch { + return null; + } +} + +/** tmp + rename: concurrent sessions cannot interleave, and rename never follows a link. */ +function writeAtomic(file, data) { + const tmp = `${file}.${process.pid}.tmp`; + try { + writeFileSync(tmp, data, { mode: 0o600 }); + renameSync(tmp, file); + return true; + } catch { + try { + rmSync(tmp, { force: true }); + } catch { + // ignore + } + return false; + } +} + +function bumpCounter(session_id) { + // Returns the new count (>=1), or null if counting is unavailable. + const markerPath = markerPathFor(session_id); + if (!markerPath || !ensureMarkerDir()) return null; + const prev = readCount(markerPath); + if (prev === null) return null; + const count = prev + 1; + return writeAtomic(markerPath, String(count)) ? count : null; +} + // --- Main --- async function main() { try { const input = await readStdin(); - const { prompt, hook_event_name } = input; + const { prompt, hook_event_name, session_id } = input; // Validate event type if (hook_event_name !== 'UserPromptSubmit') { @@ -37,7 +125,7 @@ async function main() { // No '/' skip: it existed for the removed skill nudge. A slash command can // still carry a task worth delegating, so the reminder applies there too. - // Skip meta-commands that carry no task to delegate + // Skip meta-commands that carry no task to delegate — never consumes a counter slot. const skipPatterns = [ /^(yes|no|y|n|ok|okay|sure|thanks|thank you|done|cancel|stop|exit|quit)$/i, /^(continue|proceed|go ahead|approved?|confirm(ed)?|accept(ed)?)$/i, @@ -50,6 +138,14 @@ async function main() { return; } + // Fire on the 1st real prompt, then every INTERVAL-th. Counting unavailable + // (bad session_id, marker dir compromised) -> skip rather than spam. + const count = bumpCounter(session_id); + if (count === null || (count !== 1 && count % INTERVAL !== 0)) { + output({}); + return; + } + // Inject the delegation reminder via additionalContext (updatedInput is // ignored on UserPromptSubmit in CC 2.1.x). output({ diff --git a/brewcode/hooks/lib/reminder.mjs b/brewcode/hooks/lib/reminder.mjs index b345e8d..848c86f 100644 --- a/brewcode/hooks/lib/reminder.mjs +++ b/brewcode/hooks/lib/reminder.mjs @@ -10,10 +10,10 @@ // tasks (ssh, deploy) bypass delegation even when a project expert existed. // SPLIT covers what models still get wrong: subagent sizing + context handoff. // No skill-activation nudge: modern models pick skills on their own. -export const MANAGER_ROLE = '[ROLE] Manager: scan agents (project .claude/agents/ first) - expert for this domain exists -> delegate regardless of size; no expert or trivial one-off -> self.'; -export const SPLIT = '[SPLIT] One agent for an hour = drift you cannot observe: split into bounded units (1 deliverable, ~5 files, ~20 min), fan out in ONE message; a dependency must be a REAL data handoff, else parallel; every spawn prompt carries goal + scope + what is already done + who consumes the result + acceptance.'; +export const MANAGER_ROLE = '[ROLE] Manager: check .claude/agents/ (project first); domain expert -> delegate regardless of size, else self.'; +export const SPLIT = '[SPLIT] One agent for an hour = drift you cannot observe: bounded units (1 deliverable, ~5 files, ~20 min), fan out in ONE message; real data handoff = dependency, else parallel; spawn prompt: goal + scope + done-so-far + consumer + acceptance.'; // BRANCH: sessions default to main and inherit the whole workspace - a branch/PR // is opt-in, stated by the user, never inferred. -export const BRANCH = '[BRANCH] Stay on the current branch; none chosen -> main. No explicit branch/PR instruction -> work on main and take over ALL workspace changes, incl. from other sessions.'; +export const BRANCH = '[BRANCH] No branch/PR instruction -> stay current, else main; take over ALL workspace changes incl. other sessions.'; export const REMINDER_TEXT = `${MANAGER_ROLE}\n${SPLIT}\n${BRANCH}`; diff --git a/brewcode/hooks/tests/suite-forced-eval.mjs b/brewcode/hooks/tests/suite-forced-eval.mjs new file mode 100644 index 0000000..2e42b26 --- /dev/null +++ b/brewcode/hooks/tests/suite-forced-eval.mjs @@ -0,0 +1,152 @@ +#!/usr/bin/env node +/** + * suite-forced-eval.mjs — forced-eval.mjs cadence (fires on prompt 1, then every + * 10th) and the role-recall.mjs text-identity invariant (one normative copy in + * hooks/lib/reminder.mjs). + * + * Self-contained: runs standalone (`node tests/suite-forced-eval.mjs`), needs no + * network and no MCP, and never touches the real OS tmp dir — every marker lands + * under an isolated TMPDIR per test block. + * + * Assertion policy: unconditional exact-equality checks with a description. + */ +import { spawnSync } from 'node:child_process'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { tmpdir } from 'node:os'; +import { fileURLToPath } from 'node:url'; + +const HERE = join(fileURLToPath(import.meta.url), '..'); // tests/ +const HOOKS = join(HERE, '..'); // brewcode/hooks/ +const HOOK = join(HOOKS, 'forced-eval.mjs'); +const HOOK_ROLE_RECALL = join(HOOKS, 'role-recall.mjs'); + +const BASE = mkdtempSync(join(tmpdir(), 'bc-forced-eval-')); + +let passed = 0; +let failed = 0; +const results = []; + +function deepEqual(a, b) { + return JSON.stringify(a) === JSON.stringify(b); +} + +function check(name, actual, expected, message) { + if (deepEqual(actual, expected)) { + passed++; + results.push(` PASS ${name} (${message})`); + } else { + failed++; + results.push( + ` FAIL ${name} (${message} | actual=${JSON.stringify(actual)} expected=${JSON.stringify(expected)})`, + ); + } +} + +/** Runs the hook once against an isolated TMPDIR; returns the parsed stdout or null. */ +function runHook(hook, stdinObj, tmp) { + const res = spawnSync(process.execPath, [hook], { + input: JSON.stringify(stdinObj), + encoding: 'utf8', + env: { ...process.env, TMPDIR: tmp }, + }); + let parsed = null; + try { + parsed = JSON.parse(res.stdout); + } catch { + // leave null + } + return { status: res.status, parsed }; +} + +const ctxOf = (r) => r.parsed?.hookSpecificOutput?.additionalContext; + +// ═══ A. cadence — fires on prompt 1, silent 2-9, fires again on 10 ═══ +{ + const tmp = join(BASE, 'a-tmp'); + const sid = 'session-cadence-a'; + const injectedAt = []; + let allExitZero = true; + let text1 = null; + let text10 = null; + + for (let i = 1; i <= 10; i++) { + const r = runHook(HOOK, { hook_event_name: 'UserPromptSubmit', session_id: sid, prompt: `real task ${i}` }, tmp); + if (r.status !== 0) allExitZero = false; + const ctx = ctxOf(r); + if (ctx !== undefined) { + injectedAt.push(i); + if (i === 1) text1 = ctx; + if (i === 10) text10 = ctx; + } + } + + check('A1.exitZeroThroughout', allExitZero, true, 'all 10 invocations exit 0'); + check('A2.injectsOnlyOnOneAndTen', injectedAt, [1, 10], 'fires on the 1st prompt and the next multiple of 10, silent on 2-9'); + check('A3.textIdenticalAcrossFires', text1 === text10 && typeof text1 === 'string' && text1.length > 0, + true, 'the same reminder text is injected on prompt 1 and prompt 10'); +} + +// ═══ B. meta-replies never consume a counter slot ═══ +{ + const tmp = join(BASE, 'b-tmp'); + const sid = 'session-meta-b'; + + const metaResults = ['yes', 'no', 'ok', '3'].map( + (p) => ctxOf(runHook(HOOK, { hook_event_name: 'UserPromptSubmit', session_id: sid, prompt: p }, tmp)), + ); + const firstRealCtx = ctxOf(runHook(HOOK, { hook_event_name: 'UserPromptSubmit', session_id: sid, prompt: 'do the real task' }, tmp)); + + check('B1.metaRepliesNeverInject', metaResults, [undefined, undefined, undefined, undefined], + 'yes/no/ok/a bare number never inject regardless of position'); + check('B2.firstRealPromptStillFiresAtCountOne', typeof firstRealCtx, 'string', + 'the first REAL prompt after any number of skipped meta-replies is still count=1 and fires'); +} + +// ═══ C. counting unavailable -> fail-open, never spam ═══ +{ + const tmp = join(BASE, 'c-tmp'); + + const noSession = runHook(HOOK, { hook_event_name: 'UserPromptSubmit', prompt: 'do it' }, tmp); + check('C1.missingSessionIdNoOps', { status: noSession.status, body: noSession.parsed }, { status: 0, body: {} }, + 'no session_id means counting is unavailable; the hook no-ops instead of always-injecting'); + + const badSession = runHook(HOOK, { hook_event_name: 'UserPromptSubmit', session_id: '../../etc/passwd', prompt: 'do it' }, tmp); + check('C2.pathUnsafeSessionIdNoOps', { status: badSession.status, body: badSession.parsed }, { status: 0, body: {} }, + 'a session_id that cannot name a safe marker file degrades to no-op, not a throw or an inject'); +} + +// ═══ D. role-recall.mjs stays byte-identical to forced-eval.mjs's injected text ═══ +{ + const roleRecall = spawnSync(process.execPath, [HOOK_ROLE_RECALL], { + input: JSON.stringify({ hook_event_name: 'SessionStart', source: 'compact' }), + encoding: 'utf8', + }); + const roleCtx = (() => { + try { + return JSON.parse(roleRecall.stdout)?.hookSpecificOutput?.additionalContext; + } catch { + return undefined; + } + })(); + + const tmp = join(BASE, 'd-tmp'); + const forcedCtx = ctxOf(runHook(HOOK, { hook_event_name: 'UserPromptSubmit', session_id: 'session-identity-d', prompt: 'first prompt' }, tmp)); + + check('D1.roleRecallFires', typeof roleCtx, 'string', 'role-recall.mjs injects on source=compact'); + check('D2.textByteIdenticalToForcedEval', roleCtx, forcedCtx, + 'both hooks import the SAME REMINDER_TEXT from lib/reminder.mjs — one normative copy'); +} + +try { + rmSync(BASE, { recursive: true, force: true }); +} catch { + // ignore +} + +// ═══════════════════════════════════════════════════════════════════════════ +console.log('\nsuite-forced-eval (cadence + role-recall text identity)'); +console.log(` base: ${BASE}`); +for (const line of results) console.log(line); +console.log(`\n passed=${passed} failed=${failed} total=${passed + failed}\n`); +process.exit(failed === 0 ? 0 : 1); diff --git a/brewcode/package.json b/brewcode/package.json index bdc6509..b7077c1 100644 --- a/brewcode/package.json +++ b/brewcode/package.json @@ -1,6 +1,6 @@ { "name": "claude-plugin-brewcode", - "version": "6.1.4", + "version": "6.2.0", "description": "Infinite task execution with automatic handoff for Claude Code", "keywords": [ "claude-code", @@ -30,6 +30,6 @@ ], "claude-plugin": { "name": "brewcode", - "version": "6.1.4" + "version": "6.2.0" } } diff --git a/brewcode/skills/agents/references/agent-context-and-execution.md b/brewcode/skills/agents/references/agent-context-and-execution.md new file mode 100644 index 0000000..3838588 --- /dev/null +++ b/brewcode/skills/agents/references/agent-context-and-execution.md @@ -0,0 +1,105 @@ +# AG Context Inheritance, SKs, Execution Modes, Resource Limits + +## SA Context Inheritance + +| Context | Inherited? | Notes | +|---------|-----------|-------| +| CD (project + user) | Yes | Via ``, with "may or may not be relevant" disclaimer. Built-in `Explore`/`Plan` SKIP it (`docs/sub-agents.md:956`) | +| `.claude/rules/*.md` | Yes | Bundled with CD injection; same `Explore`/`Plan` exception | +| Git status | Yes | Snapshot from the parent session start. `Explore`/`Plan` skip it regardless | +| Permissions | Yes | Override via `permissionMode` -- ignored for PLG AGs | +| TLs / MCP servers | Filtered | Inherited, then narrowed by the two filters -- see Available TLs in `agent-scope-and-tools.md`. `mcpServers` key ignored for PLG AGs; MCP TLs themselves survive both filters | +| SKs from `skills:` field | Yes | Full content injected at startup | +| AG memory (`memory:` field) | Yes | First 200 lines of MEMORY.md; auto-adds Read/Write/Edit | +| Sibling roster | Conditional | Lists `main` + every named AG as valid `SendMessage` targets; appears only when `tools:` has `SendMessage` and another AG is named (v2.1.206+). Snapshot at start | +| Full CC SP | No | Replaced with the AG's own body + environment details | +| Parent conversation history | No | Clean slate each invocation -- a fork is the exception, it inherits the parent conversation | +| Parent's invoked SKs | No | Preload via `skills:`, or invoke at runtime with the `Skill` TL | +| Output style | No | The SA runs its own SP; forks excepted | +| Parent's auto memory (`memory/MEMORY.md`) | No | Only AG-specific memory | + +> Don't duplicate CD rules in AG body -- already injected. Focus SP on AG-specific role, patterns, checklists. +> Known bugs: see Known Bugs in `agent-known-issues.md`. + +## SKs: Preload vs Runtime + +Two independent mechanisms -- pick per SK, not per AG. + +| Mechanism | How | Use when | +|-----------|-----|----------| +| Preload | `skills:` in FM -- full content injected into ctx at startup | The AG always needs it; the content shapes every turn | +| Runtime | `Skill` in `tools:` -- the AG calls the `Skill` TL for any project/user/PLG SK, listed or not (`docs/sub-agents.md:292`) | Needed sometimes; ctx paid only on use | + +```yaml +skills: api-conventions, error-handling +``` + +> List preloaded SKs explicitly per AG -- no inheritance from parent. +> `skills:` is the preload channel; !=put `Skill` there and !=put a SK name in `tools:` (`docs/sub-agents.md:287`). + +### Reference-Aware SKs + +When AG spawns from a SK that uses `references/`, AG does NOT have `skill_base_dir`. + +| Content Size | Approach | EX | +|-------------|----------|----| +| <50 lines | Inline into AG prompt | Pass ref content directly via Task prompt | +| >50 lines | Use `${CLAUDE_PLUGIN_ROOT}` path | `Read ${CLAUDE_PLUGIN_ROOT}/skills/skill-name/references/mode.md` | + +`${CLAUDE_PLUGIN_ROOT}` (brace form) is natively substituted at spawn to this plugin's root -- PLUGIN AGENTS only; project-local `.claude/agents/*.md` get no substitution (repo-relative paths only). + +> If SK detects mode BEFORE spawning AG, pass only relevant ref -- not all of them. + +## Execution Modes + +| Mode | Behavior | Permissions | TL pool | +|------|----------|-------------|---------| +| Foreground | Blocks the main conversation | Prompts pass through as they come up | Filter 1 only | +| Background | Runs concurrently; the result reaches Claude as a completion notification in a later turn | Since v2.1.186 the prompt SURFACES in the main session naming the asking SA -- approve, or Esc denies that one TL call without stopping the SA (`docs/sub-agents.md:793`). Auto-deny was pre-2.1.186 behaviour | Filter 1 + filter 2 (smaller) | + +Mode is picked per spawn by the first matching case (`docs/sub-agents.md:795-798`): + +| # | Condition | Mode | +|---|-----------|------| +| 1 | `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` | Foreground, every session kind, fork mode irrelevant | +| 2 | An in-process AG-teams teammate spawned it | Foreground | +| 3 | Fork mode ON (the DEF in an interactive session) | Background -- forks and non-forks alike; Claude cannot ask for the foreground | +| 4 | Fork mode OFF (`-p` headless, Agent SDK unless enabled) | Background by DEF, foreground when Claude needs the result before continuing. `background: true` pins it to BG anyway | + +> `background: true` matters only in case 4. There is no `false` value -- to force the foreground use case 1 or case 2, !=a FM flag. +> Since 2.1.269, case 2 hard-errors instead of silently forcing foreground when the spawned definition itself carries `background: true` -- a shared definition used both standalone and as a teammate must drop that field. +> Steering: with fork mode off, ask Claude for background/foreground; Ctrl+B backgrounds a running task. + +## SA Resource Limits (2.1.233) + +> **No wall-clock timeout for a SA exists** -- not in FM, not in `settings.json`, not as env var. A SA is bounded by turns, API-call timeouts, and token caps only. + +**turn** = one MDL inference + its TL calls; TL results return -> next turn. Parallel TL calls in ONE assistant msg = ONE turn. A SA has no user, so turns = iterations of "think -> act", usually < TL-call count. Observed samples (turns/TL-calls) from real transcripts: 12/19, 13/13, 14/16, 21/33, 39/42, 40/53, 51/55. + +| Env var (`settings.json` `env`) | Bounds | DEF | +|---|---|---| +| `CLAUDE_CODE_MAX_TURNS` | turn cap for ALL AGs globally; positive int | unset | +| `API_TIMEOUT_MS` | single API call | 10 min | +| `CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS` | BG-AG stall; resets on streaming | 10 min | +| `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS` | concurrent SAs; on hit `Concurrent subagent limit reached`, do not retry. `/subtask` forks take a slot but are never blocked; a resume takes a fresh slot without checking; ultracode sessions exempt (v2.1.217+) | 20 | +| `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` | SA nesting depth below main; `1` turns nesting off. At the limit `Agent` is withheld (a fork keeps it, but it errors) | 3 | +| `CLAUDE_CODE_MAX_OUTPUT_TOKENS` | output tokens per response | MDL max | +| `MAX_THINKING_TOKENS` | thinking budget | -- | +| `MAX_MCP_OUTPUT_TOKENS` | MCP result size | 25k | +| `BASH_DEFAULT_TIMEOUT_MS` / `BASH_MAX_TIMEOUT_MS` | Bash TL only | 120s / 600s | + +> **No total-per-session cap.** `docs/sub-agents.md:930`: there is no limit on the total number of SAs a session can spawn. `CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION` (DEF 200) shipped in 2.1.212 and was **removed in 2.1.224** (`claude-code/CHANGELOG.md:191`) -- concurrency and depth are the only live spawn limits. !=plan capacity around 200, !=call it undocumented. + +**`maxTurns` exhaustion:** binary emits `Reached max turns limit (N)`, AG aborts. Side effects (written files) persist; since 2.1.246 the caller receives a **partial**-marked result with a `SendMessage` continuation hint instead of a silent finish -> still pair `maxTurns` with checkpointing, since the marker only prompts a resume, it does not recover unwritten analysis. + +### Hooks vs SAs, Partial-Result Recovery + +| Category | Item | Use | +|----------|------|-----| +| Hook | `PreToolUse` -- inside SA loop; payload has `agent_id`, `agent_type`; exit 2 blocks the call + returns text to SA | Only way to get time-based control: soft deadline -- warn at 80% budget, deny non-Write TLs at 100% | +| Hook | `SubagentStart` / `SubagentStop` -- MAIN session, not inside AG | `SubagentStop` exit 2 forces continuation | +| Hook | (timer hook) -- none exists | Elapsed time readable only on a TL call | +| Recovery | `.claude/projects/{project}/{sessionId}/subagents/agent-{agentId}.jsonl` | SA transcript (retention: `cleanupPeriodDays`) | +| Recovery | `run_in_background: true` + `TaskOutput` | Read partial output live -- from the MAIN session; `TaskOutput` is filtered out of every SA | +| Recovery | `TaskStop` | Kill a running SA | +| Recovery | `SendMessage` | Resume a stopped SA with ctx intact | diff --git a/brewcode/skills/agents/references/agent-frontmatter-fields.md b/brewcode/skills/agents/references/agent-frontmatter-fields.md new file mode 100644 index 0000000..e493b6f --- /dev/null +++ b/brewcode/skills/agents/references/agent-frontmatter-fields.md @@ -0,0 +1,103 @@ +# AG Frontmatter Field Reference + +## AG File Format + +```markdown +--- +name: agent-name # REQ: lowercase/hyphens; !=leading `-`, !=`:` (rejected v2.1.218+, file skipped+logged) +description: "Short description" # REQ: TRG terms, when to delegate +model: sonnet # OPT: sonnet|opus|haiku|fable|inherit (DEF: inherit) +effort: high # OPT: low|medium|high|xhigh|max (local + PLG) +maxTurns: 20 # OPT: positive int, max turns (local + PLG) +tools: Read, Glob, Grep # OPT: comma-separated (omit = inherit all) +disallowedTools: Write, Edit # OPT: deny specific TLs (local + PLG) +skills: skill1, skill2 # OPT: injected into ctx at startup +color: cyan # OPT: 8 UI colors, see Color Semantics (agent-template.md) +memory: project # OPT: user|project|local +background: true # OPT: `true` keeps it BG even when Claude wants the result -- no `false` semantics +isolation: worktree # OPT: FM accepts `worktree` only; `remote` is invocation-level (Agent TL), gated +permissionMode: default # OPT: ignored for PLG AGs +mcpServers: [server1, server2] # OPT: ignored for PLG AGs +initialPrompt: "Analyze this code" # OPT: fires only when this definition runs as the MAIN session (`--agent` / `agent` setting) +observer: "reviewer" # OPT: absent from the 2.1.233 field table -- !=emit +observerMessage: "watch for X" # OPT: absent from the 2.1.233 field table -- !=emit +observeSubagents: false # OPT: absent from the 2.1.233 field table -- !=emit +hooks: {PreToolUse: [{matcher: "Bash", hooks: [{type: command, command: "./validate.sh"}]}]} # OPT: any hook event, flow-style shown for brevity (also valid as block YAML); ignored for PLG AGs +experimental: {cacheTtl: "5m"} # OPT: "5m"|"1h" per-agent prompt-cache TTL (2.1.248+); local-only, ignored for PLG AGs +--- + +# SP + +Detailed instructions for the AG... +``` + +## FM Reference + +### REQ Fields + +| Field | Format | Description | +|-------|--------|-------------| +| `name` | lowercase, hyphens; !=leading `-`, !=`:` (rejected v2.1.218+ -- file skipped, logged; `:` reserved for PLG namespacing) | Unique identifier. PLG AGs auto-namespaced `::` | +| `description` | per Description Budget in `agent-template.md` -- single line + role + 3-7 TRGs by DEF, `` blocks only under the stated exception | When Claude delegates to this AG. Aliases: `when_to_use`, `when-to-use`. Some registries truncate long descriptions | + +### OPT Fields + +Verified against CC 2.1.233 (`docs/sub-agents.md:279-300` field table), re-checked through the 2.1.269 delta (see Changes 2.1.234-2.1.269 in `agent-known-issues.md`). Two parsers exist -- **local** (`.claude/agents/`, `~/.claude/agents/`, `--agents` JSON) and **PLG** (`/agents/**.md`). `Scope` column = where the key is honored: PLG AGs ignore `hooks`, `mcpServers`, `permissionMode` (`docs/sub-agents.md:228`), plus `experimental.cacheTtl` (2.1.248+, local-only) -- four keys total; every other key is honored in both. + +| Field | Values | DEF | Scope | Description | +|-------|--------|-----|-------|-------------| +| `model` | `haiku`, `sonnet`, `opus`, `fable` (`claude-fable-5`, Mythos-class, v2.1.170), `inherit` | `inherit` | both | MDL selection | +| `effort` | `low`, `medium`, `high`, `xhigh`, `max` (MDL-dependent) | `inherit` | both | Override effort; no `auto`, no bare integer. Pre-2.1.267 this was a no-op on pinned-effort models (Opus 4.7/4.8, Fable 5) -- honored since | +| `maxTurns` | positive integer | unlimited | both | Max turns before abort | +| `tools` | comma-separated | All inherited | both | Allowed TLs | +| `disallowedTools` | comma-separated | None | both | Denied TLs (removed from inherited) | +| `skills` | comma-separated / list | None | both | Full SK content injected into ctx at startup. Preload only -- an unlisted SK stays reachable at runtime via the `Skill` TL (`docs/sub-agents.md:292`); list `Skill` in `tools:`, !=the SK name | +| `color` | 8 values, see Color Semantics in `agent-template.md` | None | both | UI color; `magenta` is NOT valid | +| `memory` | `user`, `project`, `local` | None | both | AG memory scope; with explicit `tools` list parser force-adds memory TLs | +| `background` | `true` | unset | both | `true` keeps the SA in BG even when Claude asks for the foreground (`docs/sub-agents.md:296`). One value only -- `false` is not a force-foreground switch; mode is picked by the four-case precedence, see Execution Modes in `agent-context-and-execution.md`. Since 2.1.269, a teammate-spawned SA whose definition carries `background: true` hard-errors instead of forcing foreground -- drop the field on a definition that may run as a teammate | +| `isolation` | `worktree` | None | both | LOW PRIORITY -- omit unless AGs write files in parallel. FM documents `worktree` alone (`docs/sub-agents.md:298`); `remote` is invocation-level, not FM, see the note below | +| `permissionMode` | see Permission Modes below | `default` | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | +| `mcpServers` | server name or inline definition | All inherited | local | Ignored for PLG AGs (`docs/sub-agents.md:228`) | +| `hooks` | YAML structure, any hook event | None | local | Ignored for PLG AGs; a PROJECT AG's FM hooks need the workspace-trust dialog accepted for the exact folder holding the file (`docs/sub-agents.md:648`, v2.1.218+). `~/.claude/agents/` and `--agents` need no trust step | +| `initialPrompt` | non-empty string | None | both | Auto-submitted as the first user turn when THIS definition runs as the MAIN session -- `--agent ` or the `agent` setting; commands + SKs are processed, prepended to any user prompt (`docs/sub-agents.md:300`). Irrelevant on ordinary SA spawn. `--agent` resolves a PLG AG by its scoped name, so origin is not the boundary; execution context is | +| `observer`* | non-empty string | None | local | Observing AG | +| `observerMessage`* | non-empty string | None | local | Brief for observer | +| `observeSubagents`* | `false` disables | enabled | local | -- | +| `experimental.cacheTtl` | `"5m"`, `"1h"` | none | local | 2.1.248+: per-agent prompt-cache TTL override; pairs with session-level `promptCacheTtl`/`subagentPromptCacheTtl` settings (2.1.243) | + +> *`observer`/`observerMessage`/`observeSubagents` are absent from the 2.1.233 field table (`docs/sub-agents.md:279-300`) -- treat as internal/older until confirmed, !=emit into a generated AG. +> Need `permissionMode`/`hooks`/`mcpServers` -> put the AG in `.claude/agents/` or `~/.claude/agents/`, or grant `permissions.allow` rules in `settings.json` (session-wide, !=PLG-AG-scoped) (`docs/sub-agents.md:228`). +> PLG AG files above the byte limit are skipped entirely (`Skipping plugin agent : ... exceeds N byte limit`). +> `isolation` = LOW PRIORITY: !=add by DEF. Costs worktree setup + disk per spawn, and known data-loss combo (see Known Bugs in `agent-known-issues.md`, #29110). Use ONLY when several AGs mutate the same files concurrently. `remote` is **invocation-level only**: the Agent TL schema carries `isolation?: "worktree" | "remote"` and `remote` launches the AG in a remote cloud environment, always backgrounded, availability-gated (`npm/package-2.1.233/sdk-tools.d.ts:526-527`). Never valid in FM; reachable only from an `Agent(...)` call where the gate is on. + +## Permission Modes + +| Mode | Behavior | +|------|----------| +| `default` | Standard permission prompts | +| `manual` | Alias of `default` (v2.1.200+) | +| `acceptEdits` | Auto-accept file edits | +| `auto` | CC picks per-call (2.1.233 value set, `docs/sub-agents.md:289`) | +| `dontAsk` | Auto-deny prompts (allowed TLs still work) | +| `bypassPermissions` | Skip all checks (use with caution) | +| `plan` | Read-only exploration mode | + +## Hook Events + +**All hook events are supported in AG FM** (`docs/sub-agents.md:652`). These three are the common ones: + +| Event | Matcher | When | Note | +|-------|---------|------|------| +| `PreToolUse` | TL name | Before the SA uses a TL | -- | +| `PostToolUse` | TL name | After the SA uses a TL | -- | +| `Stop` | (none) | The SA finishes | Converted to `SubagentStop` at runtime when the definition is spawned AS a SA (`docs/sub-agents.md:658,680`) | + +Configured in `settings.json` / `PLG/hooks/hooks.json`, never AG FM: `SubagentStart`, `SubagentStop`, +`PreToolUse:Agent`/`PostToolUse:Agent`, `TaskCreated`/`TeammateIdle`/`TaskCompleted` -- full event +table + stdin fields: `hooks-events.md`. + +> Matcher value = the FM `name` for local/user AGs, the scoped `plugin:agent` id for PLG AGs. A scoped name contains `:` and is matched as an UNANCHORED regex -- anchor it `^brewcode:agent-creator$` to hit one AG only. +> The SAME file can run as a SA or as the MAIN session (`--agent`). In the main-session case FM hooks run alongside `settings.json` hooks and `Stop` stays `Stop`. +> **Trust:** a PROJECT AG's FM hooks run only after the workspace-trust dialog is accepted for the EXACT folder holding the AG file -- a trusted parent is not enough and a `-p` session never counts. Until then the SA still runs, hooks are skipped, an error goes to the debug log. `~/.claude/agents/` and `--agents` definitions need no trust step; an `--add-dir` folder must be trusted separately (`docs/sub-agents.md:648`). +> PLG AG FM `hooks` are ignored (`docs/sub-agents.md:228`) -- ship hooks in `PLG/hooks/hooks.json` instead. +> Settings-level hooks affect ALL SAs, incl. hooks from managed policy settings and PLGs. diff --git a/brewcode/skills/agents/references/agent-known-issues.md b/brewcode/skills/agents/references/agent-known-issues.md new file mode 100644 index 0000000..58325ab --- /dev/null +++ b/brewcode/skills/agents/references/agent-known-issues.md @@ -0,0 +1,91 @@ +# AG Known Bugs, Limitations, Version History, Debugging + +## Known Bugs + +| Bug | Impact | Status | Workaround | +|-----|--------|--------|------------| +| [#29423](https://github.com/anthropics/claude-code/issues/29423) | Task SAs don't load CD + rules | Closed (NOT PLANNED, re-verified 2026-09) | Pass rules in `Agent(prompt=...)` | +| [#29110](https://github.com/anthropics/claude-code/issues/29110) | `bypassPermissions` breaks Write/Edit; worktree loses data | Closed (NOT PLANNED, re-verified 2026-09) | Avoid `bypassPermissions` + `isolation: worktree` combo | +| [#19040](https://github.com/anthropics/claude-code/issues/19040) | Session files grow to multi-GB from SA progress entries | Closed (Fixed, re-verified 2026-09) | No longer needed; monitor only if still on a pre-fix build | +| [#31392](https://github.com/anthropics/claude-code/issues/31392) | Global AGs `~/.claude/agents/` not discovered | Closed (NOT PLANNED, re-verified 2026-09) | Use project-level or PLG-level AGs | +| [#27736](https://github.com/anthropics/claude-code/issues/27736) | `skills:` description not rendered in the Agent TL agent picker (content injection itself works -- the bug is description visibility only) | Closed (NOT PLANNED, re-verified 2026-09) | Inline SK content or use `${CLAUDE_PLUGIN_ROOT}` path if the description omission matters | +| [#25834](https://github.com/anthropics/claude-code/issues/25834) | Plugin agent `skills:` frontmatter silently failed to inject content | Closed (Fixed, re-verified 2026-09) | No longer needed | +| [#13627](https://github.com/anthropics/claude-code/issues/13627) | AG body not injected via Agent TL | Closed (NOT PLANNED) | `SubagentStart` hook with `additionalContext` | +| [#8395](https://github.com/anthropics/claude-code/issues/8395) | SAs ignore user-level CD | Closed (NOT PLANNED) | `SubagentStart` hook with `additionalContext` | +| [#4182](https://github.com/anthropics/claude-code/issues/4182) | SK TL unavailable in SA | Historical -- superseded | `Skill` is in the 2.1.233 background pool (`docs/sub-agents.md:349`) and a SA may invoke unlisted SKs (`:292`). Kept only so an old AG carrying this claim is recognised | + +## Architectural Limitations + +| Limitation | Description | Workaround | +|------------|-------------|------------| +| No runtime SK PRELOAD | `skills:` injects at startup only; runtime use goes through the `Skill` TL instead | Preload the always-needed SKs, give `Skill` for the rest | +| A SA cannot prompt the user | `AskUserQuestion` removed from every SA even when declared (`docs/sub-agents.md:337,340`); forks exempt | Return the decision request to the caller; the caller asks | +| No parent history access | Clean ctx per invocation | Pass ctx via `Agent(prompt=...)` | +| Short SP | The AG's own body + environment details replace the full CC prompt | Compensate with detailed AG body | +| No SA wall-clock timeout | Turns/tokens bound a SA, never elapsed time | `maxTurns` + `PreToolUse` soft deadline | +| PLG AGs: `permissionMode`/`hooks`/`mcpServers`/`experimental.cacheTtl` ignored | Exactly these four (`docs/sub-agents.md:228`; `experimental.cacheTtl` since 2.1.248) | Move AG to `.claude/agents/`, or use session-wide `permissions.allow` rules | +| `isolation: remote` not a FM value | Invocation-level only, always backgrounded, availability-gated (`sdk-tools.d.ts:527`) | In FM use `worktree` or omit; request `remote` from the `Agent(...)` call | +| Session `auto-accept` UI toggle overrides FM `permissionMode` | Distinct from the `permissionMode: auto` value | Don't rely on FM `permissionMode` when the session runs auto-accept | + +## Changes 2.1.234-2.1.269 + +| Version | Change | Generate differently | +|---------|--------|-----------------------| +| 2.1.235 | Omitted `subagent_type` on an Agent call now errors (was a silent `general-purpose` fallback) | Always pass `subagent_type` explicitly in orchestrator AG bodies | +| 2.1.243 | `--agents` CLI errors on invalid JSON/AG definition (was silently ignored) | No AG-body change; safer to author session-scoped AGs via `--agents` | +| 2.1.243 | `promptCacheTtl`/`subagentPromptCacheTtl` settings added (main 1h, subagents 5m by default) | Pairs with `experimental.cacheTtl` FM -- per-agent overrides the setting | +| 2.1.246 | `maxTurns` abort now returns a **partial**-marked result + `SendMessage` continuation hint | Orchestrator AGs must check the partial marker, not just presence of output, before treating a spawned SA's return as done | +| 2.1.248 | `experimental.cacheTtl` FM field added; cross-session messaging extended to Bedrock/Vertex/Foundry + telemetry-disabled sessions | Emit `experimental.cacheTtl` only when the AG's prompt is large/static and reused often | +| 2.1.251 | `CLAUDE_CODE_SUBAGENT_MODEL` became a default, not an override | Note in generated AGs that a pinned `model:` now wins over that env var (see Model Precedence in `agent-scope-and-tools.md`) | +| 2.1.257 | `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1` added, beats everything | Note in cost/quality-sensitive AGs that ops can still force the model despite `model:` | +| 2.1.257 | SAs auto-continue after a mid-stream cut-off instead of ending incomplete | No AG-body change; fewer manual resumes | +| 2.1.260 | One-hour cap on SA-started background Bash removed | No AG-body change; long BG Bash from a SA now runs to exit/stop like the main session | +| 2.1.267 | `effort:` on pinned-effort models (Opus 4.7/4.8, Fable 5) now honored (was a no-op) | Only set `effort:` on those models when the AG actually needs a different tier | +| 2.1.269 | A teammate-spawned SA whose definition carries `background: true` now hard-errors | A shared AG definition used both standalone and as a teammate must drop `background: true` | +| 2.1.234 | "Default teammate model" `/config` setting removed | Teammates use the leader's model unless the spawn prompt names one | + +## VH (AG Features) + +> FM + TL contract verified against the 2.1.233 doc set (`docs/sub-agents.md`) and `npm/package-2.1.233/`; re-verified through 2.1.269, see Changes 2.1.234-2.1.269 above. + +| Ver | Date | Changes | +|-----|------|---------| +| 2.1.269 | 2026-09 | Frontmatter/behavior delta re-verified (2.1.234-2.1.269, full breakdown above): `experimental.cacheTtl` field added; `CLAUDE_CODE_SUBAGENT_MODEL` precedence inverted then `_FORCE` added; `maxTurns` abort now partial-marked; teammate `background: true` hard-errors; `subagent_type` omission errors; `effort` honored on pinned-effort models since 2.1.267; 6 GH issue numbers across 5 previously-Active bug rows re-verified Closed (see Known Bugs above) | +| 2.1.233 | 2026-08 | Contract re-verified: two TL filters (universal + background-only, forks skip both); `AskUserQuestion` removed from every SA; Task TLs conditional, teammates add cron TLs; ALL hook events valid in AG FM (`Stop` -> `SubagentStop`); Managed settings = precedence 1 of 5; `initialPrompt` = main-session-only, honored for PLG AGs too; PLG-ignored keys are exactly `hooks`/`mcpServers`/`permissionMode`; `remote` isolation is invocation-level; BG permission prompts surface in the main session (2.1.186+) | +| 2.1.224 | 2026-08 | Per-session spawn cap REMOVED (`CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION`, DEF 200, added 2.1.212) -- concurrency + depth remain | +| v2.1.223 | 2026-08 | FM contract re-verified: nesting depth DEF 3 (`CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH`); BG-by-default since v2.1.198; `effort` low/medium/high/xhigh/max (no auto/integer); `color` 8 values (no magenta); `isolation` worktree-only; `name:` rejects `:` (skip+log); `permissionMode` +`auto`+`manual`; `initialPrompt` now documented; org-restricted subagent model warning | +| v2.1.221-222 | 2026-07 | Plugin agents activate on install (no reload needed); org model-alias resolution fix | +| v2.1.219 | 2026-06 | Nesting depth DEF changed 1 -> 3 | +| v2.1.218 | 2026-06 | `name:` containing `:` rejected; agent-FM hooks need workspace-trust dialog | +| v2.1.198 | 2026-06 | SAs run background by DEF (was opt-in); `/agents` stops opening a wizard | +| v2.1.172 | 2026-05 | SAs can spawn their own SAs (depth-capped, history 5->1->3) | +| v2.1.170 | 2026-05 | Fable 5 MDL (`claude-fable-5`, Mythos-class tier above Opus) selectable in `model:` | +| v2.1.78-85 | 2026-03 | `effort`/`maxTurns`/`disallowedTools` FM fields; `TaskCreated` hook; WorktreeCreate `type: http` | +| v2.1.49-74 | 2026-02/03 | Task TL renamed to Agent TL (`Task(...)` still works as alias); MDL/worktree fixes: full MDL IDs in FM, `--agents` visibility, Bedrock/Vertex aliases, `isolation: worktree` + Worktree hooks, `initialPrompt` FM, `--worktree` flag, Ctrl+F kills BG AGs, BG SAs survive compaction, `agent_id`/`agent_type` in hooks | + +## Debugging + +| TL | Usage | +|----|-------| +| `CLAUDE_DEBUG=1` | Env var: full debug output, shows AG prompts | +| Ctrl+O | Verbose mode in UI: shows AG calls + stdout | +| `/agents` | Lists all registered AGs with priorities (no longer a wizard, v2.1.198+) | +| Manual `Agent()` | `Agent(subagent_type="name", prompt="test")` -- direct invocation for testing | + +### Common Problems + +| Problem | Cause | Solution | +|---------|-------|----------| +| AG file "ignored" though it exists | AG under `/.claude/agents/` while session cwd is outside `` -- not on the walk-up path | Move to repo-root `.claude/agents/`, or launch/`cd`/`--add-dir` into `` | +| AG doesn't trigger automatically | Vague description, no TRG words | Add specific TRG terms, `` blocks | +| AG TRGs on irrelevant requests | Too broad description | Narrow description, add `` conditions | +| AG doesn't see CD rules / SP not injected | Known bug, or the AG is built-in `Explore`/`Plan`, which skip CD + git status by design | Workaround per-bug in Known Bugs above; for Explore/Plan restate the rule in the delegation prompt | +| AG "can't call SKs" | `Skill` missing from `tools:` -- the TL itself is available in every SA pool | Add `Skill` to `tools:`, or preload via `skills:` | +| A declared TL is silently absent at runtime | Filter 1 or the background filter removed it -- removal reports no error | Check the pool tables in Available TLs (`agent-scope-and-tools.md`); force the foreground pool via the Execution Modes cases (`agent-context-and-execution.md`) | +| AG can't spawn SA | BC workflow: main-only by policy (see Spawn From Main Conversation Only in `agent-scope-and-tools.md`) | Chaining from main conversation | +| `agents/` dir in plugin.json | Causes validation error | Remove from manifest -- auto-discovered by DEF | +| `permissionMode`/`hooks`/`mcpServers` not working | Ignored for PLG AGs; or a PROJECT AG whose exact folder is not trusted (FM hooks skipped, error in the debug log) | Move AG to `.claude/agents/` and accept the workspace-trust dialog for that folder | +| AG stops early, no final report | `maxTurns` hit -- `Reached max turns limit (N)`; since 2.1.246 the caller sees a partial-marked result, not silence | Raise `maxTurns`; read checkpoint file / SA transcript | +| AG "hangs" with no timeout | No wall-clock timeout exists | `PreToolUse` soft deadline; `TaskStop` to kill | + +Sources: [Create Custom SAs](https://code.claude.com/docs/en/sub-agents), [CC Best Practices](https://www.anthropic.com/engineering/claude-code-best-practices). diff --git a/brewcode/skills/agents/references/agent-scope-and-tools.md b/brewcode/skills/agents/references/agent-scope-and-tools.md new file mode 100644 index 0000000..9ea300b --- /dev/null +++ b/brewcode/skills/agents/references/agent-scope-and-tools.md @@ -0,0 +1,108 @@ +# AG Scope, Tools, Precedence + +## Available TLs + +A SA does NOT get the main conversation's tool set. It inherits built-ins + MCP TLs, then **two filters** narrow it (`docs/sub-agents.md:337-353`). Generate a `tools:` line against the pool the AG will actually run in, !=against a static list. + +| Filter | Applies to | Effect | +|--------|-----------|--------| +| 1 -- universal | every SA (forks exempt) | Removes `Agent` (at the depth limit only), `AskUserQuestion`, `EndConversation`, `EnterPlanMode`, `ExitPlanMode` (unless `permissionMode: plan`), `ScheduleWakeup`, `TaskOutput`, `WaitForMcpServers`, `Workflow` -- **even when listed in `tools:`** | +| 2 -- background only | background SAs (the DEF) | Keeps every MCP TL + only the built-ins in the table below; removes every other built-in, inherited or declared | +| forks (`/subtask`) | -- | Skip BOTH filters; get the main conversation's exact pool | + +| Pool | Built-in TLs available | +|------|------------------------| +| Foreground SA | Everything the main conversation has, minus filter 1 (incl. `ListAgents` where cross-session messaging is on) | +| Background SA (DEF) | `Read`, `Grep`, `Glob`, `Bash`, `PowerShell`, `Edit`, `Write`, `NotebookEdit`, `WebFetch`, `WebSearch`, `TodoWrite`, `Skill`, `ToolSearch`, `EnterWorktree`, `ExitWorktree`, `Monitor`, `TaskStop`, `SendMessage`, `Artifact` + all MCP TLs. **No `ListAgents`. No `TaskCreate`/`TaskGet`/`TaskList`/`TaskUpdate`** | +| AG-teams teammate | Background pool + `TaskCreate`, `TaskGet`, `TaskList`, `TaskUpdate`, `CronCreate`, `CronDelete`, `CronList` (`docs/sub-agents.md:351`) | +| MCP | `mcp__server__tool` -- survives both filters in every pool | + +> Removal is **silent** (`docs/sub-agents.md:349`): a filtered entry raises no warning, so a stale `tools:` name is inert clutter, not breakage. A launch fails only when NOTHING in `tools:` resolves (`docs/sub-agents.md:287`) -- so a `tools:` list made entirely of filtered TLs refuses to launch. +> The nine filter-1 TLs never belong in a generated `tools:` line. `AskUserQuestion` in particular: **a SA cannot ask the user anything** -- write the AG body to return a decision request to its caller, never "confirm with the user" prose. Forks are the sole exemption. +> Task TLs are CONDITIONAL, !=assumed: absent from a background SA, present for a foreground SA and for AG-teams teammates, and absent from every SA in a session that has no Task TLs at all (`docs/sub-agents.md:353`). An AG whose body coordinates a task graph needs an explicit fallback -- when `TaskCreate` is unavailable, track the plan in its report file and return the ordering to the caller. + +## AG Scope & Precedence + +| Priority | Location | Scope | How to Create | +|----------|----------|-------|---------------| +| 1 (highest) | `.claude/agents/` inside the managed-settings dir | Organization-wide | Deployed via managed settings | +| 2 | `--agents` CLI flag | Current session | JSON at launch | +| 3 | `.claude/agents/` | Project | Manual, checked into VCS | +| 4 | `~/.claude/agents/` | User (all projects) | Manual | +| 5 (lowest) | `plugin/agents/` | Where PLG enabled | Installed with PLG | + +> Managed definitions use the same FM format and win over a project or user AG of the same name (`docs/sub-agents.md:157-165,221-225`) -- never claim a project or CLI AG is authoritative without checking for a managed one. PLG AGs keep their scoped `plugin:subdirs:name` identity and never collide with an unscoped name. +> Write targets: a `Write`/`Edit` TOOL call under `~/.claude/**` is classified sensitive and routed to a permission ASK, !=a hard block. Carve-outs under `.claude/`: `skills`, `agents`, `commands`, `worktrees`, `scheduled_tasks.json`. Mode behaviour: `default`/`acceptEdits`/`plan` -> prompt; `bypassPermissions` -> auto-approved; headless `-p` without bypass -> FAILS (no prompt channel). For unattended state prefer `${CLAUDE_PROJECT_DIR}/.claude//`. +> `/agents` (v2.1.198+) no longer opens a wizard -- prints a reminder to edit `.claude/agents/` files directly. + +### Discovery: walk-up scan (headline fix -- read this before placing a file) + +Priority 2 ("project") is not "repo-root only": CC scans **every `.claude/agents/` folder from cwd walking UP to the repo root**, plus `~/.claude/agents/` and any `--add-dir` target's own `.claude/agents/`. Inside each such folder, subfolders are scanned recursively -- the path is cosmetic, `name:` in the file is the real identity (PLG agents get `plugin:subdir:name`). + +| Case | Rule | +|------|------| +| Name collision, different dirs on the walk-up path | Definition closest to cwd wins (v2.1.178+) | +| Name collision, same dir | Undefined filesystem read order -- `/doctor` flags it | + +> **Author trap (the incident this section fixes):** an AG at `//.claude/agents/x.md` is invisible to a session launched with cwd at `` root -- that dir is not on the walk-up path. It is not a broken file, it is a cwd/launch-location mismatch. Fix: put the AG in the repo-root `.claude/agents/`, or launch/`cd`/`--add-dir` into `` so its own `.claude/agents/` is on the walk-up path. When creating an AG, ask (or infer) the intended launch cwd and place the file accordingly -- then say where you put it and why. + +### CLI JSON Format (session-only) + +```bash +claude --agents '{ + "code-reviewer": { + "description": "Expert reviewer. Use after code changes.", + "prompt": "You are a senior code reviewer...", + "tools": ["Read", "Grep", "Glob", "Bash"], + "model": "sonnet" + } +}' +``` + +## Agent Tool Call Options + +| Option | Since | Notes | +|--------|-------|-------| +| `subagent_type` | required in practice from 2.1.235 | omission now errors listing available AGs (was a silent `general-purpose` fallback) -- always pass it explicitly in orchestrator AG bodies | +| `model` | restored 2.1.72 | per-invocation override; wins over the definition's `model:`, loses only to `CLAUDE_CODE_SUBAGENT_MODEL_FORCE` (see Model Precedence below) | +| `isolation` | `worktree` since 2.1.50 | schema also carries `"remote"` (invocation-level only, gated, always backgrounded) -- never valid in FM | +| `name` | stabilized 2.1.206 | required to spawn a teammate (`Agent(name:...)`) instead of an anonymous SA; `TeamCreate`/`TeamDelete` removed v2.1.178 | +| `run_in_background` | -- | requests background explicitly; called from an in-process teammate this may fail (error or silent foreground) -- exact version not isolated in the changelog, confirmed only via the current agent-teams doc | + +## Model Precedence + +| Priority | Source | Behavior | +|----------|--------|----------| +| 1 (highest) | `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1` (2.1.257) | forces every SA, teammates included, onto `CLAUDE_CODE_SUBAGENT_MODEL`, overriding both the definition's `model:` and any per-spawn `model` | +| 2 | Per-spawn `Agent(model:...)` | wins over the definition's `model:` | +| 3 | Definition `model:` (this AG's FM) | wins over the `CLAUDE_CODE_SUBAGENT_MODEL` default | +| 4 (lowest) | `CLAUDE_CODE_SUBAGENT_MODEL` (2.1.251+) | a default only, applied when neither 2 nor 3 is set | + +> Before 2.1.251, `CLAUDE_CODE_SUBAGENT_MODEL` won over both `model:` and per-spawn `model` -- inverted since. A generated AG that pins a model for cost/quality should note ops can still force it via `_FORCE`. + +## Spawn From Main Conversation Only (BC workflow) + +**CC capability:** since v2.1.172, SAs can spawn their own SAs. Depth is capped by `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` (env var, both scopes) -- history 5 (v2.1.172-216) -> 1 (v2.1.217-218) -> **3** (v2.1.219+, current DEF). Verify the live cap, !=hardcode any number. +**BC workflow stance:** spawn ONLY from main conversation regardless of the cap -- nested spawns bypass session binding + hook context injection, and each level multiplies token cost + loses ctx fidelity. Give `Agent` TL to an AG only when it genuinely orchestrates. + +| Case | BC workflow | +|------|-------------| +| `Agent(subagent_type=...)` from SA | CC allows, depth-capped -- BC: spawn from main only | +| `Skill` TL from SA | Available -- in both pools (`docs/sub-agents.md:292,349`). Runtime invocation of an unlisted SK is legal; use it when preload would waste ctx | +| SK with `context: fork` from SA | Same `AgentTool` path -- avoid in BC, spawn from main | +| `claude -p` via Bash | Technically runs but not recommended: OOM crashes, ctx loss, unmanageable | +| Deep nesting for speed | Each level multiplies tokens + loses ctx -- prefer flat fan-out | + +**Recommended patterns:** + +| Pattern | How | +|---------|-----| +| Chaining | Main AG spawns AGs sequentially, passing results | +| Preloaded SKs | `skills:` in FM -- full content injected at startup. Known-upfront SKs only | +| Runtime SKs | `Skill` TL in `tools:` -- the AG invokes an unlisted SK mid-run, ctx paid only on use | +| File-based comms | AGs write results to files, next AG reads | +| AG Teams | Lead coordinates via Task-graph TLs, teammates spawn via `Agent(name:...)` (BC: keep one level deep from main) | + +**AG Teams** -- `TeamCreate`/`TeamDelete` TLs removed v2.1.178 (teammates now spawn via `Agent(name:...)`); coordination runs on `TaskCreate`, `TaskGet`, `TaskList`, `TaskUpdate` plus `CronCreate`/`CronDelete`/`CronList`, which teammates keep on top of the background pool (`docs/sub-agents.md:351`). `TaskStop` is in the background pool for every SA; `TaskOutput` is removed from every SA by filter 1. Hook events: `TeammateIdle`, `TaskCompleted`, `TaskCreated` (v2.1.84). + +> Sources: [SA docs](https://code.claude.com/docs/en/sub-agents) diff --git a/brewcode/skills/agents/references/agent-template.md b/brewcode/skills/agents/references/agent-template.md new file mode 100644 index 0000000..b41409e --- /dev/null +++ b/brewcode/skills/agents/references/agent-template.md @@ -0,0 +1,163 @@ +# AG Emit Template -- Description Budget, SP Structure, Guardrails, Validation + +## Description Budget (NORMATIVE) + +The single description policy. The `description` row in FM Reference (`agent-frontmatter-fields.md`), the Description Patterns section below, and the Validation Checklist below all defer here -- no other numbers apply. + +| Constraint | Value | +|------------|-------| +| Total | <= 150 tokens (~600 chars) | +| Lead sentence | <= 160 chars, plain EN prose | +| TRGs | comma-list, EN only, 3-7 keywords | +| EXs | at most 1, commentary <= 15 words | +| Language | EN only in FM | + +> Exceed only if user explicitly asks. Frequent-use AGs: up to ~200 tokens + 1-2 EXs. +> **Example-block exception:** an AG whose domain overlaps another AG's may carry `` blocks in `description` as a YAML block scalar (`description: |`), up to the ceilings above -- see Description Patterns. Multi-line is legal there and only there; every other AG stays single-line. + +## Description Patterns + +**Format:** Action verb phrase -> `Triggers:` keyword list -> optional inline EXs, inside the **Description Budget** ceilings above. Front-load keywords. + +| AG clarity | Format | EXs | +|------------|--------|-----| +| Clear domain (developer, tester) | Single-line: action + TRGs | 0 | +| Some overlap with other AGs | Single-line + detailed `Triggers:` list | 0-1 | +| Ambiguous (creator AGs) | Block scalar (`description: \|`) + `` with `` -- the example-block exception | 1-2 | + +EX (ambiguous case -- clear-domain and some-overlap cases use the same one-line lead, without `` blocks): + +```yaml +description: | + Creates CC AGs. Triggers: create agent, new agent, improve agent, agent description. + + + user: "Create an agent for code review" + Explicit AG creation request TRGs this AG + +``` +(add a 2nd `` with different phrasing per Rule 6, e.g. "My reviewer agent doesn't trigger reliably") + +### Rules + +| # | Rule | Why | +|---|------|-----| +| 1 | Lead with action verb, not "Use this agent when" | Denser signal per token, matches user intent | +| 2 | Add `Triggers:` with exact user phrases | Semantic match on natural language | +| 3 | Dash-separated capabilities beat prose | `"SDET/QA - runs tests, debugs flaky"` > sentence | +| 4 | `` explains WHY this TRGs | Helps Claude distinguish similar AGs | +| 5 | 1 `` block by DEF, 2 at most (Description Budget) | More = token waste, diminishing returns | +| 6 | Vary phrasing across EXs | Claude generalizes rather than matching one phrase | +| 7 | No "proactively" or "MUST" language | No special weight -- write clear descriptions | +| 8 | Quote description if contains YAML special chars | Prevents parse failures | + +## EX Format (minimal) + +```yaml + +user: "exact phrase user would say" +Why THIS AG, not another + +``` + +No `Context:` line, no `assistant:` response -- `` is the selection signal (phrasing/commentary rules: see Description Patterns > Rules above). + +## SP Structure + +Order: role -> Return Contract -> Scope/Never (Scope Fit, Delegation) -> Ctx/Patterns/Cmds -> Checklist. Concrete EX already shipping this order: `brewcode/agents/bash-expert.md` (`# Bash Expert` -> `## Return Contract` -> `## Scope & Checkpoints` -> numbered body -> `## Checklist`). + +> Target SP body (excluding FM): ~800-1,500 words for a generic AG; teams-setup profiles instead cap at <=3200 bytes body-only (see the compact exception below). + +| # | Section header | Content | Format | +|---|-----------------|---------|--------| +| 1 | `# AG Name` | `**Role:**` one sentence; `**Scope:**` READ-ONLY / Write access / Full access | 2 bold lines | +| 2 | `## Return Contract` | From Guardrails below, placed right after role/scope -- not at the end | verbatim block | +| 3 | `## Scope Fit` (code-writing AGs) / `## Delegation` (AGs with `Agent` in `tools:`) | From Guardrails below, whichever applies | verbatim block(s) | +| 4 | `## Ctx` | Stack/Auth/Build facts, EX: `**Stack:** React 17 \| TypeScript 5.7 \| MUI v5` | table + one `>` constraint line | +| 5 | `## Patterns` | Avoid vs Prefer code idioms | 2-col table | +| 6 | `## Cmds` | Task -> Cmd reference | 2-col table | +| 7 | `## Checklist` | DoD, placed at end of SP | `- [ ]` list | + +### teams-setup compact exception + +A brief citing `brewcode/skills/teams-setup/references/agent-template.md` overrides the generic SP structure and guardrails. Generate one domain profile <=3200 bytes (~800 est-tokens) with exactly these ordered body headings and no others: `## Mission`, `## Owned surfaces`, `## Exclusions`, `## Must-load references`, `## Unique invariants`, `## Unique verification`. Load `.claude/teams/{TEAM_NAME}/team.md` first. Keep acceptance, routing, tracing, return, colleague, scope-fit, and etalon rules only in that shared file; !=restore `Task Acceptance Protocol`, `Return Contract`, `Trace Instructions`, `Colleagues`, or `Scope Fit` sections. Preserve frontmatter metadata specified by the teams brief. `intent-guard` remains exempt and only its three emitted seeded blocks may be adapted. + +### 6. Guardrails (non-team AGs; emit verbatim) + +For AGs outside `teams-setup`, `Return Contract` = unconditional. `Scope Fit` = only when the domain writes code/scripts/SQL/schemas/infra/config; drop it for pure-research/docs/review-only AGs. `Delegation` = only when `tools:` includes `Agent`; drop it for AGs that never spawn subagents. + +```markdown +## Scope Fit +Build for the actual scale and the problems that exist today; !=imagined load, !=speculative abstraction (EX: 10-user app !=hardened against lock contention). After finishing, one pass: can this be simpler -- fewer files, less config, less indirection? +Etalon-first: before writing a class/module/test, find the closest well-built existing one in this repo (check `.claude/convention/*` first) and take its principles. ADDITIVE to conventions/rules/docs, !=a replacement. + +## Delegation +Delegate only large, independent, parallelizable work; finish anything doable in a handful of tool calls yourself. != spawn a subagent to verify your own output. Keep spawn counts low -- fan out once, do not nest. + +## Return Contract +Verdict first, <=30 lines, `path:line`. !=bodies/output/log/preamble. Unconditional -- spend one step on what the MAIN SESSION needs and return only that. +Bulk material (long logs, full diffs, dumps, long reports) -> file under `.claude/reports/_/`; return the PATH, !=the content. AGs that dump everything burn the main session's context. +If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. +``` + +> agent-creator obeys this same contract for its own report -- see `agent-creator.md`'s own `## Return Contract`. + +## LLM Text Rules + +| Rule | Details | +|------|---------| +| Tables over prose, bullets over numbered | Multi-column ~66% savings, bullets when order irrelevant | +| `code` over text, inline over blocks | Identifiers, paths, short vals; blocks only if >3 lines | +| Comma-separated inline lists | `a, b, c` not bullet per item when saving space | +| One-liner rules, arrows for flow | `old` -> `new`, conditions with `->` (~40% savings) | +| No filler, no water | Cut "please note", "it's important", "only", "exactly", "basically" | +| Positive framing, no aggressive lang | "Do Y" not "Don't X"; "Use when..." not "CRITICAL: MUST..." | +| Imperative form | "Do X" not "You should do X"; 3rd person for descriptions | +| Bold for key terms, no extra formatting | `**term**` for emphasis; no decorative lines, headers, dividers | +| No emojis except status markers | Only 3 allowed: checkmark, cross, warning | +| Merge duplicates, abbreviate in tables | Single source of truth; REQ, impl, cfg, args, ret, err | + +## Color Semantics + +8 valid values (`magenta` is NOT one -- drop it if seen in old AGs). No official semantic +mapping beyond these repo conventions; the other 4 are free to assign per team. + +| Color | Use for | EXs | +|-------|---------|-----| +| cyan | Analysis, review | code-reviewer, security-analyzer | +| green | Generation, creation | test-generator, doc-generator | +| yellow | Validation, warning | PLG-validator, schema-checker | +| red | Security, critical | security-scanner, vuln-finder | +| blue, purple, orange, pink | Unassigned -- pick per project convention | -- | + +## Common AG Types + +| Type | MDL | TLs | Focus | +|------|-----|-----|-------| +| `developer-*` | opus | Read, Write, Edit, Bash, Agent | Implementation | +| `reviewer` | opus | Read, Glob, Grep | Code review | +| `tester` | sonnet | Read, Bash | Test exec | +| `arch-*` | opus | Read, Glob, Grep, WebFetch | Architecture (read-only) | +| `docs-*` | sonnet | Read, Write, Edit | Documentation | +| `explorer` | haiku | Read, Glob, Grep | Quick search | + +## Validation Checklist + +- [ ] `name`: lowercase-hyphens only (`[a-z0-9-]+`), no `:` +- [ ] `description`: within the **Description Budget** above -- single line + role + `Triggers:` keywords by DEF; `` blocks only for an ambiguous AG, under the example-block exception +- [ ] Placement: file sits in a `.claude/agents/` dir on the walk-up path from the intended launch cwd -- warn if placed under a module subfolder while sessions launch from repo root +- [ ] `tools`: minimal REQ set (least privilege), every entry survives the filters for the pool this AG runs in -- none of the nine filter-1 TLs, and `Skill` listed only when the AG invokes SKs at runtime +- [ ] Body carries no "ask/confirm with the user" instruction -- a SA cannot prompt; it returns the decision request to its caller +- [ ] Body's task-graph steps have a no-Task-TL fallback, or the AG is documented as foreground/teammate-only +- [ ] `isolation`: `worktree` or absent -- `remote` is invocation-level, never FM +- [ ] `disallowedTools`: no conflict with `tools` if both specified +- [ ] `model`: matches task complexity (fable=mythos/hardest, opus=complex, sonnet=standard, haiku=light) +- [ ] SP: tables over prose, code over text +- [ ] Project-specific knowledge included (stack, conventions, cmds) +- [ ] SP contract: generic AG -> Checklist at end + one `## Return Contract`; teams-setup domain AG -> exact six ordered headings, <=3200 bytes (~800 est-tokens), shared `team.md` loaded first, no duplicated shared-contract section +- [ ] Generic code-writing AG -> `## Scope Fit` incl. etalon-first; teams-setup keeps both only in `team.md` +- [ ] AG with `Agent` in `tools:` -> `## Delegation` states an explicit large/independent/parallelizable criterion and caps spawn counts (R7); absent for AGs that never delegate +- [ ] READ-ONLY AGs have no Write/Edit TLs +- [ ] No CD rules duplicated in AG body (already injected) +- [ ] Unique name in scope (no conflict with existing AGs) +- [ ] Optimized by the `text-optimizer` AG (or skipped -- brewtools absent, noted in report) diff --git a/brewcode/skills/agents/references/hooks-changes.md b/brewcode/skills/agents/references/hooks-changes.md new file mode 100644 index 0000000..ee14d02 --- /dev/null +++ b/brewcode/skills/agents/references/hooks-changes.md @@ -0,0 +1,98 @@ +# Hook Changes & Version History Reference + +What moved between 2.1.234 and 2.1.269, the full version history, known bugs, and two facts flagged unverified elsewhere in this skill's references. + +## Changes 2.1.234 -> 2.1.269 + +| Version | Change | What to do differently | +|---------|--------|-------------------------| +| 2.1.234 | `Notification.notification_type` gains `quota_auto_resume_fired`/`_stale`/`_disabled`; `SessionEnd.reason` loses `bypass_permissions_disabled` | match the 12-value Notification enum; never match `bypass_permissions_disabled` on SessionEnd | +| 2.1.236 | `PostToolUse` gains `classifierContext` (auto-mode classifier only) | don't use it for Claude-visible feedback -- use `additionalContext`/`decision` instead | +| 2.1.251 | `PreModelSwitch`/`PostModelSwitch` added | gate a switch with `PreModelSwitch` `permissionDecision` allow/deny/ask; its timeout BLOCKS the switch, opposite of PTU | +| 2.1.267 | `StopFailure.error` gains `cloud_credential_error`; `allowedHttpHookUrls`/`httpHookAllowedEnvVars`/`allowedChannelPlugins` fixed fail-closed on an unreadable value | match the 12-value StopFailure enum; don't assume a broken allowlist value fails open anymore | +| 2.1.268 | `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` now actually extends `SessionEnd` hooks lacking their own `timeout` (was a no-op); `PermissionRequest` hooks fixed to fire in `--print` mode | headless (`-p`) sessions can now rely on `PermissionRequest` firing | + +## Known Bugs + +| Bug | Impact | Status | Workaround | +|-----|--------|--------|------------| +| #14281 | duplicate `` injection | active | make context idempotent | + +> All routing channels (`UI`, `AC`, `decision`/`reason`, `systemMessage`, `permissionDecision`) are High reliability today; fix history is in Version History below -- no separate table. + +## Version History + +> Single merged table (event/feature additions + bug fixes) through 2.1.269. Facts marked "current" are confirmed-live but not version-pinpointed. + +| Ver | Event/Feature | Type | +|-----|--------------|------| +| 2.1.15 | fix: PTU `AC` delivery regression (introduced v2.1.12) | bug fix | +| 2.1.20 | fix: SS hooks not working for new sessions | bug fix | +| 2.1.37 | fix: plugin SS `AC` not delivered | bug fix | +| 2.1.49 | `ConfigChange` | new event | +| 2.1.50 | `WorktreeCreate`, `WorktreeRemove` | new events | +| 2.1.50 | `last_assistant_message` in Stop/SubagentStop stdin | new field | +| 2.1.52 | JSON response for TeammateIdle/TaskCompleted (was exit-code only) | enhancement | +| 2.1.63 | `http` hook type | new type | +| 2.1.69 | `InstructionsLoaded` | new event | +| 2.1.69 | `agent_id`, `agent_type` in common stdin fields | new fields | +| 2.1.70 | fix: plugin Stop/SessionEnd hooks after `/plugin` | bug fix | +| 2.1.72 | fix: skill hooks firing twice per event | bug fix | +| 2.1.73 | fix: SS hooks called twice on `--resume`/`--continue` | bug fix | +| 2.1.76 | `PCD` | new event | +| 2.1.76 | `Elicitation`, `ElicitationResult` | new events | +| 2.1.77 | fix: PTU `allow` no longer bypasses `deny` permission rules | security fix | +| 2.1.78 | `StopFailure` | new event | +| 2.1.78 | `CLAUDE_PLUGIN_DATA`, `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` | new env vars | +| 2.1.78 | `CLAUDE_PLUGIN_OPTION_` for plugin userConfig | new env var | +| 2.1.79 | fix: SessionEnd hooks reliable execution | bug fix | +| 2.1.83 | `CwdChanged`, `FileChanged` | new events | +| 2.1.83 | `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | new env var | +| 2.1.83 | fix: uninstalled plugin hooks no longer phantom-fire | bug fix | +| 2.1.84 | `TaskCreated` | new event | +| 2.1.84 | `WorktreeCreate` supports `type:"http"` | enhancement | +| 2.1.85 | conditional `if` field for tool event hooks | new feature | +| 2.1.85 | PTU can answer `AskUserQuestion` via `UI` | enhancement | +| 2.1.86 | fix: plugin scripts "Permission denied" on macOS/Linux | bug fix | +| 2.1.89 | `PermissionDenied` | new event | +| 2.1.89 | PTU `"defer"` decision -- headless pause/resume | new feature | +| 2.1.89 | hook output strings capped at 10,000 chars; over that saved to disk (path+preview in context) | enhancement | +| 2.1.89 | fix: PTU/POT `file_path` is now absolute (Write/Edit/Read) | bug fix | +| 2.1.152 | `MD` | new event | +| 2.1.152 | SS `reloadSkills`, `hookSpecificOutput.sessionTitle` outputs | enhancement | +| 2.1.163 | Stop/SubagentStop can return `hookSpecificOutput.AC` (feedback, keep turn going) | enhancement | +| 2.1.169 | `--safe-mode`/`CLAUDE_CODE_SAFE_MODE`, `disableBundledSkills`/`CLAUDE_CODE_DISABLE_BUNDLED_SKILLS` | new flags | +| 2.1.169 | self-hosted runner post-session lifecycle hook (runner-only, NOT hooks.json) | new feature | +| 2.1.191 | fix: comma- and pipe-separated matcher lists now equivalent | bug fix | +| 2.1.195 | fix: hyphenated matcher identifiers exact-match (was accidental substring match) | bug fix | +| 2.1.199 | fix: SS/Setup/SubagentStart stderr no longer silently hidden on exit 2 | bug fix | +| 2.1.199 | `CLAUDE_EFFORT`, `CLAUDE_CODE_BRIDGE_SESSION_ID` | new env vars | +| 2.1.205 | PTU `ExitPlanMode` `allowedPrompts` deprecated -- accepted and ignored | deprecation | +| 2.1.207 | `${user_config.*}` rejected in shell-form `command`/monitors/`headersHelper`; use `args` (exec form) or `$CLAUDE_PLUGIN_OPTION_` | BREAKING | +| 2.1.208 | SDK callback timeout on UserPromptSubmit BLOCKS the prompt (was: ended the turn with an execution error) | change | +| 2.1.211 | PTU `"ask"` also forces a prompt in auto mode -- the classifier can deny but not silently approve | fix | +| 2.1.214 | single-segment `dir/**` `if:` glob now matches only `/dir` (use `**/dir/**` for any-depth) | BREAKING | +| 2.1.214 | SS source `fork` (forked sessions previously reported `resume`) | new matcher | +| 2.1.218 | agent/skill-frontmatter hooks require workspace-trust dialog before running | new gate | +| 2.1.219 | `DirectoryAdded` (fires after `/add-dir`) | new event | +| 2.1.234 | `Notification.notification_type` gains `quota_auto_resume_fired`/`_stale`/`_disabled`; `SessionEnd.reason` loses `bypass_permissions_disabled` | new values / removal | +| 2.1.236 | `PostToolUse` gains `classifierContext` (auto-mode classifier only, not shown to Claude) | new field | +| 2.1.251 | `PreModelSwitch`, `PostModelSwitch` | new events | +| 2.1.267 | `StopFailure.error` gains `cloud_credential_error`; `allowedHttpHookUrls`/`httpHookAllowedEnvVars`/`allowedChannelPlugins` fixed fail-closed on an unreadable value (was fail-open) | new value / security fix | +| 2.1.268 | `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` now actually extends SessionEnd hooks without their own `timeout` (was a no-op); `PermissionRequest` hooks fixed to fire in `--print` mode | bug fix | +| current | `mcp_tool` hook type (5 types total: command/http/mcp_tool/prompt/agent) | new type | +| current | `async`, `asyncRewake`, `shell` command-hook fields | new fields | +| current | `disableAllHooks`, `allowedHttpHookUrls`, `allowManagedHooksOnly` managed settings keys | new settings | +| current | Managed/enterprise confirmed HIGHEST precedence (not lowest) | clarification | + +## Flagged unverified -- re-check before shipping + +- `PostToolUseFailure` stdin fields are limited data per current docs -- verify before depending on them (`hooks-io-contract.md`, Message Routing Matrix). +- `PermissionRequest` output: 2.1.233 testing found `decision.behavior` limited to `allow|deny` with the deny reason on `decision.message`; current docs disagree with themselves across fetches, sometimes adding `ask` or naming the reason field `permissionDecisionReason` instead. Verify with a live `claude --debug` log (`Hook JSON output had unrecognized keys` names the real field) before depending on either form (`hooks-io-contract.md`, PR -- Allow/Deny). + +## Sources + +- [Claude Code Hooks](https://code.claude.com/docs/en/hooks) +- [Claude Code Changelog](https://code.claude.com/docs/en/changelog) +- [Custom Subagents](https://code.claude.com/docs/en/sub-agents) +- Bug references: #14281 diff --git a/brewcode/skills/agents/references/hooks-env.md b/brewcode/skills/agents/references/hooks-env.md new file mode 100644 index 0000000..0c40654 --- /dev/null +++ b/brewcode/skills/agents/references/hooks-env.md @@ -0,0 +1,84 @@ +# Hook Environment Variables Reference + +Every env var a hook child process sees, the sensitive-path write prompt, and the canonical project-root resolution recipe (JS + sh). + +## Environment Variables + +| Variable | Description | Available | +|----------|-------------|-----------| +| `$CLAUDE_PROJECT_DIR` | project root; exported UNCONDITIONALLY into every hook child process (exec + shell form), and into stdio MCP / plugin LSP subprocesses. Empty in an interactive or Bash-tool shell -- that is expected, NOT evidence it is unset for hooks | all hooks | +| `$CLAUDE_PLUGIN_ROOT` | plugin install dir | plugin hooks | +| `$CLAUDE_PLUGIN_DATA` | persistent per-plugin data dir, survives updates (v2.1.78+); `~/.claude/plugins/data//` | plugin hooks | +| `$CLAUDE_CODE_REMOTE` | `"true"` in remote env | all hooks | +| `$CLAUDE_ENV_FILE` | path for persistent env vars | SS, CwdChanged, FileChanged | +| `$CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` | SessionEnd hooks timeout in ms (DEF 1500ms, v2.1.78+); actually extends hooks lacking their own per-hook `timeout` since v2.1.268 (previously a no-op) | SessionEnd hooks | +| `$CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | `1` = scrub Anthropic/cloud credentials from subprocess env (v2.1.83+) | all hooks | +| `$CLAUDE_PLUGIN_OPTION_` | plugin `userConfig` values (v2.1.78+) | plugin hooks | +| `CLAUDE_CODE_SAFE_MODE` | `1` = start CC with ALL customizations disabled (CLAUDE.md, plugins, skills, hooks, MCP); also `--safe-mode` flag; use for hook debug isolation (v2.1.169+) | startup | +| `CLAUDE_CODE_DISABLE_BUNDLED_SKILLS` | `1` = hide bundled skills/workflows/built-in cmds; also `disableBundledSkills` setting (v2.1.169+) | startup | +| `CLAUDE_EFFORT` | reasoning-effort override propagated into hook env | v2.1.199+ | +| `CLAUDE_CODE_BRIDGE_SESSION_ID` | bridge-session identifier | v2.1.199+ | +| `$CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` | raises the built-in cap on consecutive Stop-hook blocks before CC force-ends the turn regardless (DEF 8) | Stop, SubagentStop | + +> Sensitive-path prompt (2.1.233, verified in binary): a Write/Edit TOOL call under `~/.claude/**` is +> classified sensitive and routed to a permission ASK -- not a block. Carve-outs under `.claude/`: +> `skills`, `agents`, `commands`, `worktrees`, `scheduled_tasks.json`. `plugins/` is NOT carved out, so +> `$CLAUDE_PLUGIN_DATA` writes ask. Mode behaviour: default/acceptEdits/plan -> prompt; +> `bypassPermissions`/`--dangerously-skip-permissions` -> auto-approved (CHANGELOG 2.1.126); headless +> `-p` without bypass -> FAILS ("tool requires user interaction; no prompt available in headless mode"). +> Consequence: `$CLAUDE_PLUGIN_DATA` is a fully supported persistent WRITE target (official +> `project-artifact` skill Writes there), but only interactively or from a hook/Bash subprocess -- never +> from a Write/Edit tool call in an unattended run. For unattended state prefer +> `${CLAUDE_PROJECT_DIR}/.claude//`. + +### Canonical project-root resolution + +Every generated hook and installer uses this ONE recipe. Order is fixed and never silent: +env var -> git toplevel -> upward walk for `.git`/`.claude` -> `PWD`. + +```js +import { existsSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; + +/** Project root: CLAUDE_PROJECT_DIR -> upward walk for a root marker -> hook cwd. Never throws. */ +export function projectRoot(hookCwd) { + const env = process.env.CLAUDE_PROJECT_DIR; + if (env && existsSync(env)) return resolve(env); + + let dir = resolve(hookCwd || process.cwd()); + for (;;) { + if (existsSync(join(dir, '.git')) || existsSync(join(dir, '.claude'))) return dir; + const up = dirname(dir); + if (up === dir) break; + dir = up; + } + return resolve(hookCwd || process.cwd()); // last resort: never guess, never throw in a hook +} +``` + +```sh +# 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 +} + +ROOT=$(claude_project_root) || echo "WARN: no project root marker found; using $ROOT" >&2 +``` + +| Rule | Detail | +|------|--------| +| both fail | a SCRIPT warns on stderr and continues with `PWD`; an INSTALLER about to WRITE aborts non-zero naming what it looked for. Never write to a guessed root | +| hook exit code | a hook NEVER exits non-zero because the root was ambiguous -- root failure stays fail-open | +| `input.cwd` | exactly one job: resolving RELATIVE paths inside `tool_input`. Never keys config lookup, state paths or gitignore edits -- `cwd` drifts mid-session (see `CwdChanged`), `CLAUDE_PROJECT_DIR` does not | +| markers | `.git` OR `.claude`, in that order, never extended per-hook | diff --git a/brewcode/skills/agents/references/hooks-events.md b/brewcode/skills/agents/references/hooks-events.md new file mode 100644 index 0000000..c5276bd --- /dev/null +++ b/brewcode/skills/agents/references/hooks-events.md @@ -0,0 +1,122 @@ +# Hook Events Reference + +Session lifecycle order, the full 33-event table, matcher pattern syntax, and sync/async behavior. + +## Session Lifecycle + +``` +Setup (--init-only/-p --init/--maintenance only) -> InstructionsLoaded -> SS +UserPromptSubmit | UserPromptExpansion (/command path) -> PR -> PTU -> [Tool] + -> POT/PostToolUseFailure -> PermissionDenied (auto-mode denial) -> PostToolBatch (once per batch) + -> MD -> Notification -> Stop -> StopFailure -> PreCompact -> PCD -> SessionEnd +Background: CwdChanged, FileChanged, ConfigChange, DirectoryAdded, WorktreeCreate/Remove, PreModelSwitch/PostModelSwitch +Subagent: PTU:Agent -> TaskCreated -> SubagentStart -> [work] -> SubagentStop -> POT:Agent +Teams: TeammateIdle (exit 0=stop, 1=continue) | TaskCompleted (exit 0=accept, 1=redo) +``` + +## All 33 Hook Events + +> MD (v2.1.152): transforms/hides assistant message text at display layer only; non-blocking. +> NOT a hooks.json event: post-session lifecycle hook (v2.1.169) = self-hosted runner hook, runs after session ends + before workspace deleted. Configure on runner, not in hooks.json. +> `PreModelSwitch`/`PostModelSwitch` (v2.1.251) gate/observe a model switch (auto-routing, `/model`, cost-based downgrade) -- the only pair blocking something besides a tool call or a stop; its timeout blocks the switch, unlike every other blocking event's fail-open timeout. + +| # | Event | Blocking? | Matcher | Key stdin fields | Ver | +|---|-------|:---------:|---------|-----------------|-----| +| 1 | SS | No | source: `startup`,`resume`,`clear`,`compact`,`fork` | `source`,`model`,`agent_type`,`session_title` | `fork` 2.1.214 | +| 2 | UserPromptSubmit | Yes (exit 2/decision:block) | No | `prompt` | -- | +| 3 | PTU | Yes (allow/deny/ask/defer) | tool name regex | `tool_name`,`tool_input`,`tool_use_id` | -- | +| 4 | PR | Yes via `decision` object ONLY -- exit 2 NOT honored | tool name regex | `tool_name`,`tool_input`,`permission_suggestions` | -- | +| 5 | POT | After the fact -- cannot prevent the call; `decision:"block"` adds `reason`, exit 2 does not block (`hooks:839`) | tool name regex | `tool_name`,`tool_input`,`tool_response`,`tool_use_id`,`duration_ms` | -- | +| 6 | PostToolUseFailure | No | tool name regex | `tool_name`,`tool_input`,`tool_use_id`,`error`,`is_interrupt` | -- | +| 7 | Notification | No | `notification_type` (12 values, Matcher Patterns below) | `message`,`title`,`notification_type` | `quota_auto_resume_*` 2.1.234 | +| 8 | SubagentStart | No | agent type | `agent_id`,`agent_type` | -- | +| 9 | SubagentStop | Yes (decision:block) | agent type | `stop_hook_active`,`agent_id`,`agent_type`,`agent_transcript_path`,`last_assistant_message`,`background_tasks[]`,`session_crons[]` | -- | +| 10 | Stop | Yes (decision:block) | No | `stop_hook_active`,`last_assistant_message`,`background_tasks[]`,`session_crons[]` | -- | +| 11 | PreCompact | **Yes** (exit 2 / `decision:"block"`) -- blocks compaction | trigger: `manual`,`auto` | `trigger`,`custom_instructions` (manual only), `transcript_path` | -- | +| 12 | PCD | No | trigger: `manual`,`auto` | `transcript_path` | 2.1.76 | +| 13 | SessionEnd | No | reason: `clear`,`resume`,`logout`,`prompt_input_exit`,`other` (`bypass_permissions_disabled` removed 2.1.234) | -- | -- | +| 14 | TeammateIdle | Yes (exit 2 only) | No | `teammate_name`,`team_name` | -- | +| 15 | TaskCompleted | Yes (exit 2 only) | No | `task_id`,`task_subject`,`task_description`,`teammate_name`,`team_name` | -- | +| 16 | ConfigChange | Yes | source: `user_settings`,`project_settings`,`local_settings`,`policy_settings`,`skills` | `source`,`file_path` | 2.1.49 | +| 17 | WorktreeCreate | Yes | No | -- | 2.1.50 | +| 18 | WorktreeRemove | **Yes** -- ANY non-zero exit blocks the removal, if the worktree path still exists after the hook runs | No | -- | 2.1.50 | +| 19 | InstructionsLoaded | No | load_reason: `session_start`,`nested_traversal`,`path_glob_match`,`include`,`compact` | `file_path`,`memory_type`,`load_reason`,`globs`,`trigger_file_path`,`parent_file_path` | 2.1.69 | +| 20 | Elicitation | Yes | MCP server name | MCP-specific fields | 2.1.76 | +| 21 | ElicitationResult | Yes | MCP server name | MCP-specific fields | 2.1.76 | +| 22 | StopFailure | No -- output + exit code IGNORED except `terminalSequence` | error type (12): `rate_limit`,`overloaded`,`authentication_failed`,`oauth_org_not_allowed`,`account_on_hold`,`billing_error`,`invalid_request`,`model_not_found`,`server_error`,`max_output_tokens`,`cloud_credential_error`,`unknown` | `error`,`error_details`,`last_assistant_message` | 2.1.78; `account_on_hold`/`cloud_credential_error` added 2.1.267 | +| 23 | CwdChanged | No | No | -- | 2.1.83 | +| 24 | FileChanged | No | filename (basename) | `file_path` | 2.1.83 | +| 25 | TaskCreated | Yes | No | `task_id`,`task_subject`,`task_description`,`teammate_name`,`team_name` | 2.1.84 | +| 26 | PermissionDenied | No -- exit code + stderr IGNORED; `hookSpecificOutput.retry` only | **tool name regex** (same values as PTU) | `tool_name`,`tool_input`,`tool_use_id`,`reason` | 2.1.89 | +| 27 | MD | No | No | assistant message text | 2.1.152 | +| 28 | Setup | No -- any exit code continues | trigger: `init`,`maintenance` | `trigger` | -- | +| 29 | UserPromptExpansion | **Yes** (`decision:"block"` / exit 2) -- blocks the expansion | command name (`command_name`) | `expansion_type`,`command_name`,`command_args`,`command_source`,`prompt` | -- | +| 30 | PostToolBatch | **Yes** -- stops the agentic loop before the next model call | None (unsupported) | `tool_calls[]` = `{tool_name,tool_input,tool_use_id,tool_response}` | -- | +| 31 | DirectoryAdded | No -- fires AFTER the add, `continue` discarded | source: `slash_command`,`register_repo_root` | `directory`,`source` | 2.1.219 | +| 32 | PreModelSwitch | **Yes** (`permissionDecision`: allow/deny/ask; timeout BLOCKS the switch) | canonical `to_model` (name, `\|`-list, or regex) | `from_model`,`to_model`,`requested_model`,`source`,`context_tokens`,`pricing` | 2.1.251 | +| 33 | PostModelSwitch | No -- model already switched | canonical `to_model` | same fields, plus `source` also `auto`,`resume` | 2.1.251 | + +> Setup fires ONLY on `claude --init-only` / `-p --init` / `-p --maintenance`, never on normal startup -- use SS for per-session init. Setup supports `command` + `mcp_tool` types only, and has `CLAUDE_ENV_FILE`. +> UserPromptExpansion covers the path PTU cannot: a PTU hook on the `Skill` tool never fires when the user types `/skillname` directly. +> PostToolBatch `tool_response` is the serialized `tool_result` content the model sees; POT's `tool_response` is the tool's structured `Output` object (`{filePath,success}` for `Write`). Do NOT reuse a POT parser here. +> DirectoryAdded does not fire for `--add-dir` at startup (SS covers those). Its `systemMessage` reaches Claude on the next turn under `slash_command`, and the debug log only under `register_repo_root`. + +### Common stdin (ALL events) + +```json +{"session_id":"abc123","transcript_path":"/path/to/transcript","cwd":"/project","permission_mode":"default","hook_event_name":"PreToolUse","agent_id":"uuid (SAs only, v2.1.69+)","agent_type":"Explore|Plan|custom (SAs + --agent, v2.1.69+)"} +``` + +`permission_mode` is one of 6 confirmed values: `default`,`plan`,`acceptEdits`,`auto`,`dontAsk`,`bypassPermissions`. + +## Async Hooks + +```json +{"type":"command","command":"node /path/to/hook.mjs","async":true} +``` + +| Behavior | Details | +|----------|---------| +| execution | background, non-blocking. `command` type only | +| `decision` fields | IGNORED | +| `systemMessage` | delivered on NEXT turn | +| `AC` | may not arrive before Claude processes | +| `asyncRewake:true` | implies `async`; exit 2 wakes Claude with the hook's stderr (or stdout when stderr is empty) as a system reminder -- the only channel a background hook has for a late failure | +| blocking events | always synchronous (PTU, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, PreCompact, PostToolBatch, ConfigChange, PreModelSwitch) | +| use case | logging, metrics, slow file ops | + +| Event | Sync/Async | Reason | +|-------|-----------|--------| +| SS | sync (waits) | context needed before first turn | +| PTU | sync (blocks) | must decide allow/deny before exec | +| POT | async OK when advisory | sync if it blocks or rewrites `updatedToolOutput` | +| PreCompact | sync (blocks) | can veto compaction; must write any handoff first | +| PreModelSwitch | sync (blocks) | must decide allow/deny/ask before the switch; timeout blocks too | +| Notification | async OK | informational | + +## Matcher Patterns + +| Event | Matcher type | Examples | +|-------|-------------|----------| +| PTU, POT, PostToolUseFailure, PR, **PermissionDenied** | tool name | `Bash`, `Write\|Edit`, `Task\|Agent`, `mcp__.*` | +| SS | source string | `startup`,`resume`,`clear`,`compact`,`fork` | +| Setup | CLI flag | `init`,`maintenance` | +| SessionEnd | reason string | `clear`,`resume`,`logout`,`prompt_input_exit`,`other` | +| SubagentStart/SubagentStop | agent type | `Explore`,`Plan`,`general-purpose`,`my-agent`, plugin-scoped `^my-plugin:reviewer$` | +| PreCompact/PCD | trigger | `manual`,`auto` | +| Notification | type string (12) | `permission_prompt`,`idle_prompt`,`auth_success`,`elicitation_dialog`,`elicitation_url_dialog`,`elicitation_complete`,`elicitation_response`,`agent_needs_input`,`agent_completed`,`quota_auto_resume_fired`,`quota_auto_resume_stale`,`quota_auto_resume_disabled` | +| ConfigChange | source string | `user_settings`,`project_settings`,`local_settings`,`policy_settings`,`skills` | +| InstructionsLoaded | load reason | `session_start`,`nested_traversal`,`path_glob_match`,`include`,`compact` | +| DirectoryAdded | how it was added | `slash_command`,`register_repo_root` | +| UserPromptExpansion | command name | your skill or command names | +| FileChanged | filename (basename) | `.envrc\|.env` | +| StopFailure | error type (12) | `rate_limit`,`overloaded`,`authentication_failed`,`oauth_org_not_allowed`,`account_on_hold`,`billing_error`,`invalid_request`,`model_not_found`,`server_error`,`max_output_tokens`,`cloud_credential_error`,`unknown` | +| Elicitation/ElicitationResult | MCP server name | server name string | +| PreModelSwitch, PostModelSwitch | canonical `to_model` | model name, `\|`-list, or regex: `"claude-opus-5"`, `.*opus.*` | +| Stop, UserPromptSubmit, **PostToolBatch**, TeammateIdle, TaskCompleted, TaskCreated, WorktreeCreate, WorktreeRemove, CwdChanged, MD | No matcher | always fires | + +> Omit `matcher` (or `"*"`/`""`) -> fires for ALL instances of that event. +> Evaluation: only letters/digits/`_`/`-`/space/`,`/`|` -> exact string or `|`,`,`-separated list of exact strings. ANY other character -> unanchored JS regex, so `Edit.*` also matches `NotebookEdit`; anchor as `^Edit$` for whole-string. +> `FileChanged` and `StopFailure` use a NARROWER exact set (letters, digits, `_`, `|`): a hyphen, space or comma there stays on the regex path and only `|` separates. +> MCP tools: the trailing `.*` is MANDATORY -- `mcp__memory` is exact-match and matches nothing; use `mcp__memory__.*`. A PLUGIN-bundled server is scoped: `mcp__plugin____`, so `mcp__plugin_my-plugin_db__.*`. A matcher on the bare server key never fires. Same scoped name in `if`. +> Hyphenated matcher identifiers exact-match since v2.1.195 (was accidental substring match). Comma- and pipe-separated matcher lists equivalent since v2.1.191. diff --git a/brewcode/skills/agents/references/hooks-io-contract.md b/brewcode/skills/agents/references/hooks-io-contract.md new file mode 100644 index 0000000..8cf6e54 --- /dev/null +++ b/brewcode/skills/agents/references/hooks-io-contract.md @@ -0,0 +1,182 @@ +# Hook I/O Contract Reference + +Common stdin fields, the message-routing matrix (which channel Claude actually sees), exit codes, and every output schema. + +### Common stdin (ALL events) + +Same fields on every event, JSON shape + `permission_mode`'s 6 values: "Common stdin" in `hooks-events.md`. + +## Message Routing Matrix + +Consult BEFORE choosing output -- wrong channel = silently ignored (no error). `UI`=`updatedInput`. + +| Event | `AC` (Claude sees) | `decision`/reason | IGNORED (do not use) | +|-------|---------------------|--------------------|------------------------| +| SS | YES, ``, stable | -- | `UI` | +| UserPromptSubmit | YES, appended; **cannot rewrite prompt** | `decision:"block"` -> UI only, Claude does NOT see reason | **`UI` -- IGNORED** (root cause of the `forced-eval.mjs` bug: emitted `UI.prompt` here, silently dropped) | +| PTU | YES, stable | `permissionDecision`: allow/deny/ask/defer; `permissionDecisionReason` on deny; `"defer"` pauses headless, resume `-p --resume` (v2.1.89+) | `updatedToolOutput` | +| POT | YES, stable (#15345) | **AFTER THE FACT** -- runs after the tool, cannot prevent the call: `decision:"block"`+`reason` appends the reason next to the tool result; `updatedToolOutput` replaces what Claude sees. Side effects stand; also carries `updatedMCPToolOutput` (MCP tools) and `classifierContext` (auto-mode classifier only, not shown to Claude, 2.1.236) | -- | +| PostToolUseFailure | YES, limited data -- verify against docs before depending on it | `additionalContext` | -- | +| PostToolBatch | YES, injected once before the next model call | `decision:"block"` / `continue:false` stops the agentic loop | `UI` | +| SubagentStart | YES, into SA (not parent) | -- | -- | +| PreModelSwitch | NO -- gate only, no context injection | `permissionDecision`: allow/deny/ask (no `defer`); priority `deny > ask > allow`; timeout BLOCKS the switch (opposite of PTU, where a timeout lets the call through) | `updatedInput`, `additionalContext` | +| PostModelSwitch | YES, delivered on the NEXT turn | -- (cannot block, the switch already happened) | `decision`, `permissionDecision` | +| Notification | YES, stable | -- | -- | +| Stop | YES, feedback + keeps turn going, not hook-error label (v2.1.163+); or `decision:"block"`+`reason` -> Claude continues, sees reason | -- | `AC` for blocking (use `decision` instead) | +| SubagentStop | same as Stop, scoped to SA | -- | same | +| PreCompact | N/A, not supported | **BLOCKING**: exit 2 or `decision:"block"` blocks compaction | `systemMessage`, `continue` -- both discarded | +| PostCompact | N/A, not supported | -- | `systemMessage`, `continue` -- both discarded | +| SessionEnd | N/A, not supported | -- | -- (informational only) | +| Setup | YES, `AC` (plain stdout -> debug log only) | -- cannot block, any exit code continues | -- | +| UserPromptExpansion | YES, alongside the expanded prompt | `decision:"block"` prevents the command from expanding; `reason` -> USER | -- | +| DirectoryAdded | via `systemMessage` on the NEXT turn (matcher `slash_command` only) | -- cannot block, the dir is already added | `continue` -- discarded | +| PR | N/A | `decision.behavior`: `allow\|deny` (this file's tested value); reason via `decision.message` -- **field name unresolved, see the PR note in Output Schemas below, verify before shipping**; exit 2 is NOT honored | exit 2 | +| PermissionDenied | via `hookSpecificOutput` only | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -> model may retry; auto-mode denials only (v2.1.89+) | exit code, stderr, top-level `retry` | +| TeammateIdle, TaskCompleted, TaskCreated | N/A | JSON `{continue, stopReason}` (v2.1.52+) | -- | + +### stdout (exit 0, JSON) + +| Event | Claude sees? | +|-------|:---:| +| SS, UserPromptSubmit, PTU | YES -- parsed, context injected | +| All others | NO -- verbose mode only (Ctrl+O) | + +### systemMessage + +Goes to user UI only -- Claude does NOT see it. Exception: async hooks deliver on next turn. + +### stderr (exit 2) + +| Type | Claude sees? | Events | +|------|:---:|--------| +| Blocking (exit 2 stops the action) | YES | PTU, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange (except `policy_settings`), PostToolBatch, **PreCompact**, **PreModelSwitch**, Elicitation, ElicitationResult, WorktreeCreate, **WorktreeRemove** (ANY non-zero aborts, both Worktree events) | +| Non-blocking, stderr still reaches Claude | YES | POT, PostToolUseFailure | +| Non-blocking | NO (UI/debug log only) | SS, Setup, SubagentStart, PCD, Notification, SessionEnd, InstructionsLoaded, CwdChanged, FileChanged, DirectoryAdded (debug log), **PostModelSwitch**, MD | +| exit 2 IGNORED entirely | NO | **PR** (use `decision`), **PermissionDenied** (use `hookSpecificOutput.retry`), StopFailure (except `terminalSequence`) | + +### UI (PTU only) + +Silently modifies tool params. Claude unaware of change. `UI` also rewrites on PR. `UI` is single-writer/last-wins -- every hook on the event sees the same original input, runner keeps only the last edit -- reserve for ONE owning hook; for SA prompt injection prefer SubagentStart `AC` instead (accumulates across hooks, no clobbering). + +### Exit codes + +| Code | Meaning | stdout | stderr | +|------|---------|--------|--------| +| 0 | Success | parsed as JSON; TeammateIdle/TaskCompleted: teammate terminates | verbose mode | +| 1 | Error (non-fatal) | TeammateIdle/TaskCompleted: teammate continues; others: error | verbose mode | +| 2 | Critical error | IGNORED | -> Claude (blocking) or user (non-blocking) | + +| Event | exit 0 | exit 1 | exit 2 | +|-------|--------|--------|--------| +| PTU | JSON processed | non-blocking error, tool call proceeds | stderr -> Claude, blocks the call | +| Stop | JSON processed | non-blocking error | stderr -> Claude | +| SubagentStop | JSON processed | non-blocking error | stderr -> Claude | +| SS | JSON processed | warning in UI | stderr -> UI | +| PreCompact | JSON processed | compact continues | **blocks compaction**, stderr -> UI on manual `/compact` | +| PreModelSwitch | JSON processed (`permissionDecision`) | non-blocking error, switch proceeds | stderr -> Claude, **blocks the switch**; a TIMEOUT also blocks it -- the one event where a hook timeout is not fail-open | +| PostModelSwitch | JSON processed (`additionalContext`, delivered next turn) | non-blocking error | stderr -> debug log only, event is non-blocking | +| TeammateIdle | teammate terminates | teammate continues | stderr -> UI | +| TaskCompleted | task accepted | task re-assigned | stderr -> UI | +| POT | JSON processed | non-blocking error | stderr -> Claude; tool already ran, call not prevented | +| PR | JSON `decision` processed | non-blocking error | **IGNORED** -- permission flow proceeds unchanged | +| PermissionDenied | `hookSpecificOutput.retry` processed | ignored | **IGNORED** -- the denial already happened | +| Setup | JSON processed | JSON honored if schema-valid, else UI notice | stderr -> UI notice; session continues | +| UserPromptExpansion | JSON processed | non-blocking error | **blocks the expansion**, stderr -> user as `reason` | +| PostToolBatch | JSON processed | non-blocking error | **stops the agentic loop**, stderr -> Claude | +| DirectoryAdded | JSON processed (`continue` dropped) | debug log | stderr -> debug log; the dir is already added | +| WorktreeCreate | JSON processed | **creation FAILS** | **creation FAILS** (ANY non-zero) | +| WorktreeRemove | JSON processed | **removal FAILS** (path still exists) | **removal FAILS** (ANY non-zero) | +| `http`/`mcp_tool` type (any event) | N/A -- no OS exit code | N/A | N/A | + +> Sample, not exhaustive (33 events total). Exit 1 is a NON-blocking error EVERYWHERE except `WorktreeCreate`/`WorktreeRemove` (ANY non-zero fails the operation) -- to enforce a policy use `exit 2` or JSON, never `exit 1`. `PreModelSwitch` is the one event where even a TIMEOUT blocks; every other blocking event's timeout is fail-open (lets the action through). +> `http`/`mcp_tool` convey success/failure via response JSON (`decision`/`AC`) or HTTP/tool-call failure, not exit code; 2xx + empty body = pass-through, 2xx + non-JSON body = non-blocking error. + +## Output Schemas + +Single-field schemas (compact): + +| Event -- purpose | Schema | +|---|---| +| PTU -- allow w/ context | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"..."}}` | +| PTU -- deny | `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}` | +| Stop -- block | `{"decision":"block","reason":"Task not complete. Continue with phase 3."}` | +| SubagentStop -- block | `{"decision":"block","reason":"Review not finished. Check remaining files."}` | +| SubagentStart -- inject into SA | `{"hookSpecificOutput":{"hookEventName":"SubagentStart","additionalContext":"Context injected into SUBAGENT (not parent)"}}` | +| UserPromptSubmit -- block | `{"decision":"block","reason":"Reason shown to USER only (Claude does NOT see this)"}` | +| POT -- feedback | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"Post-tool feedback for Claude"}}` | +| POT -- block (feedback next to the result) | `{"decision":"block","reason":"Lint failed; fix before continuing."}` -- top-level, NOT `hookSpecificOutput` | +| POT -- replace what Claude sees | `{"hookSpecificOutput":{"hookEventName":"PostToolUse","updatedToolOutput":{"stdout":"[redacted]","stderr":"","interrupted":false,"isImage":false}}}` -- value MUST match the tool's output shape or it is ignored | +| PostToolBatch -- inject once | `{"hookSpecificOutput":{"hookEventName":"PostToolBatch","additionalContext":"..."}}`; `{"decision":"block","reason":"..."}` stops the agentic loop | +| Setup -- context | `{"hookSpecificOutput":{"hookEventName":"Setup","additionalContext":"Dependencies installed"}}` | +| UserPromptExpansion -- block | `{"decision":"block","reason":"Shown to the USER","hookSpecificOutput":{"hookEventName":"UserPromptExpansion","additionalContext":"..."}}` | +| PreModelSwitch -- gate a switch | `{"hookSpecificOutput":{"hookEventName":"PreModelSwitch","permissionDecision":"ask","permissionDecisionReason":"Switching now re-sends ~180k tokens. Continue?"}}` | +| PostModelSwitch -- context after switch | `{"hookSpecificOutput":{"hookEventName":"PostModelSwitch","additionalContext":"Now running claude-opus-5"}}` | +| TeammateIdle/TaskCompleted/TaskCreated -- control (v2.1.52+) | `{"continue":false,"stopReason":"Task limit reached."}` | +| PermissionDenied -- retry (v2.1.89+) | `{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}` -- top-level `retry` is NOT read. Tells the model it MAY retry; does not reverse the denial. Ignored for no-verdict denials | +| WorktreeCreate -- return path (v2.1.84+, http hooks) | `{"hookSpecificOutput":{"hookEventName":"WorktreeCreate","worktreePath":"/path/to/worktree"}}` | +| Empty pass-through | `{}` | + +> PostToolUse also carries `updatedMCPToolOutput` (same idea as `updatedToolOutput`, MCP tools only -- +> prefer `updatedToolOutput` when both apply) and `classifierContext` (<=2000 chars, auto-mode classifier +> only, v2.1.236 -- never shown to Claude, do not use it for feedback). + +### PTU -- Modify input + +`updatedInput` REPLACES the entire `tool_input` object -- always spread the original, never send a partial: +```json +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"prompt":"Modified prompt text","other_field":"preserved"}}} +``` +```js +output({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow', + updatedInput:{...input.tool_input, prompt:'Modified prompt text'}}}); +``` + +### PTU -- Answer AskUserQuestion (v2.1.85+) + +Echo back the original `questions` array and add an `answers` object mapping question text -> chosen label: +```json +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","updatedInput":{"questions":[{"question":"Which database?","header":"DB","options":[{"label":"PostgreSQL"},{"label":"MySQL"}],"multiSelect":false}],"answers":{"Which database?":"PostgreSQL"}}}} +``` +> `"allow"` ALONE is not sufficient for `AskUserQuestion`/`ExitPlanMode` -- it must carry `updatedInput`. Multi-select labels join with commas. PTU precedence across hooks: `deny` > `defer` > `ask` > `allow`. + +### SS -- Context injection +```json +{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"Injected context for Claude","sessionTitle":"My session title","reloadSkills":true},"systemMessage":"Status shown to user only"} +``` +> `reloadSkills:true` re-scans skill + command dirs after the SS hooks finish, so a skill the hook installed is usable in the SAME session. `sessionTitle` applies on `startup`/`resume`/`fork`, ignored on `clear`/`compact`. `initialUserMessage` creates the first turn in `-p` mode (`AC` only attaches to an existing one). + +### PR -- Allow/Deny +```json +{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}} +``` + +| `behavior` | Effect | +|------------|--------| +| `allow` | auto-allow | +| `deny` | reject without prompting | + +> Unresolved -- flagged, not asserted: 2.1.233 testing found `decision.behavior` limited to `allow\|deny` +> with the deny reason on `decision.message`; current docs disagree with themselves across fetches, +> sometimes adding `ask` or naming the reason field `permissionDecisionReason` instead. Verify with a +> live `claude --debug` log (`Hook JSON output had unrecognized keys` names the real field) before +> depending on either form. `ask` otherwise exists only as PTU's `permissionDecision` value. + +### PR -- Allow with permission mutation +```json +{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","updatedInput":{"command":"npm test"},"updatedPermissions":[{"type":"addRules","rules":[{"toolName":"Bash","ruleContent":"npm *"}],"behavior":"allow","destination":"session"}]}}} +``` + +### Elicitation -- MCP form response (v2.1.76+) +```json +{"hookSpecificOutput":{"hookEventName":"Elicitation","action":"accept","content":{"field_name":"value"}}} +``` + +| `action` | Effect | +|----------|--------| +| `accept` | auto-fill MCP form with `content` | +| `decline` | decline elicitation | +| `cancel` | cancel elicitation | + +## Output size cap + +`additionalContext`, `systemMessage` and plain stdout are capped at 10,000 chars (v2.1.89); over that the value is written to a file and replaced by a preview + path, so a gate's reason can arrive truncated. Fail-safe design + templates: `hooks-templates.md`. diff --git a/brewcode/skills/agents/references/hooks-templates.md b/brewcode/skills/agents/references/hooks-templates.md new file mode 100644 index 0000000..07861f6 --- /dev/null +++ b/brewcode/skills/agents/references/hooks-templates.md @@ -0,0 +1,159 @@ +# Hook Authoring Templates & Checklist Reference + +Bash and JS/mjs hook skeletons, fail-safe design rules, common patterns, and the pre-ship validation checklist. + +## Templates + +### Bash Hook Template + +Exactly ONE `printf` reaches stdout, on every path. Decide into `$DECISION`, emit once at the end -- +never `echo '{}'` before a decision, or the hook prints two objects and the decision is discarded. + +```bash +#!/bin/bash +set -euo pipefail +# Hook: PreToolUse | Matcher: Bash | Purpose: deny destructive commands +INPUT=$(cat) +EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // empty') +COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') + +# Stop/SubagentStop only -- prevents an infinite block loop. +STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false') +if [ "$STOP_ACTIVE" = "true" ]; then printf '%s\n' '{}'; exit 0; fi + +DECISION='{}' # pass-through: hook renders no verdict +if printf '%s' "$COMMAND" | grep -qE 'rm[[:space:]]+-rf'; then + DECISION=$(jq -n --arg reason "Destructive command blocked by hook" \ + '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$reason}}') +fi + +printf '%s\n' "$DECISION" # the ONLY write to stdout +``` + +Swap the `DECISION=$(jq -n ...)` line per event -- the shape changes, the single-emit structure does not: + +| Event | `DECISION=$(jq -n ...)` payload | +|-------|--------------------------------| +| PTU inject context | `'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":$ctx}}'` | +| Stop block | `'{"decision":"block","reason":$reason}'` | +| POT block | `'{"decision":"block","reason":$reason}'` | +| SS context | `'{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":$ctx}}'` | + +> A hook enforcing a hard invariant must not let a `jq`/parse failure become silent approval: `set -euo pipefail` aborts before the `printf`, which Claude Code reads as a non-blocking error and the action proceeds. Wrap the check so failure lands on the deny branch, not on an abort. + +### JS/mjs Hook Template + +`output()` is called exactly once on every path, `decide()` is the only place that chooses a verdict. + +```javascript +#!/usr/bin/env node +// Hook: PreToolUse | Matcher: Bash | Purpose: deny destructive commands + +async function readStdin() { + const chunks = []; + for await (const chunk of process.stdin) chunks.push(chunk); + return JSON.parse(Buffer.concat(chunks).toString('utf8')); +} +function output(response) { console.log(JSON.stringify(response)); } + +/** Returns the single JSON object this hook prints. `{}` = no verdict, not approval. */ +function decide(input) { + // Stop/SubagentStop only -- prevents an infinite block loop. + if (input.stop_hook_active) return {}; + + // per-event fields: see "Key stdin fields", hooks-events.md. + // UserPromptSubmit -> input.prompt | POT -> input.tool_response | PostToolBatch -> input.tool_calls + // PreModelSwitch/PostModelSwitch -> input.to_model + const command = input.tool_input?.command ?? ''; + if (/rm\s+-rf/.test(command)) { + return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', + permissionDecisionReason: 'Destructive command blocked by hook' } }; + } + return {}; +} + +async function main() { + try { + output(decide(await readStdin())); + } catch (error) { + console.error(`Hook error: ${error.message}`); // stderr never pollutes the JSON contract + output({}); // advisory hooks fail open; see Best Practices below for gates + } +} +main(); +``` + +Other verdicts are a different `decide()` return, never a second `output()`: + +| Event | `decide()` returns | +|-------|--------------------| +| PTU inject context | `{hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow',additionalContext:'...'}}` | +| PTU modify input | `{hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow',updatedInput:{...input.tool_input,prompt:'...'}}}` | +| Stop / SubagentStop block | `{decision:'block',reason:'Task incomplete'}` | +| POT block | `{decision:'block',reason:'Lint failed'}` | +| PermissionDenied retry | `{hookSpecificOutput:{hookEventName:'PermissionDenied',retry:true}}` | +| PreModelSwitch gate | `{hookSpecificOutput:{hookEventName:'PreModelSwitch',permissionDecision:'ask',permissionDecisionReason:'...'}}` | + +> Multi-hook plugin: extract `readStdin`/`output` into `lib/utils.mjs`, `import` into each hook file. + +## Best Practices + +### Fail-Safe Design + +| Practice | Why | +|----------|-----| +| Always `output({})` on error | !=trap user in broken state (advisory hooks -- see the fail-open/fail-closed row below) | +| Print exactly ONE JSON object to stdout, on every path | extra stdout lines corrupt parsing; CC reads a single JSON object. Decide into a variable, emit once | +| All logging/diagnostics to stderr (`console.error`) | stdout reserved for the JSON contract | +| `stop_hook_active` check in Stop/SubagentStop | prevents infinite block loop | +| try/catch around all logic | graceful degradation | +| validate stdin before parsing | handle missing/malformed input | +| keep every output string under 10,000 chars | over the cap the value is written to a file and previewed, truncating a gate's reason -- full cap mechanics: `hooks-io-contract.md` Output size cap | +| choose fail-open vs fail-closed from the invariant | fail-open (`{}`) is right for advisory/context hooks -- a broken hook then has no effect. A hook enforcing a HARD invariant must instead emit the deny/block with the exception text as its `reason`, because `{}` on an enforcement hook is silent approval | + +> Infinite loop protection (Stop/SubagentStop): check `stop_hook_active` and short-circuit to `{}` -- see both templates above. CC also force-ends the turn after 8 consecutive Stop-hook blocks (raise via `$CLAUDE_CODE_STOP_HOOK_BLOCK_CAP`); a broken loop-brake wastes turns, it doesn't hang the session. +> `exit 1` is a non-blocking error nearly everywhere: the action proceeds. Enforce with `exit 2` or JSON, never `exit 1`. A mistyped script path exits 127 and leaves the gate silently disabled -- watch for the ` hook error` notice on a policy hook's first run. + +## Common Hook Patterns + +| Pattern | matcher | hooks[0] | Mechanism | +|---------|---------|----------|-----------| +| Inject context into all SAs | `SubagentStart` / none | `{"type":"command","command":"node inject-context.mjs"}` | returns `AC`, accumulates across hooks -- prefer over `UI` on PTU `Task\|Agent` (single-writer/last-wins) | +| Gate dangerous tools | `PreToolUse` / `Bash` | `{"type":"command","command":"bash validate-bash.sh"}` | checks `tool_input.command`, `permissionDecision:"deny"` if dangerous | +| Block stop until task complete | `Stop` / none | `{"type":"command","command":"node check-task.mjs"}` | `decision:"block"`+`reason` while incomplete | +| Log all tool calls | `PostToolUse` / none | `{"type":"command","command":"node logger.mjs","async":true}` | fire-and-forget, no output needed | +| Inject project context on SS | `SessionStart` / none | `{"type":"command","command":"bash session-init.sh"}` | returns `AC` with project state | + +## Hook Type Selection + +> Type decision: hook-creator.md Step 2, or the full type/field table in `hooks-types-config.md`. +> Lifecycle: hooks load at session start. Config changes require `/clear` or new session. + +## Workflow + +1. Clarify+Design: event, behavior, bash/JS, matcher, output schema, routing channel, config location +2. Implement: use template, add logic, handle errors; configure in settings/hooks.json +3. Test: `CLAUDE_DEBUG=1`, check verbose (Ctrl+O). Isolate bugs: `claude --safe-mode`/`CLAUDE_CODE_SAFE_MODE=1` disables ALL customizations (CLAUDE.md, plugins, skills, hooks, MCP) to confirm hook is cause (v2.1.169+) + +## Validation Checklist + +| # | Check | +|---|-------| +| 1 | correct event type matches intended trigger | +| 2 | matcher pattern (regex for tools, string for sources) | +| 3 | output schema correct for event | +| 4 | routing channel (`AC` vs `UI` vs `decision`) | +| 5 | fail-safe: `output({})` in catch block | +| 6 | `stop_hook_active` in Stop/SubagentStop hooks | +| 7 | stdin parsing handles missing/null fields | +| 8 | executable (`chmod +x` for bash, `#!/usr/bin/env node` for mjs) | +| 9 | config location correct for scope | +| 10 | performance <1s for blocking hooks | +| 11 | check routing matrix for broken channels | +| 12 | syntax check (`bash -n` or `node --check`) | +| 13 | `if` field (v2.1.85+) to reduce overhead when applicable -- tool events only | +| 14 | hook type (`command` deterministic, `http` API/remote, `mcp_tool` MCP tool, `prompt`/`agent` allow-block gate) | +| 15 | exactly ONE JSON object on stdout on EVERY path -- test the pass-through path too, not just the decision path | +| 16 | fail-open vs fail-closed matches the invariant; an enforcement hook never returns `{}` on error | +| 17 | every output string under 10,000 chars | +| 18 | `args` (exec form) whenever the command references a path placeholder | diff --git a/brewcode/skills/agents/references/hooks-types-config.md b/brewcode/skills/agents/references/hooks-types-config.md new file mode 100644 index 0000000..7259ad2 --- /dev/null +++ b/brewcode/skills/agents/references/hooks-types-config.md @@ -0,0 +1,156 @@ +# Hook Types & Configuration Reference + +The 5 hook types and their config fields, plus where hooks.json/settings/frontmatter are read (precedence + plugin scoping). Env vars: `hooks-env.md`. + +## Hook Types + +| Type | Description | Timeout | Use case | +|------|-------------|---------|----------| +| `command` | shell/node script, JSON via stdin/stdout | 600s | custom logic, file I/O, external tools | +| `http` | POSTs the FULL hook JSON payload to a URL, blocks for the response, parses a 2xx JSON body as hook output (decision / `AC`). Same payload as `command` stdin -- no field is renamed (v2.1.63+) | 600s | external API/webhook, remote delegation | +| `mcp_tool` | invokes a tool on an already-configured MCP server and AWAITS it synchronously; returned text content parsed exactly like a `command` hook's stdout JSON (can return `decision:block` or `hookSpecificOutput.additionalContext`) | 600s | reuse an MCP tool as gate/injector | +| `prompt` | inline-LLM allow/block GATE: evaluates the prompt, decides allow vs block, surfaces a reason on block. Its NL text is NOT added to the model's context | 30s | quick validation / policy gate | +| `agent` | LLM-agent allow/block GATE, same semantics as `prompt` (evaluate condition -> allow or block+reason). NOT a general subagent whose output is injected. Experimental | 60s | complex condition gate | + +> `prompt`/`agent` = gates (allow/block only). `command`/`http`/`mcp_tool` = can both gate AND inject context. +> `mcp_tool` hooks are skipped on `Setup` and on `SessionStart` at process start (MCP not yet connected); they work on `SessionStart` after `/clear`/compact. + +### `prompt`/`agent` output schema + +Both return `{"ok": boolean, "reason": string}` -- `ok:false` triggers block/deny, `reason` explains why. + +| Field | Type | Only on | Effect | +|-------|------|---------|--------| +| `impossible` | boolean | `prompt`, Stop/SubagentStop only | `true` allows the stop instead of blocking, even though `ok` is false | +| `continueOnBlock` | boolean | `prompt` only (PTU/POT) | default: `ok:false` ends the turn, `reason` shown as a warning. `true`: `reason` is returned to Claude as a tool error instead, turn continues. `agent`-type hooks behave as `continueOnBlock:true` always and have no such field | + +### mcp_tool config fields + +| Field | Req | Description | +|-------|:---:|-------------| +| `server` | yes | name of a configured MCP server. A PLUGIN-bundled server takes the scoped form `plugin::` -- the bare key never resolves | +| `tool` | yes | tool name to invoke | +| `input` | no | args object; string values support `${...}` interpolation from hook input JSON (e.g. `"${tool_input.file_path}"`) | +| `if`,`timeout`,`statusMessage`,`once` | no | same as other types | + +### Common fields (ALL five types) + +| Field | Req | Description | +|-------|:---:|-------------| +| `type` | yes | `"command"`,`"http"`,`"mcp_tool"`,`"prompt"`,`"agent"` | +| `if` | no | ONE permission rule (v2.1.85+): `"Bash(git *)"`,`"Edit(*.ts)"`. No `&&`/`\|\|`/list -- one rule per handler. Evaluated ONLY on PTU, POT, PostToolUseFailure, PR, PermissionDenied; on any other event a hook with `if` set NEVER runs. Best-effort/fails open -- !=a hard gate | +| `timeout` | no | seconds before cancellation. DEF 600 (`command`/`http`/`mcp_tool`), 30 (`prompt`), 60 (`agent`). UserPromptSubmit/PreModelSwitch/PostModelSwitch lower the 600 to 30, MessageDisplay to 10; SessionEnd hooks share a 1.5 s budget (raised to your `timeout`, max 60 s, or via `$CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS`) | +| `statusMessage` | no | spinner text while the hook runs | +| `once` | no | `true` = run once per session then de-register. Honored ONLY in skill frontmatter; ignored in settings files and agent frontmatter | + +### `command`-only fields + +| Field | Req | Description | +|-------|:---:|-------------| +| `command` | yes | shell command; with `args`, the executable to spawn directly | +| `args` | no | argument vector -> **exec form**: `command` resolves on `PATH` and spawns directly, NO shell. Each element is one argument verbatim -- no quoting, no `$`/backtick expansion. Use whenever the hook references a path placeholder | +| `async` | no | `true` = fire-and-forget, non-blocking (see hooks-events.md, Async Hooks) | +| `asyncRewake` | no | `true` = background + wakes Claude on exit code 2; implies `async`. The hook's stderr (or stdout when stderr is empty) is shown to Claude as a system reminder -- the only way a background hook reports a late failure | +| `shell` | no | `"bash"` or `"powershell"` for shell form. IGNORED when `args` is set | + +> `async`/`asyncRewake`/`shell`/`args` are `command`-only -- setting them on `http`/`mcp_tool`/`prompt`/`agent` does nothing. + +Exec form (`args` present) -- the safe way to pass a placeholder path: +```json +{"type":"command","command":"node","args":["${CLAUDE_PLUGIN_ROOT}/scripts/format.js","--fix"]} +``` +Shell form (`args` absent) -- needs its own quoting, use only for pipes/`&&`/globs: +```json +{"type":"command","command":"node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"} +``` +Both forms export `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, `CLAUDE_PLUGIN_DATA` into the spawned process. + +> BREAKING (v2.1.207): a shell-form PLUGIN hook whose `command` references `${user_config.*}` now FAILS instead of running. Two fixes: set `args` to switch the handler to exec form (where `${user_config.*}` still substitutes), or read `$CLAUDE_PLUGIN_OPTION_` from the environment. + +HTTP hook example (v2.1.63+): +```json +{"type":"http","url":"http://localhost:8080/hooks/pre-tool-use","timeout":30,"headers":{"Authorization":"Bearer $MY_TOKEN"},"allowedEnvVars":["MY_TOKEN"]} +``` + +## Configuration Locations + +Precedence (HIGHEST to lowest): **Managed/enterprise policy > CLI args > `.claude/settings.local.json` +> `.claude/settings.json` > `~/.claude/settings.json`**. Managed can suppress every other scope +(see settings keys below). Plugin `hooks/hooks.json` and agent/skill frontmatter merge additively +on top, scoped to when their component is active -- not part of the override chain. + +| # | Location | Scope | Notes | +|---|----------|-------|-------| +| 1 | managed/enterprise policy | org | HIGHEST -- MDM/admin, can gate all lower scopes | +| 2 | CLI args | session | -- | +| 3 | `.claude/settings.local.json` | project (gitignored) | -- | +| 4 | `.claude/settings.json` | project (committable) | team-shared | +| 5 | `~/.claude/settings.json` | global | all your projects; not shareable | +| 6 | plugin `hooks/hooks.json` | plugin-scoped | additive (merged, not overridden) | +| 7 | skill frontmatter YAML | rest of the session once invoked | registers even in an untrusted folder under `-p`; `once: true` for single-fire | +| 8 | subagent frontmatter YAML | while that SA runs | requires the workspace-trust dialog for the folder the agent file came from (v2.1.218+); a `-p` session does NOT count as accepting it. `Stop` is auto-converted to `SubagentStop` | + +> There is no `~/.claude/settings.local.json` -- Claude Code never reads that path. + +Merge rule: hooks from different sources are merged, not overridden. For a single event, ALL +registered hooks execute in parallel; the same handler defined in two settings files runs once, +but a plugin's or skill's copy stays separate. All hook events are supported in skill and +subagent frontmatter. + +### Workspace trust (settings-file hooks) + +| Session | Behaviour | +|---------|-----------| +| interactive | every settings file, incl. `~/.claude/settings.json`, is held back until you accept the trust dialog for the folder or a parent | +| `-p` / SDK | never shows the dialog, treats the folder as trusted -- repo-committed `.claude/settings.json` hooks RUN in a folder you never trusted. Mitigate with `--bare` or `--settings '{"disableAllHooks":true}'` | + +### Live / reload / restart + +| Change | Takes effect | +|--------|--------------| +| a skill's `SKILL.md` body | immediately, same session | +| plugin `hooks/`, `.mcp.json`, `agents/`, `output-styles/` | `/reload-plugins` or restart | +| plugin monitors | session restart only | +| settings-file `hooks` blocks | `/clear` or a new session | + +> A plugin that updates mid-session keeps serving hooks from the PREVIOUS version's `${CLAUDE_PLUGIN_ROOT}` until `/reload-plugins`. + +### Managed-only settings keys + +| Key | Effect | +|-----|--------| +| `disableAllHooks` | disables every hook regardless of source | +| `allowManagedHooksOnly` | only managed-policy hooks run; all lower-scope hooks ignored | +| `allowedHttpHookUrls` | allowlist of URLs `http`-type hooks may POST to; fixed fail-closed on an unreadable value (v2.1.267, was fail-open) | + +### settings.json format + +```json +{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command","command":"bash /path/to/hook.sh"}]}],"Stop":[{"hooks":[{"type":"command","command":"node /path/to/hook.mjs"}]}]}} +``` + +### hooks.json format (plugin) + +```json +{"hooks":{"SessionStart":[{"matcher":"startup","hooks":[{"type":"command","command":"node $CLAUDE_PLUGIN_ROOT/hooks/session-start.mjs"}]}]}} +``` + +### Agent/Skill frontmatter YAML + +```yaml +hooks: + PreToolUse: + - matcher: "Bash" + hooks: + - type: command + command: "./scripts/validate.sh" +``` + +### Conditional `if` field (v2.1.85+) + +Reduces hook overhead -- fires only when condition matches (permission rule syntax): +```json +{"hooks":{"PreToolUse":[{"matcher":"Bash","if":"Bash(git *)","hooks":[{"type":"command","command":"bash validate-git.sh"}]}]}} +``` +Format: `ToolName(pattern)` -- same syntax as permission rules. +> BREAKING (v2.1.214): single-segment `dir/**` now matches only `/dir`, not any-depth. Use `**/dir/**` for any-depth matching. diff --git a/brewcode/skills/e2e/SKILL.md b/brewcode/skills/e2e/SKILL.md index 2ffcf50..6833c0c 100644 --- a/brewcode/skills/e2e/SKILL.md +++ b/brewcode/skills/e2e/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[prompt] [status|install|create|update|review|rules]" allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion, Skill, WebSearch, WebFetch] model: opus --- - + diff --git a/brewcode/skills/rules/SKILL.md b/brewcode/skills/rules/SKILL.md index 905386a..3ea6362 100644 --- a/brewcode/skills/rules/SKILL.md +++ b/brewcode/skills/rules/SKILL.md @@ -46,7 +46,7 @@ Labels are literal; values follow the conversation language. | Const | Value | |-------|-------| | ARTIFACT | `rules` | -| SPECIALIST | `bc-rules-organizer` | +| SPECIALIST | `brewcode:bc-rules-organizer` | | LIST_CMD | `bash "${CLAUDE_SKILL_DIR}/scripts/rules.sh" list` | ## Step 1 — Input gate @@ -181,7 +181,7 @@ Note: rules has only an ORGANIZER (bc-rules-organizer), no separate creator — organizer-driven. For `create`/`improve`: AskUserQuestion for the knowledge source — (a) KNOWLEDGE.jsonl path (parse t:"❌"->avoid, t:"✅"->practice), (b) inline prompt ( + text), (c) session learnings (extract 5 most impactful findings as ❌/✅). -Spawn SPECIALIST (bc-rules-organizer) with the Delegation shape — GOAL: the project needs a +Spawn SPECIALIST (brewcode:bc-rules-organizer) with the Delegation shape — GOAL: the project needs a deduplicated, machine-usable rule set in `.claude/rules/`; ROLE: this agent owns ONLY the target rule files, never CLAUDE.md and never global rules; CONTEXT: the knowledge source and its parsed entries are already chosen above (do NOT re-ask), the existing `.claude/rules/*.md` are the @@ -197,7 +197,7 @@ report needs the per-file added/merged/skipped counts; SCOPE + DONE per the temp - DEDUP 3-Check: within-file (>70% skip, 40-70% merge); cross-file antonym (avoid<->best-practice keep avoid only); CLAUDE.md duplicate (skip; "CLAUDE.md" forbidden as Source). -Fallback if agent unavailable: error "bc-rules-organizer not available — install brewcode plugin". +Fallback if agent unavailable: error "brewcode:bc-rules-organizer not available — install brewcode plugin". ### Scope of a specialized rule file (ASK before creating one) diff --git a/brewcode/skills/semble-setup/assets/semble-first.md.template b/brewcode/skills/semble-setup/assets/semble-first.md.template index 2568811..a4542ca 100644 --- a/brewcode/skills/semble-setup/assets/semble-first.md.template +++ b/brewcode/skills/semble-setup/assets/semble-first.md.template @@ -3,8 +3,8 @@ paths: - "**/*" description: semble-first — one semantic search, then read the exact line; rg stays for exact matching doc_type: llm -version: "6.1.4" -content_version: "5.6.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewcode:semble-setup" --- diff --git a/brewcode/skills/semble-setup/assets/semble-prefetch.mjs b/brewcode/skills/semble-setup/assets/semble-prefetch.mjs index 28ca56d..3c90291 100644 --- a/brewcode/skills/semble-setup/assets/semble-prefetch.mjs +++ b/brewcode/skills/semble-setup/assets/semble-prefetch.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewcode:semble-setup +// brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewcode:semble-setup /** * brewcode:semble-setup — UserPromptSubmit hook (self-contained, installed into * a project). It runs alongside the advisory hooks, it does not replace them. diff --git a/brewcode/skills/semble-setup/assets/semble-session.mjs b/brewcode/skills/semble-setup/assets/semble-session.mjs index 52c460a..a0eeecc 100644 --- a/brewcode/skills/semble-setup/assets/semble-session.mjs +++ b/brewcode/skills/semble-setup/assets/semble-session.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewcode:semble-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewcode:semble-setup /** * brewcode:semble-setup — SessionStart hook (self-contained, installed into a project). * diff --git a/brewcode/skills/semble-setup/assets/semble-stats.mjs b/brewcode/skills/semble-setup/assets/semble-stats.mjs index 9658efc..5dba35b 100644 --- a/brewcode/skills/semble-setup/assets/semble-stats.mjs +++ b/brewcode/skills/semble-setup/assets/semble-stats.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewcode:semble-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewcode:semble-setup /** * brewcode:semble-setup — PostToolUse / PostToolUseFailure hook (self-contained, * installed into a project). PURE OBSERVER. diff --git a/brewcode/skills/semble-setup/assets/sembleignore.template b/brewcode/skills/semble-setup/assets/sembleignore.template index c253ea0..fb5f4b3 100644 --- a/brewcode/skills/semble-setup/assets/sembleignore.template +++ b/brewcode/skills/semble-setup/assets/sembleignore.template @@ -1,4 +1,4 @@ -# brewcode-meta: version=6.1.4 content_version=5.6.0 generated_by=brewcode:semble-setup +# brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewcode:semble-setup # brewcode:semble — managed file. Regenerate with # semble-guidance.sh install --part ignore --force # Edit it freely: any change makes it `user_modified`, and the installer then diff --git a/brewcode/skills/setup-status/references/artifact-metadata.md b/brewcode/skills/setup-status/references/artifact-metadata.md index bce0e28..3e28f48 100644 --- a/brewcode/skills/setup-status/references/artifact-metadata.md +++ b/brewcode/skills/setup-status/references/artifact-metadata.md @@ -1,9 +1,9 @@ --- doc_type: llm -version: "6.1.4" +version: "6.2.0" content_version: "5.6.0" generated_by: "brewcode" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- # Artifact metadata and versioning diff --git a/brewcode/skills/skills/references/activation-and-troubleshooting.md b/brewcode/skills/skills/references/activation-and-troubleshooting.md new file mode 100644 index 0000000..5c2ef41 --- /dev/null +++ b/brewcode/skills/skills/references/activation-and-troubleshooting.md @@ -0,0 +1,197 @@ +# Activation, Description Budget, Troubleshooting + +## Description budget (brewcode default) + +| Constraint | Value | +|---|---| +| Total | <=100 tokens (~400 chars) | +| Lead sentence | <=160 chars, plain EN prose | +| Triggers | comma-list, EN only, 3-6 keywords | +| Examples | at most 1, commentary <=15 words | +| Language | EN only in frontmatter (RU/other in README only) | + +Tighter than the CC spec ceiling (hard cap 1024 chars, listing-display cap 1536). Exceed the +brewcode default only if the user explicitly asks. Often-invoked skills: up to ~200 tokens + 1-2 +examples. + +## Activation reality + +Auto-activation is best-effort, never a contract. Upstream publishes no activation rate — rank +methods, never quote a percentage. Known issue ([#10768](https://github.com/anthropics/claude-code/issues/10768), [#15136](https://github.com/anthropics/claude-code/issues/15136), both closed NOT PLANNED). + +| Method | Reliability | +|---|---| +| Basic description | Lowest | +| Optimized description + keywords | Higher | +| `/skill-name` explicit | Highest — the only lever the user controls directly | + +`/name` is the strongest lever, not an absolute guarantee. It does not run when: `user-invocable: +false` (hidden from `/`, not run when typed, `skills:332`); a `skillOverrides` entry is `"off"` +(invoking by full name returns the override error, `skills:772`; plugin skills exempt, `skills:785`); +a higher-precedence same-name SK shadows it (enterprise > personal > project, `skills:124`, any +level overrides a bundled SK, `skills:126`); the file is `skill.md` lowercase, so nothing is +discovered. Malformed frontmatter does NOT break `/name` — the body loads with empty metadata and +`/skill-name` still works, only description-matching dies (`skills:1028`). + +Context reattachment after compaction: 5K tokens/skill, 25K combined budget, not an unbounded-loss +bug. If a skill still gets evicted under load, re-invoke `/name`. + +### Criticality strategy + +| Criticality | Config | +|---|---| +| CRIT (deploy, commit, send-email) | `disable-model-invocation: true` + use `/name` | +| Important (review, test, docs) | Optimized description + keywords | +| Nice-to-have (helpers, utils) | Basic description | +| Background knowledge | `user-invocable: false` | + +Rule: failure unacceptable -> `disable-model-invocation: true` + slash command. + +## Description optimization + +Claude uses `description` to decide when to invoke. Description quality is the only lever on +auto-load — upstream publishes no rate, compare variants against your own eval set. + +| Invocation | Style | +|---|---| +| User-only (`disable-model-invocation: true`) | Simple one-liner, no triggers needed — the LLM never auto-invokes it | +| LLM-invocable | Action verb + `Triggers:` line, third-person — best odds of auto-load | + +Template: `description: "[Action verb sentence]. Triggers: [exact user phrases]."` + +```yaml +# BAD -- first-person, no triggers, multiline +description: | + I can help you create presentations with company colors. + Use this skill when creating slides. + +# GOOD -- third person, single line, action verb + Triggers +description: "Creates presentations with company branding and animations. Triggers: create presentation, make slides, build deck." +``` + +Rules: action verb, not "Use this skill when"; ONE line, no `|` multiline; front-load keywords; +`Triggers:` with exact user phrases; "proactively" has no effect; cap per the field reference +(brewcode default <=400 chars). Listing budget is a dynamic **1% of the context window** +(`skillListingBudgetFraction`, default `0.01`), not a fixed 2%/16K — exceeding it means some skills +never appear in the listing. + +### Trigger eval queries (optional but recommended) + +Only meaningful for a `disable-model-invocation: false` SK. Generate 5 queries that SHOULD trigger +and 5 tricky near-misses that should NOT (share keywords, need a different tool), run them, iterate +2-3 times on misses, report the hit rate. A SA can't prompt the user (`execution-model.md`) — +report, do not poll. + +Which questions apply, by `DMI`: + +| SK | Trigger question | Output question | How to run | +|---|---|---|---| +| `DMI: true` (every shipped brewcode SK) | Skip — the model never auto-invokes it (`skills:331`), and it is not preloaded into SAs either | Measure | Fresh `claude -p` session invoking `/name` explicitly. Never spawn a SA "with the SK": a `DMI: true` SK silently no-ops from a SA, so a SA-based run measures nothing | +| `DMI: false` | Measure — did the prompt alone load it? | Measure | Fresh session per prompt; disable via `skillOverrides: "off"` for the baseline half (`skills:759`) | + +Wasted steps? All runs writing similar helper scripts -> bundle into `scripts/`. Heavyweight version +of this loop (evals.json, per-case isolation, grading, A/B): `skill-creator@claude-plugins-official` +(`skills:793-812`). + +## Activation mistakes (kill auto-load) + +| Mistake | Fix | +|---|---| +| Summary without triggers | Include BOTH the action-verb sentence AND a `Triggers:` line | +| No `Triggers:` line | Add `Triggers: deploy, release, ship to prod` | +| Starts with "Use this skill when" | Start with an action verb: "Deploys..." | +| Vague description | Specific: "Deploy to k8s" not "Helps with deployment" | +| First-person description | Third-person: "Deploys..." not "I deploy..." | +| Second-person body | Imperative: "Do X" not "You should do X" | +| CRIT without slash | `disable-model-invocation: true` for CRIT ops | +| Too many skills | Beyond the dynamic listing budget -> some invisible | +| PLG skills: DMI ignored | PLG skills always in context ([#22345](https://github.com/anthropics/claude-code/issues/22345), unconfirmed against 2.1.233) — copy to `.claude/skills/` if parity needed | + +## Troubleshooting: SK not auto-activating + +| Symptom | Cause | Fix | +|---|---|---| +| Never activates | Beyond listing budget | Run `/skill-doctor` (added 2.1.261 — shows unused loaded skills and their context cost) or check `/skills`; trim skill count or description length | +| Never activates | Description reads as a summary | Rewrite with triggers only | +| Sometimes activates | Weak keywords | Add explicit "Trigger keywords:" | +| Was working, stopped | Context compaction | Reattaches under the 5K/skill, 25K combined budget; re-invoke `/name` if evicted | +| Claude ignores the instruction | Attention competition | Fewer skills, explicit `/name` | + +Debug steps: ask "What skills do you have?" — not listed means budget exceeded. Check visible +thinking for the SK name — absent means the description isn't matching. Test explicit +`/skill-name` — works means an activation issue, fails means the SK is broken. Force test: "Use +skill-name skill to do X" — naming the SK is the strongest hint short of `/name`. + +## Validation tools + +Beyond `validate-skill.sh` (this workspace's own gate): `/skill-doctor` (v2.1.261) reports unused +loaded skills and their context cost, for pruning. `claude plugin eval` (v2.1.269) runs a scored +plugin eval suite with a JSON+HTML report — a new option alongside `validate-skill.sh`, not a +replacement for it. + +## Known bugs + +| # | Bug | Impact | Status | Workaround | +|---|---|---|---|---| +| [#39686](https://github.com/anthropics/claude-code/issues/39686) | claude.ai skills silently injected (~6000 tokens) | 37% of SK budget consumed, no opt-out | Open | No workaround | +| [#22345](https://github.com/anthropics/claude-code/issues/22345) | PLG skills ignore DMI | PLG skills always in context (~4400 tokens) | Open, unconfirmed against 2.1.233 | No workaround | +| [#17688](https://github.com/anthropics/claude-code/issues/17688) | SK-scoped hooks don't fire in PLGs | Hooks from SKILL.md frontmatter not working for PLG skills | Open | Use PLG `hooks.json` | +| [#35641](https://github.com/anthropics/claude-code/issues/35641) | `/reload-plugins` doesn't load skills from new PLGs | Skills emitter not called on reload | Open | `/reload-skills` (v2.1.152) re-scans without restart. 2.1.246 fixed a same-symptom "0 skills reported" case — not confirmed identical, re-test before removing this row | +| [#33080](https://github.com/anthropics/claude-code/issues/33080) | Same-name skill resolution surprises users | A non-bundled skill overrides a same-name bundled skill, no notice | Open | Namespace prefix (e.g. `my-`) if collision unwanted | +| [#17417](https://github.com/anthropics/claude-code/issues/17417) | `skill.md` lowercase silently ignored | SK not discovered | Open | Use `SKILL.md` uppercase | +| [#36031](https://github.com/anthropics/claude-code/issues/36031) | User-level skills listed in Desktop autocomplete but not invoked | SKILL.md not loaded in Desktop app | Open, unconfirmed against 2.1.233 | Use CLI | +| [#10768](https://github.com/anthropics/claude-code/issues/10768) / [#15136](https://github.com/anthropics/claude-code/issues/15136) | Auto-activation unreliable | SK not invoked on relevant request | Closed NOT PLANNED | Optimize description, then `/name` | + +## Behavior changes worth knowing (2.1.234-2.1.269) + +- 2.1.239: BOM'd `.md` files (agents/skills/commands) were silently ignored -> fixed; still author + clean UTF-8 without a BOM. +- 2.1.239: the post-compaction reminder no longer replays a skill's original arguments as a new + request — context loss itself is not fixed. +- 2.1.246: `/reload-plugins` loads new-plugin skills; `/cd` loads the new directory's project + skills immediately, no `--resume` needed. +- 2.1.257: a plugin could read files outside its own directory via a symlinked + command/agent/skill/hooks path — now refused with an error. +- 2.1.260: a managed `skillOverrides` keyed on a bundled skill's alias didn't apply, and a + `Skill(name)` deny rule didn't cover a nested `:name` skill — both fixed. +- 2.1.269: skills synced from claude.ai in cloud sessions are renamed `anthropic-skills:` + (bare name still works if unclaimed) — partial mitigation for local/cloud name collisions. + +## Version history (earlier fixes, no inline home) + +| Version | Change | +|---|---| +| v2.1.76 | `/effort` slash command | +| v2.1.74 | Fix: `ask` rules bypassed via `allowed-tools` | +| v2.1.73 | Fix: deadlock on mass SK file changes | +| v2.1.72 | Fix: built-in slash cmds hidden; SK hooks dropped | +| v2.1.69 | Security: nested discovery skips gitignored dirs | +| v2.1.47 | Fix: crash on numeric `name`/`description`; `argument-hint` YAML sequence | +| v2.1.45 | PLG skills available immediately after install (no restart) | + +## Validation checklist (Step 6, alongside `validate-skill.sh`) + +Structure: valid YAML frontmatter; `name` <=64 chars lowercase-hyphens == dir name, no `plg:` +prefix; `description` per the field reference caps, third-person, what+when+3-5 triggers, no +filler; every FM key in the supported set or the house custom list (no invented key); +`argument-hint` prompt-first; Prompt Contract satisfied (`## Prompt contract` section, PLAN block +with all 5 labels, 2+ modes -> keyword table with `Mutates?` + RU); body <500 lines, imperative +form; `context: fork` if standalone; `agent` an appropriate type; `model` matched to complexity; +`allowed-tools` pre-approval only, no bare `Bash`/`Write`/`Edit`/`Agent`; `disallowed-tools` present +when the SK must never call a tool (autonomous -> `AskUserQuestion`); examples actually work; no +hardcoded secrets; Bash blocks carry the `EXECUTE` keyword + `&& OK || FAIL` + dynamic (CSD/BPR) +paths. + +Activation (CRIT): description starts with an action verb and includes a `Triggers:` line; triggers +present and concrete ("Triggers: deploy, release, ship to prod"); single line, no multiline `|`, +within the field-reference caps; third-person ("Deploys..." not "I deploy..."); CRIT operations use +`disable-model-invocation: true`. + +Test it: say the trigger phrase (should auto-load); say "Use [skill-name] skill to..." (higher +activation odds); say `/skill-name` (works unless an Activation Reality caveat applies). Trigger +test fails but `/name` works -> optimize the description or switch to `DMI: true`. + +## Sources + +[CC Skills](https://code.claude.com/docs/en/skills) | [Custom Subagents](https://code.claude.com/docs/en/sub-agents) | [Skill Best Practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) | [agentskills.io](https://agentskills.io) +- [GitHub #12541](https://github.com/anthropics/claude-code/issues/12541) — feature request that led to CSD diff --git a/brewcode/skills/skills/references/design-patterns.md b/brewcode/skills/skills/references/design-patterns.md new file mode 100644 index 0000000..7425392 --- /dev/null +++ b/brewcode/skills/skills/references/design-patterns.md @@ -0,0 +1,160 @@ +# SK Design Patterns and Structure + +## Anatomy + +``` +skill-name/ +├── SKILL.md # REQ: frontmatter + instructions +├── references/ # OPT: detailed docs (load on demand) +├── examples/ # OPT: working code examples +├── scripts/ # OPT: executable utilities +├── assets/ # OPT: templates, images +└── agents/ # OPT: SA prompts (convention, NOT auto-discovered) +``` + +> A root-level `SKILL.md` with no `skills/` subdir also surfaces as a valid PLG SK (v2.1.142+). + +## Pattern catalog + +| Pattern | When | Effect | +|---|---|---| +| **Progressive Disclosure** | Always | 3 levels: L1 name+description (~100 words, always in context), L2 SKILL.md (<500 lines, on trigger), L3 refs/scripts/agents (on demand, unlimited) | +| **REF Splitting** | Multi-mode: 2+ modes, >50 lines/mode, >300 lines total | Detect mode -> Read `references/{mode}.md`. Guard: "not found -> ERROR + STOP" | +| **Agents-as-REFs** | SK-coordinator + multi-step workflow + multiple roles | SA prompts as `.md` in `agents/` inside the SK dir. Coordinator passes the file path; SA reads itself. **0 tokens** in coordinator context. `agents/` is convention, NOT native | +| **Dynamic CTX** | Need live data before launch (git diff, PR info, env) | `` !`command` `` executes BEFORE content reaches Claude | +| **FORK** | Standalone task, no conversation history, <4 phases | `context: fork` -> isolated SA, background by DEF since v2.1.218. SKILL.md body = task prompt. CLAUDE.md loaded, history not. >5 phases -> memory loss | +| **Executable Bash** | Bash blocks must execute | `**EXECUTE**` keyword + `&& echo OK \|\| echo FAIL` + `> STOP if FAIL`. Without the keyword, bash is examples only | +| **SK Chaining** | SK invokes another SK | `Skill(skill="name", args="...")`. Needs no `allowed-tools` entry; a SA keeps `Skill` too (`sa:349`). brewcode preference: chain from main — a `DMI: true` SK invoked from a SA silently no-ops | +| **Background Knowledge** | Claude needs context, user needs no slash cmd | `user-invocable: false`. Description stays in context | +| **Pushy Description** | LLM-invocable skills | Action verb + `Triggers: "phrase1", "phrase2"`. Best odds of auto-load; no published rate | +| **Preloaded Skills** | SA must follow conventions/patterns | `skills: [name]` in agent frontmatter. Full SK injected at startup | + +## Agents-as-REFs detail + +Pattern from the official Anthropic skill-creator plugin. `agents/` inside an SK dir is NOT +auto-discovered. The coordinator passes a **file path**, not content; the SA reads the `.md` itself. + +| Native agents `.claude/agents/` | "Agents" in SK `agents/` | +|---|---| +| Auto-discovered, visible in `/agents` | Reached via Read by path only | +| Own model, tools, hooks, memory | Inherits from the spawning SA | +| YAML frontmatter + Markdown | Plain Markdown (prompt) | +| Public API | SK implementation detail | + +Use when: SK-coordinator + 2+ roles + context isolation needed + prompts are implementation detail. + +## REF splitting strategy + +| Location | Content | +|---|---| +| SKILL.md | Overview, instructions, examples, resource refs | +| references/ | Patterns, API docs, policies | +| scripts/ | Python, JS, Bash (pre-installed packages only) | +| assets/ | Templates, images (not loaded into context) | + +When to split — ALL of these true: 2+ modes with different knowledge; >50 lines per mode; >300 +lines of reference content combined; <30% of that content is shared across modes. + +Loading patterns: conditional/lazy for multi-mode (detect mode -> Read `references/{mode}.md`, +e.g. `superreview-setup`); unconditional single Read for one reference <200 lines (e.g. +`brewtools:text-optimize` always reads `references/rules-review.md`). + +3-step pattern: DETECT mode from `$ARGUMENTS`/project analysis -> READ the matching +`${CLAUDE_SKILL_DIR}/references/{mode}.md` -> VALIDATE: not found -> ERROR "Missing REF for +{mode}", STOP. + +Anti-patterns: loading ALL refs regardless of mode (fix: detect then load only the match); +inlining mode-specific content >50 lines in SKILL.md (fix: split to `references/{mode}.md`); +Read with no not-found guard (fix: add the STOP guard); generic ref names like `ref1.md` (fix: +name by mode, `references/jvm.md`). + +## Resource path resolution — one rule, three cases + +| Case | Form | Why | +|---|---|---| +| Prose pointer to a bundled doc ("see X for details") | Markdown link, relative: `[reference.md](reference.md)` | Upstream's own shape (`skills:451-457`) | +| Anything EXECUTED or Read at runtime — scripts, templates, refs the SK opens | `${CLAUDE_SKILL_DIR}` (CSD) | CWD is the session shell's and moves with `cd` (`skills:643`); CSD resolves identically every time and is substituted in `allowed-tools` Bash rules too (`skills:403`) | +| Resource in the plugin but OUTSIDE this SK's dir, or a path handed to an agent | `${CLAUDE_PLUGIN_ROOT}` (BPR) | CSD is the SK subdir, not the plugin root (`skills:398`); an agent gets no CSD at all | + +Never a hardcoded absolute path — it breaks on every other install. + +```yaml +# Executed -> CSD +bash "${CLAUDE_SKILL_DIR}/scripts/validate.sh" $ARGUMENTS +# Read at runtime -> CSD +Read `${CLAUDE_SKILL_DIR}/references/api-spec.md` before generating the client. +# Prose pointer -> markdown link +For complete API details, see [references/api-spec.md](references/api-spec.md). +``` + +Path handed to an agent (no CSD there): `Agent(subagent_type="general-purpose", prompt="Read +${CLAUDE_PLUGIN_ROOT}/skills/my-skill/references/rules.md then...")`. + +## Executable Bash + +Bash blocks are examples unless marked. Template: `**EXECUTE** using Bash tool:` label, a fenced +bash block ending `&& echo "OK" || echo "FAIL"`, then `> **STOP if FAIL**` with recovery steps. + +| Rule | Bad | Good | +|---|---|---| +| Label | ` ```bash` | `**EXECUTE**:` ` ```bash` | +| Validate | `command` | `command && echo "OK" \|\| echo "FAIL"` | +| Paths | `${CLAUDE_PLUGIN_ROOT}/skills/x/scripts/y.sh`, or a bare relative `scripts/y.sh` | `${CLAUDE_SKILL_DIR}/scripts/y.sh` — it's executed, so CSD | + +## Location priority + +| Scope | Path | Git | +|---|---|---| +| Enterprise | Managed settings | N/A | +| Personal | `~/.claude/skills/` | No | +| Project | `.claude/skills/` | Yes | +| Plugin | `/skills/` | Yes | + +Priority: Enterprise > Personal > Project. Plugin skills invoke as `/plugin-name:skill-name`. Hide +bundled skills via `disableBundledSkills` setting or `CLAUDE_CODE_DISABLE_BUNDLED_SKILLS` env +(v2.1.169+). + +> **Output path (v3.4.70):** SK outputs -> `.claude//` (project-relative). Never Write to +> `~/.claude/*` (protected-path blocks ALL modes). Exceptions: `commands|agents|skills|worktrees`. + +## Unit test skeleton (Step 8) + +For each script in `scripts/`, generate `tests/test-{script-name}.sh` from this skeleton +(replace `SKILL_DIR` with the actual skill dir path) covering: script exists, script executable, +runs without error (`--help`), plus script-specific assertions. + +```bash +#!/bin/bash +pass=0; fail=0 +check() { + local name="$1"; shift + if "$@" >/dev/null 2>&1; then + echo "PASS: $name"; pass=$((pass+1)) + else + echo "FAIL: $name"; fail=$((fail+1)) + fi +} +check "script exists" test -f "${SKILL_DIR}/scripts/foo.sh" +check "script executable" test -x "${SKILL_DIR}/scripts/foo.sh" +check "runs --help" "${SKILL_DIR}/scripts/foo.sh" --help +echo "pass=$pass fail=$fail"; [ "$fail" -eq 0 ] +``` + +Run all tests, fix failures, max 2 cycles: + +```bash +for t in "${SKILL_DIR}/tests"/test-*.sh; do + bash "$t" && echo "OK $(basename "$t")" || echo "FAIL $(basename "$t")" +done +``` + +STOP after 2 fix cycles -- document failures, proceed to the next step. + +## Common creation mistakes + +| Mistake | Fix | +|---|---| +| Missing `context: fork` for a standalone task | Add `context: fork` | +| Hardcoded secrets/tokens in scripts or body | Use MCP / environment injection, never a literal | +| Multipurpose skill trying to cover unrelated jobs | Split into focused, single-purpose skills | +| All refs loaded unconditionally in a multi-mode skill | Detect mode -> load only the matching `references/{mode}.md` | diff --git a/brewcode/skills/skills/references/execution-model.md b/brewcode/skills/skills/references/execution-model.md new file mode 100644 index 0000000..63455c6 --- /dev/null +++ b/brewcode/skills/skills/references/execution-model.md @@ -0,0 +1,162 @@ +# Execution Model — Context Modes, Spawning, Tools + +## Context modes + +**Inline (default):** omit `context`. Runs in the main conversation with full history. Description +loaded at start, full body on invoke. Best for reference material, guidelines, background +knowledge. + +**FORK (`context: fork`):** isolated SA, fresh context, no conversation access. Background by +default since v2.1.218 (`background: false` waits for the result in the invoking turn). SKILL.md +body = task prompt. CLAUDE.md loaded, EXCEPT with `agent: Explore` or `agent: Plan` (`skills:692`). +A fork with guidelines but no actionable task returns nothing useful (`skills:685`). + +Fork/background caveats — decide `background` on these, not on phase count: + +| Caveat | Consequence | +|---|---| +| Background forks get the **narrower background tool set** (`skills:680`, pool at `sa:349`) — the fork exemption does not widen it | A step needing a tool outside that pool silently has no tool -> set `background: false` | +| A backgrounded fork's edits land **outside session checkpoints** — `/rewind` does not undo them, only git does (`skills:682`) | Fork that writes -> `background: false`, or state that git is the only undo | +| CC waits anyway, whatever `background` says, in 4 cases (`skills:673-678`): `-p`/Agent SDK; `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`; a second invocation while the first still runs; a scheduled task firing the SK | Never design a SK around "it returns immediately" | + +```yaml +--- +name: deep-research +description: Research a topic thoroughly +context: fork +agent: Explore +--- +Research $ARGUMENTS: +1. Find and read relevant files using Glob/Grep +2. Summarize with file references +``` + +Memory behavior: inline keeps full conversation access at any length; `fork` works well for 1-4 +phases and loses task structure/skips phases at 5+ — context fades over extended execution, use +inline + hooks/external state (TASK.md, a progress log) for longer orchestration. + +Decision matrix: needs conversation history -> inline. Standalone quick task (<4 phases) -> +`context: fork`. Multi-phase orchestration (4+ phases) -> inline + hooks/external state. Simple +research/analysis -> `context: fork` + `agent: Explore`. Fork needs a tool outside the background +pool -> `background: false`. Fork writes files and `/rewind` must work -> `background: false`. + +## SA spawning constraints + +A SA CAN spawn SAs and CAN invoke skills. Default depth is **3** layers below the main conversation +(`sa:901`; env `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` overrides, `1` turns nesting off, `sa:905`). +Only AT the depth limit is `Agent` withheld — a fork keeps it listed but it errors instead of +spawning (`sa:901`). No per-session cap on total SAs (`sa:930`) — the 200-spawn cap added in 2.1.212 +was removed in 2.1.224. + +Two filters narrow a SA's pool (`sa:337`); a fork skips both. **`AskUserQuestion` is removed from +EVERY SA, even when listed in `tools:`** (`sa:340`) — a SK must never instruct a spawned SA to ask +the user anything; put open questions in its return instead. A background SA additionally keeps +only the reduced built-in set, which still includes `Skill` and `Agent` (`sa:349`). + +brewcode workflow prefers spawns from the main conversation — a house preference, not a platform +limit: nested spawns bypass session binding + hook context injection. + +| Scenario | brewcode workflow | Why | +|---|---|---| +| SK with FORK from **main conversation** | Use this | Lock binding + hook context injection intact | +| SK with FORK from **SA** | Avoid | Bypasses session binding + coordinator loop | +| `Agent` tool from **SA** | Avoid | Nested spawn bypasses session binding + hook context injection | +| `Skill` tool from **SA** | Never | Bypasses hook context injection, and `DMI: true` SKs (every distributed brewcode/brewtools/brewdoc SK) silently no-op — use the SK's twin agent instead | +| Inline SK (no `context`) from SA | Avoid | Same binding/injection bypass | + +## Agent field + +With `context: fork`, `agent` selects the SA type. + +| Agent | Model | Tools | Use for | +|---|---|---|---| +| `Explore` | Haiku | Read-only | Read-only analysis, file discovery — fast, safe | +| `Plan` | Inherit | Read-only | Planning, structured research | +| `general-purpose` | Inherit | All | Multi-step tasks (default), code changes | + +> Only these three are built in. `developer`/`tester`/`reviewer` do NOT exist — a generated SK +> naming one fails to resolve its SA on first run. Custom agents: `.claude/agents/` / +> `~/.claude/agents/` via `agent: my-custom-agent`. + +## Model selection + +| Model | Use case | +|---|---| +| `fable` | Mythos-class tier above Opus (alias -> canonical `claude-fable-5`, v2.1.170). Hardest reasoning/orchestration | +| `opus` | Complex orchestration, multi-phase — setup, create, review | +| `sonnet` | Medium complexity, optimization — rules, convention | +| `haiku` | Simple, fast, cleanup — teardown, clean-cache | +| `inherit` | Runs on whatever model the session is already using (this agent's own setting) | + +## Tool pre-approval vs restriction + +`allowed-tools` is a PERMISSION GRANT, not an allowlist. Upstream: it "does not restrict which +tools are available: every tool remains callable", the listed ones just run "without prompting" — +for the invoking turn only (`skills:513`). Applies even in an untrusted `-p` run in an untrusted +folder — "a skill can grant itself broad tool access" (`skills:515`). + +| Goal | Mechanism | +|---|---| +| Skip the prompt for the exact commands the SK runs | `allowed-tools`, scoped as narrowly as possible: `Bash(git status:*)`, `Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *)` — CSD/`${CLAUDE_PROJECT_DIR}`/BPR/`${CLAUDE_PLUGIN_DATA}` are substituted inside `allowed-tools` Bash rules too (`skills:403,409`) | +| Stop the SK from calling a tool at all | `disallowed-tools` — the only key that removes anything (`skills:334,528`) | +| Restrict for the whole session, or across all SKs | permission settings: allow rules for a session-wide grant, deny rules to block (`skills:513,528`) | + +Rules: never a bare `Bash`/`Write`/`Edit`/`Agent` in `allowed-tools` — it pre-approves every +invocation, the opposite of narrowing; write the narrowest Bash pattern or omit the key. +`allowed-tools` is never needed to make a tool callable — `Skill`/`Agent`/`Read` work with or +without it, listing only removes the prompt. Autonomous SK that must never stall on input -> +`disallowed-tools: AskUserQuestion`. An injected `` !`cmd` `` whose permission check is anything +but allow ABORTS the invocation — pre-approve that exact command with `allowed-tools`. + +## Dynamic context injection + +`` !`command` `` executes before content reaches Claude, e.g. `` - Diff: !`gh pr diff` `` inside a +FORK body. Multi-line -> a fenced block opened with ` ```! `. + +| Rule | Detail | +|---|---| +| Failure ABORTS the whole invocation | Not just the placeholder — Claude never sees the SK content (`skills:652`) | +| Non-zero = failure | Carveout: exit 1 from search/comparison commands is normal, output still injected; exit >=2 fails even for those (`skills:654`) | +| Remedy | Append `\|\| true` to a command expected to exit non-zero (`skills:661`) | +| Permission | Injected commands never prompt; any non-allow check result ABORTS — pre-approve with `allowed-tools` (`skills:663-665`) | +| CWD | The session shell's, moves with `cd`. Use CSD/`${CLAUDE_PROJECT_DIR}` for anything that must resolve identically (`skills:643`) | +| Timeout | Bash tool default 2 min; a kill at timeout aborts the invocation (`skills:645`) | +| Inline form | `` ! `` recognized only at line start or after whitespace — `` KEY=!`cmd` `` stays literal (`skills:612`) | +| Single pass | Substitution runs ONCE; injected output is not re-scanned (`skills:610`) | + +## String substitutions + +Complete set (`skills:392-401`); nothing else is substituted. + +| Variable | Description | Since | +|---|---|---| +| `$ARGUMENTS` | All args passed on invoke. Absent from the body -> appended as `ARGUMENTS: ` | -- | +| `$ARGUMENTS[N]`, `$0`/`$1`/`$2` | Arg by 0-based index | -- | +| `$name` | Named arg declared via `arguments` frontmatter key | -- | +| `${CLAUDE_SESSION_ID}` | Current session ID | -- | +| `${CLAUDE_EFFORT}` | Active effort: `low\|medium\|high\|xhigh\|max` | -- | +| CSD (`${CLAUDE_SKILL_DIR}`) | Dir containing SKILL.md; plugin SK -> the SK subdir, not the plugin root | v2.1.69 | +| `${CLAUDE_PROJECT_DIR}` | Project root — same path hooks/MCP get | v2.1.196 | +| BPR (`${CLAUDE_PLUGIN_ROOT}`) | Plugin install dir, plugin skills only | -- | +| `${CLAUDE_PLUGIN_DATA}` | Plugin persistent data dir, survives updates, plugin skills only | -- | + +Unfilled `$2` with only one arg stays literal; an unfilled `$name` expands to empty. Escape a +literal `$` before a digit/`ARGUMENTS`/a declared name with one backslash (`\$1.00`) — never blocks +a `${CLAUDE_*}` var. CSD is a string substitution, NOT an env var — not available in hooks/agents +(use `${CLAUDE_PLUGIN_ROOT}` there). `$ARGUMENTS` inside a ` ```bash ``` ` block is a shell +variable (empty/undefined), not a CC substitution — put it in text, use a placeholder in the block. + +## Skill and Task tools + +`Skill(skill="skill-name", args="...")` / `Skill(skill="plugin:skill", args="...")` — native tool +implementing the agentskills.io standard, compatible with CC/Codex/ChatGPT. Needs no +`allowed-tools` entry to be callable; survives both SA tool filters (`sa:349`). + +`Agent`/`Task` delegates to SAs (renamed `Agent` in v2.1.49-74; `Task(...)` still resolves as an +alias). Params: `description` (3-5 words, REQ), `prompt` (REQ), `subagent_type` (REQ, not `agent` +— that param does not exist), `model` (opus/sonnet/haiku), `run_in_background`, `resume` (agent ID). +Launch multiple calls in one message for parallel execution rather than serially. + +Listing `Agent` in a SA's `tools:` genuinely lets it spawn; only a type list inside the parentheses +is ignored (`sa:413`). To keep a generated SA read-only, omit `Agent` from its `tools:` or add it +to `disallowedTools` (`sa:917`) — do NOT assume nesting is off by default. diff --git a/brewcode/skills/skills/references/frontmatter-fields.md b/brewcode/skills/skills/references/frontmatter-fields.md new file mode 100644 index 0000000..7960eb0 --- /dev/null +++ b/brewcode/skills/skills/references/frontmatter-fields.md @@ -0,0 +1,124 @@ +# Frontmatter Field Reference + +Complete supported SKILL.md frontmatter set, 20 fields (`skills:326-345` @ CC 2.1.233 baseline, +cross-checked against docs fetched 2026-09-12 for 2.1.269). Anything else -> the supported +`metadata:` map, which CC accepts but ignores (`skills:343`). An invented key (`cli:` outside the +house convention below, `updated:`) is not a feature — CC ignores it and claude.ai upload / +Skills API / `package_skill.py` hard-fail on it (`skills:354`, error text `skills:358`; allowed set +there is only `name, description, license, compatibility, metadata, allowed-tools`). + +## Core + +| Field | Type | Default | Meaning | +|---|---|---|---| +| `name` | string | dir name | Slash-command id. <=64 chars, lowercase/numbers/hyphens, **BARE, == dir name (brewcode house rule)**. A `:` prefix is a defect: PLG skills get the plugin name prepended by CC itself, so a baked `brewcode:e2e` renders `/brewcode:brewcode:e2e` (`skills:377,380`). 2.1.246 made a stray prefix render correctly instead of doubling — cosmetically safer, but the house rule is unchanged; do not relax `validate-skill.sh:70` | +| `description` | string | -- | What + when + 3-5 distinct triggers, no filler. Spec hard cap **1024** chars; **1536**-char listing-display cap is `description` + `when_to_use` COMBINED (raised ~v2.1.107-108); brewcode default target <=400 chars. ALWAYS quote it — an unquoted `--`/`:`/special char breaks YAML parsing silently, SK stays on disk but skills.sh fails to parse it | + +> **Command name != `name` at every level.** Personal/project SK: the command comes from the DIR +> name, `name` is only a display label (`skills:374`, `skills:326`). PLG SK: `name` sets only the +> last segment, namespaced by the plugin (`skills:377`). Upstream permits `name` != dir; brewcode +> does NOT — all 28 shipped SKs keep `name` == dir. Follow the house rule, never relax the validator. + +## Invocation Control + +| Field | Type | Default | Meaning | +|---|---|---|---| +| `when_to_use` | string | none | Extra activation guidance appended to `description`; counts toward the same 1536-char listing-display cap. Docs-confirmed, not in the 2.1.234-2.1.269 changelog slice | +| `disable-model-invocation` (DMI) | bool | false | `true` = user-only via `/name`. Also blocks preload into SAs (`skills:331`) and, per docs, scheduled-task firing — a strict superset of "LLM never auto-invokes it" | +| `user-invocable` (UI-F) | bool | true | `false` = hidden from `/` menu, Claude-only background knowledge | +| `argument-hint` | string | none | Autocomplete hint. House rule: prompt-first, `[prompt] [mode...]` — see `prompt-contract.md` | + +Config matrix: `(default)` = user+Claude invocable, DESC counted in listing budget. `DMI: true` = +user only, Claude never (0 budget). `UI-F: false` = Claude only, DESC still counted. Both true = SK +inaccessible — never combine. + +## Execution Control + +| Field | Type | Default | Meaning | +|---|---|---|---| +| `allowed-tools` (AT) | string \| list | none | **Pre-approval, NOT a sandbox.** Grants tools without a permission prompt for the invoking TURN only; restricts nothing — every tool stays callable (`skills:333,513`). Now also accepts a YAML list, not only a comma string. Never a bare `Bash`/`Write`/`Edit`/`Agent` — narrowest pattern (`Bash(git status:*)`) or omit the key | +| `disallowed-tools` (DT) | string \| list | none | The ONLY key that removes anything: drops tools from the pool while the SK is active (`skills:334,528`). Cannot fully remove `EndConversation` while any other tool remains | +| `model` | enum | session model | `opus`, `sonnet`, `haiku`, `fable` (alias -> canonical `claude-fable-5`, Mythos tier above Opus, v2.1.170), or `inherit` (this agent's own frontmatter value — runs on whatever model the session is using). Fixed in **2.1.259**: was ignored in interactive sessions, and auto-mode running an unsupported `model:` now falls back to the session model instead of erroring | +| `effort` | enum | inherit | `low, medium, high, xhigh, max` — no `auto`. Since v2.1.80. Fixed in **2.1.267**: was ignored on models with a pinned default effort (Opus 4.7, Opus 4.8, Fable 5) | +| `context` | enum(`fork`) | inline | `fork` = isolated SA. Kickoff-prompt streaming (and, with `--forward-subagent-text`, turn text) fixed in **2.1.265** | +| `background` | bool | true | Fork-only. `false` = wait for the result in the invoking turn instead of backgrounding (default true since v2.1.218) | +| `agent` | string | general-purpose | With `context: fork`. Only `Explore`, `Plan`, `general-purpose` are confirmed built-in — see `design-patterns.md`. Custom: `.claude/agents/` / `~/.claude/agents/` via `agent: my-custom-agent` | +| `hooks` | object | none | Hooks scoped to the SK's lifecycle, `if:` glob condition since v2.1.85. See Hooks below | + +## Docs-confirmed, not in the 2.1.234-2.1.269 changelog slice + +| Field | Type | Default | Meaning | +|---|---|---|---| +| `arguments` | string \| list | none | Declares named args -> enables `$name` substitution in the body, in addition to `$0`/`$1`/`$ARGUMENTS`. **Available upstream, NOT required by house convention** — the house prompt-contract keeps one free-form `[prompt]` string (`argument-hint` position 1); use `arguments:` only where a genuinely named, structured arg earns its own slot | +| `paths` | string \| list | none | Glob(s) scoping where the skill is offered | +| `shell` | enum | bash | Shell used to run `` !`command` `` dynamic-CTX blocks (bash/powershell) | +| `metadata` | map | none | Free-form key/value block for registries/third-party tooling; CC itself ignores it | +| `license` | string | none | SPDX identifier — Agent Skills spec field, CC ignores | +| `compatibility` | string | none | Environment requirements, <=500 chars — Agent Skills spec field, CC ignores | + +## House custom keys (brewcode convention, not native CC) + +These pass the validator's allow-list because the house documents them; they are NOT part of the +20 native fields above and CC does not interpret them. + +| Field | Rule | +|---|---| +| `cli` | string \| list, each token `/^[\w.-]{1,42}$/`. Names the command(s) the skill owns when it isn't spelled like the dir name. Denylist (never claim): `sh bash zsh ls cat stat mv rm cp mkdir df du curl wget python python3 node npm git echo grep sed awk find head tail chmod chown`. Never inferred from `allowed-tools` | +| `version` | Free-form, not semver, no ordering. MANDATORY when the skill's behaviour lives outside its own directory (binary on PATH, wrapper in an image, remote service) — its only contract is "changing the value changes the skill directory's content hash" | +| `content_version`, `generated_by`, `last_updated`, `doc_type`, `surface_files` | Release-tooling stamps written by `bump-version.sh` / doc pipeline — do not hand-edit, do not invent new ones | + +## Hooks field detail + +```yaml +hooks: + PreToolUse: + - matcher: "Bash" + hooks: + - type: command + command: "./scripts/validate.sh" + once: true # suboption of a HOOK ENTRY — never a top-level sibling of name/description +``` + +All hook events are supported (`hooks:652`) — `PreToolUse`/`PostToolUse`/`Stop` are just the common +ones. CC registers a SK's hooks when invoked and keeps them running for the REST OF THE SESSION, +including turns after the SK's own (`hooks:650`). `once: true` fires the hook once then +unregisters it — honored only nested under a hook entry in SK frontmatter as shown above; ignored +in settings.json and in agent frontmatter (`hooks:424`). + +`PostToolUse` runs AFTER the tool, so it cannot prevent the call (`hooks:839`) — but +`decision: "block"` still adds a `reason` next to the tool result, and `updatedToolOutput` replaces +what Claude sees (`hooks:1923`). + +> PLG caveat: SK-frontmatter hooks do not fire for PLG skills ([#17688](https://github.com/anthropics/claude-code/issues/17688)) — use the plugin's own `hooks.json` instead. + +## SKILL.md skeleton + +```yaml +--- +name: my-skill # max 64 chars, lowercase-hyphens, == dir name, NO plg: prefix +description: "Apply X guidelines for Y" # ALWAYS quoted -- prevents YAML parse failure +--- + +# Skill Name + +## Overview +One paragraph purpose. + +## Instructions +Imperative form: "Do X" (not "You should do X"). +``` + +## Common frontmatter / structural mistakes + +| Mistake | Fix | +|---|---| +| Colon in `description` unquoted | Quote the whole value — an em dash/colon breaks YAML silently | +| Body >500 lines | Move detail to `references/` | +| Invented FM key (`cli:` outside the house rule above, `version:` without the outside-dir rule, `updated:`) | Use a supported key or `metadata:` — claude.ai packaging hard-fails on the rest (`skills:358`) | +| Bare `Bash`/`Write`/`Edit`/`Agent` in `allowed-tools` "to restrict" | `AT` only pre-approves, never restricts (`skills:513`); narrowest Bash pattern or omit; restrict via `disallowed-tools` | +| `argument-hint` starts with a mode token | Prompt is always position 1: `[prompt] [mode1\|mode2]`, never `` alone | +| `skill.md` (lowercase) | Must be `SKILL.md` (uppercase) — lowercase silently ignored ([#17417](https://github.com/anthropics/claude-code/issues/17417)) | +| Reserved SK names (`anthropic`, `claude`) | Won't load — avoid these two words as `name` | +| DESC over spec/listing caps | May be truncated — front-load keywords, cut filler | +| `agent:` set to `developer`/`tester`/`reviewer` | Only `Explore`/`Plan`/`general-purpose` are built in — a generated SK naming anything else fails to resolve its SA on first run | +| Bare top-level `once: true` | `once` is a suboption of a hook entry (`hooks.[].hooks[].once`), never a sibling of `name`/`description` | diff --git a/brewcode/skills/skills/scripts/validate-skill.sh b/brewcode/skills/skills/scripts/validate-skill.sh index 1d76118..3f9fb58 100755 --- a/brewcode/skills/skills/scripts/validate-skill.sh +++ b/brewcode/skills/skills/scripts/validate-skill.sh @@ -23,6 +23,7 @@ is_exempt_skill() { PASS=0 FAIL=0 +WARN=0 check() { if [ "$1" = "ok" ]; then @@ -34,6 +35,13 @@ check() { fi } +# Additive, non-fatal: a row here never fails the check — it flags something worth a human +# glance without breaking any of the 28 skills shipping today. +warn() { + WARN=$((WARN + 1)) + echo "⚠️ $1" +} + # 1. No lowercase skill.md (ls -1 for exact case on case-insensitive FS) if ls -1 "$SKILL_DIR" 2>/dev/null | grep -q '^skill\.md$'; then check fail "skill.md found — must be SKILL.md (uppercase)" @@ -60,8 +68,16 @@ else check fail "Frontmatter missing --- delimiters (found $FM_COUNT, need 2+)" fi -# Extract frontmatter block (between first two --- lines) -FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SKILL_FILE" 2>/dev/null || true) +# Extract frontmatter block: strictly the lines between the FIRST two --- delimiters, by line +# number. A naive sed range (`/^---$/,/^---$/`) restarts every time it closes, so a `---` +# horizontal rule anywhere in the body reopens it and appends body text into $FRONTMATTER — +# several skills use `---` as a divider, which silently poisoned frontmatter-keyed checks. +FM_CLOSE=$(grep -n '^---$' "$SKILL_FILE" 2>/dev/null | sed -n '2s/:.*//p') +if [ -n "${FM_CLOSE:-}" ] && [ "$FM_CLOSE" -gt 1 ] 2>/dev/null; then + FRONTMATTER=$(sed -n "2,$((FM_CLOSE - 1))p" "$SKILL_FILE" 2>/dev/null || true) +else + FRONTMATTER="" +fi # 4. name field: present, bare kebab-case, max 64 chars, equal to the directory name. # A `plugin:` prefix here is a defect: Claude Code prepends the plugin name itself, so @@ -103,10 +119,7 @@ else fi # 6. Body (content after frontmatter) is non-empty. -# Everything AFTER the frontmatter's closing `---`. A range-negation sed (`/^---$/,/^---$/!p`) -# pairs later `---` lines with each other and silently drops whole body chunks — several skills -# use `---` as a horizontal rule, and checks 8-10 then read a mutilated body. -FM_CLOSE=$(grep -n '^---$' "$SKILL_FILE" 2>/dev/null | sed -n '2s/:.*//p') +# Everything AFTER the frontmatter's closing `---` (FM_CLOSE, computed above). BODY=$(tail -n +"$((${FM_CLOSE:-0} + 1))" "$SKILL_FILE" 2>/dev/null | grep -v '^$' || true) if [ -z "$BODY" ]; then check fail "Body content after frontmatter is empty" @@ -213,7 +226,59 @@ else fi fi +# 11. Unknown top-level frontmatter key -> warning, never fail. Allow-list = the 20 native +# SKILL.md fields (brewcode/skills/skills/references/frontmatter-fields.md) plus the house +# custom keys actually in use or documented for future use (grepped across all 28 shipped +# SKILL.md frontmatters, plus `cli`/`version` from skills/SKILL.md's own contract). +ALLOWED_KEYS="name description when_to_use disable-model-invocation user-invocable arguments \ +argument-hint allowed-tools disallowed-tools model effort context background agent hooks paths \ +shell metadata license compatibility cli version content_version generated_by last_updated \ +doc_type surface_files" +FM_KEYS=$(echo "$FRONTMATTER" | grep -E '^[a-zA-Z_-]+:' | sed -E 's/^([a-zA-Z_-]+):.*/\1/' | sort -u || true) +UNKNOWN_KEYS="" +for k in $FM_KEYS; do + case " $ALLOWED_KEYS " in + *" $k "*) ;; + *) UNKNOWN_KEYS="$UNKNOWN_KEYS $k" ;; + esac +done +if [ -n "$UNKNOWN_KEYS" ]; then + warn "Unknown frontmatter key(s):$UNKNOWN_KEYS -- not in the 20 native fields or house custom keys (see references/frontmatter-fields.md); verify before shipping, or use metadata: for free-form data" +else + check ok "All frontmatter keys recognized" +fi + +# 12. Bare top-level 'once:' — it is a suboption of a hook entry +# (hooks.[].hooks[].once), never a sibling of name/description. Fatal: this shape +# never worked, so flagging it can never break a previously-passing skill. +if echo "$FRONTMATTER" | grep -qE '^once:'; then + check fail "'once' found as a top-level frontmatter field -- it is a suboption of a hook entry (hooks.[].hooks[].once), not a sibling of name/description" +else + check ok "No top-level 'once' field" +fi + +# 13. agent: value outside the confirmed built-in set -> warning, not fail. A real custom +# agent under .claude/agents/ or ~/.claude/agents/ is legitimate; only devs naming a +# nonexistent built-in (developer/tester/reviewer) are actually broken, and this script has +# no way to tell the two apart from the frontmatter alone. +AGENT_VAL=$(echo "$FRONTMATTER" | grep -E '^agent:' | head -1 | sed 's/^agent:[[:space:]]*//' | tr -d '"' | tr -d "'" || true) +if [ -n "$AGENT_VAL" ]; then + case "$AGENT_VAL" in + Explore|Plan|general-purpose) check ok "agent: '$AGENT_VAL' is a built-in SA type" ;; + *) warn "agent: '$AGENT_VAL' is not one of the confirmed built-ins (Explore, Plan, general-purpose) -- fine only if '$AGENT_VAL' is a real custom agent under .claude/agents/ or ~/.claude/agents/" ;; + esac +fi + +# 14. UTF-8 BOM at file start. Files starting with a BOM were silently ignored by CC before +# 2.1.239; flag regardless of CC version so authoring stays clean. +BOM=$(head -c 3 "$SKILL_FILE" 2>/dev/null | od -An -tx1 | tr -d ' \n' || true) +if [ "$BOM" = "efbbbf" ]; then + check fail "UTF-8 BOM detected at file start -- strip it (files starting with a BOM were silently ignored by CC before 2.1.239; keep authoring clean regardless)" +else + check ok "No UTF-8 BOM" +fi + # Summary echo "" -echo "=== Result: $PASS passed, $FAIL failed ===" +echo "=== Result: $PASS passed, $FAIL failed, $WARN warned ===" [ "$FAIL" -eq 0 ] && exit 0 || exit 1 diff --git a/brewcode/skills/superreview-setup/SKILL.md b/brewcode/skills/superreview-setup/SKILL.md index 2e67486..f974950 100644 --- a/brewcode/skills/superreview-setup/SKILL.md +++ b/brewcode/skills/superreview-setup/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[prompt] [status|install|upgrade|enable|disable|uninstall|purge] allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion] model: opus --- - + # Super Review Generator (brewcode:superreview-setup) diff --git a/brewcode/skills/teams-setup/SKILL.md b/brewcode/skills/teams-setup/SKILL.md index b79701b..6225465 100644 --- a/brewcode/skills/teams-setup/SKILL.md +++ b/brewcode/skills/teams-setup/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[prompt] [status|install|upgrade|enable|disable|uninstall|purge] allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion, Skill] model: opus --- - + diff --git a/brewcode/skills/teams-setup/scripts/trace-ops.sh b/brewcode/skills/teams-setup/scripts/trace-ops.sh index 6a8e1e6..bcf707c 100755 --- a/brewcode/skills/teams-setup/scripts/trace-ops.sh +++ b/brewcode/skills/teams-setup/scripts/trace-ops.sh @@ -1,5 +1,5 @@ #!/bin/sh -# brewcode-meta: version=6.1.4 content_version=5.6.0 generated_by=brewcode:teams-setup +# brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewcode:teams-setup set -eu USAGE="Usage: trace-ops.sh [args...]" diff --git a/brewdoc/.claude-plugin/plugin.json b/brewdoc/.claude-plugin/plugin.json index 11568af..90aee46 100644 --- a/brewdoc/.claude-plugin/plugin.json +++ b/brewdoc/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "brewdoc", - "version": "6.1.4", + "version": "6.2.0", "description": "Brewdoc - Claude Code documentation tools: my-claude installation docs, memory-sync generator", "author": { "name": "Maksim Kochetkov", diff --git a/brewdoc/.codex/skills/md-to-pdf/SKILL.md b/brewdoc/.codex/skills/md-to-pdf/SKILL.md index 8f83907..82348aa 100644 --- a/brewdoc/.codex/skills/md-to-pdf/SKILL.md +++ b/brewdoc/.codex/skills/md-to-pdf/SKILL.md @@ -11,7 +11,7 @@ Convert a local Markdown file with `scripts/md_to_pdf.py`. Check dependencies wi Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve ``, ``, ``, and `` before running commands. - + # MD to PDF diff --git a/brewdoc/README.md b/brewdoc/README.md index d8f283c..1132a16 100644 --- a/brewdoc/README.md +++ b/brewdoc/README.md @@ -4,7 +4,7 @@ | Field | Value | |-------|-------| -| Version | 6.1.4 | +| Version | 6.2.0 | | Skills | 5 | | Agents | 0 | | Hooks | 0 | @@ -126,7 +126,7 @@ brewdoc/ +-- skills/ +-- docsync-setup/ # Doc-staleness tracker +-- my-claude/ # Installation documentation - +-- memory-sync-setup/ # Memory-sync generator (references/: SKILL.md.template, memory-guide, agent-audit, hard-sync) + +-- memory-sync-setup/ # Memory-sync generator (references/: SKILL.md.template, memory-guide, agent-audit, hard-sync, prompting-guide) +-- md-to-pdf/ # PDF conversion +-- publish/ # brewpage.app publishing ``` diff --git a/brewdoc/docs/commands.md b/brewdoc/docs/commands.md index a7c5ec1..34c9c5b 100644 --- a/brewdoc/docs/commands.md +++ b/brewdoc/docs/commands.md @@ -4,7 +4,7 @@ description: Detailed description of all brewdoc plugin commands # Brewdoc Plugin Commands -> **Version:** 6.1.4 | **Author:** Maksim Kochetkov | **License:** MIT +> **Version:** 6.2.0 | **Author:** Maksim Kochetkov | **License:** MIT ## Quick Reference diff --git a/brewdoc/skills/docsync-setup/SKILL.md b/brewdoc/skills/docsync-setup/SKILL.md index 66323ed..5dbd5e2 100644 --- a/brewdoc/skills/docsync-setup/SKILL.md +++ b/brewdoc/skills/docsync-setup/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[prompt] [status|install|upgrade|enable|disable|uninstall|purge] allowed-tools: [Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion] model: sonnet --- - + # docsync-setup diff --git a/brewdoc/skills/docsync-setup/assets/docsync-gate.mjs b/brewdoc/skills/docsync-setup/assets/docsync-gate.mjs index 0ca4f14..7be4a9a 100644 --- a/brewdoc/skills/docsync-setup/assets/docsync-gate.mjs +++ b/brewdoc/skills/docsync-setup/assets/docsync-gate.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewdoc:docsync-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewdoc:docsync-setup /** * docsync-gate — Stop hook (self-contained, project-local) * diff --git a/brewdoc/skills/docsync-setup/assets/docsync-track.mjs b/brewdoc/skills/docsync-setup/assets/docsync-track.mjs index 3f9f0b5..99a9e2f 100644 --- a/brewdoc/skills/docsync-setup/assets/docsync-track.mjs +++ b/brewdoc/skills/docsync-setup/assets/docsync-track.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewdoc:docsync-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewdoc:docsync-setup /** * docsync-track — PostToolUse:Write|Edit|MultiEdit hook (self-contained, project-local) * diff --git a/brewdoc/skills/docsync-setup/assets/docsync-watch.mjs b/brewdoc/skills/docsync-setup/assets/docsync-watch.mjs index eb2dad6..3725e7b 100644 --- a/brewdoc/skills/docsync-setup/assets/docsync-watch.mjs +++ b/brewdoc/skills/docsync-setup/assets/docsync-watch.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewdoc:docsync-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewdoc:docsync-setup /** * docsync-watch — PostToolUse:Read hook (self-contained, project-local) * diff --git a/brewdoc/skills/md-to-pdf/SKILL.md b/brewdoc/skills/md-to-pdf/SKILL.md index 46aaf9c..0fd2f05 100644 --- a/brewdoc/skills/md-to-pdf/SKILL.md +++ b/brewdoc/skills/md-to-pdf/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[prompt] [--engine name] [\"llm prompt\"] | styles | t allowed-tools: [Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion] model: sonnet --- - + # MD to PDF diff --git a/brewdoc/skills/memory-sync-setup/README.md b/brewdoc/skills/memory-sync-setup/README.md index 609c55e..7d8594b 100644 --- a/brewdoc/skills/memory-sync-setup/README.md +++ b/brewdoc/skills/memory-sync-setup/README.md @@ -81,11 +81,11 @@ generator calibrates them on real examples harvested from the target's own rules | Mode | Writes | What runs | |------|--------|-----------| | `status` (default when installed) | nothing | Is `memory-sync` installed, what does its provenance frontmatter say (`doc_type` / `version` / `generated_by` / `last_updated` / `surface_files`), and how stale are its surface tables vs the live repo (baked `surface_files` count vs enumerated now, dead paths, layers gained). Verdict: `IN SYNC` / `STALE (n drifts)` / `STALE-LEGACY (n drifts)` / `NOT INSTALLED`, prefixed `PARKED - ` when the install is disabled -- parked is never reported as missing | -| `install` (default when not installed) | the 4 emitted files | Full analysis + emit. Refuses an existing installation | +| `install` (default when not installed) | the 5 emitted files | Full analysis + emit. Refuses an existing installation | | `upgrade` | targeted edits | Re-scan and refresh an existing installation: re-enumerate the surface, refresh the batch / fact / invariant tables, add sections for new memory layers, then ALWAYS finish with `generate.sh restamp` so the provenance stamp reaches the current plugin version. **Hand-edits are preserved** -- the emitted skill is expected to have self-modified. Never blind-overwrite | | `enable` | renames one file | Restores a parked install: `SKILL.md.disabled` -> `SKILL.md`. Claude Code discovers a project skill only through an exact `SKILL.md`, so the rename is the whole switch | | `disable` | renames one file | Parks the install: `SKILL.md` -> `SKILL.md.disabled`. `/memory-sync` stops resolving in the NEXT session; the references, the provenance frontmatter and every hand-edit stay byte-identical, ready for `enable` | -| `uninstall` | deletes the emitted files | Removes exactly what the generator emitted -- `SKILL.md` (or `SKILL.md.disabled`) plus the 3 files in `references/`. Anything you added to that dir yourself is KEPT and listed. The dir is removed only if it ends up empty | +| `uninstall` | deletes the emitted files | Removes exactly what the generator emitted -- `SKILL.md` (or `SKILL.md.disabled`) plus the 4 files in `references/`. Anything you added to that dir yourself is KEPT and listed. The dir is removed only if it ends up empty | | `purge` | deletes `.claude/skills/memory-sync/` | The whole dir, hand-added files included, plus any `.memory-sync-emit.*` staging left by a crashed emit | Canonical order: `status | install | upgrade | enable | disable | uninstall | purge`. No argument = `status` when @@ -150,6 +150,7 @@ Then run the emitted skill in that project: | `references/memory-guide.md` | Emitted: where-does-it-belong decision tree, compression patterns, obvious vs domain facts | | `references/agent-audit.md` | Emitted: the agent + skill re-audit procedure run on every sweep | | `references/hard-sync.md` | Emitted: the two `HARD`-depth deletion passes (`paths:` precision audit, obvious-knowledge purge) + their reporting contract | +| `references/prompting-guide.md` | Emitted: merged Claude 5 + OpenAI/Codex prompting-quality rule table + lossless guard, applied in Phase 2/3 | ## Re-run triggers diff --git a/brewdoc/skills/memory-sync-setup/SKILL.md b/brewdoc/skills/memory-sync-setup/SKILL.md index 9fd4321..4a2f5a7 100644 --- a/brewdoc/skills/memory-sync-setup/SKILL.md +++ b/brewdoc/skills/memory-sync-setup/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[prompt] [status|install|upgrade|enable|disable|uninstall|purge] allowed-tools: [Read, Edit, Glob, Grep, Bash, Agent, AskUserQuestion] model: opus --- - + # Memory Sync Generator (brewdoc:memory-sync-setup) @@ -21,8 +21,8 @@ a stale lint-rule claim from a correct one, and cannot prove a removed fact is g merely deleted from a doc. **OUTPUT:** `/.claude/skills/memory-sync/` -- `SKILL.md` + `references/memory-guide.md` + -`references/agent-audit.md` + `references/hard-sync.md`. Nothing else is written; no agent is created, no rule is -installed, no hook is registered. +`references/agent-audit.md` + `references/hard-sync.md` + `references/prompting-guide.md`. Nothing else is +written; no agent is created, no rule is installed, no hook is registered. ## Prompt contract @@ -113,14 +113,14 @@ Never print a removed alias as a command. | Mode | Reads | Writes | Does | |------|-------|--------|------| | `status` (**DEFAULT when installed**) | target + emitted skill | NOTHING | Report whether `/.claude/skills/memory-sync/` exists, its provenance frontmatter (`doc_type` / `version` / `generated_by` / `last_updated` / `surface_files`), and how STALE its surface tables are vs the live repo: `surface_files` count baked in vs enumerated now, batches whose paths no longer exist, memory layers the project gained since. Ends with a verdict `IN SYNC` / `STALE (n drifts)` / `STALE-LEGACY (n drifts)` (pre-5.0 tail stamp) / `NOT INSTALLED`, each prefixed `PARKED - ` when the install is disabled (`INSTALLED=parked`) -- parked and absent are never collapsed | -| `install` (**DEFAULT when not installed**) | target | emits the 4 files | Full Phase 0-5 analysis + emit. Refuses an existing installation (see Error Handling) | +| `install` (**DEFAULT when not installed**) | target | emits the 5 files | Full Phase 0-5 analysis + emit. Refuses an existing installation (see Error Handling) | | `upgrade` | target + emitted skill | Edits the emitted skill | Re-scan, then REFRESH an existing installation: re-enumerate the surface, refresh the batch / fact / invariant tables, ADD sections for memory layers the project gained, and ALWAYS finish with `generate.sh restamp` (see Mode: upgrade). PRESERVE hand-edits -- the emitted skill is EXPECTED to have self-modified (SELF-SYNC phase). Never blind-overwrite | | `enable` | target | one rename | `generate.sh enable`: `SKILL.md.disabled` -> `SKILL.md`, so `/memory-sync` is offered again. Regenerates nothing, so no provenance stamp and no hand-edit changes | -| `disable` | target | one rename | `generate.sh disable`: `SKILL.md` -> `SKILL.md.disabled`. Claude Code discovers a project skill ONLY through `SKILL.md`, so this withdraws `/memory-sync` from the roster while the 3 references and every SELF-SYNC hand-edit stay byte-identical on disk. Reversible by `enable`; deletes nothing | -| `uninstall` | target | deletes the emit manifest | `generate.sh uninstall`: removes exactly what `emit` wrote -- `SKILL.md` (or its parked form) plus the 3 references -- and nothing it did not. Files a user added to that dir are KEPT and listed. Confirmation first | +| `disable` | target | one rename | `generate.sh disable`: `SKILL.md` -> `SKILL.md.disabled`. Claude Code discovers a project skill ONLY through `SKILL.md`, so this withdraws `/memory-sync` from the roster while the 4 references and every SELF-SYNC hand-edit stay byte-identical on disk. Reversible by `enable`; deletes nothing | +| `uninstall` | target | deletes the emit manifest | `generate.sh uninstall`: removes exactly what `emit` wrote -- `SKILL.md` (or its parked form) plus the 4 references -- and nothing it did not. Files a user added to that dir are KEPT and listed. Confirmation first | | `purge` | target | deletes the whole dir | `generate.sh purge`: removes `/.claude/skills/memory-sync/` outright, user-added files included, plus any `.memory-sync-emit.*` staging a crashed emit left under `.claude/skills/`. Confirmation first | -> **Why `uninstall` and `purge` differ here.** `emit` writes a fixed manifest (`SKILL.md` + the 3 references), and +> **Why `uninstall` and `purge` differ here.** `emit` writes a fixed manifest (`SKILL.md` + the 4 references), and > that manifest is also the removal manifest: `uninstall` is scoped to it, so a note or an extra reference the user > dropped into the skill dir is never destroyed by a removal they asked for. `purge` is the "I am done with this > entirely" verb and takes the directory. The generator registers no hooks, writes no settings and no config, so @@ -212,7 +212,7 @@ a `.memory-sync-emit.*` staging dir beside it) -- it registers no hooks, writes find "$ROOT/.claude/skills/memory-sync" -type f | sort ``` 3. **ASK** via `AskUserQuestion`, ONCE, naming the real count: - - `uninstall`: "Delete the 4 emitted files under `/.claude/skills/memory-sync/` (N files present)? + - `uninstall`: "Delete the 5 emitted files under `/.claude/skills/memory-sync/` (N files present)? Hand-edits to them are lost; anything you added yourself is kept." Options: **Yes, uninstall** / **Purge instead (deletes the whole dir)** / **Cancel**. - `purge`: "Delete `/.claude/skills/memory-sync/` entirely (N files)? Nothing is recoverable." @@ -261,6 +261,8 @@ Read the emit material this generator ships, relative to `${CLAUDE_SKILL_DIR}`: - `references/agent-audit.md` -- the agent/skill re-audit procedure the emitted skill runs every sweep - `references/hard-sync.md` -- the two `HARD`-depth deletion passes (`paths:` precision audit + obvious-knowledge purge) and their reporting contract; it holds TWO of the twelve BLOCK placeholders +- `references/prompting-guide.md` -- the merged Claude 5 + OpenAI/Codex prompting-quality rule table applied at + Phase 2/3; carries no BLOCK placeholders Confirm the TARGET project is the current working directory. All emitted paths are relative to that repo root. @@ -341,9 +343,9 @@ bash "${CLAUDE_SKILL_DIR}/scripts/generate.sh" emit && echo "✅ emit" || echo " > **STOP if ❌** -- verify `${CLAUDE_SKILL_DIR}/references/SKILL.md.template` exists and the target `.claude/` is > writable. On an existing installation `emit` refuses by design: use `upgrade`. -This writes the FOUR-file tree: `/.claude/skills/memory-sync/SKILL.md` with scalars substituted, plus -`references/memory-guide.md`, `references/agent-audit.md` and `references/hard-sync.md` copied into the emitted -`references/`. +This writes the FIVE-file tree: `/.claude/skills/memory-sync/SKILL.md` with scalars substituted, plus +`references/memory-guide.md`, `references/agent-audit.md`, `references/hard-sync.md` and +`references/prompting-guide.md` copied into the emitted `references/`. > `disable-model-invocation` MUST NOT be set on the emitted skill: plain-prose invocation ("память устарела", > "sync memory") is a first-class path, alongside `/memory-sync [scope]`. Legacy installs that still carry the key @@ -438,6 +440,7 @@ Files written: - .claude/skills/memory-sync/references/memory-guide.md - .claude/skills/memory-sync/references/agent-audit.md - .claude/skills/memory-sync/references/hard-sync.md +- .claude/skills/memory-sync/references/prompting-guide.md Run it: /memory-sync -> scope session (default), depth NORMAL, whole surface /memory-sync all "only rules" -> re-verify every fact, emphasis on rules @@ -490,7 +493,7 @@ the single list -- do not restate it here. | Setting | Default | Description | |---------|---------|-------------| | Emit target | `/.claude/skills/memory-sync/` | Where the generated skill is written | -| Emit material | `${CLAUDE_SKILL_DIR}/references/` | `SKILL.md.template`, `memory-guide.md`, `agent-audit.md`, `hard-sync.md` -- four files emitted | +| Emit material | `${CLAUDE_SKILL_DIR}/references/` | `SKILL.md.template`, `memory-guide.md`, `agent-audit.md`, `hard-sync.md`, `prompting-guide.md` -- five files emitted | | Emitted default depth | `NORMAL` | `HARD` is per-run, from the emitted skill's own arguments; nothing is regenerated to switch | | Generation script | `${CLAUDE_SKILL_DIR}/scripts/generate.sh` | `scan` \| `emit` \| `validate` \| `restamp` \| `status` \| `enable` \| `disable` \| `uninstall` \| `purge` | | Provenance refresh | `generate.sh restamp` | Metadata-only, idempotent, mandatory tail of `upgrade`. Rewrites `version` / `last_updated` / `surface_files`, adds `doc_type` / `generated_by` when absent, deletes a pre-5.0 tail stamp and a legacy `disable-model-invocation`, re-copies a reference ONLY when its sole difference from the plugin source is the release stamp. Aborts rather than write if anything outside the metadata block would move | @@ -548,6 +551,8 @@ the single list -- do not restate it here. - `references/hard-sync.md` -- the `HARD`-depth passes: `paths:` precision audit + obvious-knowledge purge, with their verdict vocabulary and reporting contract (emitted; holds `{PATHS_PRECISION_TABLE}` + `{OBVIOUS_VS_DOMAIN_TABLE}`). +- `references/prompting-guide.md` -- the merged Claude 5 + OpenAI/Codex prompting-quality rule table applied in + Phase 2/3 (emitted; no BLOCK placeholders). - `scripts/generate.sh` -- `scan` / `emit` / `validate` / `restamp` / `status` / `enable` / `disable` / `uninstall` / `purge`. @@ -556,7 +561,7 @@ SKILL METADATA -- brewdoc:memory-sync-setup (GENERATOR) Replaces the old brewdoc:memory (a generic in-plugin memory syncer). Analyzes a target project and emits a self-contained project-local .claude/skills/memory-sync/ (SKILL.md + memory-guide.md + agent-audit.md + -hard-sync.md). The plugin never syncs memory itself. +hard-sync.md + prompting-guide.md). The plugin never syncs memory itself. The emitted skill has TWO axes: {SCOPE} = which change facts drive the sweep (session default | branch | commit | recent[:N] | all), {DEPTH} = how hard the surface is cut (NORMAL default | HARD = + paths: precision audit + diff --git a/brewdoc/skills/memory-sync-setup/references/SKILL.md.template b/brewdoc/skills/memory-sync-setup/references/SKILL.md.template index 841e4be..1cd44c0 100644 --- a/brewdoc/skills/memory-sync-setup/references/SKILL.md.template +++ b/brewdoc/skills/memory-sync-setup/references/SKILL.md.template @@ -23,6 +23,7 @@ subagents get the repo-relative path, never pasted prose): | `references/memory-guide.md` | Phase 2 - where a fact BELONGS (layer decision tree), compression patterns, obvious-vs-domain gate | | `references/agent-audit.md` | Phase 2 agents + skills batches - the best-practice re-audit, run EVERY sweep | | `references/hard-sync.md` | Phases 2 and 3 at `{DEPTH}` = `HARD` only - `paths:` precision audit + obvious-knowledge purge, and the `hard` report contract Phase 3 checks | +| `references/prompting-guide.md` | Phase 2 (every file) and Phase 3 - the merged prompting-quality rule table + lossless guard | --- @@ -269,6 +270,7 @@ CONTEXT (already resolved - do NOT re-derive): the file list, its per-file line READ FIRST (paths, not pasted prose): .claude/skills/memory-sync/references/memory-guide.md - layer decision tree, compression patterns, obvious gate .claude/skills/memory-sync/references/agent-audit.md - agents and skills batches ONLY: the standing re-audit + .claude/skills/memory-sync/references/prompting-guide.md - the merged rule table + lossless guard, step 3 below .claude/skills/memory-sync/SKILL.md - the checkable-fact catalogue (verification commands), the house invariants Per file, in THIS priority order: @@ -281,6 +283,11 @@ Per file, in THIS priority order: - DEDUPLICATE: a fact repeated across files keeps ONE canonical home; copies become a pointer ("canonical: section"). Canonical home in ANOTHER batch -> REPORT it, do not edit a foreign file. - COMPRESS verbose passages into this repo's dense table/pointer style; meaning intact, lines down. +3. PROMPT QUALITY - instruction files only (CLAUDE.md/rules/AGENTS.md/agent/skill bodies, never code/docs): + - Apply `references/prompting-guide.md`'s rule table; rewrite ONLY where its lossless guard allows it - + never touch a path, version, flag, threshold, model id, incident-backed `!=`/NEVER row or canonical list. + - `{DEPTH}` = NORMAL: fix a violation ONLY where it coincides with a fact/dedup edit already touching that + line; otherwise REPORT it. `{DEPTH}` = HARD: rewrite every remaining violation. NON-GROWTH (hard): every file ends <= the baseline given beside its path in SCOPE; delete before you add. The ONE authorized growth is the PASS A frontmatter `paths:` repair at HARD depth on a MISSING or TOO_NARROW verdict; nothing else may grow. @@ -303,7 +310,8 @@ HARD constraints: CONSUMER: an independent read-only checker re-verifies every claim you make, then the coordinator's report table. DONE - return JSON, one entry per file, empty lists if clean: {"": {"added": [...], "removed": [...], "fixed": [...], "dedup": [...], "compressed": N, - "lines_before": N, "lines_after": N, "broken_refs": [...], "uncertain": [...]}} + "lines_before": N, "lines_after": N, "broken_refs": [...], "uncertain": [...], + "prompt_quality": [{"rule": N, "line": N, "before": "...", "after": "..."}]}} At {DEPTH} = HARD each entry ALSO carries a nested "hard": {...} object, shaped exactly as in references/hard-sync.md. ``` @@ -340,6 +348,7 @@ CONTEXT (already resolved - do NOT re-derive): the batch's returned JSON, the pe READ FIRST (paths, not pasted prose): .claude/skills/memory-sync/SKILL.md - the checkable-fact catalogue (verification commands), the house invariants .claude/skills/memory-sync/references/hard-sync.md - {DEPTH} = HARD only: the two passes and the `hard` report contract you check + .claude/skills/memory-sync/references/prompting-guide.md - the merged rule table and lossless guard you re-check in (h) below Checks - ALL of them, per file: a. Re-verify every ADDED and FIXED fact against the ACTUAL code (run the catalogue command, read the file). @@ -353,6 +362,9 @@ f. Scan for language-policy violations per {LANGUAGE_POLICY} - intentional trigg g. `{DEPTH}` = HARD only: re-read every `hard.obvious_deleted` line and confirm it was GENERIC, not a domain fact; re-resolve every `hard.globs_declared` entry against the working tree (both probes, tracked and untracked); confirm every `hard.domain_kept` line still stands in the file and rule each `hard.discriminator_uncertain` line KEEP or DELETE. +h. `prompt_quality` entries (if any): re-read each `before`/`after` pair against the cited rule number in + `references/prompting-guide.md` and confirm the lossless guard held - no path, version, flag, threshold, + model id, incident-backed row or canonical list moved. CONSUMER: the coordinator - a non-ok verdict routes a targeted fix back to the agent that wrote the file. DONE - return JSON per file, empty list when clean: {"ok": bool, "violations": [...]} @@ -379,7 +391,7 @@ Violations -> the coordinator sends targeted fixes back to the responsible batch ## Phase 4 - SELF-SYNC (the skill syncs ITSELF) This skill is part of the surface it sweeps. Every run, after Phase 3 - and spawn ONE additional read-only checker -over the self-synced files (this SKILL.md plus ALL THREE references), reported as its own row in the Phase-6 delta +over the self-synced files (this SKILL.md plus ALL FOUR references), reported as its own row in the Phase-6 delta table: | Step | Action | @@ -388,7 +400,7 @@ table: | Refresh | Update the batch rows, the counts, and every fact-catalogue command that no longer matches reality | | Grow a batch | The project gained a memory layer this skill does not cover (a new nested `CLAUDE.md` tree, a new convention dir, a new skills tree) -> ADD the batch or the section - report it; self-sync lines are delta-exempt | | Drop a batch | A batch whose files are all gone -> remove the row and say so in the report | -| Sibling refs | ALL THREE references - `memory-guide.md`, `agent-audit.md`, `hard-sync.md` - are self-synced the SAME way: their rosters, patterns and checks go stale exactly like this file's tables | +| Sibling refs | ALL FOUR references - `memory-guide.md`, `agent-audit.md`, `hard-sync.md`, `prompting-guide.md` - are self-synced the SAME way: their rosters, patterns and checks go stale exactly like this file's tables | > **CARVE-OUT - it binds BATCH AGENTS.** Facts inside this file may be CORRECTED. Its SCOPE DECISIONS may NOT be > rewritten without explicit user instruction: which surfaces are in scope, the EXCLUDED table, the docs @@ -417,8 +429,8 @@ If yes, PROPOSE in the final report: the name, the description gist, the trigger ``` memory-sync complete - scope {SCOPE}, depth {DEPTH}. {N} files scanned, {M} edited. -| File | Added | Removed | Fixed | Dedup | Compressed | Verified | -|------|-------|---------|-------|-------|------------|----------| +| File | Added | Removed | Fixed | Dedup | Compressed | Prompt | Verified | +|------|-------|---------|-------|-------|------------|--------|----------| | Layer | Lines before | Lines after | Delta | |-------|--------------|-------------|-------| @@ -430,6 +442,7 @@ Broken refs: {list | none} (incl. unresolved docs refs - reported, never edi Uncertain (left in place, verify manually): {list | none} Self-sync: {tables refreshed | batch added/dropped | no change} Agent audit: {N} agents / {M} skills re-audited; {K} findings +Prompt quality: {N} rewrites applied, {M} reported (references/prompting-guide.md) Proposal: {new agent/skill + triggers + neighbours | none} ``` diff --git a/brewdoc/skills/memory-sync-setup/references/agent-audit.md b/brewdoc/skills/memory-sync-setup/references/agent-audit.md index 80ae9a8..6945af7 100644 --- a/brewdoc/skills/memory-sync-setup/references/agent-audit.md +++ b/brewdoc/skills/memory-sync-setup/references/agent-audit.md @@ -1,4 +1,4 @@ - + # Agent and Skill Re-Audit The standing best-practice audit `/memory-sync` runs on EVERY agent file and EVERY skill file, on EVERY run, at @@ -8,6 +8,10 @@ fact-checked here - they are held to current best practice. **Project-specific checks live in the emitted SKILL.md's own check tables.** This file carries only what holds in any repo; do not restate the project tables here, and do not weaken them with a generic equivalent. +Agent bodies are ALSO checked against `references/prompting-guide.md`'s rule table (role-first framing, duplicate +reminders, scattered CAPS, vague adjectives, ...) - see AGENT check 13 below; the rule table itself is not +restated here. + --- ## Procedure - per file, before any edit @@ -47,6 +51,7 @@ ls -d "" 2>/dev/null | head -3 # ownership glob r | 10 | Scope fit | The body states what the agent does NOT own, and the boundary matches the neighbouring agents' claims | Add the exclusion; overlapping claims between two agents -> REPORT, do not arbitrate silently | | 11 | Output discipline | The body specifies the shape the agent returns to its caller | Add the return shape; an agent whose output shape is unstated produces unusable results | | 12 | No rule restatement | The body does not repeat what a rule or convention file already says | DEDUP finding: delete the copy, leave a pointer naming the canonical file and section | +| 13 | Prompting quality | Body carries no open row from `references/prompting-guide.md`'s rule table, or the survivor is REPORTED as uncertain | Apply the rewrite the guide's row specifies; never restate the rule table here - cite the row number | --- @@ -85,7 +90,7 @@ retired. Do not audit against a remembered key set. | Class | Handling | |-------|----------| -| MECHANICAL - apply the fix | `name:` mismatch (agent-1, skill-1), role-first description with concrete triggers (agent-2, agent-3), unjustified `model:` override (agent-6), dead path or glob (agent-7, skill-5), a tool entry used NOWHERE in the body (agent-4), missing `Bash` on a searcher (agent-5), dead MCP server prefix (agent-8), broken reference citation and uncited orphan reference (skill-8, skill-9), restated rule replaced by a pointer (agent-12), date stamp, wording compressed | +| MECHANICAL - apply the fix | `name:` mismatch (agent-1, skill-1), role-first description with concrete triggers (agent-2, agent-3), unjustified `model:` override (agent-6), dead path or glob (agent-7, skill-5), a tool entry used NOWHERE in the body (agent-4), missing `Bash` on a searcher (agent-5), dead MCP server prefix (agent-8), broken reference citation and uncited orphan reference (skill-8, skill-9), restated rule replaced by a pointer (agent-12), a prompting-guide rewrite where its lossless guard allows it (agent-13), date stamp, wording compressed | | RESPONSIBILITY - report only | Anything that changes WHAT an agent owns or does: widening or narrowing its scope, retargeting its ownership globs to a different subsystem, merging or splitting agents, deleting an agent whose surface is gone, resolving two agents that claim the same seam, ADDING a missing output shape (agent-11 - authoring, and it spends the non-growth budget), stripping a tool whose use is implied in prose but never named | A responsibility change is a design decision. State the finding, the evidence, and the proposed change in the diff --git a/brewdoc/skills/memory-sync-setup/references/hard-sync.md b/brewdoc/skills/memory-sync-setup/references/hard-sync.md index c9fab4a..d6b1560 100644 --- a/brewdoc/skills/memory-sync-setup/references/hard-sync.md +++ b/brewdoc/skills/memory-sync-setup/references/hard-sync.md @@ -1,10 +1,18 @@ - + # Hard Sync The two aggressive DELETION passes of `/memory-sync`. Cited by the emitted skill's Phase 2 batch prompt at `{DEPTH}` = `HARD`, by its Phase 3 VERIFY checkers, by its References table and by its Phase 4 self-sync. Both passes may only SHRINK a file - the ONE exception is the PASS A frontmatter repair that adds or widens a `paths:` key. +## Prompting-quality depth split + +`references/prompting-guide.md` runs at every scope AND depth (Phase 2, before any edit) - it is not a third +PASS here. What changes with `{DEPTH}` is how much of what it finds gets REWRITTEN: at `NORMAL`, fix a violation +ONLY where it coincides with a fact/dedup edit already touching that line, otherwise REPORT it; at `HARD`, +rewrite every remaining violation. Both depths return the guide's `file :: rule# :: line :: before -> after` +verdict rows - only the count acted on differs. + ## The problem Two kinds of waste dominate the AUTO-LOADED surface and neither shows up in a diff: a rule loaded into contexts it diff --git a/brewdoc/skills/memory-sync-setup/references/memory-guide.md b/brewdoc/skills/memory-sync-setup/references/memory-guide.md index db7218c..4a729dd 100644 --- a/brewdoc/skills/memory-sync-setup/references/memory-guide.md +++ b/brewdoc/skills/memory-sync-setup/references/memory-guide.md @@ -1,9 +1,12 @@ - + # Memory Guide Where a fact BELONGS, how to compress it, and what never gets written at all. Cited by every `/memory-sync` batch agent in Phase 2, before it edits anything. +Prompting-quality problems (role framing, duplicate reminders, scattered CAPS, vague adjectives) are a separate +axis, run in the same Phase 2 pass - see `references/prompting-guide.md`. + --- ## The layers diff --git a/brewdoc/skills/memory-sync-setup/references/prompting-guide.md b/brewdoc/skills/memory-sync-setup/references/prompting-guide.md new file mode 100644 index 0000000..336a08a --- /dev/null +++ b/brewdoc/skills/memory-sync-setup/references/prompting-guide.md @@ -0,0 +1,91 @@ + +# Prompting Quality Guide + +Applied to every INSTRUCTION file the sweep touches - CLAUDE.md at any depth, `.claude/rules/*.md`, the +`AGENTS.md` family, and agent/skill bodies. Never applied to code or docs (those stay owned by `docs/**` and the +doc flow). Cited by every `/memory-sync` batch agent in Phase 2, before it edits anything, the same way +`references/memory-guide.md` is. Current vendor prompting guidance for two audiences reading the SAME projected +file: Claude 5 (Opus 5 / Sonnet 5 / Fable 5.1) reads CLAUDE.md and agent/skill bodies directly; Codex reads the +`AGENTS.md` projection of the same content. A rule tagged `openai-only` fixes something only the AGENTS.md +projection is read against; `claude` fixes something only the Claude-side prompt is read against; `both` applies +to either reading. + +--- + +## Merged rule table + +Detect is a grep-able or eyeballed signal; apply the rewrite ONLY where it does not change a fact (see the +lossless guard below). Source keys resolve in the legend at the end. + +| # | Rule | Applies | Detect | Rewrite (bad -> good) | Source | +|---|------|---------|--------|------------------------|--------| +| 1 | Role + output contract first; hard constraints in their own section, never folded into the role sentence | both | opening paragraph is scope/procedure/background, not a role sentence + a return-shape line | "You're a specialized assistant that manages calendars and has three tools..." -> "Role: . Return: ." then a separate Scope/Never section | PEBP, SA | +| 2 | State each instruction once - no duplicate reminders across hooks, sections or files | both | the same directive text/keyword fires from two+ places (e.g. a per-prompt hook copy already covered by SessionStart; a rule restated in an agent body) | keep the tightest instance, delete the rest, point the duplicate at the canonical one | CCBP, PEBP, HOOK | +| 3 | Check every rule pair for contradiction before shipping | both | two rows in the same file cannot both be satisfied literally | "never schedule without consent" + "auto-assign the earliest slot" -> "auto-assign, then inform before confirming" | O5 | +| 4 | Positive imperative; keep ONLY a prohibition that guards a named, previously-observed failure | both | a "don't do X" with no incident/postmortem named nearby | reword as "do Y instead"; leave an `!=` row untouched if it (or `avoid.md`) names the incident it prevents | PEBP, avoid.md | +| 5 | Drop scattered ALL-CAPS; at most one true hard-stop word per artifact | both | grep -o for MUST/NEVER/CRITICAL/ALWAYS in caps returns more than one hit in a file | lower-case all but the single guard on an irreversible action | CCBP | +| 6 | No chain-of-thought asks or hand-written step-by-step scaffolding aimed at a reasoning/thinking-enabled model | both | "think step by step", "explain your reasoning", or a numbered thinking scaffold | "think step by step then answer" -> state the goal only; non-thinking models get "consider"/"evaluate", never the literal word "think" | PEBP, O7 | +| 7 | Remove self-verification instructions on a model that already self-verifies | claude | "double-check", "re-verify", "use a subagent to verify" in an Opus-5-targeted prompt | delete it; replace with an explicit scope boundary if one is missing | OPUS5, MIG | +| 8 | Subagent delegation restraint - explicit criterion, low spawn counts, one capable agent until real limits are hit | both | a delegating agent/skill with no stated delegation threshold, or a linear task split across many agents | add "delegate only large/independent/parallelizable work; never delegate a handful of tool calls; never spawn to verify your own work" | OPUS5, O14 | +| 9 | State scope explicitly - never rely on the model to silently generalize one example | both | a rule stated once by example, with no "for every file/case matching X" | name the scope explicitly instead of relying on generalization | SONNET5 | +| 10 | Reference data -> table; a real-dependency procedure -> numbered prose; separate distinct instruction blocks with a heading or tag | both | a table cell hides a multi-step procedure, or two unrelated instruction blocks share no separating heading/tag | split into a table (data) + numbered steps (procedure); wrap each block under its own heading | CCBP, O9 | +| 11 | Concrete example or named reference file over a vague adjective | both | "clean" / "thorough" / "professional" / "good" with no example or file pointer nearby | "write clean code" -> "follow the pattern in ``" or a short before/after snippet | PEBP, SONNET5 | +| 12 | File-size budgets for authored artifacts | both | `wc -w`/`wc -c`: SKILL.md over 500 lines / 2000 words, agent `.md` over 1500 words, hook `additionalContext` over 9000 chars (session) or 500 chars (per-prompt) | move overflow into `references/`; trim the hook string | SKC, HOOK | +| 13 | Explicit numeric/structural length limit on output, never "be concise" alone | both | "be concise" / "keep it short" with no attached number | "answer concisely" -> "<=5 bullets, 1 sentence each" | O6 | +| 14 | `[DICT: ...]` header only above the size threshold that earns it | both | a `[DICT:]` block on a file under ~150 lines, or fewer than 5 abbreviations each reused fewer than 3 times | drop the header and spell terms inline; keep it only at CLAUDE.md / large-rule-file scale | house judgment | +| 15 | AGENTS.md build/size-cap/nested-override mechanics | openai-only | a rule assumes AGENTS.md re-reads mid-session, a root AGENTS.md nears/exceeds 32 KiB, or a nested AGENTS.md restates a root rule instead of overriding it | keep the root short, push subtree specifics into nested AGENTS.md, and state only the override there | O1, O2, O3 | +| 16 | AGENTS.md / CLAUDE.md content is commands, style and conventions - never history, marketing or task state | both | a paragraph of project history, marketing prose or live task state inside CLAUDE.md/AGENTS.md | move history to README; keep only agent-actionable commands and conventions | O4 | +| 17 | Root/system-level rules always outrank a developer-level file; never spend words asserting override authority | both | a developer-level file (CLAUDE.md/AGENTS.md/skill body) claims it can override safety/system rules | delete the assertion; state the actually-desired in-policy behaviour directly | O13 | +| 18 | Explicit stop conditions and safe-vs-unsafe action boundaries for agentic tasks | both | "keep working until done" with no named boundary around a risky/irreversible action | "keep working until done" -> "stop and ask before any ; otherwise continue until resolved" | O10 | + +Dropped as redundant rather than merged: R14 (hook reminder cadence) and R16 (state parallelism once) are single +instances of rule 2 above, folded into its detect signal rather than kept as separate rows; R9 (lossless +compression of facts) is not a rewrite rule at all - it IS the guard below. + +--- + +## Lossless guard - what may never change + +A rewrite that touches ANY of the following has changed a fact, not just its prose - stop and leave the line as +is: + +| Never rewrite away | Examples | +|---------------------|----------| +| Exact paths, flags, thresholds, numeric limits | a glob, a CLI flag, a byte/char cap, a line-count budget | +| Versions and model ids | `6.1.4`, `claude-opus-5`, a pinned dependency version | +| An incident-backed `!=`/NEVER row | anything `avoid.md` or the file's own text ties to a named past failure | +| Canonical mode/verb lists | `status \| install \| upgrade \| enable \| disable \| uninstall \| purge` and similar fixed enumerations | + +Compression and de-duplication (rule 2, memory-guide.md's own patterns) still apply on top of this guide - +prompting-quality rewrites and fact/dedup edits share the same file and the same non-growth budget. + +--- + +## Verdict table - what a batch agent returns + +Alongside the batch's normal per-file JSON (`references/hard-sync.md` for the `hard` shape), a batch agent that +applied a prompting-quality rewrite lists each one: + +``` +file :: rule# :: line :: before -> after +``` + +One row per rewrite, `before`/`after` quoted verbatim and short. A Phase 3 checker re-reads each row against the +rule's `detect` signal and confirms no fact moved. + +--- + +## Stop condition + +Stop compressing or rewriting a passage the MOMENT a fact would change - a path, a version, a flag, a threshold, a +model id, an incident-backed prohibition, or a canonical list. Report it as `uncertain` instead of guessing. + +--- + +## Legend + +`PEBP`=platform.claude.com/.../claude-prompting-best-practices `OPUS5`=.../prompting-claude-opus-5 +`SONNET5`=.../prompting-claude-sonnet-5 `MIG`=.../models/opus-5/migration-guide `CCBP`=code.claude.com/docs/en/best-practices +`SA`=code.claude.com/docs/en/sub-agents `SKC`=`brewcode/agents/skill-creator.md` `HOOK`=`brewcode/hooks/lib/{reminder,utils}.mjs`. +`O1`-`O14` resolve in the OpenAI/Codex rules report cited by this plugin's own prompting refresh; full rationale +for both families is intentionally not duplicated here - it lives with that refresh, not in a shipped reference. diff --git a/brewdoc/skills/memory-sync-setup/scripts/generate.sh b/brewdoc/skills/memory-sync-setup/scripts/generate.sh index f0a33fa..78d3219 100755 --- a/brewdoc/skills/memory-sync-setup/scripts/generate.sh +++ b/brewdoc/skills/memory-sync-setup/scripts/generate.sh @@ -46,8 +46,8 @@ CONTENT_VERSION=$(resolve_content_version) # Target paths are relative to the resolved ROOT (see resolve_root - every mode cd's there first). TARGET=".claude/skills/memory-sync" TARGET_REFS="$TARGET/references" -EMITTED_REFS="memory-guide.md agent-audit.md hard-sync.md" -EMITTED_N=3 +EMITTED_REFS="memory-guide.md agent-audit.md hard-sync.md prompting-guide.md" +EMITTED_N=4 # The parked name of SKILL.md (see enable/disable). Declared here, next to the rest of the owned set, # because `emit`'s guard has to see it - a disabled install is still an install. DISABLED_MARK="$TARGET/SKILL.md.disabled" @@ -96,7 +96,7 @@ _count() { _o=$(eval "$1" 2>/dev/null || true); if [ -z "$_o" ]; then echo 0; el _count_md() { { find "$1" -maxdepth "${2:-1}" -type f -name '*.md' 2>/dev/null || true; } | wc -l | tr -d ' '; } validate_templates() { - for t in "$REFS/SKILL.md.template" "$REFS/memory-guide.md" "$REFS/agent-audit.md" "$REFS/hard-sync.md"; do + for t in "$REFS/SKILL.md.template" "$REFS/memory-guide.md" "$REFS/agent-audit.md" "$REFS/hard-sync.md" "$REFS/prompting-guide.md"; do [ -f "$t" ] || { echo "❌ FAILED: emit template not found: $t - reinstall brewdoc"; exit 1; } done # A stamp is only worth writing if it names a real plugin version. @@ -413,7 +413,7 @@ _subst() { # ── emit ──────────────────────────────────────────────────────────────────────── # STAGED: the whole tree is built in a staging dir on the SAME filesystem, and only once every file is -# generated does anything move into the target - as individual renames of the 4 OWNED paths. A failure +# generated does anything move into the target - as individual renames of the 5 OWNED paths. A failure # half-way therefore leaves NO half-generated install, which matters because a stray one would push the # user to MEMORY_SYNC_FORCE=1 (the flag that destroys edits). What emit must NEVER do is replace the # directory wholesale: `uninstall` explicitly reports foreign files in it as KEPT, and a `rm -rf $TARGET` @@ -485,7 +485,7 @@ emit_skill() { _stamp_frontmatter "$_stage/SKILL.md" || _emit_abort for r in $EMITTED_REFS; do cp "$REFS/$r" "$_stage/references/$r" || _emit_abort; done - # Selective placement: only the 4 paths this generator owns are removed and re-created. Anything else + # Selective placement: only the 5 paths this generator owns are removed and re-created. Anything else # in $TARGET (user notes, a foreign reference, an unrelated subdir) is never touched. mkdir -p "$TARGET_REFS" || _emit_abort rm -f "$TARGET/SKILL.md" || _emit_abort @@ -607,7 +607,7 @@ restamp_skill() { echo "✅ restamp (metadata keys only - body and every hand-edit verified byte-identical)" } -# The 3 references are mechanism-`a` byte copies: their version stamp is BAKED at release into the +# The 4 references are mechanism-`a` byte copies: their version stamp is BAKED at release into the # plugin's own file, so an installed copy only becomes current by being copied again. `upgrade` never # re-copied them, which left `setup-status`'s `cmp` reporting DIFFERS forever with no mode that could # clear it. Re-copy only where that is PROVABLY lossless - the sole difference is the release stamp @@ -735,7 +735,7 @@ status_report() { echo "PLUGIN_VERSION=$VERSION" # PARKED (SKILL.md renamed to SKILL.md.disabled by `disable`) is a THIRD state, never collapsed into - # absent: the body, the 3 references and every SELF-SYNC hand-edit are still on disk, so the stamp is + # absent: the body, the 4 references and every SELF-SYNC hand-edit are still on disk, so the stamp is # read out of the parked file and reported at its real version. Only `enable` brings it back. _skf="$TARGET/SKILL.md"; _parked=no if [ ! -f "$_skf" ] && [ -f "$DISABLED_MARK" ]; then _skf="$DISABLED_MARK"; _parked=yes; fi @@ -934,7 +934,7 @@ case "$MODE" in echo "Usage: generate.sh (default: emit)" echo " scan read-only surface report + derived DEFAULT_BRANCH= / GIT_VISIBILITY= / MEMORY_DIR= /" echo " TRACKER_NOTE= / SURFACE_COUNTS= / PROJECT_NAME= for pass-back to emit" - echo " emit write the 4 owned paths under $TARGET, staged (other files in that dir survive);" + echo " emit write the 5 owned paths under $TARGET, staged (other files in that dir survive);" echo " refuses over a live install (MEMORY_SYNC_FORCE=1 overrides) and over a PARKED one" echo " (no override - run \`enable\`, \`uninstall\` or \`purge\` first)" echo " validate fail on unresolved {PLACEHOLDER}, missing file, broken reference, missing/stale provenance frontmatter" diff --git a/brewdoc/skills/memory-sync-setup/tests/suite-emit-safety.mjs b/brewdoc/skills/memory-sync-setup/tests/suite-emit-safety.mjs index 2b09877..6c0cf23 100644 --- a/brewdoc/skills/memory-sync-setup/tests/suite-emit-safety.mjs +++ b/brewdoc/skills/memory-sync-setup/tests/suite-emit-safety.mjs @@ -4,7 +4,7 @@ * * Regression cover for the whole-directory replace on emit (`rm -rf $TARGET && mv`): * it deleted a PARKED SKILL.md.disabled and every file `uninstall` had just reported - * as KEPT. Emit owns exactly 4 paths (SKILL.md + 3 references) and must place them + * as KEPT. Emit owns exactly 5 paths (SKILL.md + 4 references) and must place them * individually; anything else in the skill dir belongs to the user. * * Each case runs the real generator as a child process against its own mkdtemp root @@ -60,7 +60,8 @@ const write = (p, body) => { mkdirSync(dirname(p), { recursive: true }); writeFi existsSync(join(root, SKILL, 'references/memory-guide.md')), existsSync(join(root, SKILL, 'references/agent-audit.md')), existsSync(join(root, SKILL, 'references/hard-sync.md')), - ], [true, true, true, true]); + existsSync(join(root, SKILL, 'references/prompting-guide.md')), + ], [true, true, true, true, true]); } // ── 2. uninstall KEEPS a user file, and the next emit must not undo that ─────── @@ -119,7 +120,7 @@ const write = (p, body) => { mkdirSync(dirname(p), { recursive: true }); writeFi check('FORCE emit overwrote the hand-edit', read(skill).includes('HAND EDIT'), false); } -// ── 5. a FORCE emit still owns only its own 4 paths ──────────────────────────── +// ── 5. a FORCE emit still owns only its own 5 paths ──────────────────────────── { const root = project('force-keeps-foreign'); run(root, 'emit'); diff --git a/brewtools/.claude-plugin/plugin.json b/brewtools/.claude-plugin/plugin.json index acf4bd4..6f551b3 100644 --- a/brewtools/.claude-plugin/plugin.json +++ b/brewtools/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "brewtools", - "version": "6.1.4", + "version": "6.2.0", "description": "Brewtools - universal utilities for Claude Code: text optimization, humanization, secrets scanning", "author": { "name": "Maksim Kochetkov", diff --git a/brewtools/.codex/skills/task-board-setup/references/07-agents-md-optimize.md b/brewtools/.codex/skills/task-board-setup/references/07-agents-md-optimize.md index 4ba97cd..8672c4e 100644 --- a/brewtools/.codex/skills/task-board-setup/references/07-agents-md-optimize.md +++ b/brewtools/.codex/skills/task-board-setup/references/07-agents-md-optimize.md @@ -6,7 +6,7 @@ OPTIONAL, opt-in phase. PROPOSE-ONLY: every change is gated behind request_user_ > **Verified lazy-loading mechanic (source: developers.openai.com/codex/guides/agents-md, fetched 2026-06-14).** Bake this into every proposal rationale: > - Root CMD + all ancestor AGENTS.md/AGENTS.local.md: **loaded in full AT LAUNCH**, every session, regardless of length. -> - Subdirectory (nested) AGENTS.md: **NOT loaded at launch -- loaded ON-DEMAND when Codex reads a file in that subtree.** +> - Subdirectory (nested) AGENTS.md: **NOT on-demand -- concatenated ONCE at session start.** Codex walks git root -> CWD and concatenates every AGENTS.md it finds into one instruction chain, capped by `project_doc_max_bytes` (32 KiB default; later/nested files are dropped first over the cap); a nested AGENTS.md wins for its own subtree only because it sits later in that one concatenation (verified: https://developers.openai.com/codex/guides/agents-md, 2026-09-12). > - `@path` imports: **EAGER -- expanded into context at launch.** They help organization but do NOT reduce root context. > - `.codex/rules/*.md` with `paths:` FM: on-demand when matching files are touched; without `paths:`: at launch. > CONSEQUENCE: to shrink always-on context, push MOD detail into a NESTED MODCMD. NEVER use `@import` for that goal (eager = no savings). This is the justification stated to the user in the module-split proposal. @@ -119,7 +119,7 @@ If NOT OVER: state it's within budget; offer optional tidy (markup pass 5g) but If OVER: assemble a concrete decomposition PLAN combining 5d (module split), 5e (rules dedup), 5f (compress), then AskUser ONCE with the whole plan before applying any of it: > **AGENTS.md is lines (over the ceiling; optimal ~).** Proposed decomposition to get back under budget: -> 1. Move detail for modules `` into per-module AGENTS.md (loaded on-demand, shrinks always-on context). Root keeps a 2-line module index. [est -X lines] +> 1. Move detail for modules `` into per-module AGENTS.md (it overrides root for that subtree and keeps root short so `project_doc_max_bytes` never truncates it). Root keeps a 2-line module index. [est -X lines] > 2. Move topic blocks `<...>` into path-scoped `.codex/rules/*.md` (load only when matching files are touched). [est -Y lines] > 3. Dedup overlap with existing rules `<...>`; delete duplicated spans. [est -Z lines] > 4. Deep-compress the remainder via brewtools:text-optimize. [est -W lines] @@ -136,17 +136,17 @@ Apply ONLY approved steps. Each sub-step (5d/5e/5f) below still narrates what it For each approved MOD in MODULES: 1. Gather the CMD content that is module-specific (build/test cmds, layout, conventions for that subtree). -2. Write/extend `/AGENTS.md` (a NESTED file -- this is what gives on-demand loading). If `has_own_cmd`, MERGE (Edit), do not clobber. Improve markup (headers, tables, bullets). +2. Write/extend `/AGENTS.md` (a NESTED file -- this is what makes it override the root for that subtree). If `has_own_cmd`, MERGE (Edit), do not clobber. Improve markup (headers, tables, bullets). 3. In the ROOT CMD, REPLACE the moved block with a MAX-COMPRESSED index: a couple of lines, e.g.: ``` - ## Modules (each has its own AGENTS.md, loaded on-demand when you work in it) + ## Modules (each has its own AGENTS.md, which overrides this file for that subtree) | Module | Path | Owns | |--------|------|------| | api | services/api/ | handlers, OpenAPI, db migrations | | web | apps/web/ | UI, build, e2e | ``` Keep ONLY the index in root; the detail lives in the MODCMD. -> Rationale to state in the proposal: nested AGENTS.md loads ONLY when Codex touches that subtree, so module detail leaves the always-on root context. Do NOT use `@import` here -- imports are eager and would not save context. +> Rationale to state in the proposal: a nested AGENTS.md overrides root for its own subtree (both are concatenated at session start, nested last, so nested wins) and keeps root short so `project_doc_max_bytes` never truncates it. Codex has no `@import`-style eager-include mechanism at all, so that concern does not apply here. > Do NOT move CROSS-cutting / repo-wide rules into a single module; those stay in root or go to a `.codex/rules/*.md`. --- diff --git a/brewtools/.codex/skills/text-optimize/SKILL.md b/brewtools/.codex/skills/text-optimize/SKILL.md index de5c0eb..d51f5b6 100644 --- a/brewtools/.codex/skills/text-optimize/SKILL.md +++ b/brewtools/.codex/skills/text-optimize/SKILL.md @@ -120,6 +120,7 @@ When no flag provided AND input suggests compression (not just optimization): | Perception | P.1-P.6 | Examples near rules, hierarchy, bold keywords, standard symbols, instruction order, default over options | | LLM Comprehension | L.1-L.8 | Critical info position, documents-first, conciseness, quote-first, add WHY, reiterate constraint, prompt repetition, preserve scope qualifiers | | Aggressive lossy | A.1-A.4 | Line fusion, word drop, paraphrase, known-fact elision (deep/max) | +| Prompt quality | PQ.1-PQ.13 | Role-first return contract, dedupe repeats, positive imperative (incident-tied `!=` kept), one hard-stop cap, drop step-by-step/verify filler, explicit scope, table-vs-procedure shape, example over adjective, DICT threshold gate — prompt-shaped content (system prompt/AGENTS.md/agent def/skill doc), Medium+ only | > Full per-ID definitions live in `references/rules-review.md` (loaded at Step 0) — do not restate them here. @@ -128,12 +129,13 @@ When no flag provided AND input suggests compression (not just optimization): | Mode | Applies | Notes | |------|---------|-------| | Light | C.1-C.8, T.6, D.1, R.1-R.3, P.1-P.4, L.1-L.8 | Text cleanup + exact-dup removal — no restructuring | -| Medium | All rules (C + T + S + D + R + P + L) | Balanced transformations | -| Standard | All rules (C + T + S + D + R + P + L) + `references/standard-compression.md` | 30-50% compression, human-readable, 1 verification round | -| Deep | All rules (C + T + S + D + R + P + L) + A.1-A.4 + `references/deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds (conditional) | -| Max | All rules (C + T + S + D + R + P + L) + A.1-A.4 + `references/deep-compression.md` + `references/max-compression.md` | Atomic fact-lines, ASCII operators, format-aware tables, 4 mandatory guardrails, 2 verification rounds | +| Medium | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) | Balanced transformations | +| Standard | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + `references/standard-compression.md` | 30-50% compression, human-readable, 1 verification round | +| Deep | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + A.1-A.4 + `references/deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds (conditional) | +| Max | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + A.1-A.4 + `references/deep-compression.md` + `references/max-compression.md` | Atomic fact-lines, ASCII operators, format-aware tables, 4 mandatory guardrails, 2 verification rounds | > D.5 (cross-file dedup) applies in ANY mode when processing multiple files or a folder. D.6 wrong-merge guard is mandatory wherever D.2/D.3/D.5 run. +> PQ (prompt-quality rewrite) applies at Medium mode and above, only when content type is a prompt-shaped target (system prompt/AGENTS.md/agent def/skill doc) — never Light, never generic docs/README. ### D.5 is decided by the orchestrator, never by a per-file agent @@ -424,6 +426,7 @@ the user can diff or delete it. | Snapshot first | No edit without a Phase 0 snapshot on disk and a clean tree over the targets. `!=` editing straight from the prompt | | Refuse, don't warn | A failed sub-gate restores the original bytes. A lossy file is never left in place with a warning attached | | Preserve | Names, numbers, dates, URLs, file paths, versions, ports, sizes | +| Preserve | CLI flags/options verbatim; model IDs byte-exact; thresholds/gates/percentages exactly as stated | | Preserve | Negative rule semantics (`!=` notation in deep mode) | | Preserve | At least one example per rule with examples | | Preserve | Scope qualifiers ("every section, not just the first") — high-reasoning model 4.8 literalism (Max/Deep) | diff --git a/brewtools/.codex/skills/text-optimize/references/deep-compression.md b/brewtools/.codex/skills/text-optimize/references/deep-compression.md index 7fc8670..7b48d65 100644 --- a/brewtools/.codex/skills/text-optimize/references/deep-compression.md +++ b/brewtools/.codex/skills/text-optimize/references/deep-compression.md @@ -65,48 +65,31 @@ Status emoji cost 2-4 tokens each (measured): `✅`/`❌` = 2-3 tok, `ℹ️` = ## Dictionary Format -Place DICT header at document start when terms appear 3+ times: - -``` -[DICT: CC=Codex, KB=knowledge base, SP=system prompt, ...] -``` - -Rules: -- Terms appearing 3+ times → dictionary entry -- Max 20 entries -- Sort alphabetically -- Place before first content line -- Use abbreviation from DICT throughout document +Place `[DICT: CC=Codex, KB=knowledge base, SP=system prompt, ...]` at document start when +terms appear 3+ times. Rules: max 20 entries, sort alphabetically, place before the first content +line, use the abbreviation from DICT throughout — a term used <3x stays inline (rules-review.md R13: +DICT pays only on a long, repetition-heavy file, not a short one). ## Filler Words & Phrases to Remove -Apply filler removal from `rules-review.md` rule T.6. Additional deep-mode removals: - -| Pattern | Action | -|---------|--------| -| Articles (the/a/an) | Remove when meaning clear without them | -| Relative clauses ("which is", "that are") | Remove or restructure | -| Hedging ("might", "possibly", "could potentially") | Remove — use direct statements | +Beyond `rules-review.md` T.6: drop articles (the/a/an) when meaning survives without them, relative +clauses ("which is", "that are"), and hedging ("might", "possibly", "could potentially") — state +direct facts instead. ## Structural Compression Patterns -- Conditionals: `if X → Y` or `X ? Y : Z` -- Prohibitions: `!=X bc Y` (must not X because Y) -- Lists: inline comma-separated when items are short -- Tables: for multi-attribute data -- Merge related one-liners into single line with `|` separator +- Conditionals: `if X -> Y` or `X ? Y : Z` | prohibitions: `!=X bc Y` (must not X because Y) +- Lists: inline comma-separated when items are short | tables: for multi-attribute data +- Merge related one-liners into a single line with `|` separators - Remove markdown formatting that doesn't aid parsing (bold, italic in tables) -- Headers: flatten to 2 levels max -- Remove blank lines between items in lists/tables +- Headers: flatten to 2 levels max | remove blank lines between list/table items ## Redundancy Factoring Run dedup pass (D.1-D.6, rules-review.md) BEFORE symbol substitution — merging first shrinks the text remaining passes must process and keeps verification cheap. Record merges in a dedup ledger (kept <- dropped). -- Phrase-DICT: recurring phrase >= 3 words appearing 2+ times -> DICT entry (counts toward the 20-entry cap). Source: CompactPrompt arXiv:2510.18043 -- Path-prefix hoisting: repeated path/URL prefixes -> single DICT entry (e.g. `[DICT: SR=src/main/resources]`) -- Header echo removal: subsection headers repeating parent header words -> drop the echo ("## Server Config / ### Server Config Ports" -> "### Ports") -- Number/unit normalization: "approximately 30 percent" -> `~30%`; "greater than or equal to 21" -> `>=21` +- Phrase-DICT (recurring phrase >=3 words, 2+ times -> DICT entry, counts toward the 20-cap; CompactPrompt arXiv:2510.18043) | path-prefix hoisting (repeated path/URL prefix -> one DICT entry, e.g. `[DICT: SR=src/main/resources]`) +- Header-echo removal (drop repeated parent words: "## Server Config / ### Server Config Ports" -> "### Ports") | number/unit normalization ("approximately 30 percent" -> `~30%`, "greater than or equal to 21" -> `>=21`) ## Token-Class Keep/Drop Heuristics @@ -120,58 +103,56 @@ Never drop negations or scope qualifiers (L.8; max-mode guardrail C2). ## Aggressive Lossy Techniques (A.1-A.4) -Deep/max only. Rule definitions: rules-review.md category A. Application order: - -dedup (D.1-D.6) -> line fusion (A.1) -> paraphrase (A.3) -> word drop (A.2) -> knowledge elision (A.4) -> symbol substitution - -Loss ledger REQUIRED: every A.2/A.4 drop recorded as `dropped -> reason`, listed in the report. A.4 elisions count against the fact-level loss budget (deep gate >= 95%) as `elided-known`; A.2 is word-level and gate-neutral — ledgered for transparency, no direct gate impact, but if a drop degrades a fact's meaning the verifier labels that fact `distorted` (normal gate impact). A.1/A.3 results count as preserved (kept/merged), no ledger entry. Guards: never drop negations, numbers, named entities, scope qualifiers (L.8, C2); D.6 wrong-merge guard applies before A.1 fusion; unsure whether A.4 knowledge is generic -> keep. +Deep/max only. Full rule + ledger semantics live in `rules-review.md` category A (Step 0, always +loaded) — do not restate here. Application order: dedup (D.1-D.6) -> A.1 fusion -> A.3 paraphrase -> +A.2 word drop -> A.4 elision -> symbol substitution. ### Example: A.1 fusion + A.3 paraphrase (loss-free) - -**Original**: -> The deployment script should be executed from the project root directory. In the event that the script fails, you can check the log file which is located at `logs/deploy.log`. - -**Compressed**: -> run deploy script from project root | fail -> check `logs/deploy.log` +> "The deployment script should be executed from the project root directory. In the event that the script fails, you can check the log file which is located at `logs/deploy.log`." -> "run deploy script from project root | fail -> check `logs/deploy.log`" ### Example: A.4 elision, project delta kept +> "Always write unit tests for new code, since testing catches regressions early. Keep functions small and readable. The project coverage gate is 85% (jacoco); builds fail below it." -> "coverage gate 85% (jacoco), build fails below" -**Original**: -> Always write unit tests for new code, since testing catches regressions early. Keep functions small and readable. The project coverage gate is 85% (jacoco); builds fail below it. - -**Compressed**: -> coverage gate 85% (jacoco), build fails below - -Ledger: dropped "write unit tests / catches regressions" -> generic LLM knowledge; dropped "keep functions small" -> generic. Kept: 85%, jacoco, build-fail behavior (project-specific). +Ledger: dropped "write unit tests / catches regressions" + "keep functions small" -> generic LLM knowledge. Kept: 85%, jacoco, build-fail behavior (project-specific). ## Iron Rules -Preserve in ALL cases regardless of compression level: +Preserve in ALL cases regardless of compression level — the lossless guard, never paraphrased, +rounded, or dropped: - Names, numbers, dates, URLs, file paths, versions, ports, sizes -- Negative rule semantics (use `!=` notation) -- At least one example per rule that originally has examples +- CLI flags/options verbatim (`-x`, `--max`); model IDs byte-exact (`claude-balanced model-5`, never "balanced model 5") +- Thresholds, gates, percentages exactly as stated (`>=95%`, `~20%` ceiling) — never rounded +- Negative rule semantics (use `!=` notation) | >=1 example per rule that originally had examples - DICT header at document start - Dedup ledger: every merged pair recorded (kept <- dropped); merged facts count as preserved in verification - Loss ledger: every A.2/A.4 drop recorded (dropped -> reason); never elide project-specific facts (names, numbers, paths, versions, prohibitions) +## Stop Condition + +Stop the A.1-A.4 pass the instant one of these trips — patch back, never push further: +- A.2 would touch a noun, numeral, negation or named entity (Token-Class Heuristics above already forbid it — this is the enforcement trigger) +- An A.4 candidate is not clearly generic training-knowledge (unsure -> keep, per the A.4 rule itself) +- DICT header would exceed 20 entries, or would cover a term used <3x +- The dedup/loss ledger can no longer account for every merge and drop 1:1 + ## Before/After Examples ### Example 1 — Prose Instruction -**Original** (~60 words): +**Original** (46 words): > Please note that when you are working with the database connection, it is important to make sure that you close the connection after you are done with it. Failure to do so can result in connection pool exhaustion, which may lead to the application becoming unresponsive. -**Compressed** (~15 words): +**Compressed** (13 words): > DB conn: close after use bc unclosed -> pool exhaustion -> app unresponsive ### Example 2 — Rule Block with DICT -**Original** (~90 words): +**Original** (82 words): > ## File Handling Rules > > When working with temporary files in the build directory, you should always use the project's file utility library. It is important to note that temporary files must be cleaned up after the build process completes. You must not write temporary files to the source directory because it can corrupt the version control state. The file utility library provides a `cleanup()` method that should be called in the finally block. All temporary files should use the `.tmp` extension. -**Compressed** (~35 words): +**Compressed** (35 words incl. DICT header, 27 body): > [DICT: TF=temporary files, FUL=file utility lib, BD=build dir] > > ## File Handling @@ -180,12 +161,12 @@ Preserve in ALL cases regardless of compression level: ### Example 3 — Configuration Section -**Original** (~70 words): +**Original** (56 words): > ## Server Configuration > > The application server runs on port 8443 with TLS enabled. The configuration file is located at `/etc/myapp/server.yml`. The minimum required version is Java 21. The maximum heap size should be set to 4096MB for production environments. Health check endpoint is available at `https://localhost:8443/health`. The connection timeout is 30 seconds and the read timeout is 60 seconds. -**Compressed** (~40 words): +**Compressed** (25 words): > ## Server Config > Port: 8443 (TLS) | cfg: `/etc/myapp/server.yml` | Java >= 21 > Heap max: 4096MB (prod) | health: `https://localhost:8443/health` @@ -193,14 +174,27 @@ Preserve in ALL cases regardless of compression level: ### Example 4 — Negative Rules -**Original** (~80 words): +**Original** (64 words): > ## Security Rules > > You must never store passwords in plain text in the configuration files. API keys should not be committed to the repository under any circumstances. It is important to make sure that you do not log sensitive information such as tokens or credentials at any log level. You should not disable TLS certificate verification in production environments because it exposes the application to man-in-the-middle attacks. -**Compressed** (~30 words): +**Compressed** (29 words): > ## Security > !=plaintext passwords in cfg files > !=API keys in repo > !=log sensitive data (tokens, credentials) @ any log level > !=disable TLS cert verification in prod bc MITM exposure + +### Measured (this file's own examples, `wc -w`) + +| Example | Original | Compressed | Reduction | Ratio | +|---------|----------|------------|-----------|-------| +| 1 — Prose Instruction | 46 | 13 | -71.7% | 3.54x | +| 2 — Rule Block + DICT | 82 | 27 (35 w/ DICT) | -67.1% | 3.04x | +| 3 — Config Section | 56 | 25 | -55.4% | 2.24x | +| 4 — Negative Rules | 64 | 29 | -54.7% | 2.21x | +| Total | 248 | 94 | -62.1% | 2.64x | + +Deep mode's "2-3x" target above is this file's own measured spread (2.2x-3.5x, combined 2.64x) on +dense rule prose, not an invented number — re-measure with `wc -w` before claiming a new ratio. diff --git a/brewtools/.codex/skills/text-optimize/references/max-compression.md b/brewtools/.codex/skills/text-optimize/references/max-compression.md index 1ea84eb..38603b2 100644 --- a/brewtools/.codex/skills/text-optimize/references/max-compression.md +++ b/brewtools/.codex/skills/text-optimize/references/max-compression.md @@ -22,6 +22,11 @@ Rules: > build runs unit tests after compile > test fail -> pipeline stops + artifact !=published +**Measured** (`wc -w`): before 31 words, after 23 words = -25.8% by WORD count only — atomic-fact +style forbids pronouns, so "build" repeats 3x. The paper's ~50% figure above is TOKEN reduction: a +repeated short noun costs less than the pronoun+clause structure it replaces. Word count and token +count diverge here — judge B1 by an actual token estimate, not `wc -w`, whenever a noun repeats. + ## ASCII Operator Dialect (A1 — CRITICAL) Prefer ASCII digraphs over unicode glyphs. Measured token cost (tiktoken cl100k/o200k, live): @@ -69,6 +74,9 @@ CONDITIONAL: > 1 ann admin > 2 bob user +**Measured** (`wc -w`): before 26 words / 5 lines, after 12 words / 3 lines = -53.8% — a real win on +both words and tokens (pipe alignment and separator rows carry no data). + ## Chain-of-Density Final Pass (B4) Source: arXiv:2309.04269. After all compression passes, run 1-3 rewrite iterations at FIXED length: each pass fuses 1-3 missing entities from the original back in WITHOUT growing the text (~3 iterations reach human-preferred density). Use to repair entity loss found by verification instead of re-inflating. @@ -86,17 +94,22 @@ These CAP the aggression. Sources: Anthropic context-engineering blog; Anthropic ## Iron Rules (inherited + max-specific) -Inherits ALL `deep-compression.md` iron rules: -- Preserve names, numbers, dates, URLs, file paths, versions, ports, sizes -- DICT header @ document start (terms 3+ times) -- >= 1 example per rule that originally had examples - -Max adds: +Inherits ALL of `deep-compression.md` Iron Rules (the lossless guard) unchanged — do not restate the +list here, re-read it there. Max adds: - Scope qualifiers preserved verbatim (C2) - 2 mandatory verification rounds, independent methods: claim inventory + self-QA probe (never optional) - Semantic match must be >= 95% -> else warn user with loss list - 100% sub-gate: numbers, names, negations, scope qualifiers +## Stop Condition + +Stop pushing max-mode density the instant one of these trips — patch via Chain-of-Density (B4) +instead of deleting further: +- The 20% deletion ceiling (C3) would be crossed +- A B1 atomic line would need a cross-line pronoun to stay readable (the decomposition is now lossy) +- Round 2 self-QA misses a number, name, negation or scope qualifier (100% sub-gate) +- The next fusion pass has no missing entity left to restore (B4 has converged) — ship, do not chase a fixed multiplier past this point + ## Verification (2 rounds, mandatory, INDEPENDENT methods) Never silently ship lossy max output. Two rounds use DIFFERENT methods — they catch different losses. diff --git a/brewtools/.codex/skills/text-optimize/references/rules-review.md b/brewtools/.codex/skills/text-optimize/references/rules-review.md index d1ddb1b..c1174e0 100644 --- a/brewtools/.codex/skills/text-optimize/references/rules-review.md +++ b/brewtools/.codex/skills/text-optimize/references/rules-review.md @@ -108,6 +108,35 @@ Examples (before -> after): - A.3: "In the event that the build process does not complete successfully, notify the team" -> "build fails -> notify team" - A.4: "Write unit tests for new code, tests catch regressions. Coverage gate is 85% (jacoco); build fails below." -> "coverage gate 85% (jacoco), build fails below" (generic "write tests" elided -> ledger; project delta kept) +**Lossless guard (any mode, A.2/A.4 never target these):** numbers, dates, versions, model IDs +byte-exact (`claude-balanced model-5`, never "balanced model 5"), CLI flags/options verbatim (`-x`, `--max`), +thresholds/gates/percentages exactly as stated (`>=95%`, `~20%` ceiling), URLs, file paths, ports, +sizes, named entities, negations (`!=`/NEVER/MUST NOT), scope qualifiers (L.8). A drop that touches +any of these is not A.2/A.4 — it is a defect, caught by the 100% sub-gate. + +## PQ - Prompt-Quality Rewrite (digest of `.codex/reports/20260912-173000_agents-refresh/prompting-rules.md` R1-R16) + +Separate from the 52 numbered rules above (still 8 categories, unchanged count) — a rewrite pass for +prompt-shaped targets (system prompt, agent `.md`, skill `SKILL.md`, hook prompt text, AGENTS.md), +applied Medium mode and above (never Light — Light stays wording-only, no restructuring). Stays +lossless per the guard above: R1-R16 govern SHAPE and emphasis, never facts. + +| ID | Source | Transformation | Bad -> Good | +|----|--------|-----------------|-------------| +| PQ.1 | R1, R15 | Role in one sentence, Return contract next, Scope/Never after — before procedure detail | Role buried after 3 paragraphs of scope -> "You are a code reviewer. Return: findings list, `path:line`, verdict first." then Scope/Never as its own heading | +| PQ.2 | R2 | Same instruction stated once; delete a cross-section repeat | "Never invent scope" stated, then restated 2 sentences later in other words -> keep the sharper phrasing once | +| PQ.3 | R3 | Prohibition -> positive imperative, UNLESS the `!=`/NEVER guards a named, previously-observed failure | "Do not use markdown" -> "Write in flowing prose". Keep verbatim: `!=re-run text-guard.sh` (BT-F15 regression) | +| PQ.4 | R4 | Drop scattered ALL-CAPS; keep exactly one true hard-stop (irreversible action) in caps, lower the rest | 4x MUST/CRITICAL in one file -> 1 STOP on the irreversible action (e.g. edit-without-snapshot), 3 become plain imperative | +| PQ.5 | R5 | No "think step by step" or scripted step-lists for thinking-enabled models; no bare "verify"/"be careful" filler | "Think step by step and double-check" -> delete, or state the goal only | +| PQ.6 | R6 (high-reasoning model-5-specific; apply cautiously to balanced model/Fable) | Drop carried-over generic self-verification instructions; keep verification that is a specific, gated protocol with real thresholds | "Double-check your output before returning" (generic) -> delete. A named gate (`>=95% match, 100% sub-gate`) is not this pattern — keep it | +| PQ.7 | R7 | An agent that itself delegates states an explicit delegate-only-when criterion, low spawn count | "delegate as needed" -> "delegate only for large independent parallelizable work; never to verify your own output" | +| PQ.8 | R8 | State scope explicitly; never rely on the model generalizing a rule to similar items | "apply this rule" -> "apply this rule to every file matching X, not just the first" | +| PQ.9 | R10 | Reference data (fields/flags/thresholds/model IDs) -> table. Real-dependency procedure -> numbered steps. Never mix the two shapes | A flag/target matrix written as prose -> table; a create-in-order procedure kept as numbered prose, not flattened into a table | +| PQ.10 | R11 | A concrete example or named reference file beats an adjective ("clean", "thorough", "professional") | "write clean code" -> "follow the pattern in `skill-creator.md`" or a 2-line before/after | +| PQ.11 | R13 | `[DICT: ...]` header only when it pays: >=5 distinct abbreviations, each reused >=3x, file itself hundreds of lines | A 150-line agent body with 3 abbreviations used twice each -> no DICT header, inline the 3 terms | +| PQ.12 | R14 (recommendation, not a mandate — verify against the specific hook's own miss-rate first) | Recurring reminder text: once at session-start/compaction, throttle or drop the per-turn copy | A reminder injected on every `UserPromptSubmit` AND at session-start/after-compaction -> keep the structural-checkpoint copies, narrow the per-turn one | +| PQ.13 | R16 | State "run independent tool calls in parallel" once per artifact, never per section | 3 sections each repeating the parallel-call instruction -> state it once, delete the other 2 | + ## Rules NOT Recommended | Avoid | Reality | diff --git a/brewtools/.codex/skills/text-optimize/references/standard-compression.md b/brewtools/.codex/skills/text-optimize/references/standard-compression.md index 4a4d67f..81444a2 100644 --- a/brewtools/.codex/skills/text-optimize/references/standard-compression.md +++ b/brewtools/.codex/skills/text-optimize/references/standard-compression.md @@ -68,8 +68,8 @@ After compression, verify: - API signatures and parameters - Error messages (exact text matters) - Legal/compliance text -- Version numbers, dates, URLs -- Command-line examples +- Version numbers, dates, URLs, model IDs (byte-exact) +- Command-line examples, CLI flags/options, thresholds and gates (`>=98%`, `30-50%`) verbatim > **Note:** Never convert config blocks to TOML for "efficiency" — TOML is the most token-heavy structured format (more overhead than YAML/JSON). For uniform tabular data prefer markdown tables or TSV/CSV; for nested data prefer compact JSON. @@ -77,12 +77,15 @@ After compression, verify: ### Example 1: README Intro -**Before** (~80 words): +**Before** (59 words): > This project is a command-line tool that is able to help developers in order to automate the process of deploying their applications. It is important to note that the tool supports a large number of cloud providers. Due to the fact that deployment can be complex, this tool simplifies it for the purpose of reducing errors and saving time. -**After** (~35 words): +**After** (19 words): > CLI tool that automates application deployment. Supports many cloud providers. Simplifies complex deployments to reduce errors and save time. +Measured (`wc -w`): 59 -> 19 = -67.8%, above the 30-50% default target — short, filler-heavy prose +can legitimately land higher. Treat 30-50% as the safe default for typical docs, not a ceiling. + ### Example 2: Installation Instructions **Before**: @@ -107,3 +110,11 @@ After compression, verify: | Projects | 3 | Unlimited | Unlimited | | Storage | 1 GB | 50 GB | 500 GB | | Support | Community | Email | Dedicated | + +## 8. Stop Condition + +Stop compressing the moment: the next cut would touch a name/number/path/version/flag (lossless +guard, `rules-review.md`); a paragraph-to-table conversion would need to invent a category the +source never stated; or a further sentence merge would combine facts with different scope/numbers/ +conditions (D.6). The 30-50% target is done at that point even if the actual ratio lands outside it +either way — report the real number, never force one to fit the range. diff --git a/brewtools/.codex/skills/think-short-setup/assets/think-short-prompt-counter.mjs b/brewtools/.codex/skills/think-short-setup/assets/think-short-prompt-counter.mjs index 33993c0..f09db95 100644 --- a/brewtools/.codex/skills/think-short-setup/assets/think-short-prompt-counter.mjs +++ b/brewtools/.codex/skills/think-short-setup/assets/think-short-prompt-counter.mjs @@ -1,12 +1,12 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:think-short-setup +// brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewtools:think-short-setup /** * think-short — UserPromptSubmit hook (self-contained, no plugin-root deps). * * Maintains a per-session prompt counter (marker keyed by session_id, inside a * private 0700 tmp dir we own — a planted symlink is rejected, never followed). * Increments on every user prompt; re-injects the full think-short prompt - * ONLY every 10th prompt (10, 20, 30, ...). Never on the 1st prompt + * ONLY every 20th prompt (20, 40, 60, ...). Never on the 1st prompt * (SessionStart already injected the prompt at session open). * * Inject channel: hookSpecificOutput.additionalContext (UserPromptSubmit). @@ -24,7 +24,7 @@ const HERE = path.dirname(fileURLToPath(import.meta.url)); const PROMPT_PATH = path.join(HERE, 'think-short-prompt.md'); const MARKER_DIR = path.join(os.tmpdir(), 'brewtools-think-short'); const UID = typeof process.getuid === 'function' ? process.getuid() : null; -const INTERVAL = 5; +const INTERVAL = 20; let markerDirOk; diff --git a/brewtools/.codex/skills/think-short-setup/assets/think-short-session.mjs b/brewtools/.codex/skills/think-short-setup/assets/think-short-session.mjs index 9aaa7dd..e969a47 100644 --- a/brewtools/.codex/skills/think-short-setup/assets/think-short-session.mjs +++ b/brewtools/.codex/skills/think-short-setup/assets/think-short-session.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:think-short-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewtools:think-short-setup /** * think-short — SessionStart hook (self-contained, no plugin-root deps). * diff --git a/brewtools/README.md b/brewtools/README.md index e8c44fa..41e3511 100644 --- a/brewtools/README.md +++ b/brewtools/README.md @@ -4,7 +4,7 @@ | Field | Value | |-------|-------| -| Version | 6.1.4 | +| Version | 6.2.0 | | Skills | 14 | | Agents | 3 | | Hooks | 2 | @@ -115,7 +115,7 @@ Setup skills all speak the same verbs: | [`/brewtools:manager-setup`](skills/manager-setup/README.md) | Manager mode: installs a hard delegation wall into this project and explains/customizes codewords `++m` (delegate-everything, plan-aware), `++a` (architecture-first), `++rr` (anti-regression review), `++r` (two-phase double-check). The codewords are hook-driven and fire whether or not the wall is installed; the wall itself is opt-in, per-project, and blocks main-session writes while subagents stay free | sonnet | `[status\|install\|upgrade\|enable\|disable\|uninstall\|purge] [level strict\|balanced] [edit] \| \| \| ` | | [`/brewtools:plugin-update`](skills/plugin-update/README.md) | Check/install/update brewcode plugins | sonnet | `[check\|update\|all]` | | [`/brewtools:provider-switch`](skills/provider-switch/README.md) | Configure alt API providers: DeepSeek, Z.ai/GLM, Qwen, MiniMax, OpenRouter | opus | `[status\|install\|verify\|model-check\|help\|]` -- no args = interactive status check | -| [`/brewtools:think-short-setup`](skills/think-short-setup/README.md) | Install/remove terse-mode hooks (SessionStart + every-10th UserPromptSubmit + subagent Task) that inject brevity directives; project or global. `disable` flips a flag and leaves the files in place; `purge` deletes files and state | sonnet | `[status\|install\|upgrade\|enable\|disable\|uninstall\|purge] [project\|global] \| free-text intent` | +| [`/brewtools:think-short-setup`](skills/think-short-setup/README.md) | Install/remove terse-mode hooks (SessionStart + every-20th UserPromptSubmit + subagent Task) that inject brevity directives; project or global. `disable` flips a flag and leaves the files in place; `purge` deletes files and state | sonnet | `[status\|install\|upgrade\|enable\|disable\|uninstall\|purge] [project\|global] \| free-text intent` | | [`/brewtools:agent-deadline-setup`](skills/agent-deadline-setup/README.md) | Install/remove a soft wall-clock budget for subagents: 80% -- non-blocking "wrap up" warning, 100% -- deny all tools except the finalization set; project or global, opt-in | sonnet | `[status\|install\|upgrade\|enable\|disable\|uninstall\|purge] [project\|global] [minutes] \| free-text intent` | | [`/brewtools:agent-return-setup`](skills/agent-return-setup/README.md) | Install/remove a size budget on every subagent's final return message: a SubagentStart hook injects the contract, a SubagentStop hook sizes the return (`chars/4`) and blocks at most once -- above `passTokens` (default 1000) it orders a compress, above `fileTokens` (default 2500) a write-to-file plus the path. No LLM judge; project or global, opt-in | sonnet | `[status\|install\|upgrade\|enable\|disable\|uninstall\|purge] [project\|global] [pass] [file] \| free-text intent` | | [`/brewtools:agent-router-setup`](skills/agent-router-setup/README.md) | EXPERIMENTAL. Install/remove a PreToolUse hook that denies a generic subagent spawn in favor of the real project/plugin expert, or nudges when the fit is only uncertain; tier 1 free and deterministic, tier 2 opt-in LLM judge not yet behaviorally verified; project scope only | sonnet | `[status\|install\|upgrade\|enable\|disable\|uninstall\|purge] [level fast\|strict] \| free-text intent` | diff --git a/brewtools/agents/deploy-admin.md b/brewtools/agents/deploy-admin.md index 653c044..8b93f8c 100644 --- a/brewtools/agents/deploy-admin.md +++ b/brewtools/agents/deploy-admin.md @@ -5,49 +5,43 @@ model: inherit maxTurns: 80 tools: Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewtools" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- # Deploy Admin -**Role:** GitHub Actions and deployment agent — manages workflows, releases, GHCR, CI/CD, semver, deployment tracking. -**Scope:** Full access for read/probe work. Destructive/privilege operations are never self-approved — they leave this agent as `## APPROVAL REQUIRED` envelopes, or arrive pre-approved in the prompt (see Approval Contract). +GitHub Actions and deployment agent: workflows, releases, GHCR, CI/CD, semver, deployment tracking — full access for read/probe work; never self-approves a destructive or privilege operation (see Approval Contract). Project inventory (GitHub config, workflows, server targets, secret names) is not baked into this file — read it from `CLAUDE.local.md` at task start (see Project Config). -> Project inventory (GitHub config, workflows, server targets, secret names) is NOT baked into -> this file — read it from `CLAUDE.local.md` at task start. See the sections below. +## Return Contract -## Scope guard +Verdict first, <=30 lines, `path:line` — !=workflow YAML bodies, !=`gh run` logs, !=changelog text, !=preamble, whether or not a return guard is installed. A run is cited by its URL, never by its log. -Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, -~10 steps) or spans several independent deliverables — STOP, do not start. Return a -split proposal: 2-N bounded subtasks, each with scope and a suggested owner. +```markdown +`owner/repo` — [task] — success / partial / failed — highest level: [SERVICE] -A multi-repo / multi-environment / multi-service deployment MUST be split per target: one agent per repo, per environment, per service. Never one agent looping over all of them. +### Operations +1. `git add -- package.json && git commit -m "v1.2.3: ..." && git tag v1.2.3 && git push origin HEAD && git push origin refs/tags/v1.2.3` — ok (approved envelope 1) +2. `gh workflow run deploy.yml` — run https://github.com/OWNER/REPO/actions/runs/ID (green) -Mid-flight the same: stop at the next clean boundary and report done / remaining / -how to split. An hour of unsupervised work is a failure even when it succeeds. -Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the -result) or acceptance — state your assumption explicitly in the report, or ask once. -Never invent scope. -Deliver for the CONSUMER, not the literal wording: the result must be usable as-is -by whoever takes it next, with the whole briefed scope covered. +### Verification +CI green ✅ | release v1.2.3 published ✅ | live `/version` == tag ✅ | steps skipped: post-release hook (no script) +``` -## Checkpointing +Failure triage: the failing step + job name + the URL + the one error line from `gh run view --log-failed`. Full logs, long diffs, per-file version audits -> `.claude/reports/YYYYMMDD-HHMMSS_deploy/` (the checkpoint file is already there), return the path. +If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. -`maxTurns: 80` = anti-loop stop, != budget. On hit the run aborts and the final report is lost while -tags, pushes, releases stay applied -- an unlogged deploy step is the dangerous case. Append each -step (tag, push, run id, health/version gate) to `.claude/reports/YYYYMMDD-HHMMSS_deploy/report.md` -the moment it completes. On resume: read that file first, continue from the last step -- !=re-tag or -re-push what is already logged. +## Scope & Checkpoints -> Scope guard bounds what you take on; this bounds what survives an abort. +Exceeds one bounded unit (~5 files, ~10 steps) or spans independent deliverables — STOP before starting, return a split proposal instead (2-N bounded subtasks, scope + owner each). Multi-repo/environment/service deployments split per target: one agent per repo, per environment, per service, never one looping over all. Mid-flight: stop at the next clean boundary, report done/remaining/how to split — an hour of unsupervised work is a failure even when it succeeds. -## Plugin Root +Missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptance -> a stated assumption in the report, or one question; never invented scope. Deliver for the CONSUMER: usable as-is, covering the whole briefed scope. -Resolve plugin resource paths via `${CLAUDE_PLUGIN_ROOT}` (brace form, natively substituted at spawn to this plugin's root). Use it as the prefix for all plugin resource paths below. +`maxTurns: 80` is an anti-loop stop, not a budget. On hit the run aborts and the final report is lost while tags, pushes, releases stay applied — an unlogged deploy step is the dangerous case. Append each step (tag, push, run id, health/version gate) to `.claude/reports/YYYYMMDD-HHMMSS_deploy/report.md` on completion; on resume, read that file first and continue from the last step — never re-tag or re-push what is already logged. + +Resolve plugin resource paths via `${CLAUDE_PLUGIN_ROOT}` (brace form, natively substituted at spawn to this plugin's root) — prefix for every plugin resource path below. ## Safety Rules @@ -70,18 +64,16 @@ Resolve plugin resource paths via `${CLAUDE_PLUGIN_ROOT}` (brace form, natively | Multiple operations in one script | Highest level among all operations | | Draft release + undraft (`gh release edit --draft=false`) | SERVICE (publishes release) | -> "Envelope" = do not run it. Emit it under `## APPROVAL REQUIRED` per the Approval Contract below, unless the incoming prompt already carries `APPROVED:` for that exact command. +> Envelope = do not run; emit under `## APPROVAL REQUIRED` per Approval Contract below, unless the prompt already carries `APPROVED:` for that exact command. ## Approval Contract -A subagent cannot ask, confirm, or obtain approval mid-run — `AskUserQuestion` is stripped from every -subagent at runtime, even when its `tools:` field lists it (only a fork is exempt). -This agent therefore NEVER executes a destructive operation on its own judgement. +A subagent cannot ask, confirm, or obtain approval mid-run: `AskUserQuestion` is stripped from every +subagent at runtime, even when `tools:` lists it (only a fork is exempt) — so it never executes a +destructive operation on its own judgement. Instead it: -Instead it: - -1. Performs all non-destructive work and gathers full evidence. -2. Emits in its FINAL RETURN an `## APPROVAL REQUIRED` block, one envelope per destructive +1. Gathers full evidence through non-destructive work only. +2. Emits in its final return one `## APPROVAL REQUIRED` block, one envelope per destructive operation, ids `A1..AN`, fields exactly: ```markdown @@ -95,53 +87,37 @@ EVIDENCE: ``` -3. Stops, executing nothing in that block. Nothing destructive to report -> the literal line - `APPROVAL REQUIRED: none`. +3. Stops there, executing nothing in the block — nothing destructive to report becomes the literal + line `APPROVAL REQUIRED: none`. -The CALLER (main session, which does have `AskUserQuestion`) presents the envelope and, if approved, -either runs it or re-spawns this agent with `APPROVED: ` in the prompt. -**An explicit approval token in the incoming prompt is the ONLY authorization this agent may act on.** -`APPROVED:` covers only the envelope ids it names, exactly as worded — not a similar command, not a -broader scope, not a retry with different arguments. +The caller (main session, with `AskUserQuestion`) presents the envelope; if approved, it runs the +command or re-spawns this agent with `APPROVED: `. **An explicit approval token in the prompt +is the only authorization this agent may act on** — covering only the ids it names, exactly as +worded: never a similar command, a broader scope, or a different-argument retry. -**Destructive** = irreversible or affecting a remote/shared system: `rm`/`mv` over existing paths, +**Destructive** = irreversible or remote/shared-system-affecting: `rm`/`mv` over existing paths, force-push, tag delete, DB writes/migrations, service restart/stop, firewall/user/permission changes, secret rotation, deploy/rollback, `docker system prune`, any remote `ssh` mutation. -## GitHub Config +## Project Config -**On every task start:** Read `CLAUDE.local.md` in project root, section `## GitHub Config` -(owner, repo, registry, default branch). If missing, derive from -`gh repo view --json owner,name,defaultBranchRef` and carry the derived values into every envelope's -`HOST:` field — a derived target is never self-approved for a MODIFY+ operation. +Read once at task start, all from `CLAUDE.local.md` in the project root: -## Workflow Inventory +| Section | Holds | If missing | +|---------|-------|------------| +| `## GitHub Config` | owner, repo, registry, default branch | derive via `gh repo view --json owner,name,defaultBranchRef`, carrying the derived values into every envelope's `HOST:` field; a derived target is never self-approved for a MODIFY+ operation | +| `## Workflows:` | workflow inventory | discover with `ls .github/workflows/` + `gh workflow list`, then STOP and return the list as `## NEEDS-INPUT` — never guess a workflow to trigger | +| `## SSH Servers` | deploy hosts, users, keys, ports | if the task needs a server, STOP and return the gaps as `## NEEDS-INPUT` — never invent a host | -**On every task start:** Read `## Workflows:` in `CLAUDE.local.md`. If missing, discover with -`ls .github/workflows/` + `gh workflow list`, then STOP and return that list as `## NEEDS-INPUT` -so the caller names the target — never guess a workflow to trigger. - -## Server Targets - -**On every task start:** Read `## SSH Servers` in `CLAUDE.local.md` for deploy hosts, users, -keys and ports. If missing and the task needs a server, STOP and return the missing details as a -`## NEEDS-INPUT` block. Never invent a host. - -## Secrets - -**On every task start:** Get the names with `gh secret list` (READ level; requires admin — if -it fails, say so and continue without the list). `CLAUDE.local.md` may also record which secret -each workflow expects. - -> Names only. NEVER attempt to read, print, or log secret values. +Secret names: `gh secret list` (READ level; requires admin — if it fails, say so and continue without the list). `CLAUDE.local.md` may also record which secret each workflow expects. Names only — never read, print, or log a secret value. ## gh CLI Conventions - Releases: create with `--draft` first, publish separately via `gh release edit TAG --draft=false` (SERVICE level). - Secrets: set from file/stdin (`gh secret set NAME < FILE`) — never `--body "VALUE"`, it lands in shell history. -- Failure triage: `gh run view RUN_ID --log-failed` before rerunning; `gh run watch RUN_ID` to follow a live run. +- Triage: `gh run view RUN_ID --log-failed` before rerunning; `gh run watch RUN_ID` follows any live run. ## Release Flow @@ -153,14 +129,14 @@ ls .claude/scripts/*.sh 2>/dev/null; jq -r '.scripts // {} | keys[]' package.jso | Step | Command | Level | |------|---------|-------| -| 1. Bump version | project's own bump script if the probe found one; else edit the version files the project actually has (`package.json`, `pyproject.toml`, `gradle.properties`, `*/plugin.json`, ...). No script and no obvious file set → STOP, return the candidate file list as `## NEEDS-INPUT` | MODIFY | -| 2. Changelog | `git log --oneline vPREV..HEAD` → update the project's changelog file (`CHANGELOG.md` / `RELEASE-NOTES.md`), matching its existing heading style | MODIFY | -| 3-5. Release transaction | Steps 1-2 produce a proposal, not writes. Emit the envelope covering the whole transaction (`COMMAND:` = the chain below verbatim) and STOP. Under `APPROVED:` run it as ONE chain, never split across turns. `ROLLBACK:` must state the truth: the chain ends in two pushes, so `git reset --soft HEAD~1` + `git tag -d vX.Y.Z` only recover a failure BEFORE the first push — write them as `until pushed: ...; once pushed: NONE, the commit and tag are public, remedy is the next patch version` | SERVICE | -| 6. Post-release hook | project's own post-release script, if the probe found one. None → skip | SERVICE | -| 7. Verify CI | resolve the run for THIS commit, then watch it — never read the newest rows (see below) | READ | -| 8. Verify artifact | whatever this project publishes: `gh release view vX.Y.Z`, registry tag present, live `/version` == tag. No published artifact → skip | READ | +| 1. Bump version | project's bump script if the probe found one, else edit whatever version files exist (`package.json`, `pyproject.toml`, `gradle.properties`, `*/plugin.json`, ...); neither → STOP, return the candidate list as `## NEEDS-INPUT` | MODIFY | +| 2. Changelog | `git log --oneline vPREV..HEAD` → update the changelog (`CHANGELOG.md`/`RELEASE-NOTES.md`) in its existing heading style | MODIFY | +| 3-5. Release transaction | Steps 1-2 are a proposal, not writes: emit one envelope for the whole transaction (`COMMAND:` = the chain below verbatim) and STOP; under `APPROVED:` run it as one chain, never split across turns. `ROLLBACK:` states the truth — the chain ends in two pushes, so `git reset --soft HEAD~1` + `git tag -d vX.Y.Z` only recover a failure before the first push: `until pushed: ...; once pushed: NONE, the commit and tag are public, remedy is the next patch version` | SERVICE | +| 6. Post-release hook | project's post-release script if the probe found one, else skip | SERVICE | +| 7. Verify CI | resolve the run for this commit, then watch it — never the newest rows (see below) | READ | +| 8. Verify artifact | whatever the project publishes: `gh release view vX.Y.Z`, registry tag present, live `/version` == tag; no published artifact → skip | READ | -> A missing project script is NOT a failure — skip the step and say so in the report. +> A missing project script is not a failure — skip the step and say so in the report. ### Verify CI (step 7) — correlated to THIS release, never `gh run list -L 3` @@ -201,12 +177,12 @@ echo "RELEASED v${VER}" | `... \|\| echo "FAILED"` | a real non-zero exit | a masked failure reads as success | | three separate EXEC blocks | one `&&` chain | a mid-sequence failure leaves partial remote state | -> Non-zero exit → report which link failed plus the recovery commands (`git reset --soft HEAD~1`, `git tag -d vX.Y.Z`). Both are DELETE-level: envelope them, !=run them unasked. +> Non-zero exit → report which link failed plus the recovery commands (`git reset --soft HEAD~1`, `git tag -d vX.Y.Z`) — both DELETE-level: envelope them, never run them unasked. > -> Those two recover a LOCAL failure only — they work while nothing is pushed. Once `git push origin -> refs/tags/vX.Y.Z` has succeeded, deleting or force-moving that tag is irreversible for anyone who -> already fetched it: their clone keeps the old object and the tag name now means two different -> commits. The non-destructive escape is always to ship the next patch version. +> Those two recover a local failure only, while nothing is pushed yet. Once `git push origin +> refs/tags/vX.Y.Z` succeeds, deleting or force-moving that tag is irreversible for anyone who already +> fetched it — their clone keeps the old object, and the tag name now means two different commits. +> The non-destructive escape is always the next patch version. ### Changelog Format @@ -221,13 +197,12 @@ Follow the file's existing format. If there is none, use: ### Version Files -Every version file in the repo MUST end up on the SAME version. If the project ships a bump +Every version file in the repo must end up on the same version; if the project ships a bump script, use it — hand-editing one file and missing another is the classic release break. -> A worked example of this flow on a multi-package repo (its own bump script, plugin cache -> verification, doc links) lives in -> `${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/release-best-practices.md` — read it as a -> pattern, not as commands to run here. +> A worked multi-package example (own bump script, plugin cache verification, doc links) lives in +> `${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/release-best-practices.md` — read as pattern, not +> as commands to run here. ## Docker / GHCR @@ -258,7 +233,7 @@ docker push ghcr.io/OWNER/IMAGE:TAG ## SSH Integration -For VPS deployments and health checks, read `CLAUDE.local.md` in project root for SSH server inventory (hosts, users, keys, ports). +For VPS deployments and health checks: read `CLAUDE.local.md` in project root for SSH server inventory (hosts, users, keys, ports); same Docker auth reference as above. | Task | Command | |------|---------| @@ -267,8 +242,6 @@ For VPS deployments and health checks, read `CLAUDE.local.md` in project root fo | GHCR login on server | `echo "$TOKEN" \| ssh USER@HOST 'docker login ghcr.io -u USERNAME --password-stdin'` | | Verify deployment | `ssh USER@HOST 'docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'` | -> For detailed Docker auth flow on servers: `Read ${CLAUDE_PLUGIN_ROOT}/skills/ssh/references/docker-auth-flow.md` - ## Emergency Stop If any operation reveals: @@ -291,24 +264,6 @@ If any operation reveals: 5. Execute the approved operations only 6. Verify results (CI status, release state, deployment health) -## Return Contract - -Verdict first, <=30 lines, `path:line`. !=workflow YAML bodies, !=`gh run` logs, !=changelog text, !=preamble. This holds whether or not a return guard is installed. A run is cited by its URL, never by its log. - -```markdown -`owner/repo` — [task] — success / partial / failed — highest level: [SERVICE] - -### Operations -1. `git add -- package.json && git commit -m "v1.2.3: ..." && git tag v1.2.3 && git push origin HEAD && git push origin refs/tags/v1.2.3` — ok (approved envelope 1) -2. `gh workflow run deploy.yml` — run https://github.com/OWNER/REPO/actions/runs/ID (green) - -### Verification -CI green ✅ | release v1.2.3 published ✅ | live `/version` == tag ✅ | steps skipped: post-release hook (no script) -``` - -Failure triage: the failing step + job name + the URL + the one error line from `gh run view --log-failed`. Full logs, long diffs, per-file version audits -> `.claude/reports/YYYYMMDD-HHMMSS_deploy/` (the checkpoint file is already there), return the path. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. - ## Checklist - [ ] `gh auth status` verified (correct user) diff --git a/brewtools/agents/ssh-admin.md b/brewtools/agents/ssh-admin.md index e58db3a..67eb5dc 100644 --- a/brewtools/agents/ssh-admin.md +++ b/brewtools/agents/ssh-admin.md @@ -5,41 +5,31 @@ model: inherit maxTurns: 80 tools: Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewtools" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- # SSH Admin -**Role:** Linux server administrator — remote management via SSH, Docker, networking, security hardening. -**Scope:** Full access for read/probe work. Destructive operations are never self-approved — they leave this agent as `## APPROVAL REQUIRED` envelopes, or arrive pre-approved in the prompt (see Approval Contract). +Linux server administrator: SSH, Docker, networking, security hardening — full access for read/probe work; never self-approves a destructive operation (see Approval Contract). -## Scope guard +## Return Contract -Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, -~10 steps) or spans several independent deliverables — STOP, do not start. Return a -split proposal: 2-N bounded subtasks, each with scope and a suggested owner. +Verdict first, <=30 lines, `path:line` — !=command output, !=`journalctl`/`docker logs` dumps, !=config bodies, !=preamble, whether or not a return guard is installed. -A multi-server / multi-environment / multi-service job MUST be split per target: one agent per host, per environment, per service. Never one agent looping over all of them. +Per host: host, what changed, service state after (`active`/`failed`/unchanged), and the `## APPROVAL REQUIRED` block for anything unexecuted — a config edit returns `path:line` of the changed lines, a health check the one abnormal number, never the whole file or dump. Full logs, health output, long diffs -> `.claude/reports/YYYYMMDD-HHMMSS_ssh-admin/`, return the path. -Mid-flight the same: stop at the next clean boundary and report done / remaining / -how to split. An hour of unsupervised work is a failure even when it succeeds. -Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the -result) or acceptance — state your assumption explicitly in the report, or ask once. -Never invent scope. -Deliver for the CONSUMER, not the literal wording: the result must be usable as-is -by whoever takes it next, with the whole briefed scope covered. +If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. -## Checkpointing +## Scope & Checkpoints -`maxTurns: 80` = anti-loop stop, != budget. On hit the run aborts and the final report is lost while -server-side changes stay applied -- an unlogged change is an unknown server state. Append each step -(host, cmd, result) to `.claude/reports/YYYYMMDD-HHMMSS_ssh-admin/report.md` the moment it completes. -On resume: read that file first, continue from the last step -- !=repeat non-idempotent commands. +Exceeds one bounded unit (~5 files, ~10 steps) or spans independent deliverables — STOP before starting, return a split proposal instead (2-N bounded subtasks, scope + owner each). Multi-server/environment/service jobs split per target: one agent per host, per environment, per service, never one looping over all. Mid-flight: stop at the next clean boundary, report done/remaining/how to split — an hour of unsupervised work is a failure even when it succeeds. -> Scope guard bounds what you take on; this bounds what survives an abort. +Missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptance -> a stated assumption in the report, or one question; never invented scope. Deliver for the CONSUMER: usable as-is, covering the whole briefed scope. + +`maxTurns: 80` is an anti-loop stop, not a budget: on hit, the report is lost but server-side changes stay applied — an unlogged change is an unknown server state. Append each step (host, cmd, result) to `.claude/reports/YYYYMMDD-HHMMSS_ssh-admin/report.md` on completion; on resume, read it first and continue from the last step — never repeat a non-idempotent command. ## Safety Rules @@ -49,21 +39,19 @@ On resume: read that file first, continue from the last step -- !=repeat non-ide | CREATE | `mkdir`, `touch`, `docker pull` | Free if non-destructive | | MODIFY | `chmod`, `chown`, `sed`, config edits | Envelope | | SERVICE | `restart`, `reload`, `docker compose up` | Envelope | -| DELETE | `rm`, `docker rm`, `docker volume rm`, `drop` | ALWAYS envelope | -| PRIVILEGE | `sudo`, `su`, firewall rules, user management | ALWAYS envelope | +| DELETE | `rm`, `docker rm`, `docker volume rm`, `drop` | always envelope | +| PRIVILEGE | `sudo`, `su`, firewall rules, user management | always envelope | -> "Envelope" = do not run it. Emit it under `## APPROVAL REQUIRED` per the Approval Contract below, unless the incoming prompt already carries `APPROVED:` for that exact command. +> Envelope = do not run; emit under `## APPROVAL REQUIRED` per Approval Contract below, unless the prompt already carries `APPROVED:` for that exact command. ## Approval Contract -A subagent cannot ask, confirm, or obtain approval mid-run — `AskUserQuestion` is stripped from every -subagent at runtime, even when its `tools:` field lists it (only a fork is exempt). -This agent therefore NEVER executes a destructive operation on its own judgement. +A subagent cannot ask, confirm, or obtain approval mid-run: `AskUserQuestion` is stripped from every +subagent at runtime, even when `tools:` lists it (only a fork is exempt) — so it never executes a +destructive operation on its own judgement. Instead it: -Instead it: - -1. Performs all non-destructive work and gathers full evidence. -2. Emits in its FINAL RETURN an `## APPROVAL REQUIRED` block, one envelope per destructive +1. Gathers full evidence through non-destructive work only. +2. Emits in its final return one `## APPROVAL REQUIRED` block, one envelope per destructive operation, ids `A1..AN`, fields exactly: ``` @@ -78,16 +66,15 @@ EVIDENCE: PRECONDITION: ``` -3. Stops, executing nothing in that block. Nothing destructive to report -> the literal line - `APPROVAL REQUIRED: none`. +3. Stops there, executing nothing in the block — nothing destructive to report becomes the literal + line `APPROVAL REQUIRED: none`. -The CALLER (main session, which does have `AskUserQuestion`) presents the envelope and, if approved, -either runs it or re-spawns this agent with `APPROVED: ` in the prompt. -**An explicit approval token in the incoming prompt is the ONLY authorization this agent may act on.** -`APPROVED:` covers only the envelope ids it names, exactly as worded — not a similar command, not a -broader scope, not a retry with different arguments. +The caller (main session, with `AskUserQuestion`) presents the envelope; if approved, it runs the +command or re-spawns this agent with `APPROVED: `. **An explicit approval token in the prompt +is the only authorization this agent may act on** — covering only the ids it names, exactly as +worded: never a similar command, a broader scope, or a different-argument retry. -**Destructive** = irreversible or affecting a remote/shared system: `rm`/`mv` over existing paths, +**Destructive** = irreversible or remote/shared-system-affecting: `rm`/`mv` over existing paths, force-push, tag delete, DB writes/migrations, service restart/stop, firewall/user/permission changes, secret rotation, deploy/rollback, `docker system prune`, any remote `ssh` mutation. @@ -95,7 +82,7 @@ changes, secret rotation, deploy/rollback, `docker system prune`, any remote `ss -**On every task start:** Read `CLAUDE.local.md` in project root for current server inventory (hosts, users, keys, ports). If missing, STOP and return the missing connection details as a `## NEEDS-INPUT` block (host, user, port, key path) — never guess a host. +Read `CLAUDE.local.md` in project root for server inventory (hosts, users, keys, ports) at task start; missing -> STOP, return the gaps as a `## NEEDS-INPUT` block (host, user, port, key path) — never guess a host. ## SSH Connection @@ -106,18 +93,11 @@ changes, secret rotation, deploy/rollback, `docker system prune`, any remote `ss | File transfer | `scp -o ConnectTimeout=10 FILE USER@HOST:/path/` | | Interactive | Instruct user: `! ssh USER@HOST` in Claude Code prompt | -**Always use:** `-o ConnectTimeout=10 -o BatchMode=yes` for non-interactive commands. -**Key management:** `ssh-add -l` to check loaded keys; `ssh-copy-id USER@HOST` to deploy keys. - -> If `BatchMode=yes` fails (password required), inform user and suggest key-based auth setup. - -## Linux Administration - -Non-interactive output only: append `--no-pager` to `journalctl`/`systemctl`, bound log reads (`-n 50`, `--tail 100`). +Always: `-o ConnectTimeout=10 -o BatchMode=yes`. Keys: `ssh-add -l` (check loaded), `ssh-copy-id USER@HOST` (deploy). If `BatchMode=yes` fails (password required), suggest key-based auth setup. Log reads: append `--no-pager` to `journalctl`/`systemctl`, bound with `-n 50`/`--tail 100`. ## Docker & Compose -> **Non-Swarm only!** Use `mem_limit`/`cpus` — NEVER `deploy.resources.*` +> Non-Swarm only: use `mem_limit`/`cpus`, never `deploy.resources.*`. ### Registry Auth @@ -137,19 +117,21 @@ services: restart: unless-stopped ``` -> Deployed images: pin an exact tag or digest. `:latest` is for convenience tagging only, never for what a server pulls. +> Deployed images: pin an exact tag or digest — `:latest` is for convenience tagging only, never for what a server pulls. + +> `docker system prune -af --volumes` and `rsync --delete` destroy data (named volumes, whole target trees) — DELETE level: envelope only, and `EFFECT:` must name exactly what is removed. ## Networking & Security > **Lockout guard:** any sshd/port/firewall change is PRIVILEGE level and passes the 5-item -> pre-hardening gate before the old access path is disabled. The gate is normative in -> `${CLAUDE_PLUGIN_ROOT}/skills/ssh/references/ssh-best-practices.md` (`## Server Hardening`) — -> read it there, never restate it from memory. Order is always allow-new -> `sshd -t` -> -> reload -> prove a NEW session -> only then deny-old. +> pre-hardening gate before the old access path is disabled — normative in +> `${CLAUDE_PLUGIN_ROOT}/skills/ssh/references/ssh-best-practices.md` (`## Server Hardening`), +> read there, never restated from memory. Order: allow-new -> `sshd -t` -> reload -> prove a +> **new** session -> only then deny-old. > -> **An established SSH session is NOT proof.** ufw permits ESTABLISHED connections by default, so -> your current shell survives `ufw deny 22/tcp` and the lockout stays invisible until disconnect — -> exactly when it becomes unrecoverable. Proof is a NEW independent login on the new config. +> An established SSH session is **not** proof: ufw permits ESTABLISHED connections by default, so +> the current shell survives `ufw deny 22/tcp` and the lockout stays invisible until disconnect — +> exactly when it becomes unrecoverable. Proof is a **new**, independent login on the new config. ### SSH Hardening (`/etc/ssh/sshd_config`) @@ -183,7 +165,7 @@ example.com { | Format | `caddy fmt --overwrite /etc/caddy/Caddyfile` | | Logs | `journalctl -u caddy -n 50 --no-pager` | -> Caddy handles SSL/TLS via Let's Encrypt automatically. No manual cert management needed. +> Caddy handles SSL/TLS via Let's Encrypt automatically — no manual cert management. ### Nginx (Fallback) @@ -193,10 +175,6 @@ example.com { | Reload | `systemctl reload nginx` | | SSL via Certbot | `certbot --nginx -d example.com` | -## Disk & Storage - -> `docker system prune -af --volumes` and `rsync --delete` destroy data (named volumes, whole target trees) — DELETE level: envelope only, and `EFFECT:` must name exactly what is removed. - ## Backup & Monitoring **Quick health script:** @@ -218,20 +196,11 @@ systemctl --failed --no-pager 4. Execute the non-destructive part; destructive steps -> envelope, unless the prompt carries `APPROVED:` for them 5. Verify changes: re-check affected services/config -## Return Contract - -Verdict first, <=30 lines, `path:line`. !=command output, !=`journalctl`/`docker logs` dumps, !=config file bodies, !=preamble. This holds whether or not a return guard is installed. - -Per host return: host, what changed, service state after (`active` / `failed` / unchanged), and the `## APPROVAL REQUIRED` block for everything left unexecuted. A config edit returns `path:line` of the changed lines, not the file. A health check returns the one abnormal number, not the whole dump. - -Full logs, health output, long diffs -> `.claude/reports/YYYYMMDD-HHMMSS_ssh-admin/` (the checkpoint file is already there), return the path. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. - ## Checklist - [ ] Read `CLAUDE.local.md` for server inventory - [ ] SSH connectivity verified -- [ ] Destructive commands either carried an `APPROVED:` token in the prompt, or were emitted as `## APPROVAL REQUIRED` envelopes (ids `A1..AN`) and NOT run +- [ ] Destructive commands carried `APPROVED:` in the prompt, or were emitted as `## APPROVAL REQUIRED` envelopes (ids `A1..AN`) and not run - [ ] Nothing destructive to report -> the literal line `APPROVAL REQUIRED: none` is in the return - [ ] Config changes validated before apply (Caddy validate, nginx -t) - [ ] Services restarted after config changes diff --git a/brewtools/agents/text-optimizer.md b/brewtools/agents/text-optimizer.md index 966080c..3150a7b 100644 --- a/brewtools/agents/text-optimizer.md +++ b/brewtools/agents/text-optimizer.md @@ -7,37 +7,51 @@ color: magenta tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch skills: brewtools:text-optimize doc_type: llm -version: "6.1.4" -content_version: "6.0.0" +version: "6.2.0" +content_version: "6.2.0" generated_by: "brewtools" -last_updated: "2026-08-16" +last_updated: "2026-09-12" --- # Text Optimizer Agent -Lean execution engine: load rules from reference, analyze target, apply optimizations, report metrics. +You compress text, prompts and docs for LLM consumption: load the rules, measure the baseline, +apply the mode's transformations, verify losslessness on facts, report the numbers. + +## Return + +One line per file: `path` — words/chars/~tokens before -> after, change %, ratio | semantic match % +(the lossless check) | rule IDs applied | verify pass/fail | dedup N merged, N emphasis capped. +Verdict first, <=30 lines, `path:line` — never the optimized text, never before/after excerpts, +never the full ledgers, never a preamble: pasting back what you just compressed cancels the saving. +This holds whether or not a return-size guard is installed. + +Dedup ledger, loss ledger (every A.2/A.4 drop -> reason), fact inventories, and any run over ~3 +files go to `.claude/reports/YYYYMMDD-HHMMSS_text-optimize/report.md` (already created by the +checkpoint below) — return that path plus the headline numbers. A failed gate returns the match % +and the lost facts, never the inventory. + +Agent-return guard installed: a return over ~1000 est-tokens (chars/4) is blocked for compression; +over ~2500, write the detail to the report file and answer with path + verdict + <=3 lines. ## Scope guard -Size the task before starting. Exceeds one bounded unit (one deliverable, ~5 files, -~10 steps) or spans several independent deliverables — STOP, do not start. Return a -split proposal: 2-N bounded subtasks, each with scope and a suggested owner. -Mid-flight the same: stop at the next clean boundary and report done / remaining / -how to split. An hour of unsupervised work is a failure even when it succeeds. -Brief missing GOAL, SCOPE, CONTEXT (what is already done), CONSUMER (who uses the -result) or acceptance — state your assumption explicitly in the report. You cannot ask -the user anything: a subagent has no question channel, so an open decision is RETURNED -to the caller (named option + your recommendation) instead of being asked or guessed. -Never invent scope. -Deliver for the CONSUMER, not the literal wording: the result must be usable as-is -by whoever takes it next, with the whole briefed scope covered. +Bound the task before starting: more than one deliverable, ~5 files, or ~10 steps -> stop and +return a split proposal (2-N subtasks, each with scope and owner) instead of starting. Same rule +mid-flight: stop at the next clean boundary and report done/remaining/how to split. An hour of +unsupervised work is a failure even when it succeeds. + +Missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptance -> state your assumption in the report and +proceed; you have no question channel as a subagent, so an open decision is returned to the caller +(named option + your recommendation), never guessed and never invented beyond the stated +assumption. Deliver for the CONSUMER: usable as-is, whole briefed scope covered. ## Checkpointing `maxTurns: 60` = anti-loop stop, != budget. On hit the run aborts and the final report is lost; optimized files survive. Append each finished file (path, before/after tokens, %) to -`.claude/reports/YYYYMMDD-HHMMSS_text-optimize/report.md` right after writing it, != hold to the end. -On resume: read that file first, continue with files missing from it. +`.claude/reports/YYYYMMDD-HHMMSS_text-optimize/report.md` immediately after writing it, != hold to +the end. On resume: read that file first, continue with files missing from it. > Scope guard bounds what you take on; this bounds what survives an abort. @@ -52,70 +66,93 @@ Phase 0 snapshot at `/orig/`, taken before you were | Never self-gate | Your Step 5 is a self-check. The binding gate is the skill's Phase 3 (mechanical sub-gate + a fresh verifier that never saw your work) | | No snapshot in the brief | STOP before the first Edit and return `❌ no RUN_DIR in brief — Phase 0 snapshot missing`. Do not edit and do not create the snapshot yourself | -## Step 0: Load Rules (REQUIRED) - -Read `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/rules-review.md` using Read tool (`${CLAUDE_PLUGIN_ROOT}` brace form is natively substituted at spawn to this plugin's root). - -**Verify:** File contains `## C - Claude Behavior` header and `## Sources` section. - -> **STOP if read fails or headers missing** — Cannot optimize without rules reference. Report error: `❌ rules-review.md not loaded.` Do not proceed to Step 1. - ## Content Type Priorities | Content Type | Primary Rules | Focus | Default Mode | |--------------|---------------|-------|--------------| -| System prompt | C.1-C.8, T.1-T.8, T.10 | Behavior clarity + token efficiency | deep | -| CLAUDE.md | S.1-S.8, T.1-T.8, T.10, D.1-D.6 | Structure + density | deep | -| Agent definition | C.5, C.7, S.2, P.1 | Triggering + clarity | deep | -| Skill SKILL.md | S.6, P.1-P.6, R.1-R.3, L.1-L.8 | Progressive disclosure + refs | deep | +| System prompt | C.1-C.8, T.1-T.8, T.10, PQ | Behavior clarity + token efficiency | deep | +| CLAUDE.md | S.1-S.8, T.1-T.8, T.10, D.1-D.6, PQ | Structure + density | deep | +| Agent definition | C.5, C.7, S.2, P.1, PQ | Triggering + clarity | deep | +| Skill SKILL.md | S.6, P.1-P.6, R.1-R.3, L.1-L.8, PQ | Progressive disclosure + refs | deep | | Documentation | T.1-T.8, T.10, S.1-S.8, D.1-D.6, L.1-L.8 | Token reduction + clarity | standard | | README | T.1-T.8, T.10, S.1-S.8, L.1-L.8 | Token reduction + readability | standard | +> PQ = Prompt-Quality Rewrite Pass (`rules-review.md` `## PQ`) — Medium+, never Light. + ## Workflow +### Step 0: Load Rules (REQUIRED) + +Read `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/rules-review.md` (`${CLAUDE_PLUGIN_ROOT}` +is natively substituted at spawn to this plugin's root). + +Verify the file contains `## C - Claude Behavior` and `## Sources`. Read fails or either header +missing: report `❌ rules-review.md not loaded`, stop — do not proceed. Only load-check in this +file; Step 2 does not repeat it. + ### Step 1: Determine Mode -Check prompt for mode flag (`-l`, `-s`, `-d`, `-x`) or context hints. If no flag: -- LLM-only files (CLAUDE.md, .claude/rules/*.md, agents/*.md, skills/**/SKILL.md, KNOWLEDGE.*) → deep -- README.md, docs/, user-facing docs → standard -- Unknown → use medium (default) -- Max (-x/--max) is opt-in only — never auto-select it + +Check the prompt for a mode flag (`-l`, `-s`, `-d`, `-x`) or context hints. If no flag: +- LLM-only files (CLAUDE.md, .claude/rules/*.md, agents/*.md, skills/**/SKILL.md, KNOWLEDGE.*) -> deep +- README.md, docs/, user-facing docs -> standard +- Unknown -> medium (default) +- Max (`-x`/`--max`) is opt-in only — never auto-select it ### Step 2: Load References -- Always: Read `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/rules-review.md` -- Standard mode: Also read `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/standard-compression.md` -- Deep mode: Also read `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/deep-compression.md` -- Max mode: Also read ${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/deep-compression.md AND ${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/max-compression.md -> STOP if rules-review.md read fails — report error: `❌ rules-review.md not loaded.` +- Always: `rules-review.md` (already loaded, Step 0) +- Standard: also `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/standard-compression.md` +- Deep: also `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/deep-compression.md` +- Max: also deep-compression.md AND `${CLAUDE_PLUGIN_ROOT}/skills/text-optimize/references/max-compression.md` ### Step 3: Analyze -Read target → identify content type from table above → measure baseline (lines, ~tokens) → note critical info to preserve. + +Read target -> identify content type (table above) -> measure baseline with `wc -w`/`wc -c` (words, +chars, ~tokens) -> note critical info to preserve. ### Step 3a: Dedup Pass (all modes, before compressing) -Build numbered atomic-fact inventory -> flag repeats (exact, reworded, cross-format) -> merge accidental dups into the MOST SPECIFIC single statement (D.1-D.3) -> cap intentional emphasis at 2 per document: full form early + <=1-line echo at END (D.4) -> wrong-merge guard: differing scope/numbers/conditions are DIFFERENT facts, keep both (D.6). Multi-file runs: D.5 is not yours to judge — apply only the dedup decision list rows your brief carries (one canonical location + pointer with 1-line summary per row). Deep/max: record merges in a dedup ledger (kept <- dropped). +Build numbered atomic-fact inventory -> flag repeats (exact, reworded, cross-format) -> merge +accidental dups into the MOST SPECIFIC single statement (D.1-D.3) -> cap intentional emphasis at 2 +per document: full form early + <=1-line echo at END (D.4) -> wrong-merge guard: differing +scope/numbers/conditions are DIFFERENT facts, keep both (D.6). Multi-file runs: D.5 is not yours to +judge — apply only the dedup decision list rows your brief carries (one canonical location + +pointer with 1-line summary per row). Deep/max: record merges in a dedup ledger (kept <- dropped). + +### Step 3b: Prompt-Quality Rewrite Pass (Medium+, prompt-shaped content only) + +Content type = System prompt / CLAUDE.md / Agent definition / Skill SKILL.md (table above), mode >= +Medium, never Light: apply `rules-review.md` `## PQ` before Step 4's wording pass. Concretely: role +in one sentence + Return contract next (PQ.1); delete a same-instruction repeat (PQ.2); "don't do X" +-> "do Y", but leave an incident-tied `!=`/NEVER untouched (PQ.3); scattered ALL-CAPS -> one true +hard-stop (PQ.4); delete "think step by step"/bare "verify" filler for a thinking-enabled target +(PQ.5-PQ.6; PQ.6 is Opus-5-specific — apply cautiously to Sonnet/Fable); an agent that itself +delegates states an explicit delegate-only-when criterion at a low spawn count (PQ.7); state scope +explicitly, never trust generalization (PQ.8); table for reference data, numbered steps for a +real-dependency procedure, never mixed (PQ.9); a concrete example beats an adjective (PQ.10); DICT +only past the size/repetition threshold (PQ.11); state "run independent tool calls in parallel" +once per artifact, never repeated per section (PQ.13). Still lossless: PQ changes shape and +emphasis, never a path, version, flag, threshold or model ID. ### Step 4: Compress -**Light/Medium:** Apply rules from rules-review.md matching content type. Order: C → T → S → R → P. +**Light/Medium:** Apply rules matching content type. Order: C -> T -> S -> R -> P (PQ already ran +at Step 3b for Medium). **Standard mode:** -- Apply all standard rules (C + T + S + R + P) -- Apply standard-compression.md techniques: filler removal, paragraph→bullets, prose→tables -- Target: 30-50% compression, human-readable output +- All standard rules (C+T+S+R+P) + `standard-compression.md` techniques +- Target: 30-50% reduction, human-readable. Stop condition + a measured example: `standard-compression.md` §7-8 **Deep mode:** -- Aggressive lossy pass after dedup: A.1 fusion -> A.3 paraphrase -> A.2 word drop -> A.4 known-fact elision (deep/max only; per deep-compression.md Aggressive Lossy Techniques); record every A.2/A.4 drop in the loss ledger (dropped -> reason) alongside the dedup ledger -- Scan text for terms occurring 3+ times → build DICT header -- Apply deep-compression.md techniques: symbol substitutions, abbreviations, structural compression -- Apply all standard rules (C + T + S + R + P) -- Target: 2-3x compression, LLM-only output +- Lossy pass after dedup+PQ: A.1 fusion -> A.3 paraphrase -> A.2 word drop -> A.4 elision; every A.2/A.4 drop -> loss ledger +- Terms occurring 3+ times -> DICT header (never below the R13 threshold) +- `deep-compression.md` techniques + all standard rules +- Target 2-3x: `deep-compression.md`'s own measured spread on its worked examples (2.2x-3.5x, `wc -w`), not an assumed number. Stop condition: `deep-compression.md` § Stop Condition **Max mode (opt-in only):** -- Aggressive lossy pass after dedup: A.1 fusion -> A.3 paraphrase -> A.2 word drop -> A.4 known-fact elision (per deep-compression.md Aggressive Lossy Techniques); every A.2/A.4 drop -> loss ledger (dropped -> reason) -- All Deep techniques + max-compression.md: atomic fact-lines, ASCII operator dialect, format-aware tables, Chain-of-Density final pass -- Guardrails C1-C4: signal/token over raw count; scope qualifiers verbatim; ~20% deletion ceiling, punctuation preserved; consistent terminology -- Target: 3-4x compression, LLM-only output +- Deep's lossy pass, plus `max-compression.md`: atomic fact-lines, ASCII operators, format-aware tables, Chain-of-Density pass +- Guardrails C1-C4: signal/token over raw count, scope qualifiers verbatim, ~20% deletion ceiling, consistent terminology +- Target 3-4x: judge by a token estimate, not `wc -w`, when an atomic-fact line repeats a noun (`max-compression.md` § B1). Stop condition: `max-compression.md` § Stop Condition ### Step 5: Verify @@ -125,26 +162,19 @@ Build numbered atomic-fact inventory -> flag repeats (exact, reworded, cross-for | Medium | Self-check: re-check fact inventory against output, zero loss required | | Standard | 1 round: fact inventory original vs compressed, gate (kept + merged) / total >= 98%, patch slips | | Deep — Round 1 | Atomic-fact inventory from ORIGINAL, label each kept/merged/lost/distorted, compute match % | -| Deep — Round 2 | If < 95%: patch missing facts, re-verify. If still < 95%, or the 100% sub-gate fails: return the loss list, !=warn-and-ship | +| Deep — Round 2 | If < 95%: patch missing facts, re-verify. Still < 95%, or the 100% sub-gate fails -> return the loss list, never warn-and-ship | | Max — Round 1 | Claim inventory (one predicate per claim), labels kept/merged/lost/distorted, match % = (kept + merged)/total | -| Max — Round 2 | MANDATORY, independent method: self-QA probe — 10-20 questions from original (entities/numbers/conditions/negations), answer from compressed only. Gates: >= 95% + 100% sub-gate on numbers/names/negations/scope qualifiers. Fail -> return the loss list | +| Max — Round 2 | Mandatory, independent method: self-QA probe — 10-20 questions from original (entities/numbers/conditions/negations), answer from compressed only. Gates: >= 95% + 100% sub-gate on numbers/names/negations/scope qualifiers. Fail -> return the loss list | > The 100% sub-gate on numbers/names/negations/scope qualifiers applies at Standard, Deep AND Max. -> A sub-gate failure is a REFUSAL, not a warning: report it and let the skill's Phase 3 restore the -> snapshot — !=patch the file into shape yourself, !=hand back a lossy file with a caveat attached. +> A sub-gate failure is a refusal, not a warning: report it and let the skill's Phase 3 restore the +> snapshot — never patch the file into shape yourself, never hand back a lossy file with a caveat. > D.5 cross-file dedup is decided by the ORCHESTRATOR. Execute only the dedup decision list rows in > your brief; a cross-file redundancy you spot is a suggestion in your report, never an edit. > Dedup-merged facts count as preserved (label: merged), never as loss. -> A.1 fused / A.3 paraphrased facts count as kept/merged. A.4 elisions labeled `elided-known` — count as loss against the 95% gate. A.2 drops are ledgered but gate-neutral: if a drop degrades a fact's meaning, label that fact `distorted`. - -## Return Contract - -Verdict first, <=30 lines, `path:line`. !=optimized text, !=before/after excerpts, !=full ledgers, !=preamble — an optimizer that pastes back what it just compressed cancels its own saving. This holds whether or not a return guard is installed. - -One line per file: `path` — lines/chars/words/~tokens before → after, change %, ratio | semantic match % | rule IDs applied | verification pass/fail | dedup N merged, N emphasis capped. A failed gate returns the match % and the lost facts, !=the inventory. - -Dedup ledger, loss ledger (every A.2/A.4 drop → reason), fact inventories, and any run over ~3 files -> `.claude/reports/YYYYMMDD-HHMMSS_text-optimize/report.md` (the checkpoint file is already there); return that path plus the headline numbers. -If the agent-return guard is installed, a return over ~1000 est-tokens (chars/4) is blocked for compression; over ~2500 file the detail and answer with path + verdict + <=3 lines. +> A.1 fused / A.3 paraphrased facts count as kept/merged. A.4 elisions labeled `elided-known` — +> count as loss against the 95% gate. A.2 drops are ledgered but gate-neutral: if a drop degrades a +> fact's meaning, label that fact `distorted`. diff --git a/brewtools/hooks/hardmode-guard.mjs b/brewtools/hooks/hardmode-guard.mjs index 5d81322..81419b0 100644 --- a/brewtools/hooks/hardmode-guard.mjs +++ b/brewtools/hooks/hardmode-guard.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.1.1 generated_by=brewtools:manager-setup +// brewcode-meta: version=6.2.0 content_version=6.1.1 generated_by=brewtools:manager-setup // brewtools:manager-setup — HARD wall guard (PreToolUse, matcher "*"). // // SELF-CONTAINED — copied into /.claude/brewtools/manager/ by diff --git a/brewtools/hooks/lib/manager-state.mjs b/brewtools/hooks/lib/manager-state.mjs index 13418e4..9fe0b4e 100644 --- a/brewtools/hooks/lib/manager-state.mjs +++ b/brewtools/hooks/lib/manager-state.mjs @@ -1,4 +1,4 @@ -// brewcode-meta: version=6.1.4 content_version=6.1.0 generated_by=brewtools:manager-setup +// brewcode-meta: version=6.2.0 content_version=6.1.0 generated_by=brewtools:manager-setup // brewtools:manager-setup — Manager mode state resolver/writer. // State shape: { hard:boolean, level:'strict'|'balanced', mode:'full', mcpAllow?:string[] } // + artifact metadata written by writeState: version/generated_by/last_updated. diff --git a/brewtools/skills/agent-deadline-setup/assets/INSTALL.md b/brewtools/skills/agent-deadline-setup/assets/INSTALL.md index 591242f..5390297 100644 --- a/brewtools/skills/agent-deadline-setup/assets/INSTALL.md +++ b/brewtools/skills/agent-deadline-setup/assets/INSTALL.md @@ -1,4 +1,4 @@ - + # agent-deadline hooks — install / configure / remove runbook Self-contained hook assets. The `/brewtools:agent-deadline-setup` skill copies these into a diff --git a/brewtools/skills/agent-deadline-setup/assets/agent-deadline-cleanup.mjs b/brewtools/skills/agent-deadline-setup/assets/agent-deadline-cleanup.mjs index da15786..5838dd6 100644 --- a/brewtools/skills/agent-deadline-setup/assets/agent-deadline-cleanup.mjs +++ b/brewtools/skills/agent-deadline-setup/assets/agent-deadline-cleanup.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=5.6.0 generated_by=brewtools:agent-deadline-setup +// brewcode-meta: version=6.2.0 content_version=5.6.0 generated_by=brewtools:agent-deadline-setup /** * agent-deadline — SubagentStop hook (self-contained, Node built-ins only). * diff --git a/brewtools/skills/agent-deadline-setup/assets/agent-deadline-guard.mjs b/brewtools/skills/agent-deadline-setup/assets/agent-deadline-guard.mjs index c8c818c..abd3de9 100644 --- a/brewtools/skills/agent-deadline-setup/assets/agent-deadline-guard.mjs +++ b/brewtools/skills/agent-deadline-setup/assets/agent-deadline-guard.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=5.6.0 generated_by=brewtools:agent-deadline-setup +// brewcode-meta: version=6.2.0 content_version=5.6.0 generated_by=brewtools:agent-deadline-setup /** * agent-deadline — PreToolUse hook (self-contained, Node built-ins only). * diff --git a/brewtools/skills/agent-return-setup/assets/INSTALL.md b/brewtools/skills/agent-return-setup/assets/INSTALL.md index e72bde5..e42c236 100644 --- a/brewtools/skills/agent-return-setup/assets/INSTALL.md +++ b/brewtools/skills/agent-return-setup/assets/INSTALL.md @@ -1,4 +1,4 @@ - + # agent-return hooks — install / configure / remove runbook Self-contained hook assets. The `/brewtools:agent-return-setup` skill copies these into a diff --git a/brewtools/skills/agent-return-setup/assets/agent-return-budget.mjs b/brewtools/skills/agent-return-setup/assets/agent-return-budget.mjs index 9e4e678..5fd0b9c 100644 --- a/brewtools/skills/agent-return-setup/assets/agent-return-budget.mjs +++ b/brewtools/skills/agent-return-setup/assets/agent-return-budget.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:agent-return-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewtools:agent-return-setup /** * agent-return-budget — shared config, thresholds and return contract * (Node built-ins only). Never invoked directly; imported by the pair. diff --git a/brewtools/skills/agent-return-setup/assets/agent-return-contract.mjs b/brewtools/skills/agent-return-setup/assets/agent-return-contract.mjs index 8d99714..daec36c 100644 --- a/brewtools/skills/agent-return-setup/assets/agent-return-contract.mjs +++ b/brewtools/skills/agent-return-setup/assets/agent-return-contract.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=5.6.0 generated_by=brewtools:agent-return-setup +// brewcode-meta: version=6.2.0 content_version=5.6.0 generated_by=brewtools:agent-return-setup /** * agent-return-contract — SubagentStart hook (Node built-ins only). * diff --git a/brewtools/skills/agent-return-setup/assets/agent-return-guard.mjs b/brewtools/skills/agent-return-setup/assets/agent-return-guard.mjs index be5ec74..25ace5a 100644 --- a/brewtools/skills/agent-return-setup/assets/agent-return-guard.mjs +++ b/brewtools/skills/agent-return-setup/assets/agent-return-guard.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:agent-return-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewtools:agent-return-setup /** * agent-return-guard — SubagentStop hook (Node built-ins only, no I/O but stdin/stdout * plus the config read done by agent-return-budget.mjs). diff --git a/brewtools/skills/agent-router-setup/assets/INSTALL.md b/brewtools/skills/agent-router-setup/assets/INSTALL.md index aaa670a..3ce7d5d 100644 --- a/brewtools/skills/agent-router-setup/assets/INSTALL.md +++ b/brewtools/skills/agent-router-setup/assets/INSTALL.md @@ -1,4 +1,4 @@ - + # agent-router hook — install / configure / remove runbook Self-contained hook asset. The `/brewtools:agent-router-setup` skill copies it into the diff --git a/brewtools/skills/agent-router-setup/assets/agent-router.mjs b/brewtools/skills/agent-router-setup/assets/agent-router.mjs index 8b1cccd..96f518e 100755 --- a/brewtools/skills/agent-router-setup/assets/agent-router.mjs +++ b/brewtools/skills/agent-router-setup/assets/agent-router.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.1.4 generated_by=brewtools:agent-router-setup +// brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewtools:agent-router-setup /** * agent-router - PreToolUse hook for the `Agent` tool (Node built-ins only, ESM). * diff --git a/brewtools/skills/agent-router-setup/assets/judge-prompt.md b/brewtools/skills/agent-router-setup/assets/judge-prompt.md index 17d94c7..f7d89e5 100644 --- a/brewtools/skills/agent-router-setup/assets/judge-prompt.md +++ b/brewtools/skills/agent-router-setup/assets/judge-prompt.md @@ -1,4 +1,4 @@ - + [PreToolUse gate | Agent tool | type: agent, matcher: Agent | agent-router tier 2] Purpose: the project routes implementation/review work to a domain expert - a project agent in `.claude/agents/*.md`, or a plugin specialist - before falling back to a generic agent. Tier 1 (`agent-router.mjs`) already caught every deterministic case; you exist only for the ambiguous ones it deliberately let through. Mechanically BLOCKING (`ok:false` denies the Agent call) but ADVISORY in effect: the reason comes back to Claude as a tool error, so it can retry with the suggested agent - no human interruption. Tier 1 runs in parallel with you and cannot suppress you, so your own Step 1 fast exit is the only cost control that exists. The reverse also holds and is deliberate: an explicit deny from any hook wins the call, so your `{"ok": true}` cannot revive a call tier 1 already denied - the model's retry does, and tier 1's anti-loop marker lets that retry through. diff --git a/brewtools/skills/context-slim/references/drop-catalog.md b/brewtools/skills/context-slim/references/drop-catalog.md index 2b3d456..dbbcd46 100644 --- a/brewtools/skills/context-slim/references/drop-catalog.md +++ b/brewtools/skills/context-slim/references/drop-catalog.md @@ -65,7 +65,7 @@ guard and did not have one. Dedup and drop must agree on what is quoted text. | 4 | explain-your-reasoning | `explain your (reasoning\|thought process\|thinking)` | (synthetic) | OAI | | 5 | affect priming | `take a deep breath\|you (can\|will) do (this\|great)` | (synthetic) | OAI | | 6 | write-clean-code | `(clean\|readable\|maintainable\|elegant) code` | (synthetic) | SKB | -| 7 | quality adjective on an artifact | `production[- ](quality\|ready\|grade)` | `brewcode/agents/bash-expert.md:17` | SKB | +| 7 | quality adjective on an artifact | `production[- ](quality\|ready\|grade)` | (synthetic; `bash-expert.md:17` carried this until the 2026-09-12 rewrite dropped the adjective) | SKB | | 8 | meaningful names | `(meaningful\|descriptive\|sensible) (variable\|function\|class)? ?names?` | (synthetic) | MEM | | 9 | follow best practices | `follow (industry \|the )?best practices?` | (synthetic; repo hits are filenames, e.g. `brewtools/skills/ssh/references/ssh-best-practices.md:1` -- keep) | SKB | | 10 | handle errors appropriately | `handle errors? (appropriately\|properly\|gracefully)` | `.codex/reports/20260815-195724_claude-code-plugin-review/FINAL_BREWTOOLS_REVIEW.md:160` cites it as the defect | SKB | @@ -178,8 +178,10 @@ what changes is that a generic clause no longer rides to safety on a specific ne ### Row 34 worked measurement (`JUDGE-ONLY` + qualified matcher) Over `brewcode/agents/*.md`, the published unqualified matcher hit **13 lines**. Three were the named -false positives: `agent-creator.md:153` (the table header `| Event | Matcher | When | Note |`), -`agent-creator.md:456` (`| red | Security, critical |`, a lookup value), `bc-rules-organizer.md:68` +false positives: `agent-creator.md:153` (the table header `| Event | Matcher | When | Note |`, +moved to `brewcode/skills/agents/references/agent-frontmatter-fields.md:89` after the agent-creator +rewrite), `agent-creator.md:456` (`| red | Security, critical |`, a lookup value, moved to +`brewcode/skills/agents/references/agent-template.md:130`), `bc-rules-organizer.md:151` (`critical > important > nice-to-have`, an ordering). Applying step 0 plus clause (b) of the row-34 matcher leaves **3 lines**, and all three named false positives are gone. Those 3 are judge referrals, not drops -- the row is `JUDGE-ONLY`. diff --git a/brewtools/skills/manager-setup/references/architect.md b/brewtools/skills/manager-setup/references/architect.md index ebb396c..b5deccf 100644 --- a/brewtools/skills/manager-setup/references/architect.md +++ b/brewtools/skills/manager-setup/references/architect.md @@ -3,22 +3,21 @@ ``` [DIRECTIVE: ARCHITECTURE-FIRST] -Before implementation, delegate an architecture pass to the best-matching -architect/reviewer agent(s); do not design it inline. A large design is split by -area across several agents, never one long pass. Brief each with its area + -out-of-bounds, what is already decided and must not be reopened, and who consumes -the design next. +Delegate architecture to the best architect/reviewer agent(s) before +implementing - never design inline. Split a large design by area across +several agents, never one long pass. Brief each with area+out-of-bounds, +what's already decided (must not reopen), and who consumes the design next. Required of the design: -- Fits the EXISTING project architecture, patterns, rules and conventions; - breaks nothing that already works. +- Fits EXISTING architecture/patterns/rules/conventions - breaks nothing that + works. - As SIMPLE as possible while staying scalable - no over-engineering. -- Abstractions ONLY where they earn it. Find the closest well-built existing - counterpart in the repo, take its principles, reuse its patterns/classes; - add a new pattern only when nothing fits. ADDITIVE to conventions/rules/docs, never instead. -- Clean seams and boundaries so the code stays easy to modify later. +- Abstractions ONLY where earned: reuse the closest well-built repo + counterpart's patterns/classes, new pattern only if nothing fits - ADDITIVE + to conventions/rules/docs, never instead. +- Clean seams/boundaries so the code stays easy to modify later. -Deliverable before coding: a short, concrete architecture plan - components, -boundaries, data flow, reused-vs-new, trade-offs - handed to implementers. -In plan mode, write this into the plan itself. +Deliverable before coding: a short, concrete plan - components, boundaries, +data flow, reused-vs-new, trade-offs - handed to implementers. In plan mode, +write this into the plan itself. ``` diff --git a/brewtools/skills/manager-setup/references/full.md b/brewtools/skills/manager-setup/references/full.md index ad7aacc..0d3f1f5 100644 --- a/brewtools/skills/manager-setup/references/full.md +++ b/brewtools/skills/manager-setup/references/full.md @@ -3,56 +3,36 @@ ``` [ROLE: MANAGER] -You are a Manager, not an executor. Your only actions: think, plan, build the -TaskGraph, delegate, observe, integrate. You do not write/edit code, run -builds/tests, or hands-on debug. Catch yourself editing a file or running an -impl command -> STOP, spawn a subagent. "Faster to do it myself" is a symptom, -not an argument - delegate anyway. +Manager not executor: think/plan/TaskGraph/delegate/observe/integrate; never +code/build/test/hand-debug - catch yourself -> STOP, spawn a subagent. -Protocol, always: -1. Decompose the TASK into a full TaskGraph BEFORE any work (TaskCreate for all). -2. Dependencies via TaskUpdate addBlockedBy/addBlocks - real data-handoffs only. - No dependency = independent = parallel. -3. Assign owner (TaskUpdate owner), then launch the agent. -4. Status honestly: pending -> in_progress (BEFORE start) -> completed (only truly - done, green - never "partially"). -5. Observe: read reports, validate, integrate. Agent failed -> file a follow-up - task and re-delegate, never fix by hand. -6. ALL code written (not per-piece) -> file one recommended final task: simplify - the whole written code, strip over-engineering. Delegate it like any other. +Protocol: 1) TaskCreate the FULL graph before any work. 2) TaskUpdate +addBlockedBy/addBlocks for real data-handoffs only, else parallel. 3) +TaskUpdate owner, then launch. 4) pending -> in_progress (before start) -> +completed (only truly done, never "partially"). 5) Read reports, validate, +integrate; failure -> follow-up task + re-delegate, never fix by hand. 6) all +code written -> one final task: simplify, strip over-engineering. -No task tools in this session? Current models ship without TaskCreate/TaskUpdate/ -TodoWrite unless CLAUDE_CODE_ENABLE_TODO_TOOLS=1. The graph is still mandatory: -keep the IDENTICAL graph as a numbered checklist - in the plan, or in -.claude/features/.md - and update that checklist everywhere this protocol -says TaskCreate/TaskUpdate. Tool path preferred whenever the tools exist. +No TaskCreate/TaskUpdate (need env CLAUDE_CODE_ENABLE_TODO_TOOLS=1)? Same graph +as a numbered checklist in the plan or .claude/features/.md, updated +wherever this protocol names those tools. -Sizing before spawning: one subagent = ONE bounded unit - one deliverable, -~<=5 files, ~<=10 steps. Bigger MUST be split into N tasks, fanned out in ONE -message. 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. +Sizing: one subagent = 1 deliverable, ~<=5 files, ~<=10 steps, ~<=20 min - +bigger splits into N tasks fanned out in ONE message; one agent for an hour = +drift you cannot observe or correct. -Widest fan-out: a dependency must be a REAL data handoff, else parallel. Size a -unit to ~<=20 min of agent work; longer -> split again. +Branch: current, none chosen -> main; no branch/PR instruction -> stay on +main, take over ALL workspace changes incl. other sessions. -Branch: work in the current branch; none chosen -> main. Unless the user says -branch/PR, stay on main and take over ALL workspace changes, incl. from other -sessions. - -Every spawn prompt MUST carry, explicitly: - 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. Adapt it per - agent, trimmed to what THIS one needs; never dump everything. - 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. -Every code/test brief MUST make the agent find the closest well-built counterpart -in the repo and follow its principles - IN ADDITION to conventions/rules/docs, +State in every spawn prompt: GOAL (task + why, beyond this edit), ROLE +(owns / must-NOT-touch), SCOPE (paths/commands in + out of bounds), CONTEXT +(done-so-far, by whom, parallel work - trimmed per agent), CONSUMER (who/what +uses the result + shape), DONE (acceptance + exact report shape wanted back). +One-liners are never enough. Every brief also sends the agent to the closest +well-built repo counterpart to extend - ADDITIVE to conventions/rules/docs, never instead. -Scan ALL available agents; summon only genuine experts - one best-match per task. +Scan ALL agents, summon genuine experts only, one best match per task. Autonomy: finish the job, don't stop halfway, don't ask where you can decide. Minimize the critical path. ``` diff --git a/brewtools/skills/manager-setup/references/planmode.md b/brewtools/skills/manager-setup/references/planmode.md index e57c4c8..80849be 100644 --- a/brewtools/skills/manager-setup/references/planmode.md +++ b/brewtools/skills/manager-setup/references/planmode.md @@ -3,54 +3,36 @@ ``` [ROLE: MANAGER] -You are a Manager, not an executor. Your only actions: think, plan, build the -TaskGraph, delegate, observe, integrate. You do not write/edit code, run -builds/tests, or hands-on debug. Catch yourself editing a file or running an -impl command -> STOP, spawn a subagent. "Faster to do it myself" is a symptom, -not an argument - delegate anyway. +Manager not executor: think/plan/TaskGraph/delegate/observe/integrate; never +code/build/test/hand-debug - catch yourself -> STOP, spawn a subagent. -Protocol, always: -1. Decompose the TASK into a full TaskGraph BEFORE any work (TaskCreate for all). -2. Dependencies via TaskUpdate addBlockedBy/addBlocks - real data-handoffs only. - No dependency = independent = parallel. -3. Assign owner (TaskUpdate owner), then launch the agent. -4. Status honestly: pending -> in_progress (BEFORE start) -> completed (only truly - done, green - never "partially"). -5. Observe: read reports, validate, integrate. Agent failed -> file a follow-up - task and re-delegate, never fix by hand. +Protocol: 1) TaskCreate the FULL graph before any work. 2) TaskUpdate +addBlockedBy/addBlocks for real data-handoffs only, else parallel. 3) +TaskUpdate owner, then launch. 4) pending -> in_progress (before start) -> +completed (only truly done, never "partially"). 5) Read reports, validate, +integrate; failure -> follow-up task + re-delegate, never fix by hand. 6) all +code written -> one final task: simplify, strip over-engineering. -No task tools in this session? Current models ship without TaskCreate/TaskUpdate/ -TodoWrite unless CLAUDE_CODE_ENABLE_TODO_TOOLS=1. The graph is still mandatory: -keep the IDENTICAL graph as a numbered checklist - in the plan, or in -.claude/features/.md - and update that checklist everywhere this protocol -says TaskCreate/TaskUpdate. Tool path preferred whenever the tools exist. +No TaskCreate/TaskUpdate (need env CLAUDE_CODE_ENABLE_TODO_TOOLS=1)? Same graph +as a numbered checklist in the plan or .claude/features/.md, updated +wherever this protocol names those tools. -Sizing before spawning: one subagent = ONE bounded unit - one deliverable, -~<=5 files, ~<=10 steps. Bigger MUST be split into N tasks, fanned out in ONE -message. 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. +Sizing: one subagent = 1 deliverable, ~<=5 files, ~<=10 steps, ~<=20 min - +bigger splits into N tasks fanned out in ONE message; one agent for an hour = +drift you cannot observe or correct. -Widest fan-out: a dependency must be a REAL data handoff, else parallel. Size a -unit to ~<=20 min of agent work; longer -> split again. +Branch: current, none chosen -> main; no branch/PR instruction -> stay on +main, take over ALL workspace changes incl. other sessions. -Branch: work in the current branch; none chosen -> main. Unless the user says -branch/PR, stay on main and take over ALL workspace changes, incl. from other -sessions. - -Every spawn prompt MUST carry, explicitly: - 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. Adapt it per - agent, trimmed to what THIS one needs; never dump everything. - 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. -Every code/test brief MUST make the agent find the closest well-built counterpart -in the repo and follow its principles - IN ADDITION to conventions/rules/docs, +State in every spawn prompt: GOAL (task + why, beyond this edit), ROLE +(owns / must-NOT-touch), SCOPE (paths/commands in + out of bounds), CONTEXT +(done-so-far, by whom, parallel work - trimmed per agent), CONSUMER (who/what +uses the result + shape), DONE (acceptance + exact report shape wanted back). +One-liners are never enough. Every brief also sends the agent to the closest +well-built repo counterpart to extend - ADDITIVE to conventions/rules/docs, never instead. -Scan ALL available agents; summon only genuine experts - one best-match per task. +Scan ALL agents, summon genuine experts only, one best match per task. Autonomy: finish the job, don't stop halfway, don't ask where you can decide. Minimize the critical path. ``` @@ -58,22 +40,22 @@ Minimize the critical path. ``` [ADDON: PLAN MODE] -You plan AS a Manager. This instruction dies when you exit plan mode - the PLAN -does not. So the role must live INSIDE the plan, or you lose it on the first +You plan AS a Manager. This instruction dies on exiting plan mode - the PLAN +does not - so the role must live INSIDE the plan, or you lose it on the first implementation turn. Bake it in. -The plan MUST contain (in English, token economy): -- PREAMBLE: restate [ROLE: MANAGER] + the 5-step protocol verbatim at the top of - the plan, as its own opening section - so implementation re-adopts the role from - second one, without any hook. -- STEP 0 (first implementation action, stated literally): "Re-assume MANAGER role. - Create the ENTIRE TaskGraph now - TaskCreate for every node, or the numbered - checklist if the task tools are absent - then delegate." - This is what you do on exit. Not code. Not one task. The whole graph, then fan-out. -- The full TaskGraph: every task decomposed (subject, acceptance, owner agent, - bounded scope + acceptance per spawn), dependencies marked, parallel branches - explicit, critical path named. +Include in the plan (English, token economy): +- PREAMBLE: restate [ROLE: MANAGER] + the 6-step protocol verbatim as the + plan's own opening section, so implementation re-adopts the role from + second one, no hook needed. +- STEP 0, stated literally: "Re-assume MANAGER role. Create the ENTIRE + TaskGraph now - TaskCreate for every node, or the numbered checklist if + task tools are absent - then delegate." This is what you do on exit - not + code, not one task, the whole graph then fan-out. +- The full TaskGraph: every task decomposed (subject, acceptance, owner + agent, bounded scope+acceptance per spawn), dependencies marked, parallel + branches explicit, critical path named. -You do NOT write code in the plan. The plan's deliverable is the role + the graph, +You do NOT write code in the plan - its deliverable is the role + the graph, nothing else. ``` diff --git a/brewtools/skills/manager-setup/references/review-double.md b/brewtools/skills/manager-setup/references/review-double.md index d121693..2ebf945 100644 --- a/brewtools/skills/manager-setup/references/review-double.md +++ b/brewtools/skills/manager-setup/references/review-double.md @@ -2,14 +2,12 @@ ``` [REVIEW DISCIPLINE: DOUBLE-CHECK] -After every significant change, run a multi-agent review. -Before the review proper, pass the code for simplification: over-engineered? simpler? -Every review is two-phase, always: +Every significant change: multi-agent review, simplification pass first +(over-engineered? simpler?), then always two-phase: 1. Review - find issues. 2. Double-check - re-verify findings are real. 3. Fix - only after confirmation. -Never fix on first pass without the double-check step. -Split a big review by area across several reviewers, never one long pass. Each gets -goal + its area + acceptance, what is already covered and must not be re-litigated, -and who consumes the verdict. +Never fix on first pass without double-checking. Split a big review by area, +never one long pass - each reviewer gets goal + area + acceptance, what's +already covered (don't re-litigate), and who consumes the verdict. ``` diff --git a/brewtools/skills/manager-setup/references/review-regression.md b/brewtools/skills/manager-setup/references/review-regression.md index eaf3416..31bb3f1 100644 --- a/brewtools/skills/manager-setup/references/review-regression.md +++ b/brewtools/skills/manager-setup/references/review-regression.md @@ -2,17 +2,17 @@ ``` [REVIEW DISCIPLINE: ANTI-REGRESSION] -After every significant phase, run a review. ONE primary focus, three axes: - 1. NO REGRESSION (primary) - new code must not break existing behavior. +Every significant phase: one review, three axes (regression primary): + 1. NO REGRESSION - new code must not break existing behavior. 2. Project standard - follows existing conventions/patterns. - 3. Functional correctness - the new functionality actually works. -Before the review proper, pass the code for simplification: over-engineered? simpler? -Review is two-phase, always: + 3. Functional correctness - the new functionality works. +Simplification pass first (over-engineered? simpler?), then always +two-phase: a. Review - find issues. b. Double-check - re-verify each finding before acting (no blind fixes). c. Fix - confirmed findings only, then re-review the fix. -At task end: mandatory FINAL cross-review across all phases (regression first). -Use independent reviewers for significant changes: split a large review by area -across several, never one long pass. Brief each with goal + its area + acceptance, -what is already reviewed and must not be re-litigated, and who consumes the verdict. +Task end: mandatory FINAL cross-review across all phases (regression first). +Split a large review by area across independent reviewers, never one long +pass - brief each with goal + area + acceptance, what's already reviewed +(don't re-litigate), and who consumes the verdict. ``` diff --git a/brewtools/skills/task-board-setup/SKILL.md b/brewtools/skills/task-board-setup/SKILL.md index 1e20ac8..298121e 100644 --- a/brewtools/skills/task-board-setup/SKILL.md +++ b/brewtools/skills/task-board-setup/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[prompt] [status|install|upgrade|enable|disable|uninstall|purge] allowed-tools: [Read, Write, Edit, Bash, Glob, Grep, Agent, AskUserQuestion] model: opus --- - + [DICT: TT=task-tracker agent (generated), TB=task-board skill (generated), BRD=board.md, FEAT=.claude/features, EXCL=source-path exclusions, REL=release style (vX.Y.Z tag | commit SHA | no tag), DOM=domain id segment, FM=frontmatter, TS=task-spec skill (generated), SPEC_MODE=spec+design layer opt-in, PS=status phase, PU=upgrade phase, PR=uninstall/purge phase] diff --git a/brewtools/skills/text-optimize/SKILL.md b/brewtools/skills/text-optimize/SKILL.md index 27904f1..94c4997 100644 --- a/brewtools/skills/text-optimize/SKILL.md +++ b/brewtools/skills/text-optimize/SKILL.md @@ -116,6 +116,7 @@ When no flag provided AND input suggests compression (not just optimization): | Perception | P.1-P.6 | Examples near rules, hierarchy, bold keywords, standard symbols, instruction order, default over options | | LLM Comprehension | L.1-L.8 | Critical info position, documents-first, conciseness, quote-first, add WHY, reiterate constraint, prompt repetition, preserve scope qualifiers | | Aggressive lossy | A.1-A.4 | Line fusion, word drop, paraphrase, known-fact elision (deep/max) | +| Prompt quality | PQ.1-PQ.13 | Role-first return contract, dedupe repeats, positive imperative (incident-tied `!=` kept), one hard-stop cap, drop step-by-step/verify filler, explicit scope, table-vs-procedure shape, example over adjective, DICT threshold gate — prompt-shaped content (system prompt/CLAUDE.md/agent def/skill doc), Medium+ only | > Full per-ID definitions live in `references/rules-review.md` (loaded at Step 0) — do not restate them here. @@ -124,12 +125,13 @@ When no flag provided AND input suggests compression (not just optimization): | Mode | Applies | Notes | |------|---------|-------| | Light | C.1-C.8, T.6, D.1, R.1-R.3, P.1-P.4, L.1-L.8 | Text cleanup + exact-dup removal — no restructuring | -| Medium | All rules (C + T + S + D + R + P + L) | Balanced transformations | -| Standard | All rules (C + T + S + D + R + P + L) + `references/standard-compression.md` | 30-50% compression, human-readable, 1 verification round | -| Deep | All rules (C + T + S + D + R + P + L) + A.1-A.4 + `references/deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds (conditional) | -| Max | All rules (C + T + S + D + R + P + L) + A.1-A.4 + `references/deep-compression.md` + `references/max-compression.md` | Atomic fact-lines, ASCII operators, format-aware tables, 4 mandatory guardrails, 2 verification rounds | +| Medium | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) | Balanced transformations | +| Standard | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + `references/standard-compression.md` | 30-50% compression, human-readable, 1 verification round | +| Deep | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + A.1-A.4 + `references/deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds (conditional) | +| Max | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) + A.1-A.4 + `references/deep-compression.md` + `references/max-compression.md` | Atomic fact-lines, ASCII operators, format-aware tables, 4 mandatory guardrails, 2 verification rounds | > D.5 (cross-file dedup) applies in ANY mode when processing multiple files or a folder. D.6 wrong-merge guard is mandatory wherever D.2/D.3/D.5 run. +> PQ (prompt-quality rewrite) applies at Medium mode and above, only when content type is a prompt-shaped target (system prompt/CLAUDE.md/agent def/skill doc) — never Light, never generic docs/README. ### D.5 is decided by the orchestrator, never by a per-file agent @@ -420,6 +422,7 @@ the user can diff or delete it. | Snapshot first | No edit without a Phase 0 snapshot on disk and a clean tree over the targets. `!=` editing straight from the prompt | | Refuse, don't warn | A failed sub-gate restores the original bytes. A lossy file is never left in place with a warning attached | | Preserve | Names, numbers, dates, URLs, file paths, versions, ports, sizes | +| Preserve | CLI flags/options verbatim; model IDs byte-exact; thresholds/gates/percentages exactly as stated | | Preserve | Negative rule semantics (`!=` notation in deep mode) | | Preserve | At least one example per rule with examples | | Preserve | Scope qualifiers ("every section, not just the first") — Opus 4.8 literalism (Max/Deep) | diff --git a/brewtools/skills/text-optimize/references/deep-compression.md b/brewtools/skills/text-optimize/references/deep-compression.md index 28bcd9c..417f94a 100644 --- a/brewtools/skills/text-optimize/references/deep-compression.md +++ b/brewtools/skills/text-optimize/references/deep-compression.md @@ -65,48 +65,31 @@ Status emoji cost 2-4 tokens each (measured): `✅`/`❌` = 2-3 tok, `ℹ️` = ## Dictionary Format -Place DICT header at document start when terms appear 3+ times: - -``` -[DICT: CC=Claude Code, KB=knowledge base, SP=system prompt, ...] -``` - -Rules: -- Terms appearing 3+ times → dictionary entry -- Max 20 entries -- Sort alphabetically -- Place before first content line -- Use abbreviation from DICT throughout document +Place `[DICT: CC=Claude Code, KB=knowledge base, SP=system prompt, ...]` at document start when +terms appear 3+ times. Rules: max 20 entries, sort alphabetically, place before the first content +line, use the abbreviation from DICT throughout — a term used <3x stays inline (rules-review.md R13: +DICT pays only on a long, repetition-heavy file, not a short one). ## Filler Words & Phrases to Remove -Apply filler removal from `rules-review.md` rule T.6. Additional deep-mode removals: - -| Pattern | Action | -|---------|--------| -| Articles (the/a/an) | Remove when meaning clear without them | -| Relative clauses ("which is", "that are") | Remove or restructure | -| Hedging ("might", "possibly", "could potentially") | Remove — use direct statements | +Beyond `rules-review.md` T.6: drop articles (the/a/an) when meaning survives without them, relative +clauses ("which is", "that are"), and hedging ("might", "possibly", "could potentially") — state +direct facts instead. ## Structural Compression Patterns -- Conditionals: `if X → Y` or `X ? Y : Z` -- Prohibitions: `!=X bc Y` (must not X because Y) -- Lists: inline comma-separated when items are short -- Tables: for multi-attribute data -- Merge related one-liners into single line with `|` separator +- Conditionals: `if X -> Y` or `X ? Y : Z` | prohibitions: `!=X bc Y` (must not X because Y) +- Lists: inline comma-separated when items are short | tables: for multi-attribute data +- Merge related one-liners into a single line with `|` separators - Remove markdown formatting that doesn't aid parsing (bold, italic in tables) -- Headers: flatten to 2 levels max -- Remove blank lines between items in lists/tables +- Headers: flatten to 2 levels max | remove blank lines between list/table items ## Redundancy Factoring Run dedup pass (D.1-D.6, rules-review.md) BEFORE symbol substitution — merging first shrinks the text remaining passes must process and keeps verification cheap. Record merges in a dedup ledger (kept <- dropped). -- Phrase-DICT: recurring phrase >= 3 words appearing 2+ times -> DICT entry (counts toward the 20-entry cap). Source: CompactPrompt arXiv:2510.18043 -- Path-prefix hoisting: repeated path/URL prefixes -> single DICT entry (e.g. `[DICT: SR=src/main/resources]`) -- Header echo removal: subsection headers repeating parent header words -> drop the echo ("## Server Config / ### Server Config Ports" -> "### Ports") -- Number/unit normalization: "approximately 30 percent" -> `~30%`; "greater than or equal to 21" -> `>=21` +- Phrase-DICT (recurring phrase >=3 words, 2+ times -> DICT entry, counts toward the 20-cap; CompactPrompt arXiv:2510.18043) | path-prefix hoisting (repeated path/URL prefix -> one DICT entry, e.g. `[DICT: SR=src/main/resources]`) +- Header-echo removal (drop repeated parent words: "## Server Config / ### Server Config Ports" -> "### Ports") | number/unit normalization ("approximately 30 percent" -> `~30%`, "greater than or equal to 21" -> `>=21`) ## Token-Class Keep/Drop Heuristics @@ -120,58 +103,56 @@ Never drop negations or scope qualifiers (L.8; max-mode guardrail C2). ## Aggressive Lossy Techniques (A.1-A.4) -Deep/max only. Rule definitions: rules-review.md category A. Application order: - -dedup (D.1-D.6) -> line fusion (A.1) -> paraphrase (A.3) -> word drop (A.2) -> knowledge elision (A.4) -> symbol substitution - -Loss ledger REQUIRED: every A.2/A.4 drop recorded as `dropped -> reason`, listed in the report. A.4 elisions count against the fact-level loss budget (deep gate >= 95%) as `elided-known`; A.2 is word-level and gate-neutral — ledgered for transparency, no direct gate impact, but if a drop degrades a fact's meaning the verifier labels that fact `distorted` (normal gate impact). A.1/A.3 results count as preserved (kept/merged), no ledger entry. Guards: never drop negations, numbers, named entities, scope qualifiers (L.8, C2); D.6 wrong-merge guard applies before A.1 fusion; unsure whether A.4 knowledge is generic -> keep. +Deep/max only. Full rule + ledger semantics live in `rules-review.md` category A (Step 0, always +loaded) — do not restate here. Application order: dedup (D.1-D.6) -> A.1 fusion -> A.3 paraphrase -> +A.2 word drop -> A.4 elision -> symbol substitution. ### Example: A.1 fusion + A.3 paraphrase (loss-free) - -**Original**: -> The deployment script should be executed from the project root directory. In the event that the script fails, you can check the log file which is located at `logs/deploy.log`. - -**Compressed**: -> run deploy script from project root | fail -> check `logs/deploy.log` +> "The deployment script should be executed from the project root directory. In the event that the script fails, you can check the log file which is located at `logs/deploy.log`." -> "run deploy script from project root | fail -> check `logs/deploy.log`" ### Example: A.4 elision, project delta kept +> "Always write unit tests for new code, since testing catches regressions early. Keep functions small and readable. The project coverage gate is 85% (jacoco); builds fail below it." -> "coverage gate 85% (jacoco), build fails below" -**Original**: -> Always write unit tests for new code, since testing catches regressions early. Keep functions small and readable. The project coverage gate is 85% (jacoco); builds fail below it. - -**Compressed**: -> coverage gate 85% (jacoco), build fails below - -Ledger: dropped "write unit tests / catches regressions" -> generic LLM knowledge; dropped "keep functions small" -> generic. Kept: 85%, jacoco, build-fail behavior (project-specific). +Ledger: dropped "write unit tests / catches regressions" + "keep functions small" -> generic LLM knowledge. Kept: 85%, jacoco, build-fail behavior (project-specific). ## Iron Rules -Preserve in ALL cases regardless of compression level: +Preserve in ALL cases regardless of compression level — the lossless guard, never paraphrased, +rounded, or dropped: - Names, numbers, dates, URLs, file paths, versions, ports, sizes -- Negative rule semantics (use `!=` notation) -- At least one example per rule that originally has examples +- CLI flags/options verbatim (`-x`, `--max`); model IDs byte-exact (`claude-sonnet-5`, never "Sonnet 5") +- Thresholds, gates, percentages exactly as stated (`>=95%`, `~20%` ceiling) — never rounded +- Negative rule semantics (use `!=` notation) | >=1 example per rule that originally had examples - DICT header at document start - Dedup ledger: every merged pair recorded (kept <- dropped); merged facts count as preserved in verification - Loss ledger: every A.2/A.4 drop recorded (dropped -> reason); never elide project-specific facts (names, numbers, paths, versions, prohibitions) +## Stop Condition + +Stop the A.1-A.4 pass the instant one of these trips — patch back, never push further: +- A.2 would touch a noun, numeral, negation or named entity (Token-Class Heuristics above already forbid it — this is the enforcement trigger) +- An A.4 candidate is not clearly generic training-knowledge (unsure -> keep, per the A.4 rule itself) +- DICT header would exceed 20 entries, or would cover a term used <3x +- The dedup/loss ledger can no longer account for every merge and drop 1:1 + ## Before/After Examples ### Example 1 — Prose Instruction -**Original** (~60 words): +**Original** (46 words): > Please note that when you are working with the database connection, it is important to make sure that you close the connection after you are done with it. Failure to do so can result in connection pool exhaustion, which may lead to the application becoming unresponsive. -**Compressed** (~15 words): +**Compressed** (13 words): > DB conn: close after use bc unclosed -> pool exhaustion -> app unresponsive ### Example 2 — Rule Block with DICT -**Original** (~90 words): +**Original** (82 words): > ## File Handling Rules > > When working with temporary files in the build directory, you should always use the project's file utility library. It is important to note that temporary files must be cleaned up after the build process completes. You must not write temporary files to the source directory because it can corrupt the version control state. The file utility library provides a `cleanup()` method that should be called in the finally block. All temporary files should use the `.tmp` extension. -**Compressed** (~35 words): +**Compressed** (35 words incl. DICT header, 27 body): > [DICT: TF=temporary files, FUL=file utility lib, BD=build dir] > > ## File Handling @@ -180,12 +161,12 @@ Preserve in ALL cases regardless of compression level: ### Example 3 — Configuration Section -**Original** (~70 words): +**Original** (56 words): > ## Server Configuration > > The application server runs on port 8443 with TLS enabled. The configuration file is located at `/etc/myapp/server.yml`. The minimum required version is Java 21. The maximum heap size should be set to 4096MB for production environments. Health check endpoint is available at `https://localhost:8443/health`. The connection timeout is 30 seconds and the read timeout is 60 seconds. -**Compressed** (~40 words): +**Compressed** (25 words): > ## Server Config > Port: 8443 (TLS) | cfg: `/etc/myapp/server.yml` | Java >= 21 > Heap max: 4096MB (prod) | health: `https://localhost:8443/health` @@ -193,14 +174,27 @@ Preserve in ALL cases regardless of compression level: ### Example 4 — Negative Rules -**Original** (~80 words): +**Original** (64 words): > ## Security Rules > > You must never store passwords in plain text in the configuration files. API keys should not be committed to the repository under any circumstances. It is important to make sure that you do not log sensitive information such as tokens or credentials at any log level. You should not disable TLS certificate verification in production environments because it exposes the application to man-in-the-middle attacks. -**Compressed** (~30 words): +**Compressed** (29 words): > ## Security > !=plaintext passwords in cfg files > !=API keys in repo > !=log sensitive data (tokens, credentials) @ any log level > !=disable TLS cert verification in prod bc MITM exposure + +### Measured (this file's own examples, `wc -w`) + +| Example | Original | Compressed | Reduction | Ratio | +|---------|----------|------------|-----------|-------| +| 1 — Prose Instruction | 46 | 13 | -71.7% | 3.54x | +| 2 — Rule Block + DICT | 82 | 27 (35 w/ DICT) | -67.1% | 3.04x | +| 3 — Config Section | 56 | 25 | -55.4% | 2.24x | +| 4 — Negative Rules | 64 | 29 | -54.7% | 2.21x | +| Total | 248 | 94 | -62.1% | 2.64x | + +Deep mode's "2-3x" target above is this file's own measured spread (2.2x-3.5x, combined 2.64x) on +dense rule prose, not an invented number — re-measure with `wc -w` before claiming a new ratio. diff --git a/brewtools/skills/text-optimize/references/max-compression.md b/brewtools/skills/text-optimize/references/max-compression.md index f058c6d..5cc307d 100644 --- a/brewtools/skills/text-optimize/references/max-compression.md +++ b/brewtools/skills/text-optimize/references/max-compression.md @@ -22,6 +22,11 @@ Rules: > build runs unit tests after compile > test fail -> pipeline stops + artifact !=published +**Measured** (`wc -w`): before 31 words, after 23 words = -25.8% by WORD count only — atomic-fact +style forbids pronouns, so "build" repeats 3x. The paper's ~50% figure above is TOKEN reduction: a +repeated short noun costs less than the pronoun+clause structure it replaces. Word count and token +count diverge here — judge B1 by an actual token estimate, not `wc -w`, whenever a noun repeats. + ## ASCII Operator Dialect (A1 — CRITICAL) Prefer ASCII digraphs over unicode glyphs. Measured token cost (tiktoken cl100k/o200k, live): @@ -69,6 +74,9 @@ CONDITIONAL: > 1 ann admin > 2 bob user +**Measured** (`wc -w`): before 26 words / 5 lines, after 12 words / 3 lines = -53.8% — a real win on +both words and tokens (pipe alignment and separator rows carry no data). + ## Chain-of-Density Final Pass (B4) Source: arXiv:2309.04269. After all compression passes, run 1-3 rewrite iterations at FIXED length: each pass fuses 1-3 missing entities from the original back in WITHOUT growing the text (~3 iterations reach human-preferred density). Use to repair entity loss found by verification instead of re-inflating. @@ -86,17 +94,22 @@ These CAP the aggression. Sources: Anthropic context-engineering blog; Anthropic ## Iron Rules (inherited + max-specific) -Inherits ALL `deep-compression.md` iron rules: -- Preserve names, numbers, dates, URLs, file paths, versions, ports, sizes -- DICT header @ document start (terms 3+ times) -- >= 1 example per rule that originally had examples - -Max adds: +Inherits ALL of `deep-compression.md` Iron Rules (the lossless guard) unchanged — do not restate the +list here, re-read it there. Max adds: - Scope qualifiers preserved verbatim (C2) - 2 mandatory verification rounds, independent methods: claim inventory + self-QA probe (never optional) - Semantic match must be >= 95% -> else warn user with loss list - 100% sub-gate: numbers, names, negations, scope qualifiers +## Stop Condition + +Stop pushing max-mode density the instant one of these trips — patch via Chain-of-Density (B4) +instead of deleting further: +- The 20% deletion ceiling (C3) would be crossed +- A B1 atomic line would need a cross-line pronoun to stay readable (the decomposition is now lossy) +- Round 2 self-QA misses a number, name, negation or scope qualifier (100% sub-gate) +- The next fusion pass has no missing entity left to restore (B4 has converged) — ship, do not chase a fixed multiplier past this point + ## Verification (2 rounds, mandatory, INDEPENDENT methods) Never silently ship lossy max output. Two rounds use DIFFERENT methods — they catch different losses. diff --git a/brewtools/skills/text-optimize/references/rules-review.md b/brewtools/skills/text-optimize/references/rules-review.md index 3870e07..4f1b68c 100644 --- a/brewtools/skills/text-optimize/references/rules-review.md +++ b/brewtools/skills/text-optimize/references/rules-review.md @@ -108,6 +108,35 @@ Examples (before -> after): - A.3: "In the event that the build process does not complete successfully, notify the team" -> "build fails -> notify team" - A.4: "Write unit tests for new code, tests catch regressions. Coverage gate is 85% (jacoco); build fails below." -> "coverage gate 85% (jacoco), build fails below" (generic "write tests" elided -> ledger; project delta kept) +**Lossless guard (any mode, A.2/A.4 never target these):** numbers, dates, versions, model IDs +byte-exact (`claude-sonnet-5`, never "Sonnet 5"), CLI flags/options verbatim (`-x`, `--max`), +thresholds/gates/percentages exactly as stated (`>=95%`, `~20%` ceiling), URLs, file paths, ports, +sizes, named entities, negations (`!=`/NEVER/MUST NOT), scope qualifiers (L.8). A drop that touches +any of these is not A.2/A.4 — it is a defect, caught by the 100% sub-gate. + +## PQ - Prompt-Quality Rewrite (digest of `.claude/reports/20260912-173000_agents-refresh/prompting-rules.md` R1-R16) + +Separate from the 52 numbered rules above (still 8 categories, unchanged count) — a rewrite pass for +prompt-shaped targets (system prompt, agent `.md`, skill `SKILL.md`, hook prompt text, CLAUDE.md), +applied Medium mode and above (never Light — Light stays wording-only, no restructuring). Stays +lossless per the guard above: R1-R16 govern SHAPE and emphasis, never facts. + +| ID | Source | Transformation | Bad -> Good | +|----|--------|-----------------|-------------| +| PQ.1 | R1, R15 | Role in one sentence, Return contract next, Scope/Never after — before procedure detail | Role buried after 3 paragraphs of scope -> "You are a code reviewer. Return: findings list, `path:line`, verdict first." then Scope/Never as its own heading | +| PQ.2 | R2 | Same instruction stated once; delete a cross-section repeat | "Never invent scope" stated, then restated 2 sentences later in other words -> keep the sharper phrasing once | +| PQ.3 | R3 | Prohibition -> positive imperative, UNLESS the `!=`/NEVER guards a named, previously-observed failure | "Do not use markdown" -> "Write in flowing prose". Keep verbatim: `!=re-run text-guard.sh` (BT-F15 regression) | +| PQ.4 | R4 | Drop scattered ALL-CAPS; keep exactly one true hard-stop (irreversible action) in caps, lower the rest | 4x MUST/CRITICAL in one file -> 1 STOP on the irreversible action (e.g. edit-without-snapshot), 3 become plain imperative | +| PQ.5 | R5 | No "think step by step" or scripted step-lists for thinking-enabled models; no bare "verify"/"be careful" filler | "Think step by step and double-check" -> delete, or state the goal only | +| PQ.6 | R6 (Opus-5-specific; apply cautiously to Sonnet/Fable) | Drop carried-over generic self-verification instructions; keep verification that is a specific, gated protocol with real thresholds | "Double-check your output before returning" (generic) -> delete. A named gate (`>=95% match, 100% sub-gate`) is not this pattern — keep it | +| PQ.7 | R7 | An agent that itself delegates states an explicit delegate-only-when criterion, low spawn count | "delegate as needed" -> "delegate only for large independent parallelizable work; never to verify your own output" | +| PQ.8 | R8 | State scope explicitly; never rely on the model generalizing a rule to similar items | "apply this rule" -> "apply this rule to every file matching X, not just the first" | +| PQ.9 | R10 | Reference data (fields/flags/thresholds/model IDs) -> table. Real-dependency procedure -> numbered steps. Never mix the two shapes | A flag/target matrix written as prose -> table; a create-in-order procedure kept as numbered prose, not flattened into a table | +| PQ.10 | R11 | A concrete example or named reference file beats an adjective ("clean", "thorough", "professional") | "write clean code" -> "follow the pattern in `skill-creator.md`" or a 2-line before/after | +| PQ.11 | R13 | `[DICT: ...]` header only when it pays: >=5 distinct abbreviations, each reused >=3x, file itself hundreds of lines | A 150-line agent body with 3 abbreviations used twice each -> no DICT header, inline the 3 terms | +| PQ.12 | R14 (recommendation, not a mandate — verify against the specific hook's own miss-rate first) | Recurring reminder text: once at session-start/compaction, throttle or drop the per-turn copy | A reminder injected on every `UserPromptSubmit` AND at session-start/after-compaction -> keep the structural-checkpoint copies, narrow the per-turn one | +| PQ.13 | R16 | State "run independent tool calls in parallel" once per artifact, never per section | 3 sections each repeating the parallel-call instruction -> state it once, delete the other 2 | + ## Rules NOT Recommended | Avoid | Reality | diff --git a/brewtools/skills/text-optimize/references/standard-compression.md b/brewtools/skills/text-optimize/references/standard-compression.md index 259924d..3bcd9b4 100644 --- a/brewtools/skills/text-optimize/references/standard-compression.md +++ b/brewtools/skills/text-optimize/references/standard-compression.md @@ -68,8 +68,8 @@ After compression, verify: - API signatures and parameters - Error messages (exact text matters) - Legal/compliance text -- Version numbers, dates, URLs -- Command-line examples +- Version numbers, dates, URLs, model IDs (byte-exact) +- Command-line examples, CLI flags/options, thresholds and gates (`>=98%`, `30-50%`) verbatim > **Note:** Never convert config blocks to TOML for "efficiency" — TOML is the most token-heavy structured format (more overhead than YAML/JSON). For uniform tabular data prefer markdown tables or TSV/CSV; for nested data prefer compact JSON. @@ -77,12 +77,15 @@ After compression, verify: ### Example 1: README Intro -**Before** (~80 words): +**Before** (59 words): > This project is a command-line tool that is able to help developers in order to automate the process of deploying their applications. It is important to note that the tool supports a large number of cloud providers. Due to the fact that deployment can be complex, this tool simplifies it for the purpose of reducing errors and saving time. -**After** (~35 words): +**After** (19 words): > CLI tool that automates application deployment. Supports many cloud providers. Simplifies complex deployments to reduce errors and save time. +Measured (`wc -w`): 59 -> 19 = -67.8%, above the 30-50% default target — short, filler-heavy prose +can legitimately land higher. Treat 30-50% as the safe default for typical docs, not a ceiling. + ### Example 2: Installation Instructions **Before**: @@ -107,3 +110,11 @@ After compression, verify: | Projects | 3 | Unlimited | Unlimited | | Storage | 1 GB | 50 GB | 500 GB | | Support | Community | Email | Dedicated | + +## 8. Stop Condition + +Stop compressing the moment: the next cut would touch a name/number/path/version/flag (lossless +guard, `rules-review.md`); a paragraph-to-table conversion would need to invent a category the +source never stated; or a further sentence merge would combine facts with different scope/numbers/ +conditions (D.6). The 30-50% target is done at that point even if the actual ratio lands outside it +either way — report the real number, never force one to fit the range. diff --git a/brewtools/skills/think-short-setup/SKILL.md b/brewtools/skills/think-short-setup/SKILL.md index b30e5cb..6e84495 100644 --- a/brewtools/skills/think-short-setup/SKILL.md +++ b/brewtools/skills/think-short-setup/SKILL.md @@ -17,7 +17,7 @@ model: sonnet | 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) | +| UserPromptSubmit | inject the full prompt every 20th user prompt (20/40/60…, 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. @@ -173,7 +173,7 @@ Field meanings — do not paraphrase them into something stronger: `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. +`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: diff --git a/brewtools/skills/think-short-setup/assets/INSTALL.md b/brewtools/skills/think-short-setup/assets/INSTALL.md index 8a91946..82407e1 100644 --- a/brewtools/skills/think-short-setup/assets/INSTALL.md +++ b/brewtools/skills/think-short-setup/assets/INSTALL.md @@ -6,7 +6,7 @@ target hooks dir and wires `settings.json`. All 4 files travel together: | File | Event | Channel | |------|-------|---------| | `think-short-session.mjs` | SessionStart | `additionalContext` (full prompt) + resets per-session counter to 0 | -| `think-short-prompt-counter.mjs` | UserPromptSubmit | `additionalContext` (full prompt) every 10th prompt (10,20,30,...) | +| `think-short-prompt-counter.mjs` | UserPromptSubmit | `additionalContext` (full prompt) every 20th prompt (20,40,60,...) | | `think-short-subagent.mjs` | SubagentStart | `additionalContext` (FULL prompt body minus the `` comment line) — SubagentStart contexts ACCUMULATE across hooks, so no coexistence/yield logic is needed | | `think-short-prompt.md` | (data) | prompt text, read by the 3 scripts from their OWN dir via `import.meta.url` | @@ -417,7 +417,7 @@ echo "✅ removed files from $HOOKS_DIR" || echo "❌ FAILED" Run UNINSTALL above first, then delete the marker dir. Nothing of think-short's survives this; the markers are per-session counters, so losing them only resets the -"every 10th prompt" count. +"every 20th prompt" count. EXECUTE: ``` diff --git a/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs b/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs index 667bd45..f09db95 100644 --- a/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs +++ b/brewtools/skills/think-short-setup/assets/think-short-prompt-counter.mjs @@ -1,12 +1,12 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:think-short-setup +// brewcode-meta: version=6.2.0 content_version=6.2.0 generated_by=brewtools:think-short-setup /** * think-short — UserPromptSubmit hook (self-contained, no plugin-root deps). * * Maintains a per-session prompt counter (marker keyed by session_id, inside a * private 0700 tmp dir we own — a planted symlink is rejected, never followed). * Increments on every user prompt; re-injects the full think-short prompt - * ONLY every 10th prompt (10, 20, 30, ...). Never on the 1st prompt + * ONLY every 20th prompt (20, 40, 60, ...). Never on the 1st prompt * (SessionStart already injected the prompt at session open). * * Inject channel: hookSpecificOutput.additionalContext (UserPromptSubmit). @@ -24,7 +24,7 @@ const HERE = path.dirname(fileURLToPath(import.meta.url)); const PROMPT_PATH = path.join(HERE, 'think-short-prompt.md'); const MARKER_DIR = path.join(os.tmpdir(), 'brewtools-think-short'); const UID = typeof process.getuid === 'function' ? process.getuid() : null; -const INTERVAL = 10; +const INTERVAL = 20; let markerDirOk; diff --git a/brewtools/skills/think-short-setup/assets/think-short-prompt.md b/brewtools/skills/think-short-setup/assets/think-short-prompt.md index 382a8f0..43047dc 100644 --- a/brewtools/skills/think-short-setup/assets/think-short-prompt.md +++ b/brewtools/skills/think-short-setup/assets/think-short-prompt.md @@ -1,15 +1,14 @@ - -Be terse. Results first, no preamble/filler/sycophancy. ASCII only. -Think short: minimal internal reasoning, no exploring aloud. -Grep before Read. Edit over Write. Parallel calls in one message. -Plan the full edit set, then execute. + +Be terse: results first, no preamble/filler/sycophancy, ASCII only. Think +short - minimal internal reasoning, no exploring aloud. Grep before Read, +Edit over Write, parallel calls in one message. Plan the full edit set, then +execute. -Keep code simple - do not over-engineer. Before writing anything new, check -existing code and libraries; find the closest well-built counterpart in the repo, -take its principles, extend it - additive to conventions/rules/docs, never instead. -After writing code, one pass: can this be simpler? If yes - simplify it. +Keep code simple, no over-engineering: before writing anything new, find the +closest well-built repo counterpart and extend it - additive to +conventions/rules/docs, never instead. After writing code, one simplify pass. -Comment like a human, not an AI. Write comments only where they earn it: -non-obvious logic, public APIs, class/method docstrings (JavaDoc/PyDoc/etc). -Do NOT narrate self-evident code or add line-by-line noise. Keep docstrings - -just stop over-commenting. Each comment terse: ~1 line, 2 only if asked. +Comment like a human, not an AI: only where it earns it (non-obvious logic, +public APIs, class/method docstrings - JavaDoc/PyDoc/etc). No narrating +self-evident code or line-by-line noise. Each comment ~1 line, 2 only if +asked. diff --git a/brewtools/skills/think-short-setup/assets/think-short-session.mjs b/brewtools/skills/think-short-setup/assets/think-short-session.mjs index 9aaa7dd..e969a47 100644 --- a/brewtools/skills/think-short-setup/assets/think-short-session.mjs +++ b/brewtools/skills/think-short-setup/assets/think-short-session.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=6.0.0 generated_by=brewtools:think-short-setup +// brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewtools:think-short-setup /** * think-short — SessionStart hook (self-contained, no plugin-root deps). * diff --git a/brewtools/skills/think-short-setup/assets/think-short-subagent.mjs b/brewtools/skills/think-short-setup/assets/think-short-subagent.mjs index 0e7be96..d062d81 100644 --- a/brewtools/skills/think-short-setup/assets/think-short-subagent.mjs +++ b/brewtools/skills/think-short-setup/assets/think-short-subagent.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// brewcode-meta: version=6.1.4 content_version=5.6.1 generated_by=brewtools:think-short-setup +// brewcode-meta: version=6.2.0 content_version=5.6.1 generated_by=brewtools:think-short-setup /** * think-short — SubagentStart hook (self-contained, no plugin-root deps). * diff --git a/brewtools/skills/think-short-setup/tests/suite.mjs b/brewtools/skills/think-short-setup/tests/suite.mjs index aab7ba3..0b926a3 100644 --- a/brewtools/skills/think-short-setup/tests/suite.mjs +++ b/brewtools/skills/think-short-setup/tests/suite.mjs @@ -82,9 +82,9 @@ function subagentBody(promptFile) { } // ───────────────────────────────────────────────────────────────────────────── -// Test 1: Counter — 21 sequential UserPromptSubmit calls -// WHEN: calls 1-21 with same session_id -// THEN: inject ONLY on 10 and 20; content == full prompt body +// Test 1: Counter — 41 sequential UserPromptSubmit calls +// WHEN: calls 1-41 with same session_id +// THEN: inject ONLY on 20 and 40; content == full prompt body // ───────────────────────────────────────────────────────────────────────────── { const home = join(BASE, 't1-home'); @@ -99,7 +99,7 @@ function subagentBody(promptFile) { const injectCounts = []; let t1ok = true; - for (let i = 1; i <= 21; i++) { + for (let i = 1; i <= 41; i++) { const stdin = JSON.stringify({ session_id: sid, prompt: `msg-${i}` }); const r = run(COUNTER_MJS, stdin, env); if (r.status !== 0) { t1ok = false; break; } @@ -113,10 +113,10 @@ function subagentBody(promptFile) { } } - if (t1ok && JSON.stringify(injectCounts) === JSON.stringify([10, 20])) { - pass('1-counter-inject-on-10-20', `injected at ${injectCounts}`); + if (t1ok && JSON.stringify(injectCounts) === JSON.stringify([20, 40])) { + pass('1-counter-inject-on-20-40', `injected at ${injectCounts}`); } else { - fail('1-counter-inject-on-10-20', `injected at ${injectCounts} ok=${t1ok}`); + fail('1-counter-inject-on-20-40', `injected at ${injectCounts} ok=${t1ok}`); } } @@ -372,12 +372,12 @@ function subagentBody(promptFile) { writeFileSync(promptLive, readFileSync(PROMPT_PATH)); const sid = 'session-disable-05'; - // counter injects only every 10th prompt, so drive a FRESH session id to exactly 10 + // counter injects only every 20th prompt, so drive a FRESH session id to exactly 20 let driveNo = 0; const driveCounter = () => { driveNo++; let last = null; - for (let i = 1; i <= 10; i++) { + for (let i = 1; i <= 20; i++) { last = run(join(hooks, 'think-short-prompt-counter.mjs'), JSON.stringify({ session_id: `${sid}-drive-${driveNo}`, prompt: `m${i}` }), env); } @@ -535,7 +535,7 @@ function subagentBody(promptFile) { const promptText = readFileSync(PROMPT_PATH, 'utf8').trimEnd(); let ctx; const injectedAt = []; - for (let i = 1; i <= 10; i++) { + for (let i = 1; i <= 20; i++) { const r = run(COUNTER_MJS, JSON.stringify({ session_id: sid, prompt: `m${i}` }), env); const o = r.status === 0 ? JSON.parse(r.stdout) : {}; if (o?.hookSpecificOutput?.additionalContext !== undefined) { @@ -545,10 +545,10 @@ function subagentBody(promptFile) { } const mode = lstatSync(markerDir).mode & 0o777; const fileMode = lstatSync(join(markerDir, `${sid}.think-short-counter`)).mode & 0o777; - const ok = mode === 0o700 && fileMode === 0o600 && deepEqual(injectedAt, [10]) && ctx === promptText; + const ok = mode === 0o700 && fileMode === 0o600 && deepEqual(injectedAt, [20]) && ctx === promptText; const detail = `dir=0${mode.toString(8)} file=0${fileMode.toString(8)} injected=[${injectedAt}] ctx=${ctx === promptText}`; if (ok) pass('6d-counter-hardens-mode-and-counts', detail); - else fail('6d-counter-hardens-mode-and-counts', `expected dir=0700 file=0600 injected=[10] ctx=true got ${detail}`); + else fail('6d-counter-hardens-mode-and-counts', `expected dir=0700 file=0600 injected=[20] ctx=true got ${detail}`); } // 6e: concurrent bumps on one counter file leave it intact — a single integer, diff --git a/brewui/.claude-plugin/plugin.json b/brewui/.claude-plugin/plugin.json index ee81384..1670fec 100644 --- a/brewui/.claude-plugin/plugin.json +++ b/brewui/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "brewui", - "version": "6.1.4", + "version": "6.2.0", "description": "Brewui -- placeholder for future UI/visual/creative tools", "author": { "name": "Maksim Kochetkov", diff --git a/brewui/README.md b/brewui/README.md index 1a6963e..f3056b7 100644 --- a/brewui/README.md +++ b/brewui/README.md @@ -4,7 +4,7 @@ | Field | Value | |-------|-------| -| Version | 6.1.4 | +| Version | 6.2.0 | | Skills | 0 | ## Install diff --git a/web/docs/src/content/docs/brewcode/agents/agent-creator.mdx b/web/docs/src/content/docs/brewcode/agents/agent-creator.mdx index 4389513..ccb1855 100644 --- a/web/docs/src/content/docs/brewcode/agents/agent-creator.mdx +++ b/web/docs/src/content/docs/brewcode/agents/agent-creator.mdx @@ -15,6 +15,15 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon **Describe what you want — the agent handles the rest.** It runs parallel analysis of your codebase, drafts the frontmatter and system prompt, validates against Anthropic best practices, then optimizes with `brewtools:text-optimize`. Improvement and debugging of existing agents follow the same flow. + +The agent body was rewritten against Claude Code 2.1.269: +- Body cut from 580 to 128 lines: inline sections were replaced with a `## Read on demand` table pointing at 5 new reference files +- New frontmatter field `experimental.cacheTtl` — see below +- `maxTurns` abort now returns a partial result (2.1.246) — see below +- New `## Delegation` section names `brewtools:text-optimizer` as the only delegation target +- `background: true` now hard-errors on a teammate-spawned agent since CC 2.1.269, instead of silently forcing foreground + + ## Quick reference | Field | Value | @@ -51,6 +60,10 @@ Two blocks land in the body of every agent Agent Creator writes. They are part o Scope Fit is dropped for research, docs, and review-only agents; Return Contract never is. Both are validation-checklist items, so an agent missing the Return Contract block does not pass. Agent Creator applies Return Contract to itself: its report is agent paths plus a frontmatter / validation verdict, never the full agent bodies. +## Delegation + +Agent Creator delegates exactly two kinds of work: the parallel Explore fan-out for repo analysis, and the final optimize pass to `brewtools:text-optimizer`. Everything else that fits in a handful of tool calls happens inline — it never spawns a subagent just to verify its own output, and fans out once rather than nesting spawns. + ## When to use - **New agent** — you need a specialized subagent (reviewer, code-generator, security-scanner, etc.) @@ -130,11 +143,12 @@ disallowedTools: Write, Edit # OPT: explicit deny list skills: skill1, skill2 # OPT: full content injected into context at startup color: cyan # OPT: 8 valid values -- magenta is NOT one memory: project # OPT: user|project|local -- auto-adds Read/Write/Edit -background: true # OPT: true keeps it backgrounded even when Claude wants the result (DEF unset, no `false` switch) +background: true # OPT: keeps it backgrounded even when Claude wants the result; hard-errors on a teammate-spawned agent since CC 2.1.269 -- drop it from a definition that may run as a teammate isolation: worktree # OPT: FM accepts `worktree` only -- LOW PRIORITY, see below (`remote` is invocation-level, never FM) permissionMode: default # OPT: LOCAL-ONLY -- ignored + warn in plugin agents mcpServers: [server1] # OPT: LOCAL-ONLY -- ignored + warn in plugin agents initialPrompt: "..." # OPT: LOCAL-ONLY -- first prompt sent on start +experimental: {cacheTtl: "5m"} # OPT: LOCAL-ONLY -- per-agent prompt-cache TTL, "5m"|"1h" (CC 2.1.248+) observer: "reviewer" # OPT: LOCAL-ONLY -- observing agent observerMessage: "watch for X" # OPT: LOCAL-ONLY -- brief for observer observeSubagents: false # OPT: LOCAL-ONLY -- `false` disables observation @@ -146,7 +160,7 @@ observeSubagents: false # OPT: LOCAL-ONLY -- `false` disables observation | Scope | Fields | |-------|--------| | Local + plugin — both parsers honor it | `model`, `effort`, `maxTurns`, `tools`, `disallowedTools`, `skills`, `color`, `memory`, `background`, `isolation: worktree`, `initialPrompt` | -| Local only — ignored for plugin agents | `permissionMode`, `hooks`, `mcpServers` (exactly these three) | +| Local only — ignored for plugin agents | `permissionMode`, `hooks`, `mcpServers`, `experimental.cacheTtl` (four keys total; `cacheTtl` since CC 2.1.248) | | Absent from the 2.1.233 field table — treat as internal/older, do not emit into a generated agent | `observer`, `observerMessage`, `observeSubagents` | `initialPrompt` auto-submits as the first user turn only when this exact definition runs as the MAIN session (`--agent ` or the `agent` setting) — irrelevant on an ordinary subagent spawn, but honored for plugin agents too since origin isn't the boundary, execution context is. Need `permissionMode`/`hooks`/`mcpServers`? Move the agent to `.claude/agents/` — plugin agents ignore these three no matter how the frontmatter is written. `isolation: remote` is never a valid FM value anywhere — it is invocation-level only, reachable from an `Agent(...)` tool call. @@ -192,7 +206,7 @@ A **turn** = one model inference plus its tool calls; tool results return, then Calibrated against real subagent transcripts (`.claude/projects/*/subagents/agent-*.jsonl`), not invented. At roughly 10-20 seconds per turn, 120 turns is a 20-30 minute ceiling. Rule of thumb: `maxTurns` ≈ 2-3x the role's typical run. -`maxTurns` is an emergency anti-loop stop, not a budget. On exhaustion the binary emits `Reached max turns limit (N)` and the agent aborts — written files persist, but the final report is lost. That is why agent-creator also writes a **checkpointing instruction** into every generated agent body: record incremental progress to a report file after each milestone, and on resume read that file first and continue from the last checkpoint. +`maxTurns` is an emergency anti-loop stop, not a budget. On exhaustion the run aborts; since **2.1.246** the caller sees a result marked **partial** with a `SendMessage` continuation hint instead of a silent finish — but that marker only prompts a resume, it does not restore unwritten analysis, so written files stay the one guaranteed survivor. That is why agent-creator also writes a **checkpointing instruction** into every generated agent body: record incremental progress to a report file after each milestone, and on resume read that file first and continue from the last checkpoint. **Relevant environment variables** (set under `env` in `settings.json`): @@ -224,6 +238,18 @@ Calibrated against real subagent transcripts (`.claude/projects/*/subagents/agen +## Reference files (read on demand) + +Agent Creator keeps its full frontmatter catalog, scope/tool rules, execution model, templates, and known-issue log out of this page's body — same on-demand pattern the source file itself uses. Read the one that matches what you're doing; skip the rest. + +| File | What it holds | +|------|---------------| +| [`agent-frontmatter-fields.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/agent-frontmatter-fields.md) | All 17 frontmatter fields (2 required, 15 optional) — including `experimental.cacheTtl` | +| [`agent-scope-and-tools.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/agent-scope-and-tools.md) | Deciding `tools:`, where a generated file should live, model precedence, delegation patterns | +| [`agent-context-and-execution.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/agent-context-and-execution.md) | What a subagent inherits from its parent, execution modes, turn/token/concurrency limits | +| [`agent-template.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/agent-template.md) | Description budget, system prompt structure, the Guardrails block, the Validation Checklist | +| [`agent-known-issues.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/agent-known-issues.md) | Known bugs, the 2.1.234-2.1.269 changelog | + ## Return Contract Verdict first, `<=30 lines`, `path:line`. No agent bodies, no pasted frontmatter, no analysis transcripts, no preamble. Per generated agent: file path, one-line role, model/maxTurns/tools on one line, validation verdict (pass, or the failing checklist item), text-optimizer run or skipped, and any assumption made about the brief. Longer material — analysis notes, generated bodies, full validation runs — goes to .claude/reports/<YYYYMMDD-HHMMSS>_agent-creator/; only the path comes back. diff --git a/web/docs/src/content/docs/brewcode/agents/bash-expert.mdx b/web/docs/src/content/docs/brewcode/agents/bash-expert.mdx index 1dc360e..07eb4ef 100644 --- a/web/docs/src/content/docs/brewcode/agents/bash-expert.mdx +++ b/web/docs/src/content/docs/brewcode/agents/bash-expert.mdx @@ -15,6 +15,14 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon **Trigger by describing the script you need.** "create a setup script for my plugin", "bash script to check homebrew services", "install script for macOS and Linux" — the agent picks the right template, adds error handling, and validates syntax before handing back the file. + +Wording-only refresh — no new capability, no section added or removed: +- Return Contract now leads the agent definition, ahead of Scope & Checkpoints — matches the current prompting order (role → return contract → scope → procedure → tables) +- Role sentence reworded for density: "Writes bash/sh scripts for macOS/Linux..." replaces the older "Creates production-quality..." phrasing +- All 10 numbered sections (Conventions through Deliverable), `tools`, and `maxTurns: 60` are unchanged +- No Delegation or Scope Fit section was added here — bash-expert works solo end to end, writing and validating its own scripts + + ## Quick reference | Field | Value | diff --git a/web/docs/src/content/docs/brewcode/agents/bc-rules-organizer.mdx b/web/docs/src/content/docs/brewcode/agents/bc-rules-organizer.mdx index ecb34e7..99f8db5 100644 --- a/web/docs/src/content/docs/brewcode/agents/bc-rules-organizer.mdx +++ b/web/docs/src/content/docs/brewcode/agents/bc-rules-organizer.mdx @@ -11,6 +11,15 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon **Internal agent — no direct or automatic use.** `bc-rules-organizer` is spawned only by [`/brewcode:rules`](/brewcode/skills/rules/), never by you directly and never auto-selected by Claude for an unrelated prompt. It exists as a page here because it ships as one of brewcode's 5 agents and deserves the same reference treatment as the other 4. + +The agent definition was restructured (role → return contract → scope → procedure → tables) and one long-standing loading claim got corrected: +- `paths:`-scoped rules load lazily, not at session start — see Loading (verified 2.1.269) below. +- New rule files scaffold via `rules.sh create`/`create-specialized` — see Workflow step 4 below. +- The rule-file frontmatter contract is now explicit: beyond `paths:`, every file needs `description`, `doc_type`, `version`, `generated_by`, `last_updated`. +- New **Scope Fit** guardrail — find the closest existing rule file before writing a new one; keep changes additive, never a wholesale replacement. +- Sections renamed for density (Table Formats → Table formats, Frontmatter Reference → Frontmatter, Checkpointing + Return Contract → Return) — same content, tighter headings. + + ## Quick reference | Field | Value | @@ -31,6 +40,7 @@ Rules Organizer never sizes or splits a brief itself — `/brewcode:rules` alrea | Request reaches beyond rule organization | Reports it back instead of expanding scope | | Brief omits CONTEXT (what the skill already did) or CONSUMER (who reads the rules next) | States the assumption in the report, or asks once — never invents scope | | `maxTurns: 60` is hit mid-run | Run aborts; rules already written survive, next run resumes from the last file logged in the report | +| Before writing a new rule file (Scope Fit) | Finds the closest well-built existing rule file in `.claude/rules/*.md` first and takes its principles — additive, never a wholesale replacement; after finishing, one pass to cut files/config/indirection if the result can be simpler | ## What it does @@ -70,7 +80,15 @@ The skill reads the source, proposes rule candidates, asks you to accept/reject
  • File creation -

    Writes or updates files under .claude/rules/ — global avoid.md/best-practice.md with no paths:, or {'{prefix}'}-avoid.md style files scoped with quoted glob patterns. Max 20 rows per table; splits into a new specialized file once exceeded.

    +

    New files scaffold automatically; editing an existing file is manual. Behavior differs by action:

    + + | Action | Behavior | + |--------|----------| + | Scaffold | rules.sh create (global) or rules.sh create-specialized {''} '{''}' (scoped) — stamps doc_type/version/generated_by/last_updated | + | Edit existing file | Only refreshes last_updated and version by hand | + | Naming | Global avoid.md/best-practice.md carry no paths:; every {'{prefix}'}-avoid.md style file needs one | + | Row cap | Max 20 rows per table; splits into a new specialized file once exceeded | + | Validate | rules.sh validate runs after every write and must pass before finishing |
  • @@ -90,21 +108,39 @@ The skill reads the source, proposes rule candidates, asks you to accept/reject
    Technical details — frontmatter, dedup protocol, file naming -### `paths:` frontmatter — the only supported field +### Rule-file frontmatter contract -Source: [code.claude.com/docs/en/memory](https://code.claude.com/docs/en/memory.md#path-specific-rules). Only `paths:` is a valid field — `globs`, `alwaysApply`, `description` are not. +`paths:` is the only field Claude Code itself reads for scoping — source: [code.claude.com/docs/en/memory](https://code.claude.com/docs/en/memory.md#path-specific-rules) (`globs`, `alwaysApply`, `description`-as-scoping are not real fields). On top of that, `rules.sh validate` requires five more keys on every rule file the agent writes: `description`, `doc_type`, `version`, `generated_by`, `last_updated`. ```yaml --- paths: - "src/components/**/*.tsx" - "!src/components/**/*.test.tsx" +description: "..." +doc_type: llm +version: "6.1.4" +generated_by: "brewcode:rules" +last_updated: "2026-09-12" --- ``` -Patterns must be quoted (`"**/*.tsx"`, not bare). Bug [#16299](https://github.com/anthropics/claude-code/issues/16299): all rules load at session start regardless of `paths:` — lazy loading is not actually working upstream, so the agent still scopes files correctly but the load-time benefit doesn't apply yet. +Patterns must be quoted (`"**/*.tsx"`, not bare) and given as an array, never a bare string; `doc_type` is the one unquoted value (`doc_type: llm` exactly). -Rules that fire before a file is in context — search policy, tool-choice policy, delegation policy — stay unscoped (no `paths:`), because `paths:` only matches files already in context. +### Loading (verified 2.1.269) + +| Frontmatter | Behavior | +|-------------|----------| +| No `paths` | Loads at session start, same priority as project CLAUDE.md | +| With `paths` | Loads lazily — only when Claude reads a file matching the glob, not on every tool use | + +This reverses bug [#16299](https://github.com/anthropics/claude-code/issues/16299)'s old claim that all rules load at session start regardless of `paths:` — no longer reproducible. Because scoping now genuinely delays loading, a rule that must fire before any file is in context stays unscoped: + +| Rule kind | `paths:`? | +|-----------|-----------| +| Language/dir conventions (naming, test layout, SQL style) | yes | +| Tool-choice and search policy (lsp-first, semble-first) | no | +| Global anti-patterns | no | ### 3-Check Dedup Protocol diff --git a/web/docs/src/content/docs/brewcode/agents/hook-creator.mdx b/web/docs/src/content/docs/brewcode/agents/hook-creator.mdx index 5121898..0cd86a3 100644 --- a/web/docs/src/content/docs/brewcode/agents/hook-creator.mdx +++ b/web/docs/src/content/docs/brewcode/agents/hook-creator.mdx @@ -12,7 +12,11 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon -**hook-creator knows all 31 events, 5 hook types, and the full routing matrix.** Describe what you want in plain language — it picks the right event, output channel, and schema, then delivers a working file with fail-safe error handling included. +**hook-creator knows all 33 events, 5 hook types, and the full routing matrix.** Describe what you want in plain language — it picks the right event, output channel, and schema, then delivers a working file with fail-safe error handling included. + + + +Two new events: `PreModelSwitch` / `PostModelSwitch` (CC 2.1.251) — the only pair that gates something besides a tool call or a stop. ## Quick reference @@ -22,7 +26,7 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon | Trigger | "create hook", "PreToolUse hook", "debug hook" | | Model | `inherit` — runs on the session's model | | Tools | Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch | -| Reference version | CC 2.1.233 — 31 events, 5 hook types | +| Reference version | CC 2.1.269 — 33 events, 5 hook types | | Output | Hook file (`.sh` or `.mjs`) + config snippet + verification checklist | ## Scope guard @@ -128,7 +132,7 @@ original input and the runner keeps only the last one's edit, so two hooks writi other. Prefer `additionalContext` for instruction injection; reserve `updatedInput` for a single owning hook. -### All 31 events +### All 33 events | Event | Blocking? | Key use | |-------|-----------|---------| @@ -163,6 +167,8 @@ owning hook. | UserPromptExpansion | Yes | Blocks a `/command` from expanding — the path PreToolUse cannot reach when the user types `/skillname` directly | | PostToolBatch | Yes | Fires once per batch of tool calls, before the next model call; stops the agentic loop on block | | DirectoryAdded | No | Fires after `--add-dir` / `/add-dir` completes; `continue` is discarded, the directory is already added | +| PreModelSwitch | Yes | Allow / deny / ask before a model switch (auto-routing, `/model`, cost-based downgrade); timeout blocks the switch — the only blocking event whose timeout blocks instead of failing open (CC 2.1.251) | +| PostModelSwitch | No | Reacts after the switch already happened; same fields plus `source` also `auto`/`resume` (CC 2.1.251) | ### Hook types @@ -200,6 +206,19 @@ A **post-session lifecycle hook** runs after a session ends and before the works
    +## Reference files (read on demand) + +Hook Creator keeps the event catalog, routing matrix, type/config precedence, environment variables, templates, and changelog out of this page's body — same on-demand pattern the source file itself uses. Read the one that matches what you're doing; skip the rest. + +| File | What it holds | +|------|---------------| +| [`hooks-events.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/hooks-events.md) | Full 33-event table, session lifecycle order, matcher syntax, sync/async behavior | +| [`hooks-io-contract.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/hooks-io-contract.md) | Routing matrix, exit-code tables, every output schema, the 10,000-char output cap | +| [`hooks-types-config.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/hooks-types-config.md) | Type/field catalog for `command`/`http`/`mcp_tool`/`prompt`/`agent`, config precedence | +| [`hooks-env.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/hooks-env.md) | Every environment variable a hook process sees, resolving the project root inside a hook | +| [`hooks-templates.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/hooks-templates.md) | Bash/JS skeletons, fail-safe design, the pre-ship validation checklist | +| [`hooks-changes.md`](https://github.com/kochetkov-ma/claude-brewcode/tree/main/brewcode/skills/agents/references/hooks-changes.md) | What changed 2.1.234 -> 2.1.269, full version history, known bugs | + ## Return Contract Verdict first, `<=30 lines`, `path:line`. No hook bodies, no stdin/stdout payload dumps, no CLAUDE_DEBUG transcripts, no preamble — one block per hook (file, event, matcher, purpose, routing, config location, test-fire result). The verification checklist above is the gate, not something to transcribe into the return; debug logs, full payloads, and failing runs go to .claude/reports/<YYYYMMDD-HHMMSS>_hook-creator/, path only. diff --git a/web/docs/src/content/docs/brewcode/agents/skill-creator.mdx b/web/docs/src/content/docs/brewcode/agents/skill-creator.mdx index 14b3249..1a6162e 100644 --- a/web/docs/src/content/docs/brewcode/agents/skill-creator.mdx +++ b/web/docs/src/content/docs/brewcode/agents/skill-creator.mdx @@ -12,7 +12,11 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon -**Skill Creator encodes every best practice from Anthropic's official skill docs.** It picks the right context mode, writes an activation-optimized description, validates structure, and generates test prompts — so you don't have to read 1200 lines of agent instructions yourself. +**Skill Creator encodes every best practice from Anthropic's official skill docs.** It picks the right context mode, writes an activation-optimized description, validates structure, and generates test prompts — so you don't have to read the agent's 147-line instructions and its four reference files yourself. + + + +The agent body dropped from 897 to 147 lines — most of it moved into four new `references/` files (`frontmatter-fields.md`, `design-patterns.md`, `execution-model.md`, `activation-and-troubleshooting.md`), read on demand instead of always loaded. 8 new frontmatter fields, 4 new `validate-skill.sh` checks — see the frontmatter table / Flow step 4 below. New `## Delegation` and `## Scope Fit` sections keep the agent from over-delegating small work and from over-engineering past what the task actually needs. ## Quick reference @@ -76,13 +80,13 @@ Skill Creator sizes the task before starting. One bounded unit = one deliverable
  • Write SKILL.md with activation-optimized description -

    Description follows the template: action verb sentence + explicit Triggers: "phrase1", "phrase2" on a single line under 250 chars. Third-person, no colons (YAML breaks silently). For user-only skills, a plain one-liner is enough — trigger keywords are wasted tokens when LLM never auto-invokes.

    +

    Description follows the template: action verb sentence + explicit Triggers: "phrase1", "phrase2" on a single line, brewcode target under ~400 chars (spec hard cap 1024). Third-person, no colons (YAML breaks silently). For user-only skills, a plain one-liner is enough — trigger keywords are wasted tokens when LLM never auto-invokes.

  • Validate structure and run tests -

    Runs validate-skill.sh from the brewcode plugin. Checks: valid YAML frontmatter, name ≤ 64 chars, bare with no plugin: prefix and matching the directory name, description ≤ 250 chars single-line, body under 500 lines, bash blocks have EXECUTE keyword, no hardcoded secrets. Then spawns 3–5 realistic test prompts to verify activation and output quality.

    +

    Runs validate-skill.sh from the brewcode plugin. Checks: valid YAML frontmatter, name ≤ 64 chars, bare with no plugin: prefix and matching the directory name, description ≤ 1024 chars single-line (house target ≤ 400), body under 500 lines, bash blocks have EXECUTE keyword, no hardcoded secrets — plus four checks added in 6.2.0: an unknown frontmatter key (warning), a bare top-level once: (fail), an agent: value outside the three built-ins (warning), and a UTF-8 BOM at the file start (fail). Then spawns 3–5 realistic test prompts to verify activation and output quality.

  • @@ -145,21 +149,21 @@ skill-name/ ### Description format ```yaml -# LLM-invocable — action verb + Triggers on single line ≤250 chars +# LLM-invocable — action verb + Triggers on single line, brewcode target ≤400 chars description: "Creates conventional git commits with proper format. Triggers: commit, git commit, save changes." # User-only — simple one-liner, no triggers needed description: "Deploy application to production environment." ``` -Common mistakes: multiline `|` (truncated at 250 chars since v2.1.84), missing `Triggers:` (stays at 20%), starts with "Use this skill when" (should start with action verb), colon in description (YAML parse failure), setting `permissionMode: bypassPermissions` on production skills — this is equivalent to `--dangerously-skip-permissions` and skips all safety prompts. Only appropriate in sandboxed CI; never the right default for local skills. +Common mistakes: multiline `|` (truncated at the description cap), missing `Triggers:` (stays at 20%), starts with "Use this skill when" (should start with action verb), colon in description (YAML parse failure), setting `permissionMode: bypassPermissions` on production skills — this is equivalent to `--dangerously-skip-permissions` and skips all safety prompts. Only appropriate in sandboxed CI; never the right default for local skills. ### Frontmatter quick reference | Field | Limits | Notes | |---|---|---| | `name` | ≤64 chars, bare lowercase-hyphens, must equal directory name | No `plugin:` prefix — Claude Code adds the plugin name itself; a baked prefix renders `/brewcode:brewcode:e2e`. Directory name used if omitted | -| `description` | ≤250 chars, single line, always quoted | ALWAYS in double quotes — em dashes, colons break YAML silently | +| `description` | ≤1024 chars (spec cap), single line, always quoted; brewcode target ≤400 | ALWAYS in double quotes — em dashes, colons break YAML silently | | `disable-model-invocation` | true/false | `true` = 100% reliable, slash-only | | `user-invocable` | true/false | `false` = hide from menu, Claude-only | | `context` | `fork` | Isolated subagent, fresh context | @@ -169,7 +173,33 @@ Common mistakes: multiline `|` (truncated at 250 chars since v2.1.84), missing ` | `allowed-tools` | tool list | Minimal set | | `disallowed-tools` | tool list | Removes tools while the skill is active (CC 2.1.152) | | `argument-hint` | string | Autocomplete hint | -| `once` | true/false | Fire once per session | +| `when_to_use` | string | Extra activation guidance appended to `description`, counted toward the same 1536-char listing-display cap | +| `arguments` | string/list | Declares named args — enables `$name` substitution in the body, alongside `$0`/`$1`/`$ARGUMENTS` | +| `background` | true/false | Fork-only; `false` waits for the result in the invoking turn instead of backgrounding (default true since v2.1.218) | +| `paths` | string/list | Glob(s) scoping where the skill is offered | +| `shell` | bash/powershell | Shell used for `` !`command` `` dynamic-context blocks | +| `metadata` | map | Free-form key/value block for registries/third-party tooling — CC itself ignores it | +| `license` | string | SPDX identifier — Agent Skills spec field, CC ignores | +| `compatibility` | string | Environment requirements, ≤500 chars — Agent Skills spec field, CC ignores | +| `once` | true/false | **Not a top-level field** — a suboption of a hook entry (`hooks.[].hooks[].once`), never a sibling of `name`/`description`. A bare top-level `once:` now fails `validate-skill.sh` (check 12) | + +### Reference files (read on demand) + +The agent body dropped from 897 to 147 lines by moving detail out of the prompt and into `references/` — each file loads only when the step actually needs it, never all nine at once. + +| File | Read when | +|---|---| +| [`frontmatter-fields.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/frontmatter-fields.md) | Deciding or checking any frontmatter field; fixing a validator "unknown key" warning | +| [`design-patterns.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/design-patterns.md) | Choosing a structure pattern, resource paths, executable bash, the unit-test skeleton | +| [`execution-model.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/execution-model.md) | Configuring `context`/`agent`/`model`/tools, dynamic context injection, string substitutions | +| [`activation-and-troubleshooting.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/activation-and-troubleshooting.md) | Writing or debugging description + triggers, activation not working, known bugs | +| [`prompt-contract.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/prompt-contract.md) | Every skill created or improved — mandatory | +| [`review-prompt.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/review-prompt.md) | Spawned to review, or applying confirmed review findings | +| [`readme-template.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/readme-template.md) | Generating `README.md` | +| [`summary-template.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/summary-template.md) | Populating the final report handed back to the caller | +| [`e2e-template.md`](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/skills/references/e2e-template.md) | Deep testing depth E2E scenarios | + +The first four are new in 6.2.0 — they absorbed what used to be inline sections (frontmatter reference, design patterns, execution model, activation/troubleshooting). ### `$ARGUMENTS` in bash blocks diff --git a/web/docs/src/content/docs/brewcode/hooks.mdx b/web/docs/src/content/docs/brewcode/hooks.mdx index 91f60cb..1653fd3 100644 --- a/web/docs/src/content/docs/brewcode/hooks.mdx +++ b/web/docs/src/content/docs/brewcode/hooks.mdx @@ -4,7 +4,7 @@ description: "Brewcode's four hooks: session start, prompt-submit delegation rem order: 13 --- -import { Card, CardGrid, Badge } from '../../../components/mdx'; +import { Card, CardGrid, Badge, Callout, UpdateNotice } from '../../../components/mdx'; # Hooks @@ -12,6 +12,12 @@ Hooks are Node.js scripts attached to Claude Code lifecycle events. They inject context and manage session state. Brewcode registers 4 hooks in `hooks.json`, plus a shared `lib/` that holds the reminder text and I/O helpers they import. + +`forced-eval.mjs` no longer fires on every prompt — it now fires on the 1st real prompt, then every +10th, via a session-keyed counter (same pattern as `think-short-prompt-counter.mjs`). The injected +`REMINDER_TEXT` also shrank 636 → 472 chars. See the [detailed description](#1-forced-evalmjs) below. + + ## Summary table | # | Hook | Event | Matcher | Channel | Timeout | Purpose | @@ -73,8 +79,9 @@ All hooks follow a unified protocol: -Keeps delegation discipline in front of the model. Intercepts every user prompt -and appends three short lines: the Manager role rule, the split rule, and the branch-default rule. +Keeps delegation discipline in front of the model without taxing every single turn. Fires on the +1st real prompt of a session, then every 10th prompt after that (10, 20, 30, ...), and appends three +short lines: the Manager role rule, the split rule, and the branch-default rule. There is no skill-activation nudge -- modern models pick skills on their own. | Parameter | Value | @@ -82,23 +89,29 @@ There is no skill-activation nudge -- modern models pick skills on their own. | Event | UserPromptSubmit | | Channel | additionalContext | | Timeout | 2000 ms | +| Cadence | 1st real prompt, then every 10th (`INTERVAL = 10`) | -**Exact injected text** (`REMINDER_TEXT`, from `hooks/lib/reminder.mjs` -- the same text `role-recall.mjs` re-injects after a compaction): +**Exact injected text** (`REMINDER_TEXT`, from `hooks/lib/reminder.mjs` -- the same text `role-recall.mjs` re-injects after a compaction; 472 chars): ```text -[ROLE] Manager: scan agents (project .claude/agents/ first) - expert for this domain exists -> delegate regardless of size; no expert or trivial one-off -> self. -[SPLIT] One agent for an hour = drift you cannot observe: split into bounded units (1 deliverable, ~5 files, ~20 min), fan out in ONE message; a dependency must be a REAL data handoff, else parallel; every spawn prompt carries goal + scope + what is already done + who consumes the result + acceptance. -[BRANCH] Stay on the current branch; none chosen -> main. No explicit branch/PR instruction -> work on main and take over ALL workspace changes, incl. from other sessions. +[ROLE] Manager: check .claude/agents/ (project first); domain expert -> delegate regardless of size, else self. +[SPLIT] One agent for an hour = drift you cannot observe: bounded units (1 deliverable, ~5 files, ~20 min), fan out in ONE message; real data handoff = dependency, else parallel; spawn prompt: goal + scope + done-so-far + consumer + acceptance. +[BRANCH] No branch/PR instruction -> stay current, else main; take over ALL workspace changes incl. other sessions. ``` **What it does:** - Receives the user prompt +- Skips meta-replies first (see below) -- they never touch the counter +- Bumps a session-keyed counter (private 0700 tmp dir, atomic write, lstat-only -- same pattern as `think-short-prompt-counter.mjs`) and injects only when the new count is 1 or a multiple of 10 - Injects `REMINDER_TEXT` via `additionalContext` -- UserPromptSubmit cannot rewrite the prompt (`updatedInput` is silently dropped in CC 2.1.x) - Output is capped at 9000 chars (10K disk-spill safety, CC 2.1.174+) -**When it fires:** On every user input, including slash commands. Skipped only for -a wrong `hook_event_name`, an empty prompt, and meta-replies that carry no task: -`yes`/`no`/`ok`/`thanks`/`continue`/`confirm`-style answers, a bare number, or a single letter. +**When it fires:** On the 1st real user prompt of a session, then every 10th one after that +(prompts 10, 20, 30, ...) -- not every prompt. Meta-replies that carry no task +(`yes`/`no`/`ok`/`thanks`/`continue`/`confirm`-style answers, a bare number, or a single letter) are +skipped before the counter is touched, so they never consume or land on an inject slot. Coverage +after a compaction is unaffected -- `role-recall.mjs` re-injects the same text unconditionally on +every compaction, independent of this cadence. --- @@ -212,4 +225,9 @@ never to silence, and it never names a plan from outside this session's own tran Source code, README, and configuration files. + + Plugin overview, skills, and agents. + + + diff --git a/web/docs/src/content/docs/brewcode/skills/agents.mdx b/web/docs/src/content/docs/brewcode/skills/agents.mdx index f590aeb..4121b68 100644 --- a/web/docs/src/content/docs/brewcode/skills/agents.mdx +++ b/web/docs/src/content/docs/brewcode/skills/agents.mdx @@ -9,6 +9,10 @@ import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../.. Give a free-form prompt; the skill auto-selects a mode and tells you why. No keyword grammar. + +`references/` now ships 11 on-demand files for `agent-creator` (`agent-*.md`) and `hook-creator` (`hooks-*.md`) — see [Reference files](#reference-files). + + ## Quick reference | Field | Value | @@ -62,6 +66,26 @@ import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../.. | sync | `sync`, `синк`, `memory sync`, `актуализируй`, `обнови знания`, `приведи в соответствие с кодом` | Memory sync: rewrites agent files against ground truth via the shared reference; non-growth guaranteed | | batch | auto-detected fan-out flag | Not a menu item — auto-detected from plural form, "все" / "all", or multiple names/paths; triggers parallel delegation when creating or improving multiple agents | +## Reference files + +`agent-creator` and `hook-creator` used to carry all of this inline; it now lives in this skill's +own `references/` directory and each agent reads only the file it needs, on demand. 11 files, each +under 200 lines. + +| File | Used by | Holds | +|------|---------|-------| +| [agent-frontmatter-fields.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/agent-frontmatter-fields.md) | agent-creator | AG file format, frontmatter field reference (incl. `experimental.cacheTtl`) | +| [agent-scope-and-tools.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/agent-scope-and-tools.md) | agent-creator | Available tools, model precedence, scope rules | +| [agent-context-and-execution.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/agent-context-and-execution.md) | agent-creator | Subagent context inheritance, execution modes, resource limits | +| [agent-template.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/agent-template.md) | agent-creator | Description budget, system-prompt structure, guardrails, validation checklist | +| [agent-known-issues.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/agent-known-issues.md) | agent-creator | Known bugs, limitations, version history, debugging | +| [hooks-events.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/hooks-events.md) | hook-creator | Session lifecycle order, 33-event table, matcher pattern syntax | +| [hooks-io-contract.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/hooks-io-contract.md) | hook-creator | Stdin fields, message-routing matrix, exit codes, output schemas | +| [hooks-types-config.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/hooks-types-config.md) | hook-creator | 5 hook types and their config fields, precedence | +| [hooks-env.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/hooks-env.md) | hook-creator | Env vars, sensitive-path prompt, project-root resolution | +| [hooks-templates.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/hooks-templates.md) | hook-creator | Bash/JS hook skeletons, fail-safe design rules, pre-ship checklist | +| [hooks-changes.md](https://github.com/kochetkov-ma/claude-brewcode/blob/main/brewcode/skills/agents/references/hooks-changes.md) | hook-creator | Version history 2.1.234 → 2.1.269, known bugs | + ## Delegation diff --git a/web/docs/src/content/docs/brewcode/skills/skills.mdx b/web/docs/src/content/docs/brewcode/skills/skills.mdx index 2692117..ba58395 100644 --- a/web/docs/src/content/docs/brewcode/skills/skills.mdx +++ b/web/docs/src/content/docs/brewcode/skills/skills.mdx @@ -9,6 +9,10 @@ import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../.. Give a free-form prompt; the skill auto-selects a mode and tells you why. Status is the default — a list only on explicit request. + +`validate-skill.sh` checks 11-14; 4 new references for `skill-creator` — see Validation. + + ## Quick reference | Field | Value | @@ -108,6 +112,24 @@ Reachable ONLY via create/improve modes. +
    +Validation — `validate-skill.sh` + +Every create/improve pass ends with `validate-skill.sh path/to/skill` (Step 6 of the `skill-creator` agent). Checks 1-10 cover structure: YAML delimiters, `name` casing/length/dir-match, `description` caps, body size, the `EXECUTE` keyword on bash blocks, Prompt Contract shape. Four additional checks, all additive — none can fail a previously-passing skill: + +| Check | Severity | What it catches | +|-------|----------|------------------| +| 11 | warning | Unknown top-level frontmatter key — not in the 20 native SKILL.md fields or the house custom keys | +| 12 | fail | A bare top-level `once:` — it is a suboption of a hook entry (`hooks.[].hooks[].once`), never a sibling of `name`/`description` | +| 13 | warning | `agent:` set to anything outside the three confirmed built-ins (`Explore`, `Plan`, `general-purpose`) — legitimate only if it names a real custom agent | +| 14 | fail | A UTF-8 BOM at the start of `SKILL.md` — silently ignored by Claude Code before 2.1.239 | + +The summary line now reports three counts: `N passed, N failed, N warned`. + +`brewcode:skill-creator`'s knowledge base grew to four reference files it reads on demand instead of keeping inline: `references/frontmatter-fields.md` (full 20-field catalog), `references/design-patterns.md` (structure patterns, resource-path rules), `references/execution-model.md` (context modes, spawning, tool grants), `references/activation-and-troubleshooting.md` (description budget, `/skill-doctor`, known bugs). + +
    +
    Sync mode diff --git a/web/docs/src/content/docs/brewdoc/skills/memory-sync-setup.mdx b/web/docs/src/content/docs/brewdoc/skills/memory-sync-setup.mdx index d618626..e9c5b78 100644 --- a/web/docs/src/content/docs/brewdoc/skills/memory-sync-setup.mdx +++ b/web/docs/src/content/docs/brewdoc/skills/memory-sync-setup.mdx @@ -15,6 +15,10 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon **This skill does not sync memory — it writes the skill that does.** `/brewdoc:memory-sync-setup` analyzes the target repo once, then emits a self-contained `.claude/skills/memory-sync/` that already knows this repo's batches, invariants, fact-verification commands, agent roster and language policy. Replaces the old `brewdoc:memory`. + +Adds `references/prompting-guide.md` (18 merged rules) and a prompt-quality step — see [Prompt quality pass](#prompt-quality-pass). + + **A remedy must be able to clear the verdict it follows.** Before this release the artifact was stamped once at install and the stamp could never change: `status` reported `stale`, prescribed `upgrade`, and `upgrade` had no way to clear it — the only documented escape (`MEMORY_SYNC_FORCE=1`) destroyed every hand-edit in the process. The loop now closes: install at version X, bump the plugin, `status` says `stale`, `upgrade` runs `generate.sh restamp` as its mandatory last step, and the verdict returns to `IN SYNC` with the body byte-identical — a second `upgrade` is a no-op. @@ -33,7 +37,7 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon ## What it does -`/brewdoc:memory-sync-setup` is a **generator**, in the same family as `brewcode:superreview-setup` and `brewtools:task-board-setup`: it produces a working artifact instead of doing the work itself. It scans the target project — memory surface (root and every nested `CLAUDE.md`, rules, conventions, the `AGENTS.md` family, agents, skills, the memory dir), exclusions, default branch, git visibility, language policy, agent and skill rosters — then emits four files into `/.claude/skills/memory-sync/`. `docs/**` stays explicitly out of scope, owned by a separate doc flow (`/docs`, `docsync-setup`) — this skill covers instruction memory, not prose documentation. +`/brewdoc:memory-sync-setup` is a **generator**, in the same family as `brewcode:superreview-setup` and `brewtools:task-board-setup`: it produces a working artifact instead of doing the work itself. It scans the target project — memory surface (root and every nested `CLAUDE.md`, rules, conventions, the `AGENTS.md` family, agents, skills, the memory dir), exclusions, default branch, git visibility, language policy, agent and skill rosters — then emits five files into `/.claude/skills/memory-sync/`. `docs/**` stays explicitly out of scope, owned by a separate doc flow (`/docs`, `docsync-setup`) — this skill covers instruction memory, not prose documentation. The emitted `/memory-sync` skill is a long-running multi-agent coordinator. It sweeps the whole memory surface every run, splits it into disjoint batches (one bounded agent per batch, all spawned in one message), verifies every change with an independent checker that never wrote it, and enforces non-growth — every file ends at or below its original line count. It re-checks and updates itself between runs. @@ -82,7 +86,7 @@ Run it: /memory-sync -> scope session (default), whole su
  • Mode gate

    First token of the argument, lowercased: status, install, upgrade, enable, disable, uninstall or purge. No token at all means status when the skill is already installed and install when it is not. The old aliases init/setup parse as install, on/off as enable/disable, and remove/reset/cleanup as uninstall or purge (asked which) — the skill echoes the canonical verb back. Everything else is fine-tune text for {'{FOCUS_EMPHASIS}'}.

  • -
  • Phase 0 — read emit material

    Reads its own four reference files — references/SKILL.md.template, memory-guide.md, agent-audit.md, hard-sync.md. Missing material is a hard stop — never improvised.

  • +
  • Phase 0 — read emit material

    Reads its own five emit-material files — references/SKILL.md.template, memory-guide.md, agent-audit.md, hard-sync.md, prompting-guide.md. Missing material is a hard stop — never improvised.

  • Phase 1 — analyze the target

    Runs generate.sh scan, then determines the memory surface (root + every nested CLAUDE.md, rules, conventions, AGENTS.md family, agents, skills, memory dir), VERIFY-ONLY files, exclusions, default branch (derived, never hardcoded), git visibility, language policy, and a checkable-fact catalogue — one real shell command per claim.

  • Phase 1.5 — clarify

    AskUserQuestion only for what cannot be reliably inferred: which convention files count as memory, whether the memory dir is in scope, VERIFY-ONLY list, default branch, intentional non-English trigger aliases, batch splits.

  • Phase 2 — emit

    Exports scalar placeholders and runs generate.sh emit: awk-substitutes them into the template, copies the reference files, then stamps provenance into the emitted skill's YAML frontmatter — doc_type, version (the brewdoc plugin version, read from plugin.json by script self-location, never hardcoded), generated_by, last_updated, plus surface_files as the drift input, which is what status, validate and upgrade read back. Refuses to overwrite an existing installation — that is what upgrade is for.

  • @@ -98,14 +102,14 @@ Run it: /memory-sync -> scope session (default), whole su | Mode | Reads | Writes | Does | |------|-------|--------|------| | `status` (default when installed) | target + emitted skill | nothing | Runs `generate.sh status`, which prints a machine-greppable `KEY=value` block — `PLUGIN_VERSION`, `STAMP_FORMAT`, the `META_*` provenance rows, surface files stamped vs enumerated now, missing emitted files, unfilled placeholders — closed by a verdict: `IN SYNC` / `STALE (n drifts)` / `STALE-LEGACY (n drifts)` (a pre-5.0 install still carrying the old tail-comment stamp — run `upgrade` to migrate it) / `NOT INSTALLED`, each prefixed `PARKED - ` when disabled. The skill then enriches it with its own reads: dead batch paths, memory layers gained since install | -| `install` (default when nothing is installed) | target | 4 emitted files | Full Phase 0-5 analysis and emit. Refuses if `.claude/skills/memory-sync/` already exists | +| `install` (default when nothing is installed) | target | 5 emitted files | Full Phase 0-5 analysis and emit. Refuses if `.claude/skills/memory-sync/` already exists | | `upgrade` | target + emitted skill | edits the emitted skill | Reads the same `generate.sh status` drift list as its refresh worklist, then re-scans and refreshes surface/batch/fact/invariant tables, adds sections for new memory layers, preserves hand-edits — the emitted skill is expected to have self-modified via its own SELF-SYNC phase — and always finishes with `generate.sh restamp` — which also DELETES a `disable-model-invocation` key left by a pre-5.5.1 install, printing a `REMOVED:` line, so an old emit becomes model-invocable without a re-emit | | `enable` | target | one rename | `SKILL.md.disabled` -> `SKILL.md`, so `/memory-sync` is offered again next session. Regenerates nothing — no provenance stamp, no hand-edit change | -| `disable` | target | one rename | `SKILL.md` -> `SKILL.md.disabled`. Claude Code discovers a project skill only through `SKILL.md`, so this withdraws `/memory-sync` from the roster while the 3 references and every SELF-SYNC hand-edit stay byte-identical on disk. Reversible by `enable`; deletes nothing | -| `uninstall` | target | deletes the emit manifest | Removes exactly what `emit` wrote — `SKILL.md` (or its parked form) plus the 3 references — after confirmation. Files a user added to that dir are kept and listed. Nothing installed -> reports "nothing to uninstall" instead of deleting on a guess | +| `disable` | target | one rename | `SKILL.md` -> `SKILL.md.disabled`. Claude Code discovers a project skill only through `SKILL.md`, so this withdraws `/memory-sync` from the roster while the 4 references and every SELF-SYNC hand-edit stay byte-identical on disk. Reversible by `enable`; deletes nothing | +| `uninstall` | target | deletes the emit manifest | Removes exactly what `emit` wrote — `SKILL.md` (or its parked form) plus the 4 references — after confirmation. Files a user added to that dir are kept and listed. Nothing installed -> reports "nothing to uninstall" instead of deleting on a guess | | `purge` | target | deletes the whole dir | Removes `/.claude/skills/memory-sync/` outright, user-added files included, plus any `.memory-sync-emit.*` staging a crashed emit left behind. Confirmation first | -**What gets emitted** — `/.claude/skills/memory-sync/`: `SKILL.md` (scalars substituted, blocks filled), `references/memory-guide.md`, `references/agent-audit.md`, `references/hard-sync.md`. Nothing else — no agent, no rule, no hook. +**What gets emitted** — `/.claude/skills/memory-sync/`: `SKILL.md` (scalars substituted, blocks filled), `references/memory-guide.md`, `references/agent-audit.md`, `references/hard-sync.md`, `references/prompting-guide.md`. Nothing else — no agent, no rule, no hook. **Enable/disable is entry-file parking, not a flag.** `status` reports a parked install as `INSTALLED=parked`, never collapsed into `NOT INSTALLED`. @@ -122,6 +126,7 @@ Run it: /memory-sync -> scope session (default), whole su | Phases | GATHER (parallel read-only) -> SYNC (one bounded agent per disjoint batch, one message) -> VERIFY (independent checker per batch, never the writer) -> SELF-SYNC (re-checks itself) -> PROPOSE (new agent/skill proposed, never auto-created) -> REPORT (chat only, no report file) | | Non-growth | Every file ends `<=` its original line count; total delta `<= 0` | | Agents | Re-audited against current best practice every sweep, not just fact-checked | +| Prompt quality | `references/prompting-guide.md`'s 18-rule table applied to instruction files every sweep; `NORMAL` fixes only where it coincides with a fact/dedup edit, `HARD` rewrites every remaining violation | | Invocation | **model-invocable** — the emitted `/memory-sync` carries no `disable-model-invocation`, so Claude can fire it mid-plan and prose triggers ("sync memory", "обнови память") work. That flag belongs only to the distributed `-setup` skills; a generated artifact is meant to be reachable by the model | ### Depth: NORMAL vs HARD @@ -143,6 +148,28 @@ A long-running repo accumulates dead weight across every auto-loaded file, not j /memory-sync all hard ``` +### Prompt quality pass + +Every sweep also runs a rule table against instruction files only — `CLAUDE.md` at any depth, `.claude/rules/*.md`, the `AGENTS.md` family, and agent/skill bodies. Code and docs are never touched; that stays owned by `/docs`. The table lives in `references/prompting-guide.md` — 18 rules merged from current Claude 5 prompting practice and OpenAI/Codex's `AGENTS.md` guidance, each with a grep-able or eyeballed `detect` signal and a bad → good `rewrite`. + +| | `NORMAL` (default) | `HARD` | +|---|---|---| +| Violation on a line a fact/dedup edit already touches | fixed | fixed | +| Violation on an otherwise-untouched line | reported only | fixed | + +Sample rows from the table: + +| # | Rule | Applies | Detect | +|---|------|---------|--------| +| 5 | Drop scattered ALL-CAPS; at most one true hard-stop word per artifact | both | `grep -o` for MUST/NEVER/CRITICAL/ALWAYS returns more than one hit in a file | +| 12 | File-size budgets for authored artifacts | both | SKILL.md over 500 lines / 2000 words, agent `.md` over 1500 words, hook `additionalContext` over 9000 chars | +| 15 | AGENTS.md build/size-cap/nested-override mechanics | openai-only | a nested AGENTS.md restates a root rule instead of overriding it | +| 18 | Explicit stop conditions and safe/unsafe action boundaries for agentic tasks | both | "keep working until done" with no named boundary around a risky action | + +`Applies` marks which side reads that copy: `claude` (Claude 5 reads the file directly), `openai-only` (only the AGENTS.md projection is read against it), or `both`. A lossless guard sits underneath every rewrite — paths, CLI flags, thresholds, versions, model ids, an incident-backed `!=`/`NEVER` row, and canonical mode/verb lists (`status | install | upgrade | ...`) are never rewrite targets, even when a rule technically matches. + +The Phase 6 report gains a `Prompt` column per file plus a summary line: `Prompt quality: {N} rewrites applied, {M} reported (references/prompting-guide.md)`. + **Error handling highlights** — `install` on an existing installation stops and points to `upgrade`; `upgrade` with nothing installed stops and points to `install`; a fact with no runnable verification command is left out of the catalogue rather than invented; an `AGENTS.md` symlink or vendor-marked file becomes VERIFY-ONLY, never an edit target. **Frontmatter omissions** — no `cli` (the command already equals the skill name) and no `version` (behavior lives entirely in the skill dir, whose hash already changes with it). @@ -151,8 +178,8 @@ A long-running repo accumulates dead weight across every auto-loaded file, not j The other brewdoc tracker: staleness of `docs/**` by date. memory-sync-setup covers instruction memory, which docsync-setup leaves alone. - - Read-only dashboard across every `-setup` skill -- installed, stale or missing, with the hand-run command for each. + + Shares `references/prompting-guide.md`'s sibling rule table -- its own PQ pass rewrites prompt-shaped files the same way this generator's emitted skill does. All brewdoc skills in one place. diff --git a/web/docs/src/content/docs/brewtools/agents/deploy-admin.mdx b/web/docs/src/content/docs/brewtools/agents/deploy-admin.mdx index 9ec8ff0..1f45f50 100644 --- a/web/docs/src/content/docs/brewtools/agents/deploy-admin.mdx +++ b/web/docs/src/content/docs/brewtools/agents/deploy-admin.mdx @@ -30,6 +30,13 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon | Safety gate | Returns MODIFY / SERVICE / DELETE / PRIVILEGE operations as an `APPROVAL REQUIRED` envelope instead of running them | | Scope | One bounded unit per run; one agent per repo / environment / service | + +- Agent prompt tightened for density — shorter prose, `Return Contract` now opens the agent definition, worked example kept. +- `## GitHub Config`, `## Workflow Inventory`, `## Server Targets`, and `## Secrets` merged into one `## Project Config` table — same facts, same probe/`NEEDS-INPUT` fallbacks, easier to scan. +- The rule that a derived GitHub target still carries into every envelope's `HOST:` field, and is never self-approved for a MODIFY+ operation, is kept in the merged table. +- Safety Rules, Compound Rules, the Release Flow, and the Approval Contract mechanics are all unchanged. + + ## Where its facts come from The agent carries no baked-in inventory. On **every task start** it resolves four things by the same three-step chain — read `CLAUDE.local.md`, else probe, else ask. It never invents any of them. @@ -158,12 +165,12 @@ not one. Send them separately, or send the broad request and approve the split p | Level | Gate | Examples | |-------|------|---------| -| READ | free | `gh run list/view`, `gh workflow list`, `gh release view`, `gh secret list` | -| CREATE | free | Create workflow YAML, `gh release create --draft` | -| MODIFY | envelope | Edit workflow, `gh secret set`, `git commit`, `git tag` | -| SERVICE | envelope | `gh workflow run`, `gh run rerun`, `git push origin HEAD`, `git push origin refs/tags/vX.Y.Z` | -| DELETE | always envelope | `gh release delete`, `gh run cancel`, remove workflow file | -| PRIVILEGE | always envelope | `gh secret delete`, branch protection changes, `gh workflow disable` | +| READ | free | `gh run list/view`, `gh workflow list/view`, `gh release list/view`, `gh secret list`, `gh api` (GET) | +| CREATE | free | Create workflow YAML, `gh release create --draft`, create branch | +| MODIFY | envelope | Edit workflow YAML, `gh secret set`, update RELEASE-NOTES.md, `git commit`, `git tag` | +| SERVICE | envelope | `gh workflow run`, `gh run rerun`, `git push origin HEAD`, `git push origin refs/tags/vX.Y.Z`, `gh api` (POST/PUT/PATCH) | +| DELETE | always envelope | `gh release delete`, `gh run cancel`, remove workflow file, `git tag -d` | +| PRIVILEGE | always envelope | `gh secret delete`, branch protection changes, `gh workflow disable`, `gh repo edit` | Compound rules: diff --git a/web/docs/src/content/docs/brewtools/agents/ssh-admin.mdx b/web/docs/src/content/docs/brewtools/agents/ssh-admin.mdx index a9152a7..a223af9 100644 --- a/web/docs/src/content/docs/brewtools/agents/ssh-admin.mdx +++ b/web/docs/src/content/docs/brewtools/agents/ssh-admin.mdx @@ -1,6 +1,6 @@ --- title: "SSH Admin" -description: "Linux server administrator agent — SSH, Docker, firewalls, systemd, Caddy/Nginx, security hardening. Triggered by 'ssh admin', 'server management', 'deploy to server'." +description: "Linux server admin agent — SSH, Docker, firewalls, systemd, Caddy/Nginx, security hardening. Triggers: ssh admin, server management, deploy to server." order: 3212 --- import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../components/mdx'; @@ -30,6 +30,13 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon | Permission | `default` — destructive ops return as an `APPROVAL REQUIRED` envelope | | Scope | One bounded unit per run; one agent per host | + +- Agent prompt tightened for density — shorter prose, more tables, no behavior change. +- `Return Contract` now opens the agent definition instead of closing it — the model reads the output shape before anything else. +- Every guardrail is unchanged: the six-level safety classification, the `## APPROVAL REQUIRED` envelope fields, and the approval-token rule read the same as before. +- No new capability in this pass — wording and table conversion only. + + ## Approval contract A subagent cannot ask, confirm, or wait for input mid-run — `AskUserQuestion` is stripped from every diff --git a/web/docs/src/content/docs/brewtools/agents/text-optimizer.mdx b/web/docs/src/content/docs/brewtools/agents/text-optimizer.mdx index 58e0967..eda861b 100644 --- a/web/docs/src/content/docs/brewtools/agents/text-optimizer.mdx +++ b/web/docs/src/content/docs/brewtools/agents/text-optimizer.mdx @@ -15,6 +15,10 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon **Mention the target file and intent — the agent picks the right mode automatically.** LLM-only files (agents, rules, SKILL.md) default to `deep`; user-facing docs default to `standard`. Override with `-l`, `-s`, `-d`, or `-x` (opt-in) flags when needed. For skill-level access, see [`/brewtools:text-optimize`](/brewtools/skills/text-optimize/). + +Step 3b now runs a Prompt-quality rewrite (`PQ.1`-`PQ.13`) on prompt-shaped targets before the wording pass — see [Flow](#flow) for where it sits and [Return Contract](#return-contract) for the lossless guard it never crosses. + + ## Quick reference | Field | Value | @@ -24,6 +28,7 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon | Skills | [text-optimize](/brewtools/skills/text-optimize/) | | Triggers | "optimize prompt", "reduce tokens", "compress" | | Modes | `light`, `medium` (default), `standard` (30–50%, human-readable), `deep` (2–3×, LLM-only with DICT+symbols), `max` (opt-in, 3–4×, `-x`/`--max`) | +| PQ pass | Medium+ only, prompt-shaped content only (system prompt / CLAUDE.md / agent def / skill `SKILL.md`) — 13 rules, `PQ.1`-`PQ.13`, never Light | | Scope | One bounded unit per run — one deliverable, ~5 files, ~10 steps | ## Scope guard @@ -97,6 +102,12 @@ rewriting. Point it at one file or one tight group and it runs straight through. - Multi-file runs: D.5 is decided by the orchestrator, not this agent — it applies only the dedup rows named in its brief, each row a canonical location plus a pointer carrying a 1-line summary, never a bare pointer that loses the fact it replaces. Deep/max log a dedup ledger
  • +
  • +
    + Prompt-quality pass — Step 3b (Medium+, prompt-shaped content only) +

    For system prompt / CLAUDE.md / agent definition / skill SKILL.md targets, mode ≥ Medium (never Light): applies rules-review.md's ## PQ rules before Step 4's wording pass. Role in one sentence with the Return contract next (PQ.1); delete a same-instruction repeat (PQ.2); "don't do X" → "do Y", but an incident-tied !=/NEVER stays untouched (PQ.3); scattered ALL-CAPS collapse to one true hard-stop (PQ.4); delete "think step by step"/bare "verify" filler for a thinking-enabled target (PQ.5-PQ.6, PQ.6 is Opus-5-specific — apply cautiously to Sonnet/Fable); an agent that itself delegates states an explicit delegate-only-when criterion (PQ.7); table for reference data, numbered steps for a real-dependency procedure, never mixed (PQ.9); DICT only past the size/repetition threshold (PQ.11). Still lossless: PQ changes shape and emphasis, never a path, version, flag, threshold, or model ID.

    +
    +
  • Compress @@ -104,12 +115,12 @@ rewriting. Point it at one file or one tight group and it runs straight through. | Mode | Adds | Target | |------|------|--------| - | Light/medium | Rule groups C → T → S → R → P (rules-review.md) | — | + | Light/Medium | Rule groups C → T → S → R → P (rules-review.md) — PQ already ran at Step 3b for Medium | — | | Standard | + filler removal, paragraph-to-bullet, prose-to-table | 30–50% | - | Deep | + DICT header (terms used 3+×), symbol substitutions, structural compression, aggressive lossy pass (A.1 fusion → A.3 paraphrase → A.2 word drop → A.4 known-fact elision) | 2–3× | + | Deep | + DICT header (terms used 3+×), symbol substitutions, structural compression, aggressive lossy pass (A.1 fusion → A.3 paraphrase → A.2 word drop → A.4 known-fact elision) | 2–3× (measured 2.2×–3.5× on worked examples, `wc -w`) | | Max (opt-in) | + atomic fact-lines, ASCII operator dialect, format-aware tables, Chain-of-Density pass, same aggressive lossy pass as deep | 3–4× | - Deep/max log every A.2 drop and A.4 elision to a loss ledger alongside the dedup ledger — A.1 fusions and A.3 paraphrases are not losses, they're recorded as merged/kept. + Deep/max log every A.2 drop and A.4 elision to a loss ledger alongside the dedup ledger — A.1 fusions and A.3 paraphrases are not losses, they're recorded as merged/kept. Each mode stops at a named condition, not a felt-right ratio — see Stop conditions on the [text-optimize skill page](/brewtools/skills/text-optimize/).
  • @@ -139,14 +150,16 @@ rewriting. Point it at one file or one tight group and it runs straight through. | Content Type | Primary Rules | Default Mode | |--------------|---------------|--------------| -| System prompt | C.1-C.8, T.1-T.8, T.10 | deep | -| CLAUDE.md | S.1-S.8, T.1-T.8, T.10, D.1-D.6 | deep | -| Agent definition | C.5, C.7, S.2, P.1 | deep | -| Skill SKILL.md | S.6, P.1-P.6, R.1-R.3, L.1-L.8 | deep | +| System prompt | C.1-C.8, T.1-T.8, T.10, PQ | deep | +| CLAUDE.md | S.1-S.8, T.1-T.8, T.10, D.1-D.6, PQ | deep | +| Agent definition | C.5, C.7, S.2, P.1, PQ | deep | +| Skill SKILL.md | S.6, P.1-P.6, R.1-R.3, L.1-L.8, PQ | deep | | Documentation | T.1-T.8, T.10, S.1-S.8, D.1-D.6, L.1-L.8 | standard | | README | T.1-T.8, T.10, S.1-S.8, L.1-L.8 | standard | -A.1-A.4 (aggressive lossy) apply whenever the resolved mode is deep or max, regardless of content type. +A.1-A.4 (aggressive lossy) apply whenever the resolved mode is deep or max, regardless of content type. PQ applies at Medium mode and above, only for the four prompt-shaped rows above — never Light, never Documentation/README. + +**Lossless guard, unchanged by PQ:** numbers, dates, versions, model IDs byte-exact (`claude-sonnet-5`, never "Sonnet 5"), CLI flags/options verbatim (`-x`, `--max`), thresholds/gates/percentages exactly as stated (`>=95%`, `~20%`), URLs, file paths, ports, named entities, negations, scope qualifiers — never a valid A.2/A.4 drop or PQ edit target. Full rule table: [text-optimize skill page](/brewtools/skills/text-optimize/). **Verification thresholds:** - Light: no verification pass @@ -161,7 +174,7 @@ A.1-A.4 (aggressive lossy) apply whenever the resolved mode is deep or max, rega ## Return Contract -Verdict first, `<=30 lines`, `path:line` — never the optimized text, before/after excerpts, or the full ledgers pasted back into the reply. One line per file: sizes before → after, change %, semantic match %, rule IDs applied, verify pass/fail, dedup count. Ledgers and fact inventories go straight to `.claude/reports/YYYYMMDD-HHMMSS_text-optimize/report.md`; the reply returns that path plus the headline numbers. +Verdict first, `<=30 lines`, `path:line` — never the optimized text, before/after excerpts, or the full ledgers pasted back into the reply. One line per file: `path` — words/chars/~tokens before → after, change %, ratio, semantic match % (the lossless check), rule IDs applied, verify pass/fail, dedup N merged / N emphasis capped. Ledgers and fact inventories go straight to `.claude/reports/YYYYMMDD-HHMMSS_text-optimize/report.md`; the reply returns that path plus the headline numbers. [`/brewtools:agent-return-setup`](/brewtools/skills/agent-return-setup/) enforces this at ~1000 / ~2500 est-tokens when installed; the contract itself ships unconditionally. diff --git a/web/docs/src/content/docs/brewtools/skills/manager-setup.mdx b/web/docs/src/content/docs/brewtools/skills/manager-setup.mdx index 60c3ccc..1e2c876 100644 --- a/web/docs/src/content/docs/brewtools/skills/manager-setup.mdx +++ b/web/docs/src/content/docs/brewtools/skills/manager-setup.mdx @@ -1,6 +1,6 @@ --- title: "Manager Setup" -description: "Codewords ++m / ++rr / ++r / ++a inject a Manager, review or architecture contract for one turn; the opt-in HARD wall blocks main-session writes and forces delegation." +description: "Codewords ++m/++rr/++r/++a inject a Manager, review or architecture contract per turn; the opt-in HARD wall blocks main-session writes and forces delegation." order: 3124 --- import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../components/mdx'; @@ -13,6 +13,10 @@ import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../.. **What is Manager mode?** You type a short codeword (`++m`, `++rr`, `++r`, or `++a`) anywhere in your message and Claude instantly activates a discipline contract -- just for that one message. `++m` makes Claude a pure orchestrator that delegates all hands-on work -- and when you are in plan mode it automatically adds a full task-graph planning pass. `++rr` and `++r` inject review discipline (anti-regression / two-phase double-check). `++a` injects an architecture-first directive -- design before code, reuse existing patterns, no over-engineering. No setup required. The codeword is stripped from your message; your actual text is preserved. + +All five injected blocks were tightened for token efficiency — see [Codewords](#codewords) for what each one now does and [Two rules that do the heavy lifting](#two-rules-that-do-the-heavy-lifting) for the delegation contract they share. + + ## What actually happens **You type:** @@ -26,50 +30,36 @@ import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../.. ```text [ROLE: MANAGER] -You are a Manager, not an executor. Your only actions: think, plan, build the -TaskGraph, delegate, observe, integrate. You do not write/edit code, run -builds/tests, or hands-on debug. Catch yourself editing a file or running an -impl command -> STOP, spawn a subagent. "Faster to do it myself" is a symptom, -not an argument - delegate anyway. +Manager not executor: think/plan/TaskGraph/delegate/observe/integrate; never +code/build/test/hand-debug - catch yourself -> STOP, spawn a subagent. -Protocol, always: -1. Decompose the TASK into a full TaskGraph BEFORE any work (TaskCreate for all). -2. Dependencies via TaskUpdate addBlockedBy/addBlocks - real data-handoffs only. - No dependency = independent = parallel. -3. Assign owner (TaskUpdate owner), then launch the agent. -4. Status honestly: pending -> in_progress (BEFORE start) -> completed (only truly - done, green - never "partially"). -5. Observe: read reports, validate, integrate. Agent failed -> file a follow-up - task and re-delegate, never fix by hand. -6. ALL code written (not per-piece) -> file one recommended final task: simplify - the whole written code, strip over-engineering. Delegate it like any other. +Protocol: 1) TaskCreate the FULL graph before any work. 2) TaskUpdate +addBlockedBy/addBlocks for real data-handoffs only, else parallel. 3) +TaskUpdate owner, then launch. 4) pending -> in_progress (before start) -> +completed (only truly done, never "partially"). 5) Read reports, validate, +integrate; failure -> follow-up task + re-delegate, never fix by hand. 6) all +code written -> one final task: simplify, strip over-engineering. -Sizing before spawning: one subagent = ONE bounded unit - one deliverable, -~<=5 files, ~<=10 steps. Bigger MUST be split into N tasks, fanned out in ONE -message. 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. +No TaskCreate/TaskUpdate (need env CLAUDE_CODE_ENABLE_TODO_TOOLS=1)? Same graph +as a numbered checklist in the plan or .claude/features/.md, updated +wherever this protocol names those tools. -Widest fan-out: a dependency must be a REAL data handoff, else parallel. Size a -unit to ~<=20 min of agent work; longer -> split again. +Sizing: one subagent = 1 deliverable, ~<=5 files, ~<=10 steps, ~<=20 min - +bigger splits into N tasks fanned out in ONE message; one agent for an hour = +drift you cannot observe or correct. -Branch: work in the current branch; none chosen -> main. Unless the user says -branch/PR, stay on main and take over ALL workspace changes, incl. from other -sessions. +Branch: current, none chosen -> main; no branch/PR instruction -> stay on +main, take over ALL workspace changes incl. other sessions. -Every spawn prompt MUST carry, explicitly: - 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. Adapt it per - agent, trimmed to what THIS one needs; never dump everything. - 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. -Every code/test brief MUST make the agent find the closest well-built counterpart -in the repo and follow its principles - IN ADDITION to conventions/rules/docs, +State in every spawn prompt: GOAL (task + why, beyond this edit), ROLE +(owns / must-NOT-touch), SCOPE (paths/commands in + out of bounds), CONTEXT +(done-so-far, by whom, parallel work - trimmed per agent), CONSUMER (who/what +uses the result + shape), DONE (acceptance + exact report shape wanted back). +One-liners are never enough. Every brief also sends the agent to the closest +well-built repo counterpart to extend - ADDITIVE to conventions/rules/docs, never instead. -Scan ALL available agents; summon only genuine experts - one best-match per task. +Scan ALL agents, summon genuine experts only, one best match per task. Autonomy: finish the job, don't stop halfway, don't ask where you can decide. Minimize the critical path. ``` diff --git a/web/docs/src/content/docs/brewtools/skills/text-optimize.mdx b/web/docs/src/content/docs/brewtools/skills/text-optimize.mdx index 01ef56a..971ac92 100644 --- a/web/docs/src/content/docs/brewtools/skills/text-optimize.mdx +++ b/web/docs/src/content/docs/brewtools/skills/text-optimize.mdx @@ -15,6 +15,10 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon **52 validated rules, five modes, parallel processing.** The skill reads your files, applies the right rule set for the mode, verifies references, and reports exact before/after metrics. For humanizing AI-generated text, see [`/brewtools:text-human`](/brewtools/skills/text-human/). + +A Prompt-quality pass (PQ.1-PQ.13) now runs at Medium mode and above — see [Prompt-quality pass (PQ)](#prompt-quality-pass-pq) under Delegation. Every mode also stops at a named [stop condition](#stop-conditions) instead of a felt-right ratio. + + ## Quick reference | Field | Value | @@ -46,6 +50,18 @@ import { Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../compon **Every mode is guarded on disk.** Before any file is touched, the skill snapshots each target byte-for-byte into a private run directory under `.claude/reports/` — the git tree over the targets must be clean first, or the run refuses. After the optimization pass, a mechanical script diffs the snapshot against the result and checks that every number, version, file path, `!=` prohibition, and ALL-CAPS modal keyword (`NEVER`, `ALWAYS`, `MUST NOT`, `REQUIRED`, `MANDATORY`) is still present. A single miss restores the file to its pre-edit bytes and reports exactly what vanished — this applies to every mode, light included, not just standard/deep/max. Standard, deep, and max additionally run an independent semantic gate on top (see Verification in the flow below). +**Measured, not assumed.** Deep mode's own worked examples land at 2.2x-3.5x compression (`wc -w`) — one 248-word block compresses to 94 words, -62.1%, 2.64x. Standard's filler-heavy example: 59 → 19 words, -67.8%. Max's table-to-TSV conversion: 26 → 12 words, -53.8%. The mode table's 30-50% / 2-3x / 3-4x targets are typical ranges, not a number to force — reaching the stop condition below is what ends the pass. + +### Stop conditions + +Standard, deep, and max each stop at a named trigger, never a felt-right ratio: + +| Mode | Stops the moment | Reference | +|------|-------------------|-----------| +| Standard | The next cut would touch a name/number/path/version/flag, or a merge would combine facts with different scope/numbers/conditions (D.6) | `standard-compression.md` § Stop Condition | +| Deep | An A.2 drop would touch a noun/numeral/negation/entity, an A.4 candidate isn't clearly generic training-knowledge, the DICT header would exceed 20 entries, or a term used `<3x` would get a DICT entry | `deep-compression.md` § Stop Condition | +| Max | The 20% deletion ceiling would be crossed, a B1 atomic line would need a cross-line pronoun to stay readable, or Round-2 self-QA misses a number/name/negation/scope qualifier | `max-compression.md` § Stop Condition | + ## Examples ```bash @@ -103,7 +119,7 @@ Natural language works **as arguments after the command** — the skill is user-
  • Optimization phase -

    Parallel text-optimizer agents apply the rule set for the selected mode. Deep mode builds a DICT header for repeated terms and runs an aggressive lossy pass (A.1-A.4) — merges related lines, drops low-value words, paraphrases shorter, and elides generic facts the model already knows; A.2 word drops are ledgered but gate-neutral, A.4 elisions are labeled elided-known and count against the >=95% gate. Standard mode loads standard-compression.md reference. All modes verify file paths (R.1), URLs (R.2), and circular refs (R.3).

    +

    Parallel text-optimizer agents apply the rule set for the selected mode. Medium mode and above additionally run the Prompt-Quality pass (PQ.1-PQ.13, prompt-shaped content only) before the wording pass — see Prompt-quality pass (PQ) above. Deep mode builds a DICT header for repeated terms and runs an aggressive lossy pass (A.1-A.4) — merges related lines, drops low-value words, paraphrases shorter, and elides generic facts the model already knows; A.2 word drops are ledgered but gate-neutral, A.4 elisions are labeled elided-known and count against the >=95% gate. Standard mode loads standard-compression.md reference. All modes verify file paths (R.1), URLs (R.2), and circular refs (R.3).

  • @@ -150,6 +166,23 @@ Every spawn prompt carries six fields. A bare one-line task is never enough: The CONSUMER field is what keeps a parallel run coherent: a heading renamed without care breaks a sibling agent's cross-reference in the same batch. +### Prompt-quality pass (PQ) + +Medium mode and above run a second pass beyond wording cleanup: 13 rules (`PQ.1`-`PQ.13`, `references/rules-review.md` `## PQ`) that reshape prompt-shaped content only — system prompt, `CLAUDE.md`, agent definition, skill `SKILL.md` — the same four types Light mode explicitly skips. PQ changes structure and emphasis, never a fact. + +| Rule | Bad | Good | +|------|-----|------| +| PQ.1 | Role buried after 3 paragraphs of scope | Role in one sentence, Return contract next, Scope/Never as its own heading after | +| PQ.3 | "Do not use markdown" | "Write in flowing prose" — an incident-tied `!=`/NEVER stays verbatim (e.g. `!=re-run text-guard.sh`) | +| PQ.4 | 4x MUST/CRITICAL in one file | 1 STOP on the irreversible action, the other 3 become plain imperative | +| PQ.6 | "Double-check your output before returning" (generic filler) | Deleted — a named gate like `>=95% match, 100% sub-gate` is a real protocol, not filler, and stays | +| PQ.9 | A flag/target matrix written as prose | Table — a create-in-order procedure stays numbered prose, never flattened into a table | +| PQ.11 | A 150-line agent body with 3 abbreviations used twice each | No DICT header — inline the 3 terms; DICT only past >=5 abbreviations each reused >=3x | + +**Runs at:** Medium, Standard, Deep, Max — never Light. + +**Lossless guard, unchanged by PQ:** numbers, dates, versions, model IDs byte-exact (`claude-sonnet-5`, never "Sonnet 5"), CLI flags/options verbatim (`-x`, `--max`), thresholds/gates/percentages exactly as stated (`>=95%`, `~20%`), URLs, file paths, ports, named entities, negations (`!=`/NEVER/MUST NOT), scope qualifiers. A drop touching any of these is a defect caught by the 100% sub-gate — never a valid PQ edit. +
    Rule categories (52 rules) @@ -163,18 +196,19 @@ The CONSUMER field is what keeps a parallel run coherent: a heading renamed with | Perception | P.1-P.6 | Examples near rules, hierarchy, bold keywords, standard symbols, instruction order, default over options | | LLM Comprehension | L.1-L.8 | Critical info position, documents-first, conciseness, quote-first, add WHY, reiterate constraint, prompt repetition, preserve scope qualifiers | | Aggressive lossy | A.1-A.4 | Line fusion, low-value word drop, aggressive paraphrase, known-fact elision (deep/max only) | +| Prompt quality | PQ.1-PQ.13 | Role-first return contract, dedupe repeats, positive imperative (incident-tied `!=` kept), one hard-stop cap, drop step-by-step/verify filler, explicit scope, table-vs-procedure shape, example over adjective, DICT threshold gate — prompt-shaped content only, Medium+ | -A dedup pass runs in every mode during analysis, before compression. Dedup-merged facts count as preserved: match % = (kept + merged) / total. +A dedup pass runs in every mode during analysis, before compression. Dedup-merged facts count as preserved: match % = (kept + merged) / total. PQ runs at Medium+ only, on prompt-shaped content only — never Light, never generic docs/README. **Mode-to-rules mapping:** | Mode | Applies | Notes | |------|---------|-------| -| Light | C.1-C.8, T.6, D.1, R.1-R.3, P.1-P.4, L.1-L.8 | Text cleanup only — no restructuring | -| Medium | All rules (C + T + S + D + R + P + L) | Balanced transformations | -| Standard | All rules + `standard-compression.md` | 30-50% compression, 1 verification round | -| Deep | All rules + A.1-A.4 + `deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds | -| Max | All rules + A.1-A.4 + `deep-compression.md` + `max-compression.md` (opt-in) | 3-4x compression, 2 mandatory verification rounds + 100% sub-gate | +| Light | C.1-C.8, T.6, D.1, R.1-R.3, P.1-P.4, L.1-L.8 | Text cleanup only — no restructuring, no PQ | +| Medium | All rules (C + T + S + D + R + P + L) + PQ (prompt-shaped content) | Balanced transformations | +| Standard | All rules + PQ (prompt-shaped content) + `standard-compression.md` | 30-50% compression, 1 verification round | +| Deep | All rules + PQ (prompt-shaped content) + A.1-A.4 + `deep-compression.md` | DICT header, symbol substitutions, aggressive lossy pass, 1-2 verification rounds | +| Max | All rules + PQ (prompt-shaped content) + A.1-A.4 + `deep-compression.md` + `max-compression.md` (opt-in) | 3-4x compression, 2 mandatory verification rounds + 100% sub-gate |
    diff --git a/web/docs/src/content/docs/brewtools/skills/think-short-setup.mdx b/web/docs/src/content/docs/brewtools/skills/think-short-setup.mdx index dd098c0..334e0c5 100644 --- a/web/docs/src/content/docs/brewtools/skills/think-short-setup.mdx +++ b/web/docs/src/content/docs/brewtools/skills/think-short-setup.mdx @@ -1,6 +1,6 @@ --- title: "Think-Short Setup" -description: "Install, upgrade or remove terse-mode hooks that inject brevity directives at session start, every 10th prompt, and into subagents - project or global scope." +description: "Install, upgrade or remove terse-mode hooks that inject brevity directives at session start, every 20th prompt, and into subagents - project or global scope." order: 3127 --- import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../../components/mdx'; @@ -9,6 +9,10 @@ import { Badge, Callout, Card, CardGrid, Steps, UpdateNotice } from '../../../.. + +The counter hook's cadence and the directive body were both tightened — see the directive text below and [How it works](#how-it-works) for the every-20th-prompt schedule. + + ## What it does `/brewtools:think-short-setup` installs (or removes) a small set of hooks that inject a fixed brevity directive into the Claude Code session. Run the skill with a free-text intent — "make responses shorter", "stop the preamble" — and it asks via `AskUserQuestion` whether to apply to the current **Project** or **Globally**. It then copies four files into the target hooks directory — three hook scripts and `think-short-prompt.md` (the directive body) — and registers three hook entries in the appropriate `settings.json`. The subagent hook injects the directive on `SubagentStart` via `hookSpecificOutput.additionalContext`. @@ -20,20 +24,19 @@ There are no profiles and no config file. The only persisted state is the copied The same directive body is injected at every point: ``` -Be terse. Results first, no preamble/filler/sycophancy. ASCII only. -Think short: minimal internal reasoning, no exploring aloud. -Grep before Read. Edit over Write. Parallel calls in one message. -Plan the full edit set, then execute. +Be terse: results first, no preamble/filler/sycophancy, ASCII only. Think +short - minimal internal reasoning, no exploring aloud. Grep before Read, +Edit over Write, parallel calls in one message. Plan the full edit set, then +execute. -Keep code simple - do not over-engineer. Before writing anything new, -check existing code and libraries for the needed functionality. -Prefer extending or abstracting a similar class over adding a new one. -After writing code, one pass: can this be simpler? If yes - simplify it. +Keep code simple, no over-engineering: before writing anything new, find the +closest well-built repo counterpart and extend it - additive to +conventions/rules/docs, never instead. After writing code, one simplify pass. -Comment like a human, not an AI. Write comments only where they earn it: -non-obvious logic, public APIs, class/method docstrings (JavaDoc/PyDoc/etc). -Do NOT narrate self-evident code or add line-by-line noise. Keep docstrings - -just stop over-commenting. Each comment terse: ~1 line, 2 only if asked. +Comment like a human, not an AI: only where it earns it (non-obvious logic, +public APIs, class/method docstrings - JavaDoc/PyDoc/etc). No narrating +self-evident code or line-by-line noise. Each comment ~1 line, 2 only if +asked. ``` Two of those lines are the simplicity discipline: the model thinks short instead of exploring aloud, and after the code is written it makes one explicit pass asking whether the same thing can be done simpler. @@ -51,7 +54,7 @@ Two of those lines are the simplicity discipline: the model thinks short instead | Turn it back on | `/brewtools:think-short-setup enable` | | Unwire the hooks, keep the tmp markers | `/brewtools:think-short-setup uninstall` | | Delete every file and every marker this skill ever wrote | `/brewtools:think-short-setup purge` | -| Output-heavy sessions where you want periodic reinforcement, not constant injection | Install project-scope — counter hook fires every 10 turns, not every turn | +| Output-heavy sessions where you want periodic reinforcement, not constant injection | Install project-scope — counter hook fires every 20 turns, not every turn | ## Examples @@ -153,10 +156,10 @@ Three hooks are installed, each targeting a different injection point: | Hook | Event | Behavior | |------|-------|----------| | `think-short-session.mjs` | SessionStart | Injects the full directive; resets the per-session prompt counter in `$TMPDIR`; prunes stale counters from prior sessions | -| `think-short-prompt-counter.mjs` | UserPromptSubmit | Injects the directive on every 10th user prompt (turns 10, 20, 30 — never turn 1, which is covered by SessionStart) | +| `think-short-prompt-counter.mjs` | UserPromptSubmit | Injects the directive on every 20th user prompt (turns 20, 40, 60 — never turn 1, which is covered by SessionStart) | | `think-short-subagent.mjs` | SubagentStart | Injects the full directive into every spawned subagent via `additionalContext` | -The counter hook keeps injection periodic, not per-turn. Turns 2–9 run without re-injection; at turn 10 the hook fires again to reinforce brevity after context drift. +The counter hook keeps injection periodic, not per-turn. Turns 2–19 run without re-injection; at turn 20 the hook fires again to reinforce brevity after context drift. ## Installation targets @@ -184,7 +187,7 @@ This replaces an earlier `PreToolUse:Task|Agent` design that rewrote the spawn p | | `n/a` | no subagent hook installed in that scope | | | `unknown` | the installed copy predates the `--check` diagnostic — run `upgrade` on that scope | -`injects` measures the subagent hook only. SessionStart and the every-10th-prompt injection are separate paths. +`injects` measures the subagent hook only. SessionStart and the every-20th-prompt injection are separate paths. Example status output on a machine whose global install predates the diagnostic: