Codex Handbook
memories/write/src/workspace.rs 117 lines
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;