1#[cfg(feature = "git-overlay")]
5use super::commands_git_projection::SyncCommands;
6
7#[derive(Clone, Debug, clap::Args)]
9#[command(after_help = "\
10Examples:
11 heddle init # initialize here; existing Git becomes Git Overlay
12 heddle init my-project # initialize a native Heddle subdirectory
13 heddle init --principal-name 'Ada Lovelace' --principal-email ada@example.com
14")]
15pub struct InitArgs {
16 pub path: Option<std::path::PathBuf>,
18
19 #[arg(long)]
21 pub principal_name: Option<String>,
22
23 #[arg(long)]
25 pub principal_email: Option<String>,
26
27 #[arg(long)]
29 pub install_harnesses: Option<String>,
30
31 #[arg(long)]
33 pub no_harness_install: bool,
34
35 #[arg(long, visible_alias = "scope", default_value = "repo")]
37 pub harness_install_scope: String,
38
39 #[arg(long)]
41 pub harness_install_force: bool,
42}
43
44#[derive(Clone, Debug, clap::Args)]
46#[command(after_help = "\
47Examples:
48 heddle adopt # adopt all local Git refs into native Heddle storage
49 heddle adopt --ref main # adopt one branch or tag
50 heddle adopt ../repo --ref main --ref v1.0 # adopt selected refs in another repo
51
52Adoption imports Git refs, makes Heddle the source authority, and retains `.git` for explicit Git Projection. Normal Git Overlay setup uses `heddle init` instead.
53")]
54pub struct AdoptArgs {
55 pub path: Option<std::path::PathBuf>,
57
58 #[arg(long = "ref", value_name = "REF")]
60 pub refs: Vec<String>,
61}
62
63#[derive(Clone, Debug, clap::Args)]
70pub struct DoctorArgs {
71 #[arg(long, global = false)]
76 pub profile: bool,
77
78 #[command(subcommand)]
79 pub command: Option<DoctorCommands>,
80}
81
82#[derive(Clone, Debug, clap::Subcommand)]
84pub enum DoctorCommands {
85 Docs(DoctorDocsArgs),
96
97 Schemas(DoctorSchemasArgs),
107}
108
109#[derive(Clone, Debug, clap::Args)]
111pub struct DoctorDocsArgs {
112 #[arg(long, value_name = "PATH")]
117 pub path: Vec<std::path::PathBuf>,
118
119 #[arg(long)]
121 pub all: bool,
122}
123
124#[derive(Clone, Debug, clap::Args)]
126pub struct DoctorSchemasArgs {
127 #[arg(long)]
130 pub update_docs: bool,
131}
132
133fn parse_confidence(s: &str) -> Result<f32, String> {
134 let value = s
135 .parse::<f32>()
136 .map_err(|_| format!("confidence must be a finite number from 0.0 to 1.0, got `{s}`"))?;
137 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
138 return Err(format!(
139 "confidence must be a finite number from 0.0 to 1.0, got `{s}`"
140 ));
141 }
142 Ok(value)
143}
144
145#[derive(Clone, Debug, clap::Args)]
147#[command(after_help = "\
148Examples:
149 heddle capture -m 'add login route' # capture the worktree with intent
150 heddle capture -m 'wip' --confidence 0.6 # honest confidence on a draft step
151
152Agent automation flags (provider/model/session/policy/split) are hidden here.
153Run `heddle help agent-flags`, or `heddle capture --help-agent` to list them inline.
154")]
155pub struct SnapshotArgs {
156 #[arg(long, hide = true)]
166 pub help_agent: bool,
167
168 #[arg(short = 'm', long, visible_alias = "message")]
170 pub intent: Option<String>,
171
172 #[arg(long, value_parser = parse_confidence)]
174 pub confidence: Option<f32>,
175
176 #[arg(short, long)]
178 pub force: bool,
179
180 #[arg(long, hide = true)]
182 pub agent_provider: Option<String>,
183
184 #[arg(long, hide = true)]
186 pub agent_model: Option<String>,
187
188 #[arg(long, hide = true)]
190 pub agent_session: Option<String>,
191
192 #[arg(long, hide = true)]
194 pub agent_segment: Option<String>,
195
196 #[arg(long, hide = true)]
198 pub policy: Option<String>,
199
200 #[arg(long, hide = true)]
202 pub no_policy: bool,
203
204 #[arg(long, hide = true)]
206 pub no_agent: bool,
207
208 #[arg(long, hide = true)]
210 pub split: bool,
211
212 #[arg(long, hide = true, requires = "split")]
214 pub into: Option<String>,
215
216 #[arg(long = "path", hide = true, requires = "split", value_name = "PATH")]
218 pub paths: Vec<String>,
219}
220
221#[derive(Clone, Debug, clap::Args)]
223#[command(after_help = "\
224Examples:
225 heddle capture -m 'add login route'
226 heddle commit
227 heddle commit -m 'add login route'
228
229Behavior:
230 Commits the complete captured tree and replaces the Git index with that tree.
231 Git pre-commit and commit-msg hooks are not run.
232")]
233pub struct CommitArgs {
234 #[arg(short = 'm', long = "message")]
236 pub message: Option<String>,
237}
238
239#[derive(Clone, Debug, clap::Args)]
241#[command(after_help = "\
242Examples:
243 heddle log # walk the current thread
244 heddle log --oneline -n 20 # 20 most recent states in compact form
245 heddle log --timeline # show agent timeline tool-call cursor
246 heddle log --reflog # include re-attributed history
247 heddle log --path src/auth.rs # restrict to states touching a path
248")]
249pub struct LogArgs {
250 pub state: Option<String>,
252
253 #[arg(short = 'n', long, default_value = "20")]
255 pub limit: usize,
256
257 #[arg(long)]
259 pub all: bool,
260
261 #[arg(long)]
263 pub graph: bool,
264
265 #[arg(long)]
267 pub oneline: bool,
268
269 #[arg(long)]
271 pub reflog: bool,
272
273 #[arg(long)]
275 pub timeline: bool,
276
277 #[arg(long, default_value = "main")]
279 pub thread: String,
280
281 #[arg(long)]
283 pub agent: Option<String>,
284
285 #[arg(long = "path", value_name = "PATH")]
287 pub paths: Vec<String>,
288
289 #[arg(long, value_name = "STATE")]
295 pub since: Option<String>,
296}
297
298#[derive(Clone, Debug, clap::Args)]
300pub struct TimelineArgs {
301 #[command(subcommand)]
302 pub command: TimelineCommands,
303}
304
305#[derive(Clone, Debug, clap::Subcommand)]
307pub enum TimelineCommands {
308 Status(TimelineStatusArgs),
310
311 #[command(name = "record-start")]
313 RecordStart(TimelineRecordStartArgs),
314
315 #[command(name = "record-finish")]
317 RecordFinish(TimelineRecordFinishArgs),
318
319 #[command(after_help = "\
321Examples:
322 heddle timeline fork --step tls-abc --branch tlb-experiment
323 heddle timeline fork --tool-call call_123 --session ses_456 --branch tlb-alt
324")]
325 Fork(TimelineForkArgs),
326
327 #[command(after_help = "\
329Examples:
330 heddle timeline reset --step tls-abc
331 heddle timeline reset --tool-call call_123 --materialize
332")]
333 Reset(TimelineResetArgs),
334
335 Recover(TimelineRecoverArgs),
337}
338
339#[derive(Clone, Debug, clap::Args)]
341pub struct TimelineTargetArgs {
342 #[arg(long, default_value = "main")]
344 pub thread: String,
345
346 #[arg(long = "from-branch", value_name = "BRANCH")]
348 pub from_branch: Option<String>,
349
350 #[arg(long, conflicts_with_all = ["tool_call", "undo", "redo", "current"])]
352 pub step: Option<String>,
353
354 #[arg(long = "tool-call", conflicts_with_all = ["step", "undo", "redo", "current"])]
356 pub tool_call: Option<String>,
357
358 #[arg(long, default_value = "opencode")]
360 pub harness: String,
361
362 #[arg(long)]
364 pub session: Option<String>,
365
366 #[arg(long)]
368 pub message: Option<String>,
369
370 #[arg(long, conflicts_with_all = ["step", "tool_call", "redo", "current"])]
372 pub undo: bool,
373
374 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "current"])]
376 pub redo: bool,
377
378 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "redo"])]
380 pub current: bool,
381}
382
383#[derive(Clone, Debug, clap::Args)]
385pub struct TimelineForkArgs {
386 #[command(flatten)]
387 pub target: TimelineTargetArgs,
388
389 #[arg(long, value_name = "BRANCH")]
391 pub branch: Option<String>,
392
393 #[arg(long, default_value = "explicit-fork")]
395 pub reason: String,
396}
397
398#[derive(Clone, Debug, clap::Args)]
400pub struct TimelineResetArgs {
401 #[command(flatten)]
402 pub target: TimelineTargetArgs,
403
404 #[arg(long)]
406 pub materialize: bool,
407
408 #[arg(long, default_value = "fail-if-dirty")]
410 pub mode: String,
411}
412
413#[derive(Clone, Debug, clap::Args)]
415pub struct TimelineRecoverArgs {
416 #[arg(long, default_value = "main")]
418 pub thread: String,
419}
420
421#[derive(Clone, Debug, clap::Args)]
423pub struct TimelineStatusArgs {
424 #[arg(long, default_value = "main")]
426 pub thread: String,
427}
428
429#[derive(Clone, Debug, clap::Args)]
431pub struct TimelineRecordToolArgs {
432 #[arg(long, default_value = "main")]
434 pub thread: String,
435
436 #[arg(long, default_value = "opencode")]
438 pub harness: String,
439
440 #[arg(long)]
442 pub session: Option<String>,
443
444 #[arg(long)]
446 pub message: Option<String>,
447
448 #[arg(long = "tool-call")]
450 pub tool_call: String,
451
452 #[arg(long = "step-id")]
454 pub step_id: Option<String>,
455
456 #[arg(long = "branch")]
458 pub branch: Option<String>,
459
460 #[arg(long = "summary")]
462 pub summary: Option<String>,
463
464 #[arg(long = "payload-hash")]
466 pub payload_hash: Option<String>,
467}
468
469#[derive(Clone, Debug, clap::Args)]
471pub struct TimelineRecordStartArgs {
472 #[command(flatten)]
473 pub tool: TimelineRecordToolArgs,
474
475 #[arg(long = "tool-name", default_value = "tool")]
477 pub tool_name: String,
478}
479
480#[derive(Clone, Debug, clap::Args)]
482pub struct TimelineRecordFinishArgs {
483 #[command(flatten)]
484 pub tool: TimelineRecordToolArgs,
485
486 #[arg(long, default_value = "succeeded")]
488 pub status: String,
489}
490
491#[derive(Clone, Debug, clap::Args)]
498pub struct RetroArgs {
499 #[arg(long)]
504 pub since: Option<String>,
505
506 #[arg(long)]
509 pub include_merges: bool,
510
511 #[arg(long)]
514 pub include_undos: bool,
515
516 #[arg(long = "full", alias = "expand")]
520 pub full: bool,
521}
522
523#[derive(Clone, Debug, clap::Args)]
525#[command(after_help = "\
526Patch compatibility:
527 --patch output uses Git-compatible unified diff, including extended headers for type and mode changes.
528")]
529pub struct DiffArgs {
530 pub from: Option<String>,
532
533 pub to: Option<String>,
535
536 #[arg(long)]
538 pub semantic: bool,
539
540 #[arg(long)]
542 pub stat: bool,
543
544 #[arg(long)]
546 pub name_only: bool,
547
548 #[arg(short = 'U', long = "unified", default_value_t = 3)]
550 pub unified: usize,
551
552 #[arg(long)]
554 pub context: bool,
555
556 #[arg(short = 'p', long = "patch")]
558 pub patch: bool,
559}
560
561#[derive(Clone, Debug, clap::Args)]
563pub struct RevertArgs {
564 pub state: String,
566
567 #[arg(short = 'm', long)]
569 pub message: Option<String>,
570
571 #[arg(long)]
573 pub no_commit: bool,
574}
575
576#[derive(Clone, Debug, clap::Args)]
578#[command(after_help = "\
579Examples:
580 heddle undo --preview # inspect the most recent operation
581 heddle undo --hard # roll it back and rewind the worktree
582 heddle undo -n 3 --hard # roll back the last three operations
583 heddle undo --recover # restore the state preserved by the last undo
584 heddle undo --list # preview undoable operations on this thread
585 heddle undo --dry-run # show what would change without applying
586
587Undoable operations:
588 - heddle capture (restores HEAD to the pre-capture parent)
589 - heddle land (non-FF) (restores HEAD + both thread refs)
590 - heddle land (FF) (restores HEAD + the landed-into thread ref to
591 the pre-merge tip; the merged-in thread is
592 untouched.)
593 - heddle thread switch (restores HEAD to the previous thread state)
594 - heddle thread create/drop/rename
595 - heddle thread marker create/drop
596 - heddle redact apply (with --allow-redact-undo; removes the
597 redaction record so future materializes
598 restore the original blob bytes. Refused
599 when a Purge has destroyed the bytes.)
600 - heddle undo --redo re-apply the most recently undone operation
601
602Not undoable (file a follow-up if you need one):
603 - heddle push / pull (remote-affecting; out of scope)
604 - heddle redact purge apply (destructive by design; irreversible)
605 - heddle start <name> --path <dir> (refused while the materialized worktree
606 still exists — run `heddle thread drop
607 <name> --delete-thread` first, then
608 re-run `heddle undo`)
609 - cross-worktree shared-backend undo (no worktree registry yet; single-
610 worktree usage is the supported
611 configuration for 0.3)
612")]
613pub struct UndoArgs {
614 #[arg(short = 'n', long, default_value = "1")]
616 pub steps: usize,
617
618 #[arg(long)]
620 pub list: bool,
621
622 #[arg(long, default_value = "20")]
624 pub depth: usize,
625
626 #[arg(long, visible_alias = "dry-run")]
629 pub preview: bool,
630
631 #[arg(long, conflicts_with_all = ["list", "preview", "redo", "recover"])]
635 pub hard: bool,
636
637 #[arg(long, conflicts_with = "list")]
639 pub redo: bool,
640
641 #[arg(
644 long,
645 conflicts_with_all = ["steps", "list", "preview", "hard", "redo", "allow_redact_undo"]
646 )]
647 pub recover: bool,
648
649 #[arg(long)]
657 pub allow_redact_undo: bool,
658}
659
660#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq)]
666pub enum WorkspaceModeArg {
667 Auto,
669 Materialized,
671 Virtualized,
673 Solid,
675}
676
677#[derive(Clone, Debug, clap::Args)]
679#[command(after_help = "\
680Examples:
681 heddle start feature/auth --path ../feature-auth # create an isolated checkout
682 heddle start scratch --path ../scratch # place the checkout explicitly
683 heddle start fix-flake --task 'fix CI flake' # attach a task description
684
685Isolated 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.
686
687`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.
688
689Advanced (hidden) flags:
690 --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.
691")]
692pub struct ThreadStartArgs {
693 pub name: String,
695
696 #[arg(long)]
698 pub from: Option<String>,
699
700 #[arg(long)]
702 pub path: Option<std::path::PathBuf>,
703
704 #[arg(long, value_enum, default_value_t = WorkspaceModeArg::Auto)]
706 pub workspace: WorkspaceModeArg,
707
708 #[arg(long, hide = true)]
710 pub agent_provider: Option<String>,
711
712 #[arg(long, hide = true)]
714 pub agent_model: Option<String>,
715
716 #[arg(long)]
718 pub task: Option<String>,
719
720 #[arg(long, hide = true)]
722 pub parent_thread: Option<String>,
723
724 #[arg(long, hide = true)]
726 pub automated: bool,
727
728 #[arg(long, hide = true, conflicts_with_all = ["agent_provider", "agent_model"])]
735 pub print_cd_path: bool,
736
737 #[arg(
744 long,
745 overrides_with = "no_daemon",
746 action = clap::ArgAction::SetTrue,
747 default_value_t = true,
748 hide = true,
749 )]
750 pub daemon: bool,
751
752 #[arg(
758 long,
759 overrides_with = "daemon",
760 action = clap::ArgAction::SetTrue,
761 hide = true,
762 )]
763 pub no_daemon: bool,
764
765 #[arg(long)]
769 pub interactive_setup: bool,
770
771 #[arg(
785 long,
786 overrides_with = "no_shared_target",
787 action = clap::ArgAction::SetTrue,
788 hide = true,
789 )]
790 pub shared_target: bool,
791
792 #[arg(
796 long,
797 overrides_with = "shared_target",
798 action = clap::ArgAction::SetTrue,
799 hide = true,
800 )]
801 pub no_shared_target: bool,
802
803 #[arg(long)]
814 pub hydrate: bool,
815}
816
817#[derive(Clone, Debug, clap::Args)]
825pub struct TryArgs {
826 #[arg(long)]
829 pub name: Option<String>,
830
831 #[arg(long, value_enum, default_value_t = WorkspaceModeArg::Materialized)]
836 pub workspace: WorkspaceModeArg,
837 #[arg(long = "auto-merge")]
840 pub auto_merge: bool,
841
842 #[arg(long = "keep-on-success")]
846 pub keep_on_success: bool,
847
848 #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
851 pub command: Vec<String>,
852}
853
854#[derive(Clone, Debug, clap::Args)]
856pub struct RunArgs {
857 #[arg(long = "thread")]
859 pub thread: Option<String>,
860
861 #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
863 pub command: Vec<String>,
864}
865
866#[derive(Clone, Debug, clap::Args)]
868pub struct ReadyArgs {
869 #[arg(long = "thread")]
871 pub thread: Option<String>,
872
873 #[arg(short = 'm', long)]
875 pub message: Option<String>,
876
877 #[arg(long, value_parser = parse_confidence)]
879 pub confidence: Option<f32>,
880
881 #[arg(long)]
885 pub dry_run: bool,
886}
887
888#[derive(Clone, Debug, clap::Args)]
890pub struct SyncArgs {
891 #[cfg(feature = "git-overlay")]
893 #[command(subcommand)]
894 pub command: Option<SyncCommands>,
895
896 #[arg(long = "thread")]
898 pub thread: Option<String>,
899}
900
901#[derive(Clone, Debug, clap::Args)]
903pub struct LandArgs {
904 #[arg(long = "thread")]
906 pub thread: Option<String>,
907
908 #[arg(long = "threads", value_delimiter = ',')]
913 pub threads: Vec<String>,
914
915 #[arg(short = 'm', long)]
917 pub message: Option<String>,
918
919 #[arg(long)]
921 pub no_squash: bool,
922
923 #[arg(long)]
927 pub dry_run: bool,
928}
929
930#[derive(Clone, Debug, clap::Args)]
932pub struct ThreadShowArgs {
933 pub thread: Option<String>,
935
936 #[arg(long)]
938 pub watch: bool,
939
940 #[arg(long, hide = true)]
942 pub watch_iterations: Option<usize>,
943
944 #[arg(long, hide = true)]
946 pub watch_interval_ms: Option<u64>,
947}
948
949#[derive(Clone, Debug, clap::Args)]
951pub struct ThreadCapturesArgs {
952 pub thread: Option<String>,
954
955 #[arg(long, default_value_t = 20)]
957 pub limit: usize,
958}
959
960#[derive(Clone, Debug, clap::Args)]
964pub struct ThreadNameArgs {
965 pub thread: Option<String>,
967}
968
969#[derive(Clone, Debug, clap::Args)]
971pub struct ThreadRenameArgs {
972 pub old: String,
974
975 pub new: String,
977}
978
979#[derive(Clone, Debug, clap::Args)]
981pub struct ThreadPromoteArgs {
982 pub thread: String,
984
985 #[arg(long)]
987 pub path: Option<std::path::PathBuf>,
988
989 #[arg(long)]
991 pub force: bool,
992}
993
994#[derive(Clone, Debug, clap::Args)]
996pub struct ThreadMoveArgs {
997 pub from: String,
999
1000 pub to: String,
1002
1003 #[arg(long = "path", required = true, value_name = "PATH")]
1005 pub paths: Vec<String>,
1006
1007 #[arg(short = 'm', long)]
1009 pub message: Option<String>,
1010}
1011
1012#[derive(Clone, Debug, clap::Args)]
1014pub struct ThreadAbsorbArgs {
1015 pub thread: String,
1017
1018 #[arg(long)]
1020 pub into: Option<String>,
1021
1022 #[arg(short = 'm', long)]
1024 pub message: Option<String>,
1025
1026 #[arg(long)]
1028 pub preview: bool,
1029}
1030
1031#[derive(Clone, Debug, clap::Args)]
1033pub struct ThreadResolveArgs {
1034 pub thread: String,
1036}
1037
1038#[derive(Clone, Debug, clap::Args)]
1040pub struct ThreadDropArgs {
1041 pub thread: String,
1043
1044 #[arg(long)]
1046 pub delete_thread: bool,
1047
1048 #[arg(short, long)]
1050 pub force: bool,
1051}
1052
1053#[derive(Clone, Debug, clap::Args)]
1057pub struct ThreadApproveArgs {
1058 pub source: String,
1060
1061 pub target: String,
1063
1064 #[arg(long)]
1066 pub note: Option<String>,
1067
1068 #[arg(long, default_value = "origin")]
1070 pub remote: String,
1071}
1072
1073#[derive(Clone, Debug, clap::Args)]
1076pub struct ThreadApprovalsArgs {
1077 pub source: String,
1078 pub target: String,
1079 #[arg(long, default_value = "origin")]
1080 pub remote: String,
1081}
1082
1083#[derive(Clone, Debug, clap::Args)]
1086pub struct ThreadRevokeApprovalArgs {
1087 pub id: String,
1089 #[arg(long, default_value = "origin")]
1090 pub remote: String,
1091}
1092
1093#[derive(Clone, Debug, clap::Args)]
1096pub struct ThreadCheckMergeArgs {
1097 pub source: String,
1098 pub target: String,
1099
1100 #[arg(long, default_value = "merge")]
1102 pub gated_action: String,
1103
1104 #[arg(long = "path", value_delimiter = ',')]
1107 pub changed_paths: Vec<String>,
1108
1109 #[arg(long, default_value = "origin")]
1110 pub remote: String,
1111}
1112
1113#[derive(Clone, Debug, clap::Args)]
1115pub struct CollapseArgs {
1116 #[arg(required = true)]
1118 pub states: Vec<String>,
1119
1120 #[arg(long)]
1122 pub into: String,
1123
1124 #[arg(long)]
1126 pub confidence: Option<f32>,
1127}
1128
1129#[derive(Clone, Debug, clap::Args)]
1131pub struct ExpandArgs {
1132 pub reference: String,
1134}
1135
1136#[derive(Clone, Debug, clap::Args)]
1138pub struct ResolveArgs {
1139 pub path: Option<String>,
1141
1142 #[arg(long)]
1144 pub all: bool,
1145
1146 #[arg(long)]
1148 pub list: bool,
1149
1150 #[arg(long, conflicts_with = "theirs")]
1152 pub ours: bool,
1153
1154 #[arg(long, conflicts_with = "ours")]
1156 pub theirs: bool,
1157
1158 #[arg(long)]
1160 pub force: bool,
1161
1162 #[arg(long)]
1164 pub abort: bool,
1165}
1166
1167#[derive(Clone, Debug, clap::Args)]
1170pub struct RemoteOperationArgs {
1171 pub remote: Option<String>,
1173
1174 #[arg(short, long)]
1176 pub thread: Option<String>,
1177
1178 #[arg(long)]
1181 pub insecure: bool,
1182}
1183
1184#[derive(Clone, Debug, clap::Args)]
1186#[command(after_help = "\
1187Git Overlay refs:
1188 A normal push writes refs/heads/<thread> and refs/notes/heddle.
1189 --all-threads writes every refs/heads/<thread> and refs/tags/<tag>, plus refs/notes/heddle.
1190 JSON output lists changed refs in refs_written; verify with git ls-remote <remote>.
1191")]
1192pub struct PushArgs {
1193 pub remote: Option<String>,
1195
1196 #[arg(short, long, conflicts_with = "thread_arg")]
1198 pub thread: Option<String>,
1199
1200 #[arg(value_name = "THREAD")]
1202 pub thread_arg: Option<String>,
1203
1204 #[arg(short, long)]
1206 pub state: Option<String>,
1207
1208 #[arg(short, long)]
1210 pub force: bool,
1211
1212 #[arg(long)]
1214 pub all_threads: bool,
1215
1216 #[arg(long)]
1219 pub insecure: bool,
1220
1221 #[arg(long)]
1225 pub dry_run: bool,
1226}
1227
1228impl PushArgs {
1229 pub fn thread_name(&self) -> Option<String> {
1230 self.thread.clone().or_else(|| self.thread_arg.clone())
1231 }
1232}
1233
1234#[derive(Clone, Debug, clap::Args)]
1236#[command(after_help = "\
1237Advanced (hidden) flags:
1238 --lazy leaves blob content absent by design and hydrates it explicitly later. Hosted/network Heddle remotes only.
1239")]
1240pub struct PullArgs {
1241 #[command(flatten)]
1242 pub remote_op: RemoteOperationArgs,
1243
1244 #[arg(short, long)]
1246 pub local_thread: Option<String>,
1247
1248 #[arg(long, hide = true)]
1250 pub lazy: bool,
1251}
1252
1253#[derive(Clone, Debug, clap::Args)]
1261#[command(after_help = "\
1262Behavior:
1263 Clones native Heddle or Git repositories and checks out the selected default branch. Git transport runs through Sley and does not require a Git executable. Never prompts. Full details: `heddle help clone`.
1264
1265Advanced/planned flags: see `heddle help clone`.
1266
1267Examples:
1268 heddle clone ../native-repo ./clone # local native Heddle repository
1269 heddle clone heddle://host/repo ./clone --depth 1 # shallow Heddle clone: tip plus immediate parents
1270")]
1271pub struct CloneArgs {
1272 pub remote: String,
1274
1275 pub local: String,
1277
1278 #[arg(long)]
1280 pub thread: Option<String>,
1281
1282 #[arg(long)]
1284 pub depth: Option<u32>,
1285
1286 #[arg(long, hide = true)]
1290 pub lazy: bool,
1291
1292 #[arg(long)]
1294 pub insecure: bool,
1295
1296 #[arg(long, hide = true, value_name = "SPEC", value_parser = parse_clone_filter_spec)]
1302 pub filter: Option<String>,
1303
1304 #[arg(long, visible_alias = "monorepo")]
1308 pub recursive: bool,
1309}
1310
1311fn parse_clone_filter_spec(s: &str) -> Result<String, String> {
1312 match s {
1313 "blob:none" => Ok(s.to_string()),
1314 other => Err(format!(
1315 "unsupported --filter spec `{other}`; only `blob:none` is supported today"
1316 )),
1317 }
1318}
1319
1320#[derive(Clone, Debug, clap::Args)]
1322pub struct AgentProvenanceBeginArgs {
1323 #[arg(long)]
1325 pub provider: String,
1326
1327 #[arg(long)]
1329 pub model: String,
1330
1331 #[arg(long)]
1333 pub policy: Option<String>,
1334}
1335
1336#[derive(Clone, Debug, clap::Args)]
1338pub struct AgentProvenanceSegmentArgs {
1339 #[arg(long)]
1341 pub provider: String,
1342
1343 #[arg(long)]
1345 pub model: String,
1346
1347 #[arg(long)]
1349 pub policy: Option<String>,
1350}
1351
1352#[derive(Clone, Debug, clap::Args)]
1354pub struct AgentProvenanceEndArgs {
1355 pub session_id: Option<String>,
1357}
1358
1359#[derive(Clone, Debug, clap::Args)]
1361pub struct AgentProvenanceShowArgs {
1362 pub session_id: Option<String>,
1364}
1365
1366#[derive(Clone, Debug, clap::Args)]
1368pub struct AgentProvenanceListArgs {
1369 #[arg(long)]
1371 pub active: bool,
1372}
1373
1374#[derive(Clone, Debug, clap::Args)]
1376pub struct WorktreeAddArgs {
1377 pub path: std::path::PathBuf,
1379
1380 #[arg(long)]
1382 pub thread: Option<String>,
1383
1384 #[arg(long)]
1386 pub from: Option<String>,
1387}
1388
1389#[derive(Clone, Debug, clap::Args)]
1391pub struct WorktreeRemoveArgs {
1392 pub path: std::path::PathBuf,
1394
1395 #[arg(long)]
1397 pub delete_thread: bool,
1398}
1399
1400#[derive(Clone, Debug, clap::Args)]
1402pub struct AgentPresenceListArgs {
1403 #[arg(long)]
1405 pub active: bool,
1406}
1407
1408#[derive(Clone, Debug, clap::Args)]
1410pub struct AgentPresenceShowArgs {
1411 pub session: Option<String>,
1413}
1414
1415#[derive(Clone, Debug, clap::Args)]
1417pub struct AgentPresenceExplainArgs {
1418 pub session: Option<String>,
1420}
1421
1422#[derive(Clone, Debug, clap::Args)]
1424pub struct AgentPresenceCompleteArgs {
1425 #[arg(long)]
1427 pub session: Option<String>,
1428}
1429
1430#[derive(Clone, Debug, clap::Args)]
1432pub struct AgentReserveArgs {
1433 #[arg(long)]
1435 pub thread: String,
1436
1437 #[arg(long)]
1439 pub anchor: Option<String>,
1440
1441 #[arg(long)]
1443 pub task: Option<String>,
1444
1445 #[arg(long)]
1447 pub task_id: Option<String>,
1448
1449 #[arg(long, value_name = "PID")]
1451 pub hold_for_pid: Option<u32>,
1452}
1453
1454#[derive(Clone, Debug, clap::Args)]
1456pub struct AgentHeartbeatArgs {
1457 #[arg(long)]
1459 pub lease: String,
1460
1461 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1463 pub token: String,
1464}
1465
1466#[derive(Clone, Debug, clap::Args)]
1468pub struct AgentReleaseArgs {
1469 #[arg(long)]
1471 pub lease: String,
1472
1473 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1475 pub token: String,
1476
1477 #[arg(long, default_value = "complete")]
1479 pub status: AgentReleaseStatusArg,
1480}
1481
1482#[derive(Clone, Debug, clap::ValueEnum)]
1483pub enum AgentReleaseStatusArg {
1484 Complete,
1485 Abandoned,
1486}
1487
1488#[derive(Clone, Debug, clap::Args)]
1490pub struct AgentApiListArgs {
1491 #[arg(long)]
1493 pub thread: Option<String>,
1494
1495 #[arg(long)]
1497 pub alive_only: bool,
1498}
1499
1500#[derive(Clone, Debug, clap::ValueEnum)]
1501pub enum AgentTaskStatusArg {
1502 Open,
1503 InProgress,
1504 Blocked,
1505 Complete,
1506 Abandoned,
1507}
1508
1509#[derive(Clone, Debug, clap::Args)]
1511pub struct AgentTaskCreateArgs {
1512 #[arg(long)]
1514 pub task_id: Option<String>,
1515
1516 #[arg(long)]
1518 pub title: String,
1519
1520 #[arg(long)]
1522 pub body: Option<String>,
1523
1524 #[arg(long)]
1526 pub thread: String,
1527
1528 #[arg(long)]
1530 pub base_state: Option<String>,
1531
1532 #[arg(long)]
1534 pub base_root: Option<String>,
1535
1536 #[arg(long)]
1538 pub parent_task_id: Option<String>,
1539
1540 #[arg(long)]
1542 pub coordination_discussion_id: Option<String>,
1543
1544 #[arg(long)]
1546 pub allow_offline: bool,
1547
1548 #[arg(long)]
1550 pub delegated_by: Option<String>,
1551}
1552
1553#[derive(Clone, Debug, clap::Args)]
1555pub struct AgentTaskListArgs {
1556 #[arg(long)]
1558 pub thread: Option<String>,
1559
1560 #[arg(long)]
1562 pub status: Option<AgentTaskStatusArg>,
1563}
1564
1565#[derive(Clone, Debug, clap::Args)]
1567pub struct AgentTaskShowArgs {
1568 pub task_id: String,
1570}
1571
1572#[derive(Clone, Debug, clap::Args)]
1574pub struct AgentTaskUpdateArgs {
1575 pub task_id: String,
1577
1578 #[arg(long)]
1580 pub title: Option<String>,
1581
1582 #[arg(long)]
1584 pub body: Option<String>,
1585
1586 #[arg(long)]
1588 pub status: Option<AgentTaskStatusArg>,
1589
1590 #[arg(long)]
1592 pub thread: Option<String>,
1593
1594 #[arg(long)]
1596 pub base_state: Option<String>,
1597
1598 #[arg(long)]
1600 pub base_root: Option<String>,
1601
1602 #[arg(long)]
1604 pub parent_task_id: Option<String>,
1605
1606 #[arg(long)]
1608 pub coordination_discussion_id: Option<String>,
1609
1610 #[arg(long, conflicts_with = "no_allow_offline")]
1612 pub allow_offline: bool,
1613
1614 #[arg(long, conflicts_with = "allow_offline")]
1616 pub no_allow_offline: bool,
1617
1618 #[arg(long)]
1620 pub delegated_by: Option<String>,
1621}
1622
1623#[derive(Clone, Debug, clap::Args)]
1625pub struct AgentFanoutPlanArgs {
1626 #[arg(long)]
1628 pub title: String,
1629
1630 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1632 pub lane: Vec<String>,
1633
1634 #[arg(long)]
1636 pub coordination_discussion_id: Option<String>,
1637}
1638
1639#[derive(Clone, Debug, clap::Args)]
1641pub struct AgentFanoutStartArgs {
1642 #[arg(long)]
1644 pub title: String,
1645
1646 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1648 pub lane: Vec<String>,
1649
1650 #[arg(long)]
1652 pub coordination_discussion_id: Option<String>,
1653}
1654
1655#[derive(Clone, Debug, clap::Args)]
1657pub struct AgentCaptureArgs {
1658 #[arg(long)]
1660 pub lease: String,
1661
1662 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1664 pub token: String,
1665
1666 #[arg(long, short = 'm', alias = "intent")]
1668 pub message: Option<String>,
1669
1670 #[arg(long, value_parser = parse_confidence)]
1672 pub confidence: Option<f32>,
1673}
1674
1675#[derive(Clone, Debug, clap::Args)]
1677pub struct AgentReadyArgs {
1678 #[arg(long)]
1680 pub lease: String,
1681
1682 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1684 pub token: String,
1685
1686 #[arg(long, short = 'm')]
1688 pub message: Option<String>,
1689
1690 #[arg(long, value_parser = parse_confidence)]
1692 pub confidence: Option<f32>,
1693}
1694
1695#[derive(Clone, Debug, clap::Args)]
1703pub struct WatchArgs {
1704 #[arg(long, value_name = "DURATION")]
1708 pub since: Option<String>,
1709
1710 #[arg(long, value_name = "KINDS")]
1714 pub filter: Option<String>,
1715
1716 #[arg(long, hide = true)]
1719 pub max_iterations: Option<usize>,
1720
1721 #[arg(long, hide = true)]
1724 pub poll_interval_ms: Option<u64>,
1725}
1726
1727#[cfg(test)]
1733mod capture_message_alias_tests {
1734 use clap::Parser;
1735
1736 use crate::cli::{Cli, Commands, SnapshotArgs};
1737
1738 fn parse_capture(extra: &[&str]) -> Result<SnapshotArgs, clap::Error> {
1739 let mut argv: Vec<&str> = vec!["heddle", "capture"];
1740 argv.extend_from_slice(extra);
1741 let cli = Cli::try_parse_from(argv)?;
1742 match cli.command {
1743 Commands::Capture(args) => Ok(args),
1744 _ => panic!("expected Commands::Capture"),
1745 }
1746 }
1747
1748 #[test]
1749 fn capture_accepts_message_alias() {
1750 let args = parse_capture(&["--message", "my change"]).expect("--message should parse");
1751 assert_eq!(args.intent.as_deref(), Some("my change"));
1752 }
1753
1754 #[test]
1755 fn capture_accepts_intent_long_form() {
1756 let args = parse_capture(&["--intent", "my change"]).expect("--intent should parse");
1757 assert_eq!(args.intent.as_deref(), Some("my change"));
1758 }
1759
1760 #[test]
1761 fn capture_accepts_short_m() {
1762 let args = parse_capture(&["-m", "my change"]).expect("-m should parse");
1763 assert_eq!(args.intent.as_deref(), Some("my change"));
1764 }
1765
1766 #[test]
1767 fn capture_rejects_non_finite_or_out_of_range_confidence() {
1768 for value in ["NaN", "inf", "-0.1", "1.7"] {
1769 let confidence_arg = format!("--confidence={value}");
1770 let err = parse_capture(&["-m", "bad confidence", &confidence_arg])
1771 .expect_err("invalid confidence should fail to parse");
1772 assert!(
1773 err.to_string()
1774 .contains("confidence must be a finite number from 0.0 to 1.0"),
1775 "unexpected parse error for {value}: {err}"
1776 );
1777 }
1778 }
1779}
1780
1781#[cfg(test)]
1782mod clone_filter_tests {
1783 use clap::Parser;
1784
1785 use crate::cli::{Cli, CloneArgs, Commands};
1786
1787 fn parse_clone(extra: &[&str]) -> Result<CloneArgs, clap::Error> {
1788 let mut argv: Vec<&str> = vec!["heddle", "clone", "remote", "local"];
1789 argv.extend_from_slice(extra);
1790 let cli = Cli::try_parse_from(argv)?;
1791 match cli.command {
1792 Commands::Clone(args) => Ok(args),
1793 _ => panic!("expected Commands::Clone"),
1794 }
1795 }
1796
1797 #[test]
1798 fn parses_clone_filter_blob_none() {
1799 let args = parse_clone(&["--filter", "blob:none"]).expect("parse --filter blob:none");
1800 assert_eq!(args.filter.as_deref(), Some("blob:none"));
1801 assert!(!args.lazy);
1802 }
1803
1804 #[test]
1805 fn rejects_unknown_filter_spec() {
1806 let err = parse_clone(&["--filter", "tree:0"])
1807 .expect_err("unknown --filter spec should fail to parse");
1808 let msg = err.to_string();
1809 assert!(
1810 msg.contains("tree:0") && msg.contains("blob:none"),
1811 "error should name the bad spec and the supported one: {msg}"
1812 );
1813 }
1814}