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