Compare commits

..

No commits in common. "19b9388ccfe5ff0ac262fe3224c757d403d775bd" and "367df5b88fb273afaf87b355a4b21bc498a3dec9" have entirely different histories.

5 changed files with 8 additions and 50 deletions

View File

@ -145,8 +145,7 @@ skill 只是五个接入面之一,兼容层覆盖全部五面。
② Codex CLI 0.155.0-alpha.9.2(codex exec --json -s read-only,-c mcp_servers.ljprobe.url 临时覆盖,用户默认模型,cwd 为空目录):不支持。实验组、对照组都答 NONE;桩服务日志证明 initialize(codex-mcp-client,协议 2025-06-18)与 tools/list 均成功,即说明已送达客户端、但没进入模型;两组输入 token 完全相同(16320 = 16320;换宽问法「全部上下文里是否有 LJ-P7- 开头的字符串」为 16335 = 16335);该会话 rollout 文件中标记出现 0 次。正向对照:让模型调用 probe_ping,它确实发起 mcp_tool_call(server=ljprobe, tool=probe_ping)(被 approval policy never 拦下),说明桩服务的工具在模型视野里,NONE 不是没连上。加开发中特性 --enable mcp_2026_07_28 后仍答 NONE,输入 token 仍为 16320。 ② Codex CLI 0.155.0-alpha.9.2(codex exec --json -s read-only,-c mcp_servers.ljprobe.url 临时覆盖,用户默认模型,cwd 为空目录):不支持。实验组、对照组都答 NONE;桩服务日志证明 initialize(codex-mcp-client,协议 2025-06-18)与 tools/list 均成功,即说明已送达客户端、但没进入模型;两组输入 token 完全相同(16320 = 16320;换宽问法「全部上下文里是否有 LJ-P7- 开头的字符串」为 16335 = 16335);该会话 rollout 文件中标记出现 0 次。正向对照:让模型调用 probe_ping,它确实发起 mcp_tool_call(server=ljprobe, tool=probe_ping)(被 approval policy never 拦下),说明桩服务的工具在模型视野里,NONE 不是没连上。加开发中特性 --enable mcp_2026_07_28 后仍答 NONE,输入 token 仍为 16320。
③ DSH(桌面版内置运行时 headless;--patch 叠加层停用用户已有的三个 MCP 条目、插入指向桩服务的 dsh-mcp-client 条目,用户 ~/.dsh 与 DSH_HOME 均未改):支持。实验组答出 LJ-P7-B0628021BF,思考过程原样复述了说明全文;对照组答 NONE;实验组输入 token 多 53(6487 对 6434)。DSH 在 initialize 前先发 server/discover,桩服务与炼境一样回 -32601,DSH 随即回退到 initialize → tools/list;其 mcp-client 配置有 maxInstructionBytes(默认 32768)。 ③ DSH(桌面版内置运行时 headless;--patch 叠加层停用用户已有的三个 MCP 条目、插入指向桩服务的 dsh-mcp-client 条目,用户 ~/.dsh 与 DSH_HOME 均未改):支持。实验组答出 LJ-P7-B0628021BF,思考过程原样复述了说明全文;对照组答 NONE;实验组输入 token 多 53(6487 对 6434)。DSH 在 initialize 前先发 server/discover,桩服务与炼境一样回 -32601,DSH 随即回退到 initialize → tools/list;其 mcp-client 配置有 maxInstructionBytes(默认 32768)。
探针脚本、桩服务与原始日志留在本机 %TEMP%\lj-p7-probe(未入库,P4 固化时可参考)。" 探针脚本、桩服务与原始日志留在本机 %TEMP%\lj-p7-probe(未入库,P4 固化时可参考)。"
- known_limits: "Claude 与 DSH 只验了无头模式,Claude 只用了 sonnet-4-6;Codex 未验 TUI 与桌面版(桌面版可能走 app-server 路径,结论可能不同),也未验 SSE 传输。探针用的是桩服务的中英混合短说明,未用炼境真实说明(中文 238 字、UTF-8 500 字节,远低于 DSH 默认上限 32768 字节)实测。Codex 后续版本若开始带入服务器说明,按「检测行为而非嗅探版本」重跑本探针(P4 固化后纳入矩阵),三端都支持时再迁移。现有服务器说明五条要点中,「先读状态」「canonical project_id」「开发变量先查后登记」在 skill 有对应条目;「其他写操作须用户明确确认」与「unavailable/unknown 不得当作成功」在 skill 与 AGENTS.md 中只有分场景表述(合并 PR、CI workflow、工具链安装;MCP 不可用时不伪造诊断),Codex 会话只能从这些条目与工具描述获知——是否补成通用条目留待下次改 skill 时一并处理(需升接入包版本),本卡不改。 - known_limits: "Claude 与 DSH 只验了无头模式,Claude 只用了 sonnet-4-6;Codex 未验 TUI 与桌面版(桌面版可能走 app-server 路径,结论可能不同),也未验 SSE 传输。探针用的是桩服务的中英混合短说明,未用炼境真实说明(中文 238 字、UTF-8 500 字节,远低于 DSH 默认上限 32768 字节)实测。Codex 后续版本若开始带入服务器说明,按「检测行为而非嗅探版本」重跑本探针(P4 固化后纳入矩阵),三端都支持时再迁移。现有服务器说明五条要点中,「先读状态」「canonical project_id」「开发变量先查后登记」在 skill 有对应条目;「其他写操作须用户明确确认」与「unavailable/unknown 不得当作成功」在 skill 与 AGENTS.md 中只有分场景表述(合并 PR、CI workflow、工具链安装;MCP 不可用时不伪造诊断),Codex 会话只能从这些条目与工具描述获知——是否补成通用条目留待下次改 skill 时一并处理(需升接入包版本),本卡不改。"
已补齐(2026-10-03,分支 feat/agent-client-parity-p7-skill-rules):两条已作为通用边界写进 lian-jing skill 模板开头(双投影到 .agents 与 .claude),两条规则的核心句与服务器说明逐字一致;只读查询、登记开发需求元数据与回传炼境明确列为无需确认的例外,与服务器说明的「其他写操作」及 skill「回传不必逐条征得同意」相容。服务器说明的例外是泛称「不含秘密的开发需求元数据」,skill 例外括注同时列出两个登记工具 register_project_test_account 与 register_project_dev_variable(审查修复:初版只列前者,会把开发变量登记误归入须确认的写操作);同批修正 skill 第 8 条——开发变量改用 register_project_dev_variable 登记名称与用途,测试账号仍用 register_project_test_account(初版两者都指向后者)。接入包 base-onboarding 1.7.0→1.8.0(修复在 1.8.0 发布前,未再升版本,只更新指纹);mcp_server 新增 server_instruction_universal_rules_are_in_skill,服务器说明与 skill 任一侧缺这两条即失败,skill 例外行缺任一登记工具也失败。AGENTS.md 托管块未改,仍只有分场景表述;存量项目需重建安装炼境后重跑 apply_onboarding_pack 才拿到新 skill。"
### 📋 P8 — 一键接入:创建/添加子项目即完成三端可用 ### 📋 P8 — 一键接入:创建/添加子项目即完成三端可用
- status: todo - status: todo

View File

@ -7,12 +7,7 @@ description: 读取炼境项目健康状态,并使用炼境 MCP 的项目管
开始项目工作时,先读取根 `AGENTS.md` 与 `.lian-jing/context.json` 取得 `project_id`。`.claude/liangjing.json` 仅为旧项目的 Claude 兼容投影,不能作为长期身份来源。 开始项目工作时,先读取根 `AGENTS.md` 与 `.lian-jing/context.json` 取得 `project_id`。`.claude/liangjing.json` 仅为旧项目的 Claude 兼容投影,不能作为长期身份来源。
如果当前客户端已发现 `lian-jing` MCP,所有调用都遵守两条通用边界(与炼境 MCP 服务器说明一致): 如果当前客户端已发现 `lian-jing` MCP:
- 只读查询、登记不含秘密的开发需求元数据(`register_project_test_account`、`register_project_dev_variable`)与回传炼境(`report_envoy_return`)可直接调用;其他写操作仅在用户明确确认目标与影响后调用,下文安装工具链、合并 PR、生成 CI workflow 是它的具体场景。
- 状态 `unavailable` / `unknown` 不得当作成功:如实报告实际状态,不据此宣称完成。
具体用法:
1. scoped MCP 连接已由 URL 绑定当前 `project_id`,项目级调用可省略 `project_id`;若 server initialize 明确报告 legacy,迁移前才显式传入 canonical `project_id`,绝不传其他项目 ID。 1. scoped MCP 连接已由 URL 绑定当前 `project_id`,项目级调用可省略 `project_id`;若 server initialize 明确报告 legacy,迁移前才显式传入 canonical `project_id`,绝不传其他项目 ID。
2. 优先调用 `get_diagnostics`,将其作为当前项目健康状态的事实来源。 2. 优先调用 `get_diagnostics`,将其作为当前项目健康状态的事实来源。
@ -21,7 +16,7 @@ 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` 登记名称、目标系统、非生产环境与用途,开发变量用 `register_project_dev_variable` 登记名称与用途——再请用户在炼境项目 UI「开发变量」中填写。不得在对话中向用户索要明文密码,不得登记生产账号,不得把读到的值写入受 Git 跟踪的文件。读取因缺少项目认证被拒时,如实报告并改用项目原有配置方式,不得绕过认证。若 lian-jing 工具中根本没有 `list_project_dev_variables`,说明当前客户端连的是全局地址 `/mcp` 而非本项目地址,按下方 `configured` 一步处理。 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` 一步处理。
9. 遇到体系级摩擦时,按下方「遇到摩擦时回传炼境」调用 `report_envoy_return`。 9. 遇到体系级摩擦时,按下方「遇到摩擦时回传炼境」调用 `report_envoy_return`。
## 遇到摩擦时回传炼境 ## 遇到摩擦时回传炼境

View File

@ -3,7 +3,7 @@
"packs": [ "packs": [
{ {
"pack_id": "base-onboarding", "pack_id": "base-onboarding",
"version": "1.8.0", "version": "1.7.0",
"supported_clients": ["codex", "claude", "dsh"], "supported_clients": ["codex", "claude", "dsh"],
"project_artifacts": [ "project_artifacts": [
"AGENTS.md", "AGENTS.md",

View File

@ -19,14 +19,14 @@ const TMPL_GIT_WORKFLOW: &str = include_str!("../../resources/onboarding/git-wor
const TMPL_ENVOY: &str = include_str!("../../resources/onboarding/.claude/commands/envoy.md"); const TMPL_ENVOY: &str = include_str!("../../resources/onboarding/.claude/commands/envoy.md");
const TMPL_BLUEPRINT_GATE: &str = const TMPL_BLUEPRINT_GATE: &str =
include_str!("../../resources/onboarding/.claude/hooks/blueprint-gate.cjs"); include_str!("../../resources/onboarding/.claude/hooks/blueprint-gate.cjs");
pub(crate) const TMPL_LIANJING_SKILL: &str = const TMPL_LIANJING_SKILL: &str =
include_str!("../../resources/onboarding/.agents/skills/lian-jing/SKILL.md"); include_str!("../../resources/onboarding/.agents/skills/lian-jing/SKILL.md");
const TMPL_CAPABILITY_PACKS: &str = const TMPL_CAPABILITY_PACKS: &str =
include_str!("../../resources/onboarding/.lian-jing/capability-packs.json"); include_str!("../../resources/onboarding/.lian-jing/capability-packs.json");
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.8.0"; const BASE_ONBOARDING_PACK_VERSION: &str = "1.7.0";
const TOOLCHAIN_PACK_VERSION: &str = "1.0.0"; const TOOLCHAIN_PACK_VERSION: &str = "1.0.0";
/// 单个模板的分发结果。 /// 单个模板的分发结果。
@ -1947,8 +1947,8 @@ mod tests {
const TEMPLATE_FINGERPRINTS: [(&str, &str, &str); 3] = [ const TEMPLATE_FINGERPRINTS: [(&str, &str, &str); 3] = [
( (
"resources/onboarding/**", "resources/onboarding/**",
"1.8.0", "1.7.0",
"fac04a0bd9f3a443349fdd50d6eef22147c56d0843e1769a60b9bbddc372d091", "0c39acd7c0513a1063ea0c2de5da06ba2baa69b7eb85b167499859ab6ad0eb45",
), ),
( (
"AGENTS.md.tmpl#ONBOARDING_MANAGED", "AGENTS.md.tmpl#ONBOARDING_MANAGED",

View File

@ -533,42 +533,6 @@ mod tests {
); );
} }
/// 服务器说明只有部分客户端带入模型上下文(Codex 不带,见蓝图 agent-client-parity P7),
/// 其中三端都须遵守的通用规则必须同时写在接入包 lian-jing skill 里。
#[test]
fn server_instruction_universal_rules_are_in_skill() {
let skill = crate::commands::onboarding::TMPL_LIANJING_SKILL;
for phrase in [
"其他写操作仅在用户明确确认目标与影响后调用",
"unavailable",
"unknown",
"不得当作成功",
] {
assert!(
MCP_SERVER_INSTRUCTIONS.contains(phrase),
"服务器说明缺少「{phrase}」"
);
assert!(skill.contains(phrase), "lian-jing skill 缺少「{phrase}」");
}
// 服务器说明的免确认例外是泛称「开发需求元数据」,两个登记工具都属于;
// skill 的例外括注须同时列出两者,否则开发变量登记会被误归入「其他写操作」。
assert!(MCP_SERVER_INSTRUCTIONS.contains("开发需求元数据"));
let exception = skill
.lines()
.find(|line| line.contains("其他写操作仅在用户明确确认目标与影响后调用"))
.expect("lian-jing skill 缺少写操作确认规则");
for term in [
"开发需求元数据",
"register_project_test_account",
"register_project_dev_variable",
] {
assert!(
exception.contains(term),
"lian-jing skill 免确认例外缺少「{term}」"
);
}
}
#[tokio::test] #[tokio::test]
async fn scoped_handshake_binds_project_and_filters_global_tools() { async fn scoped_handshake_binds_project_and_filters_global_tools() {
let init = process_rpc(&rpc("initialize"), Some("p1")).await.unwrap(); let init = process_rpc(&rpc("initialize"), Some("p1")).await.unwrap();