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
1 change: 1 addition & 0 deletions .extensionignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Development and publishing files — not needed when installed
PUBLISH.md
catalog-entry.json
tests/
.git/
.github/
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4

- name: Configure git for tests
run: |
git config --global user.name "test"
git config --global user.email "test@test.com"

- name: Run create-worktree tests
run: bash tests/test-create-worktree.sh

- name: Run post-install tests
run: bash tests/test-post-install.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.worktrees/
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 1.2.0 (2026-04-14)

### Changed
- Default layout switched from `sibling` to `nested` — worktrees now created at `.worktrees/<branch>/` inside the repo by default
- Sibling layout (`../<repo>--<branch>`) remains available via `layout: "sibling"` in config

### Added
- `post_install` lifecycle script — adds `.worktrees/` to `.gitignore` at install time (not just at first worktree creation)
- README section "How worktrees stay isolated" documenting gitignore + commit isolation model

## 1.1.0 (2026-04-13)

### Added
- `modifies_hooks` declaration: automatically disables `before_specify -> speckit.git.feature` on install (with user consent) so the primary checkout stays on a stable branch
- Requires Spec Kit with `modifies_hooks` support ([github/spec-kit#2209](https://github.com/github/spec-kit/pull/2209))

## 1.0.0 (2026-04-13)

### Added
Expand Down
70 changes: 49 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# spec-kit-worktree-parallel

[![Tests](https://github.com/dango85/spec-kit-worktree-parallel/actions/workflows/test.yml/badge.svg)](https://github.com/dango85/spec-kit-worktree-parallel/actions/workflows/test.yml)

A [Spec Kit](https://github.com/github/spec-kit) extension for **default-on** git worktree isolation — work on multiple features (or run parallel agents) without checkout switching.

## Why another worktree extension?

The community [spec-kit-worktree](https://github.com/Quratulain-bilal/spec-kit-worktree) extension is a good starting point. This extension differs in three ways:

1. **Default-on** — worktrees are created automatically after `/speckit.specify`. Opt *out* with `--in-place`, rather than opting in.
2. **Sibling-dir layout** — worktrees live at `../<repo>--<branch>` by default, so each feature gets its own top-level IDE window. Nested `.worktrees/` is available as an option.
2. **Nested layout by default** — worktrees live at `.worktrees/<branch>/` inside the repo (gitignored, self-contained). Sibling-dir layout (`../<repo>--<branch>`) is available as an option for IDE-per-feature workflows.
3. **Deterministic bash script** — a real script (`create-worktree.sh`) with `--json` output, `--dry-run`, and `SPECIFY_WORKTREE_PATH` override, suitable for CI and scripted workflows.

## Installation
Expand All @@ -18,20 +20,7 @@ specify extension add --from https://github.com/dango85/spec-kit-worktree-parall

## Layout modes

### Sibling (default)

Each worktree is a sibling directory of the primary clone:

```
parent/
├── my-project/ ← primary checkout (main)
├── my-project--005-user-auth/ ← worktree (005-user-auth branch)
├── my-project--006-chat/ ← worktree (006-chat branch)
```

Open each directory in its own IDE window. No `.gitignore` changes needed.

### Nested
### Nested (default)

Worktrees live inside the repo under `.worktrees/` (auto-gitignored):

Expand All @@ -44,19 +33,39 @@ my-project/
├── src/
```

Switch with `layout: nested` in `worktree-config.yml`.
Self-contained — everything stays in one directory. `.worktrees/` is added to `.gitignore` at install time so worktree directories are never accidentally committed to the main repo. Work inside each worktree is committed on its own feature branch.

### Sibling

Each worktree is a sibling directory of the primary clone:

```
parent/
├── my-project/ ← primary checkout (main)
├── my-project--005-user-auth/ ← worktree (005-user-auth branch)
├── my-project--006-chat/ ← worktree (006-chat branch)
```

Open each directory in its own IDE window. Switch with `layout: "sibling"` in `worktree-config.yml`.

## Configuration

Create `.specify/extensions/worktrees/worktree-config.yml` to override defaults:

```yaml
layout: "sibling" # sibling | nested
layout: "nested" # nested | sibling
auto_create: true # false to prompt instead of auto-creating
sibling_pattern: "{{repo}}--{{branch}}"
dotworktrees_dir: ".worktrees"
```

## How worktrees stay isolated

- **On install** (`specify extension add`): `.worktrees/` is added to `.gitignore` so the directory is ignored before any worktree exists
- **On create** (`/speckit.worktrees.create`): the script double-checks `.gitignore` as a safety net
- **Commits stay on the right branch**: each worktree has its own working tree and index — `git add` and `git commit` inside a worktree only affect that worktree's branch, not the main repo
- **Cleanup**: `/speckit.worktrees.clean` removes worktree directories; it never deletes the git branch itself

## Commands

| Command | Description | Modifies files? |
Expand All @@ -65,20 +74,39 @@ dotworktrees_dir: ".worktrees"
| `/speckit.worktrees.list` | Dashboard: status, artifacts, tasks | No |
| `/speckit.worktrees.clean` | Remove merged/stale worktrees | Yes |

## Hook
## Hooks

**`after_specify`** — automatically creates a worktree after a new feature is specified. Controlled by the `auto_create` config value.

## Hook overrides

This extension declares `modifies_hooks` in `extension.yml` to **disable** the git extension's `before_specify -> speckit.git.feature` hook on install. This keeps the primary checkout on a stable branch (e.g. `main`) while worktrees handle feature branch isolation.

During `specify extension add`, you will see a consent prompt:

```
Extension 'worktrees' requests the following hook modifications:

Hook Target Extension Command Action Reason
before_specify git speckit.git.feature disable Worktree-parallel keeps primary checkout...

Apply these modifications? [Y/n]:
```

Answering **Y** disables the hook. Answering **N** installs the extension without modifying hooks (you can disable it manually in `.specify/extensions.yml`). Removing the extension via `specify extension remove worktrees` restores the original hook state.

**Requires**: Spec Kit with `modifies_hooks` support (see [github/spec-kit#2209](https://github.com/github/spec-kit/pull/2209)).

## Script usage

The bash script can be called directly for automation:

```bash
# Create a sibling worktree for branch 005-user-auth
# Create a nested worktree for branch 005-user-auth (default)
bash scripts/bash/create-worktree.sh --json 005-user-auth

# Nested layout
bash scripts/bash/create-worktree.sh --json --layout nested 005-user-auth
# Sibling layout instead
bash scripts/bash/create-worktree.sh --json --layout sibling 005-user-auth

# Explicit path
bash scripts/bash/create-worktree.sh --json --path /tmp/my-worktree 005-user-auth
Expand Down
2 changes: 1 addition & 1 deletion commands/speckit.worktrees.create.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Read configuration from `.specify/extensions/worktrees/worktree-config.yml` if i

| Key | Default | Description |
|-----|---------|-------------|
| `layout` | `sibling` | `sibling` — worktree at `../<repo>--<branch>` (IDE-friendly); `nested` — at `.worktrees/<branch>/` inside repo |
| `layout` | `nested` | `nested` — worktree at `.worktrees/<branch>/` inside repo (self-contained); `sibling` — at `../<repo>--<branch>` (IDE-friendly) |
| `auto_create` | `true` | When `true`, the `after_specify` hook creates a worktree without prompting |
| `sibling_pattern` | `{{repo}}--{{branch}}` | Name pattern for sibling directories |
| `dotworktrees_dir` | `.worktrees` | Subdirectory name for nested layout |
Expand Down
16 changes: 14 additions & 2 deletions extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ schema_version: "1.0"
extension:
id: worktrees
name: "Worktrees"
version: "1.0.0"
version: "1.2.0"
description: "Default-on worktree isolation for parallel agents — sibling or nested layout"
author: "dango85"
repository: "https://github.com/dango85/spec-kit-worktree-parallel"
Expand Down Expand Up @@ -39,6 +39,18 @@ hooks:
optional: false
description: "Auto-spawn a worktree after a new feature is specified"

lifecycle:
post_install:
script: scripts/bash/post-install.sh
description: "Add .worktrees/ to .gitignore so worktree directories are never committed to the main repo"

modifies_hooks:
- hook: before_specify
extension: git
command: speckit.git.feature
action: disable
reason: "Worktree-parallel keeps primary checkout on a stable branch (e.g. main); branch creation is handled by git worktree add -b during after_specify"

tags:
- "worktree"
- "git"
Expand All @@ -48,7 +60,7 @@ tags:

config:
defaults:
layout: "sibling"
layout: "nested"
auto_create: true
sibling_pattern: "{{repo}}--{{branch}}"
dotworktrees_dir: ".worktrees"
4 changes: 2 additions & 2 deletions scripts/bash/create-worktree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
set -euo pipefail

# --- defaults ---
LAYOUT="sibling"
LAYOUT="nested"
WORKTREE_PATH_OVERRIDE=""
IN_PLACE=false
JSON_MODE=false
Expand All @@ -45,7 +45,7 @@ while [[ $# -gt 0 ]]; do
echo "Usage: $0 [options] <branch-name>"
echo ""
echo "Options:"
echo " --layout sibling|nested Worktree location strategy (default: sibling)"
echo " --layout nested|sibling Worktree location strategy (default: nested)"
echo " --path <dir> Explicit worktree path (overrides layout)"
echo " --in-place Skip worktree creation (no-op exit 0)"
echo " --json Output JSON instead of key=value"
Expand Down
23 changes: 23 additions & 0 deletions scripts/bash/post-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# post-install.sh — runs after `specify extension add worktrees`
# Ensures .worktrees/ is in .gitignore immediately so the directory
# is ignored before any worktree is ever created.

set -euo pipefail

REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0

# Load dotworktrees_dir from config, fall back to .worktrees
CONFIG_FILE="$REPO_ROOT/.specify/extensions/worktrees/worktree-config.yml"
DOTWORKTREES_DIR=".worktrees"
if [[ -f "$CONFIG_FILE" ]]; then
val=$(grep -E "^dotworktrees_dir:" "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/^[^:]*: *//; s/ *#.*//; s/^"//; s/"$//' || true)
if [[ -n "$val" ]]; then DOTWORKTREES_DIR="$val"; fi
fi

GITIGNORE="$REPO_ROOT/.gitignore"

if ! grep -qxF "$DOTWORKTREES_DIR/" "$GITIGNORE" 2>/dev/null; then
echo "$DOTWORKTREES_DIR/" >> "$GITIGNORE"
echo "[worktrees] Added '$DOTWORKTREES_DIR/' to .gitignore" >&2
fi
Loading
Loading