本机派生目录 .export 只认项目根那一层

project/filesystem.rs:should_skip_project_derived_path 与快照同步判定改为只看第一个路径组件,源码里的嵌套 game/.export 不再被误排。
project_snapshot/tests.rs:补测试区分根 .export 与嵌套 .export。
This commit is contained in:
2026-10-06 21:36:45 +08:00
parent 22ad218dc7
commit d7ca19c598
2 changed files with 44 additions and 5 deletions
@@ -415,12 +415,15 @@ pub(crate) fn should_skip_project_snapshot_path(relative_path: &str) -> bool {
/// 这些文件能从项目源码重跑出来,不该进索引、checkpoint 或版本库视角,也不该被当成项目变更;
/// 但 Agent 必须能写它们,所以不能并进 `should_skip_project_snapshot_path`——那份列表同时是
/// `agc_apply_patch` 的写入门禁,并进去会让首次适配直接卡死。
///
/// `.export` 只认项目根那一层(`EXPORT_WORK_RELATIVE_DIR` 就是 `root/.export`):源码里合法的
/// 嵌套目录 `game/.export/...` 是用户自己的文件,不能被误伤。
pub(crate) fn should_skip_project_derived_path(relative_path: &str) -> bool {
project_snapshot_path_is_excluded(relative_path, false)
|| relative_path
.split('/')
.filter(|component| !component.is_empty())
.any(|component| {
.find(|component| !component.is_empty())
.is_some_and(|component| {
PROJECT_DERIVED_EXCLUDED_COMPONENTS
.iter()
.any(|candidate| component.eq_ignore_ascii_case(candidate))
@@ -439,7 +442,8 @@ pub(crate) fn should_skip_project_derived_path(relative_path: &str) -> bool {
* `.env*` 与凭据类文件名一律不参与同步;符号链接与重解析点在扫描阶段单独跳过。
*
* 另外,同步还多认 `PROJECT_DERIVED_EXCLUDED_COMPONENTS`(`.export`):本机派生
* 的导出工作目录不进同步,但 Agent 仍要能读写它。
* 的导出工作目录不进同步,但 Agent 仍要能读写它。`.export` 同样只认项目根那一层,
* 源码里的嵌套 `game/.export/...` 不在此列。
*/
pub(crate) fn should_skip_project_snapshot_sync_path(relative_path: &str) -> bool {
project_snapshot_path_is_excluded(relative_path, true)
@@ -536,8 +540,8 @@ fn project_snapshot_path_is_excluded(relative_path: &str, include_agent_state: b
&& !(include_agent_state && component == ".agent")
}) || (include_agent_state
&& components
.iter()
.any(|component| PROJECT_DERIVED_EXCLUDED_COMPONENTS.contains(&component.as_str())))
.first()
.is_some_and(|component| PROJECT_DERIVED_EXCLUDED_COMPONENTS.contains(&component.as_str())))
{
return true;
}
@@ -267,6 +267,41 @@ fn project_snapshot_sync_policy_keeps_agent_state_and_still_blocks_credentials()
assert!(!should_skip_project_index_path("game/main.ts"));
}
#[test]
fn derived_export_directory_is_matched_only_at_the_project_root() {
// 项目根的 `.export` 才是本机派生工作目录:数据视角与快照同步都排除。
for root_export in [
".export/xhs-minitool.zip",
".export/xhs-minitool.json",
".EXPORT/xhs-minitool.zip",
] {
assert!(
should_skip_project_derived_path(root_export),
"根目录的 .export 必须排除:{root_export}"
);
assert!(
should_skip_project_snapshot_sync_path(root_export),
"根目录的 .export 不进快照同步:{root_export}"
);
}
// 嵌套在源码目录里的 `.export` 是用户自己的文件,不能按本机派生目录误伤。
for nested in [
"game/.export/local.ts",
"game/.export/xhs-minitool.zip",
"src/vendor/.export/keep.json",
] {
assert!(
!should_skip_project_derived_path(nested),
"嵌套 .export 是用户源码,不该被数据视角排除:{nested}"
);
assert!(
!should_skip_project_snapshot_sync_path(nested),
"嵌套 .export 是用户源码,不该被排除出快照同步:{nested}"
);
}
}
#[test]
fn project_snapshot_diff_reuses_metadata_and_reports_a_single_modification() {
let root = fixture_root();