1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// SPDX-License-Identifier: Apache-2.0
//! Thread command definitions.
use clap::{Args, Subcommand};
use super::{
ThreadAbsorbArgs, ThreadApprovalsArgs, ThreadApproveArgs, ThreadCapturesArgs,
ThreadCheckMergeArgs, ThreadDropArgs, ThreadMoveArgs, ThreadNameArgs, ThreadPromoteArgs,
ThreadRenameArgs, ThreadResolveArgs, ThreadRevokeApprovalArgs, ThreadShowArgs,
};
#[derive(Subcommand, Clone)]
pub enum ThreadCommands {
/// Create a thread ref at the current state.
#[command(after_help = "\
Advanced split form:
heddle start <name> --path <dir> is the normal one-step isolated-checkout path.
heddle thread create <name> only creates the thread ref. Pair it later with
heddle thread promote <name> --path <dir> when you intentionally need to
create the ref now and materialize the checkout later.
")]
Create {
/// Thread identifier.
name: String,
/// Mark the thread ephemeral. Auto-collapses after `--ttl` if not
/// promoted. The collapse is recorded as
/// `OpRecord::EphemeralThreadCollapse`; underlying states stay
/// addressable. (W1/A13.)
#[arg(long)]
ephemeral: bool,
/// TTL in seconds. Defaults to 24h when `--ephemeral` is set
/// without `--ttl`.
#[arg(long, requires = "ephemeral")]
ttl_secs: Option<u32>,
},
/// Print the name of the current thread (the thread the working
/// checkout is attached to). Read-only — no state change.
/// Useful in shell pipelines: `cd "$(heddle thread cd "$(heddle thread current)")"`.
Current,
/// Switch the current checkout to an existing thread ref.
Switch {
/// Thread identifier.
name: String,
/// Print only the target thread's checkout path on stdout and
/// exit. Used by the shell hook (`heddle shell init`) to auto-cd
/// into the new thread:
/// dir=$(heddle thread switch X --print-cd-path) && cd "$dir"
/// Auto-capture still runs; rich output is suppressed.
#[arg(long, hide_short_help = true)]
print_cd_path: bool,
/// Discard uncommitted changes in the current checkout before switching.
#[arg(short, long)]
force: bool,
},
/// Print the on-disk path for a thread. Read-only — no state change,
/// no auto-capture. Pair with the shell hook (`heddle shell init`)
/// to land in the right directory:
/// eval "$(heddle thread cd X)"
/// Or use the shell function directly: `heddle thread cd X` becomes
/// `cd <path>` when the hook is installed.
Cd {
/// Thread identifier.
name: String,
},
/// List threads.
List(ThreadListArgs),
/// Show one thread with actor and workflow context.
Show(ThreadShowArgs),
/// Show granular captures on a thread.
Captures(ThreadCapturesArgs),
/// Rename a thread ref.
Rename(ThreadRenameArgs),
/// Refresh a thread onto its target thread.
Refresh(ThreadNameArgs),
/// Move selected captured paths from one thread into another.
Move(ThreadMoveArgs),
/// Absorb a child thread into its parent or another thread.
Absorb(ThreadAbsorbArgs),
/// Guide a blocked or stale thread toward its next clean state.
Resolve(ThreadResolveArgs),
/// Materialize an existing thread ref at a chosen path.
#[command(after_help = "\
Advanced split form:
heddle start <name> --path <dir> creates the thread ref and isolated checkout
in one step. `thread promote` is the second step after
`heddle thread create <name>` when you intentionally created the ref first
and want to materialize it later.
")]
Promote(ThreadPromoteArgs),
/// Drop a thread and mark it abandoned.
#[command(visible_alias = "delete")]
Drop(ThreadDropArgs),
/// Record a merge approval for `<source> -> <target>`.
Approve(ThreadApproveArgs),
/// List approvals recorded for `<source> -> <target>`.
Approvals(ThreadApprovalsArgs),
/// Revoke a previously recorded approval by id.
RevokeApproval(ThreadRevokeApprovalArgs),
/// Check whether `<source> -> <target>` would merge under
/// the repo's branch-protection policies. Read-only.
CheckMerge(ThreadCheckMergeArgs),
/// Sweep merged or stale auto-created threads.
#[command(
long_about = "\
Sweep threads that have outlived their usefulness. Cleanup removes recorded checkouts, marks matching thread records abandoned, and prunes live thread refs so everyday thread lists stay focused.
Modes:
- --merged: clean up threads recorded as merged.
- --auto --older-than <duration>: clean up harness-created threads that have not been touched in the given duration.
The two modes can be combined. Pair with --dry-run to preview the work without changing anything on disk.",
after_help = "\
Examples:
heddle thread cleanup --merged --dry-run
heddle thread cleanup --merged
heddle thread cleanup --auto --older-than 7d --dry-run
"
)]
Cleanup(ThreadCleanupArgs),
/// Manage named state markers under the thread namespace.
Marker {
#[command(subcommand)]
command: ThreadMarkerCommands,
},
}
#[derive(Subcommand, Clone)]
pub enum ThreadMarkerCommands {
/// List markers, optionally filtered by name prefix.
///
/// Pass `--filter <PREFIX>` to return only markers whose name
/// starts with the given prefix. The match is a literal
/// `starts_with` check, not a glob.
List {
/// Return only markers whose name starts with this prefix.
#[arg(long, value_name = "PREFIX")]
filter: Option<String>,
},
/// Create marker at current state.
Create {
/// Marker name.
name: String,
},
/// Delete marker(s).
///
/// Pass an exact marker name, or `--prefix <PFX>` to delete every marker
/// whose name starts with the given prefix. Exactly one of `<NAME>` or
/// `--prefix` must be supplied.
Delete {
/// Marker name (exact match). Mutually exclusive with `--prefix`.
#[arg(required_unless_present = "prefix", conflicts_with = "prefix")]
name: Option<String>,
/// Delete every marker whose name starts with this prefix.
#[arg(long)]
prefix: Option<String>,
},
/// Show marker details.
Show {
/// Marker name.
name: String,
},
}
/// Arguments for `heddle thread list`.
///
/// The default view hides harness-auto-created threads (those marked
/// with `auto: true` on disk). Pass `--include-auto` to surface them.
#[derive(Args, Clone, Debug, Default)]
pub struct ThreadListArgs {
/// Include threads created automatically by harness integrations
/// (e.g. Claude Code segment-rotation). Hidden by default to keep
/// the view focused on threads the user explicitly created.
#[arg(long)]
pub include_auto: bool,
}
/// Arguments for `heddle thread cleanup`.
///
/// At least one of `--merged` or `--auto` must be set; otherwise the
/// command refuses with a clear message. `--older-than` is required
/// when `--auto` is set.
#[derive(Args, Clone, Debug)]
pub struct ThreadCleanupArgs {
/// Clean up threads whose recorded state is `merged`.
#[arg(long)]
pub merged: bool,
/// Drop harness-auto-created threads (those tagged `auto: true`).
/// Combine with `--older-than` to gate the sweep on staleness.
#[arg(long)]
pub auto: bool,
/// Maximum age (since `updated_at`) for an auto-thread to be
/// considered live. Threads older than this are eligible for
/// sweep when `--auto` is set. Accepts a Go-style duration like
/// `7d`, `24h`, `30m`, `15s` (or a raw integer interpreted as
/// seconds).
#[arg(long, value_name = "DURATION")]
pub older_than: Option<String>,
/// Print what would be dropped without actually dropping it.
#[arg(long)]
pub dry_run: bool,
}