Extract shared project root resolution helper

This commit is contained in:
Abhinav Vedmala
2026-04-14 11:47:29 -07:00
parent a97234a252
commit f60b2cc5b1
3 changed files with 37 additions and 75 deletions

View File

@@ -25,6 +25,7 @@ use std::path::Path;
#[cfg(windows)]
use std::path::PathBuf;
use toml::Value as TomlValue;
use tracing::warn;
pub use codex_config::AppRequirementToml;
pub use codex_config::AppsRequirementsToml;
@@ -760,6 +761,31 @@ pub(crate) async fn find_project_root(
Ok(cwd.clone())
}
pub(crate) async fn resolve_project_root(
config_layer_stack: &ConfigLayerStack,
cwd: &AbsolutePathBuf,
) -> io::Result<AbsolutePathBuf> {
let mut merged = TomlValue::Table(toml::map::Map::new());
for layer in config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
if matches!(layer.name, ConfigLayerSource::Project { .. }) {
continue;
}
merge_toml_values(&mut merged, &layer.config);
}
let project_root_markers = match project_root_markers_from_config(&merged) {
Ok(Some(markers)) => markers,
Ok(None) => default_project_root_markers(),
Err(err) => {
warn!("invalid project_root_markers: {err}");
default_project_root_markers()
}
};
find_project_root(cwd, &project_root_markers).await
}
/// Return the appropriate list of layers (each with
/// [ConfigLayerSource::Project] as the source) between `cwd` and
/// `project_root`, inclusive. The list is ordered in _increasing_ precdence,

View File

@@ -1,7 +1,6 @@
use std::future::Future;
use std::sync::Arc;
use codex_app_server_protocol::ConfigLayerSource;
use codex_hooks::PermissionRequestDecision;
use codex_hooks::PermissionRequestOutcome;
use codex_hooks::PermissionRequestRequest;
@@ -30,17 +29,12 @@ use codex_protocol::protocol::HookStartedEvent;
use codex_protocol::protocol::WarningEvent;
use codex_protocol::user_input::UserInput;
use serde_json::Value;
use toml::Value as TomlValue;
use tracing::warn;
use crate::codex::Session;
use crate::codex::TurnContext;
use crate::config::Config;
use crate::config_loader::ConfigLayerStackOrdering;
use crate::config_loader::default_project_root_markers;
use crate::config_loader::find_project_root;
use crate::config_loader::merge_toml_values;
use crate::config_loader::project_root_markers_from_config;
use crate::config_loader::resolve_project_root;
use crate::event_mapping::parse_turn_item;
use crate::tools::sandboxing::PermissionRequestPayload;
@@ -289,28 +283,12 @@ async fn apply_execpolicy_amendment_destination(
}
async fn project_codex_home(config: &Config) -> std::io::Result<std::path::PathBuf> {
let mut merged = TomlValue::Table(toml::map::Map::new());
for layer in config.config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
if matches!(layer.name, ConfigLayerSource::Project { .. }) {
continue;
}
merge_toml_values(&mut merged, &layer.config);
}
let project_root_markers = match project_root_markers_from_config(&merged) {
Ok(Some(markers)) => markers,
Ok(None) => default_project_root_markers(),
Err(err) => {
warn!("invalid project_root_markers: {err}");
default_project_root_markers()
}
};
Ok(find_project_root(&config.cwd, &project_root_markers)
.await?
.join(".codex")
.to_path_buf())
Ok(
resolve_project_root(&config.config_layer_stack, &config.cwd)
.await?
.join(".codex")
.to_path_buf(),
)
}
pub(crate) async fn run_post_tool_use_hooks(

View File

@@ -16,18 +16,13 @@
//! 3. We do **not** walk past the project root.
use crate::config::Config;
use crate::config_loader::ConfigLayerStackOrdering;
use crate::config_loader::default_project_root_markers;
use crate::config_loader::merge_toml_values;
use crate::config_loader::project_root_markers_from_config;
use codex_app_server_protocol::ConfigLayerSource;
use crate::config_loader::resolve_project_root;
use codex_exec_server::Environment;
use codex_exec_server::ExecutorFileSystem;
use codex_features::Feature;
use codex_utils_absolute_path::AbsolutePathBuf;
use dunce::canonicalize as normalize_path;
use std::io;
use toml::Value as TomlValue;
use tracing::error;
pub(crate) const HIERARCHICAL_AGENTS_MESSAGE: &str =
@@ -226,51 +221,14 @@ pub async fn discover_project_doc_paths(
dir = AbsolutePathBuf::try_from(canon)?;
}
let mut merged = TomlValue::Table(toml::map::Map::new());
for layer in config.config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
if matches!(layer.name, ConfigLayerSource::Project { .. }) {
continue;
}
merge_toml_values(&mut merged, &layer.config);
}
let project_root_markers = match project_root_markers_from_config(&merged) {
Ok(Some(markers)) => markers,
Ok(None) => default_project_root_markers(),
Err(err) => {
tracing::warn!("invalid project_root_markers: {err}");
default_project_root_markers()
}
};
let mut project_root = None;
if !project_root_markers.is_empty() {
for ancestor in dir.ancestors() {
for marker in &project_root_markers {
let marker_path = AbsolutePathBuf::try_from(ancestor.join(marker))?;
let marker_exists = match fs.get_metadata(&marker_path).await {
Ok(_) => true,
Err(err) if err.kind() == io::ErrorKind::NotFound => false,
Err(err) => return Err(err),
};
if marker_exists {
project_root = Some(AbsolutePathBuf::try_from(ancestor.to_path_buf())?);
break;
}
}
if project_root.is_some() {
break;
}
}
}
let project_root = resolve_project_root(&config.config_layer_stack, &dir).await?;
let search_dirs: Vec<AbsolutePathBuf> = if let Some(root) = project_root {
let search_dirs: Vec<AbsolutePathBuf> = if project_root != dir {
let mut dirs = Vec::new();
let mut cursor = dir.clone();
loop {
dirs.push(cursor.clone());
if cursor == root {
if cursor == project_root {
break;
}
let Some(parent) = cursor.parent() else {