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
//! R1 facade boundary — no external process is spawned outside a facade.
//!
//! Charter `docs/charters/2026-08-20-r1-facades.md` §4. This is the greppable
//! invariant of the pattern sweep made a committed test so it cannot rot: every
//! `Command::new(` under `pushkin-cli/src/**` must sit in a facade module
//! (`verbs/git.rs` for git, `verbs/external.rs` for other tools) or on a line
//! immediately preceded by a `// FACADE-EXEMPT:` marker naming why.
//!
//! A new legitimate exemption is a visible edit to the allow-set here — that
//! friction is the point (design §3.1, `docs/claude_refactor-patterns-design-2026-08-20.md`).
//!
//! Not a `.github/workflows/**` change (that tree is protected): the boundary is
//! enforced by this source-text scan, run like any other test.
use std::fs;
use std::path::{Path, PathBuf};
/// The `pushkin-cli/src` tree this test guards.
fn src_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("src")
}
/// Facade modules allowed to call `Command::new` directly — relative to `src/`.
const FACADE_FILES: &[&str] = &["verbs/git.rs", "verbs/external.rs"];
/// The exact marker an exempt spawn site must carry on the line above it.
const EXEMPT_MARKER: &str = "// FACADE-EXEMPT:";
/// The spawn constructor the boundary is about.
const SPAWN: &str = "Command::new(";
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().is_some_and(|ext| ext == "rs") {
out.push(path);
}
}
}
/// The contiguous run of `//` comment lines immediately above `index`
/// (nearest-first), stopping at the first non-comment line. This is where a
/// `FACADE-EXEMPT` marker may live, so a multi-line reason still counts.
fn preceding_comment_block<'a>(lines: &[&'a str], index: usize) -> Vec<&'a str> {
let mut block = Vec::new();
let mut cursor = index;
while cursor > 0 {
cursor -= 1;
let line = lines[cursor].trim_start();
if line.starts_with("//") {
block.push(lines[cursor]);
} else {
break;
}
}
block
}
/// Every `path:line` under `src/**` that calls `Command::new(` outside a facade
/// module and without a `FACADE-EXEMPT` marker in the comment block above it.
fn unrouted_spawn_sites() -> Vec<String> {
let root = src_root();
let mut files = Vec::new();
walk(&root, &mut files);
let mut offenders = Vec::new();
for file in files {
let relative = file
.strip_prefix(&root)
.unwrap_or(&file)
.to_string_lossy()
.replace('\\', "/");
if FACADE_FILES.contains(&relative.as_str()) {
continue;
}
let Ok(body) = fs::read_to_string(&file) else {
continue;
};
let lines: Vec<&str> = body.lines().collect();
for (index, line) in lines.iter().enumerate() {
if !line.contains(SPAWN) {
continue;
}
// The marker may sit on any line of the contiguous `//` comment
// block immediately above the spawn — multi-line reasons are normal.
let exempt = preceding_comment_block(&lines, index)
.iter()
.any(|line| line.contains(EXEMPT_MARKER));
if !exempt {
offenders.push(format!("{relative}:{}", index + 1));
}
}
}
offenders.sort();
offenders
}
#[test]
fn no_command_new_outside_a_facade_or_marked_exemption() {
let offenders = unrouted_spawn_sites();
assert!(
offenders.is_empty(),
"external processes must be spawned through a facade module \
(verbs/git.rs, verbs/external.rs) or carry a `{EXEMPT_MARKER}` marker \
on the line above. Un-routed sites:\n {}",
offenders.join("\n ")
);
}