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:
16
HOOKS.md
16
HOOKS.md
@@ -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/`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
ts
|
|
||||||
25
formatters/js
Executable file
25
formatters/js
Executable 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
|
||||||
@@ -1 +0,0 @@
|
|||||||
json
|
|
||||||
19
formatters/md
Executable file
19
formatters/md
Executable 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
|
||||||
@@ -1 +0,0 @@
|
|||||||
json
|
|
||||||
19
formatters/yaml
Executable file
19
formatters/yaml
Executable 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
44
hooks/set-wezterm-profile.sh
Executable 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
|
||||||
23
memory/log/2026-03-18.002319.md
Normal file
23
memory/log/2026-03-18.002319.md
Normal 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
|
||||||
40
memory/reference-infrastructure-docs.md
Normal file
40
memory/reference-infrastructure-docs.md
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user