diff --git a/AGENTS.md b/AGENTS.md index 3e482a2..96d6b62 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,12 @@ ecluse/ │ ├── env.rs .env.ecluse generation │ ├── compose.rs compose parse + overlay generation │ ├── docker.rs docker shell-outs -│ ├── postgres.rs psql shell-outs +│ ├── process.rs tmux/nohup service spawning +│ ├── sync.rs external process/container discovery +│ ├── hooks.rs lifecycle hook execution +│ ├── validate.rs config validation + port probing +│ ├── whose_pid.rs PID → session reverse lookup +│ ├── log.rs console step output │ ├── detect.rs mode detection signals │ ├── error.rs EcluseError enum │ └── modes/ container / host / hybrid handlers @@ -36,11 +41,10 @@ The `rust-toolchain.toml` pins to stable. Do not add nightly features. ## Key invariants — do not break these -- **State is always consistent.** `state.json` is written atomically (write `.json.tmp`, then rename). The lock at `state.lock` is held for the entire duration of `up` and `down`. +- **State is always consistent.** `state.json` is written atomically (write `.json.tmp`, then rename) and only ever mutated while holding the `state.lock` file lock via `StateGuard`. - **Mode is per-repo, not per-session.** Stored in `.ecluse.toml`. `up` never reads `--mode` from CLI. -- **Rollback on failure.** Every `bring_up` in `modes/*.rs` must roll back any partially-created resources (worktree, containers, database) if it returns an error before `state_guard.commit()`. +- **Rollback on failure.** Every `bring_up` in `modes/*.rs` must roll back any partially-created resources (worktree, containers, database) if it returns an error before the session is committed to state. - **Same CLI surface across all modes.** No mode-specific subcommands or flags. If you find yourself adding `--container-only-flag`, stop and reconsider. -- **LoC budget: 2500 lines of Rust** (`src/**/*.rs`). Check with `find src -name '*.rs' | xargs wc -l`. ## Skills are in `skills/`, not `src/` diff --git a/CLAUDE.md b/CLAUDE.md index 9040167..051f54f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,10 @@ ecluse shutdown # tear down ALL active sessions (--keep-worktrees to leave wor ecluse ls # list active sessions ecluse env # print session env vars as JSON ecluse shell # drop into worktree with env loaded (interactive use only) +ecluse status # per-service health for a session +ecluse sync # register externally-started processes as a session +ecluse whose-pid # reverse-map a PID to the owning session +ecluse flush # hard-reset: tear down everything ecluse knows about ecluse validate # validate .ecluse.toml port ranges and service gaps ``` @@ -66,7 +70,7 @@ Not every change touches all five — a slot-allocation bug fix probably touches ``` src/ -├── main.rs command handlers (init/up/down/ls/shell/env) +├── main.rs command handlers ├── cli.rs clap CLI definitions ├── config.rs .ecluse.toml parsing, Config struct, Mode enum ├── slot.rs slot allocation (first free in 1..max_slots) @@ -75,7 +79,12 @@ src/ ├── error.rs EcluseError variants with actionable messages ├── detect.rs mode auto-detection via signal scoring ├── worktree.rs git worktree create/remove wrappers -├── hooks.rs on_up/on_down lifecycle hook execution +├── hooks.rs lifecycle hook execution (pre_up/pre_spawn/post_up/pre_down/post_down) +├── process.rs native service spawning (tmux/nohup) + global config +├── sync.rs discovery of externally-started processes/containers +├── validate.rs config validation + free-port probing +├── whose_pid.rs PID → owning session reverse lookup +├── log.rs step/detail/warn console output ├── compose.rs docker-compose.yml parsing + overlay generation ├── docker.rs Docker CLI wrappers └── modes/ ModeHandler trait + container/host/hybrid impls diff --git a/README.md b/README.md index 7b3edfb..b64b93d 100644 --- a/README.md +++ b/README.md @@ -217,10 +217,13 @@ name = "postgres" run = "docker" base_port = 5432 # slot 1 → ECLUSE_POSTGRES_PORT=5433, slot 2 → 5434 -# Optional: lifecycle hooks — run in the worktree with all env vars set +# Optional: lifecycle hooks — shell commands run in the worktree. +# Order: pre_up (nothing exists yet, no env) → pre_spawn (env written, +# services not started) → post_up (everything up) → pre_down (before +# teardown) → post_down (after teardown). [hooks] -on_up = "npx prisma migrate deploy" -on_down = "npx prisma migrate reset --force" +post_up = "npx prisma migrate deploy" # full ECLUSE_* env available +pre_down = "npx prisma migrate reset --force" ``` `ecluse init` writes `~/.config/ecluse/config.toml` with the detected process manager (`tmux` if installed, otherwise `nohup`). Services with `command` are spawned on `ecluse up` and killed on `ecluse down`. Set `process_manager = "none"` to opt out. @@ -248,7 +251,7 @@ compose = "services/worker/docker-compose.yml" # its own compose file **Port collision handling** — by default ecluse searches for a free port if the nominal one is taken, trying `nominal + i × max_slots` to stay out of other slots' territory. Set `strict_port = true` to fail immediately instead. Run `ecluse validate` to check your config and preview the full port allocation table. -Hooks run as shell commands inside the worktree directory with all `.env.ecluse` variables pre-loaded. Use them for migrations, seeding, or teardown. ecluse doesn't manage databases directly — your app's own tooling handles that via `on_up`. +Hooks run as shell commands inside the worktree directory with all `.env.ecluse` variables pre-loaded (except `pre_up`, which runs before any env exists). Use them for migrations, seeding, or teardown. ecluse doesn't manage databases directly — your app's own tooling handles that via `post_up`. The old `on_up`/`on_down` names still work as deprecated aliases for `pre_up`/`pre_down`. ## Known limits