1#[cfg(feature = "git-overlay")]
5use super::commands_git_projection::SyncCommands;
6
7pub const INIT_VERB: &str = "init";
13
14#[derive(Clone, Debug, clap::Args)]
16#[command(after_help = "\
17Examples:
18 heddle init # initialize here; existing Git becomes Git Overlay
19 heddle init my-project # initialize a native Heddle subdirectory
20 heddle init --principal-name 'Ada Lovelace' --principal-email ada@example.com
21")]
22pub struct InitArgs {
23 pub path: Option<std::path::PathBuf>,
25
26 #[arg(long)]
28 pub principal_name: Option<String>,
29
30 #[arg(long)]
32 pub principal_email: Option<String>,
33
34 #[arg(long)]
36 pub install_harnesses: Option<String>,
37
38 #[arg(long)]
40 pub no_harness_install: bool,
41
42 #[arg(long, visible_alias = "scope", default_value = "repo")]
44 pub harness_install_scope: String,
45
46 #[arg(long)]
48 pub harness_install_force: bool,
49}
50
51impl InitArgs {
52 pub const VERB: &'static str = INIT_VERB;
54}
55
56#[derive(Clone, Debug, clap::Args)]
58#[command(after_help = "\
59Examples:
60 heddle adopt # adopt all local Git refs into native Heddle storage
61 heddle adopt --ref main # adopt one branch or tag
62 heddle adopt ../repo --ref main --ref v1.0 # adopt selected refs in another repo
63
64Adoption imports Git refs, makes Heddle the source authority, and retains `.git` for explicit Git Projection. Normal Git Overlay setup uses `heddle init` instead.
65")]
66pub struct AdoptArgs {
67 pub path: Option<std::path::PathBuf>,
69
70 #[arg(long = "ref", value_name = "REF")]
72 pub refs: Vec<String>,
73}
74
75#[derive(Clone, Debug, clap::Args)]
82pub struct DoctorArgs {
83 #[arg(long, global = false)]
88 pub profile: bool,
89
90 #[command(subcommand)]
91 pub command: Option<DoctorCommands>,
92}
93
94#[derive(Clone, Debug, clap::Subcommand)]
96pub enum DoctorCommands {
97 Docs(DoctorDocsArgs),
110
111 Schemas(DoctorSchemasArgs),
121}
122
123#[derive(Clone, Debug, clap::Args)]
125pub struct DoctorDocsArgs {
126 #[arg(long, value_name = "PATH")]
131 pub path: Vec<std::path::PathBuf>,
132
133 #[arg(long)]
135 pub all: bool,
136}
137
138#[derive(Clone, Debug, clap::Args)]
140pub struct DoctorSchemasArgs {
141 #[arg(long)]
144 pub update_docs: bool,
145}
146
147fn parse_confidence(s: &str) -> Result<f32, String> {
148 let value = s
149 .parse::<f32>()
150 .map_err(|_| format!("confidence must be a finite number from 0.0 to 1.0, got `{s}`"))?;
151 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
152 return Err(format!(
153 "confidence must be a finite number from 0.0 to 1.0, got `{s}`"
154 ));
155 }
156 Ok(value)
157}
158
159#[derive(Clone, Debug, clap::Args)]
161#[command(override_usage = "heddle capture -m <INTENT> [OPTIONS]")]
162#[command(after_help = "\
163Examples:
164 heddle capture -m 'add login route' # capture the worktree with intent
165 heddle capture -m 'wip' --confidence 0.6 # honest confidence on a draft step
166
167Agent automation flags (provider/model/session/policy/split) are hidden here.
168Run `heddle help agent-flags`, or `heddle capture --help-agent` to list them inline.
169")]
170pub struct SnapshotArgs {
171 #[arg(long, hide = true)]
181 pub help_agent: bool,
182
183 #[arg(short = 'm', long, visible_alias = "message", value_name = "INTENT")]
185 pub intent: Option<String>,
186
187 #[arg(long, value_parser = parse_confidence)]
189 pub confidence: Option<f32>,
190
191 #[arg(short, long)]
193 pub force: bool,
194
195 #[arg(long, hide = true)]
197 pub agent_provider: Option<String>,
198
199 #[arg(long, hide = true)]
201 pub agent_model: Option<String>,
202
203 #[arg(long, hide = true)]
205 pub agent_session: Option<String>,
206
207 #[arg(long, hide = true)]
209 pub agent_segment: Option<String>,
210
211 #[arg(long, hide = true)]
213 pub policy: Option<String>,
214
215 #[arg(long, hide = true)]
217 pub no_policy: bool,
218
219 #[arg(long, hide = true)]
221 pub no_agent: bool,
222
223 #[arg(long, hide = true)]
225 pub split: bool,
226
227 #[arg(long, hide = true, requires = "split")]
229 pub into: Option<String>,
230
231 #[arg(long = "path", hide = true, requires = "split", value_name = "PATH")]
233 pub paths: Vec<String>,
234}
235
236#[derive(Clone, Debug, clap::Args)]
238#[command(after_help = "\
239Examples:
240 heddle capture -m 'add login route'
241 heddle commit
242 heddle commit -m 'add login route'
243
244Behavior:
245 Commits the complete captured tree and replaces the Git index with that tree.
246 Git pre-commit and commit-msg hooks are not run.
247")]
248pub struct CommitArgs {
249 #[arg(short = 'm', long = "message")]
251 pub message: Option<String>,
252}
253
254#[derive(Clone, Debug, clap::Args)]
256#[command(after_help = "\
257Examples:
258 heddle log # walk the current thread
259 heddle log --oneline -n 20 # 20 most recent states in compact form
260 heddle log --timeline # show agent timeline tool-call cursor
261 heddle log --reflog # include re-attributed history
262 heddle log --path src/auth.rs # restrict to states touching a path
263")]
264pub struct LogArgs {
265 pub state: Option<String>,
267
268 #[arg(short = 'n', long, default_value = "20")]
270 pub limit: usize,
271
272 #[arg(long)]
274 pub all: bool,
275
276 #[arg(long)]
278 pub graph: bool,
279
280 #[arg(long)]
282 pub oneline: bool,
283
284 #[arg(long)]
286 pub reflog: bool,
287
288 #[arg(long)]
290 pub timeline: bool,
291
292 #[arg(long, default_value = "main")]
294 pub thread: String,
295
296 #[arg(long)]
298 pub agent: Option<String>,
299
300 #[arg(long = "path", value_name = "PATH")]
302 pub paths: Vec<String>,
303
304 #[arg(long, value_name = "STATE")]
310 pub since: Option<String>,
311}
312
313#[derive(Clone, Debug, clap::Args)]
315pub struct TimelineArgs {
316 #[command(subcommand)]
317 pub command: TimelineCommands,
318}
319
320#[derive(Clone, Debug, clap::Subcommand)]
322pub enum TimelineCommands {
323 Status(TimelineStatusArgs),
325
326 #[command(name = "record-start")]
328 RecordStart(TimelineRecordStartArgs),
329
330 #[command(name = "record-finish")]
332 RecordFinish(TimelineRecordFinishArgs),
333
334 #[command(after_help = "\
336Examples:
337 heddle timeline fork --step tls-abc --branch tlb-experiment
338 heddle timeline fork --tool-call call_123 --session ses_456 --branch tlb-alt
339")]
340 Fork(TimelineForkArgs),
341
342 #[command(after_help = "\
344Examples:
345 heddle timeline reset --step tls-abc
346 heddle timeline reset --tool-call call_123 --materialize
347")]
348 Reset(TimelineResetArgs),
349
350 Recover(TimelineRecoverArgs),
352}
353
354#[derive(Clone, Debug, clap::Args)]
356pub struct TimelineTargetArgs {
357 #[arg(long, default_value = "main")]
359 pub thread: String,
360
361 #[arg(long = "from-branch", value_name = "BRANCH")]
363 pub from_branch: Option<String>,
364
365 #[arg(long, conflicts_with_all = ["tool_call", "undo", "redo", "current"])]
367 pub step: Option<String>,
368
369 #[arg(long = "tool-call", conflicts_with_all = ["step", "undo", "redo", "current"])]
371 pub tool_call: Option<String>,
372
373 #[arg(long, default_value = "opencode")]
375 pub harness: String,
376
377 #[arg(long)]
379 pub session: Option<String>,
380
381 #[arg(long)]
383 pub message: Option<String>,
384
385 #[arg(long, conflicts_with_all = ["step", "tool_call", "redo", "current"])]
387 pub undo: bool,
388
389 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "current"])]
391 pub redo: bool,
392
393 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "redo"])]
395 pub current: bool,
396}
397
398#[derive(Clone, Debug, clap::Args)]
400pub struct TimelineForkArgs {
401 #[command(flatten)]
402 pub target: TimelineTargetArgs,
403
404 #[arg(long, value_name = "BRANCH")]
406 pub branch: Option<String>,
407
408 #[arg(long, default_value = "explicit-fork")]
410 pub reason: String,
411}
412
413#[derive(Clone, Debug, clap::Args)]
415pub struct TimelineResetArgs {
416 #[command(flatten)]
417 pub target: TimelineTargetArgs,
418
419 #[arg(long)]
421 pub materialize: bool,
422
423 #[arg(long, default_value = "fail-if-dirty")]
425 pub mode: String,
426}
427
428#[derive(Clone, Debug, clap::Args)]
430pub struct TimelineRecoverArgs {
431 #[arg(long, default_value = "main")]
433 pub thread: String,
434}
435
436#[derive(Clone, Debug, clap::Args)]
438pub struct TimelineStatusArgs {
439 #[arg(long, default_value = "main")]
441 pub thread: String,
442}
443
444#[derive(Clone, Debug, clap::Args)]
446pub struct TimelineRecordToolArgs {
447 #[arg(long, default_value = "main")]
449 pub thread: String,
450
451 #[arg(long, default_value = "opencode")]
453 pub harness: String,
454
455 #[arg(long)]
457 pub session: Option<String>,
458
459 #[arg(long)]
461 pub message: Option<String>,
462
463 #[arg(long = "tool-call")]
465 pub tool_call: String,
466
467 #[arg(long = "step-id")]
469 pub step_id: Option<String>,
470
471 #[arg(long = "branch")]
473 pub branch: Option<String>,
474
475 #[arg(long = "summary")]
477 pub summary: Option<String>,
478
479 #[arg(long = "payload-hash")]
481 pub payload_hash: Option<String>,
482}
483
484#[derive(Clone, Debug, clap::Args)]
486pub struct TimelineRecordStartArgs {
487 #[command(flatten)]
488 pub tool: TimelineRecordToolArgs,
489
490 #[arg(long = "tool-name", default_value = "tool")]
492 pub tool_name: String,
493}
494
495#[derive(Clone, Debug, clap::Args)]
497pub struct TimelineRecordFinishArgs {
498 #[command(flatten)]
499 pub tool: TimelineRecordToolArgs,
500
501 #[arg(long, default_value = "succeeded")]
503 pub status: String,
504}
505
506#[derive(Clone, Debug, clap::Args)]
513pub struct RetroArgs {
514 #[arg(long)]
519 pub since: Option<String>,
520
521 #[arg(long)]
524 pub include_merges: bool,
525
526 #[arg(long)]
529 pub include_undos: bool,
530
531 #[arg(long = "full", alias = "expand")]
535 pub full: bool,
536}
537
538#[derive(Clone, Debug, clap::Args)]
540#[command(after_help = "\
541Examples:
542 heddle diff # worktree vs HEAD
543 heddle diff NOTES.md # only that path
544 heddle diff -- NOTES.md # same, after the path separator
545 heddle diff --path NOTES.md # same, explicit path filter
546 heddle diff HEAD~1 HEAD -- src # two states, filtered to src/
547
548Path-shaped positionals and arguments after `--` are worktree/path filters,
549not missing states. `log --path` uses the same filter spelling.
550
551Restore:
552 Heddle does not restore one file from a saved state (no restore/checkout/reset).
553 Materialize one state in a new checkout: heddle start <name> --from <state> --path <dir>
554 Apply the inverse of one state to this worktree: heddle revert <state> [--no-commit]
555 Restore the tree preserved by the last undo: heddle undo --recover
556
557Patch compatibility:
558 --patch output uses Git-compatible unified diff, including extended headers for type and mode changes.
559")]
560pub struct DiffArgs {
561 pub from: Option<String>,
563
564 pub to: Option<String>,
566
567 #[arg(long = "path", value_name = "PATH")]
569 pub path_filters: Vec<String>,
570
571 #[arg(last = true, value_name = "PATH")]
573 pub paths: Vec<String>,
574
575 #[arg(long)]
577 pub semantic: bool,
578
579 #[arg(long)]
581 pub stat: bool,
582
583 #[arg(long)]
585 pub name_only: bool,
586
587 #[arg(short = 'U', long = "unified", default_value_t = 3)]
589 pub unified: usize,
590
591 #[arg(long)]
593 pub context: bool,
594
595 #[arg(short = 'p', long = "patch")]
597 pub patch: bool,
598}
599
600#[derive(Clone, Debug, clap::Args)]
602#[command(after_help = "\
603Restore:
604 `revert` applies the inverse of one state's changes. It is not a single-file
605 restore, and Heddle has no restore/checkout/reset verb.
606 Materialize one state in a new checkout: heddle start <name> --from <state> --path <dir>
607 Restore the tree preserved by the last undo: heddle undo --recover
608 Heddle cannot put one file back from a saved state.
609")]
610pub struct RevertArgs {
611 pub state: String,
613
614 #[arg(short = 'm', long)]
616 pub message: Option<String>,
617
618 #[arg(long)]
620 pub no_commit: bool,
621}
622
623#[derive(Clone, Debug, clap::Args)]
625#[command(after_help = "\
626Examples:
627 heddle undo --preview # inspect the most recent operation
628 heddle undo --hard --preview # preview the worktree rewind --hard would apply
629 heddle undo --hard # roll it back and rewind the worktree
630 heddle undo -n 3 --hard # roll back the last three operations
631 heddle undo --recover # restore the state preserved by the last undo
632 heddle undo --list # preview undoable operations on this thread
633 heddle undo --dry-run # show what would change without applying
634
635Restore:
636 `--recover` restores only the last undo's preserved tree as worktree changes.
637 Heddle does not restore one arbitrary file or an arbitrary saved state
638 (no restore/checkout/reset). Materialize a state with
639 `heddle start <name> --from <state> --path <dir>`, or invert one with
640 `heddle revert <state>`.
641
642Undoable operations:
643 - heddle capture (restores HEAD to the pre-capture parent)
644 - heddle land (non-FF) (restores HEAD + both thread refs)
645 - heddle land (FF) (restores HEAD + the landed-into thread ref to
646 the pre-merge tip; the merged-in thread is
647 untouched.)
648 - heddle thread switch (restores HEAD to the previous thread state)
649 - heddle thread create/drop/rename
650 - heddle thread marker create/drop
651 - heddle redact apply (with --allow-redact-undo; removes the
652 redaction record so future materializes
653 restore the original blob bytes. Refused
654 when a Purge has destroyed the bytes.)
655 - heddle undo --redo re-apply the most recently undone operation
656
657Not undoable (file a follow-up if you need one):
658 - heddle push / pull (remote-affecting; out of scope)
659 - heddle redact purge apply (destructive by design; irreversible)
660 - heddle start <name> --path <dir> (refused while the materialized worktree
661 still exists — run `heddle thread drop
662 <name> --delete-thread` first, then
663 re-run `heddle undo`)
664 - cross-worktree shared-backend undo (no worktree registry yet; single-
665 worktree usage is the supported
666 configuration for 0.3)
667")]
668pub struct UndoArgs {
669 #[arg(short = 'n', long, default_value = "1")]
671 pub steps: usize,
672
673 #[arg(long)]
675 pub list: bool,
676
677 #[arg(long, default_value = "20")]
679 pub depth: usize,
680
681 #[arg(long, visible_alias = "dry-run")]
684 pub preview: bool,
685
686 #[arg(long, conflicts_with_all = ["list", "redo", "recover"])]
690 pub hard: bool,
691
692 #[arg(long, conflicts_with = "list")]
694 pub redo: bool,
695
696 #[arg(
699 long,
700 conflicts_with_all = ["steps", "list", "preview", "hard", "redo", "allow_redact_undo"]
701 )]
702 pub recover: bool,
703
704 #[arg(long)]
712 pub allow_redact_undo: bool,
713}
714
715#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq)]
721pub enum WorkspaceModeArg {
722 Auto,
724 Materialized,
726 Virtualized,
728 Solid,
730}
731
732#[derive(Clone, Debug, clap::Args)]
734#[command(after_help = "\
735Examples:
736 heddle start feature/auth --path ../feature-auth # create an isolated checkout
737 heddle start scratch --path ../scratch # place the checkout explicitly
738 heddle start fix-flake --path ../fix-flake --task 'fix CI flake'
739
740`--path` is required when workspace is omitted or `auto`. Without it, start
741refuses instead of hiding a checkout under `.heddle/threads/<name>/`.
742`--workspace auto` is the same default and still requires `--path`.
743Pass `--path ../<name>`, or an explicit `--workspace solid|materialized|virtualized`
744if you want the managed layout. To stay on this checkout, use
745`heddle thread create <name>` then `heddle thread switch <name>`.
746
747Isolated checkouts are Heddle-managed working directories. They do not contain a .git directory; use Heddle commands inside them, and run Git-authority operations through Heddle from the parent Git-overlay repository.
748
749`heddle start <name> --path <dir>` is the one-step form of the advanced split flow: `heddle thread create <name>` creates the ref now, and `heddle thread promote <name> --path <dir>` materializes it later. Use the split form only when you intentionally need ref-first, checkout-later staging.
750
751Advanced (hidden) flags:
752 --agent-provider/--agent-model (agent attribution for the registered thread), --parent-thread (delegated child work), --print-cd-path (print only the checkout path for shell wrappers), --daemon/--no-daemon (virtualized-mount ownership), --shared-target/--no-shared-target (workspace-shared cargo target dir; default on for Rust solid/materialized). All are accepted here; they stay out of the flag list to keep everyday help terse.
753")]
754pub struct ThreadStartArgs {
755 pub name: String,
757
758 #[arg(long)]
760 pub from: Option<String>,
761
762 #[arg(long)]
765 pub path: Option<std::path::PathBuf>,
766
767 #[arg(long, value_enum)]
770 pub workspace: Option<WorkspaceModeArg>,
771
772 #[arg(long, hide = true)]
774 pub agent_provider: Option<String>,
775
776 #[arg(long, hide = true)]
778 pub agent_model: Option<String>,
779
780 #[arg(long)]
782 pub task: Option<String>,
783
784 #[arg(long, hide = true)]
786 pub parent_thread: Option<String>,
787
788 #[arg(long, hide = true)]
790 pub automated: bool,
791
792 #[arg(long, hide = true, conflicts_with_all = ["agent_provider", "agent_model"])]
799 pub print_cd_path: bool,
800
801 #[arg(
808 long,
809 overrides_with = "no_daemon",
810 action = clap::ArgAction::SetTrue,
811 default_value_t = true,
812 hide = true,
813 )]
814 pub daemon: bool,
815
816 #[arg(
822 long,
823 overrides_with = "daemon",
824 action = clap::ArgAction::SetTrue,
825 hide = true,
826 )]
827 pub no_daemon: bool,
828
829 #[arg(long)]
833 pub interactive_setup: bool,
834
835 #[arg(
849 long,
850 overrides_with = "no_shared_target",
851 action = clap::ArgAction::SetTrue,
852 hide = true,
853 )]
854 pub shared_target: bool,
855
856 #[arg(
860 long,
861 overrides_with = "shared_target",
862 action = clap::ArgAction::SetTrue,
863 hide = true,
864 )]
865 pub no_shared_target: bool,
866
867 #[arg(long)]
878 pub hydrate: bool,
879}
880
881#[derive(Clone, Debug, clap::Args)]
889pub struct TryArgs {
890 #[arg(long)]
893 pub name: Option<String>,
894
895 #[arg(long, value_enum, default_value_t = WorkspaceModeArg::Materialized)]
900 pub workspace: WorkspaceModeArg,
901 #[arg(long = "auto-merge")]
904 pub auto_merge: bool,
905
906 #[arg(long = "keep-on-success")]
910 pub keep_on_success: bool,
911
912 #[arg(long)]
915 pub allow_heddle_global_args: bool,
916
917 #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
920 pub command: Vec<String>,
921}
922
923#[derive(Clone, Debug, clap::Args)]
925pub struct RunArgs {
926 #[arg(long = "thread")]
928 pub thread: Option<String>,
929
930 #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
932 pub command: Vec<String>,
933}
934
935#[derive(Clone, Debug, clap::Args)]
937pub struct ReadyArgs {
938 #[arg(long = "thread")]
940 pub thread: Option<String>,
941
942 #[arg(short = 'm', long)]
944 pub message: Option<String>,
945
946 #[arg(long, value_parser = parse_confidence)]
948 pub confidence: Option<f32>,
949
950 #[arg(long)]
954 pub dry_run: bool,
955}
956
957#[derive(Clone, Debug, clap::Args)]
959pub struct SyncArgs {
960 #[cfg(feature = "git-overlay")]
962 #[command(subcommand)]
963 pub command: Option<SyncCommands>,
964
965 #[arg(long = "thread")]
967 pub thread: Option<String>,
968}
969
970#[derive(Clone, Debug, clap::Args)]
972pub struct LandArgs {
973 #[arg(long = "thread")]
975 pub thread: Option<String>,
976
977 #[arg(long = "threads", value_delimiter = ',')]
982 pub threads: Vec<String>,
983
984 #[arg(short = 'm', long)]
986 pub message: Option<String>,
987
988 #[arg(long)]
990 pub no_squash: bool,
991
992 #[arg(long)]
996 pub dry_run: bool,
997}
998
999#[derive(Clone, Debug, clap::Args)]
1001pub struct ThreadShowArgs {
1002 pub thread: Option<String>,
1004
1005 #[arg(long)]
1007 pub watch: bool,
1008
1009 #[arg(long, hide = true)]
1011 pub watch_iterations: Option<usize>,
1012
1013 #[arg(long, hide = true)]
1015 pub watch_interval_ms: Option<u64>,
1016}
1017
1018#[derive(Clone, Debug, clap::Args)]
1020pub struct ThreadCapturesArgs {
1021 pub thread: Option<String>,
1023
1024 #[arg(long, default_value_t = 20)]
1026 pub limit: usize,
1027}
1028
1029#[derive(Clone, Debug, clap::Args)]
1033pub struct ThreadNameArgs {
1034 pub thread: Option<String>,
1036}
1037
1038#[derive(Clone, Debug, clap::Args)]
1040pub struct ThreadRenameArgs {
1041 pub old: String,
1043
1044 pub new: String,
1046}
1047
1048#[derive(Clone, Debug, clap::Args)]
1050pub struct ThreadPromoteArgs {
1051 pub thread: String,
1053
1054 #[arg(long)]
1056 pub path: Option<std::path::PathBuf>,
1057
1058 #[arg(long)]
1060 pub force: bool,
1061}
1062
1063#[derive(Clone, Debug, clap::Args)]
1065pub struct ThreadMoveArgs {
1066 pub from: String,
1068
1069 pub to: String,
1071
1072 #[arg(long = "path", required = true, value_name = "PATH")]
1074 pub paths: Vec<String>,
1075
1076 #[arg(short = 'm', long)]
1078 pub message: Option<String>,
1079}
1080
1081#[derive(Clone, Debug, clap::Args)]
1083pub struct ThreadAbsorbArgs {
1084 pub thread: String,
1086
1087 #[arg(long)]
1089 pub into: Option<String>,
1090
1091 #[arg(short = 'm', long)]
1093 pub message: Option<String>,
1094
1095 #[arg(long)]
1097 pub preview: bool,
1098}
1099
1100#[derive(Clone, Debug, clap::Args)]
1102pub struct ThreadResolveArgs {
1103 pub thread: String,
1105}
1106
1107#[derive(Clone, Debug, clap::Args)]
1109pub struct ThreadDropArgs {
1110 pub thread: String,
1112
1113 #[arg(long)]
1115 pub delete_thread: bool,
1116
1117 #[arg(short, long)]
1119 pub force: bool,
1120}
1121
1122#[derive(Clone, Debug, clap::Args)]
1126pub struct ThreadApproveArgs {
1127 pub source: String,
1129
1130 pub target: String,
1132
1133 #[arg(long)]
1135 pub note: Option<String>,
1136
1137 #[arg(long, default_value = "origin")]
1139 pub remote: String,
1140}
1141
1142#[derive(Clone, Debug, clap::Args)]
1145pub struct ThreadApprovalsArgs {
1146 pub source: String,
1147 pub target: String,
1148 #[arg(long, default_value = "origin")]
1149 pub remote: String,
1150}
1151
1152#[derive(Clone, Debug, clap::Args)]
1155pub struct ThreadRevokeApprovalArgs {
1156 pub id: String,
1158 #[arg(long, default_value = "origin")]
1159 pub remote: String,
1160}
1161
1162#[derive(Clone, Debug, clap::Args)]
1165pub struct ThreadCheckMergeArgs {
1166 pub source: String,
1167 pub target: String,
1168
1169 #[arg(long, default_value = "merge")]
1171 pub gated_action: String,
1172
1173 #[arg(long = "path", value_delimiter = ',')]
1176 pub changed_paths: Vec<String>,
1177
1178 #[arg(long, default_value = "origin")]
1179 pub remote: String,
1180}
1181
1182#[derive(Clone, Debug, clap::Args)]
1184pub struct CollapseArgs {
1185 #[arg(required = true)]
1187 pub states: Vec<String>,
1188
1189 #[arg(long)]
1191 pub into: String,
1192
1193 #[arg(long)]
1195 pub confidence: Option<f32>,
1196}
1197
1198#[derive(Clone, Debug, clap::Args)]
1200pub struct ExpandArgs {
1201 pub reference: String,
1203}
1204
1205#[derive(Clone, Debug, clap::Args)]
1207pub struct ResolveArgs {
1208 pub path: Option<String>,
1210
1211 #[arg(long)]
1213 pub all: bool,
1214
1215 #[arg(long)]
1217 pub list: bool,
1218
1219 #[arg(long, conflicts_with = "theirs")]
1221 pub ours: bool,
1222
1223 #[arg(long, conflicts_with = "ours")]
1225 pub theirs: bool,
1226
1227 #[arg(long)]
1229 pub force: bool,
1230}
1231
1232#[derive(Clone, Debug, clap::Args)]
1235pub struct RemoteOperationArgs {
1236 pub remote: Option<String>,
1238
1239 #[arg(short, long)]
1241 pub thread: Option<String>,
1242
1243 #[arg(long)]
1246 pub insecure: bool,
1247}
1248
1249#[derive(Clone, Debug, clap::Args)]
1251#[command(after_help = "\
1252Git Overlay refs:
1253 A normal push writes refs/heads/<thread> and refs/notes/heddle.
1254 --all-threads writes every refs/heads/<thread> and refs/tags/<tag>, plus refs/notes/heddle.
1255 JSON output lists changed refs in refs_written; verify with git ls-remote <remote>.
1256")]
1257pub struct PushArgs {
1258 pub remote: Option<String>,
1260
1261 #[arg(short, long, conflicts_with = "thread_arg")]
1263 pub thread: Option<String>,
1264
1265 #[arg(value_name = "THREAD")]
1267 pub thread_arg: Option<String>,
1268
1269 #[arg(short, long)]
1271 pub state: Option<String>,
1272
1273 #[arg(short, long)]
1275 pub force: bool,
1276
1277 #[arg(long)]
1279 pub all_threads: bool,
1280
1281 #[arg(long)]
1284 pub insecure: bool,
1285
1286 #[arg(long)]
1290 pub dry_run: bool,
1291}
1292
1293impl PushArgs {
1294 pub fn thread_name(&self) -> Option<String> {
1295 self.thread.clone().or_else(|| self.thread_arg.clone())
1296 }
1297}
1298
1299#[derive(Clone, Debug, clap::Args)]
1301#[command(after_help = "\
1302Advanced (hidden) flags:
1303 --lazy leaves blob content absent by design and hydrates it explicitly later. Hosted/network Heddle remotes only.
1304")]
1305pub struct PullArgs {
1306 #[command(flatten)]
1307 pub remote_op: RemoteOperationArgs,
1308
1309 #[arg(short, long)]
1311 pub local_thread: Option<String>,
1312
1313 #[arg(long, hide = true)]
1315 pub lazy: bool,
1316}
1317
1318#[derive(Clone, Debug, clap::Args)]
1326#[command(after_help = "\
1327Behavior:
1328 Clones native Heddle or Git repositories. Native clones follow the remote default thread; `--thread` overrides. Git clones check out the selected default branch. Git transport runs through Sley and does not require a Git executable. Never prompts. Full details: `heddle help clone`.
1329
1330Advanced/planned flags: see `heddle help clone`.
1331
1332Examples:
1333 heddle clone ../native-repo ./clone # local native Heddle repository
1334 heddle clone heddle://host/repo ./clone --depth 1 # shallow Heddle clone: tip plus immediate parents
1335")]
1336pub struct CloneArgs {
1337 pub remote: String,
1339
1340 pub local: String,
1342
1343 #[arg(long)]
1345 pub thread: Option<String>,
1346
1347 #[arg(long)]
1349 pub depth: Option<u32>,
1350
1351 #[arg(long, hide = true)]
1355 pub lazy: bool,
1356
1357 #[arg(long)]
1359 pub insecure: bool,
1360
1361 #[arg(long, hide = true, value_name = "SPEC", value_parser = parse_clone_filter_spec)]
1367 pub filter: Option<String>,
1368
1369 #[arg(long, visible_alias = "monorepo")]
1373 pub recursive: bool,
1374}
1375
1376fn parse_clone_filter_spec(s: &str) -> Result<String, String> {
1377 match s {
1378 "blob:none" => Ok(s.to_string()),
1379 other => Err(format!(
1380 "unsupported --filter spec `{other}`; only `blob:none` is supported today"
1381 )),
1382 }
1383}
1384
1385#[derive(Clone, Debug, clap::Args)]
1387pub struct AgentProvenanceBeginArgs {
1388 #[arg(long)]
1390 pub provider: String,
1391
1392 #[arg(long)]
1394 pub model: String,
1395
1396 #[arg(long)]
1398 pub policy: Option<String>,
1399}
1400
1401#[derive(Clone, Debug, clap::Args)]
1403pub struct AgentProvenanceSegmentArgs {
1404 #[arg(long)]
1406 pub provider: String,
1407
1408 #[arg(long)]
1410 pub model: String,
1411
1412 #[arg(long)]
1414 pub policy: Option<String>,
1415}
1416
1417#[derive(Clone, Debug, clap::Args)]
1419pub struct AgentProvenanceEndArgs {
1420 pub session_id: Option<String>,
1422}
1423
1424#[derive(Clone, Debug, clap::Args)]
1426pub struct AgentProvenanceShowArgs {
1427 pub session_id: Option<String>,
1429}
1430
1431#[derive(Clone, Debug, clap::Args)]
1433pub struct AgentProvenanceListArgs {
1434 #[arg(long)]
1436 pub active: bool,
1437}
1438
1439#[derive(Clone, Debug, clap::Args)]
1441pub struct WorktreeAddArgs {
1442 pub path: std::path::PathBuf,
1444
1445 #[arg(long)]
1447 pub thread: Option<String>,
1448
1449 #[arg(long)]
1451 pub from: Option<String>,
1452}
1453
1454#[derive(Clone, Debug, clap::Args)]
1456pub struct WorktreeRemoveArgs {
1457 pub path: std::path::PathBuf,
1459
1460 #[arg(long)]
1462 pub delete_thread: bool,
1463}
1464
1465#[derive(Clone, Debug, clap::Args)]
1467pub struct AgentPresenceListArgs {
1468 #[arg(long)]
1470 pub active: bool,
1471}
1472
1473#[derive(Clone, Debug, clap::Args)]
1475pub struct AgentPresenceShowArgs {
1476 pub session: Option<String>,
1478}
1479
1480#[derive(Clone, Debug, clap::Args)]
1482pub struct AgentPresenceExplainArgs {
1483 pub session: Option<String>,
1485}
1486
1487#[derive(Clone, Debug, clap::Args)]
1489pub struct AgentPresenceCompleteArgs {
1490 #[arg(long)]
1492 pub session: Option<String>,
1493}
1494
1495#[derive(Clone, Debug, clap::Args)]
1497pub struct AgentReserveArgs {
1498 #[arg(long)]
1500 pub thread: String,
1501
1502 #[arg(long)]
1504 pub anchor: Option<String>,
1505
1506 #[arg(long)]
1508 pub task: Option<String>,
1509
1510 #[arg(long)]
1512 pub task_id: Option<String>,
1513
1514 #[arg(long, value_name = "PID")]
1516 pub hold_for_pid: Option<u32>,
1517}
1518
1519#[derive(Clone, Debug, clap::Args)]
1521pub struct AgentHeartbeatArgs {
1522 #[arg(long)]
1524 pub lease: String,
1525
1526 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1528 pub token: String,
1529}
1530
1531#[derive(Clone, Debug, clap::Args)]
1533pub struct AgentReleaseArgs {
1534 #[arg(long)]
1536 pub lease: String,
1537
1538 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1540 pub token: String,
1541
1542 #[arg(long, default_value = "complete")]
1544 pub status: AgentReleaseStatusArg,
1545}
1546
1547#[derive(Clone, Debug, clap::ValueEnum)]
1548pub enum AgentReleaseStatusArg {
1549 Complete,
1550 Abandoned,
1551}
1552
1553#[derive(Clone, Debug, clap::Args)]
1555pub struct AgentApiListArgs {
1556 #[arg(long)]
1558 pub thread: Option<String>,
1559
1560 #[arg(long)]
1562 pub alive_only: bool,
1563}
1564
1565#[derive(Clone, Debug, clap::ValueEnum)]
1566pub enum AgentTaskStatusArg {
1567 Open,
1568 InProgress,
1569 Blocked,
1570 Complete,
1571 Abandoned,
1572}
1573
1574#[derive(Clone, Debug, clap::Args)]
1576pub struct AgentTaskCreateArgs {
1577 #[arg(long)]
1579 pub task_id: Option<String>,
1580
1581 #[arg(long)]
1583 pub title: String,
1584
1585 #[arg(long)]
1587 pub body: Option<String>,
1588
1589 #[arg(long)]
1591 pub thread: String,
1592
1593 #[arg(long)]
1595 pub base_state: Option<String>,
1596
1597 #[arg(long)]
1599 pub base_root: Option<String>,
1600
1601 #[arg(long)]
1603 pub parent_task_id: Option<String>,
1604
1605 #[arg(long)]
1607 pub coordination_discussion_id: Option<String>,
1608
1609 #[arg(long)]
1611 pub allow_offline: bool,
1612
1613 #[arg(long)]
1615 pub delegated_by: Option<String>,
1616}
1617
1618#[derive(Clone, Debug, clap::Args)]
1620pub struct AgentTaskListArgs {
1621 #[arg(long)]
1623 pub thread: Option<String>,
1624
1625 #[arg(long)]
1627 pub status: Option<AgentTaskStatusArg>,
1628}
1629
1630#[derive(Clone, Debug, clap::Args)]
1632pub struct AgentTaskShowArgs {
1633 pub task_id: String,
1635}
1636
1637#[derive(Clone, Debug, clap::Args)]
1639pub struct AgentTaskUpdateArgs {
1640 pub task_id: String,
1642
1643 #[arg(long)]
1645 pub title: Option<String>,
1646
1647 #[arg(long)]
1649 pub body: Option<String>,
1650
1651 #[arg(long)]
1653 pub status: Option<AgentTaskStatusArg>,
1654
1655 #[arg(long)]
1657 pub thread: Option<String>,
1658
1659 #[arg(long)]
1661 pub base_state: Option<String>,
1662
1663 #[arg(long)]
1665 pub base_root: Option<String>,
1666
1667 #[arg(long)]
1669 pub parent_task_id: Option<String>,
1670
1671 #[arg(long)]
1673 pub coordination_discussion_id: Option<String>,
1674
1675 #[arg(long, conflicts_with = "no_allow_offline")]
1677 pub allow_offline: bool,
1678
1679 #[arg(long, conflicts_with = "allow_offline")]
1681 pub no_allow_offline: bool,
1682
1683 #[arg(long)]
1685 pub delegated_by: Option<String>,
1686}
1687
1688#[derive(Clone, Debug, clap::Args)]
1690pub struct AgentFanoutPlanArgs {
1691 #[arg(long)]
1693 pub title: String,
1694
1695 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1697 pub lane: Vec<String>,
1698
1699 #[arg(long)]
1701 pub coordination_discussion_id: Option<String>,
1702}
1703
1704#[derive(Clone, Debug, clap::Args)]
1706pub struct AgentFanoutStartArgs {
1707 #[arg(long)]
1709 pub title: String,
1710
1711 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1713 pub lane: Vec<String>,
1714
1715 #[arg(long)]
1717 pub coordination_discussion_id: Option<String>,
1718}
1719
1720#[derive(Clone, Debug, clap::Args)]
1722pub struct AgentCaptureArgs {
1723 #[arg(long)]
1725 pub lease: String,
1726
1727 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1729 pub token: String,
1730
1731 #[arg(long, short = 'm', alias = "intent")]
1733 pub message: Option<String>,
1734
1735 #[arg(long, value_parser = parse_confidence)]
1737 pub confidence: Option<f32>,
1738}
1739
1740#[derive(Clone, Debug, clap::Args)]
1742pub struct AgentReadyArgs {
1743 #[arg(long)]
1745 pub lease: String,
1746
1747 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1749 pub token: String,
1750
1751 #[arg(long, short = 'm')]
1753 pub message: Option<String>,
1754
1755 #[arg(long, value_parser = parse_confidence)]
1757 pub confidence: Option<f32>,
1758}
1759
1760#[derive(Clone, Debug, clap::Args)]
1768pub struct WatchArgs {
1769 #[arg(long, value_name = "DURATION")]
1773 pub since: Option<String>,
1774
1775 #[arg(long, value_name = "KINDS")]
1779 pub filter: Option<String>,
1780
1781 #[arg(long, hide = true)]
1784 pub max_iterations: Option<usize>,
1785
1786 #[arg(long, hide = true)]
1789 pub poll_interval_ms: Option<u64>,
1790}
1791
1792#[cfg(test)]
1798mod capture_message_alias_tests {
1799 use clap::Parser;
1800
1801 use crate::cli::{Cli, Commands, SnapshotArgs};
1802
1803 fn parse_capture(extra: &[&str]) -> Result<SnapshotArgs, clap::Error> {
1804 let mut argv: Vec<&str> = vec!["heddle", "capture"];
1805 argv.extend_from_slice(extra);
1806 let cli = Cli::try_parse_from(argv)?;
1807 match cli.command {
1808 Commands::Capture(args) => Ok(args),
1809 _ => panic!("expected Commands::Capture"),
1810 }
1811 }
1812
1813 #[test]
1814 fn capture_accepts_message_alias() {
1815 let args = parse_capture(&["--message", "my change"]).expect("--message should parse");
1816 assert_eq!(args.intent.as_deref(), Some("my change"));
1817 }
1818
1819 #[test]
1820 fn capture_accepts_intent_long_form() {
1821 let args = parse_capture(&["--intent", "my change"]).expect("--intent should parse");
1822 assert_eq!(args.intent.as_deref(), Some("my change"));
1823 }
1824
1825 #[test]
1826 fn capture_accepts_short_m() {
1827 let args = parse_capture(&["-m", "my change"]).expect("-m should parse");
1828 assert_eq!(args.intent.as_deref(), Some("my change"));
1829 }
1830
1831 #[test]
1832 fn capture_parses_without_intent_so_the_refuse_can_fire() {
1833 let args =
1834 parse_capture(&[]).expect("omitted -m is a semantic refuse, not a clap usage error");
1835 assert!(args.intent.is_none());
1836 }
1837
1838 #[test]
1839 fn capture_rejects_non_finite_or_out_of_range_confidence() {
1840 for value in ["NaN", "inf", "-0.1", "1.7"] {
1841 let confidence_arg = format!("--confidence={value}");
1842 let err = parse_capture(&["-m", "bad confidence", &confidence_arg])
1843 .expect_err("invalid confidence should fail to parse");
1844 assert!(
1845 err.to_string()
1846 .contains("confidence must be a finite number from 0.0 to 1.0"),
1847 "unexpected parse error for {value}: {err}"
1848 );
1849 }
1850 }
1851}
1852
1853#[cfg(test)]
1854mod clone_filter_tests {
1855 use clap::Parser;
1856
1857 use crate::cli::{Cli, CloneArgs, Commands};
1858
1859 fn parse_clone(extra: &[&str]) -> Result<CloneArgs, clap::Error> {
1860 let mut argv: Vec<&str> = vec!["heddle", "clone", "remote", "local"];
1861 argv.extend_from_slice(extra);
1862 let cli = Cli::try_parse_from(argv)?;
1863 match cli.command {
1864 Commands::Clone(args) => Ok(args),
1865 _ => panic!("expected Commands::Clone"),
1866 }
1867 }
1868
1869 #[test]
1870 fn parses_clone_filter_blob_none() {
1871 let args = parse_clone(&["--filter", "blob:none"]).expect("parse --filter blob:none");
1872 assert_eq!(args.filter.as_deref(), Some("blob:none"));
1873 assert!(!args.lazy);
1874 }
1875
1876 #[test]
1877 fn rejects_unknown_filter_spec() {
1878 let err = parse_clone(&["--filter", "tree:0"])
1879 .expect_err("unknown --filter spec should fail to parse");
1880 let msg = err.to_string();
1881 assert!(
1882 msg.contains("tree:0") && msg.contains("blob:none"),
1883 "error should name the bad spec and the supported one: {msg}"
1884 );
1885 }
1886}