Skip to main content

fallow_config/config/
similar_code.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4const fn default_threshold() -> f64 {
5    0.80
6}
7
8const fn default_min_lines() -> usize {
9    3
10}
11
12fn deserialize_threshold<'de, D>(deserializer: D) -> Result<f64, D::Error>
13where
14    D: Deserializer<'de>,
15{
16    let value = f64::deserialize(deserializer)?;
17    if !value.is_finite() || !(0.0..=1.0).contains(&value) {
18        return Err(serde::de::Error::custom(format!(
19            "similarCode.threshold must be finite and between 0 and 1 (got {value})"
20        )));
21    }
22    Ok(value)
23}
24
25fn deserialize_min_lines<'de, D>(deserializer: D) -> Result<usize, D::Error>
26where
27    D: Deserializer<'de>,
28{
29    let value = usize::deserialize(deserializer)?;
30    if value == 0 {
31        return Err(serde::de::Error::custom(
32            "similarCode.minLines must be at least 1",
33        ));
34    }
35    Ok(value)
36}
37
38/// Project-owned tuning for the explicit `fallow similar-code` workflow.
39///
40/// Provider identity, executable discovery, model setup, credentials, and
41/// consent are intentionally absent. Project configuration cannot select code
42/// destinations or authorize model downloads.
43#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
44#[serde(rename_all = "camelCase", deny_unknown_fields)]
45pub struct SimilarCodeConfig {
46    /// Minimum model-specific cosine similarity retained as an unverified
47    /// candidate. It is not a probability or refactor-safety verdict.
48    #[serde(
49        default = "default_threshold",
50        deserialize_with = "deserialize_threshold"
51    )]
52    #[schemars(range(min = 0.0, max = 1.0))]
53    pub threshold: f64,
54
55    /// Minimum source line count for a function to enter model inference.
56    #[serde(
57        default = "default_min_lines",
58        deserialize_with = "deserialize_min_lines"
59    )]
60    #[schemars(range(min = 1))]
61    pub min_lines: usize,
62
63    /// Additional project-root-relative globs excluded only from similar-code
64    /// extraction. Global `ignorePatterns` remain authoritative first.
65    #[serde(default)]
66    pub ignore: Vec<String>,
67}
68
69impl Default for SimilarCodeConfig {
70    fn default() -> Self {
71        Self {
72            threshold: default_threshold(),
73            min_lines: default_min_lines(),
74            ignore: Vec::new(),
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn defaults_are_candidate_only_calibration() {
85        let config = SimilarCodeConfig::default();
86        assert!((config.threshold - 0.80).abs() < f64::EPSILON);
87        assert_eq!(config.min_lines, 3);
88        assert!(config.ignore.is_empty());
89    }
90
91    #[test]
92    fn invalid_threshold_and_line_floor_fail_loud() {
93        let threshold = serde_json::from_str::<SimilarCodeConfig>(r#"{"threshold":1.1}"#)
94            .unwrap_err()
95            .to_string();
96        assert!(threshold.contains("between 0 and 1"));
97        let lines = serde_json::from_str::<SimilarCodeConfig>(r#"{"minLines":0}"#)
98            .unwrap_err()
99            .to_string();
100        assert!(lines.contains("at least 1"));
101    }
102}