Add CLAUDE.md with project architecture and developer guide
This commit is contained in:
parent
0f18f1e5ff
commit
1b7219e666
130
CLAUDE.md
Normal file
130
CLAUDE.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# CLAUDE.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Sandlot** is a CLI tool for branch-based development using git worktrees and [Apple Container](https://github.com/apple/container). Each branch gets its own isolated worktree and container. The primary workflow is: create a session (`sandlot new <branch>`), do work with Claude Code inside the container, then merge and clean up.
|
||||
|
||||
**Platform requirement**: macOS on Apple Silicon only.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Runtime**: [Bun](https://bun.sh) (not Node.js — use `bun` for everything)
|
||||
- **Language**: TypeScript (strict, ESNext, bundler module resolution)
|
||||
- **CLI parsing**: [Commander.js](https://github.com/tj/commander.js) 13.x
|
||||
- **Containers**: Apple Container (`brew install container`)
|
||||
- **Entry point**: `src/cli.ts` (shebang: `#!/usr/bin/env bun`, runs directly without compilation)
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
bun install # install dependencies
|
||||
bun link # make `sandlot` available globally
|
||||
sandlot --help # list all commands
|
||||
```
|
||||
|
||||
No build step. TypeScript runs directly via Bun. There are no tests.
|
||||
|
||||
## Source Structure
|
||||
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
Each module has a single responsibility. No classes — only exported async functions.
|
||||
|
||||
**Session flow for `sandlot new <branch>`:**
|
||||
1. `git.createWorktree()` → creates worktree at `~/.sandlot/<repo>/<branch>`
|
||||
2. Creates symlink `<repo-root>/.sandlot/<branch>` → worktree path
|
||||
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
|
||||
|
||||
**Worktree location**: `~/.sandlot/<repo-name>/<branch>/` (outside the repo)
|
||||
**Symlink in repo**: `<repo-root>/.sandlot/<branch>` → worktree
|
||||
**State file**: `<repo-root>/.sandlot/state.json`
|
||||
**Container name**: always `"sandlot"` (single shared container per machine)
|
||||
|
||||
## Container Details
|
||||
|
||||
- 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
|
||||
|
||||
## Shell Command Pattern
|
||||
|
||||
Uses Bun's `$` template literal for shell execution:
|
||||
|
||||
```typescript
|
||||
import { $ } from "bun"
|
||||
|
||||
// Capture output
|
||||
const text = await $`git rev-parse --show-toplevel`.cwd(dir).nothrow().quiet().text()
|
||||
|
||||
// Suppress output, ignore failures
|
||||
await $`git worktree prune`.cwd(cwd).nothrow().quiet()
|
||||
|
||||
// Check exit code
|
||||
const result = await $`git diff --staged --quiet`.nothrow().quiet()
|
||||
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):
|
||||
|
||||
```json
|
||||
{
|
||||
"sessions": {
|
||||
"branch-name": {
|
||||
"branch": "branch-name",
|
||||
"worktree": "/Users/you/.sandlot/repo/branch-name",
|
||||
"created_at": "2026-02-16T10:30:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling Conventions
|
||||
|
||||
- Throw `Error` with descriptive messages from git/vm modules
|
||||
- CLI commands catch and display errors, then `process.exit(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
|
||||
- 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/` should be in the repo's `.gitignore`
|
||||
Loading…
Reference in New Issue
Block a user