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

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