tasktree-manager 2.1.0__tar.gz → 2.2.0__tar.gz

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 (57) hide show
  1. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/CHANGELOG.md +6 -0
  2. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/PKG-INFO +1 -1
  3. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/README.md +16 -22
  4. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/docs/configuration.md +40 -5
  5. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/docs/user-guide.md +33 -6
  6. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/pyproject.toml +1 -1
  7. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/_version.py +2 -2
  8. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/app.py +45 -6
  9. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/services/claude_hooks.py +1 -1
  10. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/services/config.py +8 -1
  11. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/services/git_ops.py +7 -0
  12. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/SKILL.md +0 -0
  13. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/assets/README.md +0 -0
  14. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
  15. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
  16. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
  17. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
  18. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/references/layouts.md +0 -0
  19. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
  20. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/references/styling.md +0 -0
  21. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.claude/skills/textual-tui/references/widgets.md +0 -0
  22. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.github/templates/CHANGELOG.md.j2 +0 -0
  23. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.github/workflows/ci.yml +0 -0
  24. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.github/workflows/release.yml +0 -0
  25. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/.gitignore +0 -0
  26. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/CONTRIBUTING.md +0 -0
  27. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/LICENSE +0 -0
  28. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/OPENSPEC.md +0 -0
  29. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/ROADMAP.md +0 -0
  30. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/docs/claude-code-integration-spec.md +0 -0
  31. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/docs/installation.md +0 -0
  32. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/docs/troubleshooting.md +0 -0
  33. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/mise.toml +0 -0
  34. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/scripts/bump_version.py +0 -0
  35. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/__init__.py +0 -0
  36. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/services/__init__.py +0 -0
  37. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/services/models.py +0 -0
  38. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/services/task_manager.py +0 -0
  39. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/widgets/__init__.py +0 -0
  40. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/widgets/create_modal.py +0 -0
  41. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/widgets/messages_panel.py +0 -0
  42. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/widgets/setup_modal.py +0 -0
  43. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/widgets/status_panel.py +0 -0
  44. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/widgets/task_list.py +0 -0
  45. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tasktree_manager/widgets/worktree_list.py +0 -0
  46. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/__init__.py +0 -0
  47. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/conftest.py +0 -0
  48. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_app.py +0 -0
  49. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_config.py +0 -0
  50. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_git_ops.py +0 -0
  51. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_integration_git.py +0 -0
  52. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_memory.py +0 -0
  53. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_messages_panel.py +0 -0
  54. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_modals.py +0 -0
  55. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_setup_modal.py +0 -0
  56. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_status_panel.py +0 -0
  57. {tasktree_manager-2.1.0 → tasktree_manager-2.2.0}/tests/test_task_manager.py +0 -0
@@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  - Git hooks support
16
16
  - Custom task metadata
17
17
 
18
+ ## [2.2.0] - 2026-03-06
19
+
20
+ ### Added
21
+ - Add periodic auto-refresh for git status with selection preserv… (#3)
22
+
23
+
18
24
  ## [2.1.0] - 2026-02-10
19
25
 
20
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktree-manager
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: TUI application for managing worktree-based tasks
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
@@ -19,26 +19,18 @@
19
19
  - 🚀 **Fast & Responsive** - Parallel git operations with loading indicators
20
20
  - 🔧 **Flexible Configuration** - TOML config file, custom keybindings, environment variables
21
21
 
22
- ## 📸 Screenshots
23
-
24
- ```
25
- ┌─ tasktree-manager ──────────────────────────────────────────────────────┐
26
- │ Tasks │ Worktrees │
27
- │ ● FEAT-42 (2) │ web-api FEAT-42 ✗ 3 │
28
- │ FEAT-38-auth │ frontend FEAT-42 ✓ │
29
- │ │ │
30
- │ │ │
31
- ├──────────────────────────────────────────────────────────────────────────┤
32
- │ Status: web-api │
33
- │ Branch: FEAT-42 │
34
- │ Sync: ↑2 ↓1 │
35
- │ │
36
- │ M src/main.py │
37
- │ ?? README.md │
38
- │ M tests/test_app.py │
39
- └──────────────────────────────────────────────────────────────────────────┘
40
- n New task d Delete g Lazygit p Push r Refresh ? Help q Quit
41
- ```
22
+ ## 📊 Status Indicators
23
+
24
+ | Indicator | Meaning |
25
+ |-----------|---------|
26
+ | `●` red | Uncommitted changes |
27
+ | `◆` blue | Has CLAUDE.md file |
28
+ | `⟳` magenta | Claude Code session running |
29
+ | `!` yellow | Claude Code session waiting for input |
30
+ | `✓` green | Clean worktree / Claude session ended |
31
+ | `✗ N` red | N files changed |
32
+ | `↑N` green | N commits ahead of remote |
33
+ | `↓N` yellow | N commits behind remote |
42
34
 
43
35
  ## 🚀 Quick Start
44
36
 
@@ -85,9 +77,11 @@ For detailed workflows and examples, see the [User Guide](docs/user-guide.md).
85
77
  | `n` | New task | `g` | Open lazygit |
86
78
  | `a` | Add repo | `e` | Open editor |
87
79
  | `d` | Delete task | `o` | Open folder |
88
- | `p` | Push all | `Enter` | Open shell |
80
+ | `p` | Push all | `P` | Pull all |
81
+ | `c` | Claude (resume) | `C` | Claude (new) |
89
82
  | `r` | Refresh | `s` | Sort tasks |
90
- | `S` | Group worktrees | `Tab` | Next panel |
83
+ | `S` | Group worktrees | `m` | Messages |
84
+ | `Enter` | Open shell | `Tab` | Next panel |
91
85
  | `?` | Show help | `q` | Quit |
92
86
 
93
87
  **Themes:** Press `Ctrl+P` to switch between 7 built-in themes (textual-dark, nord, gruvbox, tokyo-night, monokai, dracula, textual-light).
@@ -93,6 +93,11 @@ theme = "textual-dark"
93
93
  # Default: false
94
94
  show_hidden_files = false
95
95
 
96
+ # Auto-refresh interval in seconds (0 = disabled)
97
+ # Periodically refreshes git status in the background
98
+ # Default: 30
99
+ refresh_interval = 30
100
+
96
101
  # ============================================================================
97
102
  # Git Settings
98
103
  # ============================================================================
@@ -160,6 +165,13 @@ push_all = "p" # Push all worktrees in current task
160
165
  pull_all = "P" # Pull all worktrees in current task (shift+p)
161
166
  refresh = "r" # Refresh git status
162
167
 
168
+ # Claude Code integration
169
+ open_claude_resume = "c" # Resume Claude Code session in task directory
170
+ open_claude_new = "C" # Start new Claude Code session (shift+c)
171
+
172
+ # Messages panel
173
+ toggle_messages = "m" # Show/hide the activity messages panel
174
+
163
175
  # Navigation
164
176
  focus_next = "tab" # Switch to next panel
165
177
  focus_previous = "shift+tab" # Switch to previous panel
@@ -224,10 +236,11 @@ tasks_dir = "/Volumes/FastSSD/worktrees"
224
236
 
225
237
  ## UI Settings
226
238
 
227
- | Option | Type | Default | Environment Variable | Description |
228
- |--------------------|---------|-----------------|-----------------------|---------------------------------|
229
- | `theme` | string | `textual-dark` | `TASKTREE_THEME` | Color theme for the interface |
230
- | `show_hidden_files` | boolean | `false` | - | Show hidden files in listings |
239
+ | Option | Type | Default | Environment Variable | Description |
240
+ |--------------------|---------|-----------------|-----------------------|------------------------------------------|
241
+ | `theme` | string | `textual-dark` | `TASKTREE_THEME` | Color theme for the interface |
242
+ | `show_hidden_files` | boolean | `false` | - | Show hidden files in listings |
243
+ | `refresh_interval` | integer | `30` | - | Auto-refresh interval in seconds (0 = disabled) |
231
244
 
232
245
  ### `theme`
233
246
 
@@ -245,6 +258,20 @@ Available themes (press `Ctrl+P` in app to switch):
245
258
 
246
259
  Currently unused (reserved for future file browser features).
247
260
 
261
+ ### `refresh_interval`
262
+
263
+ Automatically refreshes git status for all tasks and worktrees in the background:
264
+
265
+ - Default: `30` (every 30 seconds)
266
+ - Set to `0` to disable auto-refresh
267
+ - Runs in a background thread — does not block the UI
268
+ - Preserves current task and worktree selection during refresh
269
+
270
+ ```toml
271
+ [ui]
272
+ refresh_interval = 15 # Refresh every 15 seconds
273
+ ```
274
+
248
275
  ## Git Settings
249
276
 
250
277
  | Option | Type | Default | Environment Variable | Description |
@@ -390,7 +417,7 @@ blocklist = ["*"]
390
417
 
391
418
  ## Keybindings Reference
392
419
 
393
- All 18 customizable keybindings with descriptions:
420
+ All 21 customizable keybindings with descriptions:
394
421
 
395
422
  ### Application Control
396
423
 
@@ -419,6 +446,14 @@ All 18 customizable keybindings with descriptions:
419
446
  | `pull_all` | `P` | Pull all worktrees in current task | `pull_all = "ctrl+shift+p"` |
420
447
  | `refresh` | `r` | Refresh git status for all worktrees | `refresh = "F5"` |
421
448
 
449
+ ### Claude Code Integration
450
+
451
+ | Action | Default | Description | Customization Example |
452
+ |----------------------|---------|------------------------------------------|--------------------------------------|
453
+ | `open_claude_resume` | `c` | Resume Claude Code session in task dir | `open_claude_resume = "ctrl+c"` |
454
+ | `open_claude_new` | `C` | Start new Claude Code session | `open_claude_new = "ctrl+shift+c"` |
455
+ | `toggle_messages` | `m` | Show/hide activity messages panel | `toggle_messages = "ctrl+m"` |
456
+
422
457
  ### Navigation
423
458
 
424
459
  | Action | Default | Description | Customization Example |
@@ -177,7 +177,9 @@ Shows all your tasks:
177
177
 
178
178
  - **Name**: Task identifier (e.g., `FEAT-123`)
179
179
  - **Count**: Number of worktrees in parentheses (e.g., `(3)`)
180
- - **Dirty indicator** `●`: Task has uncommitted changes
180
+ - **Dirty indicator** `●`: Red dot — task has uncommitted changes
181
+ - **CLAUDE.md indicator** `◆`: Blue diamond — task has a CLAUDE.md file
182
+ - **Claude session status**: `⟳` running (magenta), `!` waiting (yellow), `✓` ended (green)
181
183
  - **Sort mode**: Press `s` to cycle through sort modes (name ↑/↓, date ↑/↓, dirty/clean first)
182
184
 
183
185
  **Navigation:**
@@ -221,13 +223,24 @@ Shows detailed git status for the selected worktree:
221
223
 
222
224
  ### Visual Indicators
223
225
 
226
+ **Task & Worktree Status:**
227
+
224
228
  | Indicator | Meaning |
225
229
  |-----------|--------------------------------------------|
226
- | `●` | Task has uncommitted changes |
227
- | `✗ N` | N files staged or modified |
228
- | `↑N` | N commits ahead of remote (unpushed) |
229
- | `↓N` | N commits behind remote (need to pull) |
230
- | `✓` | Clean worktree, in sync |
230
+ | `●` | Task/worktree has uncommitted changes |
231
+ | `✗ N` | N files staged or modified |
232
+ | `↑N` | N commits ahead of remote (unpushed) |
233
+ | `↓N` | N commits behind remote (need to pull) |
234
+ | `✓` | Clean worktree, in sync |
235
+
236
+ **Claude Code Integration:**
237
+
238
+ | Indicator | Color | Meaning |
239
+ |-----------|---------|------------------------------------------|
240
+ | `◆` | Blue | Task/worktree has a CLAUDE.md file |
241
+ | `⟳` | Magenta | Claude Code session is running |
242
+ | `!` | Yellow | Claude Code session is waiting for input |
243
+ | `✓` | Green | Claude Code session has ended |
231
244
 
232
245
  ## Complete Keyboard Reference
233
246
 
@@ -318,12 +331,26 @@ All keybindings can be customized in [`config.toml`](configuration.md).
318
331
  - When focused on task list: Opens the task folder (`~/tasks/TASK-NAME`)
319
332
  - When focused on worktree list: Opens the selected worktree folder
320
333
 
334
+ ### Claude Code Integration
335
+
336
+ | Key | Action | Description |
337
+ |---------|-------------------------|----------------------------------------------|
338
+ | `c` | Open Claude (resume) | Resume Claude Code session in task directory |
339
+ | `C` | Open Claude (new) | Start new Claude Code session in task directory |
340
+
341
+ **How it works:**
342
+ - Opens Claude Code in a new Ghostty terminal tab at the task directory
343
+ - Generates `CLAUDE.md` files with task/worktree context
344
+ - Installs hooks that report session status back to tasktree-manager
345
+ - Session status (`⟳` running, `!` waiting, `✓` ended) appears next to the task name
346
+
321
347
  ### General
322
348
 
323
349
  | Key | Action | Description |
324
350
  |---------|-------------------------|----------------------------------------------|
325
351
  | `Ctrl+P`| Open Command Palette | Switch themes and run commands |
326
352
  | `o` | Open folder | Open current folder in new terminal tab |
353
+ | `m` | Toggle messages | Show/hide the activity messages panel |
327
354
  | `?` | Show help | Display help modal with keybindings |
328
355
  | `q` | Quit | Exit tasktree-manager |
329
356
 
@@ -70,7 +70,7 @@ version-file = "tasktree_manager/_version.py"
70
70
  # Semantic Release Configuration
71
71
  [tool.semantic_release]
72
72
  version_toml = ["pyproject.toml:tool.semantic_release.version"]
73
- version = "2.1.0"
73
+ version = "2.2.0"
74
74
  tag_format = "v{version}"
75
75
  branch = "main"
76
76
  commit_message = "chore(release): {version} [skip ci]"
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.1.0'
32
- __version_tuple__ = version_tuple = (2, 1, 0)
31
+ __version__ = version = '2.2.0'
32
+ __version_tuple__ = version_tuple = (2, 2, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -338,6 +338,10 @@ class TaskTreeApp(App):
338
338
  # Poll Claude session status files every 5 seconds
339
339
  self.set_interval(5, self._poll_claude_statuses)
340
340
 
341
+ # Periodic auto-refresh of git status
342
+ if self.config.refresh_interval > 0:
343
+ self.set_interval(self.config.refresh_interval, self._periodic_git_refresh)
344
+
341
345
  def watch_theme(self, theme: str) -> None:
342
346
  """Save theme to config when changed."""
343
347
  if hasattr(self, "config") and self.config.theme != theme:
@@ -449,6 +453,41 @@ class TaskTreeApp(App):
449
453
  self._claude_statuses = statuses
450
454
  task_list.refresh_claude_indicators(statuses)
451
455
 
456
+ def _periodic_git_refresh(self) -> None:
457
+ """Trigger a background git status refresh."""
458
+ self._run_periodic_refresh()
459
+
460
+ @work(thread=True, exclusive=True, group="auto_refresh")
461
+ def _run_periodic_refresh(self) -> None:
462
+ """Refresh git status in background thread."""
463
+ tasks = self.task_manager.list_tasks()
464
+ all_worktrees = [wt for task in tasks for wt in task.worktrees]
465
+ GitOps.update_all_worktree_statuses(all_worktrees)
466
+
467
+ current_task_name = self.current_task.name if self.current_task else None
468
+ current_worktree_name = self.current_worktree.name if self.current_worktree else None
469
+
470
+ def _apply():
471
+ try:
472
+ task_list = self.query_one("#task-list", TaskList)
473
+ if current_worktree_name:
474
+ self._preserved_worktree_name = current_worktree_name
475
+ task_list.load_tasks(tasks, preserve_selection=current_task_name)
476
+
477
+ if self.current_task:
478
+ for t in tasks:
479
+ if t.name == self.current_task.name:
480
+ self.current_task = t
481
+ worktree_list = self.query_one("#worktree-list", WorktreeList)
482
+ worktree_list.load_worktrees(
483
+ t.worktrees, preserve_selection=current_worktree_name
484
+ )
485
+ break
486
+ except Exception:
487
+ pass
488
+
489
+ self.call_from_thread(_apply)
490
+
452
491
  def _refresh_current_task(self, preserve_selection: bool = False) -> None:
453
492
  """Refresh the current task's worktrees and status.
454
493
 
@@ -693,8 +732,8 @@ class TaskTreeApp(App):
693
732
  except Exception as e:
694
733
  failed.append((repo, e))
695
734
 
696
- self._load_tasks()
697
- self._refresh_current_task()
735
+ self._load_tasks(preserve_selection=True)
736
+ self._refresh_current_task(preserve_selection=True)
698
737
 
699
738
  if added:
700
739
  self.notify(f"Added {len(added)} repo(s) to task")
@@ -1129,7 +1168,7 @@ class TaskTreeApp(App):
1129
1168
  task_name,
1130
1169
  )
1131
1170
 
1132
- self._refresh_current_task()
1171
+ self._refresh_current_task(preserve_selection=True)
1133
1172
  finally:
1134
1173
  worktree_list.loading = False
1135
1174
 
@@ -1164,14 +1203,14 @@ class TaskTreeApp(App):
1164
1203
  task_name,
1165
1204
  )
1166
1205
 
1167
- self._refresh_current_task()
1206
+ self._refresh_current_task(preserve_selection=True)
1168
1207
  finally:
1169
1208
  worktree_list.loading = False
1170
1209
 
1171
1210
  def action_refresh(self) -> None:
1172
1211
  """Refresh all status."""
1173
- self._load_tasks()
1174
- self._refresh_current_task()
1212
+ self._load_tasks(preserve_selection=True)
1213
+ self._refresh_current_task(preserve_selection=True)
1175
1214
  self.notify("Refreshed")
1176
1215
 
1177
1216
  def action_toggle_messages(self) -> None:
@@ -9,7 +9,7 @@ def _make_hook(status: str, status_file: str) -> dict:
9
9
  safe_path = status_file.replace("'", "'\\''")
10
10
  return {
11
11
  "type": "command",
12
- "command": f"printf '{{\"status\":\"{status}\",\"ts\":%d}}' $(date +%s) > '{safe_path}'",
12
+ "command": f'printf \'{{"status":"{status}","ts":%d}}\' $(date +%s) > \'{safe_path}\'',
13
13
  "async": True,
14
14
  }
15
15
 
@@ -59,6 +59,7 @@ class Config:
59
59
  # UI settings
60
60
  theme: str = "textual-dark"
61
61
  show_hidden_files: bool = False
62
+ refresh_interval: int = 30 # Auto-refresh interval in seconds (0 = disabled)
62
63
 
63
64
  # Git settings
64
65
  default_base_branch: str = "main"
@@ -103,7 +104,8 @@ class Config:
103
104
  Priority: Environment variables > Config file > Defaults
104
105
  """
105
106
  config_dir = (
106
- Path(os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))) / "tasktree-manager"
107
+ Path(os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config")))
108
+ / "tasktree-manager"
107
109
  )
108
110
  config_file = config_dir / "config.toml"
109
111
 
@@ -122,6 +124,7 @@ class Config:
122
124
  ui_config = config_data.get("ui", {})
123
125
  theme = ui_config.get("theme", "textual-dark")
124
126
  show_hidden_files = ui_config.get("show_hidden_files", False)
127
+ refresh_interval = ui_config.get("refresh_interval", 30)
125
128
 
126
129
  # Git settings
127
130
  git_config = config_data.get("git", {})
@@ -185,6 +188,7 @@ class Config:
185
188
  config_dir=config_dir,
186
189
  theme=theme,
187
190
  show_hidden_files=show_hidden_files,
191
+ refresh_interval=refresh_interval,
188
192
  default_base_branch=default_base_branch,
189
193
  auto_push=auto_push,
190
194
  git_timeout=git_timeout,
@@ -286,6 +290,9 @@ theme = "{self._toml_escape(self.theme)}"
286
290
  # Show hidden files in file listings
287
291
  show_hidden_files = {str(self.show_hidden_files).lower()}
288
292
 
293
+ # Auto-refresh interval in seconds (0 = disabled)
294
+ refresh_interval = {self.refresh_interval}
295
+
289
296
  # ============================================================================
290
297
  # Git Settings
291
298
  # ============================================================================
@@ -190,6 +190,13 @@ class GitOps:
190
190
  True if current branch is merged into base_branch, False otherwise.
191
191
  """
192
192
  try:
193
+ # Fetch latest remote refs so we detect merges done via GitLab/GitHub UI
194
+ subprocess.run(
195
+ ["git", "fetch", "origin", base_branch],
196
+ cwd=worktree.path,
197
+ capture_output=True,
198
+ timeout=10,
199
+ )
193
200
  # Use git merge-base --is-ancestor to check if HEAD is reachable from base
194
201
  # This checks if the current branch has been merged
195
202
  result = subprocess.run(