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
//! What:
//! The fixed registry of policies compiled into the native wrapper.
//! Why:
//! Repository JSONC can only tune shipped policies;
//! it can never name code to run.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! // const POLICY_REGISTRY = [{ name: 'require-root', defaultSeverity: 'error', ... }] as const;
//! ```
/// What:
/// `enum Severity` is a closed set of three named values.
/// `#[derive(...)]` asks the compiler to generate copying,
/// printing and `==`.
/// Why:
/// A closed set makes an unhandled severity a compile error,
/// unlike a free string.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type Severity = 'off' | 'warn' | 'error';
/// ```
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Severity {
/// The policy does not run.
Off,
/// Findings are reported without blocking the Git command.
Warn,
/// Findings block the Git command.
Error,
}
/// What:
/// One variant per shipped policy;
/// the variant is the policy's identity in Rust.
/// Why:
/// Typed identities let options and settings be looked up without string comparison.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type PolicyId = 'require-root' | 'linked-worktree-only' | ... | 'security/forbidden-strings';
/// ```
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyId {
/// `require-root`:
/// repository-root working directory requirement.
RequireRoot,
/// `linked-worktree-only`:
/// destructive commands stay in linked worktrees.
LinkedWorktreeOnly,
/// `branch-worktree-only`:
/// branch creation goes through `git worktree add`.
BranchWorktreeOnly,
/// `add-explicit`:
/// bulk staging patterns are rejected.
AddExplicit,
/// `final-newline`:
/// text candidates end with one line feed.
FinalNewline,
/// `markdown/autofix`:
/// commit-time Markdown normalization.
MarkdownAutofix,
/// `mono/forbidden-root-context`:
/// no root `CONTEXT.md` enters a commit.
ForbiddenRootContext,
/// `mono/dependent-version-bump`:
/// dependent package versions follow a bumped package.
DependentVersionBump,
/// `security/forbidden-strings`:
/// candidate bytes are scanned by the bundled scanner.
ForbiddenStrings,
}
/// What:
/// `struct PolicyDescriptor` is one registry row.
/// `&'static str` is a borrowed string baked into the executable for its whole run
/// (`'static` is that "lives forever" lifetime).
/// Sibling:
/// `String`,
/// an owned copy.
/// `bool` is `true`/`false`,
/// exactly TS `boolean`.
/// Why:
/// Registry text is compiled in and never changes,
/// so borrowing forever is free,
/// while `String` would allocate at startup for no gain.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type PolicyDescriptor = { id: PolicyId; name: string; defaultSeverity: Severity;
/// warnSafe: boolean; acceptsOptions: boolean; offUnlessListed: boolean };
/// ```
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PolicyDescriptor {
/// Typed identity used by Rust callers.
pub id: PolicyId,
/// Stable configuration key and JSONL `policyId`.
pub name: &'static str,
/// The incumbent policy definition's `defaultSeverity`.
/// A built-in policy the
/// repository configuration does not name runs at this severity.
pub default_severity: Severity,
/// Whether `warn` keeps the policy's protection (an unsafe `warn` earns a warning event).
pub warn_safe: bool,
/// Whether the `["severity", { ... }]` form is accepted for this policy.
pub accepts_options: bool,
/// Whether the policy runs only where `cli-git.config.jsonc` names it.
/// The four
/// policies that plugins used to provide are off everywhere else,
/// with or without
/// a configuration file (decided 2026-10-05).
pub off_unless_listed: bool,
}
/// What:
/// `pub const POLICY_REGISTRY: &[PolicyDescriptor]` is a compiled-in,
/// read-only list.
/// `&[T]` is a borrowed view of an array;
/// siblings are `Vec<T>` (owned,
/// growable)
/// and `[T; N]` (fixed length in the type).
/// Why:
/// Execution and configuration order must be identical on every run;
/// a borrowed
/// constant needs no allocation and cannot be reordered at runtime.
/// Every default is the incumbent policy definition's own `defaultSeverity`.
/// The four formerly plugin-provided policies are additionally off unless the
/// repository configuration names them,
/// because the incumbent only ran them in
/// repositories whose configuration registered their plugin.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// export const POLICY_REGISTRY: readonly PolicyDescriptor[] = [ ... ];
/// ```
pub const POLICY_REGISTRY: &[PolicyDescriptor] = &[
PolicyDescriptor {
id: PolicyId::RequireRoot,
name: "require-root",
default_severity: Severity::Error,
warn_safe: false,
accepts_options: false,
off_unless_listed: false,
},
PolicyDescriptor {
id: PolicyId::LinkedWorktreeOnly,
name: "linked-worktree-only",
default_severity: Severity::Error,
warn_safe: false,
accepts_options: false,
off_unless_listed: false,
},
PolicyDescriptor {
id: PolicyId::BranchWorktreeOnly,
name: "branch-worktree-only",
default_severity: Severity::Error,
warn_safe: true,
accepts_options: false,
off_unless_listed: false,
},
PolicyDescriptor {
id: PolicyId::AddExplicit,
name: "add-explicit",
default_severity: Severity::Error,
warn_safe: false,
accepts_options: false,
off_unless_listed: false,
},
PolicyDescriptor {
id: PolicyId::FinalNewline,
name: "final-newline",
default_severity: Severity::Warn,
warn_safe: true,
accepts_options: false,
off_unless_listed: false,
},
PolicyDescriptor {
id: PolicyId::MarkdownAutofix,
name: "markdown/autofix",
default_severity: Severity::Warn,
warn_safe: true,
accepts_options: true,
off_unless_listed: true,
},
PolicyDescriptor {
id: PolicyId::ForbiddenRootContext,
name: "mono/forbidden-root-context",
default_severity: Severity::Error,
warn_safe: true,
accepts_options: false,
off_unless_listed: true,
},
PolicyDescriptor {
id: PolicyId::DependentVersionBump,
name: "mono/dependent-version-bump",
default_severity: Severity::Error,
warn_safe: false,
accepts_options: false,
off_unless_listed: true,
},
PolicyDescriptor {
id: PolicyId::ForbiddenStrings,
name: "security/forbidden-strings",
default_severity: Severity::Error,
warn_safe: false,
accepts_options: true,
off_unless_listed: true,
},
];
/// What:
/// Find a registry row by its configuration key.
/// `Option<T>` is Rust's "value or nothing":
/// `Some(row)` or `None`.
/// `&'static PolicyDescriptor` borrows a row that lives as long as the program.
/// Why:
/// An unknown policy ID must be reported,
/// never treated as a new policy.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function policyByName(name: string): PolicyDescriptor | undefined;
/// ```
pub fn policy_by_name(name: &str) -> Option<&'static PolicyDescriptor> {
// What: `usize` is the index type of every Rust list (siblings `u32`, `i64`);
// `&POLICY_REGISTRY[index]` borrows one row without copying it.
// Why: Nine rows make a linear scan cheaper than building a map, and an indexed
// `while` loop needs no callback to stop at the first match.
//
// In TS you'd write (pseudocode):
// ```ts
// for (let index = 0; index < POLICY_REGISTRY.length; index += 1) { ... }
// ```
let mut index: usize = 0;
while index < POLICY_REGISTRY.len() {
let descriptor: &PolicyDescriptor = &POLICY_REGISTRY[index];
index += 1;
if descriptor.name == name {
// What: `Some(descriptor)` wraps the found row in the "present" variant.
// Why: The caller must handle absence explicitly; there is no `undefined`.
//
// In TS you'd write (pseudocode):
// ```ts
// return descriptor;
// ```
return Some(descriptor);
}
}
// `None` is the "absent" variant: no shipped policy has this name.
return None;
}
/// What:
/// Find the registry row for a typed identity.
/// Why:
/// Callers holding a `PolicyId` need its name,
/// default and warn-safety.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function policyDescriptor(id: PolicyId): PolicyDescriptor;
/// ```
pub fn policy_descriptor(id: PolicyId) -> &'static PolicyDescriptor {
// `for descriptor in POLICY_REGISTRY` borrows each row in order; no index is needed
// because the row itself is returned, not wrapped in `Some(...)`.
for descriptor in POLICY_REGISTRY {
if descriptor.id == id {
return descriptor;
}
}
// What: `unreachable!` stops the program with this message if ever executed.
// The `!` marks a macro (compiler-expanded code), not negation.
// Why: Every `PolicyId` variant has a row; the registry test proves it, so
// reaching here means the registry itself was edited incorrectly.
//
// In TS you'd write (pseudocode):
// ```ts
// throw new Error('unreachable: every PolicyId has a registry row');
// ```
unreachable!("every PolicyId variant has a POLICY_REGISTRY row");
}
/// What:
/// Translate a configuration word into a typed severity.
/// Why:
/// Only the three documented spellings are settings;
/// anything else is an error
/// the caller reports with the offending key.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function severityFromName(name: string): Severity | undefined;
/// ```
pub fn severity_from_name(name: &str) -> Option<Severity> {
if name == "off" {
return Some(Severity::Off);
}
if name == "warn" {
return Some(Severity::Warn);
}
if name == "error" {
return Some(Severity::Error);
}
return None;
}
/// What:
/// Translate a typed severity back to its configuration and JSONL spelling.
/// Why:
/// Events and diagnostics print the same words the configuration accepts.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function severityName(severity: Severity): string;
/// ```
pub fn severity_name(severity: Severity) -> &'static str {
if severity == Severity::Off {
return "off";
}
if severity == Severity::Warn {
return "warn";
}
return "error";
}
/// What:
/// `#[cfg(test)]` compiles the next item only for `cargo test`;
/// `#[path = "..."]` names the sibling file holding the module body.
/// Why:
/// Registry controls stay out of the release executable.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// // policy_registry.unit.test.ts lives beside this file.
/// ```
#[cfg(test)]
#[path = "policy_registry_tests.rs"]
mod tests;