@ -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: 使者回传覆盖率( 沉默项目信号) ────────────────────────────────────