vtcode_core/core/agent/
harness_artifacts.rs1use 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(¤t_spec_path(workspace_root), "Spec")
41}
42
43pub fn read_contract_summary(workspace_root: &Path) -> Option<String> {
44 read_markdown_summary(¤t_contract_path(workspace_root), "Contract")
45}
46
47pub fn read_evaluation_summary(workspace_root: &Path) -> Option<String> {
48 read_markdown_summary(¤t_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}