infra-kit 0.1.117 → 0.1.121

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/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import{A as de,B as me,C as le,D as pe,E as fe,F as ue,G as ge,H as ve,I as ye,J as he,K as we,L as ke,M as Ce,N as Ae,O as xe,P as _,Q as Ee,R as Se,S as Re,T as Le,U as be,W as j,a as U,d as a,e as B,f as T,g as z,h as Y,i as h,j as u,k as D,l as q,m as Q,n as I,o as X,p as Z,q as ee,r as oe,s as te,t as re,u as ne,v as ie,w as C,x as ae,y as se,z as ce}from"./chunk-FEGOOVZE.js";import{e as $}from"./chunk-WA4BQRDC.js";import vo,{Separator as x}from"@inquirer/select";import{Command as yo}from"commander";import w from"node:process";import N from"node:fs/promises";import Xe from"node:path";import je from"node:process";import{$ as Ze}from"zx";var O=async()=>{let e=await T(),o=await Promise.all([{label:"project (committed)",path:e.main},{label:"user global",path:e.userGlobal},{label:"user project",path:e.userProject}].map(async r=>({...r,exists:await h(r.path)})));a.info(`Project name: ${e.projectName}
1
+ import{A as de,B as me,C as le,D as pe,E as fe,F as ue,G as ge,H as ve,I as ye,J as he,K as we,L as ke,M as Ce,N as Ae,O as xe,P as _,Q as Ee,R as Se,S as Re,T as Le,U as be,W as j,a as U,d as a,e as B,f as T,g as z,h as Y,i as h,j as u,k as D,l as q,m as Q,n as I,o as X,p as Z,q as ee,r as oe,s as te,t as re,u as ne,v as ie,w as C,x as ae,y as se,z as ce}from"./chunk-PWOX6B3T.js";import{e as $}from"./chunk-WA4BQRDC.js";import vo,{Separator as x}from"@inquirer/select";import{Command as yo}from"commander";import w from"node:process";import N from"node:fs/promises";import Xe from"node:path";import je from"node:process";import{$ as Ze}from"zx";var O=async()=>{let e=await T(),o=await Promise.all([{label:"project (committed)",path:e.main},{label:"user global",path:e.userGlobal},{label:"user project",path:e.userProject}].map(async r=>({...r,exists:await h(r.path)})));a.info(`Project name: ${e.projectName}
2
2
  `),a.info(`Config merge chain (later overrides earlier):
3
3
  `);for(let r of o){let c=r.exists?" [\u2713]":" [ ]";a.info(`${c} ${r.label.padEnd(22)} ${u(r.path)}`)}let t={projectName:e.projectName,layers:o.map(r=>({label:r.label,path:r.path,exists:r.exists}))};return{content:[{type:"text",text:JSON.stringify(t,null,2)}],structuredContent:t}},M=async()=>{let e=await T(),o=je.env.EDITOR||je.env.VISUAL||"vi";if(await N.mkdir(Xe.dirname(e.userProject),{recursive:!0}),!await h(e.userProject)){let r=eo(e.userProject);await N.writeFile(e.userProject,`{}
4
4
  `,"utf-8"),await N.writeFile(r,oo(e.projectName),"utf-8"),a.info(`Created ${u(e.userProject)} \u2014 see ${u(r)} for the annotated reference.`)}a.info(`Opening ${u(e.userProject)} in ${o}`),await Ze({stdio:"inherit"})`${o} ${e.userProject}`,Y();let t={path:e.userProject,editor:o};return{content:[{type:"text",text:JSON.stringify(t,null,2)}],structuredContent:t}},eo=e=>e.replace(/\.json$/,".example.jsonc"),oo=e=>`// infra-kit user override for ${e} \u2014 ~/.infra-kit/projects/${e}/infra-kit.json
package/dist/mcp.js CHANGED
@@ -1,2 +1,2 @@
1
- import{V as m,b as s,c as p,d as c}from"./chunk-FEGOOVZE.js";import"./chunk-WA4BQRDC.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import h from"node:process";import o from"node:process";var l=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${s}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${s}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as x}from"@modelcontextprotocol/sdk/server/mcp.js";var d=async r=>{};var u=async r=>{};var a=r=>async e=>{let{toolName:n,handler:S}=r;c.info({msg:`Tool execution started: ${n}`,params:e});try{let i=await S({...e,confirmedCommand:!0});return c.info({msg:`Tool execution successful: ${n}`}),i}catch(i){throw c.error({err:i,params:e,msg:`Tool execution failed: ${n}`}),i}};var f=async r=>{for(let e of m())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function v(){let r=new x({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await d(r),await u(r),await f(r),r}var t=p(),T=async()=>{let r;try{r=await v(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),h.exit(1)}try{let e=new g;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),h.exit(1)}};l(t);T();
1
+ import{V as m,b as s,c as p,d as c}from"./chunk-PWOX6B3T.js";import"./chunk-WA4BQRDC.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import h from"node:process";import o from"node:process";var l=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${s}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${s}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as x}from"@modelcontextprotocol/sdk/server/mcp.js";var d=async r=>{};var u=async r=>{};var a=r=>async e=>{let{toolName:n,handler:S}=r;c.info({msg:`Tool execution started: ${n}`,params:e});try{let i=await S({...e,confirmedCommand:!0});return c.info({msg:`Tool execution successful: ${n}`}),i}catch(i){throw c.error({err:i,params:e,msg:`Tool execution failed: ${n}`}),i}};var f=async r=>{for(let e of m())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function v(){let r=new x({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await d(r),await u(r),await f(r),r}var t=p(),T=async()=>{let r;try{r=await v(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),h.exit(1)}try{let e=new g;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),h.exit(1)}};l(t);T();
2
2
  //# sourceMappingURL=mcp.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.117",
4
+ "version": "0.1.121",
5
5
  "description": "infra-kit",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -0,0 +1,2 @@
1
+ {"t":0,"agent":"ab0bb13","agent_type":"architect","event":"agent_start","parent_mode":"none"}
2
+ {"t":0,"agent":"ab0bb13","agent_type":"architect","event":"agent_stop","success":true,"duration_ms":146258}
@@ -0,0 +1,7 @@
1
+ {
2
+ "tool_name": "Bash",
3
+ "tool_input_preview": "{\"command\":\"cd /Users/arthur/projects/infra-kit && rtk grep -rn \\\"WORKTREES_DIR_SUFFIX\\\" apps/infra-kit/cli/src/lib/constants.ts\",\"description\":\"Verify constants.ts line for suffix\"}",
4
+ "error": "Exit code 2\ngrep: apps/infra-kit/cli/src/lib/constants.ts: No such file or directory",
5
+ "timestamp": "2026-06-30T06:08:21.827Z",
6
+ "retry_count": 1
7
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "updatedAt": "2026-06-30T06:10:31.962Z",
3
+ "missions": [
4
+ {
5
+ "id": "session:707b4784-2117-4b19-ae68-68a031f0d76d:none",
6
+ "source": "session",
7
+ "name": "none",
8
+ "objective": "Session mission",
9
+ "createdAt": "2026-06-30T06:08:05.704Z",
10
+ "updatedAt": "2026-06-30T06:10:31.962Z",
11
+ "status": "done",
12
+ "workerCount": 1,
13
+ "taskCounts": {
14
+ "total": 1,
15
+ "pending": 0,
16
+ "blocked": 0,
17
+ "inProgress": 0,
18
+ "completed": 1,
19
+ "failed": 0
20
+ },
21
+ "agents": [
22
+ {
23
+ "name": "architect:ab0bb13",
24
+ "role": "architect",
25
+ "ownership": "ab0bb13a5e2ee0436",
26
+ "status": "done",
27
+ "currentStep": null,
28
+ "latestUpdate": "completed",
29
+ "completedSummary": null,
30
+ "updatedAt": "2026-06-30T06:10:31.962Z"
31
+ }
32
+ ],
33
+ "timeline": [
34
+ {
35
+ "id": "session-start:ab0bb13a5e2ee0436:2026-06-30T06:08:05.704Z",
36
+ "at": "2026-06-30T06:08:05.704Z",
37
+ "kind": "update",
38
+ "agent": "architect:ab0bb13",
39
+ "detail": "started architect:ab0bb13",
40
+ "sourceKey": "session-start:ab0bb13a5e2ee0436"
41
+ },
42
+ {
43
+ "id": "session-stop:ab0bb13a5e2ee0436:2026-06-30T06:10:31.962Z",
44
+ "at": "2026-06-30T06:10:31.962Z",
45
+ "kind": "completion",
46
+ "agent": "architect:ab0bb13",
47
+ "detail": "completed",
48
+ "sourceKey": "session-stop:ab0bb13a5e2ee0436"
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": 1,
3
+ "entries": {
4
+ "70618baf7ddd1c017d8e7ffe84ab90f8fa22ca1640c91f45c1731fe6330fc294": {
5
+ "last_emitted_at_ms": 1782799685575,
6
+ "message": "Spawning agent: oh-my-claudecode:architect (inherit) | Task: Architect review of worktree-folder plan"
7
+ },
8
+ "445ed27a3872b681d98190bae61ccb84954e1bc4e140df5370be958dee776b3a": {
9
+ "last_emitted_at_ms": 1782799670035,
10
+ "message": "Verify changes work after editing. Test functionality before marking complete."
11
+ },
12
+ "466399dafa2d20f60587180bad0a07358eb7f2bce724df0ff682f038ad33f5ad": {
13
+ "last_emitted_at_ms": 1782799612172,
14
+ "message": "Read multiple files in parallel when possible for faster analysis."
15
+ },
16
+ "79a93d4a2f8f50b95f852280616242fee1855dc99a3c75211917f55e72e95fae": {
17
+ "last_emitted_at_ms": 1782799606281,
18
+ "message": "Use parallel execution for independent tasks. Use run_in_background for long operations (npm install, builds, tests)."
19
+ }
20
+ },
21
+ "updated_at": "2026-06-30T06:08:05.575Z"
22
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "agents": [
3
+ {
4
+ "agent_id": "ab0bb13a5e2ee0436",
5
+ "agent_type": "oh-my-claudecode:architect",
6
+ "started_at": "2026-06-30T06:08:05.704Z",
7
+ "parent_mode": "none",
8
+ "status": "completed",
9
+ "completed_at": "2026-06-30T06:10:31.962Z",
10
+ "duration_ms": 146258
11
+ }
12
+ ],
13
+ "total_spawned": 1,
14
+ "total_completed": 1,
15
+ "total_failed": 0,
16
+ "last_updated": "2026-06-30T06:10:32.065Z"
17
+ }
@@ -170,7 +170,7 @@ const logResults = (removed: string[]): void => {
170
170
  export const worktreesRemoveMcpTool = defineMcpTool({
171
171
  name: 'worktrees-remove',
172
172
  description:
173
- 'Remove local git worktrees for release branches. When everything is removed, also runs "git worktree prune" and deletes the worktrees directory. When invoked via MCP, pass either "versions" (comma-separated) or all=true — the branch picker is unreachable without a TTY, and the CLI confirmation is auto-skipped for MCP calls, so the caller is responsible for gating.',
173
+ 'Remove local git worktrees for release branches. When everything is removed, also runs "git worktree prune" to clear stale metadata; the worktrees directory and its release/feature subfolders are left in place. When invoked via MCP, pass either "versions" (comma-separated) or all=true — the branch picker is unreachable without a TTY, and the CLI confirmation is auto-skipped for MCP calls, so the caller is responsible for gating.',
174
174
  inputSchema: {
175
175
  all: z
176
176
  .boolean()
@@ -98,8 +98,9 @@ export const commandCatalog: CommandCatalogEntry[] = [
98
98
  { cliName: 'worktrees-add', menuGroup: 'worktrees', mcpTool: worktreesAddMcpTool, mcpExposed: true },
99
99
  { cliName: 'worktrees-list', menuGroup: 'worktrees', mcpTool: worktreesListMcpTool, mcpExposed: true },
100
100
  { cliName: 'worktrees-reload', menuGroup: 'worktrees', mcpTool: worktreesReloadMcpTool, mcpExposed: true },
101
- // worktrees-remove runs rm -rf on worktree dirs genuinely irreversible, so
102
- // it is CLI-only by design (mirrors the vendor-sync/manifest rationale).
101
+ // worktrees-remove runs `git worktree remove` on each leaf worktree —
102
+ // genuinely irreversible (uncommitted work is lost), so it is CLI-only by
103
+ // design (mirrors the vendor-sync/manifest rationale).
103
104
  { cliName: 'worktrees-remove', menuGroup: 'worktrees', mcpTool: worktreesRemoveMcpTool, mcpExposed: false },
104
105
  { cliName: 'worktrees-sync', menuGroup: 'worktrees', mcpTool: worktreesSyncMcpTool, mcpExposed: true },
105
106
 
@@ -0,0 +1,167 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { $ } from 'zx'
3
+
4
+ import { removeWorktrees } from '../remove-worktrees'
5
+
6
+ // `$` resolves to a bare awaitable result — removeWorktrees only ever `await`s
7
+ // the tagged template (no `.quiet()`/`.nothrow()` chaining anymore), so a plain
8
+ // resolved promise is a faithful stand-in.
9
+ vi.mock('zx', () => {
10
+ return {
11
+ $: vi.fn(() => {
12
+ return Promise.resolve({ stdout: '', exitCode: 0 })
13
+ }),
14
+ }
15
+ })
16
+
17
+ vi.mock('src/integrations/cmux', () => {
18
+ return {
19
+ buildCmuxWorkspaceTitle: vi.fn(({ repoName, branch }: { repoName: string; branch: string }) => {
20
+ return `${repoName}:${branch}`
21
+ }),
22
+ closeCmuxWorkspaceByTitle: vi.fn(() => {
23
+ return Promise.resolve()
24
+ }),
25
+ }
26
+ })
27
+
28
+ vi.mock('src/lib/logger', () => {
29
+ return {
30
+ logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
31
+ }
32
+ })
33
+
34
+ type DollarCall = [TemplateStringsArray, ...unknown[]]
35
+
36
+ /** Reconstruct the full command string for a recorded `$` tagged-template call. */
37
+ const commandOf = (call: DollarCall): string => {
38
+ const [strings, ...values] = call
39
+
40
+ return strings.reduce((acc, part, index) => {
41
+ const value = index < values.length ? String(values[index]) : ''
42
+
43
+ return acc + part + value
44
+ }, '')
45
+ }
46
+
47
+ /** Interpolated `${...}` arguments of a recorded `$` call (the dynamic paths). */
48
+ const argsOf = (call: DollarCall): string[] => {
49
+ return call.slice(1).map((value) => {
50
+ return String(value)
51
+ })
52
+ }
53
+
54
+ const recordedCalls = (): DollarCall[] => {
55
+ return vi.mocked($).mock.calls as unknown as DollarCall[]
56
+ }
57
+
58
+ const WORKTREE_DIR = '/repos/hulyo-monorepo-worktrees'
59
+ const REPO_NAME = 'hulyo-monorepo'
60
+
61
+ describe('removeWorktrees', () => {
62
+ beforeEach(() => {
63
+ vi.clearAllMocks()
64
+ })
65
+
66
+ it('removes each leaf worktree and returns the removed branches', async () => {
67
+ const branches = ['release/1.2.5', 'feature/foo']
68
+
69
+ const removed = await removeWorktrees({ branches, worktreeDir: WORKTREE_DIR, repoName: REPO_NAME })
70
+
71
+ expect(removed).toEqual(branches)
72
+
73
+ const removeCommands = recordedCalls()
74
+ .map(commandOf)
75
+ .filter((command) => {
76
+ return command.startsWith('git worktree remove ')
77
+ })
78
+
79
+ expect(removeCommands).toContain(`git worktree remove ${WORKTREE_DIR}/release/1.2.5`)
80
+ expect(removeCommands).toContain(`git worktree remove ${WORKTREE_DIR}/feature/foo`)
81
+ })
82
+
83
+ it('never deletes the worktrees container or its group subdirs (regression)', async () => {
84
+ const branches = ['release/1.2.5', 'release/1.2.6']
85
+
86
+ await removeWorktrees({ branches, worktreeDir: WORKTREE_DIR, repoName: REPO_NAME, pruneFolder: true })
87
+
88
+ const calls = recordedCalls()
89
+ const commands = calls.map(commandOf)
90
+
91
+ // No destructive directory removal of any kind.
92
+ expect(
93
+ commands.some((command) => {
94
+ return command.includes('rm -rf')
95
+ }),
96
+ ).toBe(false)
97
+ expect(
98
+ commands.some((command) => {
99
+ return command.includes('rmdir')
100
+ }),
101
+ ).toBe(false)
102
+
103
+ // Invariant: no `$` call ever targets the bare parent worktreeDir as an
104
+ // interpolated argument — guards against any future deletion mechanism.
105
+ const targetsBareParent = calls.some((call) => {
106
+ return argsOf(call).includes(WORKTREE_DIR)
107
+ })
108
+
109
+ expect(targetsBareParent).toBe(false)
110
+ })
111
+
112
+ it('runs `git worktree prune` when pruneFolder is set and every branch was removed', async () => {
113
+ const branches = ['release/1.2.5']
114
+
115
+ await removeWorktrees({ branches, worktreeDir: WORKTREE_DIR, repoName: REPO_NAME, pruneFolder: true })
116
+
117
+ const prunedCalls = recordedCalls()
118
+ .map(commandOf)
119
+ .filter((command) => {
120
+ return command === 'git worktree prune'
121
+ })
122
+
123
+ expect(prunedCalls).toHaveLength(1)
124
+ })
125
+
126
+ it('does not run `git worktree prune` when pruneFolder is false', async () => {
127
+ const branches = ['release/1.2.5']
128
+
129
+ await removeWorktrees({ branches, worktreeDir: WORKTREE_DIR, repoName: REPO_NAME, pruneFolder: false })
130
+
131
+ const pruned = recordedCalls().map(commandOf).includes('git worktree prune')
132
+
133
+ expect(pruned).toBe(false)
134
+ })
135
+
136
+ it('skips `git worktree prune` when a branch removal fails (not every branch removed)', async () => {
137
+ const branches = ['release/1.2.5', 'release/1.2.6']
138
+
139
+ // Reject the `git worktree remove` for the second branch so it is not counted
140
+ // in `removed`, leaving `removed.length !== branches.length`. Cast the whole
141
+ // impl to `typeof $` since zx's `$` is a multi-overload callable.
142
+ const failingDollar = (strings: TemplateStringsArray, ...values: unknown[]) => {
143
+ const command = commandOf([strings, ...values])
144
+
145
+ if (command === `git worktree remove ${WORKTREE_DIR}/release/1.2.6`) {
146
+ return Promise.reject(new Error('uncommitted changes block removal'))
147
+ }
148
+
149
+ return Promise.resolve({ stdout: '', exitCode: 0 })
150
+ }
151
+
152
+ vi.mocked($).mockImplementation(failingDollar as unknown as typeof $)
153
+
154
+ const removed = await removeWorktrees({
155
+ branches,
156
+ worktreeDir: WORKTREE_DIR,
157
+ repoName: REPO_NAME,
158
+ pruneFolder: true,
159
+ })
160
+
161
+ expect(removed).toEqual(['release/1.2.5'])
162
+
163
+ const pruned = recordedCalls().map(commandOf).includes('git worktree prune')
164
+
165
+ expect(pruned).toBe(false)
166
+ })
167
+ })
@@ -16,9 +16,10 @@ interface RemoveWorktreesArgs {
16
16
  * returning the branches that were removed cleanly. Failures are logged but
17
17
  * never thrown, so a single bad worktree doesn't poison a batch removal.
18
18
  *
19
- * When `pruneFolder` is true and every branch was removed, also prune the
20
- * worktree metadata and delete the worktrees folder used by the
21
- * `worktrees-remove` "all" path to leave the filesystem clean.
19
+ * When `pruneFolder` is true and every branch was removed, also run
20
+ * `git worktree prune` to clear stale worktree metadata. The `<repo>-worktrees`
21
+ * container directory and its `release/`/`feature/` subfolders are deliberately
22
+ * left in place so the per-repo worktree scaffold persists even when empty.
22
23
  */
23
24
  export const removeWorktrees = async (args: RemoveWorktreesArgs): Promise<string[]> => {
24
25
  const { branches, worktreeDir, repoName, pruneFolder = false } = args
@@ -53,27 +54,12 @@ export const removeWorktrees = async (args: RemoveWorktreesArgs): Promise<string
53
54
  }
54
55
  }
55
56
 
56
- // `git worktree remove` only deletes the leaf worktree, leaving the group
57
- // folder (e.g. `release/`, `feature/`) behind. Remove it when it's now empty;
58
- // `rmdir` is a no-op when the folder still holds other worktrees.
59
- const groupDirs = new Set<string>()
60
-
61
- for (const branch of removed) {
62
- if (branch.includes('/')) {
63
- groupDirs.add(`${worktreeDir}/${branch.split('/')[0]}`)
64
- }
65
- }
66
-
67
- for (const groupDir of groupDirs) {
68
- await $`rmdir ${groupDir}`.nothrow()
69
- }
70
-
57
+ // `git worktree remove` deletes only the leaf worktree. We intentionally leave
58
+ // the `<repo>-worktrees` container and its `release/`/`feature/` group folders
59
+ // in place so the worktree scaffold survives an empty state (it is recreated
60
+ // lazily by `worktrees-add` via `mkdir -p` regardless).
71
61
  if (pruneFolder && removed.length === branches.length) {
72
62
  await $`git worktree prune`
73
- await $`rm -rf ${worktreeDir}`
74
-
75
- logger.info(`🗑️ Removed worktree folder: ${worktreeDir}`)
76
- logger.info('')
77
63
  }
78
64
 
79
65
  return removed