From 7854db5b937dd0ccd4c41a92f0c5046c83c5fa87 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sat, 21 Feb 2026 08:45:40 -0800 Subject: [PATCH] Update CLAUDE.md to reflect expanded command structure, new modules, and implementation details --- CLAUDE.md | 72 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 41e82e9..b863456 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,12 +28,32 @@ No build step. TypeScript runs directly via Bun. There are no tests. ``` src/ - cli.ts # CLI entry point, all command handlers (Commander.js) - git.ts # Git operations: worktrees, branches, merge - vm.ts # Container lifecycle: create, provision, exec, shell, claude - state.ts # Per-repo session persistence (.sandlot/state.json) - config.ts # Load optional sandlot.json config from repo root - spinner.ts # CLI progress spinner (braille frames) + cli.ts # CLI entry point, command registration (Commander.js) + git.ts # Git operations: worktrees, branches, merge, rebase + vm.ts # Container lifecycle: create, provision, exec, shell, claude + state.ts # Per-repo session persistence (.sandlot/state.json) + env.ts # Read ANTHROPIC_API_KEY from ~/.env + fmt.ts # ANSI escape codes, die/success/info helpers, pager + markdown.ts # Terminal markdown renderer (headings, bold, links, code blocks) + spinner.ts # CLI progress spinner (braille frames) + commands/ + new.ts # Create session, derive branch name from prompt + open.ts # Re-enter existing session + close.ts # Remove worktree and clean up session + list.ts # Show all active sessions with status + save.ts # Stage all changes and commit + merge.ts # Merge branch into main with conflict resolution + rebase.ts # Rebase branch onto main with conflict resolution + review.ts # Launch grumpy code review with Claude + diff.ts # Show uncommitted changes or full branch diff + show.ts # Show prompt and full diff for a branch + log.ts # Show commits on branch not on main + dir.ts # Print worktree path for a session + shell.ts # Open fish shell in container + cleanup.ts # Remove stale sessions + vm.ts # VM subcommands (create, start, stop, destroy, status, info) + completions.ts # Fish shell completions generator + helpers.ts # Shared: requireSession, resolveConflicts, saveChanges ``` ## Architecture @@ -46,6 +66,7 @@ Each module has a single responsibility. No classes — only exported async func 3. `vm.ensure()` → start/create/provision the container 4. `state.setSession()` → write to `.sandlot/state.json` 5. `vm.claude()` → launch Claude Code in container at worktree path +6. `saveChanges()` → auto-save on exit (stage all, AI-generated commit message) unless `--no-save` **Worktree location**: `~/.sandlot///` (outside the repo) **Symlink in repo**: `/.sandlot/` → worktree @@ -57,9 +78,10 @@ Each module has a single responsibility. No classes — only exported async func - Image: `ubuntu:24.04` - User: `ubuntu` - Mounts: `~/dev` and `~/.sandlot` from host -- Provisioned once on first use: installs `curl git neofetch fish`, Claude Code, git identity, API key helper -- API key: read from `~/.env` on host (`ANTHROPIC_API_KEY=...`), written as `~/.claude/api-key-helper.sh` in the container (never passed as a process argument) -- Claude settings: `skipDangerousModePermissionPrompt: true` in container +- Provisioned once on first use: installs `curl git neofetch fish unzip`, Bun, Claude Code, git identity, API key helper, activity tracking hook +- API key: read from `~/.env` on host (`ANTHROPIC_API_KEY=...`), written to a temp file and copied as `~/.claude/api-key-helper.sh` in the container (never passed as a process argument) +- Claude settings: `skipDangerousModePermissionPrompt: true`, activity tracking hooks (`UserPromptSubmit` / `Stop`) in container +- Also writes `~/.claude.json` with `hasCompletedOnboarding: true` to skip first-run prompts ## Shell Command Pattern @@ -81,23 +103,6 @@ if (result.exitCode === 0) { ... } Always use `.nothrow()` for commands that may fail non-fatally. Use `.quiet()` to suppress stdout/stderr. -## Configuration - -Optional `sandlot.json` at repo root (loaded by `config.ts`): - -```json -{ - "vm": { - "cpus": 4, - "memory": "8GB", - "image": "ubuntu:24.04", - "mounts": { "/host/path": "/container/path" } - } -} -``` - -Note: `config.ts` loads this but `vm.ts` does not yet use it — the container is hardcoded with `-m 4G` and `ubuntu:24.04`. - ## State Schema `.sandlot/state.json` (per repo, gitignored): @@ -108,7 +113,8 @@ Note: `config.ts` loads this but `vm.ts` does not yet use it — the container i "branch-name": { "branch": "branch-name", "worktree": "/Users/you/.sandlot/repo/branch-name", - "created_at": "2026-02-16T10:30:00Z" + "created_at": "2026-02-16T10:30:00Z", + "prompt": "optional initial prompt text" } } } @@ -117,14 +123,20 @@ Note: `config.ts` loads this but `vm.ts` does not yet use it — the container i ## Error Handling Conventions - Throw `Error` with descriptive messages from git/vm modules -- CLI commands catch and display errors, then `process.exit(1)` +- Command handlers use `die()` from `fmt.ts` for user-facing errors (writes to stderr, exits 1) - On `new` failure, roll back: remove worktree, delete branch, unlink symlink - Non-fatal cleanup steps use `.catch(() => {})` to continue past failures ## Key Implementation Notes - `vm.exec()` prepends `export PATH=$HOME/.local/bin:$PATH` so `claude` binary is found -- `vm.claude()` uses `Bun.spawn` with `stdin/stdout/stderr: "inherit"` for interactive TTY +- `vm.claude()` uses `Bun.spawn` with `stdin/stdout/stderr: "inherit"` for interactive TTY; in print mode (`-p`), captures stdout via pipe and returns the output +- `vm.claudePipe()` writes input to a temp file in `~/.sandlot/`, pipes it to `claude -p` inside the container, and returns the result — used for commit message generation and conflict resolution +- `vm.isClaudeActive()` reads activity marker files written by the in-container `sandlot-activity` hook script - Branch creation in `createWorktree()` handles three cases: local branch, remote branch (tracks origin), new branch from HEAD -- `sandlot save` uses Claude (`claude -p "..."`) inside the container to generate commit messages +- `sandlot new` accepts a prompt instead of a branch name — derives a 2-word branch name via the Anthropic API (Haiku), falling back to simple text munging +- `sandlot save` uses `vm.claudePipe()` to generate commit messages from the staged diff +- `sandlot merge` and `sandlot rebase` use `vm.claudePipe()` to resolve merge conflicts automatically +- `sandlot new` and `sandlot open` auto-save changes when Claude exits (disable with `--no-save`) +- Default behavior (no subcommand): shows `list` if sessions exist, otherwise shows help - `.sandlot/` should be in the repo's `.gitignore`