Skip to main content

heddle_cli_args/cli/cli_args/
commands_main.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Top-level CLI commands.
3
4use clap::{Args, Subcommand};
5
6#[cfg(feature = "git-overlay")]
7use super::BridgeCommands;
8#[cfg(feature = "semantic")]
9use super::SemanticCommands;
10use super::{
11    AgentCommands, CiCommands, CompletionSubject, ContextCommands, DiscussCommands, HookCommands,
12    IntegrationCommands, OplogCommands, PresenceCommands, QueryArgs, RedactCommands,
13    RemoteCommands, ReviewCommands, ShellCommands, ThreadCommands, VisibilityCommands,
14    commands_args::{
15        AdoptArgs, CloneArgs, CollapseArgs, CommitArgs, DiffArgs, DoctorArgs, ExpandArgs,
16        INIT_VERB, InitArgs, LandArgs, LogArgs, PullArgs, PushArgs, ReadyArgs, ResolveArgs,
17        RetroArgs, RevertArgs, RunArgs, SnapshotArgs, SyncArgs, ThreadStartArgs, TimelineArgs,
18        TryArgs, UndoArgs, WatchArgs,
19    },
20};
21#[cfg(feature = "client")]
22use super::{AuthCommands, IdentityCommands};
23
24#[derive(Clone, Debug, Args)]
25pub struct FsckArgs {
26    /// Full check (includes content verification).
27    #[arg(long)]
28    pub full: bool,
29
30    /// Run slower graph and signature integrity checks.
31    #[arg(long)]
32    pub thorough: bool,
33
34    /// Verify offline authorship identity and review-signature chains.
35    #[arg(long, requires = "thorough")]
36    pub provenance: bool,
37
38    /// Include Git projection, mapping, notes, and checkout checks.
39    #[arg(long)]
40    pub git: bool,
41
42    #[command(subcommand)]
43    pub command: Option<FsckCommands>,
44}
45
46#[derive(Clone, Debug, Subcommand)]
47pub enum FsckCommands {
48    /// Repair an integrity surface, then verify it.
49    Repair {
50        #[command(subcommand)]
51        target: FsckRepairCommands,
52    },
53}
54
55#[derive(Clone, Debug, Subcommand)]
56pub enum FsckRepairCommands {
57    /// Reconcile Git projection metadata or one projected ref.
58    Git(FsckRepairGitArgs),
59}
60
61#[derive(Clone, Debug, Args)]
62pub struct FsckRepairGitArgs {
63    /// Git ref to reconcile. Required for native repositories.
64    #[arg(long = "ref", value_name = "BRANCH")]
65    pub ref_name: Option<String>,
66
67    /// Assert the intended authority direction.
68    #[arg(long, value_parser = ["git", "heddle"])]
69    pub prefer: Option<String>,
70
71    /// Show the authority-valid repair without changing refs.
72    #[arg(long)]
73    pub preview: bool,
74}
75
76#[derive(Subcommand)]
77pub enum Commands {
78    /// Initialize Heddle in a directory or existing Git checkout.
79    #[command(name = INIT_VERB)]
80    Init(InitArgs),
81
82    /// Adopt Git history into Heddle-native source authority.
83    ///
84    /// Git Overlay is the normal existing-Git mode: Git keeps source objects,
85    /// refs, index, and worktree state while Heddle stores metadata in
86    /// `.heddle`. `adopt` imports history and moves source authority to Heddle.
87    Adopt(AdoptArgs),
88
89    /// Curated, progressive-disclosure help.
90    ///
91    /// `heddle help` prints the curated everyday verbs and points at
92    /// `heddle help advanced` for everything else. `heddle help
93    /// <topic>` prints the topic page (e.g. `daemon`, `signals`,
94    /// `bridge`). `heddle help <command path>` falls through to that
95    /// command's `--help` so the printer never duplicates clap's
96    /// per-verb derivation.
97    Help {
98        /// Topic name (`advanced`, `daemon`, `signals`, …) or command
99        /// path. When omitted, prints the curated default.
100        #[arg(value_name = "TOPIC_OR_COMMAND")]
101        topics: Vec<String>,
102    },
103
104    /// Show what needs attention and the next safe Heddle action.
105    #[command(after_help = "\
106Examples:
107  heddle status               # current thread, dirty paths, recommended next step
108  heddle status --short       # one-line summary for shell prompts
109  heddle status --watch       # live dashboard that refreshes in place
110")]
111    Status {
112        /// Short format.
113        #[arg(short, long)]
114        short: bool,
115
116        /// Continuously refresh status.
117        #[arg(long)]
118        watch: bool,
119
120        /// Internal helper for tests: stop after N watch updates.
121        #[arg(long, hide = true)]
122        watch_iterations: Option<usize>,
123
124        /// Internal helper for tests: polling interval in milliseconds.
125        #[arg(long, hide = true)]
126        watch_interval_ms: Option<u64>,
127    },
128
129    /// Stream live oplog activity.
130    ///
131    /// Tails the repository's append-only oplog file like `tail -f`,
132    /// emitting snapshots, merges, and thread events as they happen.
133    /// Exits on Ctrl-C.
134    Watch(WatchArgs),
135
136    /// Verify this workspace; exits nonzero until every check is clean.
137    #[command(after_help = "\
138Checks: Git mapping, worktree, remote, operation, clone verification, machine contract.
139
140Examples:
141  heddle verify                # strict verification gate and next recovery step
142  heddle verify --verbose      # full proof rows and machine-contract details
143  heddle verify --output json  # proof JSON when clean; error envelope when blocked
144")]
145    Verify {
146        /// Verify each state's offline authorship and review-signature chain.
147        #[arg(long)]
148        provenance: bool,
149    },
150
151    /// Explain repository health, or run targeted doctor checks.
152    ///
153    /// `heddle doctor` (no subcommand) reports repository health and
154    /// the next recovery step. `heddle doctor docs` diff-checks markdown
155    /// documentation against
156    /// the actual CLI surface and exits non-zero on drift — wire it
157    /// into CI to stop docs from going stale.
158    Doctor(DoctorArgs),
159
160    /// Print the JSON Schema for a `--output json`-emitting verb.
161    ///
162    /// Contract-table introspection over CLI output shapes —
163    /// useful when wiring tools that consume `heddle <verb>
164    /// --output json` and want to validate or generate types. The schemas
165    /// live in `crates/cli-contract/src/cli/commands/schemas.rs`; the
166    /// command contract table registers available and documented
167    /// schema verbs for `heddle doctor schemas` drift detection
168    /// against `docs/json-schemas.md`.
169    ///
170    /// With no `<verb>`, prints the registered schema verbs. `<verb>`
171    /// is the joined subcommand path — e.g. `status`, `log`,
172    /// `fsck repair git`, `marker list`.
173    #[command(visible_alias = "schema")]
174    Schemas {
175        /// The verb whose schema to emit. Run `heddle schemas --help`
176        /// or look at `docs/json-schemas.md` for the registered list.
177        ///
178        /// `trailing_var_arg = true` lets the verb spec carry literal
179        /// `--flag` tokens (e.g. `heddle schemas log --reflog`,
180        /// `heddle schemas marker delete --prefix`) without clap
181        /// parsing them as options on `schemas` itself.
182        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
183        verb: Vec<String>,
184    },
185
186    /// Create or resume an isolated thread for focused work.
187    Start(ThreadStartArgs),
188
189    /// Run a command in a sandboxed ephemeral thread.
190    ///
191    /// Heddle creates a fresh thread with an isolated checkout, runs
192    /// `<cmd>` inside it, and then either captures the result on a
193    /// zero exit or drops the thread on a non-zero exit. The parent
194    /// thread's working tree is never touched — the ephemeral thread
195    /// is the sandbox. Implements item 3.1 from the heddle 6→8 plan.
196    ///
197    /// `try` is the **new-sandbox** sibling to `run`. Reach for `run`
198    /// when you already have a thread and just want to exec a command
199    /// inside its checkout (no thread creation, no capture, no
200    /// rollback).
201    #[command(after_help = "\
202Heddle options must appear before `--`. Everything after `--` is passed unchanged to the inner command. If the inner command uses a Heddle global-option spelling, pass `--allow-heddle-global-args` before `--`.
203")]
204    Try(TryArgs),
205
206    /// Automation/workflow command: run a command inside an existing
207    /// thread's execution root.
208    ///
209    /// `run` is the **existing-thread** sibling to `try`. It looks up
210    /// the named (or current) thread, sets the child's cwd to that
211    /// thread's checkout, exports `HEDDLE_THREAD_*`, and runs `<cmd>`.
212    /// It does NOT create a thread, capture
213    /// state on success, or roll back on failure — those are `try`'s
214    /// job. Reach for `try` when you want the sandbox lifecycle; reach
215    /// for `run` when you already have a thread and just need to exec
216    /// inside it.
217    Run(RunArgs),
218
219    /// Run Heddle CI checks.
220    Ci {
221        #[command(subcommand)]
222        command: CiCommands,
223    },
224
225    /// Automation/workflow command: refresh the current thread onto its target when safe.
226    Sync(SyncArgs),
227
228    /// Continue the active operation without remembering the specific subcommand.
229    Continue,
230
231    /// Abort the active operation without remembering the specific subcommand.
232    Abort,
233
234    /// Integrate a ready thread into its local target.
235    ///
236    /// `land` is the local integration verb: capture outstanding work if needed,
237    /// refresh against the target when safe, and land the thread. It fails
238    /// closed when conflicts or other blockers exist. Pair it with `ready`
239    /// when you want the verdict and next action before landing anything.
240    Land(LandArgs),
241
242    /// Prepare this thread for review or merge.
243    ///
244    /// `ready` captures outstanding work if needed, checks conflicts,
245    /// blockers, freshness, and semantic risk, then marks the thread
246    /// ready or blocked and prints the next action. It never lands,
247    /// checkpoints, or pushes; use it when you want Heddle's verdict
248    /// before integrating the work.
249    Ready(ReadyArgs),
250
251    /// Capture a recoverable Heddle step for undo, provenance, and review.
252    Capture(SnapshotArgs),
253
254    /// Write captured source history to `.git` in Git Overlay.
255    Commit(CommitArgs),
256
257    /// Show state history.
258    ///
259    /// By default, when a thread name is given (e.g. `heddle log master`),
260    /// the walk is *first-parent only* — equivalent to `git log
261    /// --first-parent <branch>`. To see every ancestor reachable through
262    /// merge commits, pass `--graph` (which renders the full DAG) or
263    /// `--all` (which lists every state regardless of ancestry).
264    #[command(visible_alias = "history")]
265    Log(LogArgs),
266
267    /// Navigate, fork, reset, and recover agent tool-call timelines.
268    #[command(after_help = "\
269Examples:
270  heddle log --timeline
271  heddle timeline fork --tool-call call_123 --branch tlb-alt
272  heddle timeline reset --step tls-abc --materialize
273  heddle timeline recover
274")]
275    Timeline(TimelineArgs),
276
277    /// Show state details.
278    Show {
279        /// State by physical state ID, logical change ID, or unambiguous prefix.
280        /// Defaults to HEAD.
281        state: Option<String>,
282    },
283
284    /// Summarize a working session.
285    ///
286    /// Combines oplog, agent registry, marker, and context-annotation
287    /// reads into one structured payload — agent-readable retro of
288    /// captures, signals, and notable events since `--since`. Replaces
289    /// the reconstruct-from-`heddle log` boilerplate.
290    Retro(RetroArgs),
291
292    /// Show what changed in the worktree, a thread, or two states.
293    Diff(DiffArgs),
294
295    /// Open or resolve discussions anchored to symbols.
296    ///
297    /// Open a discussion against a symbol; append turns;
298    /// resolve by edit or dismiss. Anchors
299    /// travel across renames and cross-file moves on subsequent
300    /// state mutations.
301    ///
302    /// Native Heddle only. Discussions live in `.heddle` and travel
303    /// over `heddle push` / `heddle pull` to a Heddle remote. They are
304    /// not projected into Git, so `git clone` does not carry them; in
305    /// Git Overlay mode they are local to that working copy.
306    #[command(after_help = "\
307Scope:
308  Native Heddle only. Discussions are stored in `.heddle` and move over
309  `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
310  Git Overlay repository arrives with no discussions and no Heddle store.
311
312Examples:
313  heddle discuss open src/auth.rs verify 'Should this reject expired tokens?'  # anchor a discussion
314  heddle discuss append <id> 'switched to argon2'          # add a turn
315  heddle discuss resolve <id> --mode by-edit --state HEAD
316")]
317    Discuss {
318        #[command(subcommand)]
319        command: DiscussCommands,
320    },
321
322    /// Structured query over the operation log. Filter by
323    /// actor, time window, signal kind, symbol, thread, verbs. Returns
324    /// structured results consumable by agents.
325    Query(QueryArgs),
326
327    /// Review a state — render the payload, sign, see signal health.
328    ///
329    /// `heddle review show` renders the review payload (summary,
330    /// agent narrative, in-budget signals, anchored discussions).
331    /// `heddle review sign` submits a `read` / `agent_preview` /
332    /// `agent_co_review` signature on the state. `heddle review
333    /// health` reports per-module signal fire rates over a rolling
334    /// window.
335    #[command(after_help = "\
336Examples:
337  heddle review show HEAD                                # render the review payload for HEAD
338  heddle review sign HEAD --kind read --public-key <hex> --signature <hex> --signed-at-unix <ts>
339  heddle review health --window 7                       # signal fire-rates over recent states
340")]
341    Review {
342        #[command(subcommand)]
343        command: ReviewCommands,
344    },
345
346    /// Redact a sensitive blob in a state so reads return a stub
347    /// instead of the content.
348    ///
349    /// `heddle redact apply` declares a redaction; the blob bytes stay
350    /// on disk and reads return the operator-supplied stub. `heddle
351    /// redact purge` afterward physically removes the bytes. Both are signed,
352    /// attributed, oplog-audited operations. See
353    /// `docs/PRINCIPLES.md` (the honesty principle) for context.
354    Redact {
355        #[command(subcommand)]
356        command: RedactCommands,
357    },
358
359    /// Declare and inspect a state's audience visibility tier.
360    ///
361    /// `heddle visibility set` binds a tier to a state; `promote` lifts it to
362    /// a less-restrictive tier via a superseding record; `show` reports the
363    /// effective tier; `list` enumerates non-public states. Capture binds the
364    /// inherited `[review.discussion] default_visibility` automatically
365    /// (Invariant A) — these verbs are the explicit operator overrides.
366    Visibility {
367        #[command(subcommand)]
368        command: VisibilityCommands,
369    },
370
371    /// Revert changes from a state.
372    Revert(RevertArgs),
373
374    /// Undo the last Heddle operation.
375    Undo(UndoArgs),
376
377    /// Collapse (squash) multiple states into one.
378    Collapse(CollapseArgs),
379
380    /// Expand a squashed land into the captures it collapsed.
381    Expand(ExpandArgs),
382
383    /// Manage threads.
384    Thread {
385        #[command(subcommand)]
386        command: ThreadCommands,
387    },
388
389    /// Shell integration helpers (auto-cd on thread start/switch/cd).
390    Shell {
391        #[command(subcommand)]
392        command: ShellCommands,
393    },
394
395    /// Print a tab-completion script for bash, zsh, or fish.
396    ///
397    /// With no shell, prints install lines. With `bash`, `zsh`, or `fish`,
398    /// emits the same script as `heddle shell completion`.
399    Completions {
400        /// Shell to generate completion for: bash, zsh, or fish.
401        #[arg(value_name = "SHELL")]
402        shell: Option<String>,
403    },
404
405    /// Internal shell-completion candidate helper.
406    #[command(name = "complete", alias = "__complete", hide = true)]
407    Complete {
408        /// Candidate set to print, one candidate per line.
409        #[arg(value_enum)]
410        subject: CompletionSubject,
411    },
412
413    /// Resolve merge conflicts.
414    Resolve(ResolveArgs),
415
416    /// Inspect and repair the operation log.
417    ///
418    /// `heddle oplog recover` explicitly salvages a truncated or torn oplog,
419    /// reporting what was recovered — the operator-facing entrypoint over the
420    /// same recovery the everyday read path runs automatically.
421    Oplog {
422        #[command(subcommand)]
423        command: OplogCommands,
424    },
425
426    /// Explicit interoperability with other version-control formats.
427    #[cfg(feature = "git-overlay")]
428    Bridge {
429        #[command(subcommand)]
430        command: BridgeCommands,
431    },
432
433    /// Push the source-authoritative history to a remote.
434    Push(PushArgs),
435
436    /// Pull source-authoritative history from a remote.
437    Pull(PullArgs),
438
439    /// Manage remote repositories.
440    Remote {
441        #[command(subcommand)]
442        command: RemoteCommands,
443    },
444
445    /// Authenticate with a Heddle server.
446    #[cfg(feature = "client")]
447    Auth {
448        #[command(subcommand)]
449        command: AuthCommands,
450    },
451
452    /// Ensure and claim this machine's hosted agent identity.
453    #[cfg(feature = "client")]
454    Identity {
455        #[command(subcommand)]
456        command: IdentityCommands,
457    },
458
459    /// Report the capture actor, then hosted auth.
460    ///
461    /// The capture actor is who the next capture is attributed to
462    /// (`user_config`, `init --principal-*`, or `HEDDLE_PRINCIPAL_*`).
463    /// Hosted auth is whether this machine has a server credential.
464    /// These are different objects. `identity ensure` does not set the
465    /// local actor.
466    #[cfg(feature = "client")]
467    #[command(after_help = "\
468The capture actor and hosted auth are different objects:
469  capture actor  who the next capture is attributed to
470                 (user_config, init --principal-*, or HEDDLE_PRINCIPAL_*)
471  hosted auth    whether this machine has a credential for the server
472                 (heddle auth login). identity ensure does not set the actor.
473
474Examples:
475  heddle whoami                       # capture actor first, then hosted auth
476  heddle whoami --output json         # machine-readable, stable output_kind shape
477  heddle whoami --server api.heddle.sh")]
478    Whoami {
479        /// Heddle server address (defaults to the configured server).
480        #[arg(long)]
481        server: Option<String>,
482    },
483
484    /// Manage code context annotations.
485    ///
486    /// Native Heddle only. Annotations live in `.heddle`, and travel
487    /// over `heddle push` / `heddle pull` to a Heddle remote. They are
488    /// deliberately not projected into Git — not into `refs/notes/*`,
489    /// not into a tracked file — so `git push` and `git clone` do not
490    /// carry them. In Git Overlay mode annotations still work and are
491    /// still useful; they are simply local to that working copy.
492    #[command(after_help = "\
493Scope:
494  Native Heddle only. Annotations are stored in `.heddle` and move over
495  `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
496  Git Overlay repository arrives with no annotations and no Heddle store.
497
498Examples:
499  heddle context set --path src/auth.rs --scope symbol:verify --kind invariant -m 'returns false on timing mismatch'
500  heddle context get --path src/auth.rs --scope symbol:verify
501  heddle context list --prefix src/auth          # everything attached under a path
502  heddle context check --path src/auth.rs        # surface annotations for editor tooling
503")]
504    Context {
505        #[command(subcommand)]
506        command: ContextCommands,
507    },
508
509    /// Manage ambient harness integrations.
510    Integration {
511        #[command(subcommand)]
512        command: IntegrationCommands,
513    },
514
515    /// Semantic analysis queries (call-graph hot-spots, churn,
516    /// signature-stability surfaces).
517    #[cfg(feature = "semantic")]
518    Semantic {
519        #[command(subcommand)]
520        command: SemanticCommands,
521    },
522
523    /// FUSE mount-daemon control plane — distinct from `agent`.
524    ///
525    /// `heddle daemon serve` runs a foreground mount daemon that
526    /// owns FUSE sessions for `--workspace virtualized --daemon`
527    /// threads. It is normally spawned on demand by the per-thread
528    /// CLI; running it interactively is for debugging.
529    /// `status` reports liveness/uptime/mount count without spawning;
530    /// `stop` asks a running daemon to drain mounts and exit.
531    Daemon {
532        #[command(subcommand)]
533        command: DaemonCommands,
534    },
535
536    /// Agent reservation and one-shot orchestration API.
537    ///
538    /// `heddle agent reserve|capture|ready|release|list|heartbeat` is the stable
539    /// JSON contract orchestrators use to coordinate parallel
540    /// writers. `heddle daemon` remains the distinct FUSE mount control plane.
541    Agent {
542        #[command(subcommand)]
543        command: AgentCommands,
544    },
545
546    /// Inspect attribution and work context for local agents.
547    ///
548    /// Presence is intentionally separate from `agent`: presence records
549    /// explain who is acting, while `agent reserve` grants writer authority.
550    Presence {
551        #[command(subcommand)]
552        command: PresenceCommands,
553    },
554
555    /// Inspect and refresh rebuildable performance sidecars.
556    Maintenance {
557        #[command(subcommand)]
558        command: MaintenanceCommands,
559    },
560
561    /// Clone from remote.
562    Clone(CloneArgs),
563
564    /// Manage repository hooks.
565    Hook {
566        #[command(subcommand)]
567        command: HookCommands,
568    },
569}
570
571/// Maintenance subcommands.
572#[derive(Clone, Debug, clap::Subcommand)]
573pub enum MaintenanceCommands {
574    /// Verify repository integrity or explicitly repair one surface.
575    Fsck(FsckArgs),
576
577    /// Inspect repository performance sidecars and repo shape.
578    Inspect,
579
580    /// Refresh repository performance sidecars without changing repository meaning.
581    Refresh,
582
583    /// Repack native objects now through the resource-controlled scheduler.
584    Repack,
585
586    /// Garbage collect unreachable objects.
587    Gc {
588        /// Prune unreachable objects.
589        #[arg(long)]
590        prune: bool,
591
592        /// Aggressive garbage collection.
593        #[arg(long)]
594        aggressive: bool,
595
596        /// Show what would be removed without removing.
597        #[arg(long)]
598        dry_run: bool,
599    },
600}
601
602/// Daemon control plane subcommands. See `Commands::Daemon`.
603#[derive(Clone, Debug, clap::Subcommand)]
604pub enum DaemonCommands {
605    /// Run a foreground mount daemon for this repository.
606    ///
607    /// Normally spawned on demand by the per-thread CLI when
608    /// `--daemon` is passed. Running interactively is for
609    /// debugging the daemon protocol.
610    Serve,
611
612    /// Report daemon liveness, version, uptime, and active mount
613    /// count. No-op success when the daemon isn't running.
614    Status,
615
616    /// Ask the running daemon to drain its mounts and exit. Sweeps
617    /// any leftover registry entries with `fusermount -u` as a
618    /// safety net before returning.
619    Stop,
620}