Files
codex/prs/bolinfest/study/PR-1730-study.md
2025-09-02 15:17:45 -07:00

5.0 KiB
Raw Blame History

PR #1730 Review Takeaways (bolinfest)

DOs

  • Bold the keyword: concise description.
  • Provide short code blocks that show the pattern.

DOs

  • Use async file I/O with Tokio: prefer tokio::fs + BufReader and limit reads.
use tokio::fs::File;
use tokio::io::{self, AsyncReadExt, BufReader};

async fn read_up_to(path: &std::path::Path, max_bytes: usize) -> io::Result<Option<String>> {
    let file = match File::open(path).await {
        Ok(f) => f,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(e) => return Err(e),
    };
    let mut reader = BufReader::new(file);
    let mut buf = Vec::new();
    reader.take(max_bytes as u64).read_to_end(&mut buf).await?;
    let s = String::from_utf8_lossy(&buf).to_string();
    Ok((!s.trim().is_empty()).then_some(s))
}
  • Make path discovery async and stop at the Git root.
use tokio::fs;
use tokio::io;
use std::path::{Path, PathBuf};

async fn discover_project_doc_path_from_dir(start_dir: &Path, names: &[&str]) -> io::Result<Option<PathBuf>> {
    let mut dir = fs::canonicalize(start_dir).await.unwrap_or_else(|_| start_dir.to_path_buf());

    // Try in the current directory first
    if let Some(p) = first_nonempty_candidate_async(&dir, names).await? {
        return Ok(Some(p));
    }

    // Walk up until a .git is found; do not walk past it
    while let Some(parent) = dir.parent() {
        let git_marker = dir.join(".git");
        if fs::metadata(&git_marker).await.is_ok() {
            return first_nonempty_candidate_async(&dir, names).await;
        }
        dir = parent.to_path_buf();
    }
    Ok(None)
}
  • Consolidate discovery logic; reuse it for both “path” and “contents” to avoid duplication.
async fn first_nonempty_candidate_async(dir: &Path, names: &[&str]) -> io::Result<Option<PathBuf>> {
    for name in names {
        let candidate = dir.join(name);
        if let Some(_) = read_up_to(&candidate, 8192).await? {
            return Ok(Some(candidate));
        }
    }
    Ok(None)
}

async fn find_project_doc(config: &Config) -> io::Result<Option<String>> {
    let Some(path) = discover_project_doc_path_from_dir(&config.cwd, CANDIDATE_FILENAMES).await? else {
        return Ok(None);
    };
    read_up_to(&path, config.project_doc_max_bytes).await
}
  • Return early using expression style; list the common case first.
pub async fn discover_project_doc_path(config: &Config) -> io::Result<Option<PathBuf>> {
    if config.project_doc_max_bytes > 0 {
        discover_project_doc_path_from_dir(&config.cwd, CANDIDATE_FILENAMES).await
    } else {
        Ok(None)
    }
}
  • Pass agents_doc_path in SessionConfiguredEvent; let the TUI just render it.
// core: when sending the event
EventMsg::SessionConfigured(SessionConfiguredEvent {
    session_id,
    model,
    agents_doc_path: agents_doc_path_string(&config),
    history_log_id,
    history_entry_count,
});

// tui: consume what core sent (no filesystem work here)
let model = event.model.clone();
let agents_doc_path = event.agents_doc_path.clone();
  • Canonicalize early; comment accurately (“start_dir contains ..”).
// Avoid loops if start_dir contains `..`
let dir = fs::canonicalize(start_dir).await.unwrap_or_else(|_| start_dir.to_path_buf());
  • Treat whitespace-only files as empty; enforce byte limits.
let s = String::from_utf8_lossy(&buf);
if s.trim().is_empty() { return Ok(None); } // consider as not found
  • Inline variables in format!.
let u = "/home/me/.codex/AGENTS.md";
let pr = "/repo/AGENTS.md";
let summary = format!("Using user instructions ({u}) and project instructions ({pr})");
  • Cover precedence and disabling in tests (cwd beats repo root; repo root fallback; 0-byte limit disables).
assert_eq!(discover_project_doc_path(&cfg).await?.unwrap(), nested.join("AGENTS.md"));

DONTs

  • Use blocking std::fs/std::io::Read in async code paths; prefer Tokio equivalents.
// ❌ Avoid in async contexts:
use std::fs::File;
use std::io::Read;
  • Assume Read::read() fills the buffer; respect the returned byte count or use read_to_end() with take().
// ❌ Anti-pattern:
let n = file.read(&mut buf)?; // may be < buf.len()
// ✅ Use AsyncReadExt::take + read_to_end, or handle `n` carefully
  • Duplicate discovery and loading logic across functions.
// ❌ Dont reimplement “find candidate, then read” in multiple places
// ✅ Centralize in `first_nonempty_candidate_async` and reuse
  • Do filesystem discovery in the TUI at startup; dont block the UI thread.
// ❌ TUI calling filesystem discovery directly
// ✅ Have core include `agents_doc_path` in the session-configured event
  • Walk past the Git root or mis-detect it.
// ❌ Continuing upwards after finding `.git`
// ✅ Stop once `.git` exists in the directory
  • Bury imports or helpers mid-file; keep use and helpers at the top for clarity.
// ✅ Top-of-file imports
use tokio::fs;
use tokio::io::{self, AsyncReadExt, BufReader};