diff --git a/.claude/README.md b/.claude/README.md new file mode 100644 index 0000000..083d5bb --- /dev/null +++ b/.claude/README.md @@ -0,0 +1,55 @@ +# Claude Code Configuration — Heady Context + +This directory configures Claude Code for **maximum usable context** and +**Heady-optimal operation** when working in this repository. Settings are +team-wide (committed); personal overrides belong in `.claude/settings.local.json` +(gitignored). + +## Maximum context + +| Setting | Value | Effect | +|---------|-------|--------| +| `autoCompactWindow` | `1000000` | Compaction defers until the **1M-token** boundary — the full window is used before any summarization. | +| `env.ANTHROPIC_BETAS` | `context-1m-2025-08-07` | Opts into the 1M-token context beta. Pair with a 1M-capable model (e.g. `claude-opus-4-8[1m]`). | +| `autoCompactEnabled` | `true` | Long sessions never hard-stop; context is summarized at the boundary instead of failing. | +| `env.CLAUDE_CODE_MAX_OUTPUT_TOKENS` | `64000` | Large single-turn outputs (full files, long reports). | +| `env.MAX_THINKING_TOKENS` | `32000` | Deep extended-thinking budget (stays below the output cap). | +| `env.MAX_MCP_OUTPUT_TOKENS` | `50000` | Heady runs many MCP servers; raises the per-call output cap so tool results aren't truncated. | +| `effortLevel` | `xhigh` | Maximum reasoning effort on supported models. | +| `alwaysThinkingEnabled` / `showThinkingSummaries` | `true` | Thinking always on and visible in the transcript. | + +> **Model note:** the 1M window requires a 1M-capable model. Select it per +> session (`/model claude-opus-4-8[1m]`) or pin it in your personal +> `.claude/settings.local.json`. It is intentionally **not** pinned in +> committed settings so the team isn't forced onto one model/tier. + +## Heady-optimal operation + +- **`enableAllProjectMcpServers: true`** — auto-approves the project's MCP + servers (`.mcp.json`) so the Heady toolchain is available without per-server + prompts. +- **`fileCheckpointingEnabled` / `todoFeatureEnabled`** — safe `/rewind` and + task tracking for multi-stage pipeline work. +- **`cleanupPeriodDays: 90`** — long transcript retention for long-running + orchestration sessions. +- **Permissions** — read/search/agent and the common Heady dev commands + (git, npm/pnpm, node, python, gitleaks) are allowed; secrets + (`.env`, `.heady/**`, keys) are **deny-read**; catastrophic and + destructive-infra commands are blocked or gated (see hooks). + +## Hooks + +| Hook | Script | Purpose | +|------|--------|---------| +| `SessionStart` | `hooks/session-context.sh` | Injects live repo context (branch, HEAD, present canonical configs, Stop Rule) so each session starts grounded in current state, not a stale snapshot. | +| `PreToolUse(Bash)` | `hooks/guard-bash.sh` | Hard-**denies** catastrophic commands (`rm -rf /`, force-push, `mkfs`, fork-bombs) and **asks** before destructive-but-legitimate ones (`DROP TABLE`, `gcloud … delete`, `wrangler … delete`). Enforces the governance rule that irreversible actions need human confirmation. | + +Both scripts are pure `bash` + `jq` (both required at runtime) and emit the +documented hook JSON contracts. Test them directly: + +```bash +echo '{}' | bash .claude/hooks/session-context.sh | jq . +echo '{"tool_input":{"command":"rm -rf /"}}' | bash .claude/hooks/guard-bash.sh | jq . +``` + +Review or disable any hook live via the `/hooks` menu. diff --git a/.claude/hooks/guard-bash.sh b/.claude/hooks/guard-bash.sh new file mode 100755 index 0000000..0664907 --- /dev/null +++ b/.claude/hooks/guard-bash.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# PreToolUse(Bash) hook — last-line guard against catastrophic, irreversible +# commands that pattern-match past the permission allowlist. Aligns with the +# Heady governance rule: destructive actions require explicit human confirmation. +# +# Exit contract: emits a PreToolUse permissionDecision. "deny" hard-blocks the +# call; "ask" forces a confirmation prompt; absence of either lets normal +# permission rules decide. Never blocks on benign commands. +set -uo pipefail + +payload="$(cat)" +if command -v jq >/dev/null 2>&1; then + cmd="$(printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null)" +else + cmd="$payload" +fi + +emit() { + # $1 = allow|deny|ask, $2 = reason + jq -n --arg d "$1" --arg r "$2" \ + '{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: $d, permissionDecisionReason: $r}}' +} + +# Catastrophic / irreversible — hard deny. +deny_patterns=( + 'rm[[:space:]]+-rf[[:space:]]+/' + 'rm[[:space:]]+-rf[[:space:]]+~' + ':\(\)\{.*\|.*&.*\}' + 'git[[:space:]]+push[[:space:]].*(--force|-f)([[:space:]]|$)' + 'git[[:space:]]+reset[[:space:]]+--hard[[:space:]]+origin' + 'mkfs\.' + 'dd[[:space:]]+if=.*of=/dev/' + '>[[:space:]]*/dev/sd' +) + +# Destructive but legitimate in context — require confirmation. +ask_patterns=( + 'DROP[[:space:]]+(TABLE|DATABASE|SCHEMA)' + 'TRUNCATE[[:space:]]+TABLE' + 'gcloud[[:space:]]+.*delete' + 'gh[[:space:]]+repo[[:space:]]+delete' + 'wrangler[[:space:]]+.*delete' + 'kubectl[[:space:]]+delete' +) + +for p in "${deny_patterns[@]}"; do + if printf '%s' "$cmd" | grep -qiE "$p"; then + emit deny "Blocked by Heady guard: matches catastrophic pattern /$p/. Irreversible data/infra loss — run by hand after confirming." + exit 0 + fi +done + +for p in "${ask_patterns[@]}"; do + if printf '%s' "$cmd" | grep -qiE "$p"; then + emit ask "Heady guard: destructive operation (/$p/). Confirm before proceeding." + exit 0 + fi +done + +# No match: stay silent so normal permission rules apply. +exit 0 diff --git a/.claude/hooks/session-context.sh b/.claude/hooks/session-context.sh new file mode 100755 index 0000000..344e95b --- /dev/null +++ b/.claude/hooks/session-context.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# SessionStart hook — injects live Heady ecosystem context into the model at +# the start of every session so it operates against current repo state, not a +# stale snapshot. Emits the SessionStart additionalContext contract as JSON. +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +cd "$repo_root" + +branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'detached')" +head_sha="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" +head_subject="$(git log -1 --pretty=%s 2>/dev/null || echo 'n/a')" + +# Surface which canonical config files are present (source of truth per CLAUDE.md). +configs="" +for f in configs/hcfullpipeline.yaml configs/resource-policies.yaml \ + configs/governance-policies.yaml configs/service-catalog.yaml \ + heady-registry.json; do + [ -f "$f" ] && configs="${configs}${f} " +done +[ -z "$configs" ] && configs="(none found in this repo)" + +context=$(cat <