Parallel development environment manager using git worktrees
Project description
Maelstrom
Parallel development environment manager using git worktrees. Run multiple isolated development environments simultaneously, each with its own branch, port allocations, and running services. Integrates with GitHub, Linear, and Sentry.
Installation
# Using uv (recommended)
uv tool install git+https://github.com/sminnee/maelstrom.git
# Or install locally for development
git clone https://github.com/sminnee/maelstrom.git
cd maelstrom
uv sync
uv tool install --editable .
Quick Start
# Clone a project and initialize it for maelstrom
mael add-project git@github.com:org/repo.git
# Create a new worktree for a feature branch
mael add myproject feature/avatar-upload
# Or from within any worktree directory, the project is auto-detected
mael add feature/avatar-upload
# List all worktrees in a project
mael list myproject
# Start services
mael env start
# Open a worktree in your editor
mael open myproject.bravo
# Close a worktree when done (syncs, resets to main, ready for reuse)
mael close
Targeting Worktrees
Most commands accept an optional target argument in the form project.worktree:
mael list myproject # List worktrees in myproject
mael open myproject.bravo # Open the bravo worktree
mael env start myproject.c # Start services in charlie (shortcode)
Shortcodes map single letters to NATO names: a → alpha, b → bravo, c → charlie, etc. If you're inside a worktree directory, the project and worktree are auto-detected.
Worktree Management
| Command | Description |
|---|---|
mael add [PROJECT] BRANCH |
Create a new worktree (or recycle a closed one). Options: --no-open, --no-recycle |
mael remove TARGET |
Remove one or more worktrees. -f to skip dirty-file confirmation |
mael list [PROJECT] |
List worktrees with branch, dirty files, local commits, PR info, app URL |
mael list-all |
List worktrees across all projects. --json for machine-readable output |
mael open [TARGET] |
Open a worktree in the configured editor |
mael close [TARGET] |
Sync, verify clean, reset to main. Preserves folder and ports for recycling |
mael sync [TARGET] |
Rebase worktree against origin/main and push |
mael sync-all [PROJECT] |
Sync all worktrees in a project |
mael tidy-branches [PROJECT] |
Rebase feature branches, delete merged ones, force-push unmerged |
mael add-project GIT_URL |
Clone a repo and initialize for maelstrom. --projects-dir to override location |
Worktree Naming
Worktrees use NATO phonetic alphabet names: alpha, bravo, charlie, delta, echo, foxtrot, golf, hotel, india, juliet, kilo, lima, mike, november, oscar, papa, quebec, romeo, sierra, tango, uniform, victor, whiskey, xray, yankee, zulu.
When a worktree is closed with mael close, it is reset to origin/main but the folder, NATO name, and port allocation are preserved. The next mael add will recycle a closed worktree rather than creating a new one.
Repository Structure
~/Projects/myproject/
├── .git/ # Shared bare git directory
├── myproject-alpha/ # Worktree (main branch)
│ ├── .maelstrom.yaml # Project config (checked into repo)
│ ├── .env # Generated port assignments (gitignored)
│ ├── Procfile # Service definitions (checked into repo)
│ └── ...
├── myproject-bravo/ # Feature worktree
│ ├── .env # Different PORT_BASE
│ └── ...
└── myproject-charlie/ # Another feature worktree
└── ...
Configuration
Project Configuration (.maelstrom.yaml)
Create this file in your repository root:
# Port names — each gets a _PORT environment variable
port_names:
- FRONTEND
- SERVER
- DB
- REDIS
# Shared port names — allocated once per project, shared across worktrees
shared_port_names:
- SHARED_REDIS
# Command to install dependencies (run on worktree creation and env start)
install_cmd: "uv sync"
# Fallback start command if no Procfile is present
start_cmd: "npm run dev"
# Linear integration
linear:
team_id: "your-team-uuid"
workspace_labels: [alpha, bravo, charlie]
product_label: "YourProduct" # Auto-assigned to tasks; used by `mael linear release`
# Sentry integration
sentry_org: "your-org"
sentry_project: "your-project-slug"
Global Configuration (~/.maelstrom/config.yaml)
projects_dir: ~/Projects # Base directory for projects
open_command: "cursor" # Editor command (default: "code")
linear:
api_key: "lin_api_xxx" # Linear API key
Port Allocation
Each worktree is assigned a unique PORT_BASE in the range 300–999. Service ports are calculated as PORT_BASE * 10 + index.
For example, with PORT_BASE=300 and port names [FRONTEND, SERVER, DB]:
PORT_BASE=300
FRONTEND_PORT=3000
SERVER_PORT=3001
DB_PORT=3002
Port allocations are persisted in ~/.maelstrom/port_allocations.json and checked for socket availability when assigned. The first port (PORT_BASE * 10) is used as the app URL.
Environment Management
Maelstrom manages service processes for each worktree.
Procfile
Define services in a Procfile in your repository root:
web: npm run dev
worker: python manage.py worker
redis: redis-server --port $REDIS_PORT
Services with names ending in -shared are shared across worktrees in the same project. If no Procfile is present, maelstrom falls back to start_cmd from .maelstrom.yaml.
Commands
| Command | Description |
|---|---|
mael env start [TARGET] |
Run install command, then start all services. --skip-install to skip |
mael env stop [TARGET] |
Stop all services (SIGTERM, then SIGKILL after 10s) |
mael env status [TARGET] |
Show service PIDs, status, and log file paths |
mael env logs [TARGET] [SERVICE] |
View service logs. -f to follow, -n NUM for line count |
mael env list [PROJECT] |
List running environments for a project |
mael env list-all |
List all running environments across all projects |
mael env stop-all |
Stop all environments globally |
GitHub Integration
| Command | Description |
|---|---|
mael gh create-pr [ISSUE_ID] |
Create or update a pull request. --draft for draft PRs, --target for worktree |
mael gh read-pr [TARGET] |
Show PR status, unresolved comments, and CI check results |
mael gh show-code [TARGET] |
Show commits and diffs. --committed or --uncommitted |
mael gh check-log RUN_ID |
View GitHub Actions logs. --failed-only for failures |
mael gh download-artifact RUN_ID NAME |
Download a workflow artifact. -o DIR for output |
Linear Integration
| Command | Description |
|---|---|
mael linear list-tasks |
List tasks in the current cycle. --status STATUS to filter |
mael linear read-task ISSUE_ID |
Show task details, subtasks, comments |
mael linear start-task ISSUE_ID |
Mark task as In Progress, add worktree label |
mael linear complete-task ISSUE_ID |
Mark task as Done (subtask) or Unreleased (parent) |
mael linear create-subtask PARENT TITLE [DESC] |
Create a subtask linked to a parent |
mael linear write-plan ISSUE_ID FILE |
Write a plan file to the task description |
mael linear read-plan ISSUE_ID |
Extract and display the plan from a task |
mael linear release |
Promote all "Unreleased" tasks with product label to "Done" |
Sentry Integration
| Command | Description |
|---|---|
mael sentry list-issues |
List unresolved issues. --env ENV (default: prod) |
mael sentry get-issue ISSUE_ID |
Show exception details, tags, and stacktraces |
Code Review
| Command | Description |
|---|---|
mael review squash |
Squash all fixup! commits via autosquash rebase |
mael review status |
Show pending fixup commits and their targets |
Claude Code Integration
Maelstrom includes skills and commands for Claude Code:
# Install skills, hooks, and commands into ~/.claude/
mael install
# Update maelstrom from git
mael self-update
Once installed, these skills are available in Claude Code:
| Skill | Description |
|---|---|
/mael |
Load git workflow, Linear, Sentry, and env management instructions |
/plan-task ISSUE_ID |
Plan implementation for a Linear task (requires plan mode) |
/continue-task ISSUE_ID |
Read plan from Linear and begin implementation |
/create-subtasks ISSUE_ID |
Break a task into subtasks (requires plan mode) |
/review-branch |
Review code changes before creating a PR (requires plan mode) |
Development
# Install dev dependencies
uv sync --all-extras
# Run tests
uv run pytest
# Run with coverage
uv run pytest --cov=maelstrom
Desktop UI & Agent-CLI (Incomplete)
A Tauri desktop app (app/) and Node.js agent-CLI bridge (agent-cli/) are in development. These will provide a graphical interface for managing sessions and interacting with Claude Code via a Unix domain socket protocol. They are not yet functional.
Notes for Contributors
Publishing to PyPI
The package is published to PyPI as sminnee-maelstrom. To publish a new version:
# Set your PyPI token (or add UV_PUBLISH_TOKEN to .env)
export UV_PUBLISH_TOKEN=pypi-...
# Publish with a patch version bump (0.1.0 → 0.1.1)
./bin/publish
# Or bump minor/major
./bin/publish --minor
./bin/publish --major
This bumps the version, builds, publishes to PyPI, commits the version change, and tags it. After publishing, run git push && git push --tags.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sminnee_maelstrom-0.1.1.tar.gz.
File metadata
- Download URL: sminnee_maelstrom-0.1.1.tar.gz
- Upload date:
- Size: 386.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b372da33ffee90ec7242476b561cf86c1bc5710ad8209e9abdd7de9adccf4687
|
|
| MD5 |
fb4fde6e4308cb17e12488d5be1532f4
|
|
| BLAKE2b-256 |
cc4493bde7dbce4e8301e2a2dc0d3fa4c5415a7482a7698181de03135e420307
|
File details
Details for the file sminnee_maelstrom-0.1.1-py3-none-any.whl.
File metadata
- Download URL: sminnee_maelstrom-0.1.1-py3-none-any.whl
- Upload date:
- Size: 62.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5764c267ea46dbbcf15643469dd4cab0d3df870140fed676ec57ac6288304d6d
|
|
| MD5 |
d9fb8b3c60b0c9ee0cd87799619ec205
|
|
| BLAKE2b-256 |
6767f3f67cab7a78bc665fa93385526befc7830b0c46521b559370170afe4823
|