yggtree 1.2.0 β†’ 1.3.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
@@ -118,8 +118,8 @@ Use the interactive UI or drive everything through commands and flags.
118
118
  ## 🧠 Parallel Development, Done Right
119
119
 
120
120
  ```bash
121
- yggtree wt create feat/eng-2581-state-selection
122
- yggtree wt create fix/eng-2610-validation
121
+ yggtree wt create feat/city-selection
122
+ yggtree wt create fix/validation
123
123
  yggtree wt create chore/cleanup-api
124
124
  ```
125
125
 
@@ -373,11 +373,12 @@ yggtree wt create feat/login-flow
373
373
 
374
374
  **What happens:**
375
375
 
376
- * Creates a new branch if it doesn’t exist
376
+ * Creates a new branch if it doesn’t exist (without inheriting base tracking), then publishes it to `origin` when possible
377
377
  * Creates a dedicated worktree
378
378
  * Runs bootstrap if enabled
379
379
  * Drops you into a sub-shell inside the worktree
380
380
 
381
+
381
382
  </details>
382
383
  ---
383
384
 
@@ -4,7 +4,7 @@ import path from 'path';
4
4
  import fs from 'fs-extra';
5
5
  import { execa } from 'execa';
6
6
  import { log, ui, createSpinner } from '../../lib/ui.js';
7
- import { findSandboxRoot, readSandboxMeta, writeSandboxMeta } from '../../lib/sandbox.js';
7
+ import { findSandboxRoot, readSandboxMeta, writeSandboxMeta, YGGTREE_DIR } from '../../lib/sandbox.js';
8
8
  export async function applyCommand() {
9
9
  try {
10
10
  const cwd = process.cwd();
@@ -35,6 +35,12 @@ export async function applyCommand() {
35
35
  ...stagedFiles.split('\n').filter(Boolean),
36
36
  ...untrackedFiles.split('\n').filter(Boolean)
37
37
  ]);
38
+ // Exclude internal .yggtree/ directory (config, sandbox metadata, etc.)
39
+ for (const file of allChanges) {
40
+ if (file.startsWith(`${YGGTREE_DIR}/`)) {
41
+ allChanges.delete(file);
42
+ }
43
+ }
38
44
  changedFiles = [...allChanges];
39
45
  }
40
46
  catch (e) {
@@ -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, getRepoName, verifyRef, fetchAll, getCurrentBranch, ensureCorrectUpstream } from '../../lib/git.js';
4
+ import { getRepoRoot, getRepoName, verifyRef, fetchAll, getCurrentBranch, ensureCorrectUpstream, publishBranch } 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';
@@ -76,7 +76,7 @@ export async function createCommandNew(options) {
76
76
  baseRef = `origin/${baseRef}`;
77
77
  }
78
78
  // Convert branch name to slug (friendly folder name)
79
- // e.g. feat/eng-2222-new-button -> feat-eng-2222-new-button
79
+ // e.g. feat/new-button -> feat-new-button
80
80
  const slug = branchName.replace(/[\/\\]/g, '-').replace(/\s+/g, '-');
81
81
  const repoName = await getRepoName();
82
82
  const wtPath = path.join(WORKTREES_ROOT, repoName, slug);
@@ -98,24 +98,23 @@ export async function createCommandNew(options) {
98
98
  spinner.text = `Creating worktree at ${ui.path(wtPath)}...`;
99
99
  // Check if target branch already exists
100
100
  const targetBranchExists = await verifyRef(branchName);
101
- // If branch doesn't exist, we create it from base
102
- // If it does exist, we just check it out
103
- const createBranchFlag = targetBranchExists ? '' : `-b ${branchName}`;
104
101
  try {
105
102
  await fs.ensureDir(path.dirname(wtPath));
106
- // slightly different logic for creating new branch vs existing
107
103
  if (targetBranchExists) {
104
+ // Branch exists β€” just attach the worktree
108
105
  await execa('git', ['worktree', 'add', wtPath, branchName]);
109
106
  }
110
107
  else {
111
- await execa('git', ['worktree', 'add', '-b', branchName, wtPath, baseRef]);
108
+ // Create branch WITHOUT tracking the base, then attach worktree
109
+ await execa('git', ['branch', '--no-track', branchName, baseRef]);
110
+ await execa('git', ['worktree', 'add', wtPath, branchName]);
112
111
  }
113
112
  }
114
113
  catch (e) {
115
114
  spinner.fail('Failed to create worktree.');
116
115
  const cmd = targetBranchExists
117
116
  ? `git worktree add ${wtPath} ${branchName}`
118
- : `git worktree add -b ${branchName} ${wtPath} ${baseRef}`;
117
+ : `git branch --no-track ${branchName} ${baseRef} && git worktree add ${wtPath} ${branchName}`;
119
118
  log.actionableError(e.message, cmd, wtPath, [
120
119
  'Check if the folder already exists: ls ' + wtPath,
121
120
  'Check if the branch is already used: git worktree list',
@@ -124,20 +123,21 @@ export async function createCommandNew(options) {
124
123
  ]);
125
124
  return;
126
125
  }
126
+ // Safety net: ensure no incorrect upstream was inherited
127
+ spinner.text = 'Verifying upstream safety...';
128
+ await ensureCorrectUpstream(wtPath, branchName);
129
+ // Auto-publish: push to origin and set correct tracking
127
130
  try {
128
- // Strong Safety Mode: Ensure upstream is origin/<branchName> and publish
129
- spinner.text = 'Safely publishing branch...';
130
- await ensureCorrectUpstream(wtPath, branchName);
131
+ spinner.text = 'Publishing branch...';
132
+ await publishBranch(wtPath, branchName);
131
133
  spinner.succeed('Worktree created and branch published.');
132
134
  }
133
135
  catch (e) {
134
- spinner.fail('Worktree created, but branch publication failed.');
136
+ spinner.succeed('Worktree created (publish failed β€” push manually later).');
135
137
  log.actionableError(e.message, 'git push -u origin HEAD', wtPath, [
136
138
  `cd ${wtPath}`,
137
139
  'Attempt to push manually: git push -u origin HEAD',
138
- 'Check if the remote branch already exists or if you have push permissions'
139
140
  ]);
140
- // We don't return here because the worktree IS created, we just failed to publish
141
141
  }
142
142
  if (shouldBootstrap) {
143
143
  await runBootstrap(wtPath, repoRoot);
@@ -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, getRepoName, verifyRef, fetchAll, getCurrentBranch, ensureCorrectUpstream } from '../../lib/git.js';
4
+ import { getRepoRoot, getRepoName, verifyRef, fetchAll, getCurrentBranch, ensureCorrectUpstream, publishBranch } 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';
@@ -82,14 +82,16 @@ export async function createCommandMulti(options) {
82
82
  await execa('git', ['worktree', 'add', wtPath, branchName]);
83
83
  }
84
84
  else {
85
- await execa('git', ['worktree', 'add', '-b', branchName, wtPath, baseRef]);
85
+ // Create branch WITHOUT tracking the base, then attach worktree
86
+ await execa('git', ['branch', '--no-track', branchName, baseRef]);
87
+ await execa('git', ['worktree', 'add', wtPath, branchName]);
86
88
  }
87
89
  }
88
90
  catch (error) {
89
91
  wtSpinner.fail(`Failed to create worktree for ${branchName}.`);
90
92
  const cmd = targetBranchExists
91
93
  ? `git worktree add ${wtPath} ${branchName}`
92
- : `git worktree add -b ${branchName} ${wtPath} ${baseRef}`;
94
+ : `git branch --no-track ${branchName} ${baseRef} && git worktree add ${wtPath} ${branchName}`;
93
95
  log.actionableError(error.message, cmd, wtPath, [
94
96
  'Check if the folder already exists: ls ' + wtPath,
95
97
  'Check if the branch is already used: git worktree list',
@@ -98,22 +100,23 @@ export async function createCommandMulti(options) {
98
100
  ]);
99
101
  continue;
100
102
  }
103
+ // Safety net: ensure no incorrect upstream was inherited
104
+ wtSpinner.text = `Verifying upstream safety for ${branchName}...`;
105
+ await ensureCorrectUpstream(wtPath, branchName);
106
+ // Auto-publish: push to origin and set correct tracking
101
107
  try {
102
- // Strong Safety Mode: Ensure upstream is origin/<branchName> and publish
103
- wtSpinner.text = `Safely publishing branch ${branchName}...`;
104
- await ensureCorrectUpstream(wtPath, branchName);
108
+ wtSpinner.text = `Publishing branch ${branchName}...`;
109
+ await publishBranch(wtPath, branchName);
105
110
  wtSpinner.succeed(`Worktree for ${chalk.cyan(branchName)} created and published.`);
106
- createdWorktrees.push(wtPath);
107
111
  }
108
112
  catch (error) {
109
- wtSpinner.fail(`Worktree for ${branchName} created, but publication failed.`);
113
+ wtSpinner.succeed(`Worktree for ${chalk.cyan(branchName)} created (publish failed β€” push manually later).`);
110
114
  log.actionableError(error.message, 'git push -u origin HEAD', wtPath, [
111
115
  `cd ${wtPath}`,
112
116
  'Attempt to push manually: git push -u origin HEAD',
113
- 'Check if the remote branch already exists or if you have push permissions'
114
117
  ]);
115
- createdWorktrees.push(wtPath); // Still added to list since wt exists
116
118
  }
119
+ createdWorktrees.push(wtPath);
117
120
  // 4. Bootstrap
118
121
  if (shouldBootstrap) {
119
122
  await runBootstrap(wtPath, repoRoot);
@@ -1,9 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
3
  import path from 'path';
4
- import { listWorktrees, removeWorktree, getRepoRoot } from '../../lib/git.js';
4
+ import { listWorktrees, removeWorktree, getRepoRoot, getLastActivity } from '../../lib/git.js';
5
5
  import { WORKTREES_ROOT } from '../../lib/paths.js';
6
- import { log, createSpinner } from '../../lib/ui.js';
6
+ import { log, createSpinner, timeAgo } from '../../lib/ui.js';
7
7
  export async function deleteCommand() {
8
8
  try {
9
9
  const _ = await getRepoRoot();
@@ -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
- const relative = path.relative(WORKTREES_ROOT, wt.path);
17
+ // Pre-fetch activity for all managed worktrees in parallel
18
+ const activities = await Promise.all(managedWts.map(wt => getLastActivity(wt.path)));
19
+ const choices = managedWts.map((wt, i) => {
20
+ const branchName = wt.branch || wt.HEAD || 'detached';
21
+ const active = activities[i] ? chalk.magenta(timeAgo(activities[i])) : chalk.dim('β€”');
19
22
  return {
20
- name: `${chalk.bold(relative)} (${chalk.dim(wt.branch || wt.HEAD)})`,
23
+ name: `${chalk.bold.yellow(branchName)} ${chalk.dim('Β·')} ${active}`,
21
24
  value: wt.path,
22
25
  };
23
26
  });
@@ -1,8 +1,7 @@
1
1
  import chalk from 'chalk';
2
- import path from 'path';
3
- import { listWorktrees, getRepoRoot, isGitClean } from '../../lib/git.js';
2
+ import { listWorktrees, getRepoRoot, isGitClean, getLastActivity } from '../../lib/git.js';
4
3
  import { WORKTREES_ROOT } from '../../lib/paths.js';
5
- import { log } from '../../lib/ui.js';
4
+ import { log, timeAgo } from '../../lib/ui.js';
6
5
  export async function listCommand() {
7
6
  try {
8
7
  const _ = await getRepoRoot(); // Verify we are in a git repo
@@ -13,21 +12,22 @@ export async function listCommand() {
13
12
  }
14
13
  console.log(chalk.bold('\n Active Worktrees:\n'));
15
14
  // Header
16
- console.log(` ${chalk.dim('TYPE')} ${chalk.dim('STATE')} ${chalk.dim('BRANCH')} ${chalk.dim('PATH')}`);
17
- console.log(chalk.dim(' ' + '-'.repeat(75)));
15
+ console.log(` ${chalk.dim('TYPE')} ${chalk.dim('STATE')} ${chalk.dim('LAST ACTIVE')} ${chalk.dim('BRANCH')}`);
16
+ console.log(chalk.dim(' ' + '-'.repeat(70)));
18
17
  for (const wt of worktrees) {
19
18
  const isManaged = wt.path.startsWith(WORKTREES_ROOT);
20
19
  const type = isManaged ? chalk.green('MANAGED') : chalk.blue('MAIN ');
21
20
  const branchName = wt.branch || wt.HEAD || 'detached';
22
- let displayPath = wt.path.replace(process.env.HOME || '', '~');
23
- if (isManaged) {
24
- displayPath = path.relative(WORKTREES_ROOT, wt.path);
25
- }
26
- const colorPath = isManaged ? chalk.cyan(displayPath) : chalk.dim(displayPath);
27
- const isClean = await isGitClean(wt.path);
21
+ // Fetch state and activity in parallel
22
+ const [isClean, lastActive] = await Promise.all([
23
+ isGitClean(wt.path),
24
+ getLastActivity(wt.path),
25
+ ]);
28
26
  const stateLabel = (isClean ? 'clean' : 'dirty').padEnd(8);
29
27
  const stateText = isClean ? chalk.green(stateLabel) : chalk.yellow(stateLabel);
30
- console.log(` ${type} ${stateText} ${chalk.yellow(branchName.padEnd(18))} ${colorPath}`);
28
+ const activeLabel = lastActive ? timeAgo(lastActive) : 'β€”';
29
+ const activeText = chalk.magenta(activeLabel.padEnd(14));
30
+ console.log(` ${type} ${stateText} ${activeText} ${chalk.yellow(branchName)}`);
31
31
  }
32
32
  console.log('');
33
33
  }
@@ -3,13 +3,26 @@ import fs from 'fs-extra';
3
3
  import { execa } from 'execa';
4
4
  import { log, createSpinner } from './ui.js';
5
5
  export async function getBootstrapCommands(repoRoot, wtPath) {
6
- const searchPaths = [];
7
- if (wtPath)
6
+ // repoRoot first (source of truth β€” where yggtree is run from)
7
+ // wtPath second (per-worktree override if needed)
8
+ const searchPaths = [repoRoot];
9
+ if (wtPath && wtPath !== repoRoot)
8
10
  searchPaths.push(wtPath);
9
- searchPaths.push(repoRoot);
10
11
  for (const searchPath of searchPaths) {
12
+ const yggtreeConfigPath = path.join(searchPath, '.yggtree', 'worktree-setup.json');
11
13
  const configPath = path.join(searchPath, 'yggtree-worktree.json');
12
14
  const cursorConfigPath = path.join(searchPath, '.cursor', 'worktrees.json');
15
+ if (await fs.pathExists(yggtreeConfigPath)) {
16
+ try {
17
+ const config = await fs.readJSON(yggtreeConfigPath);
18
+ if (config['setup-worktree'] && Array.isArray(config['setup-worktree'])) {
19
+ return config['setup-worktree'];
20
+ }
21
+ }
22
+ catch (e) {
23
+ log.warning(`Failed to parse ${yggtreeConfigPath}.`);
24
+ }
25
+ }
13
26
  if (await fs.pathExists(configPath)) {
14
27
  try {
15
28
  const config = await fs.readJSON(configPath);
@@ -50,7 +63,7 @@ export async function runBootstrap(wtPath, repoRoot) {
50
63
  spinner.fail(`Failed: ${cmd}`);
51
64
  log.actionableError(e.message, cmd, wtPath, [
52
65
  `Try running the command manually: cd ${wtPath} && ${cmd}`,
53
- 'Check your configuration in yggtree-worktree.json'
66
+ 'Check your configuration in .yggtree/worktree-setup.json or yggtree-worktree.json'
54
67
  ]);
55
68
  }
56
69
  }
package/dist/lib/git.js CHANGED
@@ -86,9 +86,12 @@ export async function isGitClean(cwd) {
86
86
  }
87
87
  }
88
88
  /**
89
- * Ensures the branch in the given worktree path tracks origin/<branchName>.
90
- * If it tracks a different base branch (e.g. origin/main), it unsets it and publishes to origin.
91
- * Fails if origin/<branchName> already exists on remote and is not already the upstream.
89
+ * Safety net: ensures the branch does NOT track an incorrect upstream
90
+ * (e.g. origin/main when the branch is feat/new-thing).
91
+ *
92
+ * - Correct tracking (origin/<branchName>) β†’ no-op
93
+ * - No tracking at all β†’ no-op
94
+ * - Wrong tracking β†’ unsets it
92
95
  */
93
96
  export async function ensureCorrectUpstream(wtPath, branchName) {
94
97
  const desiredUpstream = `origin/${branchName}`;
@@ -98,21 +101,68 @@ export async function ensureCorrectUpstream(wtPath, branchName) {
98
101
  currentUpstream = stdout.trim();
99
102
  }
100
103
  catch {
101
- // No upstream set
104
+ // No upstream set β€” safe
105
+ return;
102
106
  }
103
107
  if (currentUpstream === desiredUpstream) {
104
108
  return; // Already correct
105
109
  }
106
- // If it's set to something else, unset it
107
- if (currentUpstream) {
108
- log.info(`Incorrect upstream detected: ${currentUpstream}. Unsetting...`);
109
- await execa('git', ['branch', '--unset-upstream'], { cwd: wtPath });
110
+ // Wrong tracking β€” kill it
111
+ log.warning(`Incorrect upstream detected: ${chalk.red(currentUpstream)} (expected ${chalk.cyan(desiredUpstream)}). Unsetting...`);
112
+ await execa('git', ['branch', '--unset-upstream'], { cwd: wtPath });
113
+ }
114
+ /**
115
+ * Publishes a local branch to origin and sets upstream tracking.
116
+ * Skips if the branch is already published (origin/<branchName> exists and is tracked).
117
+ */
118
+ export async function publishBranch(wtPath, branchName) {
119
+ const desiredUpstream = `origin/${branchName}`;
120
+ // Check if already tracking the correct remote
121
+ try {
122
+ const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], { cwd: wtPath });
123
+ if (stdout.trim() === desiredUpstream) {
124
+ return; // Already published and tracked
125
+ }
110
126
  }
111
- // Check if remote branch already exists
112
- const remoteExists = await verifyRef(desiredUpstream);
113
- if (remoteExists) {
114
- throw new Error(`Remote branch '${desiredUpstream}' already exists. Cannot publish safely without more info. Use 'git push -u origin HEAD' manually if you want to link them.`);
127
+ catch {
128
+ // No upstream β€” expected for new branches
115
129
  }
116
- log.info(`Publishing branch ${chalk.cyan(branchName)} to ${chalk.cyan(desiredUpstream)}...`);
130
+ log.info(`Publishing ${chalk.cyan(branchName)} β†’ ${chalk.cyan(desiredUpstream)}...`);
117
131
  await execa('git', ['push', '-u', 'origin', 'HEAD'], { cwd: wtPath });
118
132
  }
133
+ /**
134
+ * Returns the most recent activity date for a worktree by checking two signals:
135
+ * 1. Last commit time (captures committed work)
136
+ * 2. Git index mtime (captures staging, checkouts, uncommitted work)
137
+ *
138
+ * The most recent of the two wins. Returns null if both fail.
139
+ */
140
+ export async function getLastActivity(wtPath) {
141
+ const dates = [];
142
+ // Signal 1 β€” last commit epoch
143
+ try {
144
+ const { stdout } = await execa('git', ['log', '-1', '--format=%ct'], { cwd: wtPath });
145
+ const epoch = parseInt(stdout.trim(), 10);
146
+ if (!isNaN(epoch)) {
147
+ dates.push(new Date(epoch * 1000));
148
+ }
149
+ }
150
+ catch {
151
+ // no commits reachable β€” skip
152
+ }
153
+ // Signal 2 β€” git index file mtime
154
+ try {
155
+ const { stdout: gitDir } = await execa('git', ['rev-parse', '--git-dir'], { cwd: wtPath });
156
+ const gitDirPath = gitDir.trim();
157
+ const resolvedGitDir = path.isAbsolute(gitDirPath) ? gitDirPath : path.join(wtPath, gitDirPath);
158
+ const indexPath = path.join(resolvedGitDir, 'index');
159
+ const stat = await fs.stat(indexPath);
160
+ dates.push(stat.mtime);
161
+ }
162
+ catch {
163
+ // index not found β€” skip
164
+ }
165
+ if (dates.length === 0)
166
+ return null;
167
+ return new Date(Math.max(...dates.map(d => d.getTime())));
168
+ }
@@ -1,26 +1,28 @@
1
1
  import crypto from 'crypto';
2
2
  import path from 'path';
3
3
  import fs from 'fs-extra';
4
- const SANDBOX_META_FILE = '.sandbox-meta.json';
4
+ export const YGGTREE_DIR = '.yggtree';
5
+ export const SANDBOX_META_FILE = 'sandbox-meta.json';
5
6
  /**
6
7
  * Generate a sandbox worktree name: <branch>_<4-char-hash>
7
8
  */
8
9
  export function generateSandboxName(branch) {
9
10
  const hash = crypto.randomBytes(2).toString('hex'); // 4 hex chars
10
11
  const safeBranch = branch.replace(/[\\/]/g, '-');
11
- return `${safeBranch}_${hash}`;
12
+ return `sandbox-${hash}_${safeBranch}`;
12
13
  }
13
14
  /**
14
15
  * Get the path to the sandbox metadata file
15
16
  */
16
17
  export function getSandboxMetaPath(wtPath) {
17
- return path.join(wtPath, SANDBOX_META_FILE);
18
+ return path.join(wtPath, YGGTREE_DIR, SANDBOX_META_FILE);
18
19
  }
19
20
  /**
20
21
  * Write sandbox metadata to the worktree
21
22
  */
22
23
  export async function writeSandboxMeta(wtPath, meta) {
23
24
  const metaPath = getSandboxMetaPath(wtPath);
25
+ await fs.ensureDir(path.dirname(metaPath));
24
26
  await fs.writeJSON(metaPath, meta, { spaces: 2 });
25
27
  }
26
28
  /**
package/dist/lib/ui.js CHANGED
@@ -50,3 +50,23 @@ export const ui = {
50
50
  code: (cmd) => chalk.bgBlack.white(` ${cmd} `),
51
51
  path: (p) => chalk.cyan(p),
52
52
  };
53
+ // --- Time helpers ---
54
+ /**
55
+ * Returns a human-friendly relative time string like "just now", "5 min ago", "3 days ago".
56
+ */
57
+ export function timeAgo(date) {
58
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
59
+ if (seconds < 60)
60
+ return 'just now';
61
+ if (seconds < 3600)
62
+ return `${Math.floor(seconds / 60)} min ago`;
63
+ if (seconds < 86400)
64
+ return `${Math.floor(seconds / 3600)}h ago`;
65
+ if (seconds < 604800)
66
+ return `${Math.floor(seconds / 86400)}d ago`;
67
+ if (seconds < 2592000)
68
+ return `${Math.floor(seconds / 604800)}w ago`;
69
+ if (seconds < 31536000)
70
+ return `${Math.floor(seconds / 2592000)}mo ago`;
71
+ return `${Math.floor(seconds / 31536000)}y ago`;
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yggtree",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Interactive CLI for managing git worktrees and configs",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,13 +0,0 @@
1
- import { log } from '../../lib/ui.js';
2
- export async function leaveCommand() {
3
- if (process.env.YGGTREE_SHELL === 'true') {
4
- log.info('Leaving worktree sub-shell...');
5
- // SIGHUP is standard for closing shells
6
- process.kill(process.ppid, 'SIGHUP');
7
- }
8
- else {
9
- log.warning('You are not in a yggtree sub-shell.');
10
- log.dim('Try using "yggtree wt enter" first.');
11
- process.exit(0);
12
- }
13
- }