Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/rules/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ CI config: `.github/workflows/ci.yml`
- Tests run on Windows — `syscall.Flock` is Unix-only
- File-locking code (`state/`, `registry/`) needs build tags or conditional compilation if tests fail on Windows
- Use `filepath.Join()` not string concatenation for paths
- Use `os.UserConfigDir()` not hardcoded `~/.config`
- Config path uses `~/.config/fr8/` (intentional — `os.UserConfigDir()` maps to `~/Library/Application Support` on macOS which is wrong for CLI tools)
2 changes: 1 addition & 1 deletion .claude/rules/go-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
- Define interfaces at the consumer site, not the implementation site
- Avoid premature interfaces — only when 2+ implementations exist or needed for testing
- Zero value should be useful where possible (nil slices work as empty)
- Use `os.UserConfigDir()` / `os.UserCacheDir()` for platform-appropriate paths
- Config path: `~/.config/fr8/` via `os.UserHomeDir()` — not `os.UserConfigDir()` (which maps to `~/Library/Application Support` on macOS, wrong for CLI tools)

## Error Messages

Expand Down
1 change: 1 addition & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"permissions": {
"allow": [
"Bash(go:*)",
"Bash(bin/ci)",
"Bash(git log:*)",
"Bash(git diff:*)",
"Bash(git show:*)",
Expand Down
15 changes: 8 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ Go CLI for managing git worktrees as isolated dev workspaces. See `README.md` fo
## Build & Test

```bash
go build ./... # Build all packages
go build -o fr8 . # Build binary
go vet ./... # Static analysis
go test ./... # Run tests
bin/ci # Run all CI checks locally (build, vet, test, lint, tidy)
go build ./... # Build all packages
go build -o fr8 . # Build binary
go vet ./... # Static analysis
go test ./... # Run tests
go test -race -count=1 ./... # Exactly what CI runs
go install . # Install to GOPATH/bin
go install . # Install to GOPATH/bin
```

CI also runs `golangci-lint` v2 — see `.claude/rules/ci.md`.
CI also runs `golangci-lint` v2 — see `.claude/rules/ci.md`. Run `bin/ci` to approximate the full CI pipeline locally before pushing.

## Project Layout

Expand Down Expand Up @@ -78,4 +79,4 @@ Key architectural notes:

- `createWorkspace()` in `cmd/new.go` is the shared creation function used by both CLI and TUI dashboard
- Background process management uses tmux sessions named `fr8/<repo>/<workspace>`; graceful degradation when tmux is not installed
- Workspace openers are stored at `~/.config/fr8/openers.json`; TUI picker shown when multiple are configured
- Workspace openers are stored at `~/.config/fr8/openers.json`; TUI picker shown when multiple workspaces are configured
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Protocollar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ All workspace commands live under `fr8 ws` (alias `fr8 workspace`).
| `fr8 ws browser [name]` | Open workspace dev server in the browser |
| `fr8 ws archive [name] [--force]` | Tear down workspace (archive script + remove worktree) |
| `fr8 dashboard` | Interactive TUI for browsing repos and workspaces |
| `fr8 config show\|validate` | View and validate configuration |
| `fr8 config show\|doctor [--fix]` | View config or check health (fix issues with --fix) |
| `fr8 repo add\|list\|remove` | Manage the global repo registry |
| `fr8 opener add\|list\|remove\|set-default` | Manage workspace openers (e.g. VSCode, Cursor) |
| `fr8 completion [bash\|zsh\|fish]` | Generate shell completions |
Expand All @@ -82,9 +82,9 @@ Create `fr8.json` in your repo root:
"run": "bin/run-workspace",
"archive": "bin/archive-workspace"
},
"portRange": 10,
"basePort": 8000,
"worktreePath": "~/fr8"
"port_range": 10,
"base_port": 8000,
"worktree_path": "~/fr8"
}
```

Expand All @@ -93,13 +93,15 @@ Create `fr8.json` in your repo root:
| `scripts.setup` | | Command to run after creating a workspace |
| `scripts.run` | | Command to start the dev server |
| `scripts.archive` | | Command to run before removing a workspace |
| `portRange` | `10` | Number of consecutive ports per workspace |
| `basePort` | `8000` | Starting port for allocation |
| `worktreePath` | `~/fr8` | Where to create worktrees (supports `~`, relative, or absolute paths) |
| `port_range` | `10` | Number of consecutive ports per workspace |
| `base_port` | `8000` | Starting port for allocation |
| `worktree_path` | `~/fr8` | Where to create worktrees (supports `~`, relative, or absolute paths) |

Falls back to `conductor.json` if `fr8.json` doesn't exist, so projects using [Conductor](https://conductor.build) work without changes.

Use `fr8 config show` to see the resolved configuration (with defaults applied) and `fr8 config validate` to check for issues.
Legacy camelCase keys (`portRange`, `basePort`, `worktreePath`) are still accepted but deprecated. Run `fr8 config doctor --fix` to migrate automatically.

Use `fr8 config show` to see the resolved configuration (with defaults applied) and `fr8 config doctor` to check for issues.

## How It Works

Expand Down Expand Up @@ -201,9 +203,9 @@ Supports glob patterns including `**`. Files are only copied when their content

### Port Allocation

Ports are allocated sequentially in blocks of `portRange` (default 10) starting from `basePort`. Each workspace gets exclusive use of its block. Your scripts can use the base port (`FR8_PORT`) and offset from it for additional services (e.g. Redis on `FR8_PORT + 1`).
Ports are allocated sequentially in blocks of `port_range` (default 10) starting from `base_port`. Each workspace gets exclusive use of its block. Your scripts can use the base port (`FR8_PORT`) and offset from it for additional services (e.g. Redis on `FR8_PORT + 1`).

When allocating ports, fr8 checks all registered repos (see `fr8 repo list`) to avoid conflicts across projects that share the same `basePort`. If the global registry is unavailable, allocation falls back to the current repo's ports only.
When allocating ports, fr8 checks all registered repos (see `fr8 repo list`) to avoid conflicts across projects that share the same `base_port`. If the global registry is unavailable, allocation falls back to the current repo's ports only.

### State

Expand Down Expand Up @@ -339,7 +341,7 @@ The MCP server exposes 12 tools:
| `workspace_rename` | Rename a workspace |
| `repo_list` | List registered repos (optionally include workspace details) |
| `config_show` | Show resolved fr8 configuration for a repo |
| `config_validate` | Validate fr8 configuration and report errors/warnings |
| `config_doctor` | Check fr8 configuration health and report errors/warnings |

All tools accept an optional `repo` parameter to target a specific registered repo. The MCP server uses the global registry for workspace resolution (it does not auto-detect from CWD since it runs as a long-lived process).

Expand Down Expand Up @@ -402,4 +404,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).

## License

MIT
[MIT](LICENSE)
81 changes: 81 additions & 0 deletions bin/ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail

cd "$(git rev-parse --show-toplevel)"

pass=0
fail=0
failed_steps=()

step() {
printf "\n\033[1;34m==> %s\033[0m\n" "$1"
}

ok() {
printf "\033[1;32m ✓ %s\033[0m\n" "$1"
((pass++)) || true
}

err() {
printf "\033[1;31m ✗ %s\033[0m\n" "$1"
((fail++)) || true
failed_steps+=("$1")
}

# Build
step "Build"
if go build ./...; then
ok "go build ./..."
else
err "go build ./..."
fi

# Vet
step "Vet"
if go vet ./...; then
ok "go vet ./..."
else
err "go vet ./..."
fi

# Test
step "Test"
if go test -race -count=1 ./...; then
ok "go test -race -count=1 ./..."
else
err "go test -race -count=1 ./..."
fi

# Lint
step "Lint"
if command -v golangci-lint &>/dev/null; then
if golangci-lint run; then
ok "golangci-lint run"
else
err "golangci-lint run"
fi
else
printf " \033[1;33m⚠ golangci-lint not installed — skipping\033[0m\n"
fi

# Tidy
step "Tidy"
go mod tidy
if git diff --exit-code go.mod go.sum &>/dev/null; then
ok "go mod tidy (no diff)"
else
err "go mod tidy produced changes"
fi

# Summary
printf "\n\033[1;34m==> Summary\033[0m\n"
printf " %d passed, %d failed\n" "$pass" "$fail"
if ((fail > 0)); then
printf "\n\033[1;31m Failed:\033[0m\n"
for s in "${failed_steps[@]}"; do
printf " ✗ %s\n" "$s"
done
exit 1
else
printf "\n\033[1;32m All checks passed.\033[0m\n"
fi
Loading