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 or stale auto-created 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
129The two modes can be combined. Pair with --dry-run to preview the work without changing anything on disk.",
130 after_help = "\
131Examples:
132 heddle thread cleanup --merged --dry-run
133 heddle thread cleanup --merged
134 heddle thread cleanup --auto --older-than 7d --dry-run
135"
136 )]
137 Cleanup(ThreadCleanupArgs),
138
139 /// Manage named state markers under the thread namespace.
140 Marker {
141 #[command(subcommand)]
142 command: ThreadMarkerCommands,
143 },
144}
145
146#[derive(Subcommand, Clone)]
147pub enum ThreadMarkerCommands {
148 /// List markers, optionally filtered by name prefix.
149 ///
150 /// Pass `--filter <PREFIX>` to return only markers whose name
151 /// starts with the given prefix. The match is a literal
152 /// `starts_with` check, not a glob.
153 List {
154 /// Return only markers whose name starts with this prefix.
155 #[arg(long, value_name = "PREFIX")]
156 filter: Option<String>,
157 },
158
159 /// Create marker at current state.
160 Create {
161 /// Marker name.
162 name: String,
163 },
164
165 /// Delete marker(s).
166 ///
167 /// Pass an exact marker name, or `--prefix <PFX>` to delete every marker
168 /// whose name starts with the given prefix. Exactly one of `<NAME>` or
169 /// `--prefix` must be supplied.
170 Delete {
171 /// Marker name (exact match). Mutually exclusive with `--prefix`.
172 #[arg(required_unless_present = "prefix", conflicts_with = "prefix")]
173 name: Option<String>,
174
175 /// Delete every marker whose name starts with this prefix.
176 #[arg(long)]
177 prefix: Option<String>,
178 },
179
180 /// Show marker details.
181 Show {
182 /// Marker name.
183 name: String,
184 },
185}
186
187/// Arguments for `heddle thread list`.
188///
189/// The default view hides harness-auto-created threads (those marked
190/// with `auto: true` on disk). Pass `--include-auto` to surface them.
191#[derive(Args, Clone, Debug, Default)]
192pub struct ThreadListArgs {
193 /// Include threads created automatically by harness integrations
194 /// (e.g. Claude Code segment-rotation). Hidden by default to keep
195 /// the view focused on threads the user explicitly created.
196 #[arg(long)]
197 pub include_auto: bool,
198}
199
200/// Arguments for `heddle thread cleanup`.
201///
202/// At least one of `--merged` or `--auto` must be set; otherwise the
203/// command refuses with a clear message. `--older-than` is required
204/// when `--auto` is set.
205#[derive(Args, Clone, Debug)]
206pub struct ThreadCleanupArgs {
207 /// Clean up threads whose recorded state is `merged`.
208 #[arg(long)]
209 pub merged: bool,
210
211 /// Drop harness-auto-created threads (those tagged `auto: true`).
212 /// Combine with `--older-than` to gate the sweep on staleness.
213 #[arg(long)]
214 pub auto: bool,
215
216 /// Maximum age (since `updated_at`) for an auto-thread to be
217 /// considered live. Threads older than this are eligible for
218 /// sweep when `--auto` is set. Accepts a Go-style duration like
219 /// `7d`, `24h`, `30m`, `15s` (or a raw integer interpreted as
220 /// seconds).
221 #[arg(long, value_name = "DURATION")]
222 pub older_than: Option<String>,
223
224 /// Print what would be dropped without actually dropping it.
225 #[arg(long)]
226 pub dry_run: bool,
227}