Project-local CLI for git worktree and issue tracker management
  • Scala 91.5%
  • Shell 7.9%
  • CSS 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Claude Agent 222ddfeac9
All checks were successful
Release / release (push) Successful in 19m35s
Merge pull request 'chore: Bump version to 0.22.1' (#801) from chore/bump-version-0.22.1 into main
Bump version to 0.22.1
2026-10-16 16:30:30 +00:00
.claude Merge pull request 'Dashboard: tell the projects apart by colour, and narrow the board to one project' (#575) from dashboard-control-plane into main 2026-09-17 08:10:43 +00:00
.git-hooks Merge main into IW-463 2026-09-09 07:05:29 +02:00
.github Merge main into IW-463 2026-09-09 07:05:29 +02:00
.iw chore(loops): name the variables a kanon loop session must pass on 2026-10-03 13:07:15 +02:00
commands Phase 2: iw port-slot + docs (#753) 2026-10-08 02:48:23 +00:00
core Merge pull request 'IW-557: Delete GitLabClient.parseMrPipeline, its tests and its API doc' (#791) from IW-557 into main 2026-10-11 07:08:33 +00:00
dashboard Merge pull request 'IW-681: ./mill dashboard.test output is pristine' (#798) from IW-681 into main 2026-10-11 17:45:09 +00:00
docs Merge pull request 'IW-557: Delete GitLabClient.parseMrPipeline, its tests and its API doc' (#791) from IW-557 into main 2026-10-11 07:08:33 +00:00
project-management Merge pull request 'IW-681: ./mill dashboard.test output is pristine' (#798) from IW-681 into main 2026-10-11 17:45:09 +00:00
schemas Phase 2: Pre-gate budget, so a forge that will not answer before the gate resumes (#779) 2026-10-10 23:32:20 +00:00
scripts Phase 2: Claude links on the / board and agent lane (Presentation + route wiring) (#739) 2026-10-07 04:11:56 +00:00
test Merge pull request 'IW-548: a non-repeatable valued flag given twice takes the last value everywhere' (#786) from IW-548 into main 2026-10-11 07:08:12 +00:00
webapp-knowledge Phase 2: Claude links on the / board and agent lane (Presentation + route wiring) (#739) 2026-10-07 04:11:56 +00:00
.gitignore feat(ci-gate): normalise CI job logs into greppable evidence 2026-08-11 22:53:47 +02:00
.mill-version IW-345: Mill module for dashboard with Vite + Tailwind + Web Awesome (#370) 2026-04-27 11:30:26 +02:00
.scalafix.conf feat(quality): Add CI workflow, git hooks, scalafix config, and CONTRIBUTING.md 2026-02-13 14:08:43 +01:00
.scalafmt.conf IW-345: Mill module for dashboard with Vite + Tailwind + Web Awesome (#370) 2026-04-27 11:30:26 +02:00
build.mill Phase 3: Undertow, XNIO and JBoss Threads INFO chatter on every server start and stop (#794) 2026-10-11 07:13:05 +00:00
CLAUDE.md docs: put the test-scope policy where the agent actually reads it 2026-09-20 12:15:02 +02:00
CONTRIBUTING.md docs: record the format gate's inputs and the suite outcome type 2026-09-04 10:39:24 +02:00
iw IW-106: Restructure source code location - separate from .iw directory (#330) 2026-04-02 23:21:06 +02:00
iw-bootstrap docs(IW-463): Phase 6 Steps 6-7 — iw-bootstrap, the docs, the log and #202 2026-09-05 16:56:42 +02:00
iw-run feat(launcher): name a plugin loaded from another working tree 2026-09-13 14:59:35 +02:00
llms.txt Phase 3: TrackerError on the port's forge issue calls (#780) 2026-10-11 00:15:44 +00:00
mill IW-345: Mill module for dashboard with Vite + Tailwind + Web Awesome (#370) 2026-04-27 11:30:26 +02:00
README.md Phase 2: iw port-slot + docs (#753) 2026-10-08 02:48:23 +00:00
RELEASE.md fix(IW-399): address Phase 3 code-review findings 2026-07-24 21:44:59 +02:00
VERSION chore: Bump version to 0.22.1 2026-10-16 15:27:49 +02:00

iw-cli

Project-local CLI tool for managing git worktrees and integrating with issue trackers.

Overview

iw is a project-local tool (similar to mill) that:

  • Manages git worktrees as sibling directories for isolated development
  • Integrates with issue trackers (Linear, YouTrack, GitHub, GitLab)
  • Provides tmux session management per worktree
  • Supports project-specific extensibility via scala-cli scripts

Installation

Quick Start

Download and run the bootstrap script in your project root:

curl -L https://code.iterative.works/iterative-works/iw-cli/releases/download/vlatest/iw-bootstrap -o iw
chmod +x iw
./iw init

The bootstrap script will:

  1. Read the version from .iw/config.conf (defaults to "latest")
  2. Download the specified version to ~/.local/share/iw/versions/<version>/
  3. Pre-compile dependencies for offline use
  4. Execute the requested command

Version Pinning

Pin a specific version in your project's .iw/config.conf:

version = "0.1.0"

Or use the latest release:

version = "latest"

If not specified, defaults to "latest".

Requirements

  • scala-cli: Install from scala-cli.virtuslab.org
  • git: For worktree management
  • tmux (optional): For session management
  • curl: For downloading releases

Dashboard contributor requirements

Additional prerequisites for working on the dashboard module:

  • Node 20+ with corepack enable (drives Yarn 4 via the packageManager field in package.json)
  • Yarn 4 — installed automatically by Corepack; no separate install needed
  • Mill 1.1.5 — the committed ./mill wrapper downloads it on first use
  • WEBAWESOME_NPM_TOKEN — set in env before running yarn install; needed to fetch licensed UI components

Commands

An orientation list, by name and one clause each — not the flags or exit codes, which live in one place: run iw --list for the whole roster, grouped, and iw <cmd> --help for one command's full surface.

Command Description
iw init Interactive setup, creates .iw/config.conf
iw doctor Validate environment (tokens, tmux, etc.)
iw start Create sibling worktree + tmux session
iw open Open tmux session (defaults to current branch)
iw rm Close workspace, remove worktree, keep branch
iw issue Fetch issue details from configured tracker
iw projects List all registered projects
iw worktrees List worktrees for current project
iw status Show detailed worktree status
iw review-state Locate, validate, write and update the review-state card (kept in the common git dir, not in the tree)
iw service Declare the services of a worktree (add, list, rm) for the dashboard to link to
iw port-slot Claim or list the port slot of a worktree, a number unique among the worktrees of the repository (claim, list)
iw ./test Run tests (project-specific command)

Agent integration

The --prompt flag on start and open enables agent-driven development. When provided, iw launches a Claude Code session in the tmux pane with the given prompt and exits immediately (detached mode).

# Start work on an issue with an agent
iw start 203 --prompt "Analyze this issue and create analysis.md"

# Send new instructions to an existing session
iw open 203 --prompt "Continue with implementation phase 2"

When standard input is not a terminal (an agent shell, CI, cron), start and open do all their work, do not attach, print the attach command, and exit 0 (5 if a --prompt was not delivered).

For an unattended caller that launches its own agent process separately, start --no-agent creates the worktree and workspace and runs the setup hooks (direnv allow, a search-index build), but skips the session-action hook that would launch an agent, and does not attach or switch to the session. The setup commands are sent to the pane and can still be running when start returns.

# Create the worktree and session without launching an agent
iw start 203 --no-agent

--no-agent cannot be combined with --prompt.

The projects, worktrees, and status commands support --json for machine-readable output, enabling agents to discover and monitor worktrees programmatically.

# List all projects
iw projects --json

# List worktrees for current project
iw worktrees --json

# Get detailed status for a worktree
iw status IW-203 --json

For the card format, the schema contract and common workflows, see docs/review-state-cards.md; for the flags and exit codes themselves, see iw review-state --help or the generated docs/commands/review-state.md.

Effort worktrees

A worktree is keyed either on a tracker issue or on an effort — a human-named branch with no tracker issue, hosting several slices that ship as one PR. The effort must already be framed (project-management/efforts/<slug>/ present) before the worktree is created:

iw start mail-capture

This creates branch mail-capture and worktree <project>-mail-capture. iw open, iw rm and iw status accept the slug wherever they accept an issue ID.

See docs/effort-worktrees.md for the slug rules, the on-disk layout, and what the dashboard shows for an effort worktree.

Removing a worktree

iw rm closes the workspace, runs the cleanup hooks, removes the directory and keeps the branch. It names every uncommitted file before it asks, and refuses rather than ask when it has no terminal to ask in.

See docs/commands/rm.md for the flags and the exit codes a caller reads it by, and docs/worktree-removal.md for what it refuses beyond those and how the dashboard's Remove control uses it.

Plugins

AI-specific workflow commands (e.g. implement, batch-implement, transcript-analyze) are not part of iw-cli core. They are distributed as plugin commands — the reference distribution is kanon, which packages Claude Code integrations as hooks and commands.

Plugins are discovered automatically under $XDG_DATA_HOME/iw/plugins/ (defaults to ~/.local/share/iw/plugins/) or via the IW_PLUGIN_DIRS environment variable. A plugin's commands are invoked with the <plugin>/<command> syntax:

iw kanon/batch-implement IW-123
iw kanon/implement IW-123 --phase 2

Plugins may also contribute hooks that extend the behavior of core commands (e.g. kanon's claude-session.hook-start.scala adds an agent session launch to iw start). iw start --no-agent skips that session-action hook — the setup hooks still run.

A hook may declare the version it was written against with a // REQUIRES: iw-cli >= X.Y.Z header. Hooks the installed version cannot satisfy are skipped with a warning naming the hook and both versions, and the command runs without them — a hook decorates a command rather than being the thing you asked to run, so one stale plugin hook never blocks it. Commands carrying the same header are stricter: an unsatisfied requirement stops the run.

Session hooks receive a SessionContext carrying key (the identity the worktree is addressed under) and kind (Issue or Effort). Read kind before interpreting key: only an issue key resolves against the tracker — an effort worktree's key is its slug. See docs/effort-worktrees.md.

Setting IW_PLUGINS_DISABLED=1 suppresses discovery from every source, so the run only sees the commands and hooks shipped with the installation and the project. Use it to reproduce iw-cli behavior without installed plugins.

GitHub Integration

Repository Auto-Detection

When using iw init --tracker=github, the repository is automatically detected from your git remote URL:

# Automatically detects "iterative-works/iw-cli" from remote URL
iw init --tracker=github

Supported URL formats:

  • HTTPS: https://github.com/owner/repo.git
  • HTTPS without .git: https://github.com/owner/repo
  • HTTPS with username: https://username@github.com/owner/repo.git
  • HTTPS with trailing slash: https://github.com/owner/repo/
  • SSH: git@github.com:owner/repo.git
  • SSH without .git: git@github.com:owner/repo

Multiple remotes: When your repository has multiple remotes (e.g., origin and upstream), the origin remote is always used for auto-detection.

Manual input: If auto-detection fails (non-GitHub remote or no remote configured), you'll be prompted to enter the repository manually in owner/repo format.

Authentication

GitHub integration uses the gh CLI for authentication:

# Authenticate once
gh auth login

# Then use iw normally
iw init --tracker=github
iw issue

No API tokens needed - iw uses your existing gh authentication.

GitLab Integration

Repository Auto-Detection

When using iw init --tracker=gitlab, the repository is automatically detected from your git remote URL:

# Automatically detects "owner/project" from remote URL
iw init --tracker=gitlab --team-prefix=PROJ

Supported URL formats:

  • HTTPS: https://gitlab.com/owner/project.git
  • HTTPS without .git: https://gitlab.com/owner/project
  • HTTPS with trailing slash: https://gitlab.com/owner/project/
  • SSH: git@gitlab.com:owner/project.git
  • SSH without .git: git@gitlab.com:owner/project
  • Self-hosted: https://gitlab.company.com/owner/project.git
  • Nested groups: https://gitlab.com/company/team/project.git

Multiple remotes: When your repository has multiple remotes (e.g., origin and upstream), the origin remote is always used for auto-detection.

Manual input: If auto-detection fails (non-GitLab remote or no remote configured), you'll be prompted to enter the repository manually in owner/project or group/subgroup/project format.

Authentication

GitLab integration uses the glab CLI for authentication:

# Install glab CLI (macOS)
brew install glab

# Install glab CLI (Linux/Windows)
# See https://gitlab.com/gitlab-org/cli#installation

# Authenticate once
glab auth login

# Then use iw normally
iw init --tracker=gitlab --team-prefix=PROJ
iw issue

No API tokens needed - iw uses your existing glab authentication.

Self-Hosted GitLab

For self-hosted GitLab instances, specify the base URL during initialization:

iw init --tracker=gitlab --team-prefix=PROJ --base-url=https://gitlab.company.com

Or add it manually to .iw/config.conf:

tracker {
  type = gitlab
  repository = "team/project"
  teamPrefix = "PROJ"
  baseUrl = "https://gitlab.company.com"  # Optional, defaults to gitlab.com
}

Nested Groups

GitLab supports nested group structures like company/team/project. These are fully supported:

# Auto-detection works with nested groups
git remote add origin https://gitlab.com/company/team/project.git
iw init --tracker=gitlab --team-prefix=PROJ

Configuration:

tracker {
  type = gitlab
  repository = "company/team/project"
  teamPrefix = "PROJ"
}

Worktree Layout

Worktrees are created as sibling directories:

~/projects/
├── myproject/              # main worktree (master/main)
├── myproject-IW-123/       # worktree for issue IW-123
├── myproject-IW-456/       # worktree for issue IW-456

Development

Setup

Clone the repository and use the local iw script directly:

git clone https://code.iterative.works/iterative-works/iw-cli.git
cd iw-cli
./iw --list

The local iw script runs commands from commands/ without downloading anything - ideal for development.

Testing in Other Projects

To test your local iw-cli changes in other projects, set IW_HOME:

# In any project using iw-bootstrap
IW_HOME=/path/to/iw-cli ./iw --list

# Or export for the session
export IW_HOME=/path/to/iw-cli
./iw start ISSUE-123

This bypasses version download and uses your local iw-run directly.

Testing

The project has three types of tests:

Unit Tests (Scala/munit)

Run Scala unit tests with:

./iw ./test unit

Or directly with scala-cli:

scala-cli test core/

E2E Tests (BATS)

End-to-end tests verify the CLI behavior. By default, tests that would create real Linear issues are skipped.

Run E2E tests (without live API calls):

./iw ./test e2e

Or directly with BATS:

bats test/

Live API Tests

Some E2E tests can create real Linear issues for comprehensive testing. These are disabled by default to prevent issue accumulation.

To enable live API tests:

ENABLE_LIVE_API_TESTS=1 ./iw ./test e2e

Requirements:

  • LINEAR_API_TOKEN environment variable must be set
  • ENABLE_LIVE_API_TESTS=1 must be explicitly set

Warning: Live API tests will create real issues in Linear with [TEST] prefix. These should be cleaned up periodically.

Run All Tests

./iw ./test

Or manually:

scala-cli test core/ && bats test/

Project Structure

iw-cli/
├── iw                    # Development launcher (runs locally)
├── iw-bootstrap          # Distribution bootstrap (downloads releases)
├── iw-run                # Distribution launcher (in release tarball)
├── VERSION               # Current version
├── commands/             # Shared command implementations (*.scala)
├── core/                 # Shared library code
│   ├── project.scala     # Build configuration (deps, Scala version)
│   └── test/             # Unit tests
├── test/                 # E2E tests (*.bats)
├── scripts/              # Build/release scripts
├── .iw/
│   ├── config.conf       # Project config (tracker, etc.)
│   └── commands/         # Project-specific commands (test.scala)
└── RELEASE.md            # Release process documentation

Creating a Release

See RELEASE.md for the full release process. Quick version:

scripts/package-release.sh 0.1.0
# Creates release/iw-cli-0.1.0.tar.gz

License

MIT