Add best-practices library, knowledge distillation pipeline settings, and first session log

- best-practices/: 11 topic files + INDEX.md extracted from cluster-bootstrap
  and custom-claude-skills (validation, k8s, helm, ansible, secrets, debugging, etc.)
- settings.yaml: pipeline config (log retention, tracked projects, max logs per run)
- CLAUDE.md: updated with best-practices loading and pipeline documentation
- memory/log/: first session log demonstrating the format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-03-12 23:39:06 +13:00
parent a4967df815
commit e0f8e6471c
15 changed files with 472 additions and 2 deletions

View File

@@ -15,9 +15,9 @@
Scan the directories at runtime so the list is always current. Include a brief description if the project has a CLAUDE.md or README.md you can glean one from. Scan the directories at runtime so the list is always current. Include a brief description if the project has a CLAUDE.md or README.md you can glean one from.
2. Based on the user's choice: 2. Based on the user's choice:
- **Existing project**: `cd` into the directory, read all `.md` files, and read `~/dev/claude/secrets/` (read-only reference — review every file to refresh context). Ask clarifying questions if anything is unclear or incomplete, and note context in MEMORY.md. - **Existing project**: `cd` into the directory, read all `.md` files, and read `~/dev/claude/secrets/` (read-only reference — review every file to refresh context). Then read the [Best Practices Index](best-practices/INDEX.md) and load any topic files relevant to the selected project's technology stack. Ask clarifying questions if anything is unclear or incomplete, and note context in MEMORY.md.
- **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. - **New project!**: Follow the "New Projects" section below. Also read `~/dev/claude/secrets/` as above. Read the [Best Practices Index](best-practices/INDEX.md) and load topic files relevant to the new project's technology stack.
## Secrets (`~/dev/claude/secrets/`) ## Secrets (`~/dev/claude/secrets/`)
@@ -110,6 +110,27 @@ Human-readable project documentation:
- 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
## Knowledge Distillation Pipeline
Three skills form a continuous learning pipeline across projects:
1. **`/log`** — Run at end of session. Captures decisions, gotchas, open questions to `memory/log/YYYY-MM-DD.<HHMMSS>.md` in the current project. Also prunes old reflected logs.
2. **`/reflect-logs`** — Run periodically. Processes unprocessed session logs into topic memory files (`memory/gotchas-*.md`, `memory/process-lessons.md`, etc.). Flags stale entries. Tracks state in `.reflection-state.json`.
3. **`/distill-best-practices`** — Run from any project. Reads changed memory files across all tracked projects and proposes updates to `claude-foundations/best-practices/`. Tracks state in `best-practices/.distill-state.json`.
### State Files
- **`.reflection-state.json`** — Per-project, tracks which logs have been reflected on (md5 hashes of log file content)
- **`best-practices/.distill-state.json`** — In claude-foundations, tracks git SHAs per project at time of last distillation
- **`settings.yaml`** — In claude-foundations, configures log retention (default 7 days), max logs per reflection run, and tracked project list
### Log Format
Session logs use structured markdown with parseable section headers: Summary, Decisions, Gotchas Discovered (tagged with `[topic]` for routing), Open Questions, Key Context, Process Notes. Empty sections are omitted.
### Pruning
- Reflected logs older than `log.retention_days` (default: 7) are automatically deleted by `/log`
- Unreflected logs older than `log.warn_unreflected_days` (default: 14) trigger a warning instead of deletion
- `/reflect-logs` flags stale memory entries (version-specific bugs that have been fixed, manual processes that have been automated)
## Milestones ## Milestones
Break projects into numbered milestones (M1, M2, ...). Every milestone completion MUST include: Break projects into numbered milestones (M1, M2, ...). Every milestone completion MUST include:

17
best-practices/INDEX.md Normal file
View File

@@ -0,0 +1,17 @@
# Best Practices Index
Generalised best practices extracted from real project work. Each topic file is self-contained — read only the files relevant to the current project.
## Topics
- [Validation & Deployment](validation.md) — Validate locally, deploy once; full-chain testing; pre-flight checks
- [Secrets Management](secrets-management.md) — SOPS + age, credential handling, file naming, encryption gotchas
- [Git & Source Control](git-source-control.md) — Commit practices, GitOps workflows, remote conventions
- [Kubernetes Patterns](kubernetes.md) — Volume mounts, deployment strategies, naming, bootstrap ordering
- [Helm Charts](helm.md) — Schema validation, version verification, values structure
- [Ansible](ansible.md) — Inventory, templates, idempotency, credential safety
- [Scripting](scripting.md) — Shell conventions, verification scripts, idempotency, colour output
- [Documentation Standards](documentation.md) — CLAUDE.md, MEMORY.md, FUTURE.md, README.md structure and tiered memory
- [Milestones & Reflections](milestones.md) — Milestone workflow, verification, reflection process
- [Debugging Methodology](debugging.md) — Systematic diagnosis, full-chain testing, common pitfalls
- [Claude Code Skills](skills-development.md) — Skill authoring, context injection, tool restrictions

36
best-practices/ansible.md Normal file
View File

@@ -0,0 +1,36 @@
# Ansible
## Inventory and Execution
- Always pass `-i inventory.yml` explicitly or run from the directory containing `ansible.cfg`
- Playbooks that can't find inventory skip silently with no error — a common source of "it ran but nothing happened" confusion
- Variables that need customisation go in `inventory.yml` files, not scattered across role defaults
## Role Structure
- Roles follow standard structure: `tasks/main.yml`, `templates/*.j2`, `handlers/main.yml`
- Jinja2 templates have `.j2` extension and include a "managed by Ansible" header comment
## Template Safety
- **Never use placeholder values with `-e` for vars that template config files.** Using `-e "var=dummy"` will overwrite live configs with garbage. Either read real values, use `--skip-tags` to skip templating tasks, or restructure roles so sensitive templates are in a separate tag.
## Credential Safety
- Pass secrets via `@file` not `-e` on the command line — `-e "key=value"` exposes secrets in `ps` output
- Use temp files with `trap rm` cleanup: `-e "@${tmpfile}"`
## Module Gotchas
- `docker_compose_v2` doesn't support `state: restarted` — use `recreate: always` instead
- `ansible.builtin.unarchive` with `remote_src` and `extra_opts: --strip-components` is unreliable — use `get_url` + `command: tar` separately
- `get_url` won't re-download when the URL changes but the destination filename stays the same — use a version marker file to detect changes
## Service Restarts
- Some services (dnsmasq, etc.) need container restarts for config changes to take effect
- Ansible handlers handle this, but always verify the change took effect (e.g., `dig @<ip> <record> +short`)
## Docker Compose
- `network_mode: host` ignores `ports:` mappings — remove `ports:` to avoid warnings

View File

@@ -0,0 +1,41 @@
# Debugging Methodology
## Check Before You Act
- Before writing firewall/network rules, check actual routing (`ip route get <dest>`)
- Before running config management with variables, ensure values are real, not placeholders
- Before assuming a container has a shell, `docker inspect` it
- Before creating API tokens, research all required scopes upfront — iterating one scope at a time costs a push-debug cycle each
## Routing and Networking
- Always run `ip route get <dest>` on the forwarding host first
- macvlan, Docker bridge, and other virtual interfaces mean the "obvious" physical interface is often wrong
- Test from both in-cluster and external perspectives
## Full-Chain Testing
After wiring up any new service:
1. Test direct to backend (bypass all proxies)
2. Test through reverse proxy (bypass DNS)
3. Test end-to-end as a user would
Use `curl --resolve` to test specific paths without depending on DNS propagation.
## When Something Doesn't Sync/Apply
- Check resource exclusions in the GitOps controller immediately
- Check if the resource type requires special permissions or labels
- Check if ServerSideApply conflicts are preventing field changes
- Don't try workarounds before understanding the root cause
## OIDC Integration Checklist
Before starting any OIDC integration, research:
1. What format is the `sub` claim (UUID? username?)
2. Which claims are in the ID token vs userinfo endpoint
3. How the consumer matches RBAC identities (groups? email? username?)
## Grep Your Own Docs
Known issues documented in CLAUDE.md or MEMORY.md but not applied to new scripts/configs waste debugging time. Search your own documentation before writing automation that touches areas with known gotchas.

View File

@@ -0,0 +1,50 @@
# Documentation Standards
Every project maintains four core markdown files.
## CLAUDE.md
The primary reference for Claude sessions. Should contain:
- Project overview and architecture
- Repository structure (keep updated as the project evolves)
- Key design decisions with rationale
- Conventions and coding standards
- Environment details (IPs, URLs, credential references — never actual values)
- Common operations / how-to recipes
- Put critical rules at the top — Claude reads sequentially and earlier content has more influence
## MEMORY.md (Tiered Memory System)
Long-running projects accumulate significant context. Use a **tiered memory** structure:
**MEMORY.md** is a **thin index only** — one-line descriptions with links to topic files in `memory/`. No content lives in MEMORY.md itself. Keep it under ~50 lines.
**memory/** contains the actual content, split by topic:
- `memory/project-status.md` — Current milestone, what's next, blockers
- `memory/gotchas-<topic>.md` — Gotchas grouped by technology
- `memory/process-lessons.md` — How-to-work-with-this-repo lessons
- `memory/m<N>-reflection.md` — One file per milestone reflection
- `memory/decisions.md` — Architecture and design decisions
**Principles:**
- Split by topic, not by time
- Index descriptions matter — they're used to decide what to read
- Prune aggressively — stale memory is worse than no memory
- Each file should be self-contained and greppable
- Deduplicate with CLAUDE.md — stable conventions go in CLAUDE.md, learnings and gotchas go in memory
## FUTURE.md
Backlog of improvement ideas, each with:
- **Problem:** What's painful or manual today
- **Idea:** What the improvement looks like
- **Open questions:** Unknowns to research before starting
- **Depends on:** Other items or milestones that should come first
## README.md
Human-readable project documentation:
- Architecture summary
- Quick start / setup instructions
- Milestone table with status
- Scripts section listing every script with purpose and usage

View File

@@ -0,0 +1,34 @@
# Git & Source Control
## Commit Practices
- Use meaningful commit messages; prefer small, focused commits over large batches
- Never commit secrets in plaintext — use SOPS + age or equivalent encryption
- Enable pre-commit hooks where appropriate (secret detection, linting, formatting)
## Pre-Commit Hooks
- Block `local_secrets/` and similar directories from being committed
- Auto-encrypt files matching `.sops.yaml` rules that aren't yet encrypted
- Enable with `git config core.hooksPath .githooks`
- Consider secret detection, linting, and formatting hooks
## GitOps Workflow
- All infrastructure changes should be tracked in Git
- No manual changes without corresponding GitOps manifests — anything applied manually (e.g., `kubectl apply`, `helm install`) should immediately get a corresponding tracked manifest
- For ArgoCD-managed clusters: edit in Git, push, sync — never edit live resources directly
## Remote Conventions
- SSH workflows preferred over HTTPS for Git remotes
- Use SSH config host aliases for multi-user setups (e.g., `gitea.example.com-<user>`)
- Remote URL format: `git@<host-alias>:<org>/<repo>.git`
- Optionally push-mirror to GitHub for public visibility
## Version Management
- Use the latest stable version of dependencies unless pinned for a reason
- Verify versions from live sources (`helm search repo`, upstream docs, package registries) — don't rely on memory
- Document the reason in a comment if a version is intentionally pinned below latest
- Check compatibility matrices before upgrading (e.g., Talos ↔ Kubernetes, framework ↔ runtime)

25
best-practices/helm.md Normal file
View File

@@ -0,0 +1,25 @@
# Helm Charts
## Schema Validation
- **Always validate values against the chart schema before committing.** Run `helm show values <repo>/<chart> --version <ver>` to check the actual structure.
- Helm chart schemas change between versions — field names and nesting can differ from documentation or online examples.
- A quick `helm template` test locally catches schema errors before deployment.
- `additionalProperties: false` in chart schemas means any extra keys at the wrong nesting level cause a hard failure.
## Version Verification
- Run `helm search repo` or check upstream docs to confirm latest stable versions
- Don't rely on memory for chart versions — they go stale quickly
- Check compatibility matrices between chart version, app version, and other cluster components
## Multi-Source Applications
- ArgoCD multi-source Applications use `$ref` syntax to combine external Helm charts with Git-stored values files
- Keep values files in Git alongside the ArgoCD Application manifest
## Timeout Handling
- Under cluster pressure (many events, etcd busy), default Helm timeouts may not be enough
- Increase timeout for initial installs (e.g., 10m instead of 5m)
- `helm upgrade --install` is idempotent — retries are safe

View File

@@ -0,0 +1,36 @@
# Kubernetes Patterns
## Volume Mounts
- **Avoid `subPath` volume mounts** for Secrets and ConfigMaps. The kubelet does not auto-update `subPath` mounts when the source changes — the pod must be restarted. Use directory mounts instead and adjust the application's config path.
- **Secret volume propagation is async.** After updating a Secret, the kubelet takes seconds to sync mounted volumes. A `rollout restart` issued immediately after may start pods with stale data. Add a short delay (5s) before restarting.
## Deployment Strategies
- **RWO PVC + RollingUpdate = Deadlock.** New pod can't attach the volume while the old pod holds it. Use `strategy: Recreate` for single-replica deployments with RWO PVCs.
- **SSA + strategy change conflict.** Switching from RollingUpdate to Recreate via ServerSideApply fails because SSA won't remove the old `rollingUpdate` field. Must patch the live resource first.
## Naming
- `metadata.name` must be DNS-1035 compliant — no dots allowed. Replace dots with dashes (e.g., `oreillyit-nz` not `oreillyit.nz`). Label values CAN contain dots.
## Bootstrap Ordering
Some components have chicken-and-egg dependencies:
1. CNI (e.g., Cilium) must be installed before anything else — nodes are NotReady without it
2. GitOps controller (e.g., ArgoCD) installed second
3. Root app applied last — the GitOps controller then "adopts" CLI-installed releases
Manual bootstrap secrets (encryption keys, OIDC client secrets) must be documented as explicit steps.
## Network Policies
- DNS egress for `toFQDNs` rules must use `toEndpoints` targeting kube-dns pods with `rules.dns` — this triggers the DNS proxy. Using `toCIDRSet` for DNS bypasses the proxy and FQDN rules never populate.
- Cross-namespace policies need explicit namespace matching (e.g., `matchExpressions` on namespace label).
- Always test from the actual consumer namespace, not same-namespace test pods.
## 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).
- Proxmox VM names must match K8s node hostnames for cloud controller manager integration.
- Metrics-server on Talos needs `--kubelet-insecure-tls` (self-signed kubelet certs).

View File

@@ -0,0 +1,42 @@
# Milestones & Reflections
## Milestone Structure
Break projects into numbered milestones (M1, M2, ...). This provides clear checkpoints, measurable progress, and natural reflection points.
## Milestone Completion Checklist
Every milestone MUST include:
### 1. Verification Script
`scripts/verify-m<N>.sh` — automated checks confirming all milestone outcomes.
- Idempotent, non-destructive, returns non-zero on failure
- Colour output (green/red) for pass/fail
- Environment-resilient (no sudo, test from accessible side)
- Check for default/insecure credentials
### 2. Milestone Reflection
Write `memory/m<N>-reflection.md` by reviewing the **entire conversation** from milestone start. Cover:
- **Process improvements:** What slowed us down? Wrong assumptions? Where did we go in circles? What would make this faster if redone from scratch?
- **Key knowledge for reproduction:** Critical facts, gotchas, non-obvious config details, version-specific quirks, debugging detours
- **Scripts and automation:** Existing tools that proved valuable, new scripts to build, patterns to extract into reusable automation
- **Future improvements:** Ideas that surfaced but don't belong in current scope — add to FUTURE.md
### 3. Updated README.md
Ensure the scripts section, milestone table, and setup steps are current.
### 4. Updated CLAUDE.md
Reflect new repo structure, conventions, and patterns discovered during the milestone.
## Reflection Quality
Good reflections capture:
- Commit stats (total commits, fix percentage) to measure validation discipline
- Longest detour and root cause
- Most avoidable waste and what would have prevented it
- Concrete checklist items for future similar work

View File

@@ -0,0 +1,29 @@
# Scripting Conventions
## Structure
- All scripts live in `scripts/` and run from the repository root
- Scripts should be idempotent and safe to re-run
- Exit non-zero on failure so `&&` chains work naturally
## Verification Scripts
- Automated checks confirming milestone or feature outcomes
- Use colour output (green/red) for pass/fail indicators
- Should be non-destructive and environment-resilient
- Avoid needing sudo — test from the accessible side of a connection instead
- Check for default/insecure credentials and print remediation instructions on failure
- Use `curl --resolve` to bypass DNS/proxy layers when testing direct connectivity
## Automation Triggers
If you run the same 3+ commands in sequence more than once, it should become a script. Look for:
- Repeated command sequences in conversation history
- Steps requiring careful ordering
- Multi-step manual processes that are error-prone
## Shell Gotchas
- `((PASS++))` fails under `set -e` when PASS=0 — the expression evaluates to 0 (false), triggering errexit. Use `PASS=$((PASS + 1))` instead.
- Always quote variables in conditionals and file paths
- Use `trap` for cleanup of temp files and credentials

View File

@@ -0,0 +1,41 @@
# Secrets Management
## SOPS + age
SOPS with age encryption is the standard across all projects. A single `.sops.yaml` at the repo root defines path-based encryption rules.
### File Naming
- `.sops.yaml` path-based rules match specific filename patterns (e.g., `**/*secret*.yaml`)
- Non-secret files must NOT contain `secret` in their name, or the pre-commit hook will encrypt them
- KSOPS generator files should be named `ksops-generator.yaml`, not `secret-generator.yaml`
### encrypted_regex Gotcha
When using `encrypted_regex` for selective field encryption (e.g., Ansible group_vars), variable names must contain a keyword that matches the regex (e.g., `password|private_key|api_key|secret|token`). Arbitrary key names are silently left unencrypted.
### SOPS Vars Plugin
Each Ansible project needs `vars_plugins_enabled = host_group_vars,community.sops.sops` in `ansible.cfg`. Files in `group_vars/` must be named after a group (e.g., `all.sops.yaml`), not arbitrary names.
### Interactive Editor Pitfalls
- `sops <file>` opens an interactive editor — fails in non-interactive sessions
- `sops -e /tmp/file` fails when the temp path doesn't match `.sops.yaml` rules
- Multiple `sops --set` calls can corrupt files — use the interactive editor for multi-field edits
## Credential Handling
- **Never pass secrets via command-line arguments** — visible in `ps` output
- Use `@file` references, environment variables sourced at runtime, or stdin
- For Ansible, use temp files with `trap rm` cleanup: `-e "@${tmpfile}"`
- Read secrets at execution time and use them ephemerally — never cache or persist values
- Reference the **existence** of a secret file in docs, never its contents
## Bootstrap Secrets
Some secrets are chicken-and-egg (e.g., the age decryption key for ArgoCD's KSOPS). These must be created manually as a bootstrap step and documented clearly.
## Backup Considerations
Backup plans must include encryption keys (age private keys, etc.) so that encrypted data in Git repos remains recoverable.

View File

@@ -0,0 +1,24 @@
# Claude Code Skills
## Skill Structure
- Each skill lives in `skills/<skill-name>/SKILL.md`
- Skills should be project-agnostic where possible — use dynamic context injection to adapt
- After adding a new skill, run the install script to register it
- Skills only useful for one project should live in that project's `.claude/skills/` instead
## Authoring Guidelines
- **Inline by default** — only use `context: fork` if the skill genuinely doesn't need conversation history
- **Pre-fetch context** with `!`command`` injection to reduce tool calls during execution
- **Restrict tools** with `allowed-tools` to the minimum needed — reduces permission prompts
- **Use $ARGUMENTS** for user input, `$0`, `$1` etc. for positional args
- Dynamic commands in `!`command`` run at skill load time, not during Claude's execution
## `!`command`` Gotchas
- **No `$()` command substitution** — the permission checker rejects commands containing `$()`
- **No complex shell pipelines relying on subshells** — keep commands simple and self-contained
- **`allowed-tools` patterns must match the command binary** — each binary used in `!`command`` blocks needs its own pattern
- **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

View File

@@ -0,0 +1,35 @@
# Validation & Deployment
## Validate Locally, Deploy Once
The single biggest time sink across projects is "deploy first, validate later." Real-world stats from a 9-milestone infrastructure project showed 50-60% of commits were fixes that could have been caught locally.
**Always validate before pushing:**
- `helm template` for Helm chart values
- `kustomize build` (or `kubectl kustomize`) for Kustomize apps
- `kubectl apply --dry-run=server` for K8s naming/schema issues
- `docker run <app> validate-configuration` for apps that support it (Authelia, Homepage, etc.)
- `docker inspect` for unfamiliar container images before writing init containers
- Lint/typecheck/test for application code
**Batch fixes locally, push once.** Each push-sync-crash-fix cycle wastes minutes and clutters Git history.
## Test the Full Chain Immediately
After wiring up any new service or endpoint, test end-to-end from the user's perspective right away. Don't assume intermediate steps working means the whole chain works.
- `curl --resolve domain:443:<ip> https://domain` to test bypassing DNS/proxy layers
- Test from the actual consumer (not same-namespace test pods for network policies)
- Test DNS resolution after deploying FQDN-based policies
## Pre-Flight Checks
Before starting a deploy or automation phase:
- Verify SSH keys are loaded (`ssh -T git@<host>`)
- Confirm environment variables and credentials are available
- Check that the target environment is in the expected state
- Verify DNS records resolve as expected
## Scripts That Change Config Must Self-Verify
After updating and restarting a service, the script should test that the change actually took effect (e.g., curl an API endpoint, check a config value). A "success" message without verification hides failures.

View File

@@ -0,0 +1,25 @@
# 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

14
settings.yaml Normal file
View File

@@ -0,0 +1,14 @@
# Knowledge distillation pipeline settings
log:
retention_days: 7 # Prune reflected logs older than this
warn_unreflected_days: 14 # Warn (don't delete) unreflected logs older than this
reflect:
max_logs_per_run: 10 # Process at most N logs per invocation
distill:
projects_dir: ~/dev/claude/projects
projects: # Projects to scan for memory changes
- cluster-bootstrap
- custom-claude-skills