yggtree 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # 🌳 Yggdrasil Worktree (yggtree)
2
2
 
3
- **Yggdrasil Worktree** (invoked as `yggtree`) is a powerful, interactive CLI designed to streamline your Git worktree workflow. Like the mythical world tree connecting the realms, Yggdrasil connects your branches into isolated, manageable worktrees.
3
+ [![npm version](https://img.shields.io/npm/v/yggtree.svg)](https://www.npmjs.com/package/yggtree)
4
+ [![license](https://img.shields.io/npm/l/yggtree.svg)](https://www.npmjs.com/package/yggtree)
5
+
6
+ **Yggdrasil Worktree** (invoked as `yggtree`) is an interactive CLI designed to turn Git worktrees into a first‑class workflow.
7
+
8
+ Like the mythical world tree connecting realms, Yggdrasil lets you grow isolated, parallel environments where ideas can evolve independently without colliding.
4
9
 
5
10
  ---
6
11
 
@@ -8,56 +13,158 @@
8
13
 
9
14
  ### Installation
10
15
 
16
+ Run without installing:
17
+
11
18
  ```bash
12
- # Clone the repository
13
- git clone https://github.com/leoreisdias/yggtree.git
14
- cd yggtree
19
+ npx yggtree
20
+ ```
15
21
 
16
- # Install dependencies and build
17
- npm install
18
- npm run build
22
+ Or install globally:
19
23
 
20
- # Link the CLI globally
21
- npm link
24
+ ```bash
25
+ npm install -g yggtree
22
26
  ```
23
27
 
24
- ### Usage
28
+ ### Basic Usage
25
29
 
26
- Simply run `yggtree` to open the interactive menu:
30
+ Run with no arguments to open the interactive menu:
27
31
 
28
32
  ```bash
29
33
  yggtree
30
34
  ```
31
35
 
32
- Or use specific commands:
36
+ Or use commands directly:
33
37
 
34
38
  ```bash
35
- yggtree wt create # Smart branch-based creation
36
- yggtree wt list # View all managed worktrees
37
- yggtree wt prune # Clean up stale worktree data
39
+ yggtree wt create
40
+ yggtree wt list
41
+ yggtree wt enter my-feature
42
+ ```
43
+
44
+ ---
45
+
46
+ ## 🧠 Mental Model
47
+
48
+ Yggdrasil is built around a few core ideas:
49
+
50
+ * **Branches are ideas**
51
+ * **Worktrees are realities**
52
+ * **Each task deserves its own realm**
53
+
54
+ Instead of constantly switching branches in one working directory, Yggdrasil creates **isolated worktrees**, each mapped to a branch, living outside your main repo.
55
+
56
+ All managed worktrees live under:
57
+
58
+ ```
59
+ ~/.yggtree/<repo-name>/<worktree-slug>
38
60
  ```
39
61
 
62
+ This keeps your main repository clean while enabling true parallelism.
63
+
64
+ ---
65
+
66
+ ## 🤔 Why Yggdrasil Worktree?
67
+
68
+ Git worktrees are powerful, but once you start doing **parallel work**, they become tedious to manage manually.
69
+
70
+ Modern development looks like this:
71
+
72
+ * Fixing a bug
73
+ * Reviewing a PR
74
+ * Prototyping a feature
75
+ * Letting AI agents explore refactors
76
+ * Running tests in isolation
77
+
78
+ All at the same time.
79
+
80
+ Yggdrasil exists to solve three problems together:
81
+
82
+ 1. Parallel work without context collision
83
+ 2. Fast, repeatable environment setup
84
+ 3. Agent‑friendly isolation for AI workflows
85
+
86
+ Each worktree becomes its own **small realm**, safe to explore and easy to discard.
87
+
40
88
  ---
41
89
 
42
90
  ## ✨ Key Features
43
91
 
44
- ### 🌿 Smart Branch Creation (`wt create`)
45
- The primary way to start working. Instead of worrying about folder names, just tell Yggdrasil which branch you want to work on.
46
- - **Auto-Slug**: Converts `feat/eng-123-ui` to a clean folder name like `feat-eng-123-ui`.
47
- - **Auto-Branching**: If the branch doesn't exist, Yggdrasil creates it for you from a base branch.
48
- - **Remote Awareness**: Seamlessly base your work on `origin/main` or local refs.
92
+ 🌳 **First-class worktree workflow**
93
+ Create, manage, and navigate Git worktrees as a primary workflow, not an afterthought.
94
+
95
+ 🧠 **Parallel development by default**
96
+ Work on multiple branches at the same time, each in its own isolated environment.
97
+
98
+ 🤖 **AI-friendly isolation**
99
+ One worktree per agent, per experiment, per idea. No shared state, no collisions.
100
+
101
+ ⚡ **Automatic bootstrapping**
102
+ Run installs, submodules, and setup scripts automatically for each worktree.
103
+
104
+ 🚪 **Enter, exec, and exit with ease**
105
+ Enter worktrees, execute commands, or run tasks without changing directories.
106
+
107
+ 📍 **Predictable structure**
108
+ All managed worktrees live under `~/.yggtree`, keeping your repository clean.
109
+
110
+ 🧭 **Interactive or scriptable**
111
+ Use the interactive UI or drive everything through commands and flags.
112
+
113
+ ---
114
+
115
+ ## 🧠 Parallel Development, Done Right
116
+
117
+ ```bash
118
+ yggtree wt create feat/eng-2581-state-selection
119
+ yggtree wt create fix/eng-2610-validation
120
+ yggtree wt create chore/cleanup-api
121
+ ```
122
+
123
+ Each command creates:
124
+
125
+ * A clean folder
126
+ * A dedicated branch
127
+ * A bootstrapped environment
128
+
129
+ No stash juggling.
130
+ No branch confusion.
131
+ No shared state accidents.
132
+
133
+ ---
134
+
135
+ ## 🤖 Built for AI‑Assisted Workflows
136
+
137
+ Yggdrasil shines when paired with AI agents.
138
+
139
+ Instead of running agents against the same directory, you can assign **one worktree per agent**.
140
+
141
+ ```bash
142
+ yggtree wt create feat/ai-refactor-a --exec "cursor ."
143
+ yggtree wt create feat/ai-refactor-b --exec "codex"
144
+ ```
145
+
146
+ Each agent operates in its own realm:
49
147
 
50
- ### 🌳 Batch Creation (`wt create-multi`)
51
- Need to spin up multiple features? Provide branch names separated by spaces, and Yggdrasil will provision all of them in one go.
148
+ * Model A refactors architecture
149
+ * Model B focuses on tests
150
+ * Model C explores performance
52
151
 
53
- ### 🚀 Custom Bootstrapping
54
- Configure your environment automatically using an `anvil-worktree.json` (also compatible with `.cursor/worktrees.json`) file in your project root.
152
+ All in parallel. All reviewable. All isolated.
55
153
 
56
154
  ---
57
155
 
58
- ## ⚙️ Configuration
156
+ ## Bootstrapping & Configuration
157
+
158
+ Yggdrasil automatically prepares each worktree.
159
+
160
+ Resolution order:
161
+
162
+ 1. `yggtree-worktree.json` inside the worktree
163
+ 2. `yggtree-worktree.json` in the repo root
164
+ 3. `.cursor/worktrees.json`
165
+ 4. Fallback: `npm install` + submodules
59
166
 
60
- Yggdrasil looks for setup instructions in your project root:
167
+ ### Example configuration
61
168
 
62
169
  ```json
63
170
  {
@@ -65,26 +172,268 @@ Yggdrasil looks for setup instructions in your project root:
65
172
  "npm install",
66
173
  "git submodule sync --recursive",
67
174
  "git submodule update --init --recursive",
68
- "npm run build",
69
- "echo '🌳 The realm is ready!'"
175
+ "echo \"🌳 Realm ready\""
70
176
  ]
71
177
  }
72
178
  ```
73
179
 
74
180
  ---
75
181
 
76
- ## 🛠️ Commands Reference
182
+ ## 🛠️ Command Reference
183
+
184
+ ### `yggtree`
185
+
186
+ Open the interactive menu.
187
+
188
+ ---
189
+
190
+ ### `yggtree wt create [branch]`
191
+
192
+ Create a worktree from a branch.
193
+
194
+ Options:
195
+
196
+ * `--base <ref>`
197
+ * `--source local|remote`
198
+ * `--no-bootstrap`
199
+ * `--enter / --no-enter`
200
+ * `--exec "<command>"`
201
+
202
+ <details>
203
+ <summary>Example</summary>
204
+
205
+ ```bash
206
+ yggtree wt create feat/new-ui --base main --exec "cursor ."
207
+ ```
208
+
209
+ </details>
210
+
211
+ ---
212
+
213
+ ### `yggtree wt create-multi`
214
+
215
+ Create multiple worktrees at once.
216
+
217
+ <details>
218
+ <summary>Example</summary>
219
+
220
+ ```bash
221
+ yggtree wt create-multi --base main
222
+ ```
223
+
224
+ </details>
225
+
226
+ ---
227
+
228
+ ### `yggtree wt list`
229
+
230
+ List all worktrees with state.
231
+
232
+ Columns:
233
+
234
+ * TYPE (MAIN / MANAGED)
235
+ * STATE (clean / dirty)
236
+ * BRANCH
237
+ * PATH
238
+
239
+ ---
240
+
241
+ ### `yggtree wt enter [worktree]`
242
+
243
+ Enter a worktree using a sub‑shell.
244
+
245
+ * Uses your default shell
246
+ * Type `exit` to return
247
+
248
+ Optional:
249
+
250
+ * `--exec "<command>"`
251
+
252
+ <details>
253
+ <summary>Example</summary>
254
+
255
+ ```bash
256
+ yggtree wt enter feat/new-ui --exec "npm test"
257
+ ```
258
+
259
+ </details>
260
+
261
+ ---
262
+
263
+ ### `yggtree wt exec [worktree] -- <command>`
264
+
265
+ Run a command inside a worktree **without entering**.
266
+
267
+ <details>
268
+ <summary>Example</summary>
269
+
270
+ ```bash
271
+ yggtree wt exec feat/new-ui -- npm test
272
+ ```
273
+
274
+ </details>
275
+
276
+ ---
277
+
278
+ ### `yggtree wt path [worktree]`
279
+
280
+ Print a `cd` command for a worktree.
281
+
282
+ Useful for scripting and shell aliases.
283
+
284
+ ---
285
+
286
+ ### `yggtree wt bootstrap`
287
+
288
+ Re‑run bootstrap commands for a worktree.
289
+
290
+ ---
291
+
292
+ ### `yggtree wt delete`
293
+
294
+ Interactively delete managed worktrees.
295
+
296
+ ---
297
+
298
+ ### `yggtree wt prune`
299
+
300
+ Clean up stale git worktree metadata.
301
+
302
+ ---
303
+
304
+ ## 🌱 When Should You Use Yggdrasil?
305
+
306
+ Yggdrasil is ideal when:
307
+
308
+ * You work on multiple tasks in parallel
309
+ * You use AI agents for exploration
310
+ * You want isolation without duplication
311
+ * You value scripted, repeatable setups
312
+ * `git checkout` no longer scales
313
+
314
+ ---
315
+
316
+ ## 📝 Practical Examples
317
+
318
+ <details>
319
+ <summary>Create a worktree and enter it immediately</summary>
320
+
321
+ **Command:**
322
+
323
+ ```
324
+ yggtree wt create feat/login-flow
325
+ ```
326
+
327
+ **What happens:**
328
+
329
+ * Creates a new branch if it doesn’t exist
330
+ * Creates a dedicated worktree
331
+ * Runs bootstrap if enabled
332
+ * Drops you into a sub-shell inside the worktree
333
+
334
+ </details>
335
+ ---
336
+
337
+ <details>
338
+ <summary>Create a worktree without bootstrap and without entering</summary>
339
+
340
+ **Command:**
341
+
342
+ ```
343
+ yggtree wt create feat/cleanup-api --no-bootstrap --no-enter
344
+ ```
345
+
346
+ **When to use:**
347
+
348
+ * You just want the folder ready
349
+ * You’ll enter it later
350
+ * You don’t want installs running automatically
351
+
352
+ </details>
353
+ ---
354
+
355
+ <details>
356
+ <summary>Create a worktree and open it in your IDE</summary>
357
+
358
+ **Command:**
359
+
360
+ ```
361
+ yggtree wt create feat/ui-refactor --exec "cursor ."
362
+ ```
363
+
364
+ Works with:
365
+
366
+ * `cursor .`
367
+ * `code .`
368
+ * `codex`
369
+ * Any custom command available in your shell
370
+
371
+ </details>
372
+ ---
373
+
374
+ <details>
375
+ <summary>Execute a command inside an existing worktree (no shell)</summary>
376
+
377
+ **Command:**
378
+
379
+ ```
380
+ yggtree wt exec test -- npm test
381
+ ```
382
+
383
+ **What this does:**
384
+
385
+ * Runs the command inside the selected worktree
386
+ * Does not enter a sub-shell
387
+ * Ideal for CI-like checks, scripts, or quick validations
388
+
389
+ </details>
390
+ ---
391
+
392
+ <details>
393
+ <summary>Enter a worktree and run a command before entering</summary>
394
+
395
+ **Command:**
396
+
397
+ ```
398
+ yggtree wt enter test --exec "codex"
399
+ ```
400
+
401
+ **What happens:**
402
+
403
+ * Executes the command inside the worktree
404
+ * Then drops you into a sub-shell
405
+ * Type `exit` to return to your original directory
406
+
407
+ </details>
408
+ ---
409
+
410
+ <details>
411
+ <summary>Get the path to a worktree</summary>
412
+
413
+ **Command:**
414
+
415
+ ```
416
+ yggtree wt path test
417
+ ```
418
+
419
+ **Output:**
420
+
421
+ ```
422
+ cd ~/.yggtree/your-repo-name/test
423
+ ```
424
+
425
+ Useful when you want to manually navigate or copy the path into scripts.
426
+
427
+ </details>
428
+
429
+ ---
430
+
431
+ ## 🌍 Philosophy
432
+
433
+ Branches are ideas.
434
+ Worktrees are realities.
77
435
 
78
- | Command | Description |
79
- | :--- | :--- |
80
- | `yggtree` | Open the interactive main menu. |
81
- | `yggtree wt create` | Create a worktree by branch name (Recommended). |
82
- | `yggtree wt create-multi` | Create multiple worktrees in a single command. |
83
- | `yggtree wt create-slug` | Manually specify both folder name and branch ref. |
84
- | `yggtree wt list` | List all managed worktrees and their status. |
85
- | `yggtree wt delete` | Interactively select and remove a worktree. |
86
- | `yggtree wt bootstrap` | Re-run the setup commands for an existing worktree. |
87
- | `yggtree wt prune` | Clean up Git's internal data for worktrees. |
436
+ Yggdrasil helps you grow many worlds and decide later which ones deserve to merge.
88
437
 
89
438
  ---
90
439
 
@@ -17,10 +17,13 @@ export async function bootstrapCommand() {
17
17
  log.info('No managed worktrees found to bootstrap.');
18
18
  return;
19
19
  }
20
- const choices = managedWts.map(wt => ({
21
- name: `${chalk.bold(path.basename(wt.path))} (${chalk.dim(wt.branch || wt.HEAD)})`,
22
- value: wt.path,
23
- }));
20
+ const choices = managedWts.map(wt => {
21
+ const relative = path.relative(WORKTREES_ROOT, wt.path);
22
+ return {
23
+ name: `${chalk.bold(relative)} (${chalk.dim(wt.branch || wt.HEAD)})`,
24
+ value: wt.path,
25
+ };
26
+ });
24
27
  const { selectedPath } = await inquirer.prompt([
25
28
  {
26
29
  type: 'list',
@@ -35,7 +38,7 @@ export async function bootstrapCommand() {
35
38
  log.success('Bootstrap completed!');
36
39
  }
37
40
  catch (error) {
38
- log.error(error.message);
41
+ log.actionableError(error.message, 'yggtree wt bootstrap');
39
42
  process.exit(1);
40
43
  }
41
44
  }
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
3
  import path from 'path';
4
- import { getRepoRoot, verifyRef, fetchAll, getCurrentBranch } from '../../lib/git.js';
4
+ import { getRepoRoot, getRepoName, verifyRef, fetchAll, getCurrentBranch } from '../../lib/git.js';
5
5
  import { runBootstrap } from '../../lib/config.js';
6
6
  import { WORKTREES_ROOT } from '../../lib/paths.js';
7
7
  import { log, ui, createSpinner } from '../../lib/ui.js';
@@ -41,37 +41,45 @@ export async function createCommandNew(options) {
41
41
  { name: 'Local', value: 'local' },
42
42
  ],
43
43
  default: 'remote',
44
- when: !options.base,
44
+ when: !options.base && !options.source,
45
45
  },
46
46
  {
47
47
  type: 'confirm',
48
48
  name: 'bootstrap',
49
49
  message: 'Run bootstrap? (npm install + submodules)',
50
50
  default: true,
51
- when: options.bootstrap !== false,
51
+ when: options.bootstrap !== false && options.bootstrap !== true,
52
+ },
53
+ {
54
+ type: 'confirm',
55
+ name: 'shouldEnter',
56
+ message: 'Do you want to enter the new worktree now?',
57
+ default: true,
58
+ when: options.enter === undefined,
52
59
  },
60
+ {
61
+ type: 'input',
62
+ name: 'exec',
63
+ message: 'Command to run after creation (optional):',
64
+ default: options.exec,
65
+ when: options.exec === undefined,
66
+ }
53
67
  ]);
54
- let shouldEnter = false;
55
- if (!options.branch) {
56
- const finalAnswer = await inquirer.prompt([{
57
- type: 'confirm',
58
- name: 'shouldEnter',
59
- message: 'Do you want to enter the new worktree now?',
60
- default: true
61
- }]);
62
- shouldEnter = finalAnswer.shouldEnter;
63
- }
64
68
  const branchName = options.branch || answers.branch;
65
69
  let baseRef = options.base || answers.base;
70
+ const source = options.source || answers.source;
71
+ const shouldEnter = options.enter !== undefined ? options.enter : answers.shouldEnter;
72
+ const shouldBootstrap = options.bootstrap !== undefined ? options.bootstrap : answers.bootstrap;
73
+ const execCommandStr = options.exec || answers.exec;
66
74
  // Append origin/ if remote is selected and not already present
67
- if (!options.base && answers.source === 'remote' && !baseRef.startsWith('origin/')) {
75
+ if (!options.base && source === 'remote' && !baseRef.startsWith('origin/')) {
68
76
  baseRef = `origin/${baseRef}`;
69
77
  }
70
- const shouldBootstrap = options.bootstrap === false ? false : answers.bootstrap;
71
78
  // Convert branch name to slug (friendly folder name)
72
79
  // e.g. feat/eng-2222-new-button -> feat-eng-2222-new-button
73
80
  const slug = branchName.replace(/[\/\\]/g, '-').replace(/\s+/g, '-');
74
- const wtPath = path.join(WORKTREES_ROOT, slug);
81
+ const repoName = await getRepoName();
82
+ const wtPath = path.join(WORKTREES_ROOT, repoName, slug);
75
83
  // 2. Validation
76
84
  if (!slug)
77
85
  throw new Error('Invalid name');
@@ -106,14 +114,38 @@ export async function createCommandNew(options) {
106
114
  }
107
115
  catch (e) {
108
116
  spinner.fail('Failed to create worktree.');
109
- log.error(e.message);
117
+ const cmd = targetBranchExists
118
+ ? `git worktree add ${wtPath} ${branchName}`
119
+ : `git worktree add -b ${branchName} ${wtPath} ${baseRef}`;
120
+ log.actionableError(e.message, cmd, wtPath, [
121
+ 'Check if the folder already exists: ls ' + wtPath,
122
+ 'Check if the branch is already used: git worktree list',
123
+ 'Try pruning stale worktrees: yggtree wt prune',
124
+ `Run manually: ${cmd}`
125
+ ]);
110
126
  return;
111
127
  }
112
- // 4. Bootstrap
113
128
  if (shouldBootstrap) {
114
129
  await runBootstrap(wtPath, repoRoot);
115
130
  }
116
- // 5. Final Output
131
+ // 5. Exec Command
132
+ if (execCommandStr && execCommandStr.trim()) {
133
+ log.info(`Executing: ${execCommandStr} in ${ui.path(wtPath)}`);
134
+ try {
135
+ await execa(execCommandStr, {
136
+ cwd: wtPath,
137
+ stdio: 'inherit',
138
+ shell: true
139
+ });
140
+ }
141
+ catch (error) {
142
+ log.actionableError(error.message, execCommandStr, wtPath, [
143
+ `cd ${wtPath} && ${execCommandStr}`,
144
+ 'Check your command syntax and environment variables'
145
+ ]);
146
+ }
147
+ }
148
+ // 6. Final Output
117
149
  log.success('Worktree ready!');
118
150
  if (shouldEnter) {
119
151
  log.info(`Spawning sub-shell in ${ui.path(wtPath)}...`);
@@ -132,7 +164,7 @@ export async function createCommandNew(options) {
132
164
  }
133
165
  }
134
166
  catch (error) {
135
- log.error(error.message);
167
+ log.actionableError(error.message, 'yggtree wt create');
136
168
  process.exit(1);
137
169
  }
138
170
  }