yggtree 1.0.0 → 1.0.1

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,5 +1,8 @@
1
1
  # 🌳 Yggdrasil Worktree (yggtree)
2
2
 
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
+
3
6
  **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.
4
7
 
5
8
  ---
@@ -8,17 +11,16 @@
8
11
 
9
12
  ### Installation
10
13
 
14
+ You can run it directly without installing:
15
+
11
16
  ```bash
12
- # Clone the repository
13
- git clone https://github.com/leoreisdias/yggtree.git
14
- cd yggtree
17
+ npx yggtree
18
+ ```
15
19
 
16
- # Install dependencies and build
17
- npm install
18
- npm run build
20
+ Or install it globally for convenience:
19
21
 
20
- # Link the CLI globally
21
- npm link
22
+ ```bash
23
+ npm install -g yggtree
22
24
  ```
23
25
 
24
26
  ### Usage
@@ -53,6 +55,140 @@ Need to spin up multiple features? Provide branch names separated by spaces, and
53
55
  ### 🚀 Custom Bootstrapping
54
56
  Configure your environment automatically using an `anvil-worktree.json` (also compatible with `.cursor/worktrees.json`) file in your project root.
55
57
 
58
+ ---
59
+
60
+ ## 🤔 Why Yggdrasil Worktree?
61
+
62
+ Git worktrees are incredibly powerful, but they are also **painfully manual** once you go beyond a single task.
63
+
64
+ Modern development isn’t sequential anymore. You’re constantly switching contexts, juggling experiments, fixes, reviews, and now **AI agents running in parallel**.
65
+
66
+ Yggdrasil exists to solve three modern problems at once:
67
+
68
+ 1. **Parallel work without context collision**
69
+ 2. **Fast environment setup per task**
70
+ 3. **Agent-friendly isolation for AI workflows**
71
+
72
+ Instead of juggling branches, folders, and bootstrap scripts by hand, Yggdrasil gives you a repeatable, intentional workflow.
73
+ Each worktree becomes its own **small realm** — isolated, focused, and safe to explore.
74
+
75
+ ---
76
+
77
+ ## 🧠 Parallel Development, Done Right
78
+
79
+ Traditional branching assumes *sequential* work.
80
+
81
+ Reality looks more like this:
82
+
83
+ * You’re fixing a bug
84
+ * Reviewing a PR
85
+ * Prototyping a feature
86
+ * Letting an AI agent explore refactors
87
+ * Letting another agent generate tests
88
+
89
+ All at the same time.
90
+
91
+ Yggdrasil lets each task live in its **own isolated worktree** — a separate realm where ideas can evolve without colliding.
92
+
93
+ ```bash
94
+ yggtree wt create feat/eng-2581-state-selection
95
+ yggtree wt create fix/eng-2610-validation
96
+ yggtree wt create chore/cleanup-api
97
+ ```
98
+
99
+ Each command spins up:
100
+
101
+ * A clean folder
102
+ * A dedicated branch
103
+ * A fully bootstrapped environment
104
+
105
+ No context bleeding.
106
+ No stash juggling.
107
+ No “what branch am I on?” moments.
108
+
109
+ ---
110
+
111
+ ## 🤖 Built for AI-Assisted Workflows
112
+
113
+ Yggdrasil shines when combined with AI agents.
114
+
115
+ Instead of running agents against the same working directory, you can:
116
+
117
+ * Assign one worktree per agent
118
+ * Let each agent explore independently
119
+ * Compare results safely
120
+ * Merge only what makes sense
121
+
122
+ Example workflow:
123
+
124
+ ```bash
125
+ # Prepare isolated worktrees
126
+ yggtree wt create feat/ai-refactor-a
127
+ yggtree wt create feat/ai-refactor-b
128
+ ```
129
+
130
+ Each agent operates in its own realm, free to experiment without side effects.
131
+
132
+ This enables patterns like:
133
+
134
+ * Model A refactors architecture
135
+ * Model B focuses on tests
136
+ * Model C explores performance improvements
137
+
138
+ All in parallel.
139
+ All reviewable.
140
+ All under your control.
141
+
142
+ ---
143
+
144
+ ## ⚡ Zero-Friction Bootstrapping
145
+
146
+ Every worktree can auto-configure itself.
147
+
148
+ Yggdrasil reads your project’s setup file and prepares the realm immediately:
149
+
150
+ ```json
151
+ {
152
+ "setup-worktree": [
153
+ "npm install",
154
+ "git submodule sync --recursive",
155
+ "git submodule update --init --recursive"
156
+ ]
157
+ }
158
+ ```
159
+
160
+ That means:
161
+
162
+ * No manual installs
163
+ * No forgotten submodules
164
+ * No “works on main but not here” surprises
165
+
166
+ You enter a worktree and it’s ready to work.
167
+
168
+ ---
169
+
170
+ ## 🌱 When Should You Use Yggdrasil?
171
+
172
+ Yggdrasil is ideal when:
173
+
174
+ * You work on multiple tasks in parallel
175
+ * You use AI agents for exploration or execution
176
+ * You want isolation without duplication
177
+ * You value repeatable, scripted setup
178
+ * You’re tired of fighting your own repo
179
+
180
+ If your workflow has outgrown `git checkout`, Yggdrasil is the missing layer.
181
+
182
+ ---
183
+
184
+ ## 🌍 Philosophy
185
+
186
+ Branches are ideas.
187
+ Worktrees are realities.
188
+
189
+ Yggdrasil helps you keep each idea grounded in its own world — until you decide which ones deserve to merge.
190
+
191
+
56
192
  ---
57
193
 
58
194
  ## ⚙️ Configuration
@@ -88,6 +224,23 @@ Yggdrasil looks for setup instructions in your project root:
88
224
 
89
225
  ---
90
226
 
227
+ ## 🛠️ Development
228
+
229
+ If you'd like to contribute or run the latest development version:
230
+
231
+ ```bash
232
+ # Clone the repository
233
+ git clone https://github.com/leoreisdias/yggdrasil-cli.git
234
+ cd yggdrasil-cli
235
+
236
+ # Install dependencies and build
237
+ npm install
238
+ npm run build
239
+
240
+ # Link the CLI locally
241
+ npm link
242
+ ```
243
+
91
244
  ## 📄 License
92
245
 
93
246
  MIT License.
@@ -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',
@@ -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';
@@ -71,7 +71,8 @@ export async function createCommandNew(options) {
71
71
  // Convert branch name to slug (friendly folder name)
72
72
  // e.g. feat/eng-2222-new-button -> feat-eng-2222-new-button
73
73
  const slug = branchName.replace(/[\/\\]/g, '-').replace(/\s+/g, '-');
74
- const wtPath = path.join(WORKTREES_ROOT, slug);
74
+ const repoName = await getRepoName();
75
+ const wtPath = path.join(WORKTREES_ROOT, repoName, slug);
75
76
  // 2. Validation
76
77
  if (!slug)
77
78
  throw new Error('Invalid name');
@@ -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';
@@ -66,9 +66,10 @@ export async function createCommandMulti(options) {
66
66
  spinner.succeed(`Base ref ${chalk.cyan(baseRef)} verified.`);
67
67
  const createdWorktrees = [];
68
68
  // 3. Execution for each branch
69
+ const repoName = await getRepoName();
69
70
  for (const branchName of branchNames) {
70
71
  const slug = branchName.replace(/[\/\\]/g, '-').replace(/\s+/g, '-');
71
- const wtPath = path.join(WORKTREES_ROOT, slug);
72
+ const wtPath = path.join(WORKTREES_ROOT, repoName, slug);
72
73
  log.header(`Processing: ${branchName}`);
73
74
  const wtSpinner = createSpinner(`Creating worktree at ${ui.path(wtPath)}...`).start();
74
75
  try {
@@ -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, createWorktree, fetchAll, getCurrentBranch } from '../../lib/git.js';
4
+ import { getRepoRoot, getRepoName, verifyRef, createWorktree, 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';
@@ -68,7 +68,8 @@ export async function createCommand(options) {
68
68
  }
69
69
  const shouldBootstrap = options.bootstrap === false ? false : answers.bootstrap;
70
70
  const slug = name.replace(/\s+/g, '-');
71
- const wtPath = path.join(WORKTREES_ROOT, slug);
71
+ const repoName = await getRepoName();
72
+ const wtPath = path.join(WORKTREES_ROOT, repoName, slug);
72
73
  // 2. Validation
73
74
  if (!slug)
74
75
  throw new Error('Invalid name');
@@ -14,10 +14,13 @@ export async function deleteCommand() {
14
14
  log.info('No managed worktrees found to delete.');
15
15
  return;
16
16
  }
17
- const choices = managedWts.map(wt => ({
18
- name: `${chalk.bold(path.basename(wt.path))} (${chalk.dim(wt.branch || wt.HEAD)})`,
19
- value: wt.path,
20
- }));
17
+ const choices = managedWts.map(wt => {
18
+ const relative = path.relative(WORKTREES_ROOT, wt.path);
19
+ return {
20
+ name: `${chalk.bold(relative)} (${chalk.dim(wt.branch || wt.HEAD)})`,
21
+ value: wt.path,
22
+ };
23
+ });
21
24
  const { selectedPath } = await inquirer.prompt([
22
25
  {
23
26
  type: 'list',
@@ -26,7 +29,7 @@ export async function deleteCommand() {
26
29
  choices: choices,
27
30
  },
28
31
  ]);
29
- const worktreeName = path.basename(selectedPath);
32
+ const worktreeName = path.relative(WORKTREES_ROOT, selectedPath);
30
33
  const { confirm } = await inquirer.prompt([
31
34
  {
32
35
  type: 'input',
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import path from 'path';
2
3
  import { listWorktrees, getRepoRoot } from '../../lib/git.js';
3
4
  import { WORKTREES_ROOT } from '../../lib/paths.js';
4
5
  import { log } from '../../lib/ui.js';
@@ -19,7 +20,10 @@ export async function listCommand() {
19
20
  const isMain = !isManaged; // Simplification: assume main repo is not in managed dir
20
21
  const type = isManaged ? chalk.green('MANAGED') : chalk.blue('MAIN ');
21
22
  const branchName = wt.branch || wt.HEAD || 'detached';
22
- const displayPath = wt.path.replace(process.env.HOME || '', '~');
23
+ let displayPath = wt.path.replace(process.env.HOME || '', '~');
24
+ if (isManaged) {
25
+ displayPath = path.relative(WORKTREES_ROOT, wt.path);
26
+ }
23
27
  const colorPath = isManaged ? chalk.cyan(displayPath) : chalk.dim(displayPath);
24
28
  console.log(` ${type} ${chalk.yellow(branchName.padEnd(18))} ${colorPath}`);
25
29
  }
package/dist/lib/git.js CHANGED
@@ -10,6 +10,10 @@ export async function getRepoRoot() {
10
10
  throw new Error('Not a git repository');
11
11
  }
12
12
  }
13
+ export async function getRepoName() {
14
+ const root = await getRepoRoot();
15
+ return path.basename(root);
16
+ }
13
17
  export async function getCurrentBranch() {
14
18
  try {
15
19
  const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
package/dist/lib/paths.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import os from 'os';
2
2
  import path from 'path';
3
- export const ANVIL_ROOT = path.join(os.homedir(), '.yggdrasil');
4
- export const WORKTREES_ROOT = path.join(ANVIL_ROOT, 'worktrees');
3
+ export const YGG_ROOT = path.join(os.homedir(), '.yggtree');
4
+ export const WORKTREES_ROOT = YGG_ROOT;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yggtree",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Interactive CLI for managing git worktrees and configs",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",