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