use std::path::Path;
use git2::Repository;
use serde_json::Value;
use tracing::{debug, error};
use crate::core::{detect_version, StorageVersion};
use crate::domain::{ObjectType, WrappedBlob, WrappedNeuralCommit};
use crate::mcp::protocol::{GetContextParams, ToolCallResult};
use crate::storage::{
FileHeadStore, FileObjectStore, FileRefStore, GitObjectStore, GitRefStore, HeadStore,
ObjectStore, RefStore,
};
pub fn execute(project_root: &Path, agit_dir: &Path, arguments: Option<Value>) -> ToolCallResult {
let args = match arguments {
Some(v) => v,
None => {
return ToolCallResult::error("Missing arguments for agit_get_context");
},
};
let params: GetContextParams = match serde_json::from_value(args) {
Ok(p) => p,
Err(e) => {
error!("Invalid params for agit_get_context: {}", e);
return ToolCallResult::error(&format!("Invalid parameters: {}", e));
},
};
if !agit_dir.exists() {
return ToolCallResult::error("AGIT not initialized. Run 'agit init' first.");
}
match find_context_for_git_hash(project_root, agit_dir, ¶ms.git_hash) {
Ok(context) => ToolCallResult::text(&context),
Err(e) => {
debug!("Failed to find context: {}", e);
ToolCallResult::error(&format!(
"No context found for git hash '{}': {}",
params.git_hash, e
))
},
}
}
fn find_context_for_git_hash(
project_root: &Path,
agit_dir: &Path,
git_hash: &str,
) -> Result<String, String> {
let head_store = FileHeadStore::new(agit_dir);
let is_v2 = match Repository::discover(project_root) {
Ok(repo) => matches!(detect_version(agit_dir, &repo), StorageVersion::V2GitNative),
Err(_) => false,
};
let branch = head_store
.get()
.map_err(|e| format!("Failed to read HEAD: {}", e))?
.unwrap_or_else(|| "main".to_string());
let mut current_hash: Option<String> = if is_v2 {
let ref_store = GitRefStore::new(project_root);
ref_store
.get(&branch)
.map_err(|e| format!("Failed to read ref: {}", e))?
} else {
let ref_store = FileRefStore::new(agit_dir);
ref_store
.get(&branch)
.map_err(|e| format!("Failed to read ref: {}", e))?
};
while let Some(hash) = current_hash {
let commit_data = if is_v2 {
let object_store = GitObjectStore::new(project_root);
object_store
.load(&hash)
.map_err(|e| format!("Failed to load commit: {}", e))?
} else {
let object_store = FileObjectStore::new(agit_dir);
object_store
.load(&hash)
.map_err(|e| format!("Failed to load commit: {}", e))?
};
let wrapped: WrappedNeuralCommit = serde_json::from_slice(&commit_data)
.map_err(|e| format!("Failed to parse commit: {}", e))?;
let commit = &wrapped.data;
if commit.git_hash.starts_with(git_hash) || git_hash.starts_with(&commit.git_hash) {
let mut context = String::new();
context.push_str(&format!("# Context for git commit {}\n\n", commit.git_hash));
context.push_str(&format!("**Neural Commit:** {}\n", hash));
context.push_str(&format!("**Author:** {}\n", commit.author));
context.push_str(&format!(
"**Date:** {}\n\n",
commit.created_at.format("%Y-%m-%d %H:%M:%S UTC")
));
context.push_str(&format!("## Summary\n\n{}\n\n", commit.summary));
let trace_result = if is_v2 {
let object_store = GitObjectStore::new(project_root);
object_store.load(&commit.trace_hash)
} else {
let object_store = FileObjectStore::new(agit_dir);
object_store.load(&commit.trace_hash)
};
if let Ok(trace_data) = trace_result {
if let Ok(trace_blob) = serde_json::from_slice::<WrappedBlob>(&trace_data) {
if trace_blob.object_type == ObjectType::Blob {
context.push_str("## Trace\n\n```\n");
context.push_str(&trace_blob.data.content);
context.push_str("\n```\n");
}
}
}
return Ok(context);
}
current_hash = commit.first_parent().map(|s| s.to_string());
}
Err("Not found in neural commit history".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use tempfile::TempDir;
#[test]
fn test_get_context_not_initialized() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let agit_dir = temp.path().join(".agit");
let args = json!({
"git_hash": "abc123"
});
let result = execute(project_root, &agit_dir, Some(args));
assert_eq!(result.is_error, Some(true));
}
#[test]
fn test_get_context_missing_args() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let agit_dir = temp.path().join(".agit");
std::fs::create_dir_all(&agit_dir).unwrap();
let result = execute(project_root, &agit_dir, None);
assert_eq!(result.is_error, Some(true));
}
#[test]
fn test_get_context_no_commits() {
let temp = TempDir::new().unwrap();
let project_root = temp.path();
let agit_dir = temp.path().join(".agit");
std::fs::create_dir_all(agit_dir.join("objects")).unwrap();
std::fs::create_dir_all(agit_dir.join("refs/heads")).unwrap();
std::fs::write(agit_dir.join("HEAD"), "main").unwrap();
let args = json!({
"git_hash": "abc123"
});
let result = execute(project_root, &agit_dir, Some(args));
assert_eq!(result.is_error, Some(true));
}
}