mirror of
https://github.com/openai/codex.git
synced 2026-05-23 12:34:25 +00:00
118 lines
4.2 KiB
Rust
118 lines
4.2 KiB
Rust
use anyhow::Context;
|
|
use codex_git_utils::GitBaselineDiff;
|
|
use codex_git_utils::diff_since_latest_init;
|
|
use codex_git_utils::ensure_git_baseline_repository;
|
|
use codex_git_utils::reset_git_repository;
|
|
use std::path::Path;
|
|
|
|
/// Prepares the memory directory for git-baseline diffing.
|
|
///
|
|
/// This keeps an existing usable `.git/` baseline intact. It initializes a new git baseline when the
|
|
/// metadata is missing or unusable, and removes any stale generated `phase2_workspace_diff.md` file
|
|
/// so that the next diff does not include a previous prompt artifact.
|
|
pub async fn prepare_memory_workspace(root: &Path) -> anyhow::Result<()> {
|
|
tokio::fs::create_dir_all(root)
|
|
.await
|
|
.with_context(|| format!("create memory workspace {}", root.display()))?;
|
|
remove_workspace_diff(root).await?;
|
|
ensure_git_baseline_repository(root).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the current workspace diff after removing any stale generated diff artifact.
|
|
///
|
|
/// The removed file is only `phase2_workspace_diff.md`; memory artifacts and `.git/` metadata are
|
|
/// left intact.
|
|
pub async fn memory_workspace_diff(root: &Path) -> anyhow::Result<GitBaselineDiff> {
|
|
remove_workspace_diff(root).await?;
|
|
diff_since_latest_init(root).await
|
|
}
|
|
|
|
/// Writes `phase2_workspace_diff.md` with a bounded git-style diff from the current baseline.
|
|
pub async fn write_workspace_diff(root: &Path, diff: &GitBaselineDiff) -> anyhow::Result<()> {
|
|
let path = root.join(crate::workspace_diff::FILENAME);
|
|
tokio::fs::write(&path, render_workspace_diff_file(diff))
|
|
.await
|
|
.with_context(|| format!("write memory workspace diff file {}", path.display()))
|
|
}
|
|
|
|
/// Marks the current memory root as the new baseline.
|
|
///
|
|
/// The generated diff file is removed before resetting the baseline so deleted memory content is
|
|
/// not retained in the prompt artifact or in unreachable git objects.
|
|
pub async fn reset_memory_workspace_baseline(root: &Path) -> anyhow::Result<()> {
|
|
remove_workspace_diff(root).await?;
|
|
reset_git_repository(root).await
|
|
}
|
|
|
|
/// Removes the generated `phase2_workspace_diff.md` prompt artifact.
|
|
///
|
|
/// This does not remove `.git/`, reset the baseline, or delete memory content. It is used before
|
|
/// diffing and before baseline reset so the generated diff file itself is not treated as memory
|
|
/// workspace input.
|
|
pub(super) async fn remove_workspace_diff(root: &Path) -> anyhow::Result<()> {
|
|
let path = root.join(crate::workspace_diff::FILENAME);
|
|
match tokio::fs::remove_file(&path).await {
|
|
Ok(()) => Ok(()),
|
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
|
|
Err(err) => Err(err)
|
|
.with_context(|| format!("remove memory workspace diff file {}", path.display())),
|
|
}
|
|
}
|
|
|
|
fn render_workspace_diff_file(diff: &GitBaselineDiff) -> String {
|
|
let mut rendered = String::from(
|
|
"# Memory Workspace Diff\n\n\
|
|
Generated by Codex before Phase 2 memory consolidation. Read this file first and do not edit it.\n\n\
|
|
## Status\n",
|
|
);
|
|
|
|
if !diff.has_changes() {
|
|
rendered.push_str("- none\n");
|
|
return rendered;
|
|
}
|
|
|
|
for change in &diff.changes {
|
|
rendered.push_str(&format!("- {} {}\n", change.status.label(), change.path));
|
|
}
|
|
rendered.push_str("\n## Diff\n\n```diff\n");
|
|
append_bounded_diff(&mut rendered, &diff.unified_diff);
|
|
rendered.push_str("```\n");
|
|
rendered
|
|
}
|
|
|
|
fn append_bounded_diff(rendered: &mut String, diff: &str) {
|
|
if diff.len() <= crate::workspace_diff::MAX_BYTES {
|
|
rendered.push_str(diff);
|
|
if !diff.ends_with('\n') {
|
|
rendered.push('\n');
|
|
}
|
|
return;
|
|
}
|
|
|
|
let boundary = previous_char_boundary(diff, crate::workspace_diff::MAX_BYTES);
|
|
rendered.push_str(&diff[..boundary]);
|
|
if !rendered.ends_with('\n') {
|
|
rendered.push('\n');
|
|
}
|
|
rendered.push_str(&format!(
|
|
"\n[workspace diff truncated at {} bytes]\n",
|
|
crate::workspace_diff::MAX_BYTES
|
|
));
|
|
}
|
|
|
|
fn previous_char_boundary(value: &str, max_bytes: usize) -> usize {
|
|
if max_bytes >= value.len() {
|
|
return value.len();
|
|
}
|
|
let mut index = max_bytes;
|
|
while !value.is_char_boundary(index) {
|
|
index -= 1;
|
|
}
|
|
index
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "workspace_tests.rs"]
|
|
mod tests;
|