@doccy/fell 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/cli.ts +6 -2
  2. package/lib/git.ts +30 -4
  3. package/package.json +1 -1
package/cli.ts CHANGED
@@ -838,7 +838,7 @@ async function handleBrowseKey(state: State, key: Key): Promise<void> {
838
838
  }
839
839
 
840
840
  // Quit
841
- if (key === "ctrl-c" || ch === "q") {
841
+ if (key === "ctrl-c" || key === "escape" || ch === "q") {
842
842
  state.shouldQuit = true
843
843
  return
844
844
  }
@@ -1171,7 +1171,7 @@ async function main() {
1171
1171
 
1172
1172
  case "deleting":
1173
1173
  // Background delete in progress - ignore keys (spinner keeps animating via timer)
1174
- if (key === "ctrl-c") {
1174
+ if (key === "ctrl-c" || key === "escape" || keyChar(key) === "q") {
1175
1175
  state.shouldQuit = true
1176
1176
  }
1177
1177
  break
@@ -1201,6 +1201,10 @@ async function main() {
1201
1201
  clearInterval(spinnerTimer)
1202
1202
  } finally {
1203
1203
  cleanup()
1204
+ // Force exit immediately. Background async tasks (PR fetching, size
1205
+ // estimation) hold the event loop open. They're non-critical UI state
1206
+ // -- no data corruption risk from terminating mid-flight.
1207
+ process.exit(0)
1204
1208
  }
1205
1209
  }
1206
1210
 
package/lib/git.ts CHANGED
@@ -130,6 +130,10 @@ export async function listWorktrees(): Promise<Worktree[]> {
130
130
  /**
131
131
  * Remove a worktree directory and its git administrative tracking.
132
132
  * Use force when the worktree has uncommitted changes.
133
+ *
134
+ * Handles zombie worktrees (directory exists but .git file is missing):
135
+ * falls back to `git worktree prune` to clean the reference, then
136
+ * removes the leftover directory manually.
133
137
  */
134
138
  export async function removeWorktree(
135
139
  path: string,
@@ -137,19 +141,41 @@ export async function removeWorktree(
137
141
  ): Promise<{ ok: boolean; error?: string }> {
138
142
  const flags = force ? ["--force"] : []
139
143
  const result = await $`git worktree remove ${flags} ${path}`.nothrow().quiet()
140
- if (result.exitCode !== 0) {
141
- return { ok: false, error: result.stderr.toString().trim() }
144
+
145
+ if (result.exitCode === 0) return { ok: true }
146
+
147
+ const stderr = result.stderr.toString().trim()
148
+
149
+ // Zombie worktree: directory exists but .git is missing.
150
+ // git worktree remove refuses to act, so we prune the reference
151
+ // and remove the leftover directory ourselves.
152
+ const isZombie =
153
+ stderr.includes("does not exist") &&
154
+ (stderr.includes(".git") || stderr.includes("validation failed"))
155
+
156
+ if (isZombie) {
157
+ // Prune cleans the stale git reference
158
+ await $`git worktree prune`.nothrow().quiet()
159
+
160
+ // Remove the leftover directory if it still exists
161
+ await $`rm -rf ${path}`.nothrow().quiet()
162
+
163
+ return { ok: true }
142
164
  }
143
- return { ok: true }
165
+
166
+ return { ok: false, error: stderr }
144
167
  }
145
168
 
146
169
  /**
147
170
  * Dry-run prune to show what stale references would be cleaned.
148
171
  * Returns human-readable descriptions of each stale entry.
172
+ * Note: git writes prune verbose output to stderr, not stdout.
149
173
  */
150
174
  export async function pruneWorktreesDryRun(): Promise<string[]> {
151
175
  const result = await $`git worktree prune --dry-run -v`.nothrow().quiet()
152
- return result.stdout.toString().trim().split("\n").filter(Boolean)
176
+ const output = result.stderr.toString().trim()
177
+ if (!output) return []
178
+ return output.split("\n").filter(Boolean)
153
179
  }
154
180
 
155
181
  /** Actually prune stale worktree references. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccy/fell",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Interactive git worktree manager. Navigate, inspect, delete, and prune worktrees with async PR status fetching.",
5
5
  "license": "MIT",
6
6
  "bin": {