Compare commits
6 Commits
8e13bb4cf5
...
9211883a9b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9211883a9b | ||
|
|
4a6fbf9863 | ||
|
|
8f3be6b948 | ||
|
|
061d377d0d | ||
|
|
acfd54f5d3 | ||
|
|
1f316c0abb |
@ -502,9 +502,9 @@ modules:
|
||||
name: 项目开发变量补给
|
||||
area: infra
|
||||
status: in_progress
|
||||
progress: 91
|
||||
updated: 2026-09-28
|
||||
note: "V1–V6、V8–V11 合成验收通过;V7 真实 Codex 工具调用仍被客户端 approval policy=never 拒绝。真实桌面 UI 和跨会话便利性待终验;仅非生产开发用途,运行时 env 注入后置。"
|
||||
progress: 100
|
||||
updated: 2026-09-30
|
||||
note: "V1–V11 agent_verify 全部通过;V7 于 2026-09-30 以真实 Codex CLI 三会话完成登记→填写→跨会话读取→撤销后读取失败(单次 --approve-for-me,仅登记写调用)。待人终验真实桌面 UI 与日常便利;仅非生产开发用途。运行时落地增量见 docs/tasks/2026-09-30-dev-variables-materialize.md。"
|
||||
position: [3250, 950]
|
||||
|
||||
edges:
|
||||
@ -831,3 +831,10 @@ edges:
|
||||
- from: agent-client-capability-packs
|
||||
to: agent-access-reliability
|
||||
type: dependency
|
||||
# 项目资源代办复用现有 Gitea 绑定与单机 scoped Agent 入口;scoped URL 不是认证。
|
||||
- from: gitea-integration
|
||||
to: project-resource-broker
|
||||
type: dependency
|
||||
- from: agent-access-reliability
|
||||
to: project-resource-broker
|
||||
type: dependency
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# API密钥管理
|
||||
|
||||
统一存储和管理各类 API 密钥(OpenAI、Anthropic、阿里云等),支持按服务商分类、显示/隐藏、复制到剪贴板,密钥加密存储在本地 SQLite。
|
||||
统一存储和管理各类 API 密钥(OpenAI、Anthropic、阿里云等),支持按服务商分类、显示/隐藏、复制到剪贴板。密钥值存于 Windows Credential Manager,SQLite 仅保存非秘密元数据与引用(`project-resource-broker` R2/R3/R6)。
|
||||
|
||||
## 决策记录
|
||||
|
||||
- 存本地 SQLite,不上云,炼境定位是本地工具,密钥不离开本机
|
||||
- 目前不做系统 Keychain 集成,SQLite 存储已满足个人开发者场景;如有需求可在迭代二引入
|
||||
- 不上云,炼境定位是本地工具,密钥不离开本机
|
||||
- 历史决策:初版明文存 SQLite、不做 Keychain,认为已满足个人场景;2026-09-23 安全审计推翻,已迁移到本机受保护存储(见 `project-resource-broker`);常态列表不回显原值,人显式 reveal
|
||||
- 支持「注入到项目环境变量」的快捷操作(配合 project-tools 模块)
|
||||
|
||||
## 任务卡
|
||||
|
||||
@ -83,3 +83,5 @@ progress: 100
|
||||
|
||||
> 背景:批量同步后用户发现所有项目只落了 CLAUDE.md——跨工具内容被锁进单一工具私有文件,违反宪章「跨工具单一来源 = AGENTS.md」。
|
||||
|
||||
|
||||
> 待议设计证据包:`docs/tasks/2026-09-23-blueprint-agent-handoff-pilot.md`(安全派发与 Enterprise 试点;未立项,不改变本模块 done 状态)。
|
||||
|
||||
@ -102,14 +102,16 @@ progress: 91
|
||||
- acceptance: "[真实 scoped MCP] p1 Agent 登记 E2E_PASSWORD → UI 待填写;人保存合成值后已认证 Agent 单项读取成功;未认证、p2 和全局路由拒绝;tools/list 无批量导出或任意代理。"
|
||||
- evidence: `scoped_dev_variable_http_pilot_denies_missing_wrong_cross_and_global_auth` 真实本机 HTTP 测试通过;撤销后读取失败,工具清单无批量出口。
|
||||
|
||||
### 🔴 V7 — 契约、合成试点与改动边界验收
|
||||
- status: blocked
|
||||
### ✅ V7 — 契约、合成试点与改动边界验收
|
||||
- status: done
|
||||
- complexity: M
|
||||
- files: docs/ai-context/project-context.yaml, docs/ai-context/agent-button-system.md, .blueprint/modules/project-dev-variables.md
|
||||
- files: docs/ai-context/project-context.yaml, docs/ai-context/agent-button-system.md, .blueprint/modules/project-dev-variables.md, src-tauri/src/mcp_server.rs
|
||||
- depends: V6
|
||||
- acceptance: "agent_verify: [真实 Codex CLI + 合成数据] 跨会话登记→填写→读取→撤销后再读失败,项目目录无新 .env 或 token;[CLI] cargo test、pnpm typecheck、git diff --check 均 exit 0。human_verify: 不使用真实生产账号、不触发真实 PR/部署/数据库写入,确认日常开发摩擦是否下降。"
|
||||
- blocked_reason: 真实 Codex CLI 三次隔离合成试点均在调用 `register_project_dev_variable` 时被客户端审批策略拒绝(`MCP tool call requires approval, but approval policy is never`),服务端未收到登记请求;未尝试绕过审批。需用户决定是否允许一次受控、可审批的真实客户端试点。
|
||||
- pending: 真实 Codex CLI 调新工具完成整条业务链、正式打包版、桌面 UI 人眼便利性终验;V1 的真实 Codex 认证初始化与 V6 的真实 scoped HTTP 业务链已分别验证,不把组合证据冒充本卡端到端完成。
|
||||
- resolved_blocker: 原阻碍为非交互 `exec` 默认 approval=never 拒绝写工具。2026-09-30 用户授权单次 `--approve-for-me`(仅会话 A 登记这一写调用),沿用 R11 先例;未放宽全局策略,读工具无需审批。
|
||||
- verification: "2026-09-30:新增 ignored 宿主 `mcp_server::tests::isolated_dev_variables_client_pilot`(内存 DB、随机合成账户、临时 scoped MCP,认证走真实 `dev-manager-tauri.exe --lianjing-mcp-header` helper 取 Windows 凭据中的项目 bearer)。自检:helper exit 0;无认证 read_project_test_account → HTTP 403。真实 Codex CLI 0.155.0-alpha.9.2(gpt-5.6-terra)三个独立会话:A 登记 PILOT_LOGIN(environment=local)→ 宿主代人填写;B 新会话 list → configured=true、read → username=pilot-user-12abb974,与宿主生成值一致;宿主代人撤销后 C 新会话 read → 失败「开发变量尚未由用户填写」。finish 后宿主断言:状态全部未配置、目录无 `.env*`、任何文件不含 MCP token 与测试密码、MCP 认证材料已删除、临时目录已清理,test ok。回归:cargo test --locked 227 passed / 5 ignored,pnpm typecheck exit 0,git diff --check 通过。"
|
||||
- human_verify: pending(真实桌面 UI 填写/撤销与日常开发摩擦是否下降,需人终验)
|
||||
- unverified: "正式打包版 helper 路径;Claude/DeepSeek 等非 Codex 客户端的 bearer helper 取值;B 会话中模型把 list/read 各重复调用 3 次(结果一致,属模型行为,未影响断言);宿主强制终止时 Drop 不保证执行,可能残留合成 Windows 凭据。"
|
||||
|
||||
## 设计决策:测试账户一次填写与跨会话复用(2026-09-28)
|
||||
|
||||
|
||||
@ -23,3 +23,4 @@
|
||||
{"content":"【复盘】P3-S2 Agent PR 合并安全门禁\n任务类型: MCP工具\n复杂度: M\n实际轮数: 2轮(预估2轮)\n是否返工: 是\n返工原因: 初版只在发起 merge 前读取并比较 head SHA,代码审查发现检查与执行之间仍有 TOCTOU 窗口;补充 Gitea head_commit_id 由服务端在合并时再次绑定确认 SHA。\n有效策略: 将 confirmed、完整 expected_head_sha、PR open/mergeable、CI、review 全部收敛在 Rust 能力本体;MCP 仅传参。先用错误 SHA 验证拒绝,再在用户确认 PR #14 与 b6d749025458928b86ab607a5a7f31479f8db85f 后真实合并并回读状态。\n遗留风险: review=pending 在当前轻量治理策略中允许合并;若未来需要强制审批,应新增项目级 required-review policy,不应在客户端复制判断。\nauto_applied_rules: R06,R07,R10,R12,R13\nrule_effectiveness: R06=有效,R07=有效,R10=有效,R12=有效,R13=有效","created_at":"2026-09-22 21:20:28","id":176,"project_id":"dev-manager-tauri","source":"mcp"}
|
||||
{"content":"【复盘】A9 炼境与 Enterprise 消费者端到端验收\n任务类型: MCP工具\n复杂度: M\n实际轮数: 3轮(预估2轮)\n是否返工: 是\n返工原因: 初次只验证 scoped HTTP,尚不能证明 Codex 新任务已原生加载工具;随后增加新任务 tool marker 与托盘关闭后存活证据。\n有效策略: 以 127.0.0.1 scoped endpoint 先验证作用域、跨项目拒绝与 unavailable 边界,再用两个全新 Codex 任务禁止 shell/HTTP/file fallback,确认 lian-jing/get_delivery_status 原生 tool marker;关闭窗口后继续验证进程、health、26 个工具与交付状态。\n遗留风险: Settings 页视觉 human_verify 与正式安装版 autostart human_verify 尚未完成;开发态继续优先 pnpm tauri dev,正式包仅在安装/自启动/升级/发布里程碑验收。","created_at":"2026-09-23 04:20:52","id":177,"project_id":"dev-manager-tauri","source":"mcp"}
|
||||
{"content":"【复盘】Agent 访问可靠性 A1-A9 收官\n任务类型: 架构调整\n复杂度: L\n实际轮数: 分阶段完成(预估分阶段)\n是否返工: 是\n返工原因: 初期静态配置被误当作 Agent 当前可用,之后补齐 listener、握手、核心工具、客户端重载的分层证据;原生客户端验收和正式安装版自启动另行补测。\n有效策略: 单一 loopback MCP 加项目作用域;以两个新 Codex 任务的原生工具调用验证消费端;开发态用 pnpm tauri dev 快速验证,正式安装包只在自启动里程碑验收。0.1.19 正式版默认无自启动项,用户通过 UI 完成启停,终态为关闭,get_diagnostics 五层就绪。\n遗留风险: 开启瞬间 HKCU Run 读数未捕获,Windows 注销后的自动拉起未实测;若后续需要证明登录触发效果,再做一次计划内登录验证。P3-E 与 usage.json 并行改动不属于本模块。","created_at":"2026-09-23 09:45:16","id":178,"project_id":"dev-manager-tauri","source":"mcp"}
|
||||
{"content":"【复盘】R1 Gitea 与 webhook 输出脱敏\n任务类型: 后端命令|前端契约\n复杂度: M\n实际轮数: 1轮(预估M轮)\n是否返工: 否\n有效策略: 在 Rust DTO 序列化边界排除 access_token 与 webhook_secret,同时让 TypeScript 契约不再声明这两个字段,并以 JSON 序列化回归测试固化。\n遗留风险: UI 人工终验与完整 cargo test 尚未完成;当前存储迁移尚未开始。","created_at":"2026-09-23 17:26:40","id":179,"project_id":"dev-manager-tauri","source":"mcp"}
|
||||
|
||||
19
AGENTS.md
19
AGENTS.md
@ -4,8 +4,9 @@
|
||||
|
||||
## 项目定位
|
||||
|
||||
Git 多空间开发管理器,同时也是**项目 Agent 化的本地控制面**。
|
||||
以蓝图为神经系统,为 Codex、Claude 等可替换客户端 adapter 分发基线、发现状态并采集交付信号;项目自身保留业务规则、测试、部署声明与交付决策。
|
||||
**炼境是 agent 开发子项目的管家**:agent(Claude、Codex、DeepSeek 等)在哪里反复摩擦,管家就把那项能力沉淀并结构化为跨 agent 可调用的通用能力,让 agent 拿到任务即能开工、能自验、能交付。
|
||||
分工:agent 负责推理、编码、模型与子 Agent 调度;炼境负责项目身份、环境就绪、验证资源、任务上下文与证据采集;子项目保留业务规则、spec、测试、部署链路与生产凭证,离开炼境仍可独立开发。
|
||||
成长方式是摩擦驱动闭环:摩擦信号(envoy 回传、复盘、人工解围、返工)→ 准入筛选 → 结构化为 MCP 工具 / 能力包 / 分发基线 → 在成熟子项目实测收益 → 保留、调整或撤销。准入与边界见下方「管家准入与退出」。
|
||||
炼境本身亦被纳入其中,形成可自我进化的开发智能闭环。
|
||||
|
||||
## 技术栈
|
||||
@ -298,6 +299,20 @@ docs/templates/ 炼境接入包模板草案(设计层)
|
||||
注意 master 直推场景 CI 是**事后红灯**(提交已落库,红灯提醒修复),仅 PR 流程构成事前拦截——
|
||||
这是与「S 级直推 master」约定的有意权衡,勿以「加分支保护禁直推」名义收紧。
|
||||
|
||||
### 管家准入与退出(2026-09-30 用户立法)
|
||||
|
||||
- **准入**:新能力须同时满足五条才沉淀进炼境——通用(多个子项目会遇到)、可结构化(能成为
|
||||
schema / MCP 工具,agent 可查询可断言)、跨 agent(同一入口,不绑某家客户端)、与 agent 原生能力
|
||||
互补(不与其调度、子 Agent、模型选择竞争)、消除重复(每个项目/会话都要重做的配置或摸索)。
|
||||
- **不做**:不替 agent 思考或选模型;不替 agent 判定完成;不接管子项目自有事实;不让子项目依赖炼境在线。
|
||||
- **分发 vs 查询**:需随分支走、离线必读、客户端启动即读的稳定基线才写进子项目(版本化能力包 +
|
||||
托管块,先预览再单项目 apply);易变、跨项目、含敏感、可重建的内容一律走 MCP 按需查询。凭据值永不进项目 Git。
|
||||
进入 agent 上下文仅限 `project-dev-variables` 的有界例外(非生产、低权限、可重置,经项目级认证单项读取);
|
||||
能不经手就不经手——优先写入经 `git check-ignore` 确认被忽略的本地文件,由项目自有脚本消费。
|
||||
- **退出**:实测无收益、被 agent 原生能力覆盖、或维护成本超过收益的能力要撤销,不因「已经做了」而保留。
|
||||
- **为什么**:管家的价值是给 agent 减负,一旦越权或膨胀,就会变成新的摩擦来源;能力是否留下要看实测数据,
|
||||
验证场优先用最成熟的子项目 enterprise-system。
|
||||
|
||||
### 炼境自己的 Agent 按钮清单
|
||||
|
||||
| 能力 | 调用方式 | 通过标准 |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 炼境 — 速查卡
|
||||
|
||||
**定位**:项目 Agent 化的本地控制面。炼境管理多个 Git 工作区,分发版本化基线、发现状态并汇总交付信号;项目本身仍拥有业务规则、测试、部署声明与交付决策。
|
||||
**定位**:agent 开发子项目的管家。agent 反复摩擦之处,炼境沉淀为跨 agent 可调用的结构化能力(分发版本化基线、提供项目身份/环境/验证资源/任务上下文、汇总交付信号);项目本身仍拥有业务规则、测试、部署声明与交付决策。准入/退出规则见 AGENTS.md「管家准入与退出」。
|
||||
|
||||
## 技术栈
|
||||
|
||||
|
||||
66
docs/tasks/2026-09-23-blueprint-agent-handoff-pilot.md
Normal file
66
docs/tasks/2026-09-23-blueprint-agent-handoff-pilot.md
Normal file
@ -0,0 +1,66 @@
|
||||
# 蓝图规则安全派发与 Agent 分工试点
|
||||
|
||||
> status: todo | complexity: L | created: 2026-09-23 | blueprint: blueprint-governance(待确认新迭代或独立模块)
|
||||
> ⚠️ 处置条款:本文件 done 后 24h 内删除;稳定结论沉淀到蓝图决策与复盘,不让简报成为第二事实来源。
|
||||
> 类型:Feature / 工作流行为变更。开工前按 AGENTS.md 输出 L 级确认卡,用户确认设计后再拆 S/M 任务卡。本次仅保存讨论,尚未确认实施或修改规则。
|
||||
|
||||
## 为什么(压缩背景 + 证据)
|
||||
|
||||
- 来源讨论:`codex://threads/01a0c8e3-56a6-79a0-b3f8-0fb3322746b2` 提议强模型规划、经济模型执行。该任务可读取的核心回复确认 Codex 支持不同模型的子 Agent 配置,但蓝图复杂度驱动的完整工作流并非原生一键能力;其后数轮正文当时未能读取,勿假定整段会话已核验。官方参考:https://learn.chatgpt.com/docs/agent-configuration/subagents 。
|
||||
- 当前共识:默认单 Agent 端到端;仅当任务边界清楚、子任务独立、验收可执行且规划成果可复用时试拆分。比较总耗时、使用量、返工、漏验和人工介入,不以单次模型价格判断收益;复杂度单一字段不足以判断风险。
|
||||
- 炼境负责版本化蓝图基线、派发、自检、采集执行信号;子项目拥有业务规格、项目规则、测试和交付决策。见 `AGENTS.md:5`、`AGENTS.md:287`、`docs/ai-context/agent-button-system.md:162`。Enterprise 新功能规格唯一来源是 `E:/code-project/enterprise-system/AGENTS.md:122` 指向的 `.specify/specs/.../spec.md`,蓝图应引用而非复制。
|
||||
- 安全派发的实锤阻碍:`src-tauri/src/commands/blueprint.rs:1434` 的 `save_user_conventions` 保存后自动遍历所有项目同步;`:1291` 的 `sync_blueprint_rules` 直接整文件写入项目 `.blueprint/CONVENTIONS.md`,并更新 `AGENTS.md` / `CLAUDE.md` 托管块。当前行为不能安全地“先炼境、再单个 Enterprise 试点、最后推广”。
|
||||
- 状态语义需收敛:`.blueprint/CONVENTIONS.md:25` 说 in_progress/done 由 Git 信号推断,同文件 `:50` 又要求任务卡标 🔵/✅。Git/CI 应给事实证据,Agent 负责完成语义判断,人负责目标终验。`src-tauri/src/commands/blueprint.rs:1191` 的 `synced` 主要依据规则版本及 manifest 时间,不能证明蓝图与代码一致。
|
||||
- 现有 MCP `src-tauri/src/mcp_tools.rs:27` 可读蓝图,`:38` 可读项目文件;不必先造调度服务。`get_blueprint` 在 `src-tauri/src/commands/blueprint.rs:304` 读路径中追加 usage 快照,可能产生 Git 工作区噪声,应列为治理时的分离候选。
|
||||
- Enterprise 已有双层验收实例:`E:/code-project/enterprise-system/.blueprint/modules/enterprise-company.md:29` 的 T3 分列 SQL `agent_verify` 和页面 `human_verify`,适合作为任务卡契约样本,但生产 migration 不应成为低成本模型试验任务。
|
||||
- 2026-09-23 后续讨论澄清了“蓝图”的目标:它不只是静态架构图或模型路由表,更是持续记录方向、任务安排、阻碍、执行反馈和下一步的**项目工作账本**;炼境是持有账本并为开发 Agent 提供上下文、环境诊断和受限能力的经纪人/管家。人保留目标定义与终验,读过代码的 Agent 保留实现和完成语义判断,炼境负责协调与采集信号。
|
||||
- 这不等于把 `.blueprint/` 全部搬入炼境:项目业务规则、已确认的架构决策、验收契约须随 Git 分支/worktree 与代码同行;日常安排、可重建统计、跨项目索引和按任务提取的上下文更适合由炼境提供。Enterprise 当前 `.blueprint/modules/` 有 101 个文件,`manifest.yaml` 约 43 KB、`CONVENTIONS.md` 约 46 KB、`usage.json` 约 611 KB;`usage.json` 会产生工作区噪声。可用 `Get-Item .blueprint/manifest.yaml,.blueprint/CONVENTIONS.md,.blueprint/usage.json | Select-Object Name,Length` 和 `(Get-ChildItem .blueprint/modules -File | Measure-Object).Count` 复核,不应让新会话每次通读全部材料。
|
||||
- 可验证的收益假设:炼境按当前 project_id、分支/worktree 和任务交付“少而准”的任务简报(目标、相关规则/文件、依赖、可执行验收、现有证据、阻碍及可调用能力),Agent 独立定位、实施、自验后回传证据;与现有方式对照人工解围次数、上下文量、完成至终验耗时、漏验/返工。方向有潜力,尚无实测收益,不能宣称已实现。
|
||||
|
||||
## 改什么(按依赖顺序,非已批准任务卡)
|
||||
|
||||
- [ ] ⛔ 先向用户提交 L 级设计确认卡,确定本工作是 `blueprint-governance` 新迭代还是独立模块;确认前不改蓝图基线、不派发 Enterprise。
|
||||
- [ ] 设计安全派发:保存草稿与项目同步解耦,提供目标项目与差异预览;先单项目试点,再显式批量推广。先审 `src-tauri/src/commands/blueprint.rs`、`src-tauri/src/mcp_tools.rs`、`src/lib/commands.ts`、设置/蓝图 UI。
|
||||
- 2026-09-30 补充目标:**蓝图规则并入能力包范式**——登记为 `capability-packs.json` 中的版本化 pack(如 `blueprint-baseline`,含 `self_check` / `upgrade_hint`),从整文件覆盖 `CONVENTIONS.md` 改为托管块,与接入包、工具链包同一套机制(依据 AGENTS.md「管家准入与退出」的「分发 vs 查询」)。
|
||||
- 同时瘦身分发内容:子项目只留稳定最小核心(状态语义、任务契约格式、复杂度流程)+ 指向 MCP 按需取详细章节的指针;`usage.json` 等可重建数据移出项目 Git,由炼境持有。
|
||||
- [ ] 统一 `.blueprint/CONVENTIONS.md` 中任务状态、`synced` 状态、可执行验收、风险/升级条件和模型无关角色表述;维护版本与兼容旧项目。项目任务卡不写死模型名。
|
||||
- [ ] 设计蓝图信息归属与同步契约:区分炼境的动态工作账本/跨项目视图与项目 Git 中随分支变化的业务决策/验收事实;定义 project_id + 分支/worktree + 任务的定位方式、过期/冲突提示,以及炼境不可用时直接读项目文件的降级路径。先设计,不迁移现有 Enterprise 蓝图。
|
||||
- [ ] 设计最小任务简报的按需读取与回执:Agent 只取本任务相关上下文、能力入口和未验项;回执包含实际验证证据、阻碍与人工终验待办,不能仅凭 Git/CI 信号自动标 ✅。优先复用现有 `get_project_blueprint`、`get_project_file` 和项目能力工具,不先造完整调度器。
|
||||
- [ ] 在炼境自己的真实任务卡自测规则;再于 Enterprise 的干净隔离分支/worktree 指定项目试点。先检查 Git 工作区与派发 diff,保留托管块外内容、`.blueprint/rules.md`、spec 和现有业务任务。
|
||||
- [ ] 先用 Enterprise 与一个轻量项目的低风险真实任务测试“任务简报 → Agent 定位/自验 → 证据回执”,记录接入至首次验证时间、人工解围次数、上下文量、返工和漏验;再在边界稳定的任务上比较单 Agent 与分工模式的总耗时和用量。用失败案例收窄规则;只有效果稳定才考虑结构化任务包/模型建议产品化。
|
||||
|
||||
## 不做什么(边界 + 已否决方案)
|
||||
|
||||
- 不把蓝图 `complexity` 直接映射为模型名——文件数和任务尺寸不等于数据库、权限、生产风险;模型选择留在 Codex/Claude adapter 与当前会话,可被人覆盖。
|
||||
- 不一开始建设炼境自动派发/执行调度器——现有读取入口已足够试跑,调度和多 Agent 交接成本尚无收益证据。
|
||||
- 不通过保存规则立即广播所有项目;不覆盖 Enterprise 主工作区既有改动;不自动执行 migration、合并或部署。
|
||||
- 不把 Enterprise `.specify` 业务规格再复制进蓝图;蓝图只维护结构、执行拆解、验证证据指针。
|
||||
- 本次试点暂不把项目蓝图的版本化事实源整体移入炼境数据库,也不让生产项目运行依赖炼境在线——否则分支/worktree 与蓝图可能错位,项目离开平台难以独立开发。可重建统计和任务视图可以中心化,但须可追溯来源并提供导出/降级;长期归属仍待设计确认与实测。
|
||||
- 不把炼境做成自动裁决代码质量或业务优先级的“上级 Agent”;它是管家与协调者。凭证只通过受限能力按需代办,不把原文、整份 `.env`、生产 SSH/DB 权限塞进任务简报;凭证代理若成为试点,先审计现有 API Key/Gitea Token 暴露边界,另行确认安全设计。
|
||||
|
||||
## 验收
|
||||
|
||||
agent_verify(方案获确认、实施后):
|
||||
|
||||
- `[Rust test] 预览项目 A 的蓝图规则升级 → 返回目标版本与差异;项目文件字节不变`。
|
||||
- `[Rust test] 保存用户版规则草稿 → 所有已注册项目文件字节不变;显式同步项目 A → 项目 B 字节不变,项目 A 的非托管内容与私有规则不变`。
|
||||
- `[CLI] 在炼境与 Enterprise 试点工作区分别检查派发前后 Git diff → 仅预期托管文件变化;未触及已有未提交内容`。
|
||||
- `[任务试跑记录] 每张卡可追溯单/分工模式、验收路径、未验项、返工与人工介入;不足数据明确标为未验证,不宣称节省`。
|
||||
- `[任务简报对照] Enterprise 与轻量项目各选一张低风险卡 → Agent 只读取相关规则/文件即可定位并复述验收路径;简报标明 project_id、分支/worktree、证据来源,切换分支后不得沿用旧分支的任务事实`。
|
||||
- `[降级与噪声] 关闭炼境后 Agent 仍能从项目文件取得业务规则与验收契约;仅查询/查看任务不应新增无关 Git 改动;简报与回执均不含凭证原文`。
|
||||
|
||||
human_verify(实施后):
|
||||
|
||||
- `[UI] 预览差异 → 选择 Enterprise 试点项目 → 明确确认派发 → 能看到目标版本与自检结果;批量推广需另行显式操作`。
|
||||
- `Enterprise 会话从项目自己的 spec/蓝图任务卡独立完成一次低风险任务,并由人确认意图与页面结果`。
|
||||
|
||||
## 关联(指针,不复制)
|
||||
|
||||
- 炼境:`AGENTS.md`、`.blueprint/CONVENTIONS.md`、`.blueprint/modules/blueprint-governance.md`、`.blueprint/modules/blueprint-flywheel.md`、`docs/ai-context/agent-button-system.md`。
|
||||
- Enterprise:`E:/code-project/enterprise-system/AGENTS.md`、`E:/code-project/enterprise-system/.blueprint/modules/enterprise-company.md`、适用的 `.specify/specs/.../spec.md`。
|
||||
- 蓝图事实与现有入口:`src-tauri/src/commands/blueprint.rs:244`、`src-tauri/src/mcp_tools.rs:27`、`E:/code-project/enterprise-system/.blueprint/manifest.yaml`、`E:/code-project/enterprise-system/.blueprint/CONVENTIONS.md`。
|
||||
- Codex 官方模型与子 Agent 文档:https://learn.chatgpt.com/docs/model-selection 、https://learn.chatgpt.com/docs/agent-configuration/subagents 。
|
||||
|
||||
## 新会话启动方式
|
||||
|
||||
先读本文件及其关联指针,核对最新 Git 状态和同步代码;不要重做已完成的讨论。优先澄清“动态工作账本 vs 项目版本化事实”的归属与最小任务简报,再审安全派发和模型分工,避免把三者一次性实现。先输出 L 级设计确认卡并等待用户确认,再标本简报 `in_progress`、拆 S/M 卡,按 checklist 逐阶段实施与验收。完成后按处置条款清理本简报。
|
||||
60
docs/tasks/2026-09-30-dev-variables-materialize.md
Normal file
60
docs/tasks/2026-09-30-dev-variables-materialize.md
Normal file
@ -0,0 +1,60 @@
|
||||
# 测试账户落地到被忽略文件(project-dev-variables 增量)
|
||||
|
||||
> status: todo | complexity: L(拆为 3 张 M 卡)| created: 2026-09-30 | blueprint: project-dev-variables(该模块尚在 `feat/project-dev-variables` 分支,V7 blocked,未合入 master)
|
||||
> ⚠️ 处置条款:本简报 done 后 24h 内删除;结论并入 `project-dev-variables` 模块的决策与任务卡,不让简报成为第二事实来源。
|
||||
> 类型:Feature。开工前需用户确认本设计;且须等 `project-dev-variables` 合入 master 后在其模块内立卡。
|
||||
|
||||
## 为什么(压缩背景 + 证据)
|
||||
|
||||
- 依据 AGENTS.md「管家准入与退出」:agent 自验缺测试账号是高频摩擦。`project-dev-variables` 已解决「人填一次、炼境保管、agent 发现」——登记 `register_project_test_account`、存 Windows Credential Manager、经项目 bearer 认证由 `read_project_test_account` 单项取明文(有界例外:非生产、低权限、可重置)。
|
||||
- 该模块自己留了口子:「不自动注入子进程环境、不写 `.env`;需要时另立任务」。本简报即那个任务。仍存在三处摩擦:
|
||||
1. **项目脚本拿不到值**:Enterprise 已有 `apps/api/scripts/agent-auth.mjs`(读 `AGENT_USERNAME/AGENT_PASSWORD` 或仓库根 `.env.agent`)、Playwright `auth.setup.ts`(`E2E_USERNAME/E2E_PASSWORD`)。agent 从 MCP 读到明文后,还得自己 export 或写文件才能喂给脚本——明文在会话里转一手。
|
||||
2. **Enterprise 沉淀的原则**(`docs/agent-dx-playbook.md` 坑 4):凭据不进 agent 上下文、读取下沉到脚本;全局 `deny Read(./.env.*)` 是刻意安全兜底。2026-09-30 实测炼境会话读 `.env.agent` 同样被拦。
|
||||
3. **worktree 缺文件**:被 Git 忽略的 `.env.agent` 不随 `git worktree add` 出现,每个隔离工作区都要人工重建——与炼境推荐的隔离 worktree 试点直接冲突。
|
||||
- 客户端兼容:`read_project_test_account` 依赖 bearer helper,目前只在 Codex 验证过;其他客户端(Claude、DeepSeek harness)未验证。落地动作不返回值,可不依赖取值认证,天然跨 agent。
|
||||
|
||||
## 方案选择
|
||||
|
||||
| 方案 | 收益 | 代价 / 风险 | 结论 |
|
||||
| --- | --- | --- | --- |
|
||||
| 维持现状:agent 读明文再自行 export / 写文件 | 零开发 | 明文进会话;每个 worktree 重复;非 Codex 客户端无取值路径 | 保留为已有路径,不作首选 |
|
||||
| 项目脚本经 helper 直接向炼境取值 | 值不落盘、不进会话 | 需改项目脚本并依赖炼境 CLI 契约;子项目对炼境产生运行期依赖 | 暂缓:v1 实测不够再考虑 |
|
||||
| **`materialize_project_test_account`:炼境把已登记账户写入调用方指定、经 `git check-ignore` 确认被忽略的本地文件,只返回写入的变量名** | 复用项目现有脚本;worktree 一键补齐;值不进会话;不需要取值认证 | 值落本地磁盘(同现状 `.env.agent` 威胁模型);与 dev-variables「避免留在 .env」的初衷有张力 | **选定(v1)** |
|
||||
|
||||
张力的处理:落地文件是**可再生、可撤销的派生物**,事实源仍是炼境。撤销账户后再调用即清除对应变量行;文件永不入 Git。
|
||||
|
||||
## 能力本体(v1)
|
||||
|
||||
`materialize_project_test_account(project_id, name, target_file, username_var, password_var, worktree_path?)`
|
||||
|
||||
- 调用方给出目标文件与变量名:agent 读项目脚本即可得知,例如 Enterprise `.env.agent` + `AGENT_USERNAME/AGENT_PASSWORD`。v1 不新增项目声明文件;若同一项目反复重复参数,再考虑在项目 Git 中声明。
|
||||
- 边界在本体层校验,fail closed:
|
||||
- `worktree_path` 缺省为注册路径;给出时须与注册仓库共享 `git rev-parse --git-common-dir`,否则拒绝;
|
||||
- `target_file` 规范化后必须在工作区内;`git check-ignore` 必须确认被忽略,否则拒绝(防止入 Git);
|
||||
- 账户须已由人填写且为非生产类型;未配置时返回 `missing`;
|
||||
- 只替换这两个变量行,保留文件其他内容;幂等;账户已撤销时删除这两行;
|
||||
- 返回值、错误文本、日志均不含账号或密码。
|
||||
- MCP 投影走 scoped 路由,属写工具。客户端审批策略(参见 V7 blocker)按正常审批处理,不标注为只读来绕过。人的投影:`ProjectDevVariablesPanel` 可选加「写入工作区」按钮,属于 human_verify 范围,非 v1 必需。
|
||||
|
||||
## 任务卡草案(合入 dev-variables 模块后编号接续 V11)
|
||||
|
||||
- **V12 落地本体**(M)— files: `src-tauri/src/commands/dev_variables.rs`, `src/lib/commands.ts`;depends: project-dev-variables 合入 master
|
||||
- acceptance: `agent_verify: [cargo test] 临时 git 仓库 + 模拟受保护存储:目标被忽略 → 仅两变量行写入、其他行保留、二次调用字节不变;撤销后调用 → 两行被删;目标未被忽略 / 越出工作区 / worktree 属另一仓库 / 账户未填写 → 拒绝且文件字节不变;返回与错误序列化不含账号密码。`
|
||||
- **V13 MCP 投影**(M)— files: `src-tauri/src/mcp_tools.rs`;depends: V12
|
||||
- acceptance: `agent_verify: [cargo test] tools/list 含 materialize_project_test_account,schema 要求 project_id;跨项目参数被 scope 拒绝;无认证调用可写入但响应只含变量名;仍无任何批量导出或 get_secret 工具。`
|
||||
- **V14 Enterprise worktree 试点**(M)— files: `.blueprint/modules/project-dev-variables.md`, `docs/ai-context/project-context.yaml`, `docs/ai-context/agent-button-system.md`;depends: V13
|
||||
- acceptance: `agent_verify: [CLI] Enterprise 新建隔离 worktree(无 .env.agent)→ [MCP] materialize_project_test_account(enterprise, <账户名>, ".env.agent", "AGENT_USERNAME", "AGENT_PASSWORD", worktree) → [CLI] 起本地 API 后 pnpm --silent --filter @enterprise-system/api agent:token exit 0 且 stdout 非空;git status 显示 .env.agent 未被跟踪;会话记录不含账号值。`
|
||||
- `human_verify: 在炼境登记一次后,agent 在新 worktree 中无需向人索要账号。`
|
||||
- 度量:人工传递账号次数(目标 0)、新 worktree 就绪到首次 agent:token 成功的耗时。未达收益按「管家退出」规则撤销。
|
||||
|
||||
## 不做什么
|
||||
|
||||
- 不向 agent 返回值(取值仍只走已有的 `read_project_test_account` 有界例外);不提供通用 `export_env` / 命令代理 / 代为登录。
|
||||
- 不接管 staging / production;不写入被 Git 跟踪的文件;不改项目脚本或统一变量名。
|
||||
- 不宣称增强同一 Windows 用户下的进程隔离。
|
||||
|
||||
## 未验证 / 依赖的假设
|
||||
|
||||
- Enterprise 本地 API 能在隔离 worktree 中启动,未核实(端口冲突、依赖安装、数据库可达)。
|
||||
- 其他客户端调用 scoped 写工具的审批行为,未验证。
|
||||
- `project-dev-variables` 合入 master 的时间取决于其 V7(真实客户端审批 blocker)的处置。
|
||||
@ -1224,6 +1224,219 @@ mod tests {
|
||||
assert!(!project_dir.exists());
|
||||
}
|
||||
|
||||
/// V7 隔离真实客户端试点:仅内存数据库、随机合成凭证与临时 scoped MCP,认证走真实 helper。
|
||||
/// 阶段靠临时项目 `.pilot/` 标记推进:客户端登记后宿主代人填写合成账户(写 filled);
|
||||
/// 见 `revoke` 标记后代人撤销(写 revoked);见 `finish` 标记后检查改动边界并清理。
|
||||
#[cfg(windows)]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[ignore = "V7: connect disposable Codex sessions to the printed scoped MCP URL"]
|
||||
async fn isolated_dev_variables_client_pilot() {
|
||||
use crate::credential_store::WindowsCredentialStore;
|
||||
use std::io::Write;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
struct PilotGuard {
|
||||
project_id: String,
|
||||
project_dir: std::path::PathBuf,
|
||||
}
|
||||
impl Drop for PilotGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(conn) = db::pool().get() {
|
||||
let _ = crate::commands::dev_variables::purge_project(
|
||||
&conn,
|
||||
&WindowsCredentialStore,
|
||||
&self.project_id,
|
||||
);
|
||||
}
|
||||
let _ = crate::credential_store::delete_mcp_auth_token_for_project(
|
||||
&WindowsCredentialStore,
|
||||
&self.project_id,
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&self.project_dir);
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_until<T>(
|
||||
what: &str,
|
||||
limit: Duration,
|
||||
mut probe: impl FnMut() -> Option<T>,
|
||||
) -> T {
|
||||
let deadline = Instant::now() + limit;
|
||||
loop {
|
||||
if let Some(value) = probe() {
|
||||
return value;
|
||||
}
|
||||
assert!(Instant::now() < deadline, "等待超时:{what}");
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
|
||||
db::init_test_pool();
|
||||
let project_id = format!("synthetic-dev-variables-pilot-{}", uuid::Uuid::new_v4());
|
||||
let profile_id = format!("{project_id}-profile");
|
||||
let project_dir =
|
||||
std::env::temp_dir().join(format!("lianjing-v7-{}", uuid::Uuid::new_v4()));
|
||||
let pilot_dir = project_dir.join(".pilot");
|
||||
let guard = PilotGuard {
|
||||
project_id: project_id.clone(),
|
||||
project_dir: project_dir.clone(),
|
||||
};
|
||||
{
|
||||
let conn = db::pool().get().unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO project_profiles (id, name) VALUES (?1, 'Synthetic Dev Variables Pilot')",
|
||||
[&profile_id],
|
||||
)
|
||||
.unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO project_workspaces (id, profile_id) VALUES (?1, ?2)",
|
||||
rusqlite::params![project_id, profile_id],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let token = crate::credential_store::get_or_create_mcp_auth_token(
|
||||
&WindowsCredentialStore,
|
||||
&project_id,
|
||||
)
|
||||
.unwrap();
|
||||
let digest = crate::credential_store::mcp_auth_digest(&project_id).unwrap();
|
||||
// 测试二进制位于 target/debug/deps/,helper 即同目录上一级的应用 debug 构建。
|
||||
let helper_exe = std::env::current_exe()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.and_then(std::path::Path::parent)
|
||||
.unwrap()
|
||||
.join("dev-manager-tauri.exe");
|
||||
assert!(
|
||||
helper_exe.is_file(),
|
||||
"先 cargo build 生成 helper:{}",
|
||||
helper_exe.display()
|
||||
);
|
||||
let helper = format!(
|
||||
"\"{}\" --lianjing-mcp-header {digest}",
|
||||
helper_exe.display()
|
||||
);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(mcp_bind_addr(0))
|
||||
.await
|
||||
.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let sessions: Sessions = Arc::new(Mutex::new(HashMap::new()));
|
||||
let server = tokio::spawn(async move {
|
||||
axum::serve(listener, mcp_router(sessions)).await.unwrap();
|
||||
});
|
||||
let url = scoped_mcp_url(port, &project_id);
|
||||
std::fs::create_dir_all(project_dir.join(".lian-jing")).unwrap();
|
||||
std::fs::create_dir_all(project_dir.join(".codex")).unwrap();
|
||||
std::fs::create_dir_all(&pilot_dir).unwrap();
|
||||
std::fs::write(
|
||||
project_dir.join(".lian-jing/context.json"),
|
||||
serde_json::to_vec_pretty(&json!({"project_id": project_id, "schema_version": 1}))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
project_dir.join(".codex/config.toml"),
|
||||
format!(
|
||||
"[mcp_servers.lian-jing]\nurl = {}\nhttp_headers_helper = {}\n",
|
||||
serde_json::to_string(&url).unwrap(),
|
||||
serde_json::to_string(&helper).unwrap()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let username = format!("pilot-user-{}", &uuid::Uuid::new_v4().simple().to_string()[..8]);
|
||||
let password = format!("pilot-pass-{}", uuid::Uuid::new_v4().simple());
|
||||
println!("V7_PILOT_PROJECT_DIR={}", project_dir.display());
|
||||
println!("V7_PILOT_SCOPED_URL={url}");
|
||||
println!("V7_PILOT_PROJECT_ID={project_id}");
|
||||
println!("V7_PILOT_HELPER={helper}");
|
||||
println!("V7_PILOT_EXPECTED_USERNAME={username}");
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
||||
// 阶段 1:等客户端登记测试账户需求,再代人填写合成值。
|
||||
let name = wait_until("客户端登记测试账户需求", Duration::from_secs(1800), || {
|
||||
let conn = db::pool().get().ok()?;
|
||||
conn.query_row(
|
||||
"SELECT name FROM project_dev_variables
|
||||
WHERE project_id=?1 AND kind='test_account' ORDER BY name LIMIT 1",
|
||||
[&project_id],
|
||||
|row| row.get::<_, String>(0),
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.await;
|
||||
{
|
||||
let conn = db::pool().get().unwrap();
|
||||
crate::commands::dev_variables::save_test_account(
|
||||
&conn,
|
||||
&WindowsCredentialStore,
|
||||
&project_id,
|
||||
&name,
|
||||
&username,
|
||||
&password,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
std::fs::write(pilot_dir.join("filled"), &name).unwrap();
|
||||
println!("V7_PILOT_FILLED={name}");
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
||||
// 阶段 2:客户端跨会话读取后,由操作者放置 revoke 标记,宿主代人撤销。
|
||||
wait_until("撤销标记 .pilot/revoke", Duration::from_secs(1800), || {
|
||||
pilot_dir.join("revoke").exists().then_some(())
|
||||
})
|
||||
.await;
|
||||
{
|
||||
let conn = db::pool().get().unwrap();
|
||||
crate::commands::dev_variables::revoke(&conn, &WindowsCredentialStore, &project_id, &name)
|
||||
.unwrap();
|
||||
}
|
||||
std::fs::write(pilot_dir.join("revoked"), &name).unwrap();
|
||||
println!("V7_PILOT_REVOKED={name}");
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
||||
// 阶段 3:撤销后再读由客户端验证失败;finish 标记后检查改动边界。
|
||||
wait_until("结束标记 .pilot/finish", Duration::from_secs(1800), || {
|
||||
pilot_dir.join("finish").exists().then_some(())
|
||||
})
|
||||
.await;
|
||||
server.abort();
|
||||
{
|
||||
let conn = db::pool().get().unwrap();
|
||||
let statuses = crate::commands::dev_variables::list(
|
||||
&conn,
|
||||
&WindowsCredentialStore,
|
||||
&project_id,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(statuses.iter().all(|status| !status.configured));
|
||||
}
|
||||
let mut pending = vec![project_dir.clone()];
|
||||
while let Some(dir) = pending.pop() {
|
||||
for entry in std::fs::read_dir(&dir).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
if path.is_dir() {
|
||||
pending.push(path);
|
||||
continue;
|
||||
}
|
||||
let file_name = path.file_name().unwrap().to_string_lossy();
|
||||
assert!(!file_name.starts_with(".env"), "项目目录出现 {file_name}");
|
||||
let content = std::fs::read_to_string(&path).unwrap_or_default();
|
||||
assert!(!content.contains(&token), "{} 含 MCP token", path.display());
|
||||
assert!(!content.contains(&password), "{} 含测试密码", path.display());
|
||||
}
|
||||
}
|
||||
drop(guard);
|
||||
assert!(crate::credential_store::read_mcp_auth_token_for_digest(
|
||||
&WindowsCredentialStore,
|
||||
&digest
|
||||
)
|
||||
.is_err());
|
||||
assert!(!project_dir.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn readiness_probe_does_not_satisfy_client_reload_evidence() {
|
||||
let project_id = "reload-evidence-fixture";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user