code-squad-cli 1.2.4 → 1.2.5

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.
@@ -9,5 +9,18 @@ export declare class GitAdapter implements PartialGitPort {
9
9
  deleteBranch(branchName: string, workspaceRoot: string, force?: boolean): Promise<void>;
10
10
  isValidWorktree(path: string, workspaceRoot: string): Promise<boolean>;
11
11
  getWorktreeBranch(worktreePath: string): Promise<string>;
12
+ /**
13
+ * 현재 디렉토리가 워크트리인지 확인하고 컨텍스트 반환
14
+ */
15
+ getWorktreeContext(cwd: string): Promise<{
16
+ isWorktree: boolean;
17
+ mainRoot: string | null;
18
+ currentPath: string;
19
+ branch: string | null;
20
+ }>;
21
+ /**
22
+ * staged 또는 unstaged 변경사항이 있는지 확인 (untracked 제외)
23
+ */
24
+ hasDirtyState(workspacePath: string): Promise<boolean>;
12
25
  }
13
26
  export {};
@@ -1,102 +1,84 @@
1
- import { exec } from 'child_process';
1
+ import { exec as execCallback } from 'child_process';
2
+ import { promisify } from 'util';
2
3
  import * as fs from 'fs';
4
+ const exec = promisify(execCallback);
5
+ const execOptions = { maxBuffer: 1024 * 1024 };
3
6
  export class GitAdapter {
4
7
  async isGitRepository(workspaceRoot) {
5
- return new Promise((resolve) => {
6
- exec(`cd "${workspaceRoot}" && git rev-parse --git-dir`, { maxBuffer: 1024 * 1024 }, (error) => {
7
- resolve(!error);
8
- });
9
- });
8
+ try {
9
+ await exec(`cd "${workspaceRoot}" && git rev-parse --git-dir`, execOptions);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
10
15
  }
11
16
  async getCurrentBranch(workspaceRoot) {
12
- return new Promise((resolve, reject) => {
13
- exec(`cd "${workspaceRoot}" && git rev-parse --abbrev-ref HEAD`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
14
- if (error) {
15
- reject(error);
16
- return;
17
- }
18
- resolve(stdout.trim());
19
- });
20
- });
17
+ const { stdout } = await exec(`cd "${workspaceRoot}" && git rev-parse --abbrev-ref HEAD`, execOptions);
18
+ return stdout.trim();
21
19
  }
22
20
  async listWorktrees(workspaceRoot) {
23
- return new Promise((resolve) => {
24
- exec(`cd "${workspaceRoot}" && git worktree list --porcelain`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
25
- if (error) {
26
- resolve([]);
27
- return;
21
+ try {
22
+ const { stdout } = await exec(`cd "${workspaceRoot}" && git worktree list --porcelain`, execOptions);
23
+ const worktrees = [];
24
+ const lines = stdout.split('\n').filter((line) => line.trim());
25
+ // Parse porcelain format: groups of 3 lines
26
+ // worktree /path
27
+ // HEAD sha
28
+ // branch refs/heads/name
29
+ let i = 0;
30
+ while (i < lines.length) {
31
+ const worktreeLine = lines[i];
32
+ const headLine = lines[i + 1];
33
+ const branchLine = lines[i + 2];
34
+ if (!worktreeLine || !headLine) {
35
+ i++;
36
+ continue;
28
37
  }
29
- const worktrees = [];
30
- const lines = stdout.split('\n').filter((line) => line.trim());
31
- // Parse porcelain format: groups of 3 lines
32
- // worktree /path
33
- // HEAD sha
34
- // branch refs/heads/name
35
- let i = 0;
36
- while (i < lines.length) {
37
- const worktreeLine = lines[i];
38
- const headLine = lines[i + 1];
39
- const branchLine = lines[i + 2];
40
- if (!worktreeLine || !headLine) {
41
- i++;
42
- continue;
43
- }
44
- const pathMatch = worktreeLine.match(/^worktree (.+)$/);
45
- const headMatch = headLine.match(/^HEAD (.+)$/);
46
- const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
47
- if (pathMatch && headMatch) {
48
- const path = pathMatch[1];
49
- const head = headMatch[1];
50
- const branch = branchMatch ? branchMatch[1] : 'HEAD';
51
- // Skip main repository root (first entry)
52
- if (path !== workspaceRoot) {
53
- worktrees.push({ path, branch, head });
54
- }
38
+ const pathMatch = worktreeLine.match(/^worktree (.+)$/);
39
+ const headMatch = headLine.match(/^HEAD (.+)$/);
40
+ const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
41
+ if (pathMatch && headMatch) {
42
+ const path = pathMatch[1];
43
+ const head = headMatch[1];
44
+ const branch = branchMatch ? branchMatch[1] : 'HEAD';
45
+ // Skip main repository root (first entry)
46
+ if (path !== workspaceRoot) {
47
+ worktrees.push({ path, branch, head });
55
48
  }
56
- // Move to next worktree entry
57
- i += 3;
58
49
  }
59
- resolve(worktrees);
60
- });
61
- });
50
+ // Move to next worktree entry
51
+ i += 3;
52
+ }
53
+ return worktrees;
54
+ }
55
+ catch {
56
+ return [];
57
+ }
62
58
  }
63
59
  async createWorktree(worktreePath, branch, workspaceRoot) {
64
- return new Promise((resolve, reject) => {
65
- // Extract parent directory and create it if needed
66
- const parentDir = worktreePath.substring(0, worktreePath.lastIndexOf('/'));
67
- const mkdirCmd = parentDir ? `mkdir -p "${parentDir}" && ` : '';
68
- exec(`cd "${workspaceRoot}" && ${mkdirCmd}git worktree add "${worktreePath}" -b "${branch}"`, { maxBuffer: 1024 * 1024 }, (error) => {
69
- if (error) {
70
- reject(error);
71
- return;
72
- }
73
- resolve();
74
- });
75
- });
60
+ // Extract parent directory and create it if needed
61
+ const parentDir = worktreePath.substring(0, worktreePath.lastIndexOf('/'));
62
+ const mkdirCmd = parentDir ? `mkdir -p "${parentDir}" && ` : '';
63
+ await exec(`cd "${workspaceRoot}" && ${mkdirCmd}git worktree add "${worktreePath}" -b "${branch}"`, execOptions);
76
64
  }
77
65
  async removeWorktree(worktreePath, workspaceRoot, force = false) {
78
- return new Promise((resolve, reject) => {
79
- const forceFlag = force ? ' --force' : '';
80
- exec(`cd "${workspaceRoot}" && git worktree remove "${worktreePath}"${forceFlag}`, { maxBuffer: 1024 * 1024 }, (error) => {
81
- if (error) {
82
- reject(new Error(`Failed to remove worktree: ${error.message}`));
83
- return;
84
- }
85
- resolve();
86
- });
87
- });
66
+ const forceFlag = force ? ' --force' : '';
67
+ try {
68
+ await exec(`cd "${workspaceRoot}" && git worktree remove "${worktreePath}"${forceFlag}`, execOptions);
69
+ }
70
+ catch (error) {
71
+ throw new Error(`Failed to remove worktree: ${error.message}`);
72
+ }
88
73
  }
89
74
  async deleteBranch(branchName, workspaceRoot, force = false) {
90
- return new Promise((resolve, reject) => {
91
- const deleteFlag = force ? '-D' : '-d';
92
- exec(`cd "${workspaceRoot}" && git branch ${deleteFlag} "${branchName}"`, { maxBuffer: 1024 * 1024 }, (error) => {
93
- if (error) {
94
- reject(new Error(`Failed to delete branch: ${error.message}`));
95
- return;
96
- }
97
- resolve();
98
- });
99
- });
75
+ const deleteFlag = force ? '-D' : '-d';
76
+ try {
77
+ await exec(`cd "${workspaceRoot}" && git branch ${deleteFlag} "${branchName}"`, execOptions);
78
+ }
79
+ catch (error) {
80
+ throw new Error(`Failed to delete branch: ${error.message}`);
81
+ }
100
82
  }
101
83
  async isValidWorktree(path, workspaceRoot) {
102
84
  // Step 1: Check if path exists and is accessible
@@ -107,12 +89,10 @@ export class GitAdapter {
107
89
  return false;
108
90
  }
109
91
  // Step 2: Check if path is a valid git repository
110
- const isGitRepo = await new Promise((resolve) => {
111
- exec(`cd "${path}" && git rev-parse --git-dir`, { maxBuffer: 1024 * 1024 }, (error) => {
112
- resolve(!error);
113
- });
114
- });
115
- if (!isGitRepo) {
92
+ try {
93
+ await exec(`cd "${path}" && git rev-parse --git-dir`, execOptions);
94
+ }
95
+ catch {
116
96
  return false;
117
97
  }
118
98
  // Step 3: Verify path is listed in main repo's worktree list
@@ -120,15 +100,60 @@ export class GitAdapter {
120
100
  return worktrees.some((wt) => wt.path === path);
121
101
  }
122
102
  async getWorktreeBranch(worktreePath) {
123
- return new Promise((resolve, reject) => {
124
- exec(`cd "${worktreePath}" && git rev-parse --abbrev-ref HEAD`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
125
- if (error) {
126
- reject(new Error(`Failed to get branch name: ${error.message}`));
127
- return;
128
- }
129
- const branch = stdout.trim();
130
- resolve(branch);
131
- });
132
- });
103
+ try {
104
+ const { stdout } = await exec(`cd "${worktreePath}" && git rev-parse --abbrev-ref HEAD`, execOptions);
105
+ return stdout.trim();
106
+ }
107
+ catch (error) {
108
+ throw new Error(`Failed to get branch name: ${error.message}`);
109
+ }
110
+ }
111
+ /**
112
+ * 현재 디렉토리가 워크트리인지 확인하고 컨텍스트 반환
113
+ */
114
+ async getWorktreeContext(cwd) {
115
+ // git-common-dir로 워크트리 여부 확인
116
+ let commonDir = null;
117
+ try {
118
+ const { stdout } = await exec(`cd "${cwd}" && git rev-parse --git-common-dir`, execOptions);
119
+ commonDir = stdout.trim();
120
+ }
121
+ catch {
122
+ return { isWorktree: false, mainRoot: null, currentPath: cwd, branch: null };
123
+ }
124
+ // .git 이면 메인 레포, 아니면 워크트리
125
+ const isWorktree = commonDir !== '.git';
126
+ if (!isWorktree) {
127
+ return { isWorktree: false, mainRoot: cwd, currentPath: cwd, branch: null };
128
+ }
129
+ // 메인 레포 루트 찾기 (worktree list의 첫 번째)
130
+ let mainRoot = null;
131
+ try {
132
+ const { stdout } = await exec(`cd "${cwd}" && git worktree list --porcelain`, execOptions);
133
+ const match = stdout.match(/^worktree (.+)$/m);
134
+ mainRoot = match ? match[1] : null;
135
+ }
136
+ catch {
137
+ // ignore
138
+ }
139
+ // 현재 브랜치
140
+ const branch = await this.getWorktreeBranch(cwd).catch(() => null);
141
+ return { isWorktree, mainRoot, currentPath: cwd, branch };
142
+ }
143
+ /**
144
+ * staged 또는 unstaged 변경사항이 있는지 확인 (untracked 제외)
145
+ */
146
+ async hasDirtyState(workspacePath) {
147
+ try {
148
+ const { stdout } = await exec(`cd "${workspacePath}" && git status --porcelain`, execOptions);
149
+ // ?? 로 시작하는 줄(untracked)을 제외한 변경사항이 있는지
150
+ const lines = stdout.split('\n').filter(line => line.trim());
151
+ const dirtyLines = lines.filter(line => !line.startsWith('??'));
152
+ return dirtyLines.length > 0;
153
+ }
154
+ catch {
155
+ // 에러 시 안전하게 dirty로 처리 (데이터 손실 방지)
156
+ return true;
157
+ }
133
158
  }
134
159
  }
package/dist/index.js CHANGED
@@ -8,98 +8,76 @@ import * as crypto from "crypto";
8
8
  import chalk2 from "chalk";
9
9
 
10
10
  // dist/adapters/GitAdapter.js
11
- import { exec } from "child_process";
11
+ import { exec as execCallback } from "child_process";
12
+ import { promisify } from "util";
12
13
  import * as fs from "fs";
14
+ var exec = promisify(execCallback);
15
+ var execOptions = { maxBuffer: 1024 * 1024 };
13
16
  var GitAdapter = class {
14
17
  async isGitRepository(workspaceRoot) {
15
- return new Promise((resolve2) => {
16
- exec(`cd "${workspaceRoot}" && git rev-parse --git-dir`, { maxBuffer: 1024 * 1024 }, (error) => {
17
- resolve2(!error);
18
- });
19
- });
18
+ try {
19
+ await exec(`cd "${workspaceRoot}" && git rev-parse --git-dir`, execOptions);
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
20
24
  }
21
25
  async getCurrentBranch(workspaceRoot) {
22
- return new Promise((resolve2, reject) => {
23
- exec(`cd "${workspaceRoot}" && git rev-parse --abbrev-ref HEAD`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
24
- if (error) {
25
- reject(error);
26
- return;
27
- }
28
- resolve2(stdout.trim());
29
- });
30
- });
26
+ const { stdout } = await exec(`cd "${workspaceRoot}" && git rev-parse --abbrev-ref HEAD`, execOptions);
27
+ return stdout.trim();
31
28
  }
32
29
  async listWorktrees(workspaceRoot) {
33
- return new Promise((resolve2) => {
34
- exec(`cd "${workspaceRoot}" && git worktree list --porcelain`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
35
- if (error) {
36
- resolve2([]);
37
- return;
30
+ try {
31
+ const { stdout } = await exec(`cd "${workspaceRoot}" && git worktree list --porcelain`, execOptions);
32
+ const worktrees = [];
33
+ const lines = stdout.split("\n").filter((line) => line.trim());
34
+ let i = 0;
35
+ while (i < lines.length) {
36
+ const worktreeLine = lines[i];
37
+ const headLine = lines[i + 1];
38
+ const branchLine = lines[i + 2];
39
+ if (!worktreeLine || !headLine) {
40
+ i++;
41
+ continue;
38
42
  }
39
- const worktrees = [];
40
- const lines = stdout.split("\n").filter((line) => line.trim());
41
- let i = 0;
42
- while (i < lines.length) {
43
- const worktreeLine = lines[i];
44
- const headLine = lines[i + 1];
45
- const branchLine = lines[i + 2];
46
- if (!worktreeLine || !headLine) {
47
- i++;
48
- continue;
49
- }
50
- const pathMatch = worktreeLine.match(/^worktree (.+)$/);
51
- const headMatch = headLine.match(/^HEAD (.+)$/);
52
- const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
53
- if (pathMatch && headMatch) {
54
- const path11 = pathMatch[1];
55
- const head = headMatch[1];
56
- const branch = branchMatch ? branchMatch[1] : "HEAD";
57
- if (path11 !== workspaceRoot) {
58
- worktrees.push({ path: path11, branch, head });
59
- }
43
+ const pathMatch = worktreeLine.match(/^worktree (.+)$/);
44
+ const headMatch = headLine.match(/^HEAD (.+)$/);
45
+ const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
46
+ if (pathMatch && headMatch) {
47
+ const path11 = pathMatch[1];
48
+ const head = headMatch[1];
49
+ const branch = branchMatch ? branchMatch[1] : "HEAD";
50
+ if (path11 !== workspaceRoot) {
51
+ worktrees.push({ path: path11, branch, head });
60
52
  }
61
- i += 3;
62
53
  }
63
- resolve2(worktrees);
64
- });
65
- });
54
+ i += 3;
55
+ }
56
+ return worktrees;
57
+ } catch {
58
+ return [];
59
+ }
66
60
  }
67
61
  async createWorktree(worktreePath, branch, workspaceRoot) {
68
- return new Promise((resolve2, reject) => {
69
- const parentDir = worktreePath.substring(0, worktreePath.lastIndexOf("/"));
70
- const mkdirCmd = parentDir ? `mkdir -p "${parentDir}" && ` : "";
71
- exec(`cd "${workspaceRoot}" && ${mkdirCmd}git worktree add "${worktreePath}" -b "${branch}"`, { maxBuffer: 1024 * 1024 }, (error) => {
72
- if (error) {
73
- reject(error);
74
- return;
75
- }
76
- resolve2();
77
- });
78
- });
62
+ const parentDir = worktreePath.substring(0, worktreePath.lastIndexOf("/"));
63
+ const mkdirCmd = parentDir ? `mkdir -p "${parentDir}" && ` : "";
64
+ await exec(`cd "${workspaceRoot}" && ${mkdirCmd}git worktree add "${worktreePath}" -b "${branch}"`, execOptions);
79
65
  }
80
66
  async removeWorktree(worktreePath, workspaceRoot, force = false) {
81
- return new Promise((resolve2, reject) => {
82
- const forceFlag = force ? " --force" : "";
83
- exec(`cd "${workspaceRoot}" && git worktree remove "${worktreePath}"${forceFlag}`, { maxBuffer: 1024 * 1024 }, (error) => {
84
- if (error) {
85
- reject(new Error(`Failed to remove worktree: ${error.message}`));
86
- return;
87
- }
88
- resolve2();
89
- });
90
- });
67
+ const forceFlag = force ? " --force" : "";
68
+ try {
69
+ await exec(`cd "${workspaceRoot}" && git worktree remove "${worktreePath}"${forceFlag}`, execOptions);
70
+ } catch (error) {
71
+ throw new Error(`Failed to remove worktree: ${error.message}`);
72
+ }
91
73
  }
92
74
  async deleteBranch(branchName, workspaceRoot, force = false) {
93
- return new Promise((resolve2, reject) => {
94
- const deleteFlag = force ? "-D" : "-d";
95
- exec(`cd "${workspaceRoot}" && git branch ${deleteFlag} "${branchName}"`, { maxBuffer: 1024 * 1024 }, (error) => {
96
- if (error) {
97
- reject(new Error(`Failed to delete branch: ${error.message}`));
98
- return;
99
- }
100
- resolve2();
101
- });
102
- });
75
+ const deleteFlag = force ? "-D" : "-d";
76
+ try {
77
+ await exec(`cd "${workspaceRoot}" && git branch ${deleteFlag} "${branchName}"`, execOptions);
78
+ } catch (error) {
79
+ throw new Error(`Failed to delete branch: ${error.message}`);
80
+ }
103
81
  }
104
82
  async isValidWorktree(path11, workspaceRoot) {
105
83
  try {
@@ -107,28 +85,59 @@ var GitAdapter = class {
107
85
  } catch {
108
86
  return false;
109
87
  }
110
- const isGitRepo = await new Promise((resolve2) => {
111
- exec(`cd "${path11}" && git rev-parse --git-dir`, { maxBuffer: 1024 * 1024 }, (error) => {
112
- resolve2(!error);
113
- });
114
- });
115
- if (!isGitRepo) {
88
+ try {
89
+ await exec(`cd "${path11}" && git rev-parse --git-dir`, execOptions);
90
+ } catch {
116
91
  return false;
117
92
  }
118
93
  const worktrees = await this.listWorktrees(workspaceRoot);
119
94
  return worktrees.some((wt) => wt.path === path11);
120
95
  }
121
96
  async getWorktreeBranch(worktreePath) {
122
- return new Promise((resolve2, reject) => {
123
- exec(`cd "${worktreePath}" && git rev-parse --abbrev-ref HEAD`, { maxBuffer: 1024 * 1024 }, (error, stdout) => {
124
- if (error) {
125
- reject(new Error(`Failed to get branch name: ${error.message}`));
126
- return;
127
- }
128
- const branch = stdout.trim();
129
- resolve2(branch);
130
- });
131
- });
97
+ try {
98
+ const { stdout } = await exec(`cd "${worktreePath}" && git rev-parse --abbrev-ref HEAD`, execOptions);
99
+ return stdout.trim();
100
+ } catch (error) {
101
+ throw new Error(`Failed to get branch name: ${error.message}`);
102
+ }
103
+ }
104
+ /**
105
+ * 현재 디렉토리가 워크트리인지 확인하고 컨텍스트 반환
106
+ */
107
+ async getWorktreeContext(cwd) {
108
+ let commonDir = null;
109
+ try {
110
+ const { stdout } = await exec(`cd "${cwd}" && git rev-parse --git-common-dir`, execOptions);
111
+ commonDir = stdout.trim();
112
+ } catch {
113
+ return { isWorktree: false, mainRoot: null, currentPath: cwd, branch: null };
114
+ }
115
+ const isWorktree = commonDir !== ".git";
116
+ if (!isWorktree) {
117
+ return { isWorktree: false, mainRoot: cwd, currentPath: cwd, branch: null };
118
+ }
119
+ let mainRoot = null;
120
+ try {
121
+ const { stdout } = await exec(`cd "${cwd}" && git worktree list --porcelain`, execOptions);
122
+ const match = stdout.match(/^worktree (.+)$/m);
123
+ mainRoot = match ? match[1] : null;
124
+ } catch {
125
+ }
126
+ const branch = await this.getWorktreeBranch(cwd).catch(() => null);
127
+ return { isWorktree, mainRoot, currentPath: cwd, branch };
128
+ }
129
+ /**
130
+ * staged 또는 unstaged 변경사항이 있는지 확인 (untracked 제외)
131
+ */
132
+ async hasDirtyState(workspacePath) {
133
+ try {
134
+ const { stdout } = await exec(`cd "${workspacePath}" && git status --porcelain`, execOptions);
135
+ const lines = stdout.split("\n").filter((line) => line.trim());
136
+ const dirtyLines = lines.filter((line) => !line.startsWith("??"));
137
+ return dirtyLines.length > 0;
138
+ } catch {
139
+ return true;
140
+ }
132
141
  }
133
142
  };
134
143
 
@@ -401,6 +410,9 @@ async function confirmDeleteLocal(threadName) {
401
410
  });
402
411
  }
403
412
 
413
+ // dist/index.js
414
+ import { confirm as confirm3 } from "@inquirer/prompts";
415
+
404
416
  // dist/flip/server/Server.js
405
417
  import express2 from "express";
406
418
  import cors from "cors";
@@ -1433,7 +1445,10 @@ async function main() {
1433
1445
  await listThreads(workspaceRoot);
1434
1446
  break;
1435
1447
  case "new":
1436
- await createWorktree(workspaceRoot, filteredArgs[1]);
1448
+ await createWorktreeCommand(workspaceRoot, filteredArgs.slice(1));
1449
+ break;
1450
+ case "quit":
1451
+ await quitWorktreeCommand();
1437
1452
  break;
1438
1453
  default:
1439
1454
  if (persistentMode) {
@@ -1550,30 +1565,71 @@ async function listThreads(workspaceRoot) {
1550
1565
  console.log(`${typeLabel} ${t.name.padEnd(20)} ${chalk2.dim(t.path)}`);
1551
1566
  }
1552
1567
  }
1553
- async function createWorktree(workspaceRoot, name) {
1568
+ function parseNewArgs(args) {
1569
+ let name;
1570
+ let split = false;
1571
+ for (const arg of args) {
1572
+ if (arg === "-s" || arg === "--split") {
1573
+ split = true;
1574
+ } else if (!arg.startsWith("-") && !name) {
1575
+ name = arg;
1576
+ }
1577
+ }
1578
+ return { name, split };
1579
+ }
1580
+ async function createWorktreeCommand(workspaceRoot, args) {
1581
+ const { name, split } = parseNewArgs(args);
1582
+ if (!name) {
1583
+ console.error(chalk2.red("Error: Name is required"));
1584
+ console.error(chalk2.dim("Usage: csq new <name> [-s|--split]"));
1585
+ process.exit(1);
1586
+ }
1554
1587
  const repoName = path10.basename(workspaceRoot);
1555
1588
  const defaultBasePath = path10.join(path10.dirname(workspaceRoot), `${repoName}.worktree`);
1556
- let worktreeName;
1557
- let worktreePath;
1558
- if (name) {
1559
- worktreeName = name;
1560
- worktreePath = path10.join(defaultBasePath, name);
1561
- } else {
1562
- const form = await newWorktreeForm(defaultBasePath);
1563
- if (!form) {
1589
+ const worktreePath = path10.join(defaultBasePath, name);
1590
+ try {
1591
+ await gitAdapter.createWorktree(worktreePath, name, workspaceRoot);
1592
+ console.log(chalk2.green(`\u2713 Created worktree: ${name}`));
1593
+ await copyWorktreeFiles(workspaceRoot, worktreePath);
1594
+ if (split) {
1595
+ await openNewTerminal(worktreePath);
1596
+ } else {
1597
+ console.log(worktreePath);
1598
+ }
1599
+ } catch (error) {
1600
+ console.error(chalk2.red(`Failed to create worktree: ${error.message}`));
1601
+ process.exit(1);
1602
+ }
1603
+ }
1604
+ async function quitWorktreeCommand() {
1605
+ const cwd = process.cwd();
1606
+ const context = await gitAdapter.getWorktreeContext(cwd);
1607
+ if (!context.isWorktree) {
1608
+ console.error(chalk2.red("Error: Not in a worktree"));
1609
+ process.exit(1);
1610
+ }
1611
+ if (!context.mainRoot || !context.branch) {
1612
+ console.error(chalk2.red("Error: Could not determine worktree context"));
1613
+ process.exit(1);
1614
+ }
1615
+ const isDirty = await gitAdapter.hasDirtyState(cwd);
1616
+ if (isDirty) {
1617
+ const confirmed = await confirm3({
1618
+ message: "Uncommitted changes detected. Delete anyway?",
1619
+ default: false
1620
+ });
1621
+ if (!confirmed) {
1564
1622
  console.log(chalk2.dim("Cancelled."));
1565
- return;
1623
+ process.exit(0);
1566
1624
  }
1567
- worktreeName = form.name;
1568
- worktreePath = form.path;
1569
1625
  }
1570
1626
  try {
1571
- await gitAdapter.createWorktree(worktreePath, worktreeName, workspaceRoot);
1572
- console.log(chalk2.green(`\u2713 Created worktree: ${worktreeName}`));
1573
- await copyWorktreeFiles(workspaceRoot, worktreePath);
1574
- await writeCdFile(worktreePath);
1627
+ await gitAdapter.removeWorktree(context.currentPath, context.mainRoot, true);
1628
+ await gitAdapter.deleteBranch(context.branch, context.mainRoot, true);
1629
+ console.log(chalk2.green(`\u2713 Deleted worktree and branch: ${context.branch}`));
1630
+ console.log(context.mainRoot);
1575
1631
  } catch (error) {
1576
- console.error(chalk2.red(`Failed to create worktree: ${error.message}`));
1632
+ console.error(chalk2.red(`Failed to quit: ${error.message}`));
1577
1633
  process.exit(1);
1578
1634
  }
1579
1635
  }
@@ -1591,12 +1647,6 @@ async function copyWorktreeFiles(sourceRoot, destRoot) {
1591
1647
  console.log(chalk2.yellow(`\u26A0 Failed to copy ${failed.length} file(s)`));
1592
1648
  }
1593
1649
  }
1594
- async function writeCdFile(targetPath) {
1595
- const cdFile = process.env.CSQ_CD_FILE;
1596
- if (cdFile) {
1597
- await fs10.promises.writeFile(cdFile, targetPath);
1598
- }
1599
- }
1600
1650
  async function cdInCurrentTerminal(targetPath) {
1601
1651
  const { exec: exec2 } = await import("child_process");
1602
1652
  const escapedPath = targetPath.replace(/'/g, "'\\''");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-squad-cli",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "csq": "./dist/index.js"