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::Subcommand)]
315pub enum TimelineCommands {
316 Status(TimelineStatusArgs),
318
319 #[command(name = "record-start")]
321 RecordStart(TimelineRecordStartArgs),
322
323 #[command(name = "record-finish")]
325 RecordFinish(TimelineRecordFinishArgs),
326
327 #[command(after_help = "\
329Examples:
330 heddle agent timeline fork --step tls-abc --branch tlb-experiment
331 heddle agent timeline fork --tool-call call_123 --session ses_456 --branch tlb-alt
332")]
333 Fork(TimelineForkArgs),
334
335 #[command(after_help = "\
337Examples:
338 heddle agent timeline reset --step tls-abc
339 heddle agent timeline reset --tool-call call_123 --materialize
340")]
341 Reset(TimelineResetArgs),
342
343 Recover(TimelineRecoverArgs),
345}
346
347#[derive(Clone, Debug, clap::Args)]
349pub struct TimelineTargetArgs {
350 #[arg(long, default_value = "main")]
352 pub thread: String,
353
354 #[arg(long = "from-branch", value_name = "BRANCH")]
356 pub from_branch: Option<String>,
357
358 #[arg(long, conflicts_with_all = ["tool_call", "undo", "redo", "current"])]
360 pub step: Option<String>,
361
362 #[arg(long = "tool-call", conflicts_with_all = ["step", "undo", "redo", "current"])]
364 pub tool_call: Option<String>,
365
366 #[arg(long, default_value = "opencode")]
368 pub harness: String,
369
370 #[arg(long)]
372 pub session: Option<String>,
373
374 #[arg(long)]
376 pub message: Option<String>,
377
378 #[arg(long, conflicts_with_all = ["step", "tool_call", "redo", "current"])]
380 pub undo: bool,
381
382 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "current"])]
384 pub redo: bool,
385
386 #[arg(long, conflicts_with_all = ["step", "tool_call", "undo", "redo"])]
388 pub current: bool,
389}
390
391#[derive(Clone, Debug, clap::Args)]
393pub struct TimelineForkArgs {
394 #[command(flatten)]
395 pub target: TimelineTargetArgs,
396
397 #[arg(long, value_name = "BRANCH")]
399 pub branch: Option<String>,
400
401 #[arg(long, default_value = "explicit-fork")]
403 pub reason: String,
404}
405
406#[derive(Clone, Debug, clap::Args)]
408pub struct TimelineResetArgs {
409 #[command(flatten)]
410 pub target: TimelineTargetArgs,
411
412 #[arg(long)]
414 pub materialize: bool,
415
416 #[arg(long, default_value = "fail-if-dirty")]
418 pub mode: String,
419}
420
421#[derive(Clone, Debug, clap::Args)]
423pub struct TimelineRecoverArgs {
424 #[arg(long, default_value = "main")]
426 pub thread: String,
427}
428
429#[derive(Clone, Debug, clap::Args)]
431pub struct TimelineStatusArgs {
432 #[arg(long, default_value = "main")]
434 pub thread: String,
435}
436
437#[derive(Clone, Debug, clap::Args)]
439pub struct TimelineRecordToolArgs {
440 #[arg(long, default_value = "main")]
442 pub thread: String,
443
444 #[arg(long, default_value = "opencode")]
446 pub harness: String,
447
448 #[arg(long)]
450 pub session: Option<String>,
451
452 #[arg(long)]
454 pub message: Option<String>,
455
456 #[arg(long = "tool-call")]
458 pub tool_call: String,
459
460 #[arg(long = "step-id")]
462 pub step_id: Option<String>,
463
464 #[arg(long = "branch")]
466 pub branch: Option<String>,
467
468 #[arg(long = "summary")]
470 pub summary: Option<String>,
471
472 #[arg(long = "payload-hash")]
474 pub payload_hash: Option<String>,
475}
476
477#[derive(Clone, Debug, clap::Args)]
479pub struct TimelineRecordStartArgs {
480 #[command(flatten)]
481 pub tool: TimelineRecordToolArgs,
482
483 #[arg(long = "tool-name", default_value = "tool")]
485 pub tool_name: String,
486}
487
488#[derive(Clone, Debug, clap::Args)]
490pub struct TimelineRecordFinishArgs {
491 #[command(flatten)]
492 pub tool: TimelineRecordToolArgs,
493
494 #[arg(long, default_value = "succeeded")]
496 pub status: String,
497}
498
499#[derive(Clone, Debug, clap::Args)]
501#[command(after_help = "\
502Examples:
503 heddle diff # worktree vs HEAD
504 heddle diff --base last-turn # worktree vs this agent peer's turn start
505 heddle diff NOTES.md # only that path
506 heddle diff -- NOTES.md # same, after the path separator
507 heddle diff --path NOTES.md # same, explicit path filter
508 heddle diff HEAD~1 HEAD -- src # two states, filtered to src/
509
510Path-shaped positionals and arguments after `--` are worktree/path filters,
511not missing states. `log --path` uses the same filter spelling.
512
513Restore:
514 Heddle does not restore one file from a saved state (no restore/checkout/reset).
515 Materialize one state in a new checkout: heddle start <name> --from <state> --path <dir>
516 Apply the inverse of one state to this worktree: heddle revert <state> [--no-commit]
517 Restore the tree preserved by the last undo: heddle undo --recover
518
519Patch compatibility:
520 --patch output uses Git-compatible unified diff, including extended headers for type and mode changes.
521")]
522pub struct DiffArgs {
523 pub from: Option<String>,
525
526 pub to: Option<String>,
528
529 #[arg(long, value_enum)]
533 pub base: Option<DiffBaseArg>,
534
535 #[arg(long = "path", value_name = "PATH")]
537 pub path_filters: Vec<String>,
538
539 #[arg(last = true, value_name = "PATH")]
541 pub paths: Vec<String>,
542
543 #[arg(long)]
545 pub semantic: bool,
546
547 #[arg(long)]
549 pub stat: bool,
550
551 #[arg(long)]
553 pub name_only: bool,
554
555 #[arg(short = 'U', long = "unified", default_value_t = 3)]
557 pub unified: usize,
558
559 #[arg(long)]
561 pub context: bool,
562
563 #[arg(short = 'p', long = "patch")]
565 pub patch: bool,
566}
567
568#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
570pub enum DiffBaseArg {
571 LastTurn,
573}
574
575impl DiffBaseArg {
576 pub fn as_str(self) -> &'static str {
577 match self {
578 Self::LastTurn => "last-turn",
579 }
580 }
581}
582
583#[derive(Clone, Debug, clap::Args)]
585#[command(after_help = "\
586Restore:
587 `revert` applies the inverse of one state's changes. It is not a single-file
588 restore, and Heddle has no restore/checkout/reset verb.
589 Materialize one state in a new checkout: heddle start <name> --from <state> --path <dir>
590 Restore the tree preserved by the last undo: heddle undo --recover
591 Heddle cannot put one file back from a saved state.
592")]
593pub struct RevertArgs {
594 pub state: String,
596
597 #[arg(short = 'm', long)]
599 pub message: Option<String>,
600
601 #[arg(long)]
603 pub no_commit: bool,
604}
605
606#[derive(Clone, Debug, clap::Args)]
608#[command(after_help = "\
609Examples:
610 heddle undo --preview # inspect the most recent operation
611 heddle undo --hard --preview # preview the worktree rewind --hard would apply
612 heddle undo --hard # roll it back and rewind the worktree
613 heddle undo -n 3 --hard # roll back the last three operations
614 heddle undo --recover # restore the state preserved by the last undo
615 heddle undo --list # preview undoable operations on this thread
616 heddle undo --dry-run # show what would change without applying
617
618Restore:
619 `--recover` restores only the last undo's preserved tree as worktree changes.
620 Heddle does not restore one arbitrary file or an arbitrary saved state
621 (no restore/checkout/reset). Materialize a state with
622 `heddle start <name> --from <state> --path <dir>`, or invert one with
623 `heddle revert <state>`.
624
625Undoable operations:
626 - heddle capture (restores HEAD to the pre-capture parent)
627 - heddle land (non-FF) (restores HEAD + both thread refs)
628 - heddle land (FF) (restores HEAD + the landed-into thread ref to
629 the pre-merge tip; the merged-in thread is
630 untouched.)
631 - heddle thread switch (restores HEAD to the previous thread state)
632 - heddle thread create/drop/rename
633 - heddle thread marker create/drop
634 - heddle redact apply (with --allow-redact-undo; removes the
635 redaction record so future materializes
636 restore the original blob bytes. Refused
637 when a Purge has destroyed the bytes.)
638 - heddle undo --redo re-apply the most recently undone operation
639
640Not undoable (file a follow-up if you need one):
641 - heddle push / pull (remote-affecting; out of scope)
642 - heddle redact purge apply (destructive by design; irreversible)
643 - heddle start <name> --path <dir> (refused while the materialized worktree
644 still exists — run `heddle thread drop
645 <name> --delete-thread` first, then
646 re-run `heddle undo`)
647 - cross-worktree shared-backend undo (no worktree registry yet; single-
648 worktree usage is the supported
649 configuration for 0.3)
650")]
651pub struct UndoArgs {
652 #[arg(short = 'n', long, default_value = "1")]
654 pub steps: usize,
655
656 #[arg(long)]
658 pub list: bool,
659
660 #[arg(long, default_value = "20")]
662 pub depth: usize,
663
664 #[arg(long, visible_alias = "dry-run")]
667 pub preview: bool,
668
669 #[arg(long, conflicts_with_all = ["list", "redo", "recover"])]
673 pub hard: bool,
674
675 #[arg(long, conflicts_with = "list")]
677 pub redo: bool,
678
679 #[arg(
682 long,
683 conflicts_with_all = ["steps", "list", "preview", "hard", "redo", "allow_redact_undo"]
684 )]
685 pub recover: bool,
686
687 #[arg(long)]
695 pub allow_redact_undo: bool,
696}
697
698#[derive(Clone, Copy, Debug, clap::ValueEnum, PartialEq, Eq)]
704pub enum WorkspaceModeArg {
705 Auto,
707 Materialized,
709 Virtualized,
711 Solid,
713}
714
715#[derive(Clone, Debug, clap::Args)]
717#[command(after_help = "\
718Examples:
719 heddle start feature/auth --path ../feature-auth # create an isolated checkout
720 heddle start scratch --path ../scratch # place the checkout explicitly
721 heddle start fix-flake --path ../fix-flake --task 'fix CI flake'
722
723`--path` is required when workspace is omitted or `auto`. Without it, start
724refuses instead of hiding a checkout under `.heddle/threads/<name>/`.
725`--workspace auto` is the same default and still requires `--path`.
726Pass `--path ../<name>`, or an explicit `--workspace solid|materialized|virtualized`
727if you want the managed layout. To stay on this checkout, use
728`heddle thread create <name>` then `heddle thread switch <name>`.
729
730Isolated 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.
731
732`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.
733
734Advanced (hidden) flags:
735 --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.
736")]
737pub struct ThreadStartArgs {
738 pub name: String,
740
741 #[arg(long)]
743 pub from: Option<String>,
744
745 #[arg(long)]
748 pub path: Option<std::path::PathBuf>,
749
750 #[arg(long, value_enum)]
753 pub workspace: Option<WorkspaceModeArg>,
754
755 #[arg(long, hide = true)]
757 pub agent_provider: Option<String>,
758
759 #[arg(long, hide = true)]
761 pub agent_model: Option<String>,
762
763 #[arg(long)]
765 pub task: Option<String>,
766
767 #[arg(long, hide = true)]
769 pub parent_thread: Option<String>,
770
771 #[arg(long, hide = true)]
773 pub automated: bool,
774
775 #[arg(long, hide = true, conflicts_with_all = ["agent_provider", "agent_model"])]
782 pub print_cd_path: bool,
783
784 #[arg(
791 long,
792 overrides_with = "no_daemon",
793 action = clap::ArgAction::SetTrue,
794 default_value_t = true,
795 hide = true,
796 )]
797 pub daemon: bool,
798
799 #[arg(
805 long,
806 overrides_with = "daemon",
807 action = clap::ArgAction::SetTrue,
808 hide = true,
809 )]
810 pub no_daemon: bool,
811
812 #[arg(long)]
816 pub interactive_setup: bool,
817
818 #[arg(
832 long,
833 overrides_with = "no_shared_target",
834 action = clap::ArgAction::SetTrue,
835 hide = true,
836 )]
837 pub shared_target: bool,
838
839 #[arg(
843 long,
844 overrides_with = "shared_target",
845 action = clap::ArgAction::SetTrue,
846 hide = true,
847 )]
848 pub no_shared_target: bool,
849
850 #[arg(long)]
861 pub hydrate: bool,
862}
863
864#[derive(Clone, Debug, clap::Args)]
866pub struct ReadyArgs {
867 #[arg(long = "thread")]
869 pub thread: Option<String>,
870
871 #[arg(short = 'm', long)]
873 pub message: Option<String>,
874
875 #[arg(long, value_parser = parse_confidence)]
877 pub confidence: Option<f32>,
878
879 #[arg(long)]
883 pub dry_run: bool,
884}
885
886#[derive(Clone, Debug, clap::Args)]
888pub struct SyncArgs {
889 #[cfg(feature = "git-overlay")]
891 #[command(subcommand)]
892 pub command: Option<SyncCommands>,
893
894 #[arg(long = "thread")]
896 pub thread: Option<String>,
897}
898
899#[derive(Clone, Debug, clap::Args)]
901pub struct LandArgs {
902 #[arg(long = "thread")]
904 pub thread: Option<String>,
905
906 #[arg(long = "threads", value_delimiter = ',')]
911 pub threads: Vec<String>,
912
913 #[arg(short = 'm', long)]
915 pub message: Option<String>,
916
917 #[arg(long)]
919 pub no_squash: bool,
920
921 #[arg(long)]
925 pub dry_run: bool,
926}
927
928#[derive(Clone, Debug, clap::Args)]
930pub struct ThreadShowArgs {
931 pub thread: Option<String>,
933
934 #[arg(long)]
936 pub watch: bool,
937
938 #[arg(long, hide = true)]
940 pub watch_iterations: Option<usize>,
941
942 #[arg(long, hide = true)]
944 pub watch_interval_ms: Option<u64>,
945}
946
947#[derive(Clone, Debug, clap::Args)]
949pub struct ThreadCapturesArgs {
950 pub thread: Option<String>,
952
953 #[arg(long, default_value_t = 20)]
955 pub limit: usize,
956}
957
958#[derive(Clone, Debug, clap::Args)]
962pub struct ThreadNameArgs {
963 pub thread: Option<String>,
965}
966
967#[derive(Clone, Debug, clap::Args)]
969pub struct ThreadRenameArgs {
970 pub old: String,
972
973 pub new: String,
975}
976
977#[derive(Clone, Debug, clap::Args)]
979pub struct ThreadPromoteArgs {
980 pub thread: String,
982
983 #[arg(long)]
985 pub path: Option<std::path::PathBuf>,
986
987 #[arg(long)]
989 pub force: bool,
990}
991
992#[derive(Clone, Debug, clap::Args)]
994pub struct ThreadMoveArgs {
995 pub from: String,
997
998 pub to: String,
1000
1001 #[arg(long = "path", required = true, value_name = "PATH")]
1003 pub paths: Vec<String>,
1004
1005 #[arg(short = 'm', long)]
1007 pub message: Option<String>,
1008}
1009
1010#[derive(Clone, Debug, clap::Args)]
1012pub struct ThreadAbsorbArgs {
1013 pub thread: String,
1015
1016 #[arg(long)]
1018 pub into: Option<String>,
1019
1020 #[arg(short = 'm', long)]
1022 pub message: Option<String>,
1023
1024 #[arg(long)]
1026 pub preview: bool,
1027}
1028
1029#[derive(Clone, Debug, clap::Args)]
1031pub struct ThreadResolveArgs {
1032 pub thread: String,
1034}
1035
1036#[derive(Clone, Debug, clap::Args)]
1038pub struct ThreadDropArgs {
1039 pub thread: String,
1041
1042 #[arg(long)]
1044 pub delete_thread: bool,
1045
1046 #[arg(short, long)]
1048 pub force: bool,
1049}
1050
1051#[derive(Clone, Debug, clap::Args)]
1055pub struct ThreadApproveArgs {
1056 pub source: String,
1058
1059 pub target: String,
1061
1062 #[arg(long)]
1064 pub note: Option<String>,
1065
1066 #[arg(long, default_value = "origin")]
1068 pub remote: String,
1069}
1070
1071#[derive(Clone, Debug, clap::Args)]
1074pub struct ThreadApprovalsArgs {
1075 pub source: String,
1076 pub target: String,
1077 #[arg(long, default_value = "origin")]
1078 pub remote: String,
1079}
1080
1081#[derive(Clone, Debug, clap::Args)]
1084pub struct ThreadRevokeApprovalArgs {
1085 pub id: String,
1087 #[arg(long, default_value = "origin")]
1088 pub remote: String,
1089}
1090
1091#[derive(Clone, Debug, clap::Args)]
1094pub struct ThreadCheckMergeArgs {
1095 pub source: String,
1096 pub target: String,
1097
1098 #[arg(long, default_value = "merge")]
1100 pub gated_action: String,
1101
1102 #[arg(long = "path", value_delimiter = ',')]
1105 pub changed_paths: Vec<String>,
1106
1107 #[arg(long, default_value = "origin")]
1108 pub remote: String,
1109}
1110
1111#[derive(Clone, Debug, clap::Args)]
1113pub struct CollapseArgs {
1114 #[arg(required = true)]
1116 pub states: Vec<String>,
1117
1118 #[arg(long)]
1120 pub into: String,
1121
1122 #[arg(long)]
1124 pub confidence: Option<f32>,
1125}
1126
1127#[derive(Clone, Debug, clap::Args)]
1129pub struct ExpandArgs {
1130 pub reference: String,
1132}
1133
1134#[derive(Clone, Debug, clap::Args)]
1136pub struct ResolveArgs {
1137 pub path: Option<String>,
1139
1140 #[arg(long)]
1142 pub all: bool,
1143
1144 #[arg(long)]
1146 pub list: bool,
1147
1148 #[arg(long, conflicts_with = "theirs")]
1150 pub ours: bool,
1151
1152 #[arg(long, conflicts_with = "ours")]
1154 pub theirs: bool,
1155
1156 #[arg(long)]
1158 pub force: bool,
1159}
1160
1161#[derive(Clone, Debug, clap::Args)]
1164pub struct RemoteOperationArgs {
1165 pub remote: Option<String>,
1167
1168 #[arg(short, long)]
1170 pub thread: Option<String>,
1171
1172 #[arg(long)]
1175 pub insecure: bool,
1176}
1177
1178#[derive(Clone, Debug, clap::Args)]
1180#[command(after_help = "\
1181Git Overlay refs:
1182 A normal push writes refs/heads/<thread> and refs/notes/heddle.
1183 --all-threads writes every refs/heads/<thread> and refs/tags/<tag>, plus refs/notes/heddle.
1184 JSON output lists changed refs in refs_written; verify with git ls-remote <remote>.
1185")]
1186pub struct PushArgs {
1187 pub remote: Option<String>,
1189
1190 #[arg(short, long, conflicts_with = "thread_arg")]
1192 pub thread: Option<String>,
1193
1194 #[arg(value_name = "THREAD")]
1196 pub thread_arg: Option<String>,
1197
1198 #[arg(short, long)]
1200 pub state: Option<String>,
1201
1202 #[arg(short, long)]
1204 pub force: bool,
1205
1206 #[arg(long)]
1208 pub all_threads: bool,
1209
1210 #[arg(long)]
1213 pub insecure: bool,
1214
1215 #[arg(long)]
1219 pub dry_run: bool,
1220}
1221
1222impl PushArgs {
1223 pub fn thread_name(&self) -> Option<String> {
1224 self.thread.clone().or_else(|| self.thread_arg.clone())
1225 }
1226}
1227
1228#[derive(Clone, Debug, clap::Args)]
1230#[command(after_help = "\
1231Advanced (hidden) flags:
1232 --lazy leaves blob content absent by design and hydrates it explicitly later. Hosted/network Heddle remotes only.
1233")]
1234pub struct PullArgs {
1235 #[command(flatten)]
1236 pub remote_op: RemoteOperationArgs,
1237
1238 #[arg(short, long)]
1240 pub local_thread: Option<String>,
1241
1242 #[arg(long, hide = true)]
1244 pub lazy: bool,
1245}
1246
1247#[derive(Clone, Debug, clap::Args)]
1255#[command(after_help = "\
1256Behavior:
1257 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`.
1258
1259Advanced/planned flags: see `heddle help clone`.
1260
1261Examples:
1262 heddle clone ../native-repo ./clone # local native Heddle repository
1263 heddle clone heddle://host/repo ./clone --depth 1 # shallow Heddle clone: tip plus immediate parents
1264")]
1265pub struct CloneArgs {
1266 pub remote: String,
1268
1269 pub local: String,
1271
1272 #[arg(long)]
1274 pub thread: Option<String>,
1275
1276 #[arg(long)]
1278 pub depth: Option<u32>,
1279
1280 #[arg(long, hide = true)]
1284 pub lazy: bool,
1285
1286 #[arg(long)]
1288 pub insecure: bool,
1289
1290 #[arg(long, hide = true, value_name = "SPEC", value_parser = parse_clone_filter_spec)]
1296 pub filter: Option<String>,
1297
1298 #[arg(long, visible_alias = "monorepo")]
1302 pub recursive: bool,
1303}
1304
1305fn parse_clone_filter_spec(s: &str) -> Result<String, String> {
1306 match s {
1307 "blob:none" => Ok(s.to_string()),
1308 other => Err(format!(
1309 "unsupported --filter spec `{other}`; only `blob:none` is supported today"
1310 )),
1311 }
1312}
1313
1314#[derive(Clone, Debug, clap::Args)]
1316pub struct AgentProvenanceBeginArgs {
1317 #[arg(long)]
1319 pub provider: String,
1320
1321 #[arg(long)]
1323 pub model: String,
1324
1325 #[arg(long)]
1327 pub policy: Option<String>,
1328}
1329
1330#[derive(Clone, Debug, clap::Args)]
1332pub struct AgentProvenanceSegmentArgs {
1333 #[arg(long)]
1335 pub provider: String,
1336
1337 #[arg(long)]
1339 pub model: String,
1340
1341 #[arg(long)]
1343 pub policy: Option<String>,
1344}
1345
1346#[derive(Clone, Debug, clap::Args)]
1348pub struct AgentProvenanceEndArgs {
1349 pub session_id: Option<String>,
1351}
1352
1353#[derive(Clone, Debug, clap::Args)]
1355pub struct AgentProvenanceShowArgs {
1356 pub session_id: Option<String>,
1358}
1359
1360#[derive(Clone, Debug, clap::Args)]
1362pub struct AgentProvenanceListArgs {
1363 #[arg(long)]
1365 pub active: bool,
1366}
1367
1368#[derive(Clone, Debug, clap::Args)]
1370pub struct WorktreeAddArgs {
1371 pub path: std::path::PathBuf,
1373
1374 #[arg(long)]
1376 pub thread: Option<String>,
1377
1378 #[arg(long)]
1380 pub from: Option<String>,
1381}
1382
1383#[derive(Clone, Debug, clap::Args)]
1385pub struct WorktreeRemoveArgs {
1386 pub path: std::path::PathBuf,
1388
1389 #[arg(long)]
1391 pub delete_thread: bool,
1392}
1393
1394#[derive(Clone, Debug, clap::Args)]
1396pub struct AgentPresenceListArgs {
1397 #[arg(long)]
1399 pub active: bool,
1400}
1401
1402#[derive(Clone, Debug, clap::Args)]
1404pub struct AgentPresenceShowArgs {
1405 pub session: Option<String>,
1407}
1408
1409#[derive(Clone, Debug, clap::Args)]
1411pub struct AgentPresenceExplainArgs {
1412 pub session: Option<String>,
1414}
1415
1416#[derive(Clone, Debug, clap::Args)]
1418pub struct AgentPresenceCompleteArgs {
1419 #[arg(long)]
1421 pub session: Option<String>,
1422}
1423
1424#[derive(Clone, Debug, clap::Args)]
1426pub struct AgentReserveArgs {
1427 #[arg(long)]
1429 pub thread: String,
1430
1431 #[arg(long)]
1433 pub anchor: Option<String>,
1434
1435 #[arg(long)]
1437 pub task: Option<String>,
1438
1439 #[arg(long)]
1441 pub task_id: Option<String>,
1442
1443 #[arg(long, value_name = "PID")]
1445 pub hold_for_pid: Option<u32>,
1446}
1447
1448#[derive(Clone, Debug, clap::Args)]
1450pub struct AgentHeartbeatArgs {
1451 #[arg(long)]
1453 pub lease: String,
1454
1455 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1457 pub token: String,
1458}
1459
1460#[derive(Clone, Debug, clap::Args)]
1462pub struct AgentReleaseArgs {
1463 #[arg(long)]
1465 pub lease: String,
1466
1467 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1469 pub token: String,
1470
1471 #[arg(long, default_value = "complete")]
1473 pub status: AgentReleaseStatusArg,
1474}
1475
1476#[derive(Clone, Debug, clap::ValueEnum)]
1477pub enum AgentReleaseStatusArg {
1478 Complete,
1479 Abandoned,
1480}
1481
1482#[derive(Clone, Debug, clap::Args)]
1484pub struct AgentApiListArgs {
1485 #[arg(long)]
1487 pub thread: Option<String>,
1488
1489 #[arg(long)]
1491 pub alive_only: bool,
1492}
1493
1494#[derive(Clone, Debug, clap::ValueEnum)]
1495pub enum AgentTaskStatusArg {
1496 Open,
1497 InProgress,
1498 Blocked,
1499 Complete,
1500 Abandoned,
1501}
1502
1503#[derive(Clone, Debug, clap::Args)]
1505pub struct AgentTaskCreateArgs {
1506 #[arg(long)]
1508 pub task_id: Option<String>,
1509
1510 #[arg(long)]
1512 pub title: String,
1513
1514 #[arg(long)]
1516 pub body: Option<String>,
1517
1518 #[arg(long)]
1520 pub thread: String,
1521
1522 #[arg(long)]
1524 pub base_state: Option<String>,
1525
1526 #[arg(long)]
1528 pub base_root: Option<String>,
1529
1530 #[arg(long)]
1532 pub parent_task_id: Option<String>,
1533
1534 #[arg(long)]
1536 pub coordination_discussion_id: Option<String>,
1537
1538 #[arg(long)]
1540 pub allow_offline: bool,
1541
1542 #[arg(long)]
1544 pub delegated_by: Option<String>,
1545}
1546
1547#[derive(Clone, Debug, clap::Args)]
1549pub struct AgentTaskListArgs {
1550 #[arg(long)]
1552 pub thread: Option<String>,
1553
1554 #[arg(long)]
1556 pub status: Option<AgentTaskStatusArg>,
1557}
1558
1559#[derive(Clone, Debug, clap::Args)]
1561pub struct AgentTaskShowArgs {
1562 pub task_id: String,
1564}
1565
1566#[derive(Clone, Debug, clap::Args)]
1568pub struct AgentTaskUpdateArgs {
1569 pub task_id: String,
1571
1572 #[arg(long)]
1574 pub title: Option<String>,
1575
1576 #[arg(long)]
1578 pub body: Option<String>,
1579
1580 #[arg(long)]
1582 pub status: Option<AgentTaskStatusArg>,
1583
1584 #[arg(long)]
1586 pub thread: Option<String>,
1587
1588 #[arg(long)]
1590 pub base_state: Option<String>,
1591
1592 #[arg(long)]
1594 pub base_root: Option<String>,
1595
1596 #[arg(long)]
1598 pub parent_task_id: Option<String>,
1599
1600 #[arg(long)]
1602 pub coordination_discussion_id: Option<String>,
1603
1604 #[arg(long, conflicts_with = "no_allow_offline")]
1606 pub allow_offline: bool,
1607
1608 #[arg(long, conflicts_with = "allow_offline")]
1610 pub no_allow_offline: bool,
1611
1612 #[arg(long)]
1614 pub delegated_by: Option<String>,
1615}
1616
1617#[derive(Clone, Debug, clap::Args)]
1619pub struct AgentFanoutPlanArgs {
1620 #[arg(long)]
1622 pub title: String,
1623
1624 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1626 pub lane: Vec<String>,
1627
1628 #[arg(long)]
1630 pub coordination_discussion_id: Option<String>,
1631}
1632
1633#[derive(Clone, Debug, clap::Args)]
1635pub struct AgentFanoutStartArgs {
1636 #[arg(long)]
1638 pub title: String,
1639
1640 #[arg(long, value_name = "THREAD=PATH:TITLE")]
1642 pub lane: Vec<String>,
1643
1644 #[arg(long)]
1646 pub coordination_discussion_id: Option<String>,
1647}
1648
1649#[derive(Clone, Debug, clap::Args)]
1651pub struct AgentCaptureArgs {
1652 #[arg(long)]
1654 pub lease: String,
1655
1656 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1658 pub token: String,
1659
1660 #[arg(long, short = 'm', alias = "intent")]
1662 pub message: Option<String>,
1663
1664 #[arg(long, value_parser = parse_confidence)]
1666 pub confidence: Option<f32>,
1667}
1668
1669#[derive(Clone, Debug, clap::Args)]
1671pub struct AgentReadyArgs {
1672 #[arg(long)]
1674 pub lease: String,
1675
1676 #[arg(long, env = "HEDDLE_RESERVATION_TOKEN", hide_env_values = true)]
1678 pub token: String,
1679
1680 #[arg(long, short = 'm')]
1682 pub message: Option<String>,
1683
1684 #[arg(long, value_parser = parse_confidence)]
1686 pub confidence: Option<f32>,
1687}
1688
1689#[derive(Clone, Debug, clap::Args)]
1697pub struct WatchArgs {
1698 #[arg(long, value_name = "DURATION")]
1702 pub since: Option<String>,
1703
1704 #[arg(long, value_name = "KINDS")]
1708 pub filter: Option<String>,
1709
1710 #[arg(long, hide = true)]
1713 pub max_iterations: Option<usize>,
1714
1715 #[arg(long, hide = true)]
1718 pub poll_interval_ms: Option<u64>,
1719}
1720
1721#[cfg(test)]
1727mod capture_message_alias_tests {
1728 use clap::Parser;
1729
1730 use crate::cli::{Cli, Commands, SnapshotArgs};
1731
1732 fn parse_capture(extra: &[&str]) -> Result<SnapshotArgs, clap::Error> {
1733 let mut argv: Vec<&str> = vec!["heddle", "capture"];
1734 argv.extend_from_slice(extra);
1735 let cli = Cli::try_parse_from(argv)?;
1736 match cli.command {
1737 Commands::Capture(args) => Ok(args),
1738 _ => panic!("expected Commands::Capture"),
1739 }
1740 }
1741
1742 #[test]
1743 fn capture_accepts_message_alias() {
1744 let args = parse_capture(&["--message", "my change"]).expect("--message should parse");
1745 assert_eq!(args.intent.as_deref(), Some("my change"));
1746 }
1747
1748 #[test]
1749 fn capture_accepts_intent_long_form() {
1750 let args = parse_capture(&["--intent", "my change"]).expect("--intent should parse");
1751 assert_eq!(args.intent.as_deref(), Some("my change"));
1752 }
1753
1754 #[test]
1755 fn capture_accepts_short_m() {
1756 let args = parse_capture(&["-m", "my change"]).expect("-m should parse");
1757 assert_eq!(args.intent.as_deref(), Some("my change"));
1758 }
1759
1760 #[test]
1761 fn capture_parses_without_intent_so_the_refuse_can_fire() {
1762 let args =
1763 parse_capture(&[]).expect("omitted -m is a semantic refuse, not a clap usage error");
1764 assert!(args.intent.is_none());
1765 }
1766
1767 #[test]
1768 fn capture_rejects_non_finite_or_out_of_range_confidence() {
1769 for value in ["NaN", "inf", "-0.1", "1.7"] {
1770 let confidence_arg = format!("--confidence={value}");
1771 let err = parse_capture(&["-m", "bad confidence", &confidence_arg])
1772 .expect_err("invalid confidence should fail to parse");
1773 assert!(
1774 err.to_string()
1775 .contains("confidence must be a finite number from 0.0 to 1.0"),
1776 "unexpected parse error for {value}: {err}"
1777 );
1778 }
1779 }
1780}
1781
1782#[cfg(test)]
1783mod clone_filter_tests {
1784 use clap::Parser;
1785
1786 use crate::cli::{Cli, CloneArgs, Commands};
1787
1788 fn parse_clone(extra: &[&str]) -> Result<CloneArgs, clap::Error> {
1789 let mut argv: Vec<&str> = vec!["heddle", "clone", "remote", "local"];
1790 argv.extend_from_slice(extra);
1791 let cli = Cli::try_parse_from(argv)?;
1792 match cli.command {
1793 Commands::Clone(args) => Ok(args),
1794 _ => panic!("expected Commands::Clone"),
1795 }
1796 }
1797
1798 #[test]
1799 fn parses_clone_filter_blob_none() {
1800 let args = parse_clone(&["--filter", "blob:none"]).expect("parse --filter blob:none");
1801 assert_eq!(args.filter.as_deref(), Some("blob:none"));
1802 assert!(!args.lazy);
1803 }
1804
1805 #[test]
1806 fn rejects_unknown_filter_spec() {
1807 let err = parse_clone(&["--filter", "tree:0"])
1808 .expect_err("unknown --filter spec should fail to parse");
1809 let msg = err.to_string();
1810 assert!(
1811 msg.contains("tree:0") && msg.contains("blob:none"),
1812 "error should name the bad spec and the supported one: {msg}"
1813 );
1814 }
1815}