Compare commits
2 Commits
f4dc2de53c
...
46f75292ab
| Author | SHA1 | Date | |
|---|---|---|---|
| 46f75292ab | |||
|
|
057c2b3a10 |
@ -465,7 +465,7 @@ modules:
|
|||||||
area: infra
|
area: infra
|
||||||
status: done
|
status: done
|
||||||
progress: 100
|
progress: 100
|
||||||
note: "P2 完成:工具无关 canonical context、版本化能力包自检与 Codex Skill-only 投影已闭环;不复制 MCP、凭证或部署能力。"
|
note: "P2 完成:工具无关 canonical context、版本化能力包自检、Codex Skill-only 投影与项目自定义 lefthook 等价能力识别已闭环;不复制 MCP、凭证或部署能力。"
|
||||||
updated: 2026-09-21
|
updated: 2026-09-21
|
||||||
position: [2250, 800]
|
position: [2250, 800]
|
||||||
edges:
|
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/
|
- 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,且不把客户端插件表述为凭证或部署所有者。"
|
- acceptance: "[cargo test] 接入包升级检查 → 旧 context/旧能力包给出明确 upgrade_hint 且不自动覆盖用户配置;[rg] 常驻架构文档将炼境描述为项目控制面、客户端描述为 adapter,且不把客户端插件表述为凭证或部署所有者。"
|
||||||
- done_note: "2026-09-21:base-onboarding 升至 v1.1.0;旧能力包会显式返回 version_mismatch 与升级提示。托管模板、Git 工作流与常驻架构文档统一为 canonical context / 控制面 / 客户端 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 空格的键行)。
|
/// 从指定 hook 的指定段提取缩进四空格的键集合。
|
||||||
/// 纯函数;注释与更深缩进(run: | 多行体等)忽略。用于 U1 检查项自省——
|
/// 纯函数;注释与更深缩进(run: | 多行体等)忽略。
|
||||||
/// 期望键集合取自内嵌模板,禁止硬编码具体检查项名(模板演进校验器零维护)。
|
fn lefthook_section_keys(yml: &str, hook: &str, section: &str) -> Vec<String> {
|
||||||
pub fn lefthook_command_keys(yml: &str) -> Vec<String> {
|
|
||||||
let mut keys = Vec::new();
|
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() {
|
for line in yml.lines() {
|
||||||
let trimmed = line.trim_start();
|
let trimmed = line.trim_start();
|
||||||
if trimmed.is_empty() || trimmed.starts_with('#') {
|
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let indent = line.len() - trimmed.len();
|
let indent = line.len() - trimmed.len();
|
||||||
if indent == 2 && trimmed.trim_end() == "commands:" {
|
if indent == 0 && trimmed.ends_with(':') {
|
||||||
in_commands = true;
|
in_hook = trimmed.trim_end().trim_end_matches(':') == hook;
|
||||||
|
in_section = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if indent < 4 {
|
if !in_hook {
|
||||||
in_commands = false;
|
|
||||||
continue;
|
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(':') {
|
if let Some(key) = trimmed.trim_end().strip_suffix(':') {
|
||||||
keys.push(key.to_string());
|
keys.push(key.to_string());
|
||||||
}
|
}
|
||||||
@ -621,23 +633,43 @@ pub fn lefthook_command_keys(yml: &str) -> Vec<String> {
|
|||||||
keys
|
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) {
|
fn check_lefthook_keys(project_yml: Option<&str>, template_yml: &str) -> (bool, String) {
|
||||||
let Some(yml) = project_yml else {
|
let Some(yml) = project_yml else {
|
||||||
return (false, "lefthook.yml 缺失——重跑接入包写入".to_string());
|
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)
|
let missing: Vec<String> = lefthook_command_keys(template_yml)
|
||||||
.into_iter()
|
.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();
|
.collect();
|
||||||
if missing.is_empty() {
|
if missing.is_empty() {
|
||||||
(true, "模板要求的检查段齐全".to_string())
|
(true, "模板要求的检查能力齐全".to_string())
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
false,
|
false,
|
||||||
format!(
|
format!(
|
||||||
"缺少检查段: {}——lefthook.yml 不自动覆盖,请手动对照模板补齐",
|
"缺少检查能力: {}——lefthook.yml 不自动覆盖,请手动对照模板补齐",
|
||||||
missing.join(", ")
|
missing.join(", ")
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1201,14 +1233,14 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 项目文件与模板一致 → pass
|
// 项目文件与模板一致 → pass
|
||||||
let (pass, _) = check_lefthook_keys(Some(TMPL_LEFTHOOK), TMPL_LEFTHOOK);
|
let (pass, detail) = check_lefthook_keys(Some(TMPL_LEFTHOOK), TMPL_LEFTHOOK);
|
||||||
assert!(pass);
|
assert!(pass, "模板应满足自身检查: {detail}");
|
||||||
|
|
||||||
// 缺段 → fail 且列出缺失键
|
// 缺段 → fail 且列出缺失键
|
||||||
let partial = "pre-commit:\n commands:\n typecheck:\n run: pnpm typecheck\n";
|
let partial = "pre-commit:\n commands:\n typecheck:\n run: pnpm typecheck\n";
|
||||||
let (pass, detail) = check_lefthook_keys(Some(partial), TMPL_LEFTHOOK);
|
let (pass, detail) = check_lefthook_keys(Some(partial), TMPL_LEFTHOOK);
|
||||||
assert!(!pass);
|
assert!(!pass);
|
||||||
assert!(detail.contains("缺少检查段"), "应列出缺失键: {detail}");
|
assert!(detail.contains("缺少检查能力"), "应列出缺失键: {detail}");
|
||||||
assert!(
|
assert!(
|
||||||
!detail.contains("typecheck,") && detail.contains("blueprint-gate"),
|
!detail.contains("typecheck,") && detail.contains("blueprint-gate"),
|
||||||
"已有键不在缺失列表: {detail}"
|
"已有键不在缺失列表: {detail}"
|
||||||
@ -1216,6 +1248,29 @@ mod tests {
|
|||||||
|
|
||||||
// 文件缺失 → fail
|
// 文件缺失 → fail
|
||||||
assert!(!check_lefthook_keys(None, TMPL_LEFTHOOK).0);
|
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:使者回传覆盖率(沉默项目信号)────────────────────────────────────
|
// ── B4:使者回传覆盖率(沉默项目信号)────────────────────────────────────
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user