Skip to main content

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}