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