Skip to content

Commit a88ea53

Browse files
authored
Merge pull request #2 from dango85/feat/default-nested-layout
feat: default to nested layout, add .gitignore at install time
2 parents 33a84b2 + 2d03d55 commit a88ea53

12 files changed

Lines changed: 485 additions & 30 deletions

.extensionignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Development and publishing files — not needed when installed
22
PUBLISH.md
33
catalog-entry.json
4+
tests/
45
.git/
56
.github/

.github/workflows/test.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
matrix:
14+
os: [ubuntu-latest, macos-latest]
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Configure git for tests
19+
run: |
20+
git config --global user.name "test"
21+
git config --global user.email "test@test.com"
22+
23+
- name: Run create-worktree tests
24+
run: bash tests/test-create-worktree.sh
25+
26+
- name: Run post-install tests
27+
run: bash tests/test-post-install.sh

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.worktrees/

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 1.2.0 (2026-04-14)
4+
5+
### Changed
6+
- Default layout switched from `sibling` to `nested` — worktrees now created at `.worktrees/<branch>/` inside the repo by default
7+
- Sibling layout (`../<repo>--<branch>`) remains available via `layout: "sibling"` in config
8+
9+
### Added
10+
- `post_install` lifecycle script — adds `.worktrees/` to `.gitignore` at install time (not just at first worktree creation)
11+
- README section "How worktrees stay isolated" documenting gitignore + commit isolation model
12+
13+
## 1.1.0 (2026-04-13)
14+
15+
### Added
16+
- `modifies_hooks` declaration: automatically disables `before_specify -> speckit.git.feature` on install (with user consent) so the primary checkout stays on a stable branch
17+
- Requires Spec Kit with `modifies_hooks` support ([github/spec-kit#2209](https://github.com/github/spec-kit/pull/2209))
18+
319
## 1.0.0 (2026-04-13)
420

521
### Added

README.md

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# spec-kit-worktree-parallel
22

3+
[![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)
4+
35
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.
46

57
## Why another worktree extension?
68

79
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:
810

911
1. **Default-on** — worktrees are created automatically after `/speckit.specify`. Opt *out* with `--in-place`, rather than opting in.
10-
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.
12+
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.
1113
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.
1214

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

1921
## Layout modes
2022

21-
### Sibling (default)
22-
23-
Each worktree is a sibling directory of the primary clone:
24-
25-
```
26-
parent/
27-
├── my-project/ ← primary checkout (main)
28-
├── my-project--005-user-auth/ ← worktree (005-user-auth branch)
29-
├── my-project--006-chat/ ← worktree (006-chat branch)
30-
```
31-
32-
Open each directory in its own IDE window. No `.gitignore` changes needed.
33-
34-
### Nested
23+
### Nested (default)
3524

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

@@ -44,19 +33,39 @@ my-project/
4433
├── src/
4534
```
4635

47-
Switch with `layout: nested` in `worktree-config.yml`.
36+
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.
37+
38+
### Sibling
39+
40+
Each worktree is a sibling directory of the primary clone:
41+
42+
```
43+
parent/
44+
├── my-project/ ← primary checkout (main)
45+
├── my-project--005-user-auth/ ← worktree (005-user-auth branch)
46+
├── my-project--006-chat/ ← worktree (006-chat branch)
47+
```
48+
49+
Open each directory in its own IDE window. Switch with `layout: "sibling"` in `worktree-config.yml`.
4850

4951
## Configuration
5052

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

5355
```yaml
54-
layout: "sibling" # sibling | nested
56+
layout: "nested" # nested | sibling
5557
auto_create: true # false to prompt instead of auto-creating
5658
sibling_pattern: "{{repo}}--{{branch}}"
5759
dotworktrees_dir: ".worktrees"
5860
```
5961
62+
## How worktrees stay isolated
63+
64+
- **On install** (`specify extension add`): `.worktrees/` is added to `.gitignore` so the directory is ignored before any worktree exists
65+
- **On create** (`/speckit.worktrees.create`): the script double-checks `.gitignore` as a safety net
66+
- **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
67+
- **Cleanup**: `/speckit.worktrees.clean` removes worktree directories; it never deletes the git branch itself
68+
6069
## Commands
6170

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

68-
## Hook
77+
## Hooks
6978

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

81+
## Hook overrides
82+
83+
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.
84+
85+
During `specify extension add`, you will see a consent prompt:
86+
87+
```
88+
Extension 'worktrees' requests the following hook modifications:
89+
90+
Hook Target Extension Command Action Reason
91+
before_specify git speckit.git.feature disable Worktree-parallel keeps primary checkout...
92+
93+
Apply these modifications? [Y/n]:
94+
```
95+
96+
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.
97+
98+
**Requires**: Spec Kit with `modifies_hooks` support (see [github/spec-kit#2209](https://github.com/github/spec-kit/pull/2209)).
99+
72100
## Script usage
73101

74102
The bash script can be called directly for automation:
75103

76104
```bash
77-
# Create a sibling worktree for branch 005-user-auth
105+
# Create a nested worktree for branch 005-user-auth (default)
78106
bash scripts/bash/create-worktree.sh --json 005-user-auth
79107
80-
# Nested layout
81-
bash scripts/bash/create-worktree.sh --json --layout nested 005-user-auth
108+
# Sibling layout instead
109+
bash scripts/bash/create-worktree.sh --json --layout sibling 005-user-auth
82110
83111
# Explicit path
84112
bash scripts/bash/create-worktree.sh --json --path /tmp/my-worktree 005-user-auth

commands/speckit.worktrees.create.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Read configuration from `.specify/extensions/worktrees/worktree-config.yml` if i
2828

2929
| Key | Default | Description |
3030
|-----|---------|-------------|
31-
| `layout` | `sibling` | `sibling` — worktree at `../<repo>--<branch>` (IDE-friendly); `nested` — at `.worktrees/<branch>/` inside repo |
31+
| `layout` | `nested` | `nested` — worktree at `.worktrees/<branch>/` inside repo (self-contained); `sibling` — at `../<repo>--<branch>` (IDE-friendly) |
3232
| `auto_create` | `true` | When `true`, the `after_specify` hook creates a worktree without prompting |
3333
| `sibling_pattern` | `{{repo}}--{{branch}}` | Name pattern for sibling directories |
3434
| `dotworktrees_dir` | `.worktrees` | Subdirectory name for nested layout |

extension.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ schema_version: "1.0"
33
extension:
44
id: worktrees
55
name: "Worktrees"
6-
version: "1.0.0"
6+
version: "1.2.0"
77
description: "Default-on worktree isolation for parallel agents — sibling or nested layout"
88
author: "dango85"
99
repository: "https://github.com/dango85/spec-kit-worktree-parallel"
@@ -39,6 +39,18 @@ hooks:
3939
optional: false
4040
description: "Auto-spawn a worktree after a new feature is specified"
4141

42+
lifecycle:
43+
post_install:
44+
script: scripts/bash/post-install.sh
45+
description: "Add .worktrees/ to .gitignore so worktree directories are never committed to the main repo"
46+
47+
modifies_hooks:
48+
- hook: before_specify
49+
extension: git
50+
command: speckit.git.feature
51+
action: disable
52+
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"
53+
4254
tags:
4355
- "worktree"
4456
- "git"
@@ -48,7 +60,7 @@ tags:
4860

4961
config:
5062
defaults:
51-
layout: "sibling"
63+
layout: "nested"
5264
auto_create: true
5365
sibling_pattern: "{{repo}}--{{branch}}"
5466
dotworktrees_dir: ".worktrees"

scripts/bash/create-worktree.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
set -euo pipefail
2121

2222
# --- defaults ---
23-
LAYOUT="sibling"
23+
LAYOUT="nested"
2424
WORKTREE_PATH_OVERRIDE=""
2525
IN_PLACE=false
2626
JSON_MODE=false
@@ -45,7 +45,7 @@ while [[ $# -gt 0 ]]; do
4545
echo "Usage: $0 [options] <branch-name>"
4646
echo ""
4747
echo "Options:"
48-
echo " --layout sibling|nested Worktree location strategy (default: sibling)"
48+
echo " --layout nested|sibling Worktree location strategy (default: nested)"
4949
echo " --path <dir> Explicit worktree path (overrides layout)"
5050
echo " --in-place Skip worktree creation (no-op exit 0)"
5151
echo " --json Output JSON instead of key=value"

scripts/bash/post-install.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
# post-install.sh — runs after `specify extension add worktrees`
3+
# Ensures .worktrees/ is in .gitignore immediately so the directory
4+
# is ignored before any worktree is ever created.
5+
6+
set -euo pipefail
7+
8+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
9+
10+
# Load dotworktrees_dir from config, fall back to .worktrees
11+
CONFIG_FILE="$REPO_ROOT/.specify/extensions/worktrees/worktree-config.yml"
12+
DOTWORKTREES_DIR=".worktrees"
13+
if [[ -f "$CONFIG_FILE" ]]; then
14+
val=$(grep -E "^dotworktrees_dir:" "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/^[^:]*: *//; s/ *#.*//; s/^"//; s/"$//' || true)
15+
if [[ -n "$val" ]]; then DOTWORKTREES_DIR="$val"; fi
16+
fi
17+
18+
GITIGNORE="$REPO_ROOT/.gitignore"
19+
20+
if ! grep -qxF "$DOTWORKTREES_DIR/" "$GITIGNORE" 2>/dev/null; then
21+
echo "$DOTWORKTREES_DIR/" >> "$GITIGNORE"
22+
echo "[worktrees] Added '$DOTWORKTREES_DIR/' to .gitignore" >&2
23+
fi

0 commit comments

Comments
 (0)