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:
Paul O'Reilly
2026-03-25 11:11:40 +13:00
parent 6c0f2db169
commit e7c8214499
25 changed files with 396 additions and 98 deletions

View File

@@ -1,11 +1,13 @@
{ {
"version": 1, "version": 1,
"last_run": "2026-03-16T22:19:21Z", "last_run": "2026-03-24T10:36:46Z",
"processed": { "processed": {
"log/2026-03-12.233744.md": "ca482f03914a3b4dec89c2dbf6e0f2e5", "log/2026-03-12.233744.md": "ca482f03914a3b4dec89c2dbf6e0f2e5",
"log/2026-03-13.100758.md": "65c5b65fdd5984c036f1de53f8c82f2a", "log/2026-03-13.100758.md": "65c5b65fdd5984c036f1de53f8c82f2a",
"log/2026-03-13.115251.md": "56a27d18deeb89c8ce8b112b099ac7cf", "log/2026-03-13.115251.md": "56a27d18deeb89c8ce8b112b099ac7cf",
"log/2026-03-15.225345.md": "ae9366aed0b4c87ecbbe535e00cb2cb2", "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"
} }
} }

View File

@@ -19,6 +19,8 @@
- **No project right now**: Do nothing further — just respond normally. - **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. - **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/`) ## Secrets (`~/dev/claude/secrets/`)
**CRITICAL — treat this folder with extreme paranoia:** **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. - **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. - **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.

View File

@@ -11,6 +11,7 @@
- [install-hooks](memory/script-install-hooks.md) — Symlinks hooks into ~/.claude/hooks/ and prints settings.json config - [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 - [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) - [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 - [require-plan-file](memory/script-require-plan-file.md) — PreToolUse hook: blocks ExitPlanMode unless a *-PLAN.md file exists in the project root
## Skills ## Skills
@@ -22,6 +23,8 @@
- [/linter](memory/skill-linter.md) — Audit and manage project formatters/linters (scan, run, cleanup modes) - [/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 - [/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
- [/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 ## References
@@ -31,4 +34,5 @@
- [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
- [Process Lessons](memory/process-lessons.md) — Working rules: git-status at session start, plan mode for architecture, hook exit codes, ssh-agent - [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 - [Skills Gotchas](memory/gotchas-skills.md) — Broken symlinks under set -e, mid-session skill discovery limitation

View File

@@ -23,6 +23,8 @@ claude-foundations/
scripts/ scripts/
install-hooks.sh # Symlink hooks into ~/.claude/hooks/ install-hooks.sh # Symlink hooks into ~/.claude/hooks/
setup-formatters.sh # Set up formatters for a project 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) best-practices/ # Generalised best practices (one file per topic)
context/ # Active work focus detail files context/ # Active work focus detail files
memory/ # Session logs and reflections 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/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/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"` |

View File

@@ -48,3 +48,7 @@ Human-readable project documentation:
- Quick start / setup instructions - Quick start / setup instructions
- Milestone table with status - Milestone table with status
- Scripts section listing every script with purpose and usage - 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.

View File

@@ -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. - **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. - **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 ## 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). - `enableServiceLinks: false` may be needed when K8s-injected service env vars conflict with app config (e.g., Authelia interprets `AUTHELIA_*` service vars as configuration).

View 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.

View File

@@ -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. - `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 - Always quote variables in conditionals and file paths
- Use `trap` for cleanup of temp files and credentials - 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.

View File

@@ -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. - **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. - **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 Considerations
Backup plans must include encryption keys (age private keys, etc.) so that encrypted data in Git repos remains recoverable. Backup plans must include encryption keys (age private keys, etc.) so that encrypted data in Git repos remains recoverable.

View File

@@ -34,3 +34,13 @@
- **Prefer specific tool patterns over broad ones** — `Bash(git log *)` is safer than `Bash(git *)` - **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 - **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. - **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/`.

View File

@@ -59,3 +59,15 @@ Deep enough to show project structure without overwhelming output. Applied at ev
## CONTEXT.md follows MEMORY.md pattern ## 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. 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
View 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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View 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`

View 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).

View File

@@ -31,3 +31,11 @@ When building a tool that detects known problems (like `validate-skill`), first
## Batch parallel file creation for efficiency ## 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. 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.

View 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
View 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

View 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

View File

@@ -36,8 +36,8 @@ emit_file() {
emit_tree() { emit_tree() {
local dir="$1" local dir="$1"
emit_header "TREE: $dir (depth $TREE_DEPTH)" 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 \ 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' | sort || find "$dir" -maxdepth "$TREE_DEPTH" -not -path '*/.git/*' -not -path '*/.git' -not -path '*/customers/*' -not -path '*/customers' | sort
echo "" echo ""
} }

28
scripts/set-topic.sh Executable file
View 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
View 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