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