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