tasktree-manager 2.18.0__tar.gz → 2.20.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 (87) hide show
  1. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/CHANGELOG.md +15 -0
  2. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/PKG-INFO +1 -1
  3. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/README.md +83 -58
  4. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/docs/configuration.md +21 -1
  5. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/docs/user-guide.md +120 -33
  6. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/pyproject.toml +1 -1
  7. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/_version.py +2 -2
  8. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/app.py +431 -45
  9. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/cli.py +66 -21
  10. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/commands.py +5 -0
  11. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/agent_sessions.py +40 -15
  12. tasktree_manager-2.20.0/tasktree_manager/services/claude_bg.py +138 -0
  13. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/claude_sessions.py +26 -22
  14. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/config.py +34 -0
  15. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/epic_ask.py +26 -22
  16. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/git_ops.py +1 -1
  17. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/models.py +4 -0
  18. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/task_manager.py +37 -9
  19. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/create_modal.py +6 -0
  20. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/conftest.py +14 -1
  21. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_agent_sessions.py +20 -0
  22. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_app.py +1 -0
  23. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_app_review_fixes.py +45 -6
  24. tasktree_manager-2.20.0/tests/test_claude_bg.py +533 -0
  25. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_epic_ask.py +6 -4
  26. tasktree_manager-2.20.0/tests/test_review_findings.py +208 -0
  27. tasktree_manager-2.20.0/tests/test_terminal_tabs.py +225 -0
  28. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/SKILL.md +0 -0
  29. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/assets/README.md +0 -0
  30. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
  31. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
  32. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
  33. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
  34. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/references/layouts.md +0 -0
  35. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
  36. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/references/styling.md +0 -0
  37. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.claude/skills/textual-tui/references/widgets.md +0 -0
  38. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.github/templates/CHANGELOG.md.j2 +0 -0
  39. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.github/workflows/ci.yml +0 -0
  40. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.github/workflows/release.yml +0 -0
  41. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/.gitignore +0 -0
  42. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/CONTRIBUTING.md +0 -0
  43. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/LICENSE +0 -0
  44. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/ROADMAP.md +0 -0
  45. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/docs/claude-code-integration-spec.md +0 -0
  46. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/docs/installation.md +0 -0
  47. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/docs/troubleshooting.md +0 -0
  48. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/mise.toml +0 -0
  49. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/scripts/bump_version.py +0 -0
  50. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/__init__.py +0 -0
  51. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/__init__.py +0 -0
  52. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/claude_hooks.py +0 -0
  53. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/epic_log.py +0 -0
  54. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/services/forge.py +0 -0
  55. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/themes.py +0 -0
  56. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/__init__.py +0 -0
  57. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/app_header.py +0 -0
  58. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/messages_panel.py +0 -0
  59. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/setup_modal.py +0 -0
  60. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/status_panel.py +0 -0
  61. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/task_list.py +0 -0
  62. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tasktree_manager/widgets/worktree_list.py +0 -0
  63. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/__init__.py +0 -0
  64. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_agent_sessions_decode.py +0 -0
  65. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_bump_version.py +0 -0
  66. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_claude_hooks.py +0 -0
  67. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_claude_sessions.py +0 -0
  68. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_cli.py +0 -0
  69. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_commands.py +0 -0
  70. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_config.py +0 -0
  71. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_epic_claude_md.py +0 -0
  72. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_epic_log.py +0 -0
  73. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_epic_tui.py +0 -0
  74. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_epics.py +0 -0
  75. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_forge.py +0 -0
  76. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_git_ops.py +0 -0
  77. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_integration_git.py +0 -0
  78. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_list_selection.py +0 -0
  79. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_medium_fixes.py +0 -0
  80. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_memory.py +0 -0
  81. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_messages_panel.py +0 -0
  82. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_modals.py +0 -0
  83. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_safety_fixes.py +0 -0
  84. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_setup_modal.py +0 -0
  85. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_status_panel.py +0 -0
  86. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_task_manager.py +0 -0
  87. {tasktree_manager-2.18.0 → tasktree_manager-2.20.0}/tests/test_worktree_badges.py +0 -0
@@ -15,6 +15,21 @@ 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.20.0] - 2026-10-29
19
+
20
+ ### Added
21
+ - Keep Claude running in the background when its tab closes (#17)
22
+
23
+
24
+ ## [2.19.0] - 2026-10-29
25
+
26
+ ### Added
27
+ - Open zellij tabs that survive a terminal restart, and review fixes (#16)
28
+
29
+ ### Changed
30
+ - Split the headless CLI and epic sections into short parts (#15)
31
+
32
+
18
33
  ## [2.18.0] - 2026-10-20
19
34
 
20
35
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tasktree-manager
3
- Version: 2.18.0
3
+ Version: 2.20.0
4
4
  Summary: TUI application for managing worktree-based tasks
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
@@ -23,6 +23,8 @@
23
23
  - ✻ **Session Recap** - The Info panel shows each task's latest Claude Code session: title, last turn duration, and Claude's own idle recap of what was done and what comes next — read straight from the transcript, no CLI call
24
24
  - 🔀 **Forge-Aware Safety** - MR/PR and CI state via `glab`/`gh`; squash- and rebase-merged branches count as merged instead of blocking deletion forever
25
25
  - 🗂️ **Epics** - Group related tasks under an epic: a foldable row with rolled-up dirty and Claude state, `E` to attach tasks, and an epic log and `epic ask` so the Claude sessions of one epic share what they know
26
+ - 🧷 **zellij Tabs** - Run the TUI inside zellij (or set `zellij_autostart`) and `o`/`e`/`c` open zellij tabs that keep running across a terminal restart; pressing the key again jumps to the open tab ([details](docs/user-guide.md#zellij))
27
+ - 💤 **Background Claude** - `c` runs Claude as a background session and the tab only shows it: close the tab (`x`) and Claude keeps working, press `c` to come back to the same conversation
26
28
  - 📦 **Finish Flow with Archives** - Ending a task saves its combined diff (committed-but-unmerged + uncommitted) and the task-folder files outside the worktrees (plans, ADRs, notes) to an archive before deleting
27
29
 
28
30
  ## 📊 Status Indicators
@@ -76,72 +78,93 @@ For detailed workflows and examples, see the [User Guide](docs/user-guide.md).
76
78
 
77
79
  ### Headless CLI
78
80
 
79
- Running `tasktree-manager` with arguments skips the TUI — useful for scripts and AI-agent
80
- workflows (e.g. a skill that creates a Jira ticket and then the matching task):
81
+ Running `tasktree-manager` with arguments skips the TUI. Use it from scripts and AI agents,
82
+ for example a skill that creates a Jira ticket and then the matching task.
83
+
84
+ #### Tasks
81
85
 
82
86
  ```bash
83
- tasktree-manager create DIC-1901-argocd-tls --repos backend,frontend # --base overrides the default base branch
84
- tasktree-manager list --json # tasks with repos and state: clean / dirty / error (plain text without --json)
85
- tasktree-manager repos # available repos in REPOS_DIR
87
+ tasktree-manager create DIC-1901-argocd-tls --repos backend,frontend # --base sets the base branch
86
88
  tasktree-manager add-repo DIC-1901-argocd-tls infra
87
- tasktree-manager status # current task inferred from $PWD; also --json / --oneline / --forge
88
- tasktree-manager finish DIC-1901-argocd-tls # safety sweep -> archive diff + task files -> delete; --push / --no-archive / --force
89
- tasktree-manager delete DIC-1901-argocd-tls # archive diff + task files -> delete; refuses on unfinished work; --force overrides
89
+ tasktree-manager list --json # tasks, repos and state: clean / dirty / error
90
+ tasktree-manager repos # repos available in REPOS_DIR
91
+ tasktree-manager status # task of the current folder; --json, --oneline, --forge
92
+ tasktree-manager finish DIC-1901-argocd-tls # safety check, archive, delete; --push, --no-archive
93
+ tasktree-manager delete DIC-1901-argocd-tls # archive, delete; refuses unfinished work
90
94
  ```
91
95
 
92
- Epics group related tasks, for example a Jira epic and its child tasks. An epic is a task
93
- folder with a `.tasktree_kind` marker. It can hold its own notes, Claude session and
94
- worktrees. A child task names its epic in `.tasktree_epic`. Folders never move:
96
+ `status --oneline` prints a compact `TASK repoA✓ repoB●2↑1` line for a Claude Code
97
+ statusline or a shell prompt.
98
+
99
+ #### Epics
100
+
101
+ An epic groups related tasks, for example a Jira epic and its child tasks. It is a task
102
+ folder too, so it can hold notes, a Claude session and worktrees. Tasks stay in their own
103
+ folders; a marker file names their epic. Epics don't nest.
95
104
 
96
105
  ```bash
97
- tasktree-manager epic create DIC-2466-pcaps-epic # --repos is optional for an epic
106
+ tasktree-manager epic create DIC-2466-pcaps-epic # repos are optional for an epic
98
107
  tasktree-manager create DIC-2861-pcaps-logs --repos infra --epic DIC-2466-pcaps-epic
99
- tasktree-manager epic add DIC-2466-pcaps-epic DIC-2862-pcaps-s3 # attach or move existing tasks
100
- tasktree-manager epic remove DIC-2862-pcaps-s3 # detach
101
- tasktree-manager epic mark DIC-2466-pcaps-epic # turn an existing task into an epic; unmark reverts
102
- tasktree-manager epic list # epics and their tasks; also --json
103
- tasktree-manager epic note "chose layout A for the S3 keys" # add a note to this task's file in the epic log
104
- tasktree-manager epic log # notes and latest session recap of every task in the epic
105
- tasktree-manager epic log DIC-2862-pcaps-s3 --json # one task (or a whole epic by its name), as JSON
106
- tasktree-manager epic ask DIC-2862-pcaps-s3 "which bucket?" # a read-only copy of that task's latest session answers
107
- tasktree-manager epic sync # refresh the CLAUDE.md epic block after an upgrade
108
+ tasktree-manager epic add DIC-2466-pcaps-epic DIC-2862-pcaps-s3 # attach or move tasks
109
+ tasktree-manager epic remove DIC-2862-pcaps-s3 # detach
110
+ tasktree-manager epic mark DIC-2466-pcaps-epic # make an existing task an epic; unmark reverts
111
+ tasktree-manager epic list # epics and their tasks; --json
108
112
  ```
109
113
 
110
- Each task's `CLAUDE.md` gets a managed block naming its epic and how to find sibling
111
- tasks; it follows every epic change and leaves the rest of the file alone. The block
112
- also tells the session about the epic log: one notes file per task in `epic-log/` in the
113
- epic folder, written with `epic note` and read with `epic log`, which adds the recap of each
114
- task's latest Claude session (in the task folder or one of its worktrees). Inside a task
115
- folder, `epic note` writes only that task's notes and `epic log` reads only its epic;
116
- `epic note -` reads the text from stdin. Notes of a task that left the epic stay readable.
117
- `epic ask TASK "question"` resumes a copy of that task's latest Claude session to answer:
118
- forked and not saved, hooks off, only the Read/Grep/Glob tools, no MCP servers, so the
119
- task's own session and status stay untouched; credential files (`~/.ssh`, `~/.aws`, `.env`
120
- and similar) are denied to it. It exits 3 without asking when the task's session is
121
- running; the calling session then messages it with SendMessage (`--fork` asks a copy
122
- anyway). Each turn of the copy is a model call on that session's context, at most
123
- `--max-turns` (default 8); `--model sonnet` makes it cheaper.
124
- Epics don't
125
- nest. `list --json` and `status --json` carry `kind` (`task` or `epic`) and
126
- `epic` (the parent epic or `null`). `delete` and `finish` refuse an epic that still has
127
- tasks, even with `--force`.
128
-
129
- Exit code is 0 on success, 1 on failure with the reason on stderr, and 3 from `epic ask`
130
- when the task's session is running (its name goes to stdout). `delete` and `finish` run
131
- the same safety check as the TUI (uncommitted, unpushed, or unmerged work blocks deletion);
132
- with `glab`/`gh` installed, squash/rebase-merged branches are recognized as merged. If the
133
- diff cannot be archived for any repo, the CLI stops before deleting and prints the path of
134
- the partial archive; the TUI warns and deletes anyway. Files in the task folder outside the
135
- worktrees are copied to `<task>-<timestamp>.files/` next to the patch:
136
-
137
- - Skipped silently: hidden files, symlinks, `node_modules`/`vendor`/`.terraform` folders and
138
- an unedited generated `CLAUDE.md`.
139
- - Left out with a warning: key material (`*.pem`, `*.key`, `id_rsa`, kubeconfigs,
140
- `*.tfstate`, `credentials*.json`), files over 10 MB and files past 100 MB in total.
141
- - A file that cannot be read or copied stops `delete`/`finish` before anything is removed,
142
- unless `--force`; the TUI warns and deletes anyway.
143
- `status --oneline` emits a compact `TASK repoA✓ repoB●2↑1` summary that slots straight into
144
- a Claude Code statusline or shell prompt.
114
+ `list --json` and `status --json` show each task's `kind` (`task` or `epic`) and its `epic`.
115
+ `delete` and `finish` refuse an epic that still has tasks, even with `--force`.
116
+
117
+ #### Sharing context inside an epic
118
+
119
+ The Claude sessions of one epic can learn from each other:
120
+
121
+ | Command | What it does |
122
+ | --- | --- |
123
+ | `epic note "text"` | Adds a note to this task's file in the epic log (`-` reads stdin) |
124
+ | `epic log [TASK]` | Shows each task's notes and the recap of its latest Claude session; `--json` |
125
+ | `epic ask TASK "question"` | A read-only copy of that task's latest session answers |
126
+ | `epic sync` | Refreshes the epic block in existing tasks' `CLAUDE.md` after an upgrade |
127
+
128
+ Each task's `CLAUDE.md` gets a managed block that explains these commands to its session.
129
+
130
+ - **Scope:** inside a task folder, the commands reach only that task's epic.
131
+ - **Running sessions:** `epic ask` exits with code 3 when the task's session is running and
132
+ prints its name; the asking session then sends it a message with SendMessage.
133
+ - **Read-only:** the copy that answers can read files but not change them, can't read
134
+ credential files, and leaves the asked task's session untouched.
135
+ - **Cost:** each turn of the copy is a model call on that session's context, 8 turns at
136
+ most (`--max-turns`); `--model sonnet` makes it cheaper.
137
+
138
+ The [User Guide](docs/user-guide.md#epics) has the details.
139
+
140
+ #### Exit codes
141
+
142
+ | Code | Meaning |
143
+ | --- | --- |
144
+ | 0 | Success |
145
+ | 1 | Failure; the reason is on stderr |
146
+ | 3 | `epic ask` only: the task's session is running; its name is on stdout |
147
+
148
+ #### Safe delete and archive
149
+
150
+ `delete` and `finish` run the same safety check as the TUI: uncommitted, unpushed or
151
+ unmerged work blocks deletion unless `--force`. With `glab` or `gh` installed, squash and
152
+ rebase merges count as merged.
153
+
154
+ Before deleting, the CLI archives the task's remaining diff as a patch and copies the files
155
+ in the task folder outside the worktrees to `<task>-<timestamp>.files/` next to it
156
+ (`finish --no-archive` skips both):
157
+
158
+ - **Skipped:** hidden files, symlinks, `node_modules`, `vendor` and `.terraform` folders,
159
+ and an unedited generated `CLAUDE.md`.
160
+ - **Left out with a warning:** key material (`*.pem`, `*.key`, `id_rsa`, kubeconfigs,
161
+ `*.tfstate`, `credentials*.json`), files over 10 MB, and files past 100 MB in total.
162
+ - **Always stops the delete:** a diff that cannot be archived; the CLI prints the partial
163
+ archive's path.
164
+ - **Stops the delete unless `--force`:** a task file that cannot be read or copied.
165
+
166
+ In both cases the TUI stops too, names what was left out in the messages panel, and asks
167
+ again before it deletes.
145
168
 
146
169
  ## 📖 Documentation
147
170
 
@@ -161,6 +184,7 @@ a Claude Code statusline or shell prompt.
161
184
  | `p` | Push all | `o` | Open folder |
162
185
  | `P` | Pull all | `y` | Clone task |
163
186
  | `c` | Claude (resume) | `C` | Claude (new) |
187
+ | `x` | Close Claude tab | | |
164
188
  | `b` | Dispatch agent | `D` | Delete worktree |
165
189
  | `r` | Refresh | `s` | Sort tasks |
166
190
  | `S` | Group worktrees | `m` | Messages |
@@ -227,8 +251,9 @@ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
227
251
  - Git 2.0+
228
252
  - 256-color terminal
229
253
  - Unix-like OS (macOS, Linux, WSL2). Opening a folder, editor or Claude session in a
230
- new terminal tab (`o`, `e`, `c`) drives Ghostty via AppleScript and is macOS-only;
231
- elsewhere those keys show a notice
254
+ new terminal tab (`o`, `e`, `c`) opens a zellij tab when the TUI runs inside zellij,
255
+ on any OS. Otherwise it drives Ghostty via AppleScript and is macOS-only; elsewhere
256
+ those keys show a notice
232
257
 
233
258
  See [Installation Guide](docs/installation.md) for detailed requirements and setup.
234
259
 
@@ -202,6 +202,12 @@ claude_repo_memory = true
202
202
  # Default: true
203
203
  claude_recap = true
204
204
 
205
+ # Run the `c` key's Claude as a background session (`claude --bg`) and show it
206
+ # in a tab running `claude attach`: closing the tab leaves Claude working, and
207
+ # `c` again reopens the same conversation. false runs Claude inside the tab.
208
+ # Default: true
209
+ claude_background = true
210
+
205
211
  # Path to glab executable (GitLab CLI, used for MR/CI status)
206
212
  # Default: "glab" (searches PATH)
207
213
  glab_path = "glab"
@@ -210,6 +216,14 @@ glab_path = "glab"
210
216
  # Default: "gh" (searches PATH)
211
217
  gh_path = "gh"
212
218
 
219
+ # zellij: o/e/c open zellij tabs whenever the TUI runs inside zellij. With
220
+ # autostart, a TUI started outside zellij re-launches itself inside session
221
+ # zellij_session, so its tabs survive a terminal restart (see the user guide).
222
+ # Defaults: "zellij", false, "tasktree" (letters, digits, '.', '_', '-')
223
+ zellij_path = "zellij"
224
+ zellij_autostart = false
225
+ zellij_session = "tasktree"
226
+
213
227
  # ============================================================================
214
228
  # Forge (MR/PR state via glab/gh)
215
229
  # ============================================================================
@@ -260,6 +274,7 @@ refresh = "r" # Refresh git status
260
274
 
261
275
  # Claude Code integration
262
276
  open_claude_resume = "c" # Open Claude CLI (resume if a session exists, else new)
277
+ close_claude_tab = "x" # Close the Claude tab (zellij); Claude keeps running
263
278
  open_claude_gui_code = "C" # Open Claude desktop on Code page in task folder (shift+c)
264
279
  dispatch_agent = "b" # Dispatch background Claude agent in selected worktree
265
280
 
@@ -419,6 +434,10 @@ refresh_interval = 15 # Refresh every 15 seconds
419
434
  | `hunk_path` | string | `"hunk"` | - | Path to hunk diff-viewer executable |
420
435
  | `glab_path` | string | `"glab"` | - | Path to GitLab CLI (MR/CI status) |
421
436
  | `gh_path` | string | `"gh"` | - | Path to GitHub CLI (PR/CI status) |
437
+ | `claude_background` | bool | `true` | - | `c` runs Claude in the background |
438
+ | `zellij_path` | string | `"zellij"` | - | Path to zellij (tabs and autostart) |
439
+ | `zellij_autostart` | bool | `false` | - | Re-launch the TUI inside zellij |
440
+ | `zellij_session` | string | `"tasktree"` | - | zellij session for autostart |
422
441
 
423
442
  ## Forge Settings
424
443
 
@@ -593,7 +612,7 @@ blocklist = ["*"]
593
612
 
594
613
  ## Keybindings Reference
595
614
 
596
- All 27 customizable keybindings with descriptions:
615
+ All 28 customizable keybindings with descriptions:
597
616
 
598
617
  ### Application Control
599
618
 
@@ -631,6 +650,7 @@ All 27 customizable keybindings with descriptions:
631
650
  | Action | Default | Description | Customization Example |
632
651
  |-----------------------|---------|------------------------------------------------------|--------------------------------------|
633
652
  | `open_claude_resume` | `c` | Open Claude CLI (resume if session exists, else new) | `open_claude_resume = "ctrl+c"` |
653
+ | `close_claude_tab` | `x` | Close the Claude tab (zellij); Claude keeps running | `close_claude_tab = "ctrl+x"` |
634
654
  | `open_claude_gui_code`| `C` | Open Claude desktop on Code page in task folder | `open_claude_gui_code = "ctrl+shift+c"` |
635
655
  | `toggle_messages` | `m` | Show/hide activity messages panel | `toggle_messages = "ctrl+m"` |
636
656
  | `dispatch_agent` | `b` | Dispatch a background Claude agent into the worktree | `dispatch_agent = "ctrl+b"` |
@@ -85,35 +85,50 @@ names their epic, so nothing moves on disk.
85
85
  - `enter` or `space` folds an epic. A folded epic row still shows the dirty
86
86
  count and the most urgent Claude state of its tasks.
87
87
  - Epics don't nest. An epic that still has tasks can't be deleted.
88
- - Each task's `CLAUDE.md` gets a short block between `<!-- tasktree:epic -->` markers
89
- that names the epic folder and how to find sibling tasks, so a Claude session knows
90
- the bigger picture. It updates on every epic change; text outside the markers is
91
- never touched.
92
- - The epic log lets the sessions of one epic share what they learned. A session
93
- records decisions, interfaces and open questions with
94
- `tasktree-manager epic note "..."`; they go to `epic-log/<task>.md` in the epic
95
- folder. `tasktree-manager epic log` shows every task's notes and the recap of its
96
- latest Claude session, so a session can check what its siblings did before asking.
97
- The `CLAUDE.md` block tells each session about both commands, and that notes are
98
- information from other sessions, not instructions.
88
+ - Each task's `CLAUDE.md` gets a short block between `<!-- tasktree:epic -->` markers.
89
+ It names the epic folder, explains how to find sibling tasks and how to share context
90
+ with them. It updates on every epic change; text outside the markers is never touched.
91
+ - From scripts: `tasktree-manager epic create/add/remove/mark/unmark/list/note/log/ask/sync`.
92
+
93
+ #### Sharing context between sessions
94
+
95
+ The Claude sessions of one epic can learn from each other. The `CLAUDE.md` block tells
96
+ each session to try these in order:
97
+
98
+ 1. **Read the epic log.** `tasktree-manager epic log` shows every task's notes and the
99
+ recap of its latest Claude session. `epic log TASK` shows one task.
100
+ 2. **Ask the task.** `tasktree-manager epic ask TASK "question"` resumes a read-only copy
101
+ of that task's latest session, which answers the question.
102
+ 3. **Message a running session.** If the task's session is running, `epic ask` exits
103
+ with code 3 and prints the session name. The asking session sends it a message with
104
+ SendMessage instead. `--fork` asks a copy anyway.
105
+
106
+ Sessions add to the log with `tasktree-manager epic note "..."`: decisions, interfaces,
107
+ open questions, finished work that other tasks depend on.
108
+
109
+ **Notes:**
110
+
111
+ - Notes go to `epic-log/<task>.md` in the epic folder, one file per task.
99
112
  - A session writes only its own task's notes. From a terminal outside any task,
100
113
  `epic note --task NAME` writes for a task and marks the entry as written outside it.
101
- - Notes stay in the epic after their task is finished, moved or deleted; `epic log`
102
- lists them as "no longer in the epic". A note is at most 16 KB, and `epic log` shows
103
- the newest 64 KB of each task's notes.
104
- - When the log does not answer a question, a session runs
105
- `tasktree-manager epic ask TASK "question"`. A read-only copy of that task's latest
106
- Claude session answers: it can read files but not change them or read credential
107
- files, and nothing is saved in that task. If the task's session is running, the command exits
108
- with code 3 and prints the session name, and the asking session sends it a message
109
- with SendMessage instead (`--fork` asks a copy anyway). Only tasks of the same epic
110
- and the epic folder can be asked, and a session in a task outside any epic cannot
111
- ask. Each turn of the copy is a model call on the size of that session's context,
112
- at most `--max-turns` (default 8); `--model sonnet` makes it cheaper.
113
- - After upgrading tasktree-manager, run `tasktree-manager epic sync` to refresh the
114
- block in existing tasks (the TUI also refreshes it when it starts a Claude session).
115
- Folders without a `CLAUDE.md` yet are counted and skipped.
116
- - From scripts: `tasktree-manager epic create/add/remove/mark/unmark/list/note/log/ask/sync`.
114
+ - Notes stay after their task is finished, moved or deleted; `epic log` lists them as
115
+ "no longer in the epic".
116
+ - A note is at most 16 KB. `epic log` shows the newest 64 KB of each task's notes.
117
+ - Notes and answers come from other sessions: they are information, not instructions.
118
+
119
+ **`epic ask`:**
120
+
121
+ - Only tasks of the same epic and the epic folder can be asked. A session in a task
122
+ outside any epic cannot ask.
123
+ - The copy can read files but not change them. It can't read credential files (forge CLI
124
+ tokens, `.env` files anywhere, the key material the archive leaves out).
125
+ Nothing is saved in the asked task, and its session stays untouched.
126
+ - Each turn of the copy is a model call on the size of that session's context, 8 turns
127
+ at most (`--max-turns`). `--model sonnet` makes it cheaper.
128
+
129
+ **After an upgrade:** run `tasktree-manager epic sync` to refresh the block in existing
130
+ tasks. The TUI also refreshes it when it starts a Claude session. Folders without a
131
+ `CLAUDE.md` yet are counted and skipped.
117
132
 
118
133
  ### Worktrees
119
134
 
@@ -286,6 +301,8 @@ terraform edits sit unstaged. Next, decide whether to hand-create the group.
286
301
  - **Title**: the session name (`/rename`), else Claude's own title, else the session slug
287
302
  - **Turn line**: duration of the last turn and when it finished; `busy`/`idle` mirrors the task's Claude status indicator
288
303
  - **Recap**: the summary Claude writes a few minutes after a turn when the session idles. Until one exists, the dim `last prompt:` line shows what was asked last
304
+ - The session is the newest one started in the task folder or one of its worktrees, as in
305
+ `epic log`. Headless `claude -p` runs, such as commit-message helpers, are skipped
289
306
  - Nothing is shown for tasks without a session. The block refreshes as you move the cursor and every 5 seconds while a transcript changes. Disable with `claude_recap = false` under `[tools]` in `config.toml`
290
307
 
291
308
  **Git status codes:**
@@ -434,13 +451,29 @@ Useful when you're spinning up several similar tasks that share the same set of
434
451
 
435
452
  | Key | Action | Description |
436
453
  |---------|-------------------------|------------------------------------------------------------|
437
- | `c` | Open Claude CLI | Resume the task's Claude Code session if one exists, otherwise start a new one |
454
+ | `c` | Open Claude CLI | Open the task's Claude session in a tab: reattach, resume the last one, or start a new one |
455
+ | `x` | Close Claude tab | Close the Claude tab (zellij only); Claude keeps working in the background |
438
456
  | `C` | Open Claude desktop | Open the Claude desktop app on the Code page at the task folder |
439
457
  | `b` | Dispatch agent | Run a background Claude agent (`claude --bg`) in the selected worktree |
440
458
 
441
459
  **How it works:**
442
- - `c` opens Claude Code CLI in a new Ghostty terminal tab at the task directory.
443
- - If `~/.claude/projects/<task-folder>/` already has session transcripts, runs `claude -r` to pick from them; otherwise runs `claude` to start fresh.
460
+ - `c` runs Claude as a background session (`claude --bg`) in the task folder and opens a
461
+ terminal tab that only shows it (`claude attach`). A zellij tab inside zellij, else a
462
+ Ghostty tab; see [zellij](#zellij). Closing a zellij tab leaves Claude working; the
463
+ same with a Ghostty tab is not tested yet.
464
+ - A background session already in the task folder is attached, also a finished one.
465
+ - Otherwise the folder's last session is resumed in the background (`claude --bg
466
+ --resume`), or a new one starts. It waits for your first message; nothing runs before.
467
+ - With the worktree panel focused, `c` opens the agent `b` dispatched to that worktree,
468
+ if one exists.
469
+ - Claude already open in the task folder in another terminal is left alone.
470
+ - When `claude agents` or `claude --bg` fails (for example in an untrusted folder),
471
+ or with `claude_background = false`, Claude runs inside the tab: `claude -r` when
472
+ the folder has transcripts, else `claude`.
473
+ - Deleting a task asks first while a background session in it is working or waiting for
474
+ you, then stops and removes its sessions (`claude stop`, `claude rm`) before the
475
+ archive. Transcripts stay, so cancelling the delete later still lets `c` resume. A
476
+ session that will not stop keeps the task (the CLI goes on with `--force`).
444
477
  - `C` opens the Claude desktop app via the `claude://code/new?folder=<task-folder>` URL scheme.
445
478
  - The CLI variant (`c`) generates a task-level `CLAUDE.md` with task/worktree context, copies each repo's own `CLAUDE.md` into worktrees whose branch predates it (never generating stub content), and installs hooks that report session status back to tasktree-manager.
446
479
  - Session status (`⟳` running, `!` waiting, `✓` ended) appears next to the task name
@@ -840,9 +873,9 @@ npm i -g hunkdiff
840
873
 
841
874
  **Direct editor integration:**
842
875
  1. Highlight task or worktree
843
- 2. Press `e` to open your configured editor in a new Ghostty tab (`$EDITOR .`)
844
- 3. tasktree-manager keeps running alongside the editor (macOS + Ghostty only, see
845
- Limitations)
876
+ 2. Press `e` to open your configured editor in a new terminal tab (`$EDITOR .`)
877
+ 3. tasktree-manager keeps running alongside the editor (a zellij tab inside zellij,
878
+ otherwise a Ghostty tab on macOS; see [zellij](#zellij))
846
879
 
847
880
  **Configure your editor** in `~/.config/tasktree-manager/config.toml`:
848
881
  ```toml
@@ -873,6 +906,60 @@ code ~/tasks/FEAT-123 # Open entire task as workspace
873
906
  - Use "Open Folder" or equivalent
874
907
  - Git integration works normally
875
908
 
909
+ ### zellij
910
+
911
+ `o`, `e` and `c` open a new terminal tab. Where the tab lives depends on where
912
+ tasktree-manager runs:
913
+
914
+ - Inside a zellij session (`ZELLIJ_SESSION_NAME` is set), each key opens a zellij tab
915
+ named after the folder and what runs in it: `TASK · shell`, `TASK/repo · editor`,
916
+ `TASK · claude`. The tab runs in the zellij server, so it keeps running when you close
917
+ or restart the terminal. Pressing the key again while that tab is open switches to it
918
+ instead of opening a second one.
919
+ - Anywhere else, each key opens a Ghostty tab via AppleScript (macOS only).
920
+
921
+ To run the TUI in zellij every time, set this in `config.toml`:
922
+
923
+ ```toml
924
+ [tools]
925
+ zellij_autostart = true
926
+ zellij_session = "tasktree" # the default
927
+ ```
928
+
929
+ Plain `tasktree-manager` then re-launches itself as
930
+ `zellij attach -c tasktree -- python -m tasktree_manager.app`. Without the setting, start
931
+ it that way yourself: `zellij attach -c tasktree -- tasktree-manager`.
932
+
933
+ After a terminal restart, run `tasktree-manager` (with autostart) or
934
+ `zellij attach tasktree`. Either brings back the TUI and every tab it opened, Claude
935
+ sessions included. The command after `--` runs only when the session is created, so a
936
+ second `tasktree-manager` in another terminal tab shows the same session.
937
+
938
+ **Too many tabs.** The `c` tab only shows a background Claude session, so it can close
939
+ while Claude keeps working:
940
+
941
+ 1. Close the tab with `x` in tasktree-manager, or with zellij's own close-tab key.
942
+ 2. The task row keeps its Claude status (`⟳` running, `!` waiting). When a turn finishes
943
+ while the tab is closed, a notice says so.
944
+ 3. Press `c` on the task again: the tab reopens on the same conversation.
945
+
946
+ `x` closes only a tab whose panes run `claude attach` (zellij's pane list shows each
947
+ pane's launch command). A tab that runs Claude itself
948
+ (`claude_background = false`, or a tab from an older version) stays open, since closing
949
+ it would stop Claude.
950
+
951
+ - A tab that runs a command (`e`, `c`) closes when the command exits.
952
+ - The zellij tab runs the command with `sh -c`, so `editor` and `claude_path` must name
953
+ programs on `PATH`. A shell alias or fish function works only in the Ghostty tab.
954
+ - A zellij session does not survive a reboot. Attaching afterwards (or autostart) makes
955
+ zellij resurrect the saved layout, and each tab waits for ENTER before its command runs
956
+ again; tasktree-manager does not test that path.
957
+ - Autostart applies only when the TUI starts in a terminal outside zellij, never to a
958
+ piped run or to the headless CLI.
959
+ - zellij's default keybindings take Ctrl+P, Ctrl+T, Ctrl+O and other keys before the TUI
960
+ or Claude Code sees them. Pick zellij's "Unlock First (non-colliding)" preset or Alt-based
961
+ bindings, so Ctrl+P still opens the command palette.
962
+
876
963
  ### CI/CD
877
964
 
878
965
  tasktree-manager creates normal git branches, so CI/CD works seamlessly:
@@ -71,7 +71,7 @@ version-file = "tasktree_manager/_version.py"
71
71
  # Semantic Release Configuration
72
72
  [tool.semantic_release]
73
73
  version_toml = ["pyproject.toml:tool.semantic_release.version"]
74
- version = "2.18.0"
74
+ version = "2.20.0"
75
75
  tag_format = "v{version}"
76
76
  branch = "main"
77
77
  commit_message = "chore(release): {version} [skip ci]"
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.18.0'
22
- __version_tuple__ = version_tuple = (2, 18, 0)
21
+ __version__ = version = '2.20.0'
22
+ __version_tuple__ = version_tuple = (2, 20, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None