Add statusline scripts, context-load improvements, and prior distill updates
- Add statusline.sh and set-topic.sh for per-session status line topics - Update context-load with improved directory walking and output format - Update CLAUDE.md with status line docs and early-call safety note - Update MEMORY.md and README.md with new script/skill entries - Add memory files: script-statusline, skill-decompose, skill-orchestrate, gotchas-gitea - Add networking.md best practice (nftables, systemd sockets, Docker forwarding, TLS) - Update best practices from prior distill: documentation, kubernetes, scripting, secrets-management, skills-development - Prune reflected session logs, add new session logs - Update reflection state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"last_run": "2026-03-16T22:19:21Z",
|
||||
"last_run": "2026-03-24T10:36:46Z",
|
||||
"processed": {
|
||||
"log/2026-03-12.233744.md": "ca482f03914a3b4dec89c2dbf6e0f2e5",
|
||||
"log/2026-03-13.100758.md": "65c5b65fdd5984c036f1de53f8c82f2a",
|
||||
"log/2026-03-13.115251.md": "56a27d18deeb89c8ce8b112b099ac7cf",
|
||||
"log/2026-03-15.225345.md": "ae9366aed0b4c87ecbbe535e00cb2cb2",
|
||||
"log/2026-03-17.103204.md": "56c04b01459f80134e680150c338e9eb"
|
||||
"log/2026-03-17.103204.md": "56c04b01459f80134e680150c338e9eb",
|
||||
"log/2026-03-18.002319.md": "68ab608a650421dcc663fd4ae7131fc9",
|
||||
"log/2026-03-23.123545.md": "2610a8adb53e4b831e3e4b0c1d7cad44"
|
||||
}
|
||||
}
|
||||
|
||||
31
CLAUDE.md
31
CLAUDE.md
@@ -19,6 +19,8 @@
|
||||
- **No project right now**: Do nothing further — just respond normally.
|
||||
- **New project!**: Follow the "New Projects" section below. Also read `~/dev/claude/secrets/` as above. Read `BESTPRACTICES.md` (loaded automatically by `context-load`) and load topic files relevant to the new project's technology stack.
|
||||
|
||||
3. **Set the status line topic** after the user selects a project or describes their task. See the "Status Line" section below.
|
||||
|
||||
## Secrets (`~/dev/claude/secrets/`)
|
||||
|
||||
**CRITICAL — treat this folder with extreme paranoia:**
|
||||
@@ -277,3 +279,32 @@ These are hard-won lessons from real project work:
|
||||
- **Automate repeated sequences.** If you run the same 3+ commands in sequence more than once, it should become a script.
|
||||
- **Reflect after milestones.** Don't just finish — review what happened, what went wrong, what can be improved. Write it down so future sessions benefit.
|
||||
|
||||
## Status Line
|
||||
|
||||
A persistent bar at the bottom of Claude Code shows the current topic, model, and context usage: `[Model Name] topic | N% context`.
|
||||
|
||||
### Setting the Topic
|
||||
|
||||
After the user selects a project or describes their task (i.e., after the first response where the status line has had a chance to run), set the topic:
|
||||
|
||||
```bash
|
||||
~/.claude/status/set-topic.sh "$(pwd)" "project-name: brief task description"
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
- `~/.claude/status/set-topic.sh "$(pwd)" "brainiac-app: M2 web frontend"`
|
||||
- `~/.claude/status/set-topic.sh "$(pwd)" "cluster-bootstrap: Cilium upgrade"`
|
||||
- `~/.claude/status/set-topic.sh "$(pwd)" "General chat"`
|
||||
|
||||
### When to Update
|
||||
|
||||
- **Session start**: Set the topic once the user picks a project or task
|
||||
- **Focus change**: Update if the user shifts to a different project or task mid-session
|
||||
- **Keep it short**: Aim for `project: task` format, under ~40 characters
|
||||
|
||||
### How It Works
|
||||
|
||||
The status line script (`scripts/statusline.sh`) runs after each assistant message. It writes the session ID to `/tmp/claude-session-id-<md5 of cwd>`, which `set-topic.sh` reads to find the correct per-session topic file at `~/.claude/status/<session-id>/claude-topic.txt`.
|
||||
|
||||
**Early calls are safe:** If `set-topic.sh` is called before the status line has run (i.e., on the first message), the topic is queued to a pending file and automatically applied when the status line first runs after the next response.
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- [install-hooks](memory/script-install-hooks.md) — Symlinks hooks into ~/.claude/hooks/ and prints settings.json config
|
||||
- [setup-formatters](memory/script-setup-formatters.md) — Opts a project into auto-formatting by symlinking formatter scripts
|
||||
- [git-status-report](memory/script-git-status-report.md) — Scans directories for git repos, reports uncommitted changes and remote sync (lives in small-scripts)
|
||||
- [statusline](memory/script-statusline.md) — Status bar scripts: statusline.sh (renderer) and set-topic.sh (topic setter) for per-session topic display
|
||||
- [require-plan-file](memory/script-require-plan-file.md) — PreToolUse hook: blocks ExitPlanMode unless a *-PLAN.md file exists in the project root
|
||||
|
||||
## Skills
|
||||
@@ -22,6 +23,8 @@
|
||||
- [/linter](memory/skill-linter.md) — Audit and manage project formatters/linters (scan, run, cleanup modes)
|
||||
- [/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
|
||||
- [/decompose](memory/skill-decompose.md) — Break tasks into subtasks with dependency graph, write .agent-tasks.json for container agent orchestration
|
||||
- [/orchestrate](memory/skill-orchestrate.md) — Check task state, launch container agents in git worktrees. Use `/loop 2m /orchestrate` for auto-polling
|
||||
|
||||
## References
|
||||
|
||||
@@ -31,4 +34,5 @@
|
||||
|
||||
- [Decisions](memory/decisions.md) — Architecture and design decisions: pipeline design, linting system, context-load, CLAUDE.md structure
|
||||
- [Process Lessons](memory/process-lessons.md) — Working rules: git-status at session start, plan mode for architecture, hook exit codes, ssh-agent
|
||||
- [Gitea Gotchas](memory/gotchas-gitea.md) — Push permission denied when repo created by different API user than SSH alias
|
||||
- [Skills Gotchas](memory/gotchas-skills.md) — Broken symlinks under set -e, mid-session skill discovery limitation
|
||||
|
||||
@@ -23,6 +23,8 @@ claude-foundations/
|
||||
scripts/
|
||||
install-hooks.sh # Symlink hooks into ~/.claude/hooks/
|
||||
setup-formatters.sh # Set up formatters for a project
|
||||
statusline.sh # Status line renderer (symlinked from ~/.claude/status/)
|
||||
set-topic.sh # Set per-session topic for the status line
|
||||
best-practices/ # Generalised best practices (one file per topic)
|
||||
context/ # Active work focus detail files
|
||||
memory/ # Session logs and reflections
|
||||
@@ -141,3 +143,5 @@ rm <file>.pre-lint
|
||||
|--------|---------|
|
||||
| `scripts/install-hooks.sh` | Symlink all hooks to `~/.claude/hooks/` and print settings.json config |
|
||||
| `scripts/setup-formatters.sh` | Create formatter symlinks in a target project |
|
||||
| `scripts/statusline.sh` | Status line renderer — shows topic, model, context %. Symlinked from `~/.claude/status/` |
|
||||
| `scripts/set-topic.sh` | Set the session topic: `set-topic.sh <cwd> "topic text"` |
|
||||
|
||||
@@ -48,3 +48,7 @@ Human-readable project documentation:
|
||||
- Quick start / setup instructions
|
||||
- Milestone table with status
|
||||
- Scripts section listing every script with purpose and usage
|
||||
|
||||
## Extract Reusable Patterns Early
|
||||
|
||||
When a reusable pattern emerges during project work (a gotcha that applies to any project using the same tool, a process lesson that generalises), extract it into a best-practices guide immediately rather than waiting for a dedicated distillation pass. The guide pays for itself when used to audit and improve the current project in the same session, and benefits all future projects.
|
||||
|
||||
@@ -56,6 +56,11 @@ Manual bootstrap secrets (encryption keys, OIDC client secrets) must be document
|
||||
- **Namespace PodSecurity labels must match container security contexts.** DinD, CSI drivers, and other privileged workloads need `pod-security.kubernetes.io/enforce: privileged` on their namespace. A `baseline` or `restricted` namespace silently blocks privileged pods.
|
||||
- **Document privileged namespace requirements.** When a workload needs elevated privileges, document the specific requirement (e.g., "Docker-in-Docker for CI builds") alongside the namespace label.
|
||||
|
||||
## ArgoCD Source Type Detection
|
||||
|
||||
- **ArgoCD auto-detects Kustomize.** When a source directory contains `kustomization.yaml`, ArgoCD runs Kustomize automatically. Adding an explicit `directory:` source type overrides this detection and causes ArgoCD to try applying `kustomization.yaml` as a raw K8s resource, which fails with schema errors. Remove explicit directory source types from Kustomize sources.
|
||||
- **Credential template URL-prefix must match exactly.** ArgoCD repo-creds secrets use URL prefix matching. When migrating Git server URLs (hostname, protocol, or port changes), update the credential template to match the new prefix. Stale credentials cause "authentication required" errors on all apps using that prefix.
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
- `enableServiceLinks: false` may be needed when K8s-injected service env vars conflict with app config (e.g., Authelia interprets `AUTHELIA_*` service vars as configuration).
|
||||
|
||||
33
best-practices/networking.md
Normal file
33
best-practices/networking.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Networking & Infrastructure
|
||||
|
||||
## nftables Flush Ruleset on Remote Hosts
|
||||
|
||||
On remote hosts, `nftables flush ruleset` followed by a failed rule load leaves the host with NO firewall. SSH survives only on existing connections — new connections are blocked or allowed depending on the default policy.
|
||||
|
||||
**Always validate rules before applying:** `nft -c -f <rulefile>` does a dry-run parse. For extra safety, deploy a cron-based auto-rollback timer that reverts rules unless explicitly confirmed (similar to `shutdown -c` pattern).
|
||||
|
||||
## systemd Socket Activation Overrides Config File Ports
|
||||
|
||||
On modern Linux systems (Ubuntu 24.04+), systemd socket activation controls the listening port for services like SSH. Editing the service config file alone (e.g., `sshd_config Port 2222`) has no effect — the socket unit still binds the original port.
|
||||
|
||||
**Check socket activation first:** `systemctl cat <service>.socket` shows whether socket activation is in play. If so, override the socket unit's `ListenStream` directive, not the service config.
|
||||
|
||||
## Docker Sets iptables FORWARD Policy to DROP
|
||||
|
||||
Docker sets the iptables FORWARD chain default policy to DROP. This affects ALL forwarding on the host, not just Docker traffic. Non-Docker forwarding (VPN, VM bridges, custom NAT) silently breaks.
|
||||
|
||||
**Fix:** Add explicit ACCEPT rules in the `DOCKER-USER` chain for non-Docker forwarding needs. This chain is processed before Docker's own rules and persists across Docker restarts.
|
||||
|
||||
## HTTP Host Header vs TLS SNI Are Different Layers
|
||||
|
||||
When proxying to a backend over HTTPS, two independent identifiers must be set correctly:
|
||||
- **TLS SNI** — sent during the TLS handshake, used for certificate selection. Missing SNI causes `x509: cannot validate certificate for <IP>`.
|
||||
- **HTTP Host header** — sent after TLS is established, used for virtual host routing. Missing or wrong Host header causes 404 from the backend.
|
||||
|
||||
A reverse proxy must set both. They often need to be the same value, but they're configured independently.
|
||||
|
||||
## Wildcard Certs in Auto-Renewing Proxies
|
||||
|
||||
Auto-renewing proxies (Caddy, Traefik with Let's Encrypt, etc.) that also support file-loaded certificates treat file-loaded certs as globally available. A wildcard cert loaded for one site block will match ALL matching subdomains, silently preventing automatic certificate issuance for other sites.
|
||||
|
||||
**Rule:** Use automatic certificate management for all sites. Don't mix file-loaded and automatic certs unless you understand the matching priority.
|
||||
@@ -44,3 +44,5 @@ Every script that modifies state should support `--dryrun` / `-n`:
|
||||
- `grep` interprets option-like strings (starting with `-`) as flags — use `--` terminator before patterns or input that may start with dashes.
|
||||
- Always quote variables in conditionals and file paths
|
||||
- Use `trap` for cleanup of temp files and credentials
|
||||
- **Use `git diff --numstat` for binary file detection** instead of `file`. The `file` command is unreliable (marks shell scripts as "executable"), while `git diff --numstat` shows `-` for binary files using git's robust binary detection heuristics.
|
||||
- **Use `cat -A` to diagnose invisible character issues.** Reveals non-printing characters like em dashes, zero-width spaces, and smart quotes that look identical to correct characters but break YAML parsers, config files, and frontmatter. Essential when a file looks correct but tooling rejects it.
|
||||
|
||||
@@ -42,6 +42,12 @@ Some secrets are chicken-and-egg (e.g., the age decryption key for ArgoCD's KSOP
|
||||
- **Set alerts before expiry.** For long-lived credentials (e.g., 720-day OAuth client secrets), set calendar reminders or automated monitoring alerts well before they expire.
|
||||
- **Rotation plan.** Know the rotation procedure before you need it — some credential types (e.g., Azure app registrations) require coordinated updates across multiple systems.
|
||||
|
||||
## Multi-Field Secret Files
|
||||
|
||||
Secret files that contain multiple fields (e.g., repo URL, token, username) cannot be used as bare values. Consumers must parse individual fields (e.g., `grep + awk` or structured YAML/JSON parsing).
|
||||
|
||||
The multi-field format is preferable because it's self-documenting — all related credentials live together. But any automation reading the file needs extraction logic, not just `cat`.
|
||||
|
||||
## Backup Considerations
|
||||
|
||||
Backup plans must include encryption keys (age private keys, etc.) so that encrypted data in Git repos remains recoverable.
|
||||
|
||||
@@ -34,3 +34,13 @@
|
||||
- **Prefer specific tool patterns over broad ones** — `Bash(git log *)` is safer than `Bash(git *)`
|
||||
- **Fallback to tool instructions for dynamic paths** — if a command needs `$ARGUMENTS` to compute a path, use a plain-text instruction telling Claude to use the Read tool instead
|
||||
- **Env var expansion works** — `${CLAUDE_PROJECT_ROOT}` expands in `!`command`` blocks because they run as shell commands. This is the recommended pattern for portable cross-project paths.
|
||||
|
||||
## Non-ASCII in YAML Frontmatter
|
||||
|
||||
Skills with em dashes (`—`), smart quotes (`"`), or other non-ASCII characters in the YAML frontmatter `description` field fail to load silently — the skill appears as "Unknown skill" with no error message. The markdown body below the frontmatter can contain any characters.
|
||||
|
||||
AI models commonly generate em dashes instead of regular dashes. Always validate skill files (e.g., with `cat -A` or a dedicated validator) before committing.
|
||||
|
||||
## Profile-Independent Skills Directories
|
||||
|
||||
Each Claude Code profile maintains a completely independent skills directory. Skills installed in one profile (e.g., default) are unavailable in other profiles (e.g., `.claude-octopus`). Install scripts must use the profile-aware config directory path rather than hardcoded paths like `~/.claude/skills/`.
|
||||
|
||||
@@ -59,3 +59,15 @@ Deep enough to show project structure without overwhelming output. Applied at ev
|
||||
## CONTEXT.md follows MEMORY.md pattern
|
||||
|
||||
Thin index + `context/` folder. Consistency with MEMORY.md. CONTEXT.md focuses on active work for agent orientation; MEMORY.md on accumulated learnings.
|
||||
|
||||
## Status line: Per-session topic files keyed by session ID
|
||||
|
||||
Topic files at `~/.claude/status/<session-id>/claude-topic.txt`. Session ID uniquely identifies each session, allowing multiple concurrent sessions to have independent topics without collisions.
|
||||
|
||||
## Status line: Bridge session ID via /tmp file
|
||||
|
||||
Claude has no direct access to its own session ID. The status line script writes it to `/tmp/claude-session-id-<md5 of cwd>`, which `set-topic.sh` reads to find the correct per-session topic directory.
|
||||
|
||||
## Status line: Config must exist in every profile settings file
|
||||
|
||||
Each profile (`~/.claude`, `~/.claude-octopus`, `~/.claude-oreillyit`) has its own `settings.json` with no inheritance. The `statusLine` entry must be added to each independently.
|
||||
|
||||
7
memory/gotchas-gitea.md
Normal file
7
memory/gotchas-gitea.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Gitea Gotchas
|
||||
|
||||
## Push fails when repo created by different API user than SSH alias
|
||||
|
||||
**Symptom:** `git push` to a new repo fails with "User permission denied for writing."
|
||||
**Cause:** Repo was created via API using `ai_admin` token, but SSH alias authenticates as `cluster-administrator` who has no access.
|
||||
**Fix:** After creating a repo via API, add the SSH-authenticating user as collaborator: `PUT /api/v1/repos/<org>/<repo>/collaborators/<ssh-user>` with admin permission. Do this before attempting to push.
|
||||
@@ -1,25 +0,0 @@
|
||||
# Session Log — 2026-03-12
|
||||
|
||||
## Summary
|
||||
Created the claude-foundations repo on Gitea (skynet org), built a best-practices folder with 11 topic files extracted from cluster-bootstrap and custom-claude-skills, then designed and implemented a three-tier knowledge distillation pipeline (/log, /reflect-logs, /distill-best-practices).
|
||||
|
||||
## Decisions
|
||||
- Decision: Separate `/reflect-logs` from existing `/reflect` — Rationale: different purpose (continuous vs milestone), different cadence, avoids overcomplicating the existing skill
|
||||
- Decision: Use MD5 hashes for reflection state, git SHAs for distill state — Rationale: log files may not be committed when reflected; distill explicitly works across committed repos
|
||||
- Decision: Timestamp-based session IDs (HHMMSS) — Rationale: human-readable, naturally sorted, no external dependencies
|
||||
- Decision: Pruning happens in `/log` not `/reflect-logs` — Rationale: runs most frequently, keeps log dir clean as side effect of the most common operation
|
||||
- Decision: `/distill-best-practices` is interactive (proposals before changes) — Rationale: cross-project conventions need human judgment
|
||||
|
||||
## Gotchas Discovered
|
||||
- **[skills]** Symptom: install.sh failed with exit 1 on broken symlink — Fix: old reflect symlink pointed to pre-move path (`~/dev/claude/custom-claude-skills/` instead of `~/dev/claude/projects/custom-claude-skills/`). `readlink -f` on a broken symlink returns empty string, causing comparison failure under `set -e`. Fixed by removing stale symlink and re-running.
|
||||
- **[skills]** Symptom: skills created mid-session not available as slash commands — Fix: skills are discovered at session start, not dynamically. New skills require a new session to become available.
|
||||
|
||||
## Key Context
|
||||
- Best practices files are in claude-foundations/best-practices/ with INDEX.md as the card catalog
|
||||
- Settings for the pipeline live in claude-foundations/settings.yaml
|
||||
- State files: `.reflection-state.json` (per-project), `best-practices/.distill-state.json` (in claude-foundations)
|
||||
- SSH key for ai_enablement is password-protected — needs ssh-agent loaded before git push
|
||||
|
||||
## Process Notes
|
||||
- The plan mode workflow worked well for this — explored existing patterns, designed the architecture, got approval, then executed cleanly
|
||||
- Creating all 11 best-practices files in parallel (single Write batch) was efficient
|
||||
@@ -1,22 +0,0 @@
|
||||
# Session Log — 2026-03-13
|
||||
|
||||
## Summary
|
||||
Implemented the composable multi-language linting and formatting system for Claude Code. Created formatter scripts, PostToolUse/pre-commit hooks, install scripts, `/linter` skill, best-practices documentation, and a README with project opt-in instructions.
|
||||
|
||||
## Decisions
|
||||
- Decision: Formatter scripts exit 1 on lint errors, dispatcher hook decides exit code (exit 2 for PostToolUse feedback) — Rationale: separates formatter logic from hook semantics; same scripts work for both PostToolUse and pre-commit
|
||||
- Decision: Checkpoint uses `git hash-object -w` with `.pre-lint` sidecar file — Rationale: fast (~1ms), no commits/stash, orphan blobs auto-GC'd; falls back to `cp` outside git repos
|
||||
- Decision: Projects opt in via symlinks in `formatters/` rather than config — Rationale: zero-config, visible in `ls`, no parsing needed; hook walks up directory tree to find `formatters/`
|
||||
- Decision: Symlinked README.md into `~/dev/claude/` for visibility — Rationale: makes setup guide discoverable from the top-level working directory
|
||||
|
||||
## Key Context
|
||||
- claude-foundations lives at `~/dev/claude/projects/claude-foundations/` (not `~/dev/claude/claude-foundations/`)
|
||||
- PostToolUse exit code 2 feeds stderr back to Claude as feedback without blocking the edit
|
||||
- Agent-type hooks are read-only (no Edit/Write) — lint fixing must happen via Agent tool subagent, not hooks
|
||||
- All hooks in an array run in parallel, not sequentially
|
||||
- Formatter contract: `$1` = absolute path, format in place, stderr for errors, exit 0/1, no `set -e`, silent on missing tools
|
||||
|
||||
## Process Notes
|
||||
- Plan was thorough and complete — implementation was straightforward with minimal deviation
|
||||
- Smoke tests confirmed all four paths: clean file, missing tools, no formatters dir, no extension
|
||||
- No actual lint errors were triggered during testing since test files were already clean
|
||||
@@ -1,25 +0,0 @@
|
||||
# Session Log — 2026-03-13
|
||||
|
||||
## Summary
|
||||
Cleaned up the root CLAUDE.md (removed duplicated Ansible/Helm sections, consolidated validation guidance, fixed best-practices references) and built a `context-load` / `start-claude` script pair for automated session context gathering. Introduced the CONTEXT.md pattern for future independent agent work.
|
||||
|
||||
## Decisions
|
||||
- Decision: Remove Ansible and Helm sections from root CLAUDE.md — Rationale: already covered with more detail in `best-practices/ansible.md` and `best-practices/helm.md`; technology-specific practices belong in best-practices, not the root guidelines
|
||||
- Decision: Fold "Validate Before Deploying" into Process Principles — Rationale: was duplicated content; the examples fit naturally in the existing bullet point
|
||||
- Decision: context-load walks upward from cwd collecting CLAUDE.md, CONTEXT.md, MEMORY.md, BESTPRACTICES.md — Rationale: gives hierarchical context inheritance; highest ancestor provides global guidelines, project dir provides specifics
|
||||
- Decision: Tree depth 3 from every CLAUDE.md location — Rationale: deep enough to show project structure without overwhelming output
|
||||
- Decision: Dedup loaded files via `readlink -f` — Rationale: root CLAUDE.md is a symlink to claude-foundations; without dedup it would load twice
|
||||
- Decision: CONTEXT.md follows MEMORY.md pattern (thin index + `context/` folder) — Rationale: consistency; CONTEXT.md focuses on active work for agent orientation, MEMORY.md on accumulated learnings
|
||||
|
||||
## Key Context
|
||||
- `~/dev/claude/CLAUDE.md` is a symlink to `~/dev/claude/projects/claude-foundations/CLAUDE.md` — this is intentional, claude-foundations is the canonical source
|
||||
- `context-load` output is passed via `--append-system-prompt` by the `start-claude` wrapper
|
||||
- Both scripts symlinked into `~/sbin/`
|
||||
- CONTEXT.md is intended for future Docker-based independent agent operation — each agent gets full context load, starting prompt points to relevant CONTEXT.md entry
|
||||
- git-status-report output is ANSI-stripped before inclusion in context
|
||||
- Also committed the previously uncommitted linting system (18 files) from the earlier session
|
||||
|
||||
## Process Notes
|
||||
- Session was efficient — cleanups and script creation done in parallel with minimal iteration
|
||||
- The context-load smoke tests from different directories caught the symlink dedup working correctly
|
||||
- Previous session's linting work was uncommitted — worth running `git-status-report` at session start to catch this pattern
|
||||
@@ -1,22 +0,0 @@
|
||||
# Session Log — 2026-03-15
|
||||
|
||||
## Summary
|
||||
Fixed the `/distill-best-practices` skill which was broken due to hardcoded and relative paths in `!`command`` blocks. Replaced all paths with `CLAUDE_PROJECT_ROOT` env var for portability, added a `find-project-root` helper script, and updated `settings.yaml` to use relative paths. Also fixed the same relative-path issue in `/log` and `/reflect-logs` skills.
|
||||
|
||||
## Decisions
|
||||
- Decision: Use `CLAUDE_PROJECT_ROOT` env var for all cross-project path resolution in skills — Rationale: Makes skills shareable with colleagues; hardcoded `~/dev/claude/` paths are user-specific and relative `../` paths break depending on CWD
|
||||
- Decision: Add Step 0 (detect project root) as runtime fallback in distill skill — Rationale: Skills should degrade gracefully if env var isn't set; Claude can walk up the directory tree to find highest CLAUDE.md
|
||||
- Decision: `settings.yaml` paths relative to CLAUDE_PROJECT_ROOT, not absolute — Rationale: Portability; `projects_dir: projects` instead of `~/dev/claude/projects`
|
||||
- Decision: Added `extra_projects` section to settings.yaml for projects outside `projects_dir` — Rationale: `small-scripts` lives at root level, not under `projects/`
|
||||
|
||||
## Gotchas Discovered
|
||||
- **[skills]** Symptom: `/distill-best-practices` failed with sandbox error — `cat ../claude-foundations/...` resolved to `/home/paul/dev/claude-foundations/` (outside sandbox) when CWD was `~/dev/claude/` — Fix: Replace all relative and hardcoded paths with `${CLAUDE_PROJECT_ROOT}` env var
|
||||
- **[skills]** Symptom: `!`command`` blocks can't use `$()` command substitution — Fix: Use env var expansion (`${CLAUDE_PROJECT_ROOT}`) which works, and fall back to runtime detection in skill instructions
|
||||
- **[skills]** Symptom: Claude Code Bash tool doesn't persist `export` across `;`-separated commands in the same invocation when the variable is used in file path arguments — Fix: Use `bash -c '...'` wrapper or ensure var is in shell profile
|
||||
|
||||
## Key Context
|
||||
- `settings.yaml` now tracks 6 projects under `distill.projects` plus `small-scripts` under `extra_projects`
|
||||
- Added projects: `agent-runtimes`, `claude-foundations`, `cluster-apps/octopus-deploy`, `hugo-accelerator`
|
||||
- `find-project-root` script created at `claude-foundations/scripts/find-project-root` — walks up from CWD to find highest CLAUDE.md
|
||||
- `CLAUDE_PROJECT_ROOT` export added to `~/.bashrc`
|
||||
- Three skills updated: `distill-best-practices` (full rewrite of paths), `log` and `reflect-logs` (settings fallback path)
|
||||
18
memory/log/2026-03-23.123545.md
Normal file
18
memory/log/2026-03-23.123545.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Session Log — 2026-03-23
|
||||
|
||||
## Summary
|
||||
Set up a persistent status line for Claude Code that displays session topic, model name, and context window percentage. Scripts were created, moved into claude-foundations, and documentation updated across CLAUDE.md, MEMORY.md, and README.md. Status line config was added to all three profile settings files (~/.claude, ~/.claude-octopus, ~/.claude-oreillyit).
|
||||
|
||||
## Decisions
|
||||
- Decision: Use per-session topic files at `~/.claude/status/<session-id>/claude-topic.txt` — Rationale: Session ID from the status line JSON uniquely identifies each session, allowing multiple concurrent sessions to have independent topics
|
||||
- Decision: Status line script writes session ID to `/tmp/claude-session-id-<md5 of cwd>` — Rationale: Claude has no direct access to its own session ID; the status line script bridges this by writing it to a predictable path keyed by working directory
|
||||
- Decision: Canonical scripts live in `claude-foundations/scripts/`, symlinked from `~/.claude/status/` — Rationale: Follows the same pattern as hooks (canonical in repo, symlinked to ~/.claude/) for version control and consistency
|
||||
- Decision: Status line config added to all three profile settings files — Rationale: Each profile (default, octopus, oreillyit) has its own settings.json; statusLine must be present in each
|
||||
|
||||
## Gotchas Discovered
|
||||
- **[claude-code]** Symptom: `set-topic.sh` fails on the first message of a session because the status line hasn't run yet to write the session ID file — Fix: This is expected; set the topic from the second message onward. The bootstrap caveat is documented in CLAUDE.md and the memory file.
|
||||
|
||||
## Key Context
|
||||
- Claude Code status line receives session JSON on stdin with fields: `session_id`, `model`, `context_window`, `cwd`, `cost`, `rate_limits`, etc.
|
||||
- Status line script runs after each assistant message, debounced at 300ms
|
||||
- Three profile settings files need to stay in sync: `~/.claude/settings.json`, `~/.claude-octopus/settings.json`, `~/.claude-oreillyit/settings.json`
|
||||
19
memory/log/2026-03-25.110612.md
Normal file
19
memory/log/2026-03-25.110612.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Session Log — 2026-03-25
|
||||
|
||||
## Summary
|
||||
Fixed the `set-topic.sh` bootstrap race condition where calling it before `statusline.sh` had run caused an error. Implemented a pending-topic queue mechanism and added the permission rule across all three Claude profiles.
|
||||
|
||||
## Decisions
|
||||
- Decision: Queue pending topics to `/tmp/claude-pending-topic-<hash>` instead of erroring — Rationale: `set-topic.sh` is called early in sessions (first message) before `statusline.sh` has written the session ID file. Erroring was noisy and unhelpful since the topic could just be deferred one response.
|
||||
- Decision: Have `statusline.sh` pick up and apply pending topics via `mv` — Rationale: Keeps the logic simple — statusline already runs after every response, so it's the natural place to consume the pending file. Using `mv` is atomic and avoids double-application.
|
||||
- Decision: Add `Bash(~/.claude/status/set-topic.sh:*)` to all three profile settings files — Rationale: The script is called every session start and is safe to auto-approve.
|
||||
|
||||
## Gotchas Discovered
|
||||
- **[statusline]** Symptom: `set-topic.sh` always errored on first message with "No session ID found" because `/tmp/claude-session-id-<hash>` doesn't exist until `statusline.sh` runs after the first response — Fix: Write topic to a pending file; `statusline.sh` picks it up on its next run.
|
||||
|
||||
## Key Context
|
||||
- Three profile settings files need to stay in sync: `~/.claude/settings.json`, `~/.claude-octopus/settings.json`, `~/.claude-oreillyit/settings.json`
|
||||
- The `statusline.sh` hash uses `echo -n "$CWD" | md5sum` (note the `-n` flag — important for hash consistency with `set-topic.sh`)
|
||||
|
||||
## Process Notes
|
||||
- Clean fix — two small script changes plus doc updates. Tested both the happy path (session ID exists) and the pending path (no session ID yet).
|
||||
@@ -31,3 +31,11 @@ When building a tool that detects known problems (like `validate-skill`), first
|
||||
## Batch parallel file creation for efficiency
|
||||
|
||||
Creating many independent files in a single Write batch (e.g., 11 best-practices files at once) is significantly faster than sequential creation.
|
||||
|
||||
## Evaluate the right home for new content before building
|
||||
|
||||
Before creating a new system or document, discuss where it belongs conceptually (e.g., MEMORY.md vs SPEC vs dedicated catalog). Different content types have different lifecycles — accumulated learnings vs authoritative maintained maps vs behavioral contracts. Picking the wrong home means future maintenance friction.
|
||||
|
||||
## Add SSH-authenticating user as collaborator when creating repos via API
|
||||
|
||||
When creating Gitea repos via API token (e.g., `ai_admin`), the SSH alias may authenticate as a different user (e.g., `cluster-administrator`). Always add the SSH user as admin collaborator via API before pushing.
|
||||
|
||||
46
memory/script-statusline.md
Normal file
46
memory/script-statusline.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Status Line Scripts
|
||||
|
||||
## Overview
|
||||
|
||||
Two scripts that power a persistent status bar at the bottom of Claude Code, showing the current session topic, model name, and context window usage.
|
||||
|
||||
## Files
|
||||
|
||||
- **`scripts/statusline.sh`** — Status line renderer. Receives session JSON on stdin from Claude Code, outputs formatted text. Also writes the session ID to `/tmp/claude-session-id-<cwd-hash>` so Claude can discover it.
|
||||
- **`scripts/set-topic.sh`** — Helper to write the topic file for a session. Usage: `set-topic.sh <cwd> "topic text"`.
|
||||
- **`~/.claude/status/`** — Symlinks back to the canonical scripts. Claude Code's `settings.json` points here.
|
||||
- **`~/.claude/status/<session-id>/claude-topic.txt`** — Per-session topic files, written by `set-topic.sh`.
|
||||
|
||||
## Setup
|
||||
|
||||
The `statusLine` config in `~/.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/status/statusline.sh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Symlinks in `~/.claude/status/`:
|
||||
- `statusline.sh` → `~/dev/claude/projects/claude-foundations/scripts/statusline.sh`
|
||||
- `set-topic.sh` → `~/dev/claude/projects/claude-foundations/scripts/set-topic.sh`
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Claude Code runs `statusline.sh` after each assistant message (debounced 300ms)
|
||||
2. The script receives session JSON on stdin with `session_id`, `model`, `context_window`, `cwd`, etc.
|
||||
3. It writes the session ID to `/tmp/claude-session-id-<md5 of cwd>` so Claude can discover its own session
|
||||
4. It reads the topic from `~/.claude/status/<session-id>/claude-topic.txt` if it exists
|
||||
5. Outputs: `[Model Name] topic text | N% context` (or without topic if not set)
|
||||
|
||||
## Early Topic Setting
|
||||
|
||||
If `set-topic.sh` is called before the status line has run (e.g., on the first message), the topic is written to a pending file (`/tmp/claude-pending-topic-<hash>`). On its next run, `statusline.sh` picks up the pending file and moves it into the correct session topic location. No error is raised — the topic just appears on the next response.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `jq` — for parsing the session JSON
|
||||
- `md5sum` — for hashing the cwd to a predictable filename
|
||||
59
memory/skill-decompose.md
Normal file
59
memory/skill-decompose.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: skill-decompose
|
||||
description: /decompose skill — breaks tasks into subtasks with dependency graph, writes .agent-tasks.json for container agent orchestration
|
||||
type: reference
|
||||
---
|
||||
|
||||
# /decompose Skill
|
||||
|
||||
## Purpose
|
||||
|
||||
Decompose a complex task into independently-executable subtasks with a dependency graph. Each subtask gets a full prompt for a container agent. Writes `.agent-tasks.json` in the project root for `/orchestrate` to consume.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/decompose <task description>
|
||||
/decompose # uses conversation context
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Reads project context (CLAUDE.md, SPEC.md, PLAN.md, spec/) to understand the project
|
||||
2. Breaks the task into subtasks — each self-contained, scoped to one deliverable, testable
|
||||
3. Presents a dependency graph and table for user approval
|
||||
4. Asks user to set `max_concurrent` (number of simultaneous container agents)
|
||||
5. Writes `.agent-tasks.json` with task IDs, prompts, dependencies, reads/writes, and status fields
|
||||
|
||||
### Task State Format
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "ISO timestamp",
|
||||
"project": "project-name",
|
||||
"max_concurrent": 3,
|
||||
"tasks": {
|
||||
"task-id": {
|
||||
"name": "...", "prompt": "...", "depends_on": [],
|
||||
"reads": [], "writes": [],
|
||||
"status": "pending", "branch": null, "worktree": null,
|
||||
"host": null, "container_id": null,
|
||||
"started_at": null, "completed_at": null,
|
||||
"exit_code": null, "error": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Status values: `pending`, `running`, `completed`, `failed`, `blocked`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Task prompts need to be fully self-contained — container agents have no conversation history
|
||||
- Include explicit "read these files first" instructions in each task's prompt
|
||||
- Over-decomposing creates merge overhead; under-decomposing wastes parallelism potential
|
||||
|
||||
## Used By
|
||||
|
||||
- `agent-runtimes` — M3 harness implementation decomposition
|
||||
- Any project needing parallel container agent work
|
||||
54
memory/skill-orchestrate.md
Normal file
54
memory/skill-orchestrate.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: skill-orchestrate
|
||||
description: /orchestrate skill — checks agent task state, launches container agents in git worktrees, updates .agent-tasks.json. Use with /loop 2m /orchestrate for auto-polling.
|
||||
type: reference
|
||||
---
|
||||
|
||||
# /orchestrate Skill
|
||||
|
||||
## Purpose
|
||||
|
||||
Poll `.agent-tasks.json`, check container statuses, and launch container agents for tasks whose dependencies are met. Each task runs in its own git worktree for isolation. Designed for repeated invocation via `/loop`.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/orchestrate # one-shot check and dispatch
|
||||
/loop 2m /orchestrate # auto-poll every 2 minutes
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
Each invocation:
|
||||
|
||||
1. **Check running containers** — `docker inspect` each running task's container. Mark completed (exit 0) or failed (non-zero). Capture logs on failure.
|
||||
2. **Identify ready tasks** — pending tasks whose dependencies are all completed. Mark tasks as `blocked` if a dependency failed.
|
||||
3. **Launch ready tasks** (up to `max_concurrent`):
|
||||
- Create a git worktree: `git worktree add .worktrees/<task-id> -b agent/<task-id>` (branches from dependency branch if applicable)
|
||||
- Launch container: `docker run -d` with worktree mounted at `/project`
|
||||
- Record container ID, host, branch, worktree path in task state
|
||||
4. **Report status** — concise table showing all task statuses
|
||||
5. **Update `.agent-tasks.json`**
|
||||
|
||||
When all tasks are resolved, suggests branch review and merge. Does NOT auto-merge.
|
||||
|
||||
## Git Worktree Isolation
|
||||
|
||||
- Each task gets its own branch (`agent/<task-id>`) and worktree (`.worktrees/<task-id>`)
|
||||
- Tasks with no dependencies branch from HEAD
|
||||
- Tasks depending on one completed task branch from that task's branch
|
||||
- Tasks with multiple dependencies get an octopus merge base branch
|
||||
- Worktrees share the `.git` object store — fast creation, minimal disk
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Uses `docker run -d` (no `--rm`) so container logs survive for inspection after exit
|
||||
- `CLAUDE_CODE_OAUTH_TOKEN` must be in the environment or readable from `~/dev/claude/secrets/claude/long_lived_oauth_token`
|
||||
- Containers mount the worktree path, not the main project — the absolute path must be correct
|
||||
- `Bash(git *)` in allowed-tools is broad (validator warns) but necessary for worktree/branch/merge operations
|
||||
- Stale containers (running > 30 minutes) are flagged but not killed automatically
|
||||
|
||||
## Used By
|
||||
|
||||
- `agent-runtimes` — M3 harness implementation
|
||||
- Any project using `/decompose` for parallel container agent work
|
||||
@@ -36,8 +36,8 @@ emit_file() {
|
||||
emit_tree() {
|
||||
local dir="$1"
|
||||
emit_header "TREE: $dir (depth $TREE_DEPTH)"
|
||||
tree -L "$TREE_DEPTH" --charset utf-8 -I '.git|node_modules|__pycache__|.venv|venv' "$dir" 2>/dev/null \
|
||||
|| find "$dir" -maxdepth "$TREE_DEPTH" -not -path '*/.git/*' -not -path '*/.git' | sort
|
||||
tree -L "$TREE_DEPTH" --charset utf-8 -I '.git|node_modules|__pycache__|.venv|venv|customers' "$dir" 2>/dev/null \
|
||||
|| find "$dir" -maxdepth "$TREE_DEPTH" -not -path '*/.git/*' -not -path '*/.git' -not -path '*/customers/*' -not -path '*/customers' | sort
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
28
scripts/set-topic.sh
Executable file
28
scripts/set-topic.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: set-topic.sh <cwd> <topic text>
|
||||
# Finds the session ID for the given cwd and writes the topic file
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CWD="$1"
|
||||
shift
|
||||
TOPIC="$*"
|
||||
|
||||
HASH=$(echo -n "$CWD" | md5sum | cut -d' ' -f1)
|
||||
SESSION_FILE="/tmp/claude-session-id-$HASH"
|
||||
|
||||
if [[ ! -f "$SESSION_FILE" ]]; then
|
||||
# Session ID file doesn't exist yet — the statusline hook hasn't run.
|
||||
# Write the topic to a pending file so statusline.sh can pick it up
|
||||
# on its first run for this cwd.
|
||||
PENDING_FILE="/tmp/claude-pending-topic-$HASH"
|
||||
echo "$TOPIC" > "$PENDING_FILE"
|
||||
echo "Topic queued (session ID not yet available). Will be applied after next response."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SESSION_ID=$(cat "$SESSION_FILE")
|
||||
TOPIC_DIR="$HOME/.claude/status/$SESSION_ID"
|
||||
mkdir -p "$TOPIC_DIR"
|
||||
echo "$TOPIC" > "$TOPIC_DIR/claude-topic.txt"
|
||||
echo "Topic set for session $SESSION_ID: $TOPIC"
|
||||
40
scripts/statusline.sh
Executable file
40
scripts/statusline.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# Claude Code status line: shows topic, model, and context usage
|
||||
# Reads session JSON from stdin, displays topic from per-session file
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DATA=$(cat)
|
||||
|
||||
SESSION_ID=$(echo "$DATA" | jq -r '.session_id // empty')
|
||||
MODEL=$(echo "$DATA" | jq -r '.model.display_name // "unknown"')
|
||||
CONTEXT_PCT=$(echo "$DATA" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
|
||||
|
||||
TOPIC=""
|
||||
if [[ -n "$SESSION_ID" ]]; then
|
||||
# Write session ID to a known location so Claude can discover it
|
||||
CWD=$(echo "$DATA" | jq -r '.cwd // empty')
|
||||
if [[ -n "$CWD" ]]; then
|
||||
HASH=$(echo -n "$CWD" | md5sum | cut -d' ' -f1)
|
||||
echo "$SESSION_ID" > "/tmp/claude-session-id-$HASH"
|
||||
|
||||
# Apply any pending topic that was set before the session ID existed
|
||||
PENDING_FILE="/tmp/claude-pending-topic-$HASH"
|
||||
if [[ -f "$PENDING_FILE" ]]; then
|
||||
TOPIC_DIR="$HOME/.claude/status/$SESSION_ID"
|
||||
mkdir -p "$TOPIC_DIR"
|
||||
mv "$PENDING_FILE" "$TOPIC_DIR/claude-topic.txt"
|
||||
fi
|
||||
fi
|
||||
|
||||
TOPIC_FILE="$HOME/.claude/status/$SESSION_ID/claude-topic.txt"
|
||||
if [[ -f "$TOPIC_FILE" ]]; then
|
||||
TOPIC=$(cat "$TOPIC_FILE")
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$TOPIC" ]]; then
|
||||
echo "[$MODEL] $TOPIC | ${CONTEXT_PCT}% context"
|
||||
else
|
||||
echo "[$MODEL] ${CONTEXT_PCT}% context"
|
||||
fi
|
||||
Reference in New Issue
Block a user