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 locked everyday verbs. `heddle help
92 /// <topic>` prints the topic page (e.g. `model`, `daemon`,
93 /// `signals`, `git-concepts`). `heddle help <command path>` falls
94 /// through to that command's `--help` so the printer never
95 /// duplicates clap's per-verb derivation.
96 Help {
97 /// Topic name (`model`, `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 /// Run Heddle CI checks.
219 Ci {
220 #[command(subcommand)]
221 command: CiCommands,
222 },
223
224 /// Automation/workflow command: refresh the current thread onto its target when safe.
225 Sync(SyncArgs),
226
227 /// Continue the active operation without remembering the specific subcommand.
228 Continue,
229
230 /// Abort the active operation without remembering the specific subcommand.
231 Abort,
232
233 /// Integrate a ready thread into its local target.
234 ///
235 /// `land` is the local integration verb: capture outstanding work if needed,
236 /// refresh against the target when safe, and land the thread. It fails
237 /// closed when conflicts or other blockers exist. Pair it with `ready`
238 /// when you want the verdict and next action before landing anything.
239 Land(LandArgs),
240
241 /// Prepare this thread for review or merge.
242 ///
243 /// `ready` captures outstanding work if needed, checks conflicts,
244 /// blockers, freshness, and semantic risk, then marks the thread
245 /// ready or blocked and prints the next action. It never lands,
246 /// checkpoints, or pushes; use it when you want Heddle's verdict
247 /// before integrating the work.
248 Ready(ReadyArgs),
249
250 /// Capture a recoverable Heddle step for undo, provenance, and review.
251 Capture(SnapshotArgs),
252
253 /// Write captured source history to `.git` in Git Overlay.
254 Commit(CommitArgs),
255
256 /// Show state history.
257 ///
258 /// By default, when a thread name is given (e.g. `heddle log master`),
259 /// the walk is *first-parent only* — equivalent to `git log
260 /// --first-parent <branch>`. To see every ancestor reachable through
261 /// merge commits, pass `--graph` (which renders the full DAG) or
262 /// `--all` (which lists every state regardless of ancestry).
263 #[command(visible_alias = "history")]
264 Log(LogArgs),
265
266 /// Navigate, fork, reset, and recover agent tool-call timelines.
267 #[command(after_help = "\
268Examples:
269 heddle log --timeline
270 heddle timeline fork --tool-call call_123 --branch tlb-alt
271 heddle timeline reset --step tls-abc --materialize
272 heddle timeline recover
273")]
274 Timeline(TimelineArgs),
275
276 /// Show state details.
277 Show {
278 /// State by physical state ID, logical change ID, or unambiguous prefix.
279 /// Defaults to HEAD.
280 state: Option<String>,
281 },
282
283 /// Summarize a working session.
284 ///
285 /// Combines oplog, agent registry, marker, and context-annotation
286 /// reads into one structured payload — agent-readable retro of
287 /// captures, signals, and notable events since `--since`. Replaces
288 /// the reconstruct-from-`heddle log` boilerplate.
289 Retro(RetroArgs),
290
291 /// Show what changed in the worktree, a thread, or two states.
292 Diff(DiffArgs),
293
294 /// Open or resolve discussions anchored to symbols.
295 ///
296 /// Open a discussion against a symbol; append turns;
297 /// resolve by edit or dismiss. Anchors
298 /// travel across renames and cross-file moves on subsequent
299 /// state mutations.
300 ///
301 /// Native Heddle only. Discussions live in `.heddle` and travel
302 /// over `heddle push` / `heddle pull` to a Heddle remote. They are
303 /// not projected into Git, so `git clone` does not carry them; in
304 /// Git Overlay mode they are local to that working copy.
305 #[command(after_help = "\
306Scope:
307 Native Heddle only. Discussions are stored in `.heddle` and move over
308 `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
309 Git Overlay repository arrives with no discussions and no Heddle store.
310
311Examples:
312 heddle discuss open src/auth.rs verify 'Should this reject expired tokens?' # anchor a discussion
313 heddle discuss append <id> 'switched to argon2' # add a turn
314 heddle discuss resolve <id> --mode by-edit --state HEAD
315")]
316 Discuss {
317 #[command(subcommand)]
318 command: DiscussCommands,
319 },
320
321 /// Structured query over the operation log. Filter by
322 /// actor, time window, signal kind, symbol, thread, verbs. Returns
323 /// structured results consumable by agents.
324 Query(QueryArgs),
325
326 /// Review a state — render the payload, sign, see signal health.
327 ///
328 /// `heddle review show` renders the review payload (summary,
329 /// agent narrative, in-budget signals, anchored discussions).
330 /// `heddle review sign` submits a `read` / `agent_preview` /
331 /// `agent_co_review` signature on the state. `heddle review
332 /// health` reports per-module signal fire rates over a rolling
333 /// window.
334 #[command(after_help = "\
335Examples:
336 heddle review show HEAD # render the review payload for HEAD
337 heddle review sign HEAD --kind read --public-key <hex> --signature <hex> --signed-at-unix <ts>
338 heddle review health --window 7 # signal fire-rates over recent states
339")]
340 Review {
341 #[command(subcommand)]
342 command: ReviewCommands,
343 },
344
345 /// Redact a sensitive blob in a state so reads return a stub
346 /// instead of the content.
347 ///
348 /// `heddle redact apply` declares a redaction; the blob bytes stay
349 /// on disk and reads return the operator-supplied stub. `heddle
350 /// redact purge` afterward physically removes the bytes. Both are signed,
351 /// attributed, oplog-audited operations. See
352 /// `docs/PRINCIPLES.md` (the honesty principle) for context.
353 Redact {
354 #[command(subcommand)]
355 command: RedactCommands,
356 },
357
358 /// Declare and inspect a state's audience visibility tier.
359 ///
360 /// `heddle visibility set` binds a tier to a state; `promote` lifts it to
361 /// a less-restrictive tier via a superseding record; `show` reports the
362 /// effective tier; `list` enumerates non-public states. Capture binds the
363 /// inherited `[review.discussion] default_visibility` automatically
364 /// (Invariant A) — these verbs are the explicit operator overrides.
365 Visibility {
366 #[command(subcommand)]
367 command: VisibilityCommands,
368 },
369
370 /// Revert changes from a state.
371 Revert(RevertArgs),
372
373 /// Undo the last Heddle operation.
374 Undo(UndoArgs),
375
376 /// Collapse (squash) multiple states into one.
377 Collapse(CollapseArgs),
378
379 /// Expand a squashed land into the captures it collapsed.
380 Expand(ExpandArgs),
381
382 /// Manage threads.
383 Thread {
384 #[command(subcommand)]
385 command: ThreadCommands,
386 },
387
388 /// Shell integration helpers (auto-cd on thread start/switch/cd).
389 Shell {
390 #[command(subcommand)]
391 command: ShellCommands,
392 },
393
394 /// Print a tab-completion script for bash, zsh, or fish.
395 ///
396 /// With no shell, prints install lines. With `bash`, `zsh`, or `fish`,
397 /// emits the same script as `heddle shell completion`.
398 Completions {
399 /// Shell to generate completion for: bash, zsh, or fish.
400 #[arg(value_name = "SHELL")]
401 shell: Option<String>,
402 },
403
404 /// Internal shell-completion candidate helper.
405 #[command(name = "complete", alias = "__complete", hide = true)]
406 Complete {
407 /// Candidate set to print, one candidate per line.
408 #[arg(value_enum)]
409 subject: CompletionSubject,
410 },
411
412 /// Resolve merge conflicts.
413 Resolve(ResolveArgs),
414
415 /// Inspect and repair the operation log.
416 ///
417 /// `heddle oplog recover` explicitly salvages a truncated or torn oplog,
418 /// reporting what was recovered — the operator-facing entrypoint over the
419 /// same recovery the everyday read path runs automatically.
420 Oplog {
421 #[command(subcommand)]
422 command: OplogCommands,
423 },
424
425 /// Explicit interoperability with other version-control formats.
426 #[cfg(feature = "git-overlay")]
427 Bridge {
428 #[command(subcommand)]
429 command: BridgeCommands,
430 },
431
432 /// Push the source-authoritative history to a remote.
433 Push(PushArgs),
434
435 /// Pull source-authoritative history from a remote.
436 Pull(PullArgs),
437
438 /// Manage remote repositories.
439 Remote {
440 #[command(subcommand)]
441 command: RemoteCommands,
442 },
443
444 /// Authenticate with a Heddle server.
445 #[cfg(feature = "client")]
446 Auth {
447 #[command(subcommand)]
448 command: AuthCommands,
449 },
450
451 /// Ensure and claim this machine's hosted agent identity.
452 #[cfg(feature = "client")]
453 Identity {
454 #[command(subcommand)]
455 command: IdentityCommands,
456 },
457
458 /// Report the capture actor, then hosted auth.
459 ///
460 /// The capture actor is who the next capture is attributed to
461 /// (`user_config`, `init --principal-*`, or `HEDDLE_PRINCIPAL_*`).
462 /// Hosted auth is whether this machine has a server credential.
463 /// These are different objects. `identity ensure` does not set the
464 /// local actor.
465 #[cfg(feature = "client")]
466 #[command(after_help = "\
467The capture actor and hosted auth are different objects:
468 capture actor who the next capture is attributed to
469 (user_config, init --principal-*, or HEDDLE_PRINCIPAL_*)
470 hosted auth whether this machine has a credential for the server
471 (heddle auth login). identity ensure does not set the actor.
472
473Examples:
474 heddle whoami # capture actor first, then hosted auth
475 heddle whoami --output json # machine-readable, stable output_kind shape
476 heddle whoami --server api.heddle.sh")]
477 Whoami {
478 /// Heddle server address (defaults to the configured server).
479 #[arg(long)]
480 server: Option<String>,
481 },
482
483 /// Manage code context annotations.
484 ///
485 /// Native Heddle only. Annotations live in `.heddle`, and travel
486 /// over `heddle push` / `heddle pull` to a Heddle remote. They are
487 /// deliberately not projected into Git — not into `refs/notes/*`,
488 /// not into a tracked file — so `git push` and `git clone` do not
489 /// carry them. In Git Overlay mode annotations still work and are
490 /// still useful; they are simply local to that working copy.
491 #[command(after_help = "\
492Scope:
493 Native Heddle only. Annotations are stored in `.heddle` and move over
494 `heddle push` / `heddle pull`. Git does not carry them: a `git clone` of a
495 Git Overlay repository arrives with no annotations and no Heddle store.
496
497Examples:
498 heddle context set --path src/auth.rs --scope symbol:verify --kind invariant -m 'returns false on timing mismatch'
499 heddle context get --path src/auth.rs --scope symbol:verify
500 heddle context list --prefix src/auth # everything attached under a path
501 heddle context check --path src/auth.rs # surface annotations for editor tooling
502")]
503 Context {
504 #[command(subcommand)]
505 command: ContextCommands,
506 },
507
508 /// Manage ambient harness integrations.
509 Integration {
510 #[command(subcommand)]
511 command: IntegrationCommands,
512 },
513
514 /// Semantic analysis queries (call-graph hot-spots, churn,
515 /// signature-stability surfaces).
516 #[cfg(feature = "semantic")]
517 Semantic {
518 #[command(subcommand)]
519 command: SemanticCommands,
520 },
521
522 /// FUSE mount-daemon control plane — distinct from `agent`.
523 ///
524 /// `heddle daemon serve` runs a foreground mount daemon that
525 /// owns FUSE sessions for `--workspace virtualized --daemon`
526 /// threads. It is normally spawned on demand by the per-thread
527 /// CLI; running it interactively is for debugging.
528 /// `status` reports liveness/uptime/mount count without spawning;
529 /// `stop` asks a running daemon to drain mounts and exit.
530 Daemon {
531 #[command(subcommand)]
532 command: DaemonCommands,
533 },
534
535 /// Agent reservation and one-shot orchestration API.
536 ///
537 /// `heddle agent reserve|capture|ready|release|list|heartbeat` is the stable
538 /// JSON contract orchestrators use to coordinate parallel
539 /// writers. `heddle daemon` remains the distinct FUSE mount control plane.
540 Agent {
541 #[command(subcommand)]
542 command: AgentCommands,
543 },
544
545 /// Inspect attribution and work context for local agents.
546 ///
547 /// Presence is intentionally separate from `agent`: presence records
548 /// explain who is acting, while `agent reserve` grants writer authority.
549 Presence {
550 #[command(subcommand)]
551 command: PresenceCommands,
552 },
553
554 /// Inspect and refresh rebuildable performance sidecars.
555 Maintenance {
556 #[command(subcommand)]
557 command: MaintenanceCommands,
558 },
559
560 /// Clone from remote.
561 Clone(CloneArgs),
562
563 /// Manage repository hooks.
564 Hook {
565 #[command(subcommand)]
566 command: HookCommands,
567 },
568}
569
570/// Maintenance subcommands.
571#[derive(Clone, Debug, clap::Subcommand)]
572pub enum MaintenanceCommands {
573 /// Verify repository integrity or explicitly repair one surface.
574 Fsck(FsckArgs),
575
576 /// Inspect repository performance sidecars and repo shape.
577 Inspect,
578
579 /// Refresh repository performance sidecars without changing repository meaning.
580 Refresh,
581
582 /// Repack native objects now through the resource-controlled scheduler.
583 Repack,
584
585 /// Garbage collect unreachable objects.
586 Gc {
587 /// Prune unreachable objects.
588 #[arg(long)]
589 prune: bool,
590
591 /// Aggressive garbage collection.
592 #[arg(long)]
593 aggressive: bool,
594
595 /// Show what would be removed without removing.
596 #[arg(long)]
597 dry_run: bool,
598 },
599}
600
601/// Daemon control plane subcommands. See `Commands::Daemon`.
602#[derive(Clone, Debug, clap::Subcommand)]
603pub enum DaemonCommands {
604 /// Run a foreground mount daemon for this repository.
605 ///
606 /// Normally spawned on demand by the per-thread CLI when
607 /// `--daemon` is passed. Running interactively is for
608 /// debugging the daemon protocol.
609 Serve,
610
611 /// Report daemon liveness, version, uptime, and active mount
612 /// count. No-op success when the daemon isn't running.
613 Status,
614
615 /// Ask the running daemon to drain its mounts and exit. Sweeps
616 /// any leftover registry entries with `fusermount -u` as a
617 /// safety net before returning.
618 Stop,
619}