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
use clap::{Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
name = "svccat",
version,
about = "Service catalog drift detection for multi-service repositories",
long_about = "\
svccat reads your declared service manifest and compares it against what \
actually exists in the repo, flagging drift before it becomes toil.\n\n\
Typical workflow:\n \
svccat check # inspect drift in the current repo\n \
svccat check --fail-on-drift # gate CI on zero drift\n \
svccat graph # emit a Mermaid diagram for docs\n \
svccat export --format json # machine-readable catalog snapshot"
)]
pub struct Cli {
/// Repository root (defaults to current directory)
#[arg(short, long, global = true)]
pub root: Option<PathBuf>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Check declared services against the repo and report drift
Check {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = OutputFormat::Terminal)]
format: OutputFormat,
/// Exit with code 1 when drift is detected (useful in CI)
#[arg(long)]
fail_on_drift: bool,
/// Ping each service URL and report reachability
#[arg(long)]
ping: bool,
/// Glob patterns to exclude from discovery (repeatable, e.g. --ignore "examples/*")
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Only check services owned by this team (matches the `team:` field)
#[arg(long, value_name = "TEAM")]
team: Option<String>,
/// Compare against the manifest at this git ref; show only new / resolved drift
///
/// Example: --since HEAD~1 or --since main
#[arg(long, value_name = "GIT_REF")]
since: Option<String>,
},
/// Generate a Mermaid or Markdown view of the service catalog
Graph {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = GraphFormat::Mermaid)]
format: GraphFormat,
/// Only include services owned by this team; cross-team depends_on targets
/// are shown as external nodes
#[arg(long, value_name = "TEAM")]
team: Option<String>,
},
/// Export the full service catalog with drift summary
Export {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Export format
#[arg(short, long, value_enum, default_value_t = ExportFormat::Json)]
format: ExportFormat,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
},
/// Scaffold a services.yaml by auto-discovering services in the repo
Init {
/// Where to write the manifest (default: services.yaml in repo root)
#[arg(short, long)]
output: Option<PathBuf>,
/// Overwrite an existing manifest file
#[arg(long)]
force: bool,
},
/// Compare two svccat export JSON snapshots and show what changed
///
/// Generate snapshots with: svccat export --format json > snapshot.json
Diff {
/// Path to the older snapshot (JSON)
before: std::path::PathBuf,
/// Path to the newer snapshot (JSON)
after: std::path::PathBuf,
},
/// Watch the manifest and service directories for changes and re-run drift checks
///
/// Continuously monitors the manifest file and service directories.
/// Re-runs drift analysis whenever a file change is detected and prints
/// a timestamped report. Press Ctrl-C to stop.
Watch {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Exit with code 1 when drift is detected on the first check
#[arg(long)]
fail_on_drift: bool,
/// Only watch services owned by this team
#[arg(long, value_name = "TEAM")]
team: Option<String>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
},
/// Generate a full ownership and drift report
///
/// Outputs a per-team breakdown of every service with language, platform, oncall,
/// and drift status, plus a dependency summary and full drift details.
Report {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = ReportFormat::Markdown)]
format: ReportFormat,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
},
/// Validate the manifest for structural issues
///
/// Checks for duplicate service names, blank names, self-referential
/// depends_on entries, duplicate depends_on entries, and unknown manifest versions.
Lint {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
},
/// Print shell completion script to stdout
///
/// Source the output to enable tab completion, e.g.:
/// source <(svccat completions bash)
/// svccat completions zsh > ~/.zfunc/_svccat
Completions {
/// Shell to generate completions for
shell: Shell,
},
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum OutputFormat {
Terminal,
Json,
Sarif,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum ReportFormat {
Markdown,
Html,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum GraphFormat {
Mermaid,
Markdown,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum ExportFormat {
Json,
Markdown,
}