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    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}