use anyhow::Result;
use tracing::debug;
use crate::context::{
assembler::ChunkMetadata,
file_loader::FileLoader,
strategies::default::DefaultAssemblyStrategy,
strategy::AssemblyStrategy,
token_counter::TokenCounter,
types::{ContextBundle, ContextItem, ExpandOptions, LineRange},
};
use crate::db::traits::StoreChunks;
use crate::db::traits::StoreGraph;
use crate::db::SqliteStore;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct RustConfig {
pub include_cargo_toml: bool,
pub include_trait_impls: bool,
pub include_module_files: bool,
pub max_trait_impls: usize,
}
impl Default for RustConfig {
fn default() -> Self {
Self {
include_cargo_toml: true,
include_trait_impls: true,
include_module_files: true,
max_trait_impls: 3,
}
}
}
pub struct RustAssemblyStrategy {
store: Arc<SqliteStore>,
default: DefaultAssemblyStrategy,
config: RustConfig,
token_counter: TokenCounter,
}
impl RustAssemblyStrategy {
pub fn new(store: Arc<SqliteStore>) -> Self {
Self::with_config(store, RustConfig::default())
}
pub fn with_config(store: Arc<SqliteStore>, config: RustConfig) -> Self {
Self {
default: DefaultAssemblyStrategy::new(Arc::clone(&store)),
store,
config,
token_counter: TokenCounter::new(),
}
}
async fn add_cargo_toml(
&self,
bundle: &mut ContextBundle,
metadata: &ChunkMetadata,
budget: usize,
) -> Result<()> {
if !self.config.include_cargo_toml || bundle.total_tokens >= budget {
return Ok(());
}
let cargo_paths = ["Cargo.toml", "../Cargo.toml", "../../Cargo.toml"];
let file_loader = FileLoader::new(&metadata.worktree_path);
for cargo_path in &cargo_paths {
if bundle.total_tokens >= budget {
break;
}
if let Ok(content) = file_loader
.load_range(cargo_path, LineRange::new(1, i32::MAX))
.await
{
let tokens = self.token_counter.count(&content)?;
if !bundle.would_exceed_budget(tokens, budget) {
let item = ContextItem {
relpath: cargo_path.to_string(),
range: LineRange::new(1, content.lines().count() as i32),
role: "crate_metadata".to_string(),
reason: "Crate metadata: Cargo.toml provides crate dependencies and configuration".to_string(),
content,
tokens,
};
debug!("Adding Cargo.toml: {} tokens", tokens);
bundle.add_item(item);
break; }
}
}
Ok(())
}
async fn add_trait_impls(
&self,
bundle: &mut ContextBundle,
chunk_id: i64,
budget: usize,
) -> Result<()> {
use crate::db::sqlite::graph::ImportDirection;
if !self.config.include_trait_impls || bundle.total_tokens >= budget {
return Ok(());
}
let implementations = self
.store
.find_extensions(
chunk_id,
ImportDirection::Incoming,
Some(1), )
.await?;
let mut added_count = 0;
for impl_result in implementations {
if added_count >= self.config.max_trait_impls || bundle.total_tokens >= budget {
break;
}
if let Some(chunk) = self.store.get_chunk_by_id(impl_result.chunk_id).await? {
let metadata = self.default.get_chunk_metadata(chunk_id).await?;
let file_loader = FileLoader::new(&metadata.worktree_path);
let range = LineRange::new(chunk.start_line, chunk.end_line);
if let Ok(content) = file_loader.load_range(&chunk.file_path, range).await {
let tokens = self.token_counter.count(&content)?;
if !bundle.would_exceed_budget(tokens, budget) {
let impl_name = chunk.symbol_name.as_deref().unwrap_or("impl");
let item = ContextItem {
relpath: chunk.file_path.clone(),
range: LineRange::new(chunk.start_line, chunk.end_line),
role: "trait_impl".to_string(),
reason: format!(
"Trait implementation: {} provides trait methods",
impl_name
),
content,
tokens,
};
debug!("Adding trait impl {}: {} tokens", impl_name, tokens);
bundle.add_item(item);
added_count += 1;
}
}
}
}
Ok(())
}
async fn add_module_files(
&self,
bundle: &mut ContextBundle,
metadata: &ChunkMetadata,
budget: usize,
) -> Result<()> {
if !self.config.include_module_files || bundle.total_tokens >= budget {
return Ok(());
}
let file_path = std::path::Path::new(&metadata.file_relpath);
let dir = file_path.parent().and_then(|p| p.to_str()).unwrap_or("");
if dir.is_empty() {
return Ok(());
}
let module_files = [format!("{}/mod.rs", dir), "src/lib.rs".to_string()];
let file_loader = FileLoader::new(&metadata.worktree_path);
for module_file in &module_files {
if bundle.total_tokens >= budget {
break;
}
if module_file == &metadata.file_relpath {
continue;
}
if let Ok(content) = file_loader
.load_range(module_file, LineRange::new(1, 50)) .await
{
let tokens = self.token_counter.count(&content)?;
if !bundle.would_exceed_budget(tokens, budget) {
let item = ContextItem {
relpath: module_file.clone(),
range: LineRange::new(1, content.lines().count() as i32),
role: "module".to_string(),
reason: format!(
"Module structure: {} provides module organization context",
module_file
),
content,
tokens,
};
debug!("Adding module file {}: {} tokens", module_file, tokens);
bundle.add_item(item);
break; }
}
}
Ok(())
}
fn is_rust_type(&self, metadata: &ChunkMetadata) -> bool {
(metadata.kind == "struct" || metadata.kind == "enum")
&& metadata.file_relpath.ends_with(".rs")
}
fn is_rust_impl(&self, metadata: &ChunkMetadata) -> bool {
metadata.kind == "impl" && metadata.file_relpath.ends_with(".rs")
}
}
#[async_trait::async_trait]
impl AssemblyStrategy for RustAssemblyStrategy {
async fn assemble(
&self,
chunk_id: i64,
budget: usize,
options: ExpandOptions,
) -> Result<ContextBundle> {
debug!(
"Rust strategy assembling context for chunk {} with budget {} tokens",
chunk_id, budget
);
let mut bundle = self.default.assemble(chunk_id, budget, options).await?;
let metadata = self.default.get_chunk_metadata(chunk_id).await?;
self.add_cargo_toml(&mut bundle, &metadata, budget).await?;
if self.is_rust_type(&metadata) || self.is_rust_impl(&metadata) {
self.add_trait_impls(&mut bundle, chunk_id, budget).await?;
}
self.add_module_files(&mut bundle, &metadata, budget)
.await?;
debug!(
"Rust strategy assembled {} items, {} tokens total",
bundle.items.len(),
bundle.total_tokens
);
Ok(bundle)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rust_config_default() {
let config = RustConfig::default();
assert!(config.include_cargo_toml);
assert!(config.include_trait_impls);
assert!(config.include_module_files);
assert_eq!(config.max_trait_impls, 3);
}
#[tokio::test]
#[ignore]
async fn test_rust_assembly_strategy() {
}
}