1pub(crate) mod agents_conformance;
2pub(crate) mod bench;
3pub(crate) mod canon;
4pub(crate) mod check;
5pub(crate) mod codemod;
6pub(crate) mod config_cmd;
7pub(crate) mod conformance_helper;
8pub(crate) mod connect;
9pub(crate) mod connector;
10pub(crate) mod connector_schema_codegen;
11pub(crate) mod contracts;
12pub(crate) mod counterfactual;
13pub(crate) mod crystallize;
14pub mod demo;
15pub(crate) mod dev;
16pub(crate) mod diagnostics_catalog;
17pub(crate) mod dispatch_explain;
18pub(crate) mod doc;
19pub(crate) mod doctor;
20pub(crate) mod dump_highlight_keywords;
21pub(crate) mod dump_protocol_artifacts;
22pub(crate) mod dump_trigger_quickref;
23pub mod eval_coding_agent;
24pub(crate) mod eval_coding_agent_preset;
25pub mod eval_context;
26pub(crate) mod eval_model_selector;
27pub mod eval_prompt;
28pub(crate) mod eval_prompt_context;
29pub(crate) mod eval_scope_triage;
30pub mod eval_skill_gate;
31pub(crate) mod eval_tool_calls;
32pub(crate) mod explain;
33pub(crate) mod fix;
34pub mod flow;
35pub(crate) mod graph;
36pub(crate) mod guard;
37pub(crate) mod hardware;
38pub(crate) mod host;
39pub(crate) mod init;
40pub(crate) mod json_schemas;
41pub(crate) mod local;
42pub(crate) mod local_readiness;
43pub(crate) mod mcp;
44pub(crate) mod merge_captain;
45pub(crate) mod merge_captain_mock;
46pub(crate) mod models;
47pub mod orchestrator;
48pub mod pack;
49pub(crate) mod package_scaffold;
50pub(crate) mod parse_tokens;
51pub mod persona;
52pub mod persona_activation;
53pub mod persona_dispatch;
54pub mod persona_doctor;
55pub mod persona_prompt;
56pub mod persona_scaffold;
57pub mod persona_supervision;
58pub(crate) mod pg_codegen;
59pub mod playground;
60pub(crate) mod portal;
61pub mod precompile;
62pub(crate) mod protocol_conformance;
63pub(crate) mod provider;
64pub(crate) mod provider_capabilities;
65pub(crate) mod provider_limits;
66pub(crate) mod provider_report;
67pub(crate) mod provider_support;
68pub(crate) mod providers;
69pub(crate) mod quickstart;
70pub(crate) mod repl;
71pub(crate) mod replay;
72pub(crate) mod routes;
73pub(crate) mod rule;
74pub(crate) mod rules_cli;
75pub mod run;
76pub(crate) mod scaffold_common;
77pub(crate) mod scan;
78pub(crate) mod serve;
79pub(crate) mod session;
80pub(crate) mod skill;
81pub(crate) mod skills;
82pub(crate) mod supervisor;
83pub(crate) mod test;
84pub mod test_bench;
85pub(crate) mod test_worker;
86pub mod time;
87pub(crate) mod tool;
88pub(crate) mod tool_mode_parity;
89pub(crate) mod trace;
90pub mod trigger;
91pub(crate) mod trust;
92pub(crate) mod try_cmd;
93pub(crate) mod upgrade;
94pub(crate) mod usage;
95pub(crate) mod viz;
96pub(crate) mod workflow;
97
98use std::path::{Path, PathBuf};
99
100use ignore::WalkBuilder;
101
102pub(crate) fn nearest_rank_percentile(sorted: &[u64], quantile: f64) -> Option<u64> {
106 if sorted.is_empty() {
107 return None;
108 }
109 let rank = ((sorted.len() as f64 * quantile.clamp(0.0, 1.0)).ceil() as usize)
110 .saturating_sub(1)
111 .min(sorted.len() - 1);
112 sorted.get(rank).copied()
113}
114
115const GENERATED_SOURCE_WALK_DIRS: &[&str] = &[
116 ".burin",
117 ".build",
118 ".claude",
119 ".codex",
120 ".git",
121 ".harn",
122 ".harn-runs",
123 ".next",
124 ".svelte-kit",
125 ".turbo",
126 ".venv",
127 "build",
128 "coverage",
129 "dist",
130 "node_modules",
131 "target",
132];
133
134pub(crate) fn should_skip_recursive_source_dir(dir: &Path) -> bool {
135 let Some(name) = dir.file_name().and_then(|name| name.to_str()) else {
136 return false;
137 };
138 GENERATED_SOURCE_WALK_DIRS.contains(&name) || name.starts_with(".harn-")
139}
140
141pub(crate) fn should_skip_recursive_source_file(file: &Path) -> bool {
142 let Some(name) = file.file_name().and_then(|name| name.to_str()) else {
143 return false;
144 };
145 name.starts_with(".harn-")
146}
147
148#[derive(Default)]
149pub(crate) struct SourceTargets {
150 pub(crate) harn: Vec<PathBuf>,
151 pub(crate) prompts: Vec<PathBuf>,
152}
153
154impl SourceTargets {
155 fn sort_and_dedup(&mut self) {
156 self.harn.sort();
157 self.harn.dedup();
158 self.prompts.sort();
159 self.prompts.dedup();
160 }
161}
162
163pub(crate) fn collect_source_targets(
164 targets: &[&str],
165 include_harn: bool,
166 include_prompts: bool,
167) -> SourceTargets {
168 let mut files = SourceTargets::default();
169 for target in targets {
170 let path = Path::new(target);
171 if path.is_dir() {
172 collect_source_targets_dir(path, include_harn, include_prompts, &mut files);
173 } else {
174 push_matching_source_target(path, include_harn, include_prompts, false, &mut files);
175 }
176 }
177 files.sort_and_dedup();
178 files
179}
180
181fn collect_source_targets_dir(
182 dir: &Path,
183 include_harn: bool,
184 include_prompts: bool,
185 files: &mut SourceTargets,
186) {
187 let root = dir.to_path_buf();
188 let mut walker = WalkBuilder::new(dir);
189 walker
190 .hidden(false)
191 .ignore(true)
192 .git_ignore(true)
193 .git_global(true)
194 .git_exclude(true)
195 .require_git(false)
196 .parents(true)
197 .follow_links(false)
198 .filter_entry(move |entry| {
199 let path = entry.path();
200 if path == root {
201 return true;
202 }
203 if path.is_dir() {
204 !should_skip_recursive_source_dir(path)
205 } else {
206 !should_skip_recursive_source_file(path)
207 }
208 });
209
210 for entry in walker.build().filter_map(Result::ok) {
211 let path = entry.path();
212 if entry
213 .file_type()
214 .is_some_and(|file_type| file_type.is_file())
215 {
216 push_matching_source_target(path, include_harn, include_prompts, true, files);
217 }
218 }
219}
220
221fn push_matching_source_target(
222 path: &Path,
223 include_harn: bool,
224 include_prompts: bool,
225 honor_skip_marker: bool,
226 files: &mut SourceTargets,
227) {
228 if include_prompts && is_harn_prompt_file(path) {
229 files.prompts.push(path.to_path_buf());
230 } else if include_harn && is_harn_program_file(path) {
231 let skip_marker = path.with_extension("conformance-skip");
232 if !honor_skip_marker || !skip_marker.exists() {
233 files.harn.push(path.to_path_buf());
234 }
235 }
236}
237
238pub(crate) fn is_harn_program_file(path: &Path) -> bool {
239 let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
240 return false;
241 };
242 if name.ends_with(".harn.prompt") || name.ends_with(".prompt") {
243 return false;
244 }
245 name.ends_with(".harn") || name.ends_with(".harn.txt")
246}
247
248pub(crate) fn is_harn_prompt_file(path: &Path) -> bool {
249 let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
250 return false;
251 };
252 name.ends_with(".harn.prompt") || name.ends_with(".prompt")
253}
254
255pub(crate) fn collect_harn_files(dir: &Path, out: &mut Vec<PathBuf>) {
260 if let Ok(entries) = std::fs::read_dir(dir) {
261 let mut entries: Vec<_> = entries.filter_map(|e| e.ok()).collect();
262 entries.sort_by_key(|e| e.path());
263 for entry in entries {
264 let path = entry.path();
265 if path.is_dir() {
266 if should_skip_recursive_source_dir(&path) {
267 continue;
268 }
269 collect_harn_files(&path, out);
270 } else if should_skip_recursive_source_file(&path) {
271 continue;
272 } else if path.extension().is_some_and(|ext| ext == "harn") {
273 let skip_marker = path.with_extension("conformance-skip");
274 if skip_marker.exists() {
275 continue;
276 }
277 out.push(path);
278 }
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::nearest_rank_percentile;
286
287 #[test]
288 fn percentile_handles_empty_and_bounds() {
289 assert_eq!(nearest_rank_percentile(&[], 0.5), None);
290 let data = [10u64, 20, 30, 40, 50];
291 assert_eq!(nearest_rank_percentile(&data, 0.0), Some(10));
292 assert_eq!(nearest_rank_percentile(&data, 0.5), Some(30));
293 assert_eq!(nearest_rank_percentile(&data, 1.0), Some(50));
294 assert_eq!(nearest_rank_percentile(&data, 2.0), Some(50));
296 assert_eq!(nearest_rank_percentile(&[7u64], 0.99), Some(7));
297 }
298}