chore(agc): 清掉本轮 master 新代码带来的 14 处生产口径告警(最小修,无 #[allow])
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

背景:本 PR 的 AGC 壳 warning 门禁只覆盖「Windows 生产口径」,master 侧没有该门禁;本轮同步进来的代码
(陶泥儿导出/发布链路退役、bootstrap 退役)留下 14 处 never used / unused import。已逐条与上一轮合并点
`d5e26a68f` 比对确认:**不是我们删除造成的连带**,全部是 master 本轮自己新引入的。

逐条(文件:行 → 处理):
1. game_package_upload.rs:19 unused import `GAME_PACKAGE_UPLOAD_PROGRESS_EVENT` → 从再导出列表移除。
2. game_package_upload/runtime.rs:11 `GAME_PACKAGE_UPLOAD_PROGRESS_EVENT` 常量 → 删除;生产调用点已被
   b04aceb57(exports 全退役)删除。
3. game_package_upload.rs:73 `ensure_staging_path_in_dir` → `#[cfg(test)]`;生产调用点同样被 b04aceb57 删除,
   当前只剩本文件的用例调用。
4. command_exec.rs:645 `resolve_project_bootstrap_spec_at` → 删除。
5. command_exec.rs:2500 `run_project_bootstrap_command_at` → 删除;唯一调用方 project/export.rs 已被 master 删除。
6. project/bootstrap.rs → 整文件删除(唯一入口 run_project_bootstrap_at 失去唯一调用方),并移除 project.rs 的
   `mod bootstrap;` 与 `pub(crate) use bootstrap::*;`。
7. project/verification.rs:662 `run_project_verification_with_commit_at` → 删除;两个调用方(project/export.rs、
   xhs_minitool/build.rs)均已由 master 改动删除;下游文档里对它的 intra-doc 链接改为不带链接的描述。
8. export/draft/taonier/mod.rs:25 再导出 `TaonierArtifactPackage` → 移除;结构体本身仍被 read_artifact_package 使用。
9. export/draft/taonier/registry.rs:88 `read_form` → 删除;全仓无调用方。
10. export/draft/taonier/script.rs:22 `NpmScript.package_json_relative` 字段 → `#[cfg(test)]`(只有用例读取;
    未删除任何用例或断言)。

不使用 `#[allow]`,不删除业务校验与用例,不引入假引用。AGC 壳生产口径恢复 0 warning / EXIT 0。
This commit is contained in:
2026-10-07 16:59:13 +08:00
parent 47b5a17f54
commit 1a86675f4c
9 changed files with 10 additions and 202 deletions
@@ -638,45 +638,6 @@ pub(crate) fn resolve_project_command_spec_at(
.map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))
}
/// Resolves the one privileged npm operation used to hydrate a DirectProject.
/// It intentionally bypasses the general command.exec npm allow-list: callers
/// must use the dedicated `project.bootstrap` action, which only accepts the
/// literal `npm install` in the project's `game` directory.
pub(crate) fn resolve_project_bootstrap_spec_at(
root: &Path,
timeout_seconds: u64,
) -> Result<ProjectCommandSpec, ProjectCommandError> {
validate_project_root(root)
.map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?;
let cwd_relative = "game".to_string();
let cwd = resolve_local_project_path(root, &cwd_relative)
.map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?;
validate_project_command_cwd_components(root, &cwd_relative)
.map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?;
if !(PROJECT_COMMAND_MIN_TIMEOUT_SECONDS..=PROJECT_COMMAND_MAX_TIMEOUT_SECONDS)
.contains(&timeout_seconds)
{
return Err(ProjectCommandError::new(
ProjectCommandErrorStage::Validation,
format!("project.bootstrap timeoutSeconds 必须在 {PROJECT_COMMAND_MIN_TIMEOUT_SECONDS}-{PROJECT_COMMAND_MAX_TIMEOUT_SECONDS} 之间"),
));
}
let program = "npm".to_string();
let resolved = resolve_project_command_executable(root, &program)
.map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?;
Ok(ProjectCommandSpec {
program,
executable: resolved.executable,
safe_path: resolved.safe_path,
arguments: vec!["install".to_string()],
cwd_relative,
cwd,
timeout_seconds,
verification_eligible: false,
node_launcher: resolved.node_launcher,
})
}
fn resolve_project_command_spec_inner(
root: &Path,
program: &str,
@@ -2497,16 +2458,6 @@ pub(crate) async fn run_project_command_with_output_at(
.await
}
pub(crate) async fn run_project_bootstrap_command_at(
root: &Path,
timeout_seconds: u64,
) -> Result<ProjectCommandResult, ProjectCommandError> {
let spec = resolve_project_bootstrap_spec_at(root, timeout_seconds)?;
let launch = prepare_project_command_launch_spec(root, &spec)?;
let staged = stage_project_command_launch_spec(&spec, launch)?;
run_prepared_project_command_with_output_at(root, &spec, staged, None, || Ok(())).await
}
pub(crate) async fn run_prepared_project_command_with_output_at<F>(
root: &Path,
spec: &ProjectCommandSpec,
@@ -22,4 +22,4 @@ pub(crate) mod registry;
mod script;
/// 发布链路读固定产物的入口。只读 `.export/taonier.zip`,不接用户路径。
pub(crate) use build::{read_artifact_package, TaonierArtifactPackage};
pub(crate) use build::read_artifact_package;
@@ -80,17 +80,6 @@ pub(crate) fn save(
Ok(RegistrySnapshot { form, content_hash })
}
/// 发布链路只读草稿表单。
///
/// 发布不是写草稿的入口,所以这里只取表单、不关心指纹,也不做冲突判定;文件缺失时与读命令
/// 同一口径(`load_or_create` 补一份空表单),让后续的封面/张数校验给出确定的「请先选择封面」
/// 提示,而不是把「草稿不存在」误报成宿主故障。
pub(crate) fn read_form(root: &Path) -> Result<TaonierExportForm, String> {
let loaded = load_or_create(&registry_path(root), &TaonierExportRegistry::default())
.map_err(|error| error.to_string())?;
Ok(loaded.value.form)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -18,7 +18,8 @@ pub(crate) struct NpmScript {
pub(crate) cwd_relative: &'static str,
/// `package.json` 里声明的脚本文本。交给运行器做反漂移的 `expectedCommand`。
pub(crate) command: String,
/// 相对项目根的 `package.json` 路径,报错与提示都用它。
/// 相对项目根的 `package.json` 路径;当前只被用例读取。
#[cfg(test)]
pub(crate) package_json_relative: String,
}
@@ -59,6 +60,7 @@ pub(crate) fn find(root: &Path) -> Option<NpmScript> {
return Some(NpmScript {
cwd_relative: cwd,
command: command.to_string(),
#[cfg(test)]
package_json_relative: package_json_relative(cwd),
});
}
@@ -16,7 +16,7 @@ use serde_json::Value;
mod runtime;
pub(crate) use runtime::{
game_package_upload_staging_dir, progress_event_payload, upload_staged_game_package,
GamePackageUploadOutcome, GamePackageUploadRequest, GAME_PACKAGE_UPLOAD_PROGRESS_EVENT,
GamePackageUploadOutcome, GamePackageUploadRequest,
};
/// 已暂存(归一化后)的发行包:内容寻址,重启后同一包复用同一个文件,续传才有意义。
@@ -70,6 +70,10 @@ pub(crate) fn stage_game_package_bytes(
}
/// 暂存文件必须落在暂存目录内;渲染进程不能借这条命令读任意路径。
///
/// 生产接入已随 exports 退役(master `b04aceb57` 删除 `project/export.rs` 与 `commands/desktop.rs`
/// 的发布链路)删除,当前只有本文件的用例调用它。
#[cfg(test)]
pub(crate) fn ensure_staging_path_in_dir(
staging_dir: &Path,
staging_path: &str,
@@ -8,8 +8,6 @@ use std::{
time::Duration,
};
pub(crate) const GAME_PACKAGE_UPLOAD_PROGRESS_EVENT: &str = "game-package-upload-progress";
const AGC_CLIENT_MARKER_HEADER: &str = "X-Genarrative-Client";
const AGC_CLIENT_MARKER_VALUE: &str = "agc";
const UPLOAD_OFFSET_HEADER: &str = "x-genarrative-upload-offset";
@@ -6,7 +6,6 @@ mod agent_db;
mod asset_export;
#[cfg(not(test))]
mod asset_rename;
mod bootstrap;
mod checkpoint;
mod conversation;
mod external_editor_bindings;
@@ -27,7 +26,6 @@ pub(crate) use agent_db::*;
pub(crate) use asset_export::*;
#[cfg(not(test))]
pub(crate) use asset_rename::*;
pub(crate) use bootstrap::*;
pub(crate) use checkpoint::*;
pub(crate) use conversation::*;
pub(crate) use external_editor_bindings::*;
@@ -1,113 +0,0 @@
use super::*;
use sha2::{Digest, Sha256};
const BOOTSTRAP_PACKAGE_MAX_BYTES: u64 = 512 * 1024;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ProjectBootstrapResult {
pub(crate) status: String,
pub(crate) output: String,
pub(crate) package_sha256: String,
pub(crate) lock_sha256: Option<String>,
pub(crate) log_path: String,
pub(crate) updated_at: u64,
}
fn read_bootstrap_file(path: &Path, label: &str) -> Result<Vec<u8>, String> {
let metadata = fs::symlink_metadata(path)
.map_err(|error| format!("读取 {label} 失败:{}: {error}", path.display()))?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(format!("project.bootstrap 要求 {label} 是普通文件"));
}
if metadata.len() > BOOTSTRAP_PACKAGE_MAX_BYTES {
return Err(format!(
"project.bootstrap {label} 超过 {} 字节上限",
BOOTSTRAP_PACKAGE_MAX_BYTES
));
}
prepare_game_creator_private_path_for_read(path, false, label)?;
fs::read(path).map_err(|error| format!("读取 {label} 失败:{error}"))
}
pub(crate) async fn run_project_bootstrap_at(
root: &Path,
timeout_seconds: u64,
) -> Result<ProjectBootstrapResult, String> {
validate_project_root(root)?;
let game = resolve_local_project_path(root, "game")?;
if !game.is_dir() {
return Err("project.bootstrap 只允许项目内 game 目录".to_string());
}
let package = read_bootstrap_file(&game.join("package.json"), "game/package.json")?;
let package_json: serde_json::Value = serde_json::from_slice(&package)
.map_err(|error| format!("解析 game/package.json 失败:{error}"))?;
if let Some(manager) = package_json
.get("packageManager")
.and_then(serde_json::Value::as_str)
{
if !manager.trim().starts_with("npm@") && manager.trim() != "npm" {
return Err("project.bootstrap 当前只支持 npm packageManager".to_string());
}
}
for lock_name in ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"] {
if game.join(lock_name).exists() {
return Err(format!("project.bootstrap 检测到非 npm 锁文件 {lock_name}"));
}
}
if !package_json
.get("scripts")
.is_some_and(serde_json::Value::is_object)
{
return Err("project.bootstrap 要求 game/package.json 包含 scripts 对象".to_string());
}
let lock = match fs::symlink_metadata(game.join("package-lock.json")) {
Ok(_) => Some(read_bootstrap_file(
&game.join("package-lock.json"),
"game/package-lock.json",
)?),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
Err(error) => return Err(format!("读取 game/package-lock.json 失败:{error}")),
};
let command = crate::command_exec::run_project_bootstrap_command_at(root, timeout_seconds)
.await
.map_err(|error| error.to_string())?;
let completed = command.exit_code == Some(0) && !command.timed_out;
let status = if completed { "completed" } else { "failed" };
let updated_at = unix_timestamp();
let log_path = resolve_local_project_path(root, ".agent/logs/command.log")?;
let package_sha256 = format!("{:x}", Sha256::digest(&package));
let lock_sha256 = lock
.as_ref()
.map(|bytes| format!("{:x}", Sha256::digest(bytes)));
let output = sanitize_project_verification_output(&command.output);
let line = format!(
"{updated_at} project.bootstrap status={} packageSha256={} lockSha256={} cwd=game\n{}\n",
status,
package_sha256,
lock_sha256.as_deref().unwrap_or("none"),
output
);
append_game_creator_private_file(&log_path, line.as_bytes(), "命令日志")?;
record_command_run(
root,
GameCreationAppCommandRunState {
command_id: "project.bootstrap".to_string(),
status: if completed {
GameCreationAppCommandRunStatus::Completed
} else {
GameCreationAppCommandRunStatus::Failed
},
output: output.clone(),
log_path: ".agent/logs/command.log".to_string(),
updated_at,
},
)?;
Ok(ProjectBootstrapResult {
status: status.to_string(),
output,
package_sha256,
lock_sha256,
log_path: ".agent/logs/command.log".to_string(),
updated_at,
})
}
@@ -659,30 +659,9 @@ where
})
}
pub(crate) async fn run_project_verification_with_commit_at<F>(
root: &Path,
script: &str,
expected_command: &str,
timeout_seconds: u64,
cwd_relative: &str,
durable_commit: F,
) -> Result<ProjectVerificationResult, String>
where
F: FnOnce() -> Result<(), String>,
{
let spec = resolve_project_verification_spec_with_cwd_at(
root,
script,
expected_command,
timeout_seconds,
cwd_relative,
)?;
run_project_verification_spec_with_commit_at(root, spec, durable_commit).await
}
/// 跑一份**已解析好**的验证规格。
///
/// 从 [`run_project_verification_with_commit_at`] 拆出来,是为了让调用方能先把「项目侧预检」
/// 从「解析 + 运行」合一的入口拆出来,是为了让调用方能先把「项目侧预检」
/// (package.json / 脚本缺失或漂移 / 包管理器不支持等)与「跑起来之后的宿主侧事实」分开处理:
/// 预检错误由调用方自己决定怎么表达,不必跟沙箱、日志这类宿主故障混成同一个字符串错误。
pub(crate) async fn run_project_verification_spec_with_commit_at<F>(