1#![forbid(unsafe_code)]
2#![allow(clippy::cast_lossless)]
3#![allow(clippy::cast_precision_loss)]
4#![allow(clippy::implicit_hasher)]
5#![allow(clippy::missing_const_for_fn)]
6#![allow(clippy::missing_errors_doc)]
7#![allow(clippy::must_use_candidate)]
8#![allow(clippy::needless_pass_by_value)]
9#![allow(clippy::struct_excessive_bools)]
10#![allow(clippy::suboptimal_flops)]
11#![allow(clippy::too_many_lines)]
12
13pub mod agents;
14pub mod analysis;
15pub mod cli;
16pub mod error;
17pub mod export_md;
18pub mod export_pages;
19pub mod export_sqlite;
20pub mod loader;
21pub mod model;
22pub mod pages_wizard;
23pub mod robot;
24pub mod tui;
25pub mod viewer_assets;
26
27pub use error::{BvrError, Result};
28
29#[cfg(test)]
30mod version_guard {
31 use std::path::Path;
38
39 #[test]
41 fn no_hardcoded_version_strings_in_production_code() {
42 let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
43 let pkg_version = env!("CARGO_PKG_VERSION");
44
45 let suspicious = [
47 format!("\"{}\"", pkg_version), "\"1.0.0\"".to_string(),
49 "\"1.0\"".to_string(),
50 "\"2.0.0\"".to_string(),
51 "\"2.0\"".to_string(),
52 ];
53
54 let allowlist: &[&str] = &[
56 "agents.rs", "viewer_assets.rs", ];
59
60 let mut violations = Vec::new();
61
62 for entry in walkdir(src_dir) {
63 let path = entry.as_path();
64 if !path.extension().is_some_and(|ext| ext == "rs") {
65 continue;
66 }
67
68 let filename = path.file_name().unwrap_or_default().to_string_lossy();
69 if allowlist.iter().any(|a| filename.as_ref() == *a) {
70 continue;
71 }
72
73 let content = match std::fs::read_to_string(path) {
74 Ok(c) => c,
75 Err(_) => continue,
76 };
77
78 let prod_code = content.split("#[cfg(test)]").next().unwrap_or(&content);
80
81 for pattern in &suspicious {
82 for (line_no, line) in prod_code.lines().enumerate() {
83 let trimmed = line.trim();
84 if trimmed.starts_with("//") || trimmed.starts_with("///") {
86 continue;
87 }
88 if line.contains(pattern.as_str()) {
89 violations.push(format!(
90 "{}:{}: contains {} — use env!(\"CARGO_PKG_VERSION\") instead",
91 path.display(),
92 line_no + 1,
93 pattern,
94 ));
95 }
96 }
97 }
98 }
99
100 assert!(
101 violations.is_empty(),
102 "Found hard-coded version strings in production code:\n{}",
103 violations.join("\n")
104 );
105 }
106
107 fn walkdir(dir: std::path::PathBuf) -> Vec<std::path::PathBuf> {
109 let mut files = Vec::new();
110 if let Ok(entries) = std::fs::read_dir(&dir) {
111 for entry in entries.flatten() {
112 let path = entry.path();
113 if path.is_dir() {
114 files.extend(walkdir(path));
115 } else {
116 files.push(path);
117 }
118 }
119 }
120 files
121 }
122}