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' # save state (and its Git Overlay checkpoint)
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 log # walk the current thread
241 heddle log --oneline -n 20 # 20 most recent states in compact form
242 heddle log --timeline # show agent timeline tool-call cursor
243 heddle log --reflog # include re-attributed history
244 heddle log --path src/auth.rs # restrict to states touching a path
245")]
246pub struct LogArgs {
247 pub state: Option<String>,
249
250 #[arg(short = 'n', long, default_value = "20")]
252 pub limit: usize,
253
254 #[arg(long)]
256 pub all: bool,
257
258 #[arg(long)]
260 pub graph: bool,
261
262 #[arg(long)]
264 pub oneline: bool,
265
266 #[arg(long)]
268 pub reflog: bool,
269
270 #[arg(long)]
272 pub timeline: bool,
273
274 #[arg(long, default_value = "main")]
276 pub thread: String,
277
278 #[arg(long)]
280 pub agent: Option<String>,
281
282 #[arg(long = "path", value_name = "PATH")]
284 pub paths: Vec<String>,
285
286 #[arg(long, value_name = "STATE")]
292 pub since: Option<String>,
293}
294
295#[derive(Clone, Debug, clap::Subcommand)]
297pub enum TimelineCommands {
298 Status(TimelineStatusArgs),
300
301 #[command(name = "record-start")]
303 RecordStart(TimelineRecordStartArgs),
304
305 #[command(name = "record-finish")]
307 RecordFinish(TimelineRecordFinishArgs),
308
309 #[command(after_help = "\
311Examples:
312 heddle agent timeline fork --step tls-abc --branch tlb-experiment
313 heddle agent timeline fork --tool-call call_123 --session ses_456 --branch tlb-alt
314")]
315 Fork(TimelineForkArgs),
316
317 #[command(after_help = "\
319Examples:
320 heddle agent timeline reset --step tls-abc
321 heddle agent timeline reset --tool-call call_123 --materialize
322")]
323 Reset(TimelineResetArgs),
324
325 Recover(TimelineRecoverArgs),
327}
328
329#[derive(Clone, Debug, clap::Args)]
331pub struct TimelineTargetArgs {
332 #[arg(long, default_value = "main")]
334 pub thread: String,
335
336 #[arg(long = "from-branch", value_name = "BRANCH")]
338 pub from_branch: Option<String>,
339
340 #[arg(long, conflicts_with_all = ["tool_call", "undo", "redo", "current"])]
342 pub step: Option<String>,
343
344 #[arg(long = "tool-call", conflicts_with_all = ["step", "undo", "redo", "current"])]
346 pub tool_call: Option<String>,
347
348 #[arg(long, default_value = "opencode")]
350 pub harness: String,
351
352 #[arg(long)]
354 pub session: Option<String>,
355
356 #[arg(long)]
358 pub message: Option<String>,
359
360 #[arg(long, conflicts_with_all = ["step", "tool_call", "redo", "current"])]
362 pub undo: bool,
363
364 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "current"])]
366 pub redo: bool,
367
368 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "redo"])]
370 pub current: bool,
371}
372
373#[derive(Clone, Debug, clap::Args)]
375pub struct TimelineForkArgs {
376 #[command(flatten)]
377 pub target: TimelineTargetArgs,
378
379 #[arg(long, value_name = "BRANCH")]
381 pub branch: Option<String>,
382
383 #[arg(long, default_value = "explicit-fork")]
385 pub reason: String,
386}
387
388#[derive(Clone, Debug, clap::Args)]
390pub struct TimelineResetArgs {
391 #[command(flatten)]
392 pub target: TimelineTargetArgs,
393
394 #[arg(long)]
396 pub materialize: bool,
397
398 #[arg(long, default_value = "fail-if-dirty")]
400 pub mode: String,
401}
402
403#[derive(Clone, Debug, clap::Args)]
405pub struct TimelineRecoverArgs {
406 #[arg(long, default_value = "main")]
408 pub thread: String,
409}
410
411#[derive(Clone, Debug, clap::Args)]
413pub struct TimelineStatusArgs {
414 #[arg(long, default_value = "main")]
416 pub thread: String,
417}
418
419#[derive(Clone, Debug, clap::Args)]
421pub struct TimelineRecordToolArgs {
422 #[arg(long, default_value = "main")]
424 pub thread: String,
425
426 #[arg(long, default_value = "opencode")]
428 pub harness: String,
429
430 #[arg(long)]
432 pub session: Option<String>,
433
434 #[arg(long)]
436 pub message: Option<String>,
437
438 #[arg(long = "tool-call")]
440 pub tool_call: String,
441
442 #[arg(long = "step-id")]
444 pub step_id: Option<String>,
445
446 #[arg(long = "branch")]
448 pub branch: Option<String>,
449
450 #[arg(long = "summary")]
452 pub summary: Option<String>,
453
454 #[arg(long = "payload-hash")]
456 pub payload_hash: Option<String>,
457}
458
459#[derive(Clone, Debug, clap::Args)]
461pub struct TimelineRecordStartArgs {
462 #[command(flatten)]
463 pub tool: TimelineRecordToolArgs,
464
465 #[arg(long = "tool-name", default_value = "tool")]
467 pub tool_name: String,
468}
469
470#[derive(Clone, Debug, clap::Args)]
472pub struct TimelineRecordFinishArgs {
473 #[command(flatten)]
474 pub tool: TimelineRecordToolArgs,
475
476 #[arg(long, default_value = "succeeded")]
478 pub status: String,
479}
480
481#[derive(Clone, Debug, clap::Args)]
483#[command(after_help = "\
484Examples:
485 heddle diff # worktree vs HEAD
486 heddle diff --base last-turn # worktree vs this agent peer's turn start
487 heddle diff NOTES.md # only that path
488 heddle diff -- NOTES.md # same, after the path separator
489 heddle diff --path NOTES.md # same, explicit path filter
490 heddle diff HEAD~1 HEAD -- src # two states, filtered to src/
491
492Path-shaped positionals and arguments after `--` are worktree/path filters,
493not missing states. `log --path` uses the same filter spelling.
494
495Restore:
496 Heddle does not restore one file from a saved state (no restore/checkout/reset).
497 Materialize one state in a new checkout: heddle start <name> --from <state> --path <dir>
498 Apply the inverse of one state to this worktree: heddle revert <state> [--no-commit]
499 Restore the tree preserved by the last undo: heddle undo --recover
500
501Patch compatibility:
502 --patch output uses Git-compatible unified diff, including extended headers for type and mode changes.
503")]
504pub struct DiffArgs {
505 pub from: Option<String>,
507
508 pub to: Option<String>,
510
511 #[arg(long, value_enum)]
515 pub base: Option<DiffBaseArg>,
516
517 #[arg(long = "path", value_name = "PATH")]
519 pub path_filters: Vec<String>,
520
521 #[arg(last = true, value_name = "PATH")]
523 pub paths: Vec<String>,
524
525 #[arg(long)]
527 pub semantic: bool,
528
529 #[arg(long)]
531 pub stat: bool,
532
533 #[arg(long)]
535 pub name_only: bool,
536
537 #[arg(short = 'U', long = "unified", default_value_t = 3)]
539 pub unified: usize,
540
541 #[arg(long)]
543 pub context: bool,
544
545 #[arg(short = 'p', long = "patch")]
547 pub patch: bool,
548}
549
550#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
552pub enum DiffBaseArg {
553 LastTurn,
555}
556
557impl DiffBaseArg {
558 pub fn as_str(self) -> &'static str {
559 match self {
560 Self::LastTurn => "last-turn",
561 }
562 }
563}
564
565#[derive(Clone, Debug, clap::Args)]
567#[command(after_help = "\
568Restore:
569 `revert` applies the inverse of one state's changes. It is not a single-file
570 restore, and Heddle has no restore/checkout/reset verb.
571 Materialize one state in a new checkout: heddle start <name> --from <state> --path <dir>
572 Restore the tree preserved by the last undo: heddle undo --recover
573 Heddle cannot put one file back from a saved state.
574")]
575pub struct RevertArgs {
576 pub state: String,
578
579 #[arg(short = 'm', long)]
581 pub message: Option<String>,
582
583 #[arg(long)]
585 pub no_commit: bool,
586}
587
588#[derive(Clone, Debug, clap::Args)]
590#[command(after_help = "\
591Examples:
592 heddle undo --preview # inspect the most recent operation
593 heddle undo --hard --preview # preview the worktree rewind --hard would apply
594 heddle undo --hard # roll it back and rewind the worktree
595 heddle undo -n 3 --hard # roll back the last three operations
596 heddle undo --recover # restore the state preserved by the last undo
597 heddle undo --list # preview undoable operations on this thread
598 heddle undo --dry-run # show what would change without applying
599
600Restore:
601 `--recover` restores only the last undo's preserved tree as worktree changes.
602 Heddle does not restore one arbitrary file or an arbitrary saved state
603 (no restore/checkout/reset). Materialize a state with
604 `heddle start <name> --from <state> --path <dir>`, or invert one with
605 `heddle revert <state>`.
606
607Undoable operations:
608 - heddle capture (restores HEAD to the pre-capture parent)
609 - heddle land (non-FF) (restores HEAD + both thread refs)
610 - heddle land (FF) (restores HEAD + the landed-into thread ref to
611 the pre-merge tip; the merged-in thread is
612 untouched.)
613 - heddle thread switch (restores HEAD to the previous thread state)
614 - heddle thread create/drop/rename
615 - heddle thread marker create/drop
616 - heddle redact apply (with --allow-redact-undo; removes the
617 redaction record so future materializes
618 restore the original blob bytes. Refused
619 when a Purge has destroyed the bytes.)
620 - heddle undo --redo re-apply the most recently undone operation
621
622Not undoable (file a follow-up if you need one):
623 - heddle push / pull (remote-affecting; out of scope)
624 - heddle redact purge apply (destructive by design; irreversible)
625 - heddle start <name> --path <dir> (refused while the materialized worktree
626 still exists — run `heddle thread drop
627 <name> --delete-thread` first, then
628 re-run `heddle undo`)
629 - cross-worktree shared-backend undo (no worktree registry yet; single-
630 worktree usage is the supported
631 configuration for 0.3)
632")]
633pub struct UndoArgs {
634 #[arg(short = 'n', long, default_value = "1")]
636 pub steps: usize,
637
638 #[arg(long)]
640 pub list: bool,
641
642 #[arg(long, default_value = "20")]
644 pub depth: usize,
645
646 #[arg(long, visible_alias = "dry-run")]
649 pub preview: bool,
650
651 #[arg(long, conflicts_with_all = ["list", "redo", "recover"])]
655 pub hard: bool,
656
657 #[arg(long, conflicts_with = "list")]
659 pub redo: bool,
660
661 #[arg(
664 long,
665 conflicts_with_all = ["steps", "list", "preview", "hard", "redo", "allow_redact_undo"]
666 )]
667 pub recover: bool,
668
669 #[arg(long)]
677 pub allow_redact_undo: bool,
678}
679
680#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq)]
686pub enum WorkspaceModeArg {
687 Auto,
689 Materialized,
691 Virtualized,
693 Solid,
695}
696
697#[derive(Clone, Debug, clap::Args)]
699#[command(after_help = "\
700Examples:
701 heddle start feature/auth --path ../feature-auth # create an isolated checkout
702 heddle start scratch --path ../scratch # place the checkout explicitly
703 heddle start fix-flake --path ../fix-flake --task 'fix CI flake'
704
705`--path` is required when workspace is omitted or `auto`. Without it, start
706refuses instead of hiding a checkout under `.heddle/threads/<name>/`.
707`--workspace auto` is the same default and still requires `--path`.
708Pass `--path ../<name>`, or an explicit `--workspace solid|materialized|virtualized`
709if you want the managed layout. To stay on this checkout, use
710`heddle thread create <name>` then `heddle thread switch <name>`.
711
712Isolated 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.
713
714`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.
715
716Advanced (hidden) flags:
717 --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.
718")]
719pub struct ThreadStartArgs {
720 pub name: String,
722
723 #[arg(long)]
725 pub from: Option<String>,
726
727 #[arg(long)]
730 pub path: Option<std::path::PathBuf>,
731
732 #[arg(long, value_enum)]
735 pub workspace: Option<WorkspaceModeArg>,
736
737 #[arg(long, hide = true)]
739 pub agent_provider: Option<String>,
740
741 #[arg(long, hide = true)]
743 pub agent_model: Option<String>,
744
745 #[arg(long)]
747 pub task: Option<String>,
748
749 #[arg(long, hide = true)]
751 pub parent_thread: Option<String>,
752
753 #[arg(long, hide = true)]
755 pub automated: bool,
756
757 #[arg(long, hide = true, conflicts_with_all = ["agent_provider", "agent_model"])]
764 pub print_cd_path: bool,
765
766 #[arg(
773 long,
774 overrides_with = "no_daemon",
775 action = clap::ArgAction::SetTrue,
776 default_value_t = true,
777 hide = true,
778 )]
779 pub daemon: bool,
780
781 #[arg(
787 long,
788 overrides_with = "daemon",
789 action = clap::ArgAction::SetTrue,
790 hide = true,
791 )]
792 pub no_daemon: bool,
793
794 #[arg(long)]
798 pub interactive_setup: bool,
799
800 #[arg(
814 long,
815 overrides_with = "no_shared_target",
816 action = clap::ArgAction::SetTrue,
817 hide = true,
818 )]
819 pub shared_target: bool,
820
821 #[arg(
825 long,
826 overrides_with = "shared_target",
827 action = clap::ArgAction::SetTrue,
828 hide = true,
829 )]
830 pub no_shared_target: bool,
831
832 #[arg(long)]
843 pub hydrate: bool,
844}
845
846#[derive(Clone, Debug, clap::Args)]
848pub struct ReadyArgs {
849 #[arg(long = "thread")]
851 pub thread: Option<String>,
852
853 #[arg(short = 'm', long)]
855 pub message: Option<String>,
856
857 #[arg(long, value_parser = parse_confidence)]
859 pub confidence: Option<f32>,
860
861 #[arg(long)]
865 pub dry_run: bool,
866}
867
868#[derive(Clone, Debug, clap::Args)]
870pub struct SyncArgs {
871 #[cfg(feature = "git-overlay")]
873 #[command(subcommand)]
874 pub command: Option<SyncCommands>,
875
876 #[arg(long = "thread")]
878 pub thread: Option<String>,
879}
880
881#[derive(Clone, Debug, clap::Args)]
883pub struct LandArgs {
884 #[arg(long = "thread")]
886 pub thread: Option<String>,
887
888 #[arg(long = "threads", value_delimiter = ',')]
893 pub threads: Vec<String>,
894
895 #[arg(short = 'm', long)]
897 pub message: Option<String>,
898
899 #[arg(long)]
901 pub no_squash: bool,
902
903 #[arg(long)]
907 pub dry_run: bool,
908}
909
910#[derive(Clone, Debug, clap::Args)]
912pub struct ThreadShowArgs {
913 pub thread: Option<String>,
915
916 #[arg(long)]
918 pub watch: bool,
919
920 #[arg(long, hide = true)]
922 pub watch_iterations: Option<usize>,
923
924 #[arg(long, hide = true)]
926 pub watch_interval_ms: Option<u64>,
927}
928
929#[derive(Clone, Debug, clap::Args)]
931pub struct ThreadCapturesArgs {
932 pub thread: Option<String>,
934
935 #[arg(long, default_value_t = 20)]
937 pub limit: usize,
938}
939
940#[derive(Clone, Debug, clap::Args)]
944pub struct ThreadNameArgs {
945 pub thread: Option<String>,
947}
948
949#[derive(Clone, Debug, clap::Args)]
951pub struct ThreadRenameArgs {
952 pub old: String,
954
955 pub new: String,
957}
958
959#[derive(Clone, Debug, clap::Args)]
961pub struct ThreadPromoteArgs {
962 pub thread: String,
964
965 #[arg(long)]
967 pub path: Option<std::path::PathBuf>,
968
969 #[arg(long)]
971 pub force: bool,
972}
973
974#[derive(Clone, Debug, clap::Args)]
976pub struct ThreadMoveArgs {
977 pub from: String,
979
980 pub to: String,
982
983 #[arg(long = "path", required = true, value_name = "PATH")]
985 pub paths: Vec<String>,
986
987 #[arg(short = 'm', long)]
989 pub message: Option<String>,
990}
991
992#[derive(Clone, Debug, clap::Args)]
994pub struct ThreadAbsorbArgs {
995 pub thread: String,
997
998 #[arg(long)]
1000 pub into: Option<String>,
1001
1002 #[arg(short = 'm', long)]
1004 pub message: Option<String>,
1005
1006 #[arg(long)]
1008 pub preview: bool,
1009}
1010
1011#[derive(Clone, Debug, clap::Args)]
1013pub struct ThreadResolveArgs {
1014 pub thread: String,
1016}
1017
1018#[derive(Clone, Debug, clap::Args)]
1020pub struct ThreadDropArgs {
1021 pub thread: String,
1023
1024 #[arg(long)]
1026 pub delete_thread: bool,
1027
1028 #[arg(short, long)]
1030 pub force: bool,
1031}
1032
1033#[derive(Clone, Debug, clap::Args)]
1037pub struct ThreadApproveArgs {
1038 pub source: String,
1040
1041 pub target: String,
1043
1044 #[arg(long)]
1046 pub note: Option<String>,
1047
1048 #[arg(long, default_value = "origin")]
1050 pub remote: String,
1051}
1052
1053#[derive(Clone, Debug, clap::Args)]
1056pub struct ThreadApprovalsArgs {
1057 pub source: String,
1058 pub target: String,
1059 #[arg(long, default_value = "origin")]
1060 pub remote: String,
1061}
1062
1063#[derive(Clone, Debug, clap::Args)]
1066pub struct ThreadRevokeApprovalArgs {
1067 pub id: String,
1069 #[arg(long, default_value = "origin")]
1070 pub remote: String,
1071}
1072
1073#[derive(Clone, Debug, clap::Args)]
1076pub struct ThreadCheckMergeArgs {
1077 pub source: String,
1078 pub target: String,
1079
1080 #[arg(long, default_value = "merge")]
1082 pub gated_action: String,
1083
1084 #[arg(long = "path", value_delimiter = ',')]
1087 pub changed_paths: Vec<String>,
1088
1089 #[arg(long, default_value = "origin")]
1090 pub remote: String,
1091}
1092
1093#[derive(Clone, Debug, clap::Args)]
1095pub struct CollapseArgs {
1096 #[arg(required = true)]
1098 pub states: Vec<String>,
1099
1100 #[arg(long)]
1102 pub into: String,
1103
1104 #[arg(long)]
1106 pub confidence: Option<f32>,
1107}
1108
1109#[derive(Clone, Debug, clap::Args)]
1111pub struct ExpandArgs {
1112 pub reference: String,
1114}
1115
1116#[derive(Clone, Debug, clap::Args)]
1118pub struct ResolveArgs {
1119 pub path: Option<String>,
1121
1122 #[arg(long)]
1124 pub all: bool,
1125
1126 #[arg(long)]
1128 pub list: bool,
1129
1130 #[arg(long, conflicts_with = "theirs")]
1132 pub ours: bool,
1133
1134 #[arg(long, conflicts_with = "ours")]
1136 pub theirs: bool,
1137
1138 #[arg(long)]
1140 pub force: bool,
1141}
1142
1143#[derive(Clone, Debug, clap::Args)]
1146pub struct RemoteOperationArgs {
1147 pub remote: Option<String>,
1149
1150 #[arg(short, long)]
1152 pub thread: Option<String>,
1153
1154 #[arg(long)]
1157 pub insecure: bool,
1158}
1159
1160#[derive(Clone, Debug, clap::Args)]
1162#[command(after_help = "\
1163Git Overlay refs:
1164 A normal push writes refs/heads/<thread> and refs/notes/heddle.
1165 --all-threads writes every refs/heads/<thread> and refs/tags/<tag>, plus refs/notes/heddle.
1166 JSON output lists changed refs in refs_written; verify with git ls-remote <remote>.
1167")]
1168pub struct PushArgs {
1169 pub remote: Option<String>,
1171
1172 #[arg(short, long, conflicts_with = "thread_arg")]
1174 pub thread: Option<String>,
1175
1176 #[arg(value_name = "THREAD")]
1178 pub thread_arg: Option<String>,
1179
1180 #[arg(short, long)]
1182 pub state: Option<String>,
1183
1184 #[arg(short, long)]
1186 pub force: bool,
1187
1188 #[arg(long)]
1190 pub all_threads: bool,
1191
1192 #[arg(long)]
1195 pub insecure: bool,
1196
1197 #[arg(long)]
1201 pub dry_run: bool,
1202}
1203
1204impl PushArgs {
1205 pub fn thread_name(&self) -> Option<String> {
1206 self.thread.clone().or_else(|| self.thread_arg.clone())
1207 }
1208}
1209
1210#[derive(Clone, Debug, clap::Args)]
1212#[command(after_help = "\
1213Advanced (hidden) flags:
1214 --lazy leaves blob content absent by design and hydrates it explicitly later. Hosted/network Heddle remotes only.
1215")]
1216pub struct PullArgs {
1217 #[command(flatten)]
1218 pub remote_op: RemoteOperationArgs,
1219
1220 #[arg(short, long)]
1222 pub local_thread: Option<String>,
1223
1224 #[arg(long, hide = true)]
1226 pub lazy: bool,
1227}
1228
1229#[derive(Clone, Debug, clap::Args)]
1237#[command(after_help = "\
1238Behavior:
1239 URLs ending in `.git` clone Git repositories; other HTTPS URLs clone hosted Heddle spools. 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`.
1240
1241Advanced/planned flags: see `heddle help clone`.
1242
1243Examples:
1244 heddle clone ../native-repo ./clone # local native Heddle repository
1245 heddle clone https://host/repo ./clone --depth 1 # shallow Heddle clone: tip plus immediate parents
1246")]
1247pub struct CloneArgs {
1248 pub remote: String,
1250
1251 pub local: String,
1253
1254 #[arg(long)]
1256 pub thread: Option<String>,
1257
1258 #[arg(long)]
1260 pub depth: Option<u32>,
1261
1262 #[arg(long, hide = true)]
1266 pub lazy: bool,
1267
1268 #[arg(long)]
1270 pub insecure: bool,
1271
1272 #[arg(long, hide = true, value_name = "SPEC", value_parser = parse_clone_filter_spec)]
1278 pub filter: Option<String>,
1279
1280 #[arg(long, visible_alias = "monorepo")]
1284 pub recursive: bool,
1285}
1286
1287fn parse_clone_filter_spec(s: &str) -> Result<String, String> {
1288 match s {
1289 "blob:none" => Ok(s.to_string()),
1290 other => Err(format!(
1291 "unsupported --filter spec `{other}`; only `blob:none` is supported today"
1292 )),
1293 }
1294}
1295
1296#[derive(Clone, Debug, clap::Args)]
1298pub struct AgentProvenanceBeginArgs {
1299 #[arg(long)]
1301 pub provider: String,
1302
1303 #[arg(long)]
1305 pub model: String,
1306
1307 #[arg(long)]
1309 pub policy: Option<String>,
1310}
1311
1312#[derive(Clone, Debug, clap::Args)]
1314pub struct AgentProvenanceSegmentArgs {
1315 #[arg(long)]
1317 pub provider: String,
1318
1319 #[arg(long)]
1321 pub model: String,
1322
1323 #[arg(long)]
1325 pub policy: Option<String>,
1326}
1327
1328#[derive(Clone, Debug, clap::Args)]
1330pub struct AgentProvenanceEndArgs {
1331 pub session_id: Option<String>,
1333}
1334
1335#[derive(Clone, Debug, clap::Args)]
1337pub struct AgentProvenanceShowArgs {
1338 pub session_id: Option<String>,
1340}
1341
1342#[derive(Clone, Debug, clap::Args)]
1344pub struct AgentProvenanceListArgs {
1345 #[arg(long)]
1347 pub active: bool,
1348}
1349
1350#[derive(Clone, Debug, clap::Args)]
1352pub struct WorktreeAddArgs {
1353 pub path: std::path::PathBuf,
1355
1356 #[arg(long)]
1358 pub thread: Option<String>,
1359
1360 #[arg(long)]
1362 pub from: Option<String>,
1363}
1364
1365#[derive(Clone, Debug, clap::Args)]
1367pub struct WorktreeRemoveArgs {
1368 pub path: std::path::PathBuf,
1370
1371 #[arg(long)]
1373 pub delete_thread: bool,
1374}
1375
1376#[derive(Clone, Debug, clap::Args)]
1378pub struct AgentPresenceListArgs {
1379 #[arg(long)]
1381 pub active: bool,
1382}
1383
1384#[derive(Clone, Debug, clap::Args)]
1386pub struct AgentPresenceShowArgs {
1387 pub session: Option<String>,
1389}
1390
1391#[derive(Clone, Debug, clap::Args)]
1393pub struct AgentPresenceExplainArgs {
1394 pub session: Option<String>,
1396}
1397
1398#[derive(Clone, Debug, clap::Args)]
1400pub struct AgentPresenceCompleteArgs {
1401 #[arg(long)]
1403 pub session: Option<String>,
1404}
1405
1406#[derive(Clone, Debug, clap::Args)]
1408pub struct AgentReserveArgs {
1409 #[arg(long)]
1411 pub thread: String,
1412
1413 #[arg(long)]
1415 pub anchor: Option<String>,
1416
1417 #[arg(long)]
1419 pub task: Option<String>,
1420
1421 #[arg(long)]
1423 pub task_id: Option<String>,
1424
1425 #[arg(long, value_name = "PID")]
1427 pub hold_for_pid: Option<u32>,
1428}
1429
1430#[derive(Clone, Debug, clap::Args)]
1432pub struct AgentHeartbeatArgs {
1433 #[arg(long)]
1435 pub lease: String,
1436
1437 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1439 pub token: String,
1440}
1441
1442#[derive(Clone, Debug, clap::Args)]
1444pub struct AgentReleaseArgs {
1445 #[arg(long)]
1447 pub lease: String,
1448
1449 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1451 pub token: String,
1452
1453 #[arg(long, default_value = "complete")]
1455 pub status: AgentReleaseStatusArg,
1456}
1457
1458#[derive(Clone, Debug, clap::ValueEnum)]
1459pub enum AgentReleaseStatusArg {
1460 Complete,
1461 Abandoned,
1462}
1463
1464#[derive(Clone, Debug, clap::Args)]
1466pub struct AgentApiListArgs {
1467 #[arg(long)]
1469 pub thread: Option<String>,
1470
1471 #[arg(long)]
1473 pub alive_only: bool,
1474}
1475
1476#[derive(Clone, Debug, clap::ValueEnum)]
1477pub enum AgentTaskStatusArg {
1478 Open,
1479 InProgress,
1480 Blocked,
1481 Complete,
1482 Abandoned,
1483}
1484
1485#[derive(Clone, Debug, clap::Args)]
1487pub struct AgentTaskCreateArgs {
1488 #[arg(long)]
1490 pub task_id: Option<String>,
1491
1492 #[arg(long)]
1494 pub title: String,
1495
1496 #[arg(long)]
1498 pub body: Option<String>,
1499
1500 #[arg(long)]
1502 pub thread: String,
1503
1504 #[arg(long)]
1506 pub base_state: Option<String>,
1507
1508 #[arg(long)]
1510 pub base_root: Option<String>,
1511
1512 #[arg(long)]
1514 pub parent_task_id: Option<String>,
1515
1516 #[arg(long)]
1518 pub coordination_discussion_id: Option<String>,
1519
1520 #[arg(long)]
1522 pub allow_offline: bool,
1523
1524 #[arg(long)]
1526 pub delegated_by: Option<String>,
1527}
1528
1529#[derive(Clone, Debug, clap::Args)]
1531pub struct AgentTaskListArgs {
1532 #[arg(long)]
1534 pub thread: Option<String>,
1535
1536 #[arg(long)]
1538 pub status: Option<AgentTaskStatusArg>,
1539}
1540
1541#[derive(Clone, Debug, clap::Args)]
1543pub struct AgentTaskShowArgs {
1544 pub task_id: String,
1546}
1547
1548#[derive(Clone, Debug, clap::Args)]
1550pub struct AgentTaskUpdateArgs {
1551 pub task_id: String,
1553
1554 #[arg(long)]
1556 pub title: Option<String>,
1557
1558 #[arg(long)]
1560 pub body: Option<String>,
1561
1562 #[arg(long)]
1564 pub status: Option<AgentTaskStatusArg>,
1565
1566 #[arg(long)]
1568 pub thread: Option<String>,
1569
1570 #[arg(long)]
1572 pub base_state: Option<String>,
1573
1574 #[arg(long)]
1576 pub base_root: Option<String>,
1577
1578 #[arg(long)]
1580 pub parent_task_id: Option<String>,
1581
1582 #[arg(long)]
1584 pub coordination_discussion_id: Option<String>,
1585
1586 #[arg(long, conflicts_with = "no_allow_offline")]
1588 pub allow_offline: bool,
1589
1590 #[arg(long, conflicts_with = "allow_offline")]
1592 pub no_allow_offline: bool,
1593
1594 #[arg(long)]
1596 pub delegated_by: Option<String>,
1597}
1598
1599#[derive(Clone, Debug, clap::Args)]
1601pub struct AgentFanoutPlanArgs {
1602 #[arg(long)]
1604 pub title: String,
1605
1606 #[arg(long, value_name = "THREAD=TITLE")]
1608 pub lane: Vec<String>,
1609
1610 #[arg(long)]
1612 pub coordination_discussion_id: Option<String>,
1613}
1614
1615#[derive(Clone, Debug, clap::Args)]
1617pub struct AgentFanoutStartArgs {
1618 #[arg(long)]
1620 pub title: String,
1621
1622 #[arg(long, value_name = "THREAD=TITLE")]
1624 pub lane: Vec<String>,
1625
1626 #[arg(long)]
1628 pub coordination_discussion_id: Option<String>,
1629}
1630
1631#[derive(Clone, Debug, clap::Args)]
1633pub struct AgentCaptureArgs {
1634 #[arg(long)]
1636 pub lease: String,
1637
1638 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1640 pub token: String,
1641
1642 #[arg(long, short = 'm', alias = "intent")]
1644 pub message: Option<String>,
1645
1646 #[arg(long, value_parser = parse_confidence)]
1648 pub confidence: Option<f32>,
1649}
1650
1651#[derive(Clone, Debug, clap::Args)]
1653pub struct AgentReadyArgs {
1654 #[arg(long)]
1656 pub lease: String,
1657
1658 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1660 pub token: String,
1661
1662 #[arg(long, short = 'm')]
1664 pub message: Option<String>,
1665
1666 #[arg(long, value_parser = parse_confidence)]
1668 pub confidence: Option<f32>,
1669}
1670
1671#[derive(Clone, Debug, clap::Args)]
1679pub struct WatchArgs {
1680 #[arg(long, value_name = "DURATION")]
1684 pub since: Option<String>,
1685
1686 #[arg(long, value_name = "KINDS")]
1690 pub filter: Option<String>,
1691
1692 #[arg(long, hide = true)]
1695 pub max_iterations: Option<usize>,
1696
1697 #[arg(long, hide = true)]
1700 pub poll_interval_ms: Option<u64>,
1701}
1702
1703#[cfg(test)]
1709mod capture_message_alias_tests {
1710 use clap::Parser;
1711
1712 use crate::cli::{Cli, Commands, SnapshotArgs};
1713
1714 fn parse_capture(extra: &[&str]) -> Result<SnapshotArgs, clap::Error> {
1715 let mut argv: Vec<&str> = vec!["heddle", "capture"];
1716 argv.extend_from_slice(extra);
1717 let cli = Cli::try_parse_from(argv)?;
1718 match cli.command {
1719 Commands::Capture(args) => Ok(args),
1720 _ => panic!("expected Commands::Capture"),
1721 }
1722 }
1723
1724 #[test]
1725 fn capture_accepts_message_alias() {
1726 let args = parse_capture(&["--message", "my change"]).expect("--message should parse");
1727 assert_eq!(args.intent.as_deref(), Some("my change"));
1728 }
1729
1730 #[test]
1731 fn capture_accepts_intent_long_form() {
1732 let args = parse_capture(&["--intent", "my change"]).expect("--intent should parse");
1733 assert_eq!(args.intent.as_deref(), Some("my change"));
1734 }
1735
1736 #[test]
1737 fn capture_accepts_short_m() {
1738 let args = parse_capture(&["-m", "my change"]).expect("-m should parse");
1739 assert_eq!(args.intent.as_deref(), Some("my change"));
1740 }
1741
1742 #[test]
1743 fn capture_parses_without_intent_so_the_refuse_can_fire() {
1744 let args =
1745 parse_capture(&[]).expect("omitted -m is a semantic refuse, not a clap usage error");
1746 assert!(args.intent.is_none());
1747 }
1748
1749 #[test]
1750 fn capture_rejects_non_finite_or_out_of_range_confidence() {
1751 for value in ["NaN", "inf", "-0.1", "1.7"] {
1752 let confidence_arg = format!("--confidence={value}");
1753 let err = parse_capture(&["-m", "bad confidence", &confidence_arg])
1754 .expect_err("invalid confidence should fail to parse");
1755 assert!(
1756 err.to_string()
1757 .contains("confidence must be a finite number from 0.0 to 1.0"),
1758 "unexpected parse error for {value}: {err}"
1759 );
1760 }
1761 }
1762}
1763
1764#[cfg(test)]
1765mod clone_filter_tests {
1766 use clap::Parser;
1767
1768 use crate::cli::{Cli, CloneArgs, Commands};
1769
1770 fn parse_clone(extra: &[&str]) -> Result<CloneArgs, clap::Error> {
1771 let mut argv: Vec<&str> = vec!["heddle", "clone", "remote", "local"];
1772 argv.extend_from_slice(extra);
1773 let cli = Cli::try_parse_from(argv)?;
1774 match cli.command {
1775 Commands::Clone(args) => Ok(args),
1776 _ => panic!("expected Commands::Clone"),
1777 }
1778 }
1779
1780 #[test]
1781 fn parses_clone_filter_blob_none() {
1782 let args = parse_clone(&["--filter", "blob:none"]).expect("parse --filter blob:none");
1783 assert_eq!(args.filter.as_deref(), Some("blob:none"));
1784 assert!(!args.lazy);
1785 }
1786
1787 #[test]
1788 fn rejects_unknown_filter_spec() {
1789 let err = parse_clone(&["--filter", "tree:0"])
1790 .expect_err("unknown --filter spec should fail to parse");
1791 let msg = err.to_string();
1792 assert!(
1793 msg.contains("tree:0") && msg.contains("blob:none"),
1794 "error should name the bad spec and the supported one: {msg}"
1795 );
1796 }
1797}