1use anyhow::{Context, Result};
2use std::{fs, path::Path};
3
4pub(super) fn read_file(path: &str) -> String {
5 fs::read_to_string(path).unwrap_or_default()
6}
7
8pub(super) fn display_path(path: &Path) -> String {
9 path.strip_prefix(".")
10 .unwrap_or(path)
11 .to_string_lossy()
12 .trim_start_matches('/')
13 .to_string()
14}
15
16pub(super) fn contains_top_level_json_key(text: &str, key: &str) -> bool {
17 let pattern = format!("\"{key}\"");
20 text.lines().any(|line| {
21 let content = line.trim_start().trim_start_matches('{').trim_start();
22 content.starts_with(&pattern) && content[pattern.len()..].trim_start().starts_with(':')
23 })
24}
25
26pub(super) fn size_limit(path: &Path) -> Option<usize> {
27 if path == Path::new("crates/shared/auth/src/sqlite.rs") {
28 return Some(700);
31 }
32 if path == Path::new("crates/soma/application/src/provider_registry.rs") {
33 return Some(500);
36 }
37 if path == Path::new("crates/shared/provider-adapters/src/python/supervisor.rs")
38 || path == Path::new("crates/soma/application/src/providers/filesystem.rs")
39 {
40 return Some(500);
44 }
45 if path == Path::new("crates/shared/codex-app-server-client/src/rest/openapi/paths.rs") {
46 return Some(400);
49 }
50 if path == Path::new("xtask/src/generated_surfaces.rs") {
51 return Some(500);
54 }
55 if path == Path::new("xtask/src/scaffold.rs") {
56 return Some(600);
66 }
67 if path
68 .to_string_lossy()
69 .starts_with("xtask/src/scripts_lane_")
70 {
71 return Some(1000);
75 }
76
77 match path.extension().and_then(|ext| ext.to_str()) {
78 Some("rs") => Some(350),
79 Some("ts" | "tsx") => Some(300),
80 _ => None,
81 }
82}
83
84pub(super) fn is_size_exempt(path: &Path) -> bool {
85 let path = path.to_string_lossy();
86 if path.starts_with("crates/synapse/import/") {
90 return true;
91 }
92 if path.starts_with("docs/references/mcp/schema/") {
94 return true;
95 }
96 if path.contains("/src/generated/") {
107 return true;
108 }
109 false
110}
111
112pub(super) fn is_test_file(path: &Path) -> bool {
113 let path = path.to_string_lossy();
114 path.contains("/tests/")
115 || path.ends_with("_test.rs")
116 || path.ends_with("_tests.rs")
117 || path.ends_with("/tests.rs")
118 || path.ends_with(".test.ts")
119 || path.ends_with(".test.tsx")
120 || path.ends_with(".spec.ts")
121 || path.ends_with(".spec.tsx")
122 || path.contains("/__tests__/")
123}
124
125pub(super) fn effective_loc(path: &Path) -> Result<usize> {
126 let text =
127 fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
128 Ok(effective_loc_from_text(
129 &text,
130 path.extension().and_then(|ext| ext.to_str()) == Some("rs"),
131 ))
132}
133
134fn effective_loc_from_text(text: &str, strip_tests: bool) -> usize {
135 let text = if strip_tests {
136 strip_inline_test_module(text)
137 } else {
138 text
139 };
140 let mut count = 0usize;
141 let mut in_block = false;
142
143 for raw in text.lines() {
144 let mut line = raw.trim();
145 if line.is_empty() {
146 continue;
147 }
148 if in_block {
149 if let Some((_, after)) = line.split_once("*/") {
150 line = after.trim();
151 in_block = false;
152 if line.is_empty() {
153 continue;
154 }
155 } else {
156 continue;
157 }
158 }
159 if line.starts_with("//") {
160 continue;
161 }
162 if line.starts_with("/*") {
163 if let Some((_, after)) = line.split_once("*/") {
164 line = after.trim();
165 if line.is_empty() {
166 continue;
167 }
168 } else {
169 in_block = true;
170 continue;
171 }
172 }
173 count += 1;
174 }
175 count
176}
177
178fn strip_inline_test_module(text: &str) -> &str {
179 let lines = text.lines().collect::<Vec<_>>();
180 for index in 0..lines.len().saturating_sub(1) {
181 if lines[index].trim() != "#[cfg(test)]" {
182 continue;
183 }
184 let next = lines[index + 1].trim_start();
185 if next.starts_with("mod ") || next.starts_with("pub mod ") {
186 let byte_index = lines[..index].iter().map(|line| line.len() + 1).sum();
187 return &text[..byte_index];
188 }
189 }
190 text
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn effective_loc_ignores_comments_blanks_and_trailing_tests() {
199 let text = r#"
200// comment
201pub fn production() {}
202
203/* block
204 comment */
205pub fn more() {}
206
207#[cfg(test)]
208mod tests {
209 fn test_only() {}
210}
211"#;
212 assert_eq!(effective_loc_from_text(text, true), 2);
213 }
214
215 #[test]
216 fn effective_loc_counts_code_after_inline_block_comment() {
217 let text = "/* license */ pub fn one() {}\nlet two = 2;";
218 assert_eq!(effective_loc_from_text(text, false), 2);
219 }
220
221 #[test]
222 fn test_file_detection_accepts_both_common_rust_suffixes() {
223 assert!(is_test_file(Path::new("src/provider_test.rs")));
224 assert!(is_test_file(Path::new("src/provider_tests.rs")));
225 assert!(!is_test_file(Path::new("src/provider.rs")));
226 }
227
228 #[test]
229 fn top_level_json_key_detects_manifest_version_field() {
230 assert!(contains_top_level_json_key(
231 "{\n \"version\": \"1\"\n}",
232 "version"
233 ));
234 assert!(!contains_top_level_json_key(
235 "{\n \"not_version\": true\n}",
236 "version"
237 ));
238 }
239
240 #[test]
241 fn top_level_json_key_handles_compact_json() {
242 assert!(contains_top_level_json_key(
244 "{ \"version\": \"1\" }",
245 "version"
246 ));
247 assert!(contains_top_level_json_key(
248 "{\"version\":\"1\"}",
249 "version"
250 ));
251 assert!(!contains_top_level_json_key(
253 "{ \"name\": \"foo\" }",
254 "version"
255 ));
256 }
257
258 #[test]
259 fn size_limits_skip_exact_synapse_import_boundary() {
260 assert!(is_size_exempt(Path::new(
261 "crates/synapse/import/src/host_config.rs"
262 )));
263 assert!(!is_size_exempt(Path::new(
264 "crates/synapse/application/src/host_config.rs"
265 )));
266 }
267
268 #[test]
269 fn size_limits_skip_vendored_mcp_schema_references() {
270 assert!(is_size_exempt(Path::new(
271 "docs/references/mcp/schema/2025-11-25/schema.ts"
272 )));
273 assert!(!is_size_exempt(Path::new("apps/web/src/app/page.tsx")));
274 }
275
276 #[test]
277 fn size_limits_skip_checked_in_generator_output() {
278 assert!(is_size_exempt(Path::new(
279 "crates/shared/codex-app-server-client/clients/typescript/src/generated/openapi-types.ts"
280 )));
281 assert!(!is_size_exempt(Path::new(
284 "crates/soma/mcp/src/generated_schemas.rs"
285 )));
286 assert!(!is_size_exempt(Path::new(
287 "xtask/src/generated_surfaces.rs"
288 )));
289 }
290
291 #[test]
292 fn transitional_xtask_modules_warn_before_hard_failing() {
293 assert_eq!(size_limit(Path::new("xtask/src/scaffold.rs")), Some(600));
294 }
295
296 #[test]
297 fn split_xtask_modules_lose_their_transitional_exemption() {
298 assert_eq!(
302 size_limit(Path::new("xtask/src/rmcp_release_monitor.rs")),
303 Some(350)
304 );
305 assert_eq!(
306 size_limit(Path::new("xtask/src/rmcp_release_monitor/schema.rs")),
307 Some(350)
308 );
309 }
310}