Add formatters, hooks, memory entries and context-load improvements

- formatters/js, md, yaml: new/updated formatter scripts
- hooks/set-wezterm-profile.sh: new hook for WezTerm profile switching
- scripts/context-load: improvements from recent sessions
- HOOKS.md, MEMORY.md: updated documentation and index entries
- memory/log, memory/reference-infrastructure-docs.md: session logs and reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-03-19 10:54:49 +13:00
parent 9d747199fc
commit c3557a3d97
9 changed files with 200 additions and 3 deletions

View File

@@ -9,6 +9,7 @@ Claude Code hooks that run automatically in response to events. Source of truth
| Pre-compact backup | PreCompact (auto + manual) | `hooks/pre-compact-backup.sh` | | Pre-compact backup | PreCompact (auto + manual) | `hooks/pre-compact-backup.sh` |
| Post-edit lint | PostToolUse (Edit/Write/MultiEdit) | `hooks/post-edit-lint.sh` | | Post-edit lint | PostToolUse (Edit/Write/MultiEdit) | `hooks/post-edit-lint.sh` |
| Pre-commit lint | Git pre-commit | `hooks/pre-commit-lint.sh` | | Pre-commit lint | Git pre-commit | `hooks/pre-commit-lint.sh` |
| WezTerm profile theme | SessionStart (per-profile) | `hooks/set-wezterm-profile.sh` |
## Post-edit Lint ## Post-edit Lint
@@ -92,6 +93,21 @@ This symlinks all hooks into `~/.claude/hooks/` and prints the `settings.json` c
scripts/setup-formatters.sh <project-dir> <ext> [<ext> ...] scripts/setup-formatters.sh <project-dir> <ext> [<ext> ...]
``` ```
## WezTerm Profile Theme
Sets the WezTerm terminal theme when a Claude session starts or resumes, by emitting an OSC 1337 `SetUserVar` escape sequence. WezTerm fires its `user-var-changed` Lua event and applies the matching color scheme and background image from `wezterm.lua`.
**Derives profile name** from `CLAUDE_CONFIG_DIR` (e.g. `~/.claude-octopus` → `octopus`). No-ops for the default profile.
**Writes to `/dev/tty`** to reach WezTerm directly, bypassing Claude Code's stdout capture. Safe in non-WezTerm terminals — the sequence is silently ignored.
**Configure per profile** in `~/.claude-<name>/settings.json`:
```json
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/set-wezterm-profile.sh" }] }
]
```
## Adding New Hooks ## Adding New Hooks
1. Create the script in `hooks/` 1. Create the script in `hooks/`

View File

@@ -23,6 +23,10 @@
- [/context-load](memory/skill-context-load.md) — Reload project context after /clear or mid-session context loss - [/context-load](memory/skill-context-load.md) — Reload project context after /clear or mid-session context loss
- [/housekeeping](memory/skill-housekeeping.md) — Cross-project health check: git status, unreflected logs, skill validation, pipeline recommendations - [/housekeeping](memory/skill-housekeeping.md) — Cross-project health check: git status, unreflected logs, skill validation, pipeline recommendations
## References
- [Infrastructure Docs Repo](memory/reference-infrastructure-docs.md) — Shared infrastructure catalog at ~/dev/claude/docs/infrastructure/homelab — system descriptions and dependency map for the homelab
## Topic Files ## Topic Files
- [Decisions](memory/decisions.md) — Architecture and design decisions: pipeline design, linting system, context-load, CLAUDE.md structure - [Decisions](memory/decisions.md) — Architecture and design decisions: pipeline design, linting system, context-load, CLAUDE.md structure

View File

@@ -1 +0,0 @@
ts

25
formatters/js Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Formatter: TypeScript/JavaScript — biome format + biome check
# Contract: $1 = absolute file path, format in place, lint, exit 0 if clean / 1 if errors
# Missing tools: exit 0 silently
FILE="$1"
[ -z "$FILE" ] && exit 0
[ -f "$FILE" ] || exit 0
command -v biome >/dev/null 2>&1 || exit 0
# Format in place
biome format --write "$FILE" 2>/dev/null
# Auto-fix what we can
biome check --fix "$FILE" 2>/dev/null
# Final lint pass — remaining errors go to stderr
ERRORS=$(biome check "$FILE" 2>&1)
if [ $? -ne 0 ]; then
echo "$ERRORS" >&2
exit 1
fi
exit 0

View File

@@ -1 +0,0 @@
json

19
formatters/md Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Formatter: JSON/YAML/Markdown — prettier
# Contract: $1 = absolute file path, format in place, lint, exit 0 if clean / 1 if errors
# Missing tools: exit 0 silently
FILE="$1"
[ -z "$FILE" ] && exit 0
[ -f "$FILE" ] || exit 0
command -v prettier >/dev/null 2>&1 || exit 0
# Format in place (prettier auto-detects parser from extension)
ERRORS=$(prettier --write "$FILE" 2>&1)
if [ $? -ne 0 ]; then
echo "$ERRORS" >&2
exit 1
fi
exit 0

View File

@@ -1 +0,0 @@
json

19
formatters/yaml Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Formatter: JSON/YAML/Markdown — prettier
# Contract: $1 = absolute file path, format in place, lint, exit 0 if clean / 1 if errors
# Missing tools: exit 0 silently
FILE="$1"
[ -z "$FILE" ] && exit 0
[ -f "$FILE" ] || exit 0
command -v prettier >/dev/null 2>&1 || exit 0
# Format in place (prettier auto-detects parser from extension)
ERRORS=$(prettier --write "$FILE" 2>&1)
if [ $? -ne 0 ]; then
echo "$ERRORS" >&2
exit 1
fi
exit 0

44
hooks/set-wezterm-profile.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# set-wezterm-profile.sh — SessionStart hook: emit WezTerm theme escape sequence
#
# Derives the profile name from CLAUDE_CONFIG_DIR (e.g. ~/.claude-octopus → octopus)
# and emits a SetUserVar escape sequence so WezTerm fires its user-var-changed
# event and applies the matching theme from wezterm.lua.
#
# Hook subprocesses have no controlling terminal, so the escape sequence is
# written to the pts device found by walking up the process tree.
# Safe in non-WezTerm terminals (escape sequence is silently ignored).
#
# Usage (settings.json):
# "SessionStart": [{ "hooks": [{ "type": "command",
# "command": "~/.claude/hooks/set-wezterm-profile.sh" }] }]
set -euo pipefail
profile_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
profile_name="${profile_dir##*/.claude-}"
# No profile suffix means this is the default profile — nothing to do
if [[ "$profile_name" == "$profile_dir" ]]; then
exit 0
fi
# Emit the escape sequence to the terminal device.
# Hook subprocesses have no controlling terminal (/dev/tty unavailable),
# so walk up the process tree to find the first ancestor with a pts device.
tty_device=""
pid=$$
while [[ "$pid" -gt 1 ]]; do
pts=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ')
if [[ -n "$pts" && "$pts" != "?" && -w "/dev/$pts" ]]; then
tty_device="/dev/$pts"
break
fi
pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')
done
if [[ -n "$tty_device" ]]; then
printf '\e]1337;SetUserVar=%s=%s\a' CLAUDE_PROFILE \
"$(printf '%s' "$profile_name" | base64)" >"$tty_device" 2>/dev/null || true
fi

View File

@@ -0,0 +1,23 @@
# Session Log — 2026-03-18
## Summary
Created a shared infrastructure catalog repo (`homelab/infrastructure-docs`) documenting all homelab systems with dependency tracking. 20 system files covering physical hosts, cluster core, networking, identity, platform services, and applications.
## Decisions
- Decision: Infrastructure knowledge goes in a dedicated repo, not MEMORY.md — Rationale: Infrastructure maps are authoritative and maintained (add/remove systems), not accumulated learnings. Different lifecycle than gotchas/reflections.
- Decision: Located at `~/dev/claude/docs/infrastructure/homelab/` with parent structure supporting future locations (e.g., customer sites) — Rationale: User expects to have infrastructure at multiple logical locations in future.
- Decision: Repo in `homelab` org on Gitea, not `skynet` — Rationale: Infrastructure documentation, not AI-focused.
- Decision: Each system file uses consistent structure (what it does, depends on, depended on by, managed by) — Rationale: Enables tracing dependencies in either direction.
## Gotchas Discovered
- **[gitea]** Symptom: Push to new repo failed with "User permission denied for writing" — Fix: The SSH alias `gitea.oreillyit.nz-homelab` authenticates as `cluster-administrator`, but repo was created by `ai_admin` via API. Added `cluster-administrator` as admin collaborator via API before push succeeded.
## Key Context
- `ai_admin` Gitea token is in `cluster-bootstrap/local_secrets/gitea_ai_admin` (not in `~/dev/claude/secrets/`)
- Created repo via Gitea API: `POST /api/v1/orgs/homelab/repos`
- Added collaborator via API: `PUT /api/v1/repos/homelab/infrastructure-docs/collaborators/cluster-administrator`
- Memory reference added to both claude-foundations MEMORY.md and Claude profile memory
## Process Notes
- Good pattern: discussing the conceptual approach (MEMORY vs SPEC vs dedicated catalog) before jumping into implementation
- The Gitea permission issue could be avoided by always adding `cluster-administrator` as collaborator when creating repos via `ai_admin` API token for the `homelab` org

View File

@@ -0,0 +1,40 @@
# Infrastructure Docs Repo
## Location
- Filesystem: `~/dev/claude/docs/infrastructure/homelab/`
- Repo: `gitea.oreillyit.nz/homelab/infrastructure-docs`
- SSH remote: `git@gitea.oreillyit.nz-homelab:homelab/infrastructure-docs.git`
## Purpose
Authoritative catalog of all systems in the homelab environment. Unlike MEMORY.md (accumulated learnings) or CLAUDE.md (project conventions), this is a **maintained map** — updated when systems are added/removed, not accumulated over time.
Answers: "What depends on X?", "What breaks if Y goes down?", "What manages Z?"
## Structure
- `INDEX.md` — thin index listing all system files, grouped by category
- One `.md` per system (e.g., `cilium.md`, `traefik.md`, `authelia.md`)
- Each file: what it does, network details, depends on, depended on by, managed by
- Parent `docs/infrastructure/` supports multiple locations (future: `customer-site-x/`)
## Categories
- Physical/Hosting: proxmox, vps, bootstrap-vm
- Cluster Core: talos, cilium, argocd
- Storage: proxmox-csi, proxmox-ccm
- Networking: wireguard, caddy, traefik, coredns, dnsmasq
- Identity: authelia, headscale
- Platform: gitea, email-relay, homepage, cert-pipeline
- Applications: octopus-deploy, external-services
## Source projects
Documents systems from both `cluster-bootstrap` and `cluster-apps`. Shared across projects — not inside any single project repo.
## When to update
- Adding/removing/replacing a system
- Dependencies change (new service depends on Authelia, etc.)
- Major version upgrades worth noting

View File

@@ -80,6 +80,16 @@ if [[ ${#claude_dirs[@]} -eq 0 ]]; then
echo "# Searched from: $PWD" >&2 echo "# Searched from: $PWD" >&2
fi fi
# --- Output: Claude profile ---
profile_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
profile_name="$(basename "$profile_dir")"
if [[ "$profile_name" == ".claude" ]]; then
emit_header "CLAUDE PROFILE: default (~/.claude)"
else
emit_header "CLAUDE PROFILE: $profile_name ($profile_dir)"
fi
# --- Output: CLAUDE.md files (top-down) + tree from each --- # --- Output: CLAUDE.md files (top-down) + tree from each ---
# Track files we've already emitted to avoid duplicates # Track files we've already emitted to avoid duplicates