Skip to main content

vtcode_core/core/agent/
harness_artifacts.rs

1use anyhow::{Context, Result};
2use std::fs;
3use std::path::{Path, PathBuf};
4
5const TASKS_DIR: &str = ".vtcode/tasks";
6const CURRENT_TASK_FILE: &str = "current_task.md";
7const CURRENT_SPEC_FILE: &str = "current_spec.md";
8const CURRENT_CONTRACT_FILE: &str = "current_contract.md";
9const CURRENT_EVALUATION_FILE: &str = "current_evaluation.md";
10const SUMMARY_PREVIEW_CHARS: usize = 280;
11
12pub fn current_task_path(workspace_root: &Path) -> PathBuf {
13    workspace_root.join(TASKS_DIR).join(CURRENT_TASK_FILE)
14}
15
16pub fn current_spec_path(workspace_root: &Path) -> PathBuf {
17    workspace_root.join(TASKS_DIR).join(CURRENT_SPEC_FILE)
18}
19
20pub fn current_contract_path(workspace_root: &Path) -> PathBuf {
21    workspace_root.join(TASKS_DIR).join(CURRENT_CONTRACT_FILE)
22}
23
24pub fn current_evaluation_path(workspace_root: &Path) -> PathBuf {
25    workspace_root.join(TASKS_DIR).join(CURRENT_EVALUATION_FILE)
26}
27
28pub fn existing_harness_artifact_paths(workspace_root: &Path) -> Vec<PathBuf> {
29    [
30        current_spec_path(workspace_root),
31        current_contract_path(workspace_root),
32        current_evaluation_path(workspace_root),
33    ]
34    .into_iter()
35    .filter(|path| path.exists())
36    .collect()
37}
38
39pub fn read_spec_summary(workspace_root: &Path) -> Option<String> {
40    read_markdown_summary(&current_spec_path(workspace_root), "Spec")
41}
42
43pub fn read_contract_summary(workspace_root: &Path) -> Option<String> {
44    read_markdown_summary(&current_contract_path(workspace_root), "Contract")
45}
46
47pub fn read_evaluation_summary(workspace_root: &Path) -> Option<String> {
48    read_markdown_summary(&current_evaluation_path(workspace_root), "Evaluation")
49}
50
51pub async fn write_spec(workspace_root: &Path, content: &str) -> Result<PathBuf> {
52    let path = current_spec_path(workspace_root);
53    write_artifact(path.as_path(), content, "current spec").await?;
54    Ok(path)
55}
56
57pub async fn write_evaluation(workspace_root: &Path, content: &str) -> Result<PathBuf> {
58    let path = current_evaluation_path(workspace_root);
59    write_artifact(path.as_path(), content, "current evaluation").await?;
60    Ok(path)
61}
62
63pub async fn write_contract(workspace_root: &Path, content: &str) -> Result<PathBuf> {
64    let path = current_contract_path(workspace_root);
65    write_artifact(path.as_path(), content, "current contract").await?;
66    Ok(path)
67}
68
69async fn write_artifact(path: &Path, content: &str, label: &str) -> Result<()> {
70    if let Some(parent) = path.parent() {
71        tokio::fs::create_dir_all(parent)
72            .await
73            .with_context(|| format!("create {} directory {}", label, parent.display()))?;
74    }
75
76    tokio::fs::write(path, content)
77        .await
78        .with_context(|| format!("write {} {}", label, path.display()))?;
79    Ok(())
80}
81
82fn read_markdown_summary(path: &Path, label: &str) -> Option<String> {
83    let content = fs::read_to_string(path).ok()?;
84    let lines = content
85        .lines()
86        .map(str::trim)
87        .filter(|line| !line.is_empty())
88        .filter(|line| !line.starts_with('#'))
89        .take(4)
90        .collect::<Vec<_>>();
91    if lines.is_empty() {
92        return None;
93    }
94
95    let joined = lines.join(" | ");
96    Some(format!("{label}: {}", truncate_summary(&joined)))
97}
98
99fn truncate_summary(text: &str) -> String {
100    vtcode_commons::formatting::truncate_within(text, SUMMARY_PREVIEW_CHARS, "...")
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use tempfile::tempdir;
107
108    #[tokio::test]
109    async fn writes_and_summarizes_spec_and_evaluation_artifacts() {
110        let temp = tempdir().expect("tempdir");
111
112        write_spec(
113            temp.path(),
114            "# Spec\n\nBuild a stronger exec harness.\n\nKeep it resumable.\n",
115        )
116        .await
117        .expect("write spec");
118        write_contract(
119            temp.path(),
120            "# Contract\n\n- Deliver the requested change.\n- Verify with cargo check.\n",
121        )
122        .await
123        .expect("write contract");
124        write_evaluation(
125            temp.path(),
126            "# Evaluation\n\nVerdict: fail\n\nNeed another revision round.\n",
127        )
128        .await
129        .expect("write evaluation");
130
131        let paths = existing_harness_artifact_paths(temp.path());
132        assert_eq!(paths.len(), 3);
133        assert_eq!(
134            read_spec_summary(temp.path()),
135            Some("Spec: Build a stronger exec harness. | Keep it resumable.".to_string())
136        );
137        assert_eq!(
138            read_contract_summary(temp.path()),
139            Some(
140                "Contract: - Deliver the requested change. | - Verify with cargo check."
141                    .to_string()
142            )
143        );
144        assert_eq!(
145            read_evaluation_summary(temp.path()),
146            Some("Evaluation: Verdict: fail | Need another revision round.".to_string())
147        );
148    }
149}