dotrepo_cli/cli.rs
1use clap::{Parser, Subcommand, ValueEnum};
2use dotrepo_core::{ClaimEventKind, DoctorSurface, ImportMode};
3use std::path::PathBuf;
4
5#[derive(Parser)]
6#[command(name = "dotrepo")]
7#[command(about = "reference cli for the dotrepo protocol")]
8pub struct Cli {
9 /// Repository root containing `.repo` or overlay records.
10 #[arg(long, default_value = ".")]
11 pub root: PathBuf,
12 #[command(subcommand)]
13 pub command: Command,
14}
15
16#[derive(Subcommand)]
17pub enum Command {
18 /// Initialize a canonical root `.repo` scaffold.
19 Init {
20 /// Overwrite an existing root `.repo`.
21 #[arg(long)]
22 force: bool,
23 },
24 /// Import conventional repository surfaces into a dotrepo record.
25 Import {
26 /// Record mode for the imported manifest.
27 #[arg(long, value_enum, default_value_t = ImportModeArg::Native)]
28 mode: ImportModeArg,
29 /// Explicit repository source URL for overlays.
30 #[arg(long)]
31 source: Option<String>,
32 /// Overwrite previously imported artifacts.
33 #[arg(long)]
34 force: bool,
35 },
36 /// Bootstrap a native `.repo` from an existing public overlay record.
37 AdoptOverlay {
38 /// Path to an overlay record.toml from the public index.
39 overlay_record: PathBuf,
40 /// Overwrite an existing root `.repo`.
41 #[arg(long)]
42 force: bool,
43 },
44 /// Validate only the root `.repo` or root `record.toml`.
45 Validate,
46 /// Validate a public index tree rooted at `index/`.
47 ValidateIndex {
48 /// Index root to validate.
49 #[arg(long, default_value = "index")]
50 index_root: PathBuf,
51 },
52 /// Analyze index records for promotion eligibility to verified status.
53 PromotionReport {
54 /// Index root to analyze.
55 #[arg(long, default_value = "index")]
56 index_root: PathBuf,
57 /// Apply eligible draft/imported/inferred promotions after reporting.
58 #[arg(long)]
59 apply: bool,
60 /// Maximum number of promotions to apply.
61 #[arg(long, requires = "apply")]
62 limit: Option<usize>,
63 /// Emit the full report as JSON.
64 #[arg(long)]
65 json: bool,
66 /// Show per-record details including blockers.
67 #[arg(long)]
68 verbose: bool,
69 },
70 /// Query one field from the selected record, preserving trust-aware selection in `--json`.
71 Query {
72 /// Dot-path such as `repo.name` or `record.trust.provenance`.
73 path: String,
74 /// Emit the full conflict-aware query report as JSON.
75 #[arg(long, conflicts_with = "raw")]
76 json: bool,
77 /// Emit only the selected scalar value. Refuses when competing records exist.
78 #[arg(long, conflicts_with = "json")]
79 raw: bool,
80 },
81 /// Render generated compatibility surfaces or fail when they drift.
82 Generate {
83 /// Check generated surfaces for drift without writing files.
84 #[arg(long)]
85 check: bool,
86 },
87 /// Inspect unmanaged conventional files at the repository root.
88 Doctor {
89 /// Emit the full doctor report as JSON.
90 #[arg(long)]
91 json: bool,
92 },
93 /// Adopt one supported Markdown surface into managed-region sync.
94 Manage {
95 /// Conventional surface to manage.
96 surface: PreviewSurfaceArg,
97 /// Convert the existing file into a managed-region file.
98 #[arg(long)]
99 adopt: bool,
100 },
101 /// Preview how dotrepo would render or replace one managed surface.
102 Preview {
103 /// Conventional surface to preview.
104 #[arg(long, value_enum, conflicts_with = "all")]
105 surface: Option<PreviewSurfaceArg>,
106 /// Preview every supported surface.
107 #[arg(long, conflicts_with = "surface")]
108 all: bool,
109 /// Emit the full preview report as JSON.
110 #[arg(long)]
111 json: bool,
112 },
113 /// Inspect trust, authority handoff, and competing records for one repository identity.
114 Trust {
115 /// Emit the full conflict-aware trust report as JSON.
116 #[arg(long)]
117 json: bool,
118 },
119 /// Summarize native adoption readiness for the current repository.
120 AdoptionStatus {
121 /// Emit the full adoption readiness report as JSON.
122 #[arg(long)]
123 json: bool,
124 },
125 /// Scaffold CI for the canonical native-repo maintainer loop.
126 Ci {
127 #[command(subcommand)]
128 command: CiCommand,
129 },
130 /// Inspect one maintainer-claim directory from the index.
131 Claim {
132 /// Claim directory relative to --root.
133 path: PathBuf,
134 /// Emit the full claim inspection report as JSON.
135 #[arg(long)]
136 json: bool,
137 },
138 /// Scaffold a draft maintainer-claim directory for one index repository.
139 ClaimInit {
140 /// Repository host under index_root/repos/<host>/<owner>/<repo>/.
141 #[arg(long)]
142 host: String,
143 /// Repository owner under index_root/repos/<host>/<owner>/<repo>/.
144 #[arg(long)]
145 owner: String,
146 /// Repository name under index_root/repos/<host>/<owner>/<repo>/.
147 #[arg(long)]
148 repo: String,
149 /// Claim directory name under claims/<claim-id>/.
150 #[arg(long)]
151 claim_id: String,
152 /// Claimant display name recorded in claim.toml.
153 #[arg(long)]
154 claimant_name: String,
155 /// Claimed repository role, such as `maintainer`.
156 #[arg(long)]
157 asserted_role: String,
158 /// Optional claimant contact detail.
159 #[arg(long)]
160 contact: Option<String>,
161 /// Additional record source URLs tied to this claim.
162 #[arg(long = "record-source")]
163 record_sources: Vec<String>,
164 /// Optional canonical repository URL for the claim target.
165 #[arg(long)]
166 canonical_repo_url: Option<String>,
167 /// Create a placeholder review.md next to claim.toml.
168 #[arg(long)]
169 review_md: bool,
170 /// Replace an existing empty scaffold, but never overwrite event history.
171 #[arg(long)]
172 force: bool,
173 },
174 /// Scaffold a maintainer claim from the current native `.repo`.
175 ClaimFromNative {
176 /// Index root where the overlay claim directory should be created.
177 #[arg(long, default_value = "index")]
178 index_root: PathBuf,
179 /// Claim directory name under claims/<claim-id>/.
180 #[arg(long)]
181 claim_id: String,
182 /// Claimant display name recorded in claim.toml.
183 #[arg(long)]
184 claimant_name: String,
185 /// Claimed repository role, such as `maintainer`.
186 #[arg(long, default_value = "maintainer")]
187 asserted_role: String,
188 /// Optional claimant contact detail.
189 #[arg(long)]
190 contact: Option<String>,
191 /// Create a placeholder review.md next to claim.toml.
192 #[arg(long)]
193 review_md: bool,
194 /// Replace an existing empty scaffold, but never overwrite event history.
195 #[arg(long)]
196 force: bool,
197 },
198 /// Append a new claim event and update the current claim state.
199 ClaimEvent {
200 /// Claim directory relative to --root.
201 path: PathBuf,
202 /// Event kind to append.
203 #[arg(long, value_enum)]
204 kind: ClaimEventKindArg,
205 /// Actor label recorded in the event.
206 #[arg(long)]
207 actor: String,
208 /// Short event summary recorded in the audit trail.
209 #[arg(long)]
210 summary: String,
211 /// Optional corrected current state when kind=corrected.
212 #[arg(long, value_enum)]
213 corrected_state: Option<CorrectedClaimStateArg>,
214 /// Optional canonical `.repo` path recorded for accepted handoff.
215 #[arg(long)]
216 canonical_record_path: Option<String>,
217 /// Optional canonical mirror record path recorded for accepted handoff.
218 #[arg(long)]
219 canonical_mirror_path: Option<String>,
220 },
221 /// Append a submitted claim event with the claim path derived from the current native `.repo`.
222 ClaimSubmitNative {
223 /// Index root containing the claim directory.
224 #[arg(long, default_value = "index")]
225 index_root: PathBuf,
226 /// Claim directory name under claims/<claim-id>/.
227 #[arg(long)]
228 claim_id: String,
229 /// Actor label recorded in the event.
230 #[arg(long, default_value = "claimant")]
231 actor: String,
232 /// Short event summary recorded in the audit trail.
233 #[arg(long, default_value = "Submitted maintainer claim.")]
234 summary: String,
235 },
236 /// Append an accepted claim event with canonical links from the current native `.repo`.
237 ClaimAcceptNative {
238 /// Index root containing the claim directory.
239 #[arg(long, default_value = "index")]
240 index_root: PathBuf,
241 /// Claim directory relative to --index-root.
242 #[arg(
243 value_name = "CLAIM_PATH",
244 help = "Claim directory relative to --index-root"
245 )]
246 path: Option<PathBuf>,
247 /// Claim directory name under claims/<claim-id>; derives path from repo.homepage.
248 #[arg(long)]
249 claim_id: Option<String>,
250 /// Actor label recorded in the event.
251 #[arg(long, default_value = "index-reviewer")]
252 actor: String,
253 /// Short event summary recorded in the audit trail.
254 #[arg(
255 long,
256 default_value = "Accepted maintainer claim with canonical native record."
257 )]
258 summary: String,
259 },
260 /// Inspect or export public read-only index responses.
261 Public {
262 #[command(subcommand)]
263 command: PublicCommand,
264 },
265}
266
267#[derive(Subcommand)]
268pub enum PublicCommand {
269 /// Render one public repository summary response as JSON.
270 Summary {
271 #[arg(long, default_value = "index")]
272 index_root: PathBuf,
273 host: String,
274 owner: String,
275 repo: String,
276 /// URL base path prefix for hosted public links, such as `/dotrepo`.
277 #[arg(long, default_value = "/")]
278 base_path: String,
279 /// Advisory staleness window in hours for the rendered response.
280 #[arg(long)]
281 stale_after_hours: Option<i64>,
282 },
283 /// Render one compact public research profile response as JSON.
284 Profile {
285 #[arg(long, default_value = "index")]
286 index_root: PathBuf,
287 host: String,
288 owner: String,
289 repo: String,
290 /// URL base path prefix for hosted public links, such as `/dotrepo`.
291 #[arg(long, default_value = "/")]
292 base_path: String,
293 /// Advisory staleness window in hours for the rendered response.
294 #[arg(long)]
295 stale_after_hours: Option<i64>,
296 },
297 /// Render compact public research profiles for multiple repositories.
298 BatchProfiles {
299 #[arg(long, default_value = "index")]
300 index_root: PathBuf,
301 /// Repository identity as host/owner/repo or https://host/owner/repo.
302 #[arg(long = "repo", required = true)]
303 repos: Vec<String>,
304 /// URL base path prefix for hosted public links, such as `/dotrepo`.
305 #[arg(long, default_value = "/")]
306 base_path: String,
307 /// Advisory staleness window in hours for the rendered response.
308 #[arg(long)]
309 stale_after_hours: Option<i64>,
310 },
311 /// Render public query responses for multiple repositories and dot paths.
312 BatchQuery {
313 #[arg(long, default_value = "index")]
314 index_root: PathBuf,
315 /// Repository identity as host/owner/repo or https://host/owner/repo.
316 #[arg(long = "repo", required = true)]
317 repos: Vec<String>,
318 /// Dot path to query. Repeat for multiple fields.
319 #[arg(long = "path", required = true)]
320 paths: Vec<String>,
321 /// URL base path prefix for hosted public links, such as `/dotrepo`.
322 #[arg(long, default_value = "/")]
323 base_path: String,
324 /// Advisory staleness window in hours for the rendered response.
325 #[arg(long)]
326 stale_after_hours: Option<i64>,
327 },
328 /// Compare compact public research profiles for multiple repositories.
329 Compare {
330 #[arg(long, default_value = "index")]
331 index_root: PathBuf,
332 /// Repository identity as host/owner/repo or https://host/owner/repo.
333 #[arg(long = "repo", required = true)]
334 repos: Vec<String>,
335 /// URL base path prefix for hosted public links, such as `/dotrepo`.
336 #[arg(long, default_value = "/")]
337 base_path: String,
338 /// Advisory staleness window in hours for the rendered response.
339 #[arg(long)]
340 stale_after_hours: Option<i64>,
341 },
342 /// Traverse public repository references declared in the selected profile.
343 Relations {
344 #[arg(long, default_value = "index")]
345 index_root: PathBuf,
346 host: String,
347 owner: String,
348 repo: String,
349 /// URL base path prefix for hosted public links, such as `/dotrepo`.
350 #[arg(long, default_value = "/")]
351 base_path: String,
352 /// Advisory staleness window in hours for the rendered response.
353 #[arg(long)]
354 stale_after_hours: Option<i64>,
355 },
356 /// Search compact public research profiles by text and structured filters.
357 Search {
358 #[arg(long, default_value = "index")]
359 index_root: PathBuf,
360 /// Text query matched against identity, name, purpose, homepage, license, languages, and topics.
361 #[arg(long)]
362 q: Option<String>,
363 /// Required language. Repeat for multiple required languages.
364 #[arg(long = "language")]
365 languages: Vec<String>,
366 /// Required topic. Repeat for multiple required topics.
367 #[arg(long = "topic")]
368 topics: Vec<String>,
369 /// Required selected record status. Repeat for accepted statuses.
370 #[arg(long = "status")]
371 statuses: Vec<String>,
372 /// Required trust confidence. Repeat for accepted confidence values.
373 #[arg(long = "confidence")]
374 confidences: Vec<String>,
375 /// Require a build command signal.
376 #[arg(long)]
377 require_build: bool,
378 /// Require a test command signal.
379 #[arg(long)]
380 require_test: bool,
381 /// Require documentation signal.
382 #[arg(long)]
383 require_docs: bool,
384 /// Require security contact signal.
385 #[arg(long)]
386 require_security_contact: bool,
387 /// Require license signal.
388 #[arg(long)]
389 require_license: bool,
390 /// Maximum results to return.
391 #[arg(long)]
392 limit: Option<usize>,
393 /// URL base path prefix for hosted public links, such as `/dotrepo`.
394 #[arg(long, default_value = "/")]
395 base_path: String,
396 /// Advisory staleness window in hours for the rendered response.
397 #[arg(long)]
398 stale_after_hours: Option<i64>,
399 },
400 /// Render one public trust response as JSON.
401 Trust {
402 #[arg(long, default_value = "index")]
403 index_root: PathBuf,
404 host: String,
405 owner: String,
406 repo: String,
407 /// URL base path prefix for hosted public links, such as `/dotrepo`.
408 #[arg(long, default_value = "/")]
409 base_path: String,
410 /// Advisory staleness window in hours for the rendered response.
411 #[arg(long)]
412 stale_after_hours: Option<i64>,
413 },
414 /// Render one public query response as JSON.
415 Query {
416 #[arg(long, default_value = "index")]
417 index_root: PathBuf,
418 host: String,
419 owner: String,
420 repo: String,
421 path: String,
422 /// URL base path prefix for hosted public links, such as `/dotrepo`.
423 #[arg(long, default_value = "/")]
424 base_path: String,
425 /// Advisory staleness window in hours for the rendered response.
426 #[arg(long)]
427 stale_after_hours: Option<i64>,
428 },
429 /// Export the static-first public JSON tree for repository summary, profile, and trust.
430 Export {
431 #[arg(long, default_value = "index")]
432 index_root: PathBuf,
433 #[arg(long, default_value = "public")]
434 out_dir: PathBuf,
435 /// URL base path prefix for hosted public links, such as `/dotrepo`.
436 #[arg(long, default_value = "/")]
437 base_path: String,
438 /// Advisory staleness window in hours for exported responses.
439 #[arg(long)]
440 stale_after_hours: Option<i64>,
441 /// Fixed RFC 3339 generation timestamp for deterministic export review.
442 #[arg(long)]
443 generated_at: Option<String>,
444 /// Fixed RFC 3339 staleness timestamp for deterministic export review.
445 #[arg(long)]
446 stale_after: Option<String>,
447 /// Previously published pagedigest manifest that carries revision
448 /// state forward when exporting to a fresh directory. Defaults to
449 /// `.well-known/pagedigest.json` inside --out-dir when present.
450 #[arg(long)]
451 pagedigest_previous: Option<PathBuf>,
452 },
453}
454
455#[derive(Subcommand)]
456pub enum CiCommand {
457 /// Write the starter GitHub Actions workflow for native-repo checks.
458 Init {
459 /// Replace an existing workflow file.
460 #[arg(long)]
461 force: bool,
462 /// Release version to pin in the workflow, such as `1.0.0`.
463 #[arg(long)]
464 version: Option<String>,
465 },
466}
467
468#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
469pub enum ImportModeArg {
470 Native,
471 Overlay,
472}
473
474#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
475pub enum PreviewSurfaceArg {
476 Readme,
477 Security,
478 Contributing,
479 Codeowners,
480 PullRequestTemplate,
481}
482
483#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
484pub enum ClaimEventKindArg {
485 Submitted,
486 ReviewStarted,
487 Accepted,
488 Rejected,
489 Withdrawn,
490 Disputed,
491 Corrected,
492}
493
494#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
495pub enum CorrectedClaimStateArg {
496 Submitted,
497 InReview,
498 Accepted,
499 Rejected,
500 Withdrawn,
501 Disputed,
502}
503
504impl From<ImportModeArg> for ImportMode {
505 fn from(value: ImportModeArg) -> Self {
506 match value {
507 ImportModeArg::Native => ImportMode::Native,
508 ImportModeArg::Overlay => ImportMode::Overlay,
509 }
510 }
511}
512
513impl From<PreviewSurfaceArg> for DoctorSurface {
514 fn from(value: PreviewSurfaceArg) -> Self {
515 match value {
516 PreviewSurfaceArg::Readme => DoctorSurface::Readme,
517 PreviewSurfaceArg::Security => DoctorSurface::Security,
518 PreviewSurfaceArg::Contributing => DoctorSurface::Contributing,
519 PreviewSurfaceArg::Codeowners => DoctorSurface::Codeowners,
520 PreviewSurfaceArg::PullRequestTemplate => DoctorSurface::PullRequestTemplate,
521 }
522 }
523}
524
525impl From<ClaimEventKindArg> for ClaimEventKind {
526 fn from(value: ClaimEventKindArg) -> Self {
527 match value {
528 ClaimEventKindArg::Submitted => ClaimEventKind::Submitted,
529 ClaimEventKindArg::ReviewStarted => ClaimEventKind::ReviewStarted,
530 ClaimEventKindArg::Accepted => ClaimEventKind::Accepted,
531 ClaimEventKindArg::Rejected => ClaimEventKind::Rejected,
532 ClaimEventKindArg::Withdrawn => ClaimEventKind::Withdrawn,
533 ClaimEventKindArg::Disputed => ClaimEventKind::Disputed,
534 ClaimEventKindArg::Corrected => ClaimEventKind::Corrected,
535 }
536 }
537}
538
539impl From<CorrectedClaimStateArg> for dotrepo_core::ClaimState {
540 fn from(value: CorrectedClaimStateArg) -> Self {
541 match value {
542 CorrectedClaimStateArg::Submitted => dotrepo_core::ClaimState::Submitted,
543 CorrectedClaimStateArg::InReview => dotrepo_core::ClaimState::InReview,
544 CorrectedClaimStateArg::Accepted => dotrepo_core::ClaimState::Accepted,
545 CorrectedClaimStateArg::Rejected => dotrepo_core::ClaimState::Rejected,
546 CorrectedClaimStateArg::Withdrawn => dotrepo_core::ClaimState::Withdrawn,
547 CorrectedClaimStateArg::Disputed => dotrepo_core::ClaimState::Disputed,
548 }
549 }
550}