diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7d6210d..11c9d04 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin.json", "name": "claude-code-template", - "version": "0.2.0", - "description": "A comprehensive Claude Code starter template: devcontainer, commands, skills, subagents, plugin scaffolding, hooks, MCP, CI/CD, and SDK examples.", + "version": "0.3.0", + "description": "A comprehensive Claude Code starter template: devcontainer, commands, skills, subagents, plugin scaffolding, hooks, themes, MCP, CI/CD, and SDK examples.", "author": { "name": "Scott Havird", "url": "https://github.com/scotthavird" @@ -17,7 +17,8 @@ "starter", "hooks", "skills", - "subagents" + "subagents", + "themes" ], "components": { "commands": ".claude/commands", @@ -26,6 +27,8 @@ "hooks": ".claude/settings.json", "outputStyles": ".claude/output-styles", "statusLine": ".claude/statusline/statusline.sh", - "mcpServers": ".mcp.json" + "mcpServers": ".mcp.json", + "themes": ".claude/themes", + "bin": "bin" } } diff --git a/.claude/agents/dependency-auditor.md b/.claude/agents/dependency-auditor.md index 247f50f..99e1112 100644 --- a/.claude/agents/dependency-auditor.md +++ b/.claude/agents/dependency-auditor.md @@ -3,6 +3,13 @@ name: dependency-auditor description: Audits project dependencies for CVEs, deprecated packages, license risks, and drift from lockfiles. Use before releases or when the user asks "are my deps safe?". tools: Bash, Read, Grep, Glob model: sonnet +permissionMode: plan +mcpServers: + - fetch +hooks: + PostToolUse: + - matcher: Bash + command: bash scripts/log-hook-event.sh --- You audit third-party dependencies for risk. You run the native audit diff --git a/.claude/settings.json b/.claude/settings.json index 7d029c4..8d97a68 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -43,11 +43,23 @@ ] }, + "sandbox": { + "network": { + "deniedDomains": [ + "metadata.google.internal", + "169.254.169.254" + ] + } + }, + "statusLine": { "type": "command", - "command": ".claude/statusline/statusline.sh" + "command": ".claude/statusline/statusline.sh", + "refreshInterval": 5 }, + "prUrlTemplate": "https://github.com/{owner}/{repo}/pull/{number}", + "hooks": { "SessionStart": [ { @@ -80,6 +92,7 @@ }, { "matcher": "Bash", + "if": "Bash(*)", "hooks": [ { "type": "command", @@ -97,7 +110,7 @@ { "type": "command", "command": "bash scripts/log-hook-event.sh", - "description": "Log PostToolUse events" + "description": "Log PostToolUse events (now includes duration_ms)" } ] }, @@ -110,6 +123,29 @@ "description": "Run formatter on edited files" } ] + }, + { + "matcher": "Bash|Read|Grep", + "hooks": [ + { + "type": "command", + "command": "bash scripts/hooks/redact-secrets.sh", + "description": "Redact secret-shaped strings from tool output before the model sees it" + } + ] + } + ], + + "PreCompact": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "bash scripts/hooks/pre-compact.sh", + "description": "Save a checkpoint before compaction drops context" + } + ] } ], @@ -151,7 +187,7 @@ { "type": "command", "command": "bash scripts/hooks/session-cost.sh", - "description": "Surface session cost summary" + "description": "Surface session cost summary plus per-tool timing" } ] } diff --git a/.claude/settings.local.json.example b/.claude/settings.local.json.example index 401f9f1..423c0e4 100644 --- a/.claude/settings.local.json.example +++ b/.claude/settings.local.json.example @@ -2,9 +2,12 @@ "// INSTRUCTIONS": "Copy this file to .claude/settings.local.json and customize. It is gitignored.", "// MODEL": "Override the default model per session. Omit to inherit global/default.", - "model": "claude-sonnet-4-6", + "model": "claude-opus-4-7", - "// PERMISSION_MODE": "default | acceptEdits | plan | bypassPermissions", + "// EFFORT": "Default effort level. Recommended: xhigh on Opus 4.7 for coding work.", + "// effort": "xhigh", + + "// PERMISSION_MODE": "default | acceptEdits | plan | bypassPermissions | auto", "permissions": { "defaultMode": "default", "allow": [ @@ -14,19 +17,42 @@ ] }, + "// AUTO_MODE": "Optional opt-in to auto mode (v2.1.111+). A classifier handles permission prompts so safe actions run without interruption and risky ones are blocked. Use $defaults to extend the built-in lists rather than replace them.", + "// autoMode": { + "// allow": ["$defaults", "Bash(my-internal-tool:*)"], + "// soft_deny": ["$defaults"], + "// environment": ["$defaults"] + }, + "// ADDITIONAL_DIRS": "Let Claude also read code outside the repo (monorepos, shared libs).", "additionalDirectories": [ "~/src/shared-libs", "~/src/design-system" ], - "// ENV": "API keys and provider selection. See https://code.claude.com/docs/en/env-vars", + "// ENV": "API keys, provider selection, and feature toggles. See https://code.claude.com/docs/en/env-vars", "env": { "ANTHROPIC_API_KEY": "sk-ant-api-...", + "// CACHING": "1-hour prompt cache TTL (v2.1.108+). Default is 5 minutes.", + "// ENABLE_PROMPT_CACHING_1H": "1", + + "// RENDERING": "Flicker-free fullscreen rendering (v2.1.89+).", + "// CLAUDE_CODE_NO_FLICKER": "1", + + "// PRIVACY": "Hide working directory in startup logo (v2.1.119+).", + "// CLAUDE_CODE_HIDE_CWD": "1", + + "// SUBAGENTS": "Enable forked subagents on external builds (v2.1.117+).", + "// CLAUDE_CODE_FORK_SUBAGENT": "1", + + "// UPDATES": "Block all update paths including manual `claude update` (v2.1.118+).", + "// DISABLE_UPDATES": "1", + "// BEDROCK": "Uncomment to route through Amazon Bedrock instead of the Anthropic API.", "// CLAUDE_CODE_USE_BEDROCK": "1", "// AWS_REGION": "us-west-2", + "// ANTHROPIC_BEDROCK_SERVICE_TIER": "priority", "// VERTEX": "Uncomment to route through Google Vertex AI.", "// CLAUDE_CODE_USE_VERTEX": "1", diff --git a/.claude/skills/effort-aware/SKILL.md b/.claude/skills/effort-aware/SKILL.md new file mode 100644 index 0000000..61312ad --- /dev/null +++ b/.claude/skills/effort-aware/SKILL.md @@ -0,0 +1,55 @@ +--- +name: effort-aware +description: Demonstrates branching skill behavior on the current effort level using ${CLAUDE_EFFORT} (Claude Code v2.1.120+). Activates when discussing how to scope work across effort levels. +allowed-tools: Read, Grep, Glob +--- + +# Effort-Aware Skill + +Demonstrates how a skill can adapt its instructions to the current effort +level. Claude Code injects `${CLAUDE_EFFORT}` into skill text at load +time (v2.1.120+), so the same skill can give terse advice on `low` and +exhaustive advice on `xhigh` without needing separate skills. + +## Current effort: `${CLAUDE_EFFORT}` + +### Guidance for this effort level + +**If `${CLAUDE_EFFORT}` is `low` or `medium`:** +- Make the smallest correct change. Don't refactor surrounding code. +- Skip exploring alternatives — pick the obvious path. +- One pass, no self-review beyond a re-read. +- Don't write tests for trivial changes (one-liner config, doc fix). + +**If `${CLAUDE_EFFORT}` is `high`:** +- Sketch 2–3 approaches before picking one. Note the tradeoffs in your + response. +- Add tests for new behavior. +- Self-review the diff before reporting done. + +**If `${CLAUDE_EFFORT}` is `xhigh` or `max`:** +- Map invariants the change must preserve. Read enough surrounding code + to be confident. +- Build a sequence of small, reversible commits if the change is large. +- Tests cover happy path, error path, and at least two boundary cases. +- Run a `pr-reviewer` self-review pass before reporting done. +- Note follow-ups (debt, deferred work) explicitly so the human can + triage. + +## Why this exists + +Older Claude Code versions had no way for skills to adapt their tone to +the user's effort setting. You either had to write conservatively (and +under-deliver on `xhigh`) or aggressively (and over-engineer on `low`). +Effort interpolation closes that gap. + +## When this skill activates + +When the user discusses scoping, planning a change, or asks how +thoroughly to do something. The skill helps you calibrate effort to +match `${CLAUDE_EFFORT}` rather than picking a single fixed default. + +## See also + +- `/effort` slash command — interactive slider to change effort mid-session. +- [Effort levels in the official docs](https://code.claude.com/docs/en/model-config). diff --git a/.claude/themes/README.md b/.claude/themes/README.md new file mode 100644 index 0000000..77bbdff --- /dev/null +++ b/.claude/themes/README.md @@ -0,0 +1,14 @@ +# Themes + +Custom themes shipped with this template. Switch with `/theme` from +inside a session, or set the default in `~/.claude/settings.json`. + +| Theme | Notes | +|---|---| +| `anthropic-clay` | Warm, low-contrast. Auto-adapts to light/dark terminal background. | + +Plugins can ship themes via a `themes/` directory at the plugin root. +This template's `.claude-plugin/plugin.json` registers `themes:` so any +JSON file dropped here is picked up. + +See [official themes docs](https://code.claude.com/docs/en/settings#themes). diff --git a/.claude/themes/anthropic-clay.json b/.claude/themes/anthropic-clay.json new file mode 100644 index 0000000..b926955 --- /dev/null +++ b/.claude/themes/anthropic-clay.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-theme.json", + "name": "anthropic-clay", + "displayName": "Anthropic Clay", + "description": "A warm, low-contrast theme inspired by Anthropic's clay/slate palette. Pairs well with light or dark terminals — the accent colors stay legible against either.", + "type": "auto", + "colors": { + "accent": "#d97757", + "accentMuted": "#c6613f", + "background": null, + "foreground": null, + "secondary": "#73726c", + "muted": "#91908a", + "success": "#22c55e", + "warning": "#eab308", + "error": "#dc2626", + "info": "#3b82f6", + "diffAdded": "#22c55e", + "diffRemoved": "#dc2626", + "diffContext": "#73726c", + "promptBorder": "#d97757", + "selectionBackground": "#d97757", + "selectionForeground": "#ffffff" + } +} diff --git a/.gitignore b/.gitignore index e5ac486..fc381f1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,11 @@ logs/ # Local Claude settings (may contain sensitive data) .claude/settings.local.json +# Claude Code runtime state +.claude/scheduled_tasks.lock +.claude/checkpoints/ +.claude/*.lock + # Node modules and dependencies node_modules/ npm-debug.log* diff --git a/.mcp.json b/.mcp.json index 3cf6337..08eba09 100644 --- a/.mcp.json +++ b/.mcp.json @@ -4,25 +4,27 @@ "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."], - "description": "Sandboxed filesystem access rooted at the project directory" + "alwaysLoad": true, + "description": "Sandboxed filesystem access rooted at the project directory. alwaysLoad=true so its tools skip tool-search deferral." }, - "memory": { + "git": { "type": "stdio", "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-memory"], - "description": "Persistent knowledge graph memory across sessions" + "args": ["-y", "@modelcontextprotocol/server-git", "--repository", "."], + "alwaysLoad": true, + "description": "Git operations (log, diff, blame, show) via MCP." }, - "git": { + "memory": { "type": "stdio", "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-git", "--repository", "."], - "description": "Git operations (log, diff, blame, show) via MCP" + "args": ["-y", "@modelcontextprotocol/server-memory"], + "description": "Persistent knowledge graph memory across sessions." }, "fetch": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-fetch"], - "description": "Fetch and convert web content to markdown" + "description": "Fetch and convert web content to markdown." } } } diff --git a/CLAUDE.md b/CLAUDE.md index d7a31d9..b0fd4c1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,6 +70,7 @@ scripts/ | `api-design` | HTTP/REST/GraphQL/RPC interface work | | `performance-audit` | Profiling, optimization, hot paths | | `accessibility` | UI work (WCAG 2.2 AA baseline) | +| `effort-aware` | Adapts to `${CLAUDE_EFFORT}` (v2.1.120+) — calibrate scope | ## Hooks @@ -79,9 +80,11 @@ payload as JSON on stdin. | Event | Script | Effect | |---|---|---| | `SessionStart` | `inject-context.sh` | Injects branch + recent commits + open PRs | -| `PreToolUse` (Bash) | `block-dangerous-bash.sh` | Blocks `rm -rf /`, force-push to main, pipe-to-shell, etc. | -| `PostToolUse` (Edit\|Write) | `format-on-save.sh` | Runs Prettier / Ruff / gofmt / rustfmt on the edited file | -| `Stop` | `session-cost.sh` | Prints session cost summary to stderr | +| `PreToolUse` (Bash) | `block-dangerous-bash.sh` | Blocks destructive shell patterns | +| `PostToolUse` (Edit\|Write) | `format-on-save.sh` | Runs Prettier / Ruff / gofmt / rustfmt | +| `PostToolUse` (Bash\|Read\|Grep) | `redact-secrets.sh` | Rewrites tool output via `updatedToolOutput` (v2.1.122) to redact secrets | +| `PreCompact` | `pre-compact.sh` | Saves a checkpoint to `.claude/checkpoints/` before compaction (v2.1.105) | +| `Stop` | `session-cost.sh` | Cost summary + per-tool `duration_ms` breakdown (v2.1.121) | | All events | `log-hook-event.sh` | JSONL logging for later analysis | See [docs/hooks-cookbook.md](docs/hooks-cookbook.md) for details. @@ -90,6 +93,8 @@ See [docs/hooks-cookbook.md](docs/hooks-cookbook.md) for details. Default is `default` (prompts on every unlisted Bash / file write). Switch per session with `/mode ` or `--permission-mode `. +Modes: `default`, `acceptEdits`, `plan`, `bypassPermissions`, `auto` +(v2.1.111+ — classifier-based). See [docs/permission-modes.md](docs/permission-modes.md). @@ -98,12 +103,15 @@ See [docs/permission-modes.md](docs/permission-modes.md). Declared in `.mcp.json`. Opted in via `enabledMcpjsonServers` in `settings.json`: -| Server | What it does | -|---|---| -| `filesystem` | Sandboxed file access rooted at project dir | -| `memory` | Persistent knowledge graph across sessions | -| `git` | Git log/diff/blame/show operations | -| `fetch` | Fetch web content as markdown | +| Server | `alwaysLoad` | What it does | +|---|---|---| +| `filesystem` | yes | Sandboxed file access rooted at project dir | +| `git` | yes | Git log/diff/blame/show operations | +| `memory` | no | Persistent knowledge graph across sessions | +| `fetch` | no | Fetch web content as markdown | + +`alwaysLoad: true` (v2.1.122) skips tool-search deferral so these tools +are always available without `@`-mention discovery. ## Memory @@ -115,6 +123,18 @@ Two independent systems: this as it learns about the project. Not committed. Inspect/edit it directly if needed. +## Themes + +`.claude/themes/anthropic-clay.json` — a warm, low-contrast theme that +auto-adapts to light/dark terminals. Switch with `/theme` (v2.1.118+). + +## Plugin executables (`bin/`) + +`bin/claude-template-info` — example of a plugin-shipped executable +that goes on the Bash tool's `$PATH` when this plugin is installed +(v2.1.91+). Run it from any session to get a one-page summary of what +this template provides. + ## DevContainer `.devcontainer/` includes: diff --git a/README.md b/README.md index de9574e..b458526 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ this template, without forking the repo. `security-auditor` `doc-generator` `test-runner` `pr-reviewer` `refactor-planner` `debugger` `dependency-auditor` -### Skills (6, auto-triggered) +### Skills (7, auto-triggered) `code-review` `db-migration` `test-writing` `api-design` -`performance-audit` `accessibility` +`performance-audit` `accessibility` `effort-aware` ### Output styles (3) @@ -51,9 +51,11 @@ this template, without forking the repo. ### Hooks (real, not just logging) - **Format on save** — Prettier / Ruff / gofmt / rustfmt -- **Block dangerous bash** — `rm -rf /`, force-push to main, pipe-to-shell +- **Block dangerous bash** — destructive patterns, force-push to main, pipe-to-shell (with quote/comment-aware matching) - **Inject context on session start** — branch, commits, open PRs -- **Session cost summary on stop** +- **Redact secrets from tool output** — uses v2.1.122 `updatedToolOutput` to scrub keys/JWTs/PATs before the model sees them +- **PreCompact checkpoint** — saves session state before compaction drops context (v2.1.105+) +- **Session cost on stop** — total cost plus per-tool `duration_ms` breakdown (v2.1.121+) ### CI/CD diff --git a/bin/claude-template-info b/bin/claude-template-info new file mode 100755 index 0000000..06dfaac --- /dev/null +++ b/bin/claude-template-info @@ -0,0 +1,40 @@ +#!/bin/bash +# Plugin executable shipped at bin/ — Claude Code (v2.1.91+) puts plugin +# bin/ directories on the Bash tool's $PATH, so this is invokable as: +# claude-template-info +# from any Claude Code session that has this plugin installed. +# +# Reports a one-page summary of what the template provides. Useful in +# new repos where contributors want to see what's available without +# reading every file. + +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +count() { + # Count files matching a glob in a dir, returning 0 if dir missing. + local dir="$1" pattern="$2" + [ -d "$dir" ] || { echo 0; return; } + find "$dir" -type f -name "$pattern" 2>/dev/null | wc -l | tr -d ' ' +} + +echo "claude-code-template @ $ROOT" +echo +echo "Components:" +printf ' %-15s %s commands\n' "commands" "$(count "$ROOT/.claude/commands" '*.md')" +printf ' %-15s %s subagents\n' "agents" "$(count "$ROOT/.claude/agents" '*.md')" +printf ' %-15s %s skills\n' "skills" "$(find "$ROOT/.claude/skills" -name SKILL.md 2>/dev/null | wc -l | tr -d ' ')" +printf ' %-15s %s presets\n' "output-styles" "$(count "$ROOT/.claude/output-styles" '*.md')" +printf ' %-15s %s themes\n' "themes" "$(count "$ROOT/.claude/themes" '*.json')" +printf ' %-15s %s scripts\n' "hooks" "$(count "$ROOT/scripts/hooks" '*.sh')" +echo +echo "Quick reference:" +echo " /analyze-project project structure overview" +echo " /review review current branch / PR" +echo " /security-review OWASP scan of the diff" +echo " /debug trace bug to root cause" +echo " /refactor plan a refactor" +echo " /implement-issue read issue and ship it" +echo +echo "Docs: $ROOT/docs/" diff --git a/docs/hooks-cookbook.md b/docs/hooks-cookbook.md index 5c32d0a..2de88db 100644 --- a/docs/hooks-cookbook.md +++ b/docs/hooks-cookbook.md @@ -12,14 +12,18 @@ See the [official hooks reference](https://code.claude.com/docs/en/hooks). ## Events -| Event | When | Can block? | -|---|---|---| -| `SessionStart` | New session opens | no | -| `UserPromptSubmit` | User sends a prompt | yes (rare) | -| `PreToolUse` | Before Claude calls a tool | yes | -| `PostToolUse` | After Claude calls a tool | no | -| `Notification` | System notification | no | -| `Stop` | Session ends | no | +| Event | When | Can block? | Available since | +|---|---|---|---| +| `SessionStart` | New session opens | no | base | +| `UserPromptSubmit` | User sends a prompt | yes (rare) | base | +| `PreToolUse` | Before Claude calls a tool | yes | base | +| `PostToolUse` | After Claude calls a tool | no, but can mutate output | base; `updatedToolOutput` in v2.1.122 | +| `PostToolUseFailure` | After a tool call errored | no | recent | +| `PreCompact` | Before context is compacted | yes | v2.1.105 | +| `PermissionDenied` | Auto-mode classifier denied a call | no | v2.1.89 | +| `TaskCreated` | A Task was created via TaskCreate | no | v2.1.84 | +| `Notification` | System notification | no | base | +| `Stop` | Session ends | no | base | ## Recipes @@ -41,11 +45,61 @@ See [`scripts/hooks/inject-context.sh`](../scripts/hooks/inject-context.sh). Wired to `SessionStart` with matcher `startup`. Prints branch, recent commits, dirty file count, open PRs to stdout — injected into the session. -### 4. Surface session cost on exit +### 4. Surface session cost on exit (with per-tool timing) See [`scripts/hooks/session-cost.sh`](../scripts/hooks/session-cost.sh). Wired to `Stop`. Reads `cost.total_cost_usd` and `num_turns` from stdin, -prints a one-liner to stderr. +prints a one-liner to stderr. As of v2.1.121, `PostToolUse` payloads +include `duration_ms`; the script now tails `logs/PostToolUse-events.jsonl` +to add a "top tools by time" breakdown. + +### 5. Save a checkpoint before context compaction + +See [`scripts/hooks/pre-compact.sh`](../scripts/hooks/pre-compact.sh). +Wired to `PreCompact` (v2.1.105+). Writes `.claude/checkpoints/pre-compact-*.md` +with branch, dirty file count, and last commit. Permissive by default +(always exits 0). Customize the block-conditions if you want compaction +gated on, e.g., "no in-progress refactor without tests". + +### 6. Redact secrets from tool output + +See [`scripts/hooks/redact-secrets.sh`](../scripts/hooks/redact-secrets.sh). +Wired to `PostToolUse` matcher `Bash|Read|Grep`. Demonstrates the +v2.1.122 `hookSpecificOutput.updatedToolOutput` field — the hook rewrites +the tool result the model sees, replacing secret-shaped strings (API +keys, JWTs, AWS access keys, GitHub PATs, Slack tokens) with +`[REDACTED-*]` markers. Fail-open: leaves output untouched on parse error. + +### Conditional `if` field on hooks (v2.1.85+) + +Hooks can be filtered with an `if` clause using permission rule syntax: + +```json +{ + "matcher": "Bash", + "if": "Bash(git push:*)", + "hooks": [ + { "type": "command", "command": "bash scripts/hooks/notify-push.sh" } + ] +} +``` + +The hook only fires when the matched call satisfies the `if` rule. Useful +for narrow filters that would otherwise need shell-level matching inside +the hook script. + +### Calling MCP tools from hooks (v2.1.118+) + +Hooks can invoke MCP tools directly without spawning a subprocess: + +```json +{ + "type": "mcp_tool", + "server": "memory", + "tool": "store_observation", + "arguments": { "kind": "session_start" } +} +``` ## Payload shapes diff --git a/docs/integrations.md b/docs/integrations.md index 0bba764..6a02879 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -10,6 +10,16 @@ issues, and review comments. `.github/workflows/claude-review.yml` — runs a review pass on every new PR or push to an open PR. +### Lighter alternative: `/ultrareview` + +For an alternative to running the full Claude Code action in CI, the +CLI now ships `claude ultrareview ` (v2.1.120+) which delegates +review to cloud-managed parallel agents. Findings stream back to the +runner. Cheaper for the runner, slower wall-clock, broader coverage +than a single `claude -p` invocation. + +`scripts/ci-review.sh --ultrareview ` wraps it. + Both require `ANTHROPIC_API_KEY` in the repo's Actions secrets. **If the secret is not set, both workflows skip cleanly** (a `check-secret` gate job emits a workflow notice and the responder/review job is marked @@ -39,6 +49,14 @@ inspect live pages. [Chrome integration doc](https://code.claude.com/docs/en/chr Push events from arbitrary sources into a running session via the [channels API](https://code.claude.com/docs/en/channels). +## Routines (cloud-scheduled agents) + +Templated cloud agents that fire from a cron schedule, GitHub event, or +API call. Available on Claude Code on the web (Week 16, April 2026). +Useful for: morning PR reviews, overnight CI failure analysis, weekly +dependency audits, syncing docs after PRs merge. See +[Routines](https://code.claude.com/docs/en/routines). + ## Remote Control Continue a local session from your phone or another device with diff --git a/docs/permission-modes.md b/docs/permission-modes.md index 9472540..ac03e44 100644 --- a/docs/permission-modes.md +++ b/docs/permission-modes.md @@ -38,6 +38,31 @@ No prompts for anything. Claude can run any tool call without approval. **Only use in sandboxed / ephemeral environments** — a fresh devcontainer, a Docker container with no mounted credentials, a VM you can burn. +## `auto` (Claude Code v2.1.111+) + +A classifier handles permission prompts: safe actions run without +interruption, risky ones get blocked, and ambiguous ones still prompt. +The middle ground between `default` (prompts on everything unlisted) +and `bypassPermissions` (no prompts at all). + +Configure custom rules via `autoMode` in settings. Use `"$defaults"` to +extend the built-in lists rather than replace them: + +```json +{ + "permissions": { "defaultMode": "auto" }, + "autoMode": { + "allow": ["$defaults", "Bash(my-internal-tool:*)"], + "soft_deny": ["$defaults"], + "environment": ["$defaults"] + } +} +``` + +When auto mode denies a call, the new `PermissionDenied` hook (v2.1.89+) +fires and the call appears in `/permissions` Recent tab with a retry +option. + ## Switching modes mid-session ``` diff --git a/docs/plugins.md b/docs/plugins.md index a731acb..66bf815 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -17,12 +17,30 @@ and the [marketplace docs](https://code.claude.com/docs/en/plugin-marketplaces). |---|---| | Slash commands | `.claude/commands/` (12) | | Subagents | `.claude/agents/` (7) | -| Skills | `.claude/skills/` (6) | +| Skills | `.claude/skills/` (7) | | Output styles | `.claude/output-styles/` (3) | | Status line | `.claude/statusline/statusline.sh` | | Hooks | `.claude/settings.json` + `scripts/hooks/` | +| Themes | `.claude/themes/` (1) | +| Bin | `bin/` (1 executable on $PATH when plugin is installed) | | MCP servers | `.mcp.json` | +### What's new in the plugin spec (v2.1.85 → v2.1.122) + +- **`bin/` executables** (v2.1.91): plugins can ship CLI tools that get + added to the Bash tool's `$PATH` automatically. This template ships + `bin/claude-template-info` as a demo. +- **`themes/` directory** (v2.1.118): plugins can ship color themes that + appear in `/theme`. We ship `anthropic-clay`. +- **`monitors` manifest key** (v2.1.105): declare background reactive + monitors. Not used in this template yet — see the [official plugins + reference](https://code.claude.com/docs/en/plugins-reference) for the + schema. +- **`claude plugin tag`** (v2.1.118): create release git tags for the + plugin with version validation. +- **`claude plugin prune`** (v2.1.122): remove orphaned auto-installed + plugin dependencies. + ## Forking this plugin 1. Fork the repo or copy this directory structure into your own. diff --git a/scripts/ci-review.sh b/scripts/ci-review.sh index 5edc43b..5b3c296 100755 --- a/scripts/ci-review.sh +++ b/scripts/ci-review.sh @@ -3,11 +3,25 @@ # Usage: # scripts/ci-review.sh # review uncommitted diff # scripts/ci-review.sh origin/main...HEAD # review branch diff +# scripts/ci-review.sh --ultrareview # delegate to /ultrareview cloud agents # # Requires ANTHROPIC_API_KEY in the environment. set -euo pipefail +# Mode 1: ultrareview — delegate to the cloud-based parallel reviewer +# (Claude Code v2.1.120+). Shorter, cheaper for the runner, slower for +# the user; runs more agents than a single -p invocation can. +if [ "${1:-}" = "--ultrareview" ]; then + target="${2:-HEAD}" + if [ -z "${ANTHROPIC_API_KEY:-}" ]; then + echo "ANTHROPIC_API_KEY not set" >&2 + exit 1 + fi + exec claude ultrareview "$target" +fi + +# Mode 2: classic headless review with -p. range="${1:-HEAD}" if [ -z "${ANTHROPIC_API_KEY:-}" ]; then diff --git a/scripts/hooks/pre-compact.sh b/scripts/hooks/pre-compact.sh new file mode 100755 index 0000000..26b9398 --- /dev/null +++ b/scripts/hooks/pre-compact.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# PreCompact hook (Claude Code v2.1.105+). +# Fires before context compaction. Saves a tiny note about what was +# happening so the post-compact session can pick up where the pre-compact +# session left off. +# +# Exit codes: +# 0 = allow compaction +# 2 = block compaction (printed message goes to the model) +# +# This default implementation is permissive (always exits 0) and just +# writes a checkpoint file. Customize block-conditions to fit your team +# (e.g., block if there's an in-progress refactor with no tests yet). + +set -u + +payload="$(cat)" + +if ! command -v jq >/dev/null 2>&1; then + exit 0 +fi + +mkdir -p .claude/checkpoints + +# Pull useful context from the payload to drop a checkpoint. +session_id="$(printf '%s' "$payload" | jq -r '.session_id // "unknown"')" +ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +checkpoint=".claude/checkpoints/pre-compact-${ts}-${session_id:0:8}.md" + +# Best-effort git context for the checkpoint. +branch="$(git branch --show-current 2>/dev/null || echo unknown)" +dirty="$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')" +last_commit="$(git log -1 --oneline 2>/dev/null || echo none)" + +cat > "$checkpoint" <&2 + +exit 0 diff --git a/scripts/hooks/redact-secrets.sh b/scripts/hooks/redact-secrets.sh new file mode 100755 index 0000000..c67d39d --- /dev/null +++ b/scripts/hooks/redact-secrets.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# PostToolUse hook for Bash | Read | Grep tool output redaction. +# Demonstrates the v2.1.122 hookSpecificOutput.updatedToolOutput field, +# which lets a PostToolUse hook rewrite the tool output the model sees. +# +# Use case: prevent secret-shaped strings (API keys, JWTs, AWS creds) +# from being read by Claude even if they slip into a Bash result or a +# grep over a file the deny-list missed. +# +# Conservative: redaction is fail-open. If anything goes wrong we leave +# the output untouched. + +set -u + +payload="$(cat)" + +if ! command -v jq >/dev/null 2>&1; then + exit 0 +fi + +# Only act on textual output. tool_output may be a string or an object. +output_text="$(printf '%s' "$payload" | jq -r ' + (.tool_output // .tool_response // empty) + | if type == "string" then . + elif type == "object" and has("output") then .output + elif type == "object" and has("text") then .text + else empty + end +' 2>/dev/null)" + +[ -z "$output_text" ] && exit 0 + +# Patterns to redact. Add to taste. +redacted="$(printf '%s' "$output_text" | sed -E \ + -e 's/(sk-ant-api[0-9-]+-[A-Za-z0-9_-]{20,})/[REDACTED-anthropic-key]/g' \ + -e 's/(sk-[A-Za-z0-9_-]{20,})/[REDACTED-openai-style-key]/g' \ + -e 's/(AKIA[0-9A-Z]{16})/[REDACTED-aws-access-key]/g' \ + -e 's/(ghp_[A-Za-z0-9]{36})/[REDACTED-github-pat]/g' \ + -e 's/(github_pat_[A-Za-z0-9_]{82})/[REDACTED-github-fine-grained-pat]/g' \ + -e 's/(eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/[REDACTED-jwt]/g' \ + -e 's/(xox[abprs]-[A-Za-z0-9-]{10,})/[REDACTED-slack-token]/g')" + +# If nothing changed, exit silently. +if [ "$redacted" = "$output_text" ]; then + exit 0 +fi + +# Emit hookSpecificOutput.updatedToolOutput as JSON on stdout. +# Claude Code reads stdout as a JSON object when a hook wants to mutate +# tool output (per v2.1.122). +jq -n --arg text "$redacted" '{ + hookSpecificOutput: { + updatedToolOutput: $text + } +}' + +exit 0 diff --git a/scripts/hooks/session-cost.sh b/scripts/hooks/session-cost.sh index 26aba74..4dff7a5 100755 --- a/scripts/hooks/session-cost.sh +++ b/scripts/hooks/session-cost.sh @@ -1,6 +1,10 @@ #!/bin/bash -# Stop hook. Prints a one-line session cost summary to stderr, which -# Claude Code surfaces in the UI. Never blocks. +# Stop hook. Prints a one-line session cost summary to stderr (which +# Claude Code surfaces in the UI) plus a per-tool breakdown if the +# corresponding JSONL log file exists. +# +# Per-tool breakdown uses the duration_ms field added to PostToolUse +# payloads in v2.1.121. The hook never blocks (always exit 0). set -u @@ -10,15 +14,32 @@ if ! command -v jq >/dev/null 2>&1; then exit 0 fi -cost="$(printf '%s' "$payload" | jq -r '.cost.total_cost_usd // 0' 2>/dev/null)" +cost="$(printf '%s' "$payload" | jq -r '.cost.total_cost_usd // 0' 2>/dev/null)" dur_ms="$(printf '%s' "$payload" | jq -r '.cost.total_duration_ms // 0' 2>/dev/null)" -turns="$(printf '%s' "$payload" | jq -r '.num_turns // 0' 2>/dev/null)" +turns="$(printf '%s' "$payload" | jq -r '.num_turns // 0' 2>/dev/null)" -# Format seconds. dur_s=$(( dur_ms / 1000 )) min=$(( dur_s / 60 )) sec=$(( dur_s % 60 )) -printf 'Session: %s turns · %dm%ds · $%s\n' "$turns" "$min" "$sec" "$cost" >&2 +printf 'Session: %s turns · %dm%ds · $%s' "$turns" "$min" "$sec" "$cost" >&2 +# Per-tool timing breakdown using duration_ms from PostToolUse log. +posttool_log="logs/PostToolUse-events.jsonl" +if [ -f "$posttool_log" ]; then + top="$(jq -rs ' + [.[] | select(.duration_ms != null) | {tool: .tool_name, ms: .duration_ms}] + | group_by(.tool) + | map({tool: .[0].tool, total_ms: (map(.ms) | add), count: length}) + | sort_by(-.total_ms) + | .[0:3] + | map("\(.tool):\(.total_ms)ms×\(.count)") + | join(" ") + ' "$posttool_log" 2>/dev/null)" + if [ -n "$top" ] && [ "$top" != "null" ]; then + printf ' · top tools: %s' "$top" >&2 + fi +fi + +printf '\n' >&2 exit 0