fix(onboarding): recognize custom lefthook checks #10
@ -465,7 +465,7 @@ modules:
|
||||
area: infra
|
||||
status: done
|
||||
progress: 100
|
||||
note: "P2 完成:工具无关 canonical context、版本化能力包自检与 Codex Skill-only 投影已闭环;不复制 MCP、凭证或部署能力。"
|
||||
note: "P2 完成:工具无关 canonical context、版本化能力包自检、Codex Skill-only 投影与项目自定义 lefthook 等价能力识别已闭环;不复制 MCP、凭证或部署能力。"
|
||||
updated: 2026-09-21
|
||||
position: [2250, 800]
|
||||
edges:
|
||||
|
||||
@ -56,3 +56,10 @@ Codex 用户级插件
|
||||
- files: AGENTS.md, docs/ai-context/project-brief.md, docs/ai-context/project-context.yaml, docs/templates/agents-meta-rules.md, resources/onboarding/
|
||||
- acceptance: "[cargo test] 接入包升级检查 → 旧 context/旧能力包给出明确 upgrade_hint 且不自动覆盖用户配置;[rg] 常驻架构文档将炼境描述为项目控制面、客户端描述为 adapter,且不把客户端插件表述为凭证或部署所有者。"
|
||||
- done_note: "2026-09-21:base-onboarding 升至 v1.1.0;旧能力包会显式返回 version_mismatch 与升级提示。托管模板、Git 工作流与常驻架构文档统一为 canonical context / 控制面 / 客户端 adapter 边界。"
|
||||
|
||||
### ✅ P2-E. 自定义 lefthook 等价能力自检
|
||||
- status: done
|
||||
- complexity: S
|
||||
- files: src-tauri/src/commands/onboarding.rs
|
||||
- acceptance: "[cargo test] verify_onboarding 的 lefthook 自检 → web/API 分层 typecheck 与 commit-msg scripts 中的 Conventional 校验被识别为 ready;缺少等价能力时仍返回 missing,不因注释或无关键误报。"
|
||||
- done_note: "2026-09-21:自检保留模板为基线,但支持命名为 typecheck-* 的分层 pre-commit 命令,以及 commit-msg scripts 中含 conventional 的校验;其余基线能力仍要求明确存在,避免自定义配置被误报或宽松放行。"
|
||||
|
||||
@ -592,27 +592,39 @@ fn check_origin(origin_host: Option<&str>, instance_hosts: &[String]) -> (bool,
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 lefthook.yml 内容提取 commands 键集合(` commands:` 下缩进 4 空格的键行)。
|
||||
/// 纯函数;注释与更深缩进(run: | 多行体等)忽略。用于 U1 检查项自省——
|
||||
/// 期望键集合取自内嵌模板,禁止硬编码具体检查项名(模板演进校验器零维护)。
|
||||
pub fn lefthook_command_keys(yml: &str) -> Vec<String> {
|
||||
/// 从指定 hook 的指定段提取缩进四空格的键集合。
|
||||
/// 纯函数;注释与更深缩进(run: | 多行体等)忽略。
|
||||
fn lefthook_section_keys(yml: &str, hook: &str, section: &str) -> Vec<String> {
|
||||
let mut keys = Vec::new();
|
||||
let mut in_commands = false;
|
||||
let mut in_hook = false;
|
||||
let mut in_section = false;
|
||||
for line in yml.lines() {
|
||||
let trimmed = line.trim_start();
|
||||
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
let indent = line.len() - trimmed.len();
|
||||
if indent == 2 && trimmed.trim_end() == "commands:" {
|
||||
in_commands = true;
|
||||
if indent == 0 && trimmed.ends_with(':') {
|
||||
in_hook = trimmed.trim_end().trim_end_matches(':') == hook;
|
||||
in_section = false;
|
||||
continue;
|
||||
}
|
||||
if indent < 4 {
|
||||
in_commands = false;
|
||||
if !in_hook {
|
||||
continue;
|
||||
}
|
||||
if in_commands && indent == 4 {
|
||||
if indent == 2 && trimmed.trim_end() == format!("{section}:") {
|
||||
in_section = true;
|
||||
continue;
|
||||
}
|
||||
if indent == 2 {
|
||||
in_section = false;
|
||||
continue;
|
||||
}
|
||||
if indent < 2 {
|
||||
in_section = false;
|
||||
continue;
|
||||
}
|
||||
if in_section && indent == 4 {
|
||||
if let Some(key) = trimmed.trim_end().strip_suffix(':') {
|
||||
keys.push(key.to_string());
|
||||
}
|
||||
@ -621,23 +633,43 @@ pub fn lefthook_command_keys(yml: &str) -> Vec<String> {
|
||||
keys
|
||||
}
|
||||
|
||||
/// U1 检查:项目 lefthook.yml 是否缺少模板要求的检查段(lefthook 不自动覆盖,只检测落后)。
|
||||
/// 从 lefthook.yml 内容提取 commands 键集合。
|
||||
pub fn lefthook_command_keys(yml: &str) -> Vec<String> {
|
||||
let mut keys = lefthook_section_keys(yml, "pre-commit", "commands");
|
||||
keys.extend(lefthook_section_keys(yml, "commit-msg", "commands"));
|
||||
keys
|
||||
}
|
||||
|
||||
/// U1 检查:项目 lefthook.yml 是否缺少模板要求的检查能力。
|
||||
///
|
||||
/// lefthook.yml 是项目可定制文件,不能把模板中的键名当成唯一实现。保留模板
|
||||
/// 自省作为基线,但识别两类等价能力:分层 typecheck 命令,以及 commit-msg
|
||||
/// 目录中的 Conventional script。其余模板检查仍要求同名命令,避免将任意注释或
|
||||
/// 无关脚本误报为 ready。
|
||||
fn check_lefthook_keys(project_yml: Option<&str>, template_yml: &str) -> (bool, String) {
|
||||
let Some(yml) = project_yml else {
|
||||
return (false, "lefthook.yml 缺失——重跑接入包写入".to_string());
|
||||
};
|
||||
let have: Vec<String> = lefthook_command_keys(yml);
|
||||
let commands = lefthook_command_keys(yml);
|
||||
let commit_msg_scripts = lefthook_section_keys(yml, "commit-msg", "scripts");
|
||||
let missing: Vec<String> = lefthook_command_keys(template_yml)
|
||||
.into_iter()
|
||||
.filter(|k| !have.contains(k))
|
||||
.filter(|required| match required.as_str() {
|
||||
"typecheck" => !commands.iter().any(|key| key.contains("typecheck")),
|
||||
"conventional" => !commands
|
||||
.iter()
|
||||
.chain(commit_msg_scripts.iter())
|
||||
.any(|key| key.contains("conventional")),
|
||||
_ => !commands.contains(required),
|
||||
})
|
||||
.collect();
|
||||
if missing.is_empty() {
|
||||
(true, "模板要求的检查段齐全".to_string())
|
||||
(true, "模板要求的检查能力齐全".to_string())
|
||||
} else {
|
||||
(
|
||||
false,
|
||||
format!(
|
||||
"缺少检查段: {}——lefthook.yml 不自动覆盖,请手动对照模板补齐",
|
||||
"缺少检查能力: {}——lefthook.yml 不自动覆盖,请手动对照模板补齐",
|
||||
missing.join(", ")
|
||||
),
|
||||
)
|
||||
@ -1201,14 +1233,14 @@ mod tests {
|
||||
);
|
||||
|
||||
// 项目文件与模板一致 → pass
|
||||
let (pass, _) = check_lefthook_keys(Some(TMPL_LEFTHOOK), TMPL_LEFTHOOK);
|
||||
assert!(pass);
|
||||
let (pass, detail) = check_lefthook_keys(Some(TMPL_LEFTHOOK), TMPL_LEFTHOOK);
|
||||
assert!(pass, "模板应满足自身检查: {detail}");
|
||||
|
||||
// 缺段 → fail 且列出缺失键
|
||||
let partial = "pre-commit:\n commands:\n typecheck:\n run: pnpm typecheck\n";
|
||||
let (pass, detail) = check_lefthook_keys(Some(partial), TMPL_LEFTHOOK);
|
||||
assert!(!pass);
|
||||
assert!(detail.contains("缺少检查段"), "应列出缺失键: {detail}");
|
||||
assert!(detail.contains("缺少检查能力"), "应列出缺失键: {detail}");
|
||||
assert!(
|
||||
!detail.contains("typecheck,") && detail.contains("blueprint-gate"),
|
||||
"已有键不在缺失列表: {detail}"
|
||||
@ -1216,6 +1248,29 @@ mod tests {
|
||||
|
||||
// 文件缺失 → fail
|
||||
assert!(!check_lefthook_keys(None, TMPL_LEFTHOOK).0);
|
||||
|
||||
// 项目可将 typecheck 拆到 web/API,并把 Conventional 校验声明为 commit-msg script。
|
||||
let custom = r#"
|
||||
pre-commit:
|
||||
commands:
|
||||
typecheck-web:
|
||||
run: pnpm --filter web typecheck
|
||||
typecheck-api:
|
||||
run: pnpm --filter api typecheck
|
||||
blueprint-gate:
|
||||
run: node scripts/blueprint-gate.cjs || true
|
||||
commit-msg:
|
||||
scripts:
|
||||
"check-conventional.sh":
|
||||
runner: bash
|
||||
commands:
|
||||
feature-confirmed:
|
||||
run: test -n "$1"
|
||||
"#;
|
||||
assert!(
|
||||
check_lefthook_keys(Some(custom), TMPL_LEFTHOOK).0,
|
||||
"等价的项目自定义检查不应被误报缺失"
|
||||
);
|
||||
}
|
||||
|
||||
// ── B4:使者回传覆盖率(沉默项目信号)────────────────────────────────────
|
||||
|
||||
Loading…
Reference in New Issue
Block a user