Add claude-profile engagement mode picker with statusline and session-start integration

- claude-profile: phase 1-3 picker (profile, mode, launch) with preset support,
  dryrun, WezTerm theming, and --append-system-prompt mode body injection
- 5 mode files (chat/quick/deep/hybrid/orch) with YAML frontmatter + prose body;
  new escalates_to field drives statusline →Opus arrow for deep and hybrid
- statusline.sh reads CLAUDE_CONFIG_DIR/active-mode.env to show
  [Sonnet→Opus] deep · topic format when launched via claude-profile
- Root CLAUDE.md session-start: auto-selects project from cwd or CLAUDE_PROJECT
  in active-mode.env, skipping the interactive picker when context is clear
- Spec, tests (37 assertions, 9 test files, all passing), context docs, and
  preset example included

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-04-12 00:36:38 +12:00
parent 765f32b4fa
commit 143b32597e
19 changed files with 1506 additions and 138 deletions

View File

@@ -1,25 +0,0 @@
# Session Log — 2026-03-17
## Summary
Added a "reproduce before fixing" best practice to claude-foundations' debugging topic, committed and pushed all outstanding claude-foundations changes, then built a new `unreflected-logs` script for scanning projects for session logs not yet processed by `/reflect-logs`.
## Decisions
- Decision: Add "reproduce before fixing" to `best-practices/debugging.md` rather than TDD — Rationale: TDD file covers regression tests as artifacts; the debugging file covers the *workflow* of how to approach a bug
- Decision: Bulk commit all outstanding claude-foundations changes in one commit — Rationale: User requested committing everything, not just the single file change
- Decision: `unreflected-logs` script uses python3 for JSON parsing of `.reflection-state.json` — Rationale: Reliable JSON parsing vs fragile bash/jq alternatives; python3 is available on target systems
## Gotchas Discovered
- **[bash]** Symptom: `((PASS++))` when PASS=0 evaluates to falsy (arithmetic result 0), causing `set -e` to exit the script silently — Fix: Use `PASS=$((PASS + 1))` instead, which always succeeds as an assignment
## Key Context
- `unreflected-logs` script follows the small-scripts spec-first workflow: spec in `specs/`, script in `scripts/`, test in `tests/`, symlinked to `~/sbin`
- The script compares `memory/log/*.md` files against `.reflection-state.json` processed keys (format: `log/<filename>`)
- Live scan of `~/dev/claude` found 5 unreflected logs across 5 projects as of this session
## Process Notes
- The `((var++))` bash gotcha with `set -e` is a classic trap — already documented in `memory/gotchas-bash.md` but still bit us in a new test file. Worth noting that it applies to any arithmetic expression that evaluates to 0.

View File

@@ -1,15 +0,0 @@
# Session Log — 2026-03-17
## Summary
Diagnosed why the `/housekeeping` skill (created last session) failed to load — an em dash in the YAML frontmatter description silently broke parsing. Fixed the skill and added a non-ASCII frontmatter check to `validate-skill`.
## Gotchas Discovered
- **[skills]** Symptom: Skill installed correctly (symlink, SKILL.md present) but Claude Code reports "Unknown skill" — Fix: Non-ASCII characters (em dashes `—`, smart quotes, etc.) in YAML frontmatter silently prevent skill loading. Replace with ASCII equivalents. Claude commonly generates em dashes, so this is a recurring risk.
## Key Context
- The fix was replacing `—` (UTF-8 `\xe2\x80\x94`) with `-` in the `description: >` field of `custom-claude-skills/skills/housekeeping/SKILL.md`
- Non-ASCII in the skill body (below frontmatter) is fine — only the YAML-parsed frontmatter is affected
## Process Notes
- `cat -A` was the key diagnostic — showed `M-bM-^@M-^T` bytes revealing the em dash that looked identical to a regular dash in normal display
- Added the check to validate-skill with: script change, spec update, test fixture + 2 assertions — all 45 tests pass

View File

@@ -1,22 +0,0 @@
# Session Log — 2026-03-17
## Summary
Diagnosed why `/housekeeping` skill wasn't loading in the `~/.claude-octopus` profile, fixed the root cause in `install.sh`, and created a new `check-skills` script to detect missing/stale skill symlinks. Integrated check-skills into the `/housekeeping` skill.
## Decisions
- Decision: Use `CLAUDE_CONFIG_DIR` env var to detect the active Claude profile — Rationale: Claude Code sets this automatically; falls back to `~/.claude` when unset
- Decision: Create `check-skills` as a read-only diagnostic script rather than auto-fixing — Rationale: Keeps it safe for `/housekeeping` (information-only), users can run `install.sh` to fix
- Decision: Log to `small-scripts` rather than `custom-claude-skills` — Rationale: The new script and tests live in small-scripts; custom-claude-skills changes were smaller edits
## Gotchas Discovered
- **[claude-code]** Symptom: `/housekeeping` skill not found when using `~/.claude-octopus` profile — Fix: Each Claude profile has its own independent `skills/` directory. Skills must be symlinked into every profile, not just `~/.claude`. The `CLAUDE_CONFIG_DIR` env var identifies the active profile.
- **[claude-code]** Symptom: `install.sh` hardcoded `$HOME/.claude/skills` so new skills only appeared in the default profile — Fix: Changed to `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills`
## Key Context
- `CLAUDE_CONFIG_DIR` env var is set by Claude Code to the active profile directory (e.g., `/home/paul/.claude-octopus`)
- Multiple Claude profiles maintain completely independent `skills/` directories — no cross-profile sharing
- The `check-skills` script classifies skills as: LINKED (correct), MISSING (not in profile), STALE (wrong target), ORPHAN (not in source repo)
## Process Notes
- Spec-first workflow for `check-skills` kept implementation focused — spec, script, test, symlink, integrate
- All 17 test cases passed on first run

View File

@@ -0,0 +1,42 @@
# Session Log -- 2026-04-12
## Summary
Designed and implemented engagement modes for `claude-profile`: 5 modes (chat / quick / deep / hybrid / orch) bundling driver model, subagent policy, async tolerance, and workflow stance. Wrote a full OpenSpec, replaced the 104-line script with a ~280-line three-phase picker, added a 37-assertion test suite (all passing), and seeded FUTURE.md with five follow-ups including a MiniMax-driven inverted architecture.
## Decisions
- Decision: Mode files live in `data/claude-profile/modes/<name>.md` in the repo, not in profile directories -- Rationale: source of truth must be version-controlled; profile dirs are for user state, not script data. Established `data/` as a new repo top-level convention.
- Decision: Driver enforcement is real via `claude --model <full-id>`, not advisory via system prompt -- Rationale: confirmed `--model` is supported and session-scoped via the claude-code-guide agent. Removes the contradiction of "you are Sonnet" being injected into an Opus session.
- Decision: Use full model IDs (`claude-sonnet-4-6`, `claude-haiku-4-5-20251001`) not aliases -- Rationale: removes ambiguity about which version is selected; the `claude-code-guide` agent only confirmed `sonnet`/`opus` aliases, haiku alias is unverified.
- Decision: v1 of claude-profile does NOT ask for a project interactively -- Rationale: would duplicate the existing CLAUDE.md project picker. `--project` flag still works for non-interactive use; CLAUDE.md integration is in FUTURE.md.
- Decision: Mode 3 (Deep Work) drives with Sonnet, not Opus -- Rationale: Sonnet is the workhorse; Opus is the consultant. Opus only enters via named workflows (plan review, spec review, dual-model second opinion, gnarly-bug consult, architecture review). Default reasoning is "think hard", not ultrathink.
- Decision: Frontmatter and presets.yaml parsed by pure awk, no yq dependency -- Rationale: small-scripts targets a clean Linux env; adding yq just for one launcher is overkill. Parsing is simple key:value with one-level nesting.
- Decision: `bg-model-call` wrapper deferred to FUTURE.md, not built this session -- Rationale: Out of scope for the launcher; the modes reference it as a pattern but don't require it to ship.
## Gotchas Discovered
- **[bash]** Symptom: `read -rp` under `set -e` exits silently with code 1 when stdin is closed (no TTY), making `--dryrun` unusable from scripts -- Fix: guard every interactive read with `[[ -t 0 ]]`; fall back to default value when not on a terminal. Affected the profile picker, mode picker, and time-horizon question.
- **[claude-profile]** Symptom: Original script's `CONTEXT_LOADER="${SCRIPT_DIR}/context-load"` failed because context-load lives in `claude-foundations/scripts/`, not as a sibling -- Fix: use `command -v context-load` (it's symlinked into `~/sbin`), with the sibling location as a fallback for testing isolation. Used `readlink -f "$0"` so the lookup also works when claude-profile is symlinked.
- **[claude-code]** Symptom: Unsure whether `claude --model` was supported, considered fragile workarounds (per-driver profile dirs, settings.json rewriting) -- Fix: 30-second check via the claude-code-guide agent confirmed `--model` is session-scoped and accepts both aliases and full IDs. Saved building 3 workarounds.
- **[shell]** Symptom: `eval "$preset_data"` would have been a code-injection footgun if presets.yaml contained shell metacharacters -- Fix: parse preset blocks into `PRESET_*` variables via awk, then map them to known field names with a case statement. Never eval untrusted content.
## Open Questions
- Does CLAUDE.md (the top-level session-start instructions in `~/dev/claude/CLAUDE.md`) have a clean place to read shell env files at startup? Needed for the project-picker integration follow-up.
- Where exactly does the status-line script live in claude-foundations, and is it bash or another language? Needed before scoping the mode-tag rendering follow-up.
- Does MiniMax M2 ship a usable agentic CLI/harness in 2026, or would building one be a precondition for the inverted-architecture follow-up?
- Token-budget sketch under both architectures (claude-profile hybrid vs. minimax-profile) -- the inverted architecture needs >30% projected savings to be worth pursuing, or it's a research project not a productivity win.
- For the `switch mode` skill: literal phrase ("switch mode") matched by a UserPromptSubmit hook, or a slash command `/switch-mode`? Mode files all assume the literal phrase.
## Key Context
- New `data/` directory in small-scripts is a new top-level convention; documented in CLAUDE.md and README.md.
- Driver mapping table in spec and script: `haiku → claude-haiku-4-5-20251001`, `sonnet → claude-sonnet-4-6`, `opus → claude-opus-4-6`, `opus-1m → claude-opus-4-6[1m]`.
- `active-mode.env` is written to `$CLAUDE_CONFIG_DIR/active-mode.env`; survives `/clear` because the path is stable. Read by CLAUDE.md session-start (future) and the status-line script (future).
- `last-mode` file at `$CLAUDE_CONFIG_DIR/last-mode` records the most recent mode for picker default; falls back to `quick` if missing or invalid.
- Presets at `$CLAUDE_CONFIG_DIR/presets.yaml` (per-profile, not global). Example template shipped at `data/claude-profile/presets.yaml.example`.
- Documented v1 limitation: parallel `claude-profile` sessions against the same profile race on `active-mode.env` (last writer wins).
- Test suite: `tests/test-claude-profile.sh` exercises 10 test groups, 37 assertions, all dryrun-based so it never launches real `claude`.
## Process Notes
- File-based result passing (background bash + sentinel files) emerged as the unifying design principle across this session: it ties together `bg-model-call`, `ScheduleWakeup` patterns, and the MiniMax inverted architecture. Whenever a subagent or container produces a non-trivial result, write it to a file and only `Read` it into context if you actually need to. This keeps the driver's context lean even when subagents do heavy work.
- Always verify harness capabilities (like `claude --model`) before designing around them. A 30-second `claude-code-guide` agent check prevented committing to fragile workarounds. The pattern: if you're about to design a workaround for something the harness "doesn't support", verify the assumption first.
- Spec-first paid off when the driver-enforcement question turned out to have a clean answer. The spec was already structured to accept the change cleanly (one Edit on Phase 3, one new "Driver mapping" subsection); had implementation gone first, it would have been a more invasive rewrite.
- When extending an old script, check whether its existing references actually work in the current environment. The original `CONTEXT_LOADER="${SCRIPT_DIR}/context-load"` was broken in the live environment but had been masked by never running the script in dryrun mode. Tests would have caught this immediately if they'd existed.
- For interactive bash scripts that also need to be testable: `[[ -t 0 ]]` guard around every `read`. Allows the same code path to be interactive or scripted without `set -e` blowups.