onevcs 0.15.6

Version control and remote-host abstraction for agent workflows: host-neutral change requests, sessions, and a rules system.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
//! The command-line argument surface.
//!
//! This is the parser only: it validates what a user typed and nothing else. The
//! binary in `src/main.rs` decides what to do with the result — today, refuse
//! with exit code 70, because nothing behind the contract is implemented yet.

use std::path::PathBuf;
use std::time::Duration;

use clap::{Parser, Subcommand};
use url::Url;

use crate::releases::TargetName;
use crate::rules::MergePolicy;
use crate::sweep;

/// Version control and its remote host, behind one host-neutral vocabulary.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
#[command(name = "onevcs", version, about, long_about = None)]
pub struct Cli {
    /// What to do.
    #[command(subcommand)]
    pub command: Command,
}

/// The top-level commands.
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum Command {
    /// Register a checkout, resolving its origin to a repository identity.
    Register(RegisterArgs),
    /// List the registered repositories.
    Repos(ReposArgs),
    /// Resolve a repository to its identity.
    Resolve(ResolveArgs),
    /// Open, adopt, or close a session.
    Session {
        /// Which part of a session's life cycle.
        #[command(subcommand)]
        command: SessionCommand,
    },
    /// Verify a session's work and publish it under its policy.
    Publish(PublishArgs),
    /// Verify and publish a completed branch no session holds.
    PublishBranch(PublishBranchArgs),
    /// Verify and publish a preserved branch that was left behind.
    Recover(RecoverArgs),
    /// List preserved work that has not been published.
    Recoverable(RecoverableArgs),
    /// Report everything onevcs knows about one piece of work.
    Status(StatusArgs),
    /// Make a branch reachable from an identity's registered checkouts.
    Import(ImportArgs),
    /// Merge finished branches into their base, in order.
    Integrate(IntegrateArgs),
    /// Fast-forward a publication checkout to its origin.
    Sync(SyncArgs),
    /// Reclaim the publication workspaces this host has finished with.
    Sweep(SweepArgs),
    /// Read a session's event stream.
    Events(EventsArgs),
    /// Work with stored artifacts.
    Artifact {
        /// What to do with an artifact.
        #[command(subcommand)]
        command: ArtifactCommand,
    },
    /// Work with the rules file.
    Rules {
        /// What to do with the rules.
        #[command(subcommand)]
        command: RulesCommand,
    },
    /// Ask about the releases that follow a landed change.
    Release {
        /// Which release question.
        #[command(subcommand)]
        command: ReleaseCommand,
    },
}

/// Arguments for `onevcs register`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct RegisterArgs {
    /// The checkout to register.
    pub path: PathBuf,
    /// The origin to resolve the identity from, when the checkout's own remote
    /// is not the one to use.
    #[arg(long, value_name = "URL")]
    pub origin: Option<Url>,
}

/// Arguments for `onevcs repos`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ReposArgs {
    /// Also report which identities have merge-path verification and which do
    /// not.
    #[arg(long)]
    pub audit_gates: bool,
}

/// Arguments for `onevcs resolve`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ResolveArgs {
    /// An identity key, a registered alias, an origin URL, or a path.
    pub repo: String,
}

/// The `onevcs session` subcommands.
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum SessionCommand {
    /// Open a session over a per-run clone and worktree.
    Open(SessionOpenArgs),
    /// Re-attach to an existing session.
    Adopt(SessionTokenArgs),
    /// Release a session's worktree and its occupancy lease.
    Close(SessionTokenArgs),
    /// List every session recorded for a repository.
    Holders(SessionHoldersArgs),
}

/// Arguments for `onevcs session open`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct SessionOpenArgs {
    /// An identity key, a registered alias, an origin URL, or a path.
    pub repo: String,
    /// The branch to work on. One that already exists is continued from its own
    /// tip; one that does not is cut from the base. Omitted, one is derived.
    #[arg(long, value_name = "B")]
    pub branch: Option<String>,
    /// The branch this work is merged with and published into, and the one a new
    /// branch is cut from. Omitted, the identity's registered base is used.
    #[arg(long, value_name = "B")]
    pub base: Option<String>,
    /// Which registered checkout to clone from.
    #[arg(long, value_name = "ALIAS")]
    pub execution_checkout: Option<String>,
}

/// A session token, for the commands that take nothing else.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct SessionTokenArgs {
    /// The token `onevcs session open` printed.
    pub token: String,
}

/// Arguments for `onevcs session holders`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct SessionHoldersArgs {
    /// An identity key, a registered alias, an origin URL, or a path.
    pub repo: String,
    /// Report the holders as a JSON array.
    #[arg(long)]
    pub json: bool,
}

/// Arguments for `onevcs publish`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct PublishArgs {
    /// The token of the session to publish.
    pub token: String,
    /// Override the policy the rules chose. It may narrow the stored policy but
    /// never widen it past requiring approvals.
    #[arg(long, value_name = "P")]
    pub policy: Option<MergePolicy>,
    /// The change request's title.
    #[arg(long, value_name = "T")]
    pub title: Option<String>,
    // llmlint: ignore-block[invalid_states_unrepresentable] the two body options are
    // deliberately representable together, and refused by name in `app::explicit_body`
    // — which is where the refusal can say which two were given, which one to keep,
    // and the invocation that keeps it. A clap `conflicts_with` would answer the same
    // mistake with usage text, and every other argument this command takes is checked
    // at dispatch for that reason.
    /// The change request's body. Omitted, it is opened with none.
    #[arg(long, value_name = "TEXT")]
    pub body: Option<String>,
    /// A file holding the change request's body. A body is prose, so this is the
    /// form a caller with a real one uses.
    #[arg(long, value_name = "PATH")]
    pub body_file: Option<PathBuf>,
    // llmlint: ignore-end[invalid_states_unrepresentable]
}

/// Arguments for `onevcs publish-branch`.
///
/// A branch and a title arrive as typed text, as they do on every other command
/// that takes one, and are converted at dispatch — into the crate's validated ref
/// and [`Subject`](crate::Subject) — where a refusal can name what to do about them.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct PublishBranchArgs {
    /// The completed branch to verify and publish.
    // llmlint: ignore[invalid_states_unrepresentable] this module is the parser only,
    // and what makes a branch name valid is `git check-ref-format` — a subprocess,
    // which argument parsing must not run. `branch::prepare` is the one boundary that
    // decides it, for both verbs, and its refusal names `onevcs recoverable`;
    // `tests/e2e/publish_branch.rs` holds it there.
    pub branch: String,
    /// The checkout the branch can be reached from.
    #[arg(long, value_name = "PATH")]
    pub repo: PathBuf,
    /// The change request's title.
    // llmlint: ignore[invalid_states_unrepresentable] `Subject` is what this becomes,
    // by the same conversion the library surface uses, in `app::explicit_title` —
    // before anything is cloned or committed. It is spelled the way `PublishArgs`
    // spells the same option, so one option does not meet two refusals depending on
    // which command took it: a title clap rejected would answer with usage text where
    // `onevcs publish` answers with the title the operator typed.
    #[arg(long, value_name = "T")]
    pub title: Option<String>,
    /// Override the policy the rules chose. It may narrow the stored policy but
    /// never widen it past requiring approvals.
    #[arg(long, value_name = "P")]
    pub policy: Option<MergePolicy>,
    // llmlint: ignore-block[invalid_states_unrepresentable] the same pair `PublishArgs`
    // carries, representable together for the same reason: see the note there.
    /// The change request's body. Omitted, it is opened with none.
    #[arg(long, value_name = "TEXT")]
    pub body: Option<String>,
    /// A file holding the change request's body. A body is prose, so this is the
    /// form a caller with a real one uses.
    #[arg(long, value_name = "PATH")]
    pub body_file: Option<PathBuf>,
    // llmlint: ignore-end[invalid_states_unrepresentable]
}

/// Arguments for `onevcs recover`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct RecoverArgs {
    /// The preserved branch to verify and publish.
    pub branch: String,
    /// The checkout the branch can be reached from.
    #[arg(long, value_name = "PATH")]
    pub repo: PathBuf,
    /// The published change's title, which replaces the subject synthesized from
    /// the branch.
    // llmlint: ignore[invalid_states_unrepresentable] typed text for the reason given
    // on `PublishBranchArgs::title`: it becomes a `Subject` in `app::explicit_title`,
    // and spelling it as one here would answer a blank title with clap's usage text
    // where the other two commands name the title itself.
    #[arg(long, value_name = "T")]
    pub title: Option<String>,
    // llmlint: ignore-block[invalid_states_unrepresentable] the same pair `PublishArgs`
    // carries, representable together for the same reason: see the note there.
    /// The change request's body. Omitted, it is opened with none.
    #[arg(long, value_name = "TEXT")]
    pub body: Option<String>,
    /// A file holding the change request's body. A body is prose, so this is the
    /// form a caller with a real one uses.
    #[arg(long, value_name = "PATH")]
    pub body_file: Option<PathBuf>,
    // llmlint: ignore-end[invalid_states_unrepresentable]
}

/// Arguments for `onevcs recoverable`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct RecoverableArgs {
    /// List every preserved branch, including the ones whose work reached their
    /// base and the ones nothing here can decide about.
    #[arg(long)]
    pub all: bool,
    /// Report as JSON rather than as a human table.
    #[arg(long)]
    pub json: bool,
}

/// Arguments for `onevcs status`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct StatusArgs {
    /// The work to report on: a change request's URL, a session token, a branch
    /// name, or a commit — read in that order.
    // llmlint: ignore[invalid_states_unrepresentable] four spellings share this one
    // operand deliberately, and which one a value is cannot be decided by a parser: a
    // session token names a file under the state root, a branch name is decided by
    // `git check-ref-format`, and a commit is one a repository has. `status::resolve`
    // is the boundary that decides, and its refusal names every candidate rather
    // than answering with clap's usage text.
    pub reference: String,
    /// Report as JSON rather than as a human table.
    #[arg(long)]
    pub json: bool,
}

/// Arguments for `onevcs import`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ImportArgs {
    /// The branch to make reachable.
    // llmlint: ignore[invalid_states_unrepresentable] a branch name is decided by
    // `git check-ref-format`, which is a subprocess argument parsing must not run —
    // the same reason `PublishBranchArgs::branch` is typed text. `import::run` is the
    // one boundary that decides it, and its refusal names the command that lists the
    // branches there are.
    pub branch: String,
    /// The checkout whose identity the branch is imported into.
    #[arg(long, value_name = "PATH")]
    pub repo: PathBuf,
    /// Where to read it from: the path of a checkout or a run clone, or a remote
    /// ref. Omitted, everywhere this identity keeps work is searched.
    #[arg(long, value_name = "SOURCE")]
    pub from: Option<String>,
    /// An alternate local name to import it under, for when the original is spent.
    // llmlint: ignore[invalid_states_unrepresentable] the same boundary as `branch`
    // above, and the same reason: git's own parser decides it, in `import::run`,
    // where the refusal can name the option that carried it.
    #[arg(long, value_name = "NAME")]
    pub r#as: Option<String>,
}

/// Arguments for `onevcs integrate`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct IntegrateArgs {
    /// The branches to merge, in the order they should land.
    #[arg(required = true, num_args = 1..)]
    pub branches: Vec<String>,
    /// Push the base once every branch has landed.
    #[arg(long)]
    pub push: bool,
}

/// Arguments for `onevcs sync`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct SyncArgs {
    /// The branch to fast-forward. Omitted, the registered base is used.
    pub branch: Option<String>,
}

/// Arguments for `onevcs sweep`.
///
/// Both the spelling and the default are shared with `oneagentgraph sweep`, because
/// one composing caller forwards its own arguments to each unchanged. Neither side
/// may depart from them alone.
// llmlint: ignore-block[contracts_have_one_source_or_a_drift_gate] the default is not
// restated here: it comes from `sweep::DEFAULT_MIN_AGE_HOURS`, which is the crate's one
// source for it and is gated against `docs/inferred-surface.md`. Why the other side of
// the surface cannot be gated from this repository is written out at that constant.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct SweepArgs {
    /// Report what would be reclaimed and remove nothing.
    #[arg(long)]
    pub dry_run: bool,
    /// Leave anything written inside this many hours alone.
    // A window rather than a number, so nothing past the parser can be handed hours
    // that are negative, infinite, or not a number: `sweep::hours` refuses those
    // here, where clap's own usage error names the option that carried them.
    #[arg(
        long,
        value_name = "HOURS",
        default_value = sweep::DEFAULT_MIN_AGE_HOURS,
        value_parser = sweep::hours,
    )]
    pub min_age_hours: Duration,
}
// llmlint: ignore-end[contracts_have_one_source_or_a_drift_gate]

/// Arguments for `onevcs events`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct EventsArgs {
    /// The token of the session whose stream to read.
    pub token: String,
    /// Keep reading as the session writes.
    #[arg(long)]
    pub follow: bool,
    /// Report only the events a filter spec admits: the spec inline as JSON when
    /// it opens with `{`, otherwise the path of a file holding one.
    #[arg(long, value_name = "SPEC")]
    pub filter: Option<String>,
}

/// The `onevcs artifact` subcommands.
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum ArtifactCommand {
    /// Write a stored artifact to stdout.
    Cat(ArtifactCatArgs),
}

/// Arguments for `onevcs artifact cat`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ArtifactCatArgs {
    /// The artifact id an event referenced.
    pub id: String,
}

/// The `onevcs rules` subcommands.
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum RulesCommand {
    /// Report which rule a repository matches, and the policy that follows.
    Check(RulesCheckArgs),
}

/// Arguments for `onevcs rules check`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct RulesCheckArgs {
    /// An identity key, a registered alias, an origin URL, or a path.
    pub repo: String,
}

/// The `onevcs release` subcommands.
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum ReleaseCommand {
    /// Report which release targets a repository has, and what it adopts.
    Targets(ReleaseTargetsArgs),
    /// Report what version of a target is released right now.
    Latest(ReleaseLatestArgs),
    /// Report whether the release carrying a landed change is out yet.
    Status(ReleaseStatusArgs),
    /// Record that somebody performed a human-step release.
    Acknowledge(ReleaseAcknowledgeArgs),
}

/// Arguments for `onevcs release targets`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ReleaseTargetsArgs {
    /// An identity key, a registered alias, an origin URL, or a path.
    pub repo: String,
    /// Report as JSON rather than as a human table.
    #[arg(long)]
    pub json: bool,
}

/// Arguments for `onevcs release latest`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ReleaseLatestArgs {
    /// An identity key, a registered alias, an origin URL, or a path.
    pub repo: String,
    /// Which target to ask about. Omitted, the repository's `default_target` is
    /// used.
    #[arg(long, value_name = "NAME")]
    pub target: Option<TargetName>,
    /// Report as JSON rather than as a human table.
    #[arg(long)]
    pub json: bool,
}

/// Arguments for `onevcs release status`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ReleaseStatusArgs {
    /// The work to report on: a change request's URL, a session token, a branch
    /// name, or a commit — the same four spellings `onevcs status` takes.
    // llmlint: ignore[invalid_states_unrepresentable] the same operand `StatusArgs`
    // carries, for the same reason: which of the four spellings a value is cannot be
    // decided by a parser, and `status::resolve` is the one boundary that decides it.
    pub reference: String,
    /// Which target to ask about. Omitted, the repository's `default_target` is
    /// used.
    #[arg(long, value_name = "NAME")]
    pub target: Option<TargetName>,
    /// Report as JSON rather than as a human table.
    #[arg(long)]
    pub json: bool,
}

/// Arguments for `onevcs release acknowledge`.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct ReleaseAcknowledgeArgs {
    /// The landed work the release carries, in the same four spellings
    /// `onevcs status` takes.
    // llmlint: ignore[invalid_states_unrepresentable] as on `ReleaseStatusArgs` above.
    pub reference: String,
    /// The target that was released. Required: this operation records a fact
    /// somebody performed, and which artifact they released is not a thing to
    /// infer.
    #[arg(long, value_name = "NAME")]
    pub target: TargetName,
    /// The version that was released.
    // llmlint: ignore[invalid_states_unrepresentable] whether a value is a semantic
    // version is decided in `release::acknowledge`, beside the three other refusals
    // this operation makes, so an operator meets one vocabulary of refusal rather
    // than clap's usage text for one of the four and prose for the rest.
    #[arg(long, value_name = "VERSION")]
    pub version: String,
    /// Replace a different version already recorded for this landing, keeping the
    /// old one in the record's own history.
    #[arg(long)]
    pub supersede: bool,
    /// Report as JSON rather than as a human table.
    #[arg(long)]
    pub json: bool,
}