feat(onboarding): lian-jing skill 双投影到 Claude,接入包升至 1.6.0
All checks were successful
Push & PR Check / check (push) Successful in 1m4s

- 同一模板同时写 .agents/skills(Codex、DSH)与 .claude/skills(Claude Code)
- verify_onboarding 新增「Claude 炼境技能已分发」检查
- skill 降级步骤改为三端通用,提示无开发变量工具即连的是全局地址
- capability-packs supported_clients 增加 dsh

Feature-Confirmed: true
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
lanrtop 2026-10-01 09:11:32 +09:00
parent 8babc545c3
commit f4e75dc91f
5 changed files with 39 additions and 10 deletions

View File

@ -66,12 +66,13 @@ progress: 0
- acceptance: "agent_verify: [三端无头探针] 在 enterprise 目录 Claude 与 DSH 的 C 项均看到开发变量工具(项目工具集),E 项成功;项目目录外的会话不获得他项目工具。"
- 注:吸收原 project-dev-variables V17(Claude 以 local scope + headersHelper 取值)。
### 🔵 P2 — Claude skill 投影
- status: in_progress
### ✅ P2 — Claude skill 投影
- status: done
- complexity: S
- files: src-tauri/src/commands/onboarding.rs, src-tauri/resources/onboarding/
- depends: H3
- acceptance: "agent_verify: [cargo test onboarding] 接入包同时写 .claude/skills/lian-jing/SKILL.md,内容与 .agents/skills 单一来源一致;[Claude 无头探针] B 项看到 lian-jing。"
- verification: "2026-10-01:同一模板双投影到 .agents/skills 与 .claude/skills;verify_onboarding 新增「Claude 炼境技能已分发」(检查链 12 项);base-onboarding 升至 1.6.0,supported_clients 增 dsh;skill 降级步骤改为三端通用并提示「无开发变量工具即连的是全局地址」。新增测试 skill_is_projected_to_codex_and_claude_from_one_source;cargo test --locked 229 passed / 5 ignored。Enterprise 分发后 verify_onboarding 14/14,两份 skill 逐字节一致;Claude 无头会话 init 事件 skills 与 slash_commands 均含 lian-jing(基线探针时为不存在)。"
### 📋 P3 — 稳定 helper 入口,项目配置去本机路径
- status: todo

View File

@ -25,7 +25,7 @@ contracts:
- ".lian-jing/context.json"
- ".lian-jing/capability-packs.json"
- ".claude/liangjing.json"
doc: "`.lian-jing/context.json` 是 project_id 的工具无关单一事实来源;`.claude/liangjing.json` 仅为兼容投影;`.lian-jing/capability-packs.json` 是炼境分发基线的版本描述。迁移必须保留未知字段;验证必须返回显式状态与 upgrade_hint;升级仅覆盖炼境托管文件,绝不覆盖项目定制或手工 MCP 配置,且不得以客户端全局配置或项目 UI 身份替代项目根目录上下文。"
doc: "`.lian-jing/context.json` 是 project_id 的工具无关单一事实来源;`.claude/liangjing.json` 仅为兼容投影;`.lian-jing/capability-packs.json` 是炼境分发基线的版本描述。lian-jing skill 以同一模板双投影:`.agents/skills/lian-jing/`(Codex、DSH 读取)与 `.claude/skills/lian-jing/`(Claude Code 不读 .agents/skills),两份内容必须一致。迁移必须保留未知字段;验证必须返回显式状态与 upgrade_hint;升级仅覆盖炼境托管文件,绝不覆盖项目定制或手工 MCP 配置,且不得以客户端全局配置或项目 UI 身份替代项目根目录上下文。"
- label: Tauri command 接口(前后端契约)
paths:

View File

@ -16,11 +16,11 @@ description: 读取炼境项目健康状态,并使用炼境 MCP 的项目管
5. 交付、Gitea、CI、蓝图等平台状态优先通过 MCP 查询,不凭静态文档猜测。
6. 合并 PR 前调用 `get_delivery_status(project_id)`,向用户展示 PR 编号、完整 head SHA、CI 与 review。只有当前用户明确确认该 PR 与 SHA 后,才调用 `merge_pull_request(project_id, pr_number, expected_head_sha, confirmed=true)`;合并后再次读取交付状态。不得自行推断确认。
7. 生成 CI workflow 时先调用 `plan_cicd_workflow(project_id, workflow_type)`,向用户展示目标文件、变更类型、`content_digest`、工具链来源与所需 secret 名称;只有用户明确确认这份摘要后,才以同一 digest 调用 `apply_cicd_workflow(..., confirmed=true)`。该流程只写项目 workflow,不配置 secret、不触发 CI 或部署;不得通过 MCP 传服务器地址、命令、路径或 secret 值,部署类配置先由用户在炼境 UI 保存。
8. 需要测试账号或开发变量(登录自验、沙箱 key 等)时,先调用 `list_project_dev_variables` 查找:已配置则用 `read_project_test_account` / `read_project_dev_variable` 单项读取;没有则用 `register_project_test_account` 登记名称、目标系统、非生产环境与用途,并请用户在炼境项目 UI「开发变量」中填写。不得在对话中向用户索要明文密码,不得登记生产账号,不得把读到的值写入受 Git 跟踪的文件。读取因缺少项目认证被拒时(当前仅 Codex 经 helper 验证可读),如实报告并改用项目原有配置方式,不得绕过认证。
8. 需要测试账号或开发变量(登录自验、沙箱 key 等)时,先调用 `list_project_dev_variables` 查找:已配置则用 `read_project_test_account` / `read_project_dev_variable` 单项读取;没有则用 `register_project_test_account` 登记名称、目标系统、非生产环境与用途,并请用户在炼境项目 UI「开发变量」中填写。不得在对话中向用户索要明文密码,不得登记生产账号,不得把读到的值写入受 Git 跟踪的文件。读取因缺少项目认证被拒时,如实报告并改用项目原有配置方式,不得绕过认证。若 lian-jing 工具中根本没有 `list_project_dev_variables`,说明当前客户端连的是全局地址 `/mcp` 而非本项目地址,按下方 `configured` 一步处理。
如果 MCP 未出现,按以下顺序降级,不猜测状态:
1. `configured`:读取 `.codex/config.toml`,确认 `lian-jing` 指向 `http://127.0.0.1:<port>/mcp/projects/<canonical project_id>`;缺失或 legacy 时通过炼境重新 `inject_mcp`,手工同名 server 不覆盖。
1. `configured`:确认当前客户端的 `lian-jing` 指向 `http://127.0.0.1:<port>/mcp/projects/<canonical project_id>`——Codex 看项目 `.codex/config.toml`,Claude Code 看 `~/.claude.json` 中本项目的 local 条目(优先于 user 级全局条目),DSH 看 `~/.dsh/cordis.patch.yml`。缺失或仍指向全局 `/mcp` 时通过炼境重新 `inject_mcp`,手工同名 server 不覆盖。
2. `reachable`:配置正确后检查 loopback 服务;连接拒绝表示炼境未运行,先启动正式安装版并保持托盘进程。
3. `reload`:服务可达但当前任务看不到工具时,新建 Codex 任务或重载客户端;不要反复改配置或复制凭证。
3. `reload`:服务可达但当前会话看不到工具时,新建会话或重载客户端;不要反复改配置或复制凭证。
4. 仍不可用时,明确报告停在哪一层,继续完成不依赖 MCP 的本地工作;不得伪造诊断、项目 ID、凭证或远端状态。

View File

@ -3,11 +3,12 @@
"packs": [
{
"pack_id": "base-onboarding",
"version": "1.5.0",
"supported_clients": ["codex", "claude"],
"version": "1.6.0",
"supported_clients": ["codex", "claude", "dsh"],
"project_artifacts": [
"AGENTS.md",
".agents/skills/lian-jing/SKILL.md",
".claude/skills/lian-jing/SKILL.md",
".lian-jing/context.json"
],
"self_check": "verify_onboarding",

View File

@ -26,7 +26,7 @@ const TMPL_CAPABILITY_PACKS: &str =
const TMPL_MISE: &str = include_str!("../../resources/onboarding/mise.toml.tmpl");
const PROJECT_CONTEXT_SCHEMA_VERSION: u64 = 1;
const CAPABILITY_PACK_SCHEMA_VERSION: u64 = 1;
const BASE_ONBOARDING_PACK_VERSION: &str = "1.5.0";
const BASE_ONBOARDING_PACK_VERSION: &str = "1.6.0";
const TOOLCHAIN_PACK_VERSION: &str = "1.0.0";
/// 单个模板的分发结果。
@ -230,6 +230,12 @@ pub fn dispatch_templates(project_path: &str) -> Vec<TemplateResult> {
".agents/skills/lian-jing/SKILL.md",
TMPL_LIANJING_SKILL,
),
// Claude Code 不读 .agents/skills,同一模板投影一份(单一来源,双投影)。
dispatch_fully_managed(
root,
".claude/skills/lian-jing/SKILL.md",
TMPL_LIANJING_SKILL,
),
dispatch_fully_managed(
root,
".lian-jing/capability-packs.json",
@ -885,6 +891,8 @@ pub fn verify_onboarding(
let (pass, detail) = check_skill(read(".agents/skills/lian-jing/SKILL.md").as_deref());
checks.push(VerifyCheck::from_pass("Codex 炼境技能已分发", pass, detail));
let (pass, detail) = check_skill(read(".claude/skills/lian-jing/SKILL.md").as_deref());
checks.push(VerifyCheck::from_pass("Claude 炼境技能已分发", pass, detail));
checks.push(check_codex_mcp(
read(".codex/config.toml").as_deref(),
project_id,
@ -1310,7 +1318,7 @@ mod tests {
// 空目录:所有检查均不通过(真实反映"什么都没接")
let checks = verify_onboarding(&conn, "p1", &base);
assert_eq!(checks.len(), 11, "检查链应为十一项");
assert_eq!(checks.len(), 12, "检查链应为十二项");
assert!(checks.iter().all(|c| !c.pass), "空项目应全部不通过");
// 铺齐全部生效条件后:所有检查全通过
@ -1407,6 +1415,25 @@ mod tests {
);
}
#[test]
fn skill_is_projected_to_codex_and_claude_from_one_source() {
let base = std::env::temp_dir().join(format!(
"lianjing_skill_projection_{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&base).unwrap();
dispatch_templates(base.to_str().unwrap());
let codex = std::fs::read_to_string(base.join(".agents/skills/lian-jing/SKILL.md")).unwrap();
let claude = std::fs::read_to_string(base.join(".claude/skills/lian-jing/SKILL.md")).unwrap();
assert_eq!(codex, claude, "两端 skill 必须来自同一模板");
assert!(check_skill(Some(&claude)).0);
let packs: serde_json::Value = serde_json::from_str(TMPL_CAPABILITY_PACKS).unwrap();
let artifacts = packs["packs"][0]["project_artifacts"].to_string();
assert!(artifacts.contains(".claude/skills/lian-jing/SKILL.md"), "能力包清单须登记 Claude 投影");
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn skill_check_requires_canonical_context() {
let legacy = "---\nname: lian-jing\n---\n读取 .claude/liangjing.json";