diff --git a/.agents/skills/lian-jing/SKILL.md b/.agents/skills/lian-jing/SKILL.md
index f52f5f6..ce173ff 100644
--- a/.agents/skills/lian-jing/SKILL.md
+++ b/.agents/skills/lian-jing/SKILL.md
@@ -15,5 +15,6 @@ description: 读取炼境项目健康状态,并使用炼境 MCP 的项目管
4. 只有用户明确确认本次下载安装后,才可调用 `prepare_toolchain(project_id, confirmed=true)`;不得为适配客户端 bundled runtime 而修改项目版本或 lockfile。
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 保存。
如果 MCP 未出现,说明 Codex 项目配置尚未被加载、项目未被信任,或炼境未运行。说明该限制后继续完成不依赖 MCP 的本地工作;不得伪造诊断结果。
diff --git a/.blueprint/manifest.yaml b/.blueprint/manifest.yaml
index 543f53b..3acb0c9 100644
--- a/.blueprint/manifest.yaml
+++ b/.blueprint/manifest.yaml
@@ -173,6 +173,8 @@ modules:
area: infra
status: done
progress: 100
+ updated: 2026-09-22
+ note: "Gitea workflow 已具备 Agent 安全投影:先只读预览,再以摘要锁定和显式确认写入;不接收凭证值,不触发 CI 或部署。"
position: [0, 650]
- id: auto-update
diff --git a/.blueprint/modules/cicd-workflow.md b/.blueprint/modules/cicd-workflow.md
index f09ef9e..d0ece70 100644
--- a/.blueprint/modules/cicd-workflow.md
+++ b/.blueprint/modules/cicd-workflow.md
@@ -57,3 +57,10 @@ CI/CD 配置管理和 GitHub Actions Workflow 文件生成。支持两种部署
查看 GitHub Actions 运行状态,支持手动触发。
> ⚠️ 已随 github-decommission(2026-07-02)移除,仅作历史记录;Gitea Actions 接入后按新事实源重建。
+
+### ✅ T10. CI/CD 能力的 Agent 安全投影
+- status: done
+- complexity: M
+- files: src-tauri/src/commands/cicd.rs, src-tauri/src/mcp_tools.rs, src-tauri/src/models.rs, src-tauri/src/lib.rs, src/lib/commands.ts, docs/ai-context/project-context.yaml, .agents/skills/lian-jing/SKILL.md, src-tauri/resources/onboarding/.agents/skills/lian-jing/SKILL.md, src-tauri/resources/onboarding/.lian-jing/capability-packs.json, .lian-jing/capability-packs.json, plugins/lian-jing-codex/.codex-plugin/plugin.json, plugins/lian-jing-codex/skills/lian-jing/SKILL.md
+- acceptance: "agent_verify: [cargo test] plan_cicd_workflow(project_id, workflow_type) 返回目标文件、工具链来源、required_secrets、change 与 content_digest,且磁盘零写入;apply_cicd_workflow 在 confirmed=false、摘要不一致、未知项目、配置缺失、路径越界或工具链冲突时零写入并返回证据,只有 confirmed=true 且摘要匹配时写入预览对应的 `.gitea/workflows/` 文件;[MCP schema] 支持 pr_ci/deepseek_review/web_ssh/tauri_oss,不暴露密钥值、任意命令或远程执行参数;cargo test --lib、pnpm typecheck、git diff --check 通过,pnpm-lock.yaml 无改动。"
+- decision_note: "保留 distribute_cicd 作为 pr_ci/deepseek_review 兼容入口;部署类 workflow 只开放 plan/apply,两阶段确认仅写项目文件,不触发 Gitea Actions、SSH、部署、回滚或 migration。"
diff --git a/.lian-jing/capability-packs.json b/.lian-jing/capability-packs.json
index b40de6a..9ed8bd2 100644
--- a/.lian-jing/capability-packs.json
+++ b/.lian-jing/capability-packs.json
@@ -3,7 +3,7 @@
"packs": [
{
"pack_id": "base-onboarding",
- "version": "1.3.0",
+ "version": "1.4.0",
"supported_clients": ["codex", "claude"],
"project_artifacts": [
"AGENTS.md",
diff --git a/docs/ai-context/project-context.yaml b/docs/ai-context/project-context.yaml
index 9a38b82..3c692be 100644
--- a/docs/ai-context/project-context.yaml
+++ b/docs/ai-context/project-context.yaml
@@ -13,8 +13,9 @@ contracts:
- "src-tauri/src/mcp_tools.rs"
- "src-tauri/src/commands/delivery_status.rs"
- "src-tauri/src/commands/toolchain.rs"
+ - "src-tauri/src/commands/cicd.rs"
- "src-tauri/src/commands/gitea.rs"
- doc: "工具 schema、dispatch 与调用说明必须同步;外部来源不可用时返回显式降级状态,不得伪造成功"
+ doc: "工具 schema、dispatch 与调用说明必须同步;外部来源不可用时返回显式降级状态,不得伪造成功。CI workflow 使用 plan_cicd_workflow 只读预览,用户确认相同 digest 后方可 apply_cicd_workflow 写入;该接口不接收凭证值且不触发 CI 或部署。"
- label: 被管理项目身份上下文
paths:
diff --git a/plugins/lian-jing-codex/.codex-plugin/plugin.json b/plugins/lian-jing-codex/.codex-plugin/plugin.json
index edac06d..71c5b9b 100644
--- a/plugins/lian-jing-codex/.codex-plugin/plugin.json
+++ b/plugins/lian-jing-codex/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "lian-jing-codex",
- "version": "0.1.0",
+ "version": "0.2.0",
"description": "Guide Codex through Lian Jing project context and existing MCP workflows.",
"author": {
"name": "Lian Jing"
diff --git a/plugins/lian-jing-codex/skills/lian-jing/SKILL.md b/plugins/lian-jing-codex/skills/lian-jing/SKILL.md
index 67d586a..20309a7 100644
--- a/plugins/lian-jing-codex/skills/lian-jing/SKILL.md
+++ b/plugins/lian-jing-codex/skills/lian-jing/SKILL.md
@@ -12,7 +12,8 @@ description: Inspect an already onboarded Lian Jing project, use its existing MC
3. 当项目已有 `lian-jing` MCP 时,可调用 `get_diagnostics`、`verify_onboarding`、`get_toolchain_status` 和 `get_delivery_status` 获取可观察信号;所有调用显式传入 `project_id`。
4. Node/pnpm 项目运行包管理命令前先读取工具链状态;`ready_managed` 时使用 `mise exec -- pnpm ...`。只有用户明确确认下载安装后,才可调用 `prepare_toolchain(project_id, confirmed=true)`;不得为适配 Codex bundled runtime 修改项目版本或 lockfile。
5. 合并 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. 若缺少 canonical context、MCP 或项目登记,不得猜测 project_id、MCP URL、凭证或状态。说明缺失项,并建议通过炼境接入或 `inject_mcp` 修复。
-7. 炼境提供状态、凭证托管和受控入口;项目 Agent 仍负责代码判断、测试选择、提交分组与业务交付决策。不要把 CI 成功或部署信号表述为业务验收。
+6. 生成 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. 若缺少 canonical context、MCP 或项目登记,不得猜测 project_id、MCP URL、凭证或状态。说明缺失项,并建议通过炼境接入或 `inject_mcp` 修复。
+8. 炼境提供状态、凭证托管和受控入口;项目 Agent 仍负责代码判断、测试选择、提交分组与业务交付决策。不要把 CI 成功或部署信号表述为业务验收。
本插件不包含 MCP server、MCP URL、项目 ID、凭证、部署命令或客户端项目 UI 映射;这些都属于项目接入与炼境控制面。
diff --git a/src-tauri/resources/onboarding/.agents/skills/lian-jing/SKILL.md b/src-tauri/resources/onboarding/.agents/skills/lian-jing/SKILL.md
index f52f5f6..ce173ff 100644
--- a/src-tauri/resources/onboarding/.agents/skills/lian-jing/SKILL.md
+++ b/src-tauri/resources/onboarding/.agents/skills/lian-jing/SKILL.md
@@ -15,5 +15,6 @@ description: 读取炼境项目健康状态,并使用炼境 MCP 的项目管
4. 只有用户明确确认本次下载安装后,才可调用 `prepare_toolchain(project_id, confirmed=true)`;不得为适配客户端 bundled runtime 而修改项目版本或 lockfile。
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 保存。
如果 MCP 未出现,说明 Codex 项目配置尚未被加载、项目未被信任,或炼境未运行。说明该限制后继续完成不依赖 MCP 的本地工作;不得伪造诊断结果。
diff --git a/src-tauri/resources/onboarding/.lian-jing/capability-packs.json b/src-tauri/resources/onboarding/.lian-jing/capability-packs.json
index b40de6a..9ed8bd2 100644
--- a/src-tauri/resources/onboarding/.lian-jing/capability-packs.json
+++ b/src-tauri/resources/onboarding/.lian-jing/capability-packs.json
@@ -3,7 +3,7 @@
"packs": [
{
"pack_id": "base-onboarding",
- "version": "1.3.0",
+ "version": "1.4.0",
"supported_clients": ["codex", "claude"],
"project_artifacts": [
"AGENTS.md",
diff --git a/src-tauri/src/commands/cicd.rs b/src-tauri/src/commands/cicd.rs
index 1b167d7..3907398 100644
--- a/src-tauri/src/commands/cicd.rs
+++ b/src-tauri/src/commands/cicd.rs
@@ -2,9 +2,10 @@
// 历史部署教程与踩坑指南见 docs/tauri-cicd-guide.md(原 GitHub Actions 方案)
use crate::commands::toolchain::parse_toolchain_contract;
use crate::db;
-use crate::models::{CicdConfig, WorkflowResult};
-use rusqlite::params;
-use std::path::Path;
+use crate::models::{CicdConfig, WorkflowApplyResult, WorkflowPlan, WorkflowResult};
+use rusqlite::{params, Connection};
+use sha2::{Digest, Sha256};
+use std::path::{Component, Path, PathBuf};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -118,9 +119,7 @@ impl WorkflowToolchain {
}
}
-#[tauri::command]
-pub fn get_cicd_config(project_id: String) -> Result