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(400);
36 }
37 if path == Path::new("xtask/src/generated_surfaces.rs") {
38 return Some(500);
41 }
42 if path == Path::new("xtask/src/rmcp_release_monitor.rs")
43 || path == Path::new("xtask/src/scaffold.rs")
44 {
45 return Some(600);
49 }
50 if path
51 .to_string_lossy()
52 .starts_with("xtask/src/scripts_lane_")
53 {
54 return Some(1000);
58 }
59
60 match path.extension().and_then(|ext| ext.to_str()) {
61 Some("rs") => Some(350),
62 Some("ts" | "tsx") => Some(300),
63 _ => None,
64 }
65}
66
67pub(super) fn is_size_exempt(path: &Path) -> bool {
68 let path = path.to_string_lossy();
69 if path.starts_with("docs/references/mcp/schema/") {
71 return true;
72 }
73 if path.contains("/src/generated/") {
84 return true;
85 }
86 false
87}
88
89pub(super) fn is_test_file(path: &Path) -> bool {
90 let path = path.to_string_lossy();
91 path.contains("/tests/")
92 || path.ends_with("_test.rs")
93 || path.ends_with("/tests.rs")
94 || path.ends_with(".test.ts")
95 || path.ends_with(".test.tsx")
96 || path.ends_with(".spec.ts")
97 || path.ends_with(".spec.tsx")
98 || path.contains("/__tests__/")
99}
100
101pub(super) fn effective_loc(path: &Path) -> Result<usize> {
102 let text =
103 fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
104 Ok(effective_loc_from_text(
105 &text,
106 path.extension().and_then(|ext| ext.to_str()) == Some("rs"),
107 ))
108}
109
110fn effective_loc_from_text(text: &str, strip_tests: bool) -> usize {
111 let text = if strip_tests {
112 strip_inline_test_module(text)
113 } else {
114 text
115 };
116 let mut count = 0usize;
117 let mut in_block = false;
118
119 for raw in text.lines() {
120 let mut line = raw.trim();
121 if line.is_empty() {
122 continue;
123 }
124 if in_block {
125 if let Some((_, after)) = line.split_once("*/") {
126 line = after.trim();
127 in_block = false;
128 if line.is_empty() {
129 continue;
130 }
131 } else {
132 continue;
133 }
134 }
135 if line.starts_with("//") {
136 continue;
137 }
138 if line.starts_with("/*") {
139 if let Some((_, after)) = line.split_once("*/") {
140 line = after.trim();
141 if line.is_empty() {
142 continue;
143 }
144 } else {
145 in_block = true;
146 continue;
147 }
148 }
149 count += 1;
150 }
151 count
152}
153
154fn strip_inline_test_module(text: &str) -> &str {
155 let lines = text.lines().collect::<Vec<_>>();
156 for index in 0..lines.len().saturating_sub(1) {
157 if lines[index].trim() != "#[cfg(test)]" {
158 continue;
159 }
160 let next = lines[index + 1].trim_start();
161 if next.starts_with("mod ") || next.starts_with("pub mod ") {
162 let byte_index = lines[..index].iter().map(|line| line.len() + 1).sum();
163 return &text[..byte_index];
164 }
165 }
166 text
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn effective_loc_ignores_comments_blanks_and_trailing_tests() {
175 let text = r#"
176// comment
177pub fn production() {}
178
179/* block
180 comment */
181pub fn more() {}
182
183#[cfg(test)]
184mod tests {
185 fn test_only() {}
186}
187"#;
188 assert_eq!(effective_loc_from_text(text, true), 2);
189 }
190
191 #[test]
192 fn effective_loc_counts_code_after_inline_block_comment() {
193 let text = "/* license */ pub fn one() {}\nlet two = 2;";
194 assert_eq!(effective_loc_from_text(text, false), 2);
195 }
196
197 #[test]
198 fn top_level_json_key_detects_manifest_version_field() {
199 assert!(contains_top_level_json_key(
200 "{\n \"version\": \"1\"\n}",
201 "version"
202 ));
203 assert!(!contains_top_level_json_key(
204 "{\n \"not_version\": true\n}",
205 "version"
206 ));
207 }
208
209 #[test]
210 fn top_level_json_key_handles_compact_json() {
211 assert!(contains_top_level_json_key(
213 "{ \"version\": \"1\" }",
214 "version"
215 ));
216 assert!(contains_top_level_json_key(
217 "{\"version\":\"1\"}",
218 "version"
219 ));
220 assert!(!contains_top_level_json_key(
222 "{ \"name\": \"foo\" }",
223 "version"
224 ));
225 }
226
227 #[test]
228 fn size_limits_skip_vendored_mcp_schema_references() {
229 assert!(is_size_exempt(Path::new(
230 "docs/references/mcp/schema/2025-11-25/schema.ts"
231 )));
232 assert!(!is_size_exempt(Path::new("apps/web/src/app/page.tsx")));
233 }
234
235 #[test]
236 fn size_limits_skip_checked_in_generator_output() {
237 assert!(is_size_exempt(Path::new(
238 "crates/shared/codex-app-server-client/clients/typescript/src/generated/openapi-types.ts"
239 )));
240 assert!(!is_size_exempt(Path::new(
243 "crates/soma/mcp/src/generated_schemas.rs"
244 )));
245 assert!(!is_size_exempt(Path::new(
246 "xtask/src/generated_surfaces.rs"
247 )));
248 }
249
250 #[test]
251 fn transitional_xtask_modules_warn_before_hard_failing() {
252 assert_eq!(
253 size_limit(Path::new("xtask/src/rmcp_release_monitor.rs")),
254 Some(600)
255 );
256 assert_eq!(size_limit(Path::new("xtask/src/scaffold.rs")), Some(600));
257 }
258}