projectatlas_core/
outline.rs

1//! Purpose: Build compressed file outlines for agent context.
2
3use serde::{Deserialize, Serialize};
4
5/// Compact file outline returned before exact source is requested.
6#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
7pub struct FileOutline {
8    /// Repository-relative path.
9    pub path: String,
10    /// Optional language or file family.
11    pub language: Option<String>,
12    /// Total line count.
13    pub line_count: usize,
14    /// First non-empty lines capped for token efficiency.
15    pub preview_lines: Vec<String>,
16    /// Estimated tokens for the full file.
17    pub estimated_full_tokens: usize,
18    /// Estimated tokens for this outline.
19    pub estimated_outline_tokens: usize,
20}
21
22/// Build a compact outline from file content.
23#[must_use]
24pub fn build_outline(
25    path: &str,
26    language: Option<String>,
27    content: &str,
28    limit: usize,
29) -> FileOutline {
30    let mut preview_lines = Vec::new();
31    for line in content
32        .lines()
33        .map(str::trim)
34        .filter(|line| !line.is_empty())
35    {
36        preview_lines.push(line.to_string());
37        if preview_lines.len() >= limit {
38            break;
39        }
40    }
41    let estimated_full_tokens = estimate_tokens(content);
42    let outline_payload = preview_lines.join("\n");
43    let estimated_outline_tokens = estimate_tokens(&outline_payload);
44    FileOutline {
45        path: path.to_string(),
46        language,
47        line_count: content.lines().count(),
48        preview_lines,
49        estimated_full_tokens,
50        estimated_outline_tokens,
51    }
52}
53
54/// Estimate tokens from text using a stable lightweight heuristic.
55#[must_use]
56pub fn estimate_tokens(text: &str) -> usize {
57    let chars = text.chars().count();
58    if chars == 0 { 0 } else { chars.div_ceil(4) }
59}