feat(onboarding): lian-jing skill 双投影到 Claude,接入包升至 1.6.0
All checks were successful
Push & PR Check / check (push) Successful in 1m4s
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:
parent
8babc545c3
commit
f4e75dc91f
@ -66,12 +66,13 @@ progress: 0
|
|||||||
- acceptance: "agent_verify: [三端无头探针] 在 enterprise 目录 Claude 与 DSH 的 C 项均看到开发变量工具(项目工具集),E 项成功;项目目录外的会话不获得他项目工具。"
|
- acceptance: "agent_verify: [三端无头探针] 在 enterprise 目录 Claude 与 DSH 的 C 项均看到开发变量工具(项目工具集),E 项成功;项目目录外的会话不获得他项目工具。"
|
||||||
- 注:吸收原 project-dev-variables V17(Claude 以 local scope + headersHelper 取值)。
|
- 注:吸收原 project-dev-variables V17(Claude 以 local scope + headersHelper 取值)。
|
||||||
|
|
||||||
### 🔵 P2 — Claude skill 投影
|
### ✅ P2 — Claude skill 投影
|
||||||
- status: in_progress
|
- status: done
|
||||||
- complexity: S
|
- complexity: S
|
||||||
- files: src-tauri/src/commands/onboarding.rs, src-tauri/resources/onboarding/
|
- files: src-tauri/src/commands/onboarding.rs, src-tauri/resources/onboarding/
|
||||||
- depends: H3
|
- depends: H3
|
||||||
- acceptance: "agent_verify: [cargo test onboarding] 接入包同时写 .claude/skills/lian-jing/SKILL.md,内容与 .agents/skills 单一来源一致;[Claude 无头探针] B 项看到 lian-jing。"
|
- 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 入口,项目配置去本机路径
|
### 📋 P3 — 稳定 helper 入口,项目配置去本机路径
|
||||||
- status: todo
|
- status: todo
|
||||||
|
|||||||
@ -25,7 +25,7 @@ contracts:
|
|||||||
- ".lian-jing/context.json"
|
- ".lian-jing/context.json"
|
||||||
- ".lian-jing/capability-packs.json"
|
- ".lian-jing/capability-packs.json"
|
||||||
- ".claude/liangjing.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 接口(前后端契约)
|
- label: Tauri command 接口(前后端契约)
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
@ -16,11 +16,11 @@ description: 读取炼境项目健康状态,并使用炼境 MCP 的项目管
|
|||||||
5. 交付、Gitea、CI、蓝图等平台状态优先通过 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)`;合并后再次读取交付状态。不得自行推断确认。
|
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 保存。
|
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 未出现,按以下顺序降级,不猜测状态:
|
如果 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 服务;连接拒绝表示炼境未运行,先启动正式安装版并保持托盘进程。
|
2. `reachable`:配置正确后检查 loopback 服务;连接拒绝表示炼境未运行,先启动正式安装版并保持托盘进程。
|
||||||
3. `reload`:服务可达但当前任务看不到工具时,新建 Codex 任务或重载客户端;不要反复改配置或复制凭证。
|
3. `reload`:服务可达但当前会话看不到工具时,新建会话或重载客户端;不要反复改配置或复制凭证。
|
||||||
4. 仍不可用时,明确报告停在哪一层,继续完成不依赖 MCP 的本地工作;不得伪造诊断、项目 ID、凭证或远端状态。
|
4. 仍不可用时,明确报告停在哪一层,继续完成不依赖 MCP 的本地工作;不得伪造诊断、项目 ID、凭证或远端状态。
|
||||||
|
|||||||
@ -3,11 +3,12 @@
|
|||||||
"packs": [
|
"packs": [
|
||||||
{
|
{
|
||||||
"pack_id": "base-onboarding",
|
"pack_id": "base-onboarding",
|
||||||
"version": "1.5.0",
|
"version": "1.6.0",
|
||||||
"supported_clients": ["codex", "claude"],
|
"supported_clients": ["codex", "claude", "dsh"],
|
||||||
"project_artifacts": [
|
"project_artifacts": [
|
||||||
"AGENTS.md",
|
"AGENTS.md",
|
||||||
".agents/skills/lian-jing/SKILL.md",
|
".agents/skills/lian-jing/SKILL.md",
|
||||||
|
".claude/skills/lian-jing/SKILL.md",
|
||||||
".lian-jing/context.json"
|
".lian-jing/context.json"
|
||||||
],
|
],
|
||||||
"self_check": "verify_onboarding",
|
"self_check": "verify_onboarding",
|
||||||
|
|||||||
@ -26,7 +26,7 @@ const TMPL_CAPABILITY_PACKS: &str =
|
|||||||
const TMPL_MISE: &str = include_str!("../../resources/onboarding/mise.toml.tmpl");
|
const TMPL_MISE: &str = include_str!("../../resources/onboarding/mise.toml.tmpl");
|
||||||
const PROJECT_CONTEXT_SCHEMA_VERSION: u64 = 1;
|
const PROJECT_CONTEXT_SCHEMA_VERSION: u64 = 1;
|
||||||
const CAPABILITY_PACK_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";
|
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",
|
".agents/skills/lian-jing/SKILL.md",
|
||||||
TMPL_LIANJING_SKILL,
|
TMPL_LIANJING_SKILL,
|
||||||
),
|
),
|
||||||
|
// Claude Code 不读 .agents/skills,同一模板投影一份(单一来源,双投影)。
|
||||||
|
dispatch_fully_managed(
|
||||||
|
root,
|
||||||
|
".claude/skills/lian-jing/SKILL.md",
|
||||||
|
TMPL_LIANJING_SKILL,
|
||||||
|
),
|
||||||
dispatch_fully_managed(
|
dispatch_fully_managed(
|
||||||
root,
|
root,
|
||||||
".lian-jing/capability-packs.json",
|
".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());
|
let (pass, detail) = check_skill(read(".agents/skills/lian-jing/SKILL.md").as_deref());
|
||||||
checks.push(VerifyCheck::from_pass("Codex 炼境技能已分发", pass, detail));
|
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(
|
checks.push(check_codex_mcp(
|
||||||
read(".codex/config.toml").as_deref(),
|
read(".codex/config.toml").as_deref(),
|
||||||
project_id,
|
project_id,
|
||||||
@ -1310,7 +1318,7 @@ mod tests {
|
|||||||
|
|
||||||
// 空目录:所有检查均不通过(真实反映"什么都没接")
|
// 空目录:所有检查均不通过(真实反映"什么都没接")
|
||||||
let checks = verify_onboarding(&conn, "p1", &base);
|
let checks = verify_onboarding(&conn, "p1", &base);
|
||||||
assert_eq!(checks.len(), 11, "检查链应为十一项");
|
assert_eq!(checks.len(), 12, "检查链应为十二项");
|
||||||
assert!(checks.iter().all(|c| !c.pass), "空项目应全部不通过");
|
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]
|
#[test]
|
||||||
fn skill_check_requires_canonical_context() {
|
fn skill_check_requires_canonical_context() {
|
||||||
let legacy = "---\nname: lian-jing\n---\n读取 .claude/liangjing.json";
|
let legacy = "---\nname: lian-jing\n---\n读取 .claude/liangjing.json";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user