scribe_selection/
selector.rs

1//! Code selector module that wraps the token budget selection pipeline.
2//! The selector consumes `FileInfo` records produced by the scanner and applies
3//! the multi-tier token budget logic from the legacy CLI.
4
5use crate::token_budget::apply_token_budget_selection;
6use scribe_core::{Config, FileInfo, Result};
7
8/// Input parameters for running the selector.
9#[derive(Debug, Clone)]
10pub struct SelectionCriteria<'a> {
11    /// Files produced by the scanner that are eligible for selection.
12    pub files: Vec<FileInfo>,
13    /// Maximum number of tokens that may be emitted across the selected files.
14    pub token_budget: usize,
15    /// Analyzer configuration that influences downstream decisions (e.g. demotion options).
16    pub config: &'a Config,
17}
18
19/// Result returned by the selector.
20#[derive(Debug, Clone)]
21pub struct SelectionResult {
22    /// Files selected within the provided budget (with content/token metadata loaded).
23    pub files: Vec<FileInfo>,
24    /// Total tokens consumed by the selected files.
25    pub total_tokens_used: usize,
26    /// Budget that was provided to the selector.
27    pub budget: usize,
28    /// Tokens left unused after selection completed.
29    pub unused_tokens: usize,
30    /// Total number of files that were considered when running selection.
31    pub total_files_considered: usize,
32}
33
34impl SelectionResult {
35    /// Convenience helper returning the relative paths of all selected files.
36    pub fn file_paths(&self) -> Vec<String> {
37        self.files.iter().map(|f| f.relative_path.clone()).collect()
38    }
39}
40
41pub struct CodeSelector;
42
43impl CodeSelector {
44    pub fn new() -> Self {
45        Self
46    }
47
48    pub async fn select(&self, criteria: SelectionCriteria<'_>) -> Result<SelectionResult> {
49        let total_files_considered = criteria.files.len();
50        let budget = criteria.token_budget;
51
52        let files =
53            apply_token_budget_selection(criteria.files, criteria.token_budget, criteria.config)
54                .await?;
55
56        let total_tokens_used: usize = files.iter().filter_map(|f| f.token_estimate).sum();
57        let unused_tokens = budget.saturating_sub(total_tokens_used);
58
59        Ok(SelectionResult {
60            files,
61            total_tokens_used,
62            budget,
63            unused_tokens,
64            total_files_considered,
65        })
66    }
67}
68
69impl Default for CodeSelector {
70    fn default() -> Self {
71        Self::new()
72    }
73}