Files
obsidian-vault/docs/Trellis × 飞书实现闭环初步方案.md
T

764 lines
46 KiB
Markdown
Raw Normal View History

# Trellis × 飞书实现闭环初步方案
> 状态:V1 两个全局 skill 已实现并完成本地结构、契约 fixture 与独立前向验证;真实 Base POC 尚未运行
> 更新时间:2026-07-26
> 范围:只讨论“如何发现待实现工作、如何在 Inline / Trellis 之间路由、如何在完成后回写飞书”;不展开具体 coding 规则。
> 证据分层:文中明确区分「Trellis 官方文档事实」、「现有 Matt 文档提取」和「本文推导 / 推荐」。
> 版本注意:本机 `trellis --version` 已核对为 `0.6.8`,本次在线文档导航显示 `0.6.9`。两者在个别配置名称 / 默认值上可能有差异;真实项目 POC 必须以目标项目实际生成的 `.trellis/` 文件、本地脚本和安装版本为准。
## 1. 结论先行
目前已确认把实现侧闭环定义为:
1. **飞书 Base 管“待做什么、由谁做、依赖谁、当前业务状态”**,是 Spec / Ticket 队列和对外状态的事实源。
2. **Trellis 管复杂工作的本地执行上下文**:任务、PRD、技术设计、实施计划、检查上下文、归档和 journal。
3. **repo 管实现与验证事实**:代码、测试、ADR、差异和可回放的验证结果。
4. **普通小功能继续 Inline**,不为了状态同步强行创建 Trellis 任务;**复杂功能严格使用“1 Spec = 1 Trellis task”**,Ticket 只作为该 task 内可刷新的实施计划和验收单元,不再映射为 Trellis child task。
5. 已实现两个职责分离的全局显式 skill:
- **`start-work-feishu`**:识别当前飞书用户,列出其负责的 Spec / Ticket,让用户选择,然后路由到 Inline 或 Trellis。
- **`close-work-feishu`**:承担“部分收口、最终收口、对账重放”。它按 Spec 查找全部子 Tickets,分析可收口项并交给用户 review / 确认;只有被确认的 Tickets 或 Spec 才写入 `已完成`。
6. **Trellis 路径的 Spec 最终收口必须以归档事件为准,不能以 `finish` 事件为准**。官方明确:`after_finish` 只表示当前 session 解除任务指针,任务可能仍在其他 session 继续;外部系统 done 应接 `after_archive`。Ticket 的部分收口可提前进行,但不得因此关闭 Spec。[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
7. **不把 lifecycle hook 当成唯一保障**。官方规定 hook 失败只警告、不阻断主任务操作;因此必须由显式 close skill 完成飞书写入与回读。本方案中 hook 至多记录可重放的 pending / outbox 事件。[官方:`config.yaml` 配置](https://docs.trytrellis.app/zh/advanced/configuration)
8. **所有完成态都以人类 review 为必要门禁**。Agent 可以分析哪些 Ticket 已满足验收,但不能自行将它们或父 Spec 置为 `已完成`。
## 2. 本文如何区分事实与方案
| 标签 | 含义 | 能否当成已存在能力 |
|---|---|---|
| **[官方事实]** | 来自用户给出的 5 篇 Trellis 官方文档 | 可,但仍应以项目当前生成文件和安装版本为准 |
| **[Matt 提取]** | 来自已完成的《Matt 工作流 × 飞书 CLI 全流程总结》 | 可作为当前飞书 POC 和工作流合约 |
| **[推导 / 推荐]** | 基于上述事实对新闭环的设计 | 未标记实现时不能当成已有能力 |
| **[V1 已实现]** | 已写入全局 skill 并完成本地验证的合约 | 可用于 fixture;真实 Base 能力仍需目标项目 POC |
本文不会把本方案的 skill、字段、任务元数据或 CLI 语法误写成 Trellis 内置能力。
## 3. Trellis 官方文档事实整理
### 3.1 定位与事实源
**[官方事实]** Trellis 将自己定位为“Team-level Agent Harness with built-in LLM wiki”:
- Agent Harness 管 workflow state、hook、skill、sub-agent 和平台适配。
- LLM wiki 把 spec、task、research、journal 放在仓库文件里。
- workflow、spec、task 受 Git 跟踪;workspace memory 按 developer 隔离。
- 它的核心思路是把 AI coding 当成“工作流 + 知识管理”,而不是一次性聊天。
来源:[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
Trellis 本地内容的职责边界如下:
| 内容 | 位置 | 职责 |
|---|---|---|
| Workflow 合约 | `.trellis/workflow.md` | Plan → Execute → Finish、skill 路由和每轮 next action |
| 团队稳定规范 | `.trellis/spec/` | 可跨任务复用的团队知识 |
| 任务事实 | `.trellis/tasks/<task>/` | PRD、设计、实施计划、research、实现 / 检查 context manifest |
| 开发者记忆 | `.trellis/workspace/<developer>/` | 开发者 journal 和索引 |
| 当前任务指针 | `.trellis/.runtime/sessions/<session-key>.json` | 把一个 AI session / 窗口指向一个任务 |
来源:[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
### 3.2 任务结构和上下文加载
**[官方事实]** 典型任务目录包含:
```text
.trellis/tasks/<task>/
├── task.json
├── prd.md
├── design.md
├── implement.md
├── implement.jsonl
├── check.jsonl
└── research/
```
- `task.json` 承载状态、优先级、负责人、分支、PR URL、父子关系和扩展元数据。
- `prd.md` 承载需求、约束、验收标准和 out-of-scope。
- 复杂任务使用 `design.md` 和 `implement.md`。
- `implement.jsonl` / `check.jsonl` 分别列实现与检查所需的 spec / research 文件。
- 实现 / 检查的标准读取顺序是“JSONL entries → `prd.md` → 可选 `design.md` → 可选 `implement.md`”。
来源:[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
### 3.3 任务状态与生命周期
**[官方事实]** 默认状态是:
```mermaid
flowchart LR
NoTask["no_task\n当前 session 无 active task"] -->|create| Planning["planning\n需求与规划"]
Planning -->|start| Progress["in_progress\n实现、验收、收尾"]
Progress -->|archive| Completed["completed\n归档前写入"]
Progress -.->|finish| Detached["只清除当前 session 指针\n不等于任务完成"]
```
- `no_task` 由 hook 在没有 active task 时合成。
- 创建任务后是 `planning`,启动后是 `in_progress`。
- `completed` 由 archive 在归档前写入,正常不会作为 live breadcrumb 长时存在。
- active task 按 session 隔离;同一仓库的不同窗口可以做不同任务。
来源:[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
**[官方事实]** 任务 lifecycle hook 是“命令事件”,不是通用 status watcher:
| 事件 | 确切含义 | 是否可作为外部 done 判定信号 |
|---|---|---|
| `after_create` | 任务目录已创建 | 否 |
| `after_start` | 任务进入 `in_progress` | 可用于写“进行中” |
| `after_finish` | 当前 AI session 已解除任务指针;任务可能在其他 session 继续 | **否** |
| `after_archive` | 任务已归档 | **是,官方指定的完成事件;但它不证明外部写入已成功** |
来源:[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
### 3.4 Finish 边界
**[官方事实]** Trellis 把实现、工作 commit 和收尾记账分开:
1. implement / check 产出通过检查的 diff。
2. 主会话做最终验证并更新 spec。
3. 工作 commit 先发生。
4. `/trellis:finish-work` 如果发现当前任务改动未提交会停止,之后才归档任务并写 workspace journal。
`/trellis:finish-work` 不是提交功能代码的命令。
来源:[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
本文之后所说的“`finish-work` 之后收口”,统一解释为:**目标项目已按它的 workflow 完成最终验证,且对应 task 已真正 archive**。它绝不等同于底层 `task.py finish`,后者只是 session detach。官方 native `finish-work` 与本地 Trellis × Matt 的 `archive --no-commit` 收尾也不同;实现 skill 不猜测用户输入的名称,而是回读 archived task 证明这个门禁。
### 3.5 Workflow 的可定制点和不可随意更改的边界
**[官方事实]** `.trellis/workflow.md` 集中定义:
- Phase 和分步说明。
- Skill Routing,即“用户意图 → auto-trigger skill”。
- `[workflow-state:STATUS]` 每轮面包屑。
- `task.py` 命令参考。
可添加自定义状态、新 Phase、Plan 分支或新 skill 路由,但有三类约定受脚本依赖:
- `[workflow-state:STATUS]...[/workflow-state:STATUS]` 标签格式。
- `## Phase X` + `#### X.Y` 标题层级。
- 真实 `task.py` 子命令名。只改 Markdown 不会改变 CLI。
面包屑文本下一条用户消息生效;Phase / step 正文和 skill routing 下一个 session 生效。
来源:[官方:定制 Workflow](https://docs.trytrellis.app/zh/advanced/custom-workflow)
### 3.6 Skill 与 sub-agent 的选型
**[官方事实]** 三种扩展点的职责不同:
| 扩展点 | 适合的问题 | 本闭环中的判断 |
|---|---|---|
| Command | 用户显式决定进入的会话边界 | 可作为手动补偿入口,但本文不预设具体命令 |
| Sub-agent | 需要隔离 prompt / 角色约束的子进程 | 不是 Spec / Ticket 选择和飞书状态回写的必需条件 |
| Skill | 根据意图自动触发、能力或阶段级的可复用工作流 | **适合本闭环的主要扩展点** |
Skill 的 `description` 应该写“什么情况下触发”,正文应再做触发自检、列明动手前必读文件、给出固定输出格式。Codex 的项目级 skill 在 `.codex/skills/{name}/SKILL.md`,官方同时使用 `.agents/skills/` 作为跨平台共享层。
来源:[官方:定制 Skill](https://docs.trytrellis.app/zh/advanced/custom-skills)
**[官方事实]** Trellis 原生提供 `trellis-implement`、`trellis-check`、`trellis-research` 三个 sub-agent。Codex 也可使用 inline 模式,由主会话通过 skill 读取同一批 task artifacts;自定义 sub-agent 若要拿到同类上下文,要约定 task-local JSONL 并遵守相同读取顺序。
来源:[官方:定制 Sub-agent](https://docs.trytrellis.app/zh/advanced/custom-agents)
### 3.7 `config.yaml` 和 lifecycle hook
**[官方事实]** `.trellis/config.yaml` 是应跟仓库提交的项目级共享配置,控制 session journal commit、任务 lifecycle hook、package 映射和 Codex 派发模式。
- 不应把机器身份、token、API key 或机器绝对路径放进该文件。
- 开发者身份放 `.trellis/.developer`;凭证放环境变量或常规密钥管理。
- `hooks` 支持 `after_create`、`after_start`、`after_finish`、`after_archive`,命令会收到指向当前 `task.json` 的 `TASK_JSON_PATH`。
- Hook 失败只打印警告,不会阻断任务操作。
- 在当前在线配置文档中,`codex.dispatch_mode` 记为默认 `inline`,`sub-agent` 用于选择旧派发模式。但这一点存在明确的本地版本差异,见 3.8;本方案不应依赖该默认值。
- `session_auto_commit` 默认为 `true`。若团队希望手动 review / commit Trellis 记账改动,可设为 `false`。
来源:[官方:`config.yaml` 配置](https://docs.trytrellis.app/zh/advanced/configuration)
### 3.8 在线文档与本机可执行事实的版本边界
**[本机验证]** 2026-07-26 实际执行:
```text
trellis --version -> 0.6.8
lark-cli --version -> 1.0.76
```
在线 Trellis 文档导航已显示 `v0.6.9` changelog;本机 0.6.8 已安装模板中,`codex.dispatch_mode` 的默认值是 `auto`,`inline` 是显式退出 sub-agent 派发,`sub-agent` 只是 `auto` 的兼容别名。这与当前在线配置页的描述不一致。
因此:
- 本文只依赖稳定的 task / artifact / lifecycle 语义,不把 `dispatch_mode` 当成飞书闭环的主集成点。
- 真正实现时,以目标项目的 `trellis --version`、`.trellis/config.yaml`、已生成 agent / skill 文件和 `python3 ./.trellis/scripts/task.py --help` 为可执行事实源。
- `trellis update` 后必须检查本地 workflow 覆盖和 `.new` / migration 差异,不直接假设在线文档与目标项目已同步。
## 4. 从现有 Matt 文档提取的“实现管理”
本节只提取实际开发时的管理思想,不扩展 implement / tdd / code-review 的编码规则。本节来源均为[本地文档:《Matt 工作流 × 飞书 CLI 全流程总结》](<./Matt 工作流 × 飞书 CLI 全流程总结.md>)。
### 4.1 三类事实源不重叠
**[Matt 提取]**
| 系统 | 管理内容 |
|---|---|
| Feishu Base | 当前状态、类别、负责人、进度、时间、父子关系和阻塞关系 |
| Feishu Wiki / Docs | Spec、Triage Notes、Agent / Human Brief 等长文档 |
| repo | 代码、测试、ADR、领域上下文和被拒绝 enhancement 的决定 |
直接含义是:新闭环不应该在 Trellis 中复制一份“飞书当前业务状态”,也不应该把完整 Wiki Spec 长期复制为第二份权威文档。
### 4.2 Spec 和 Ticket 的管理含义
**[Matt 提取]**
- Spec 是“问题、方案、验收边界”的聚合产物;完整内容在 Wiki,Base 保存可查询摘要和状态。
- Ticket 是 tracer-bullet 垂直切片;每张 Ticket 是 Base 记录,V1 不建独立 Wiki,而是沿 `所属父项` 找到父 Spec 和完整文档。
- Ticket 的 `前置依赖` 使用真实 Base record ID 建图。
- 当一张 Ticket 所有 blocker 都已完成且它尚未认领时,它才是可执行 frontier。
因此,“当前用户的 Ticket”不能只做 `负责人 = 当前用户` 的扫描;列表还应该区分“可开始、可恢复、被阻塞”。
### 4.3 实现状态机和完成门禁
**[Matt 提取]** 主执行流包含:
```text
ready-for-agent / ready-for-human
→ 进行中
→ 阻塞 ⇄ 进行中
→ 待评审 ⇄ 进行中
→ 已完成
```
关键一致性规则:
- `状态=阻塞` 时,`阻塞原因` 和 `下一步` 必须非空。
- 设置 `已完成` 前必须有具体 `验证证据`;无法验证时要写“未运行”和原因,不能伪装完成。
- `完成度`、`下一步`、`阻塞原因` 需与状态一致。
- Base create / update 后必须 `record-get` 或完整分页回读,`ok:true` 本身不是最终证据。
### 4.4 身份和责任分离
**[Matt 提取]**
- API 执行者是 bot。
- CLI 工作流真实发起人写入 `最后更新人`。
- `负责人` 是当前执行责任人,不因 bot 执行 API 而被覆盖。
- 每次 Base create / update 都刷新 `最后更新人`。
这意味着“当前用户”应从已验证的 Feishu CLI 用户身份取得,不能将 macOS 用户名、Trellis `.developer` 文本或 bot 身份直接当成飞书 `负责人`。
### 4.5 当前已完成与未闭环的部分
**[Matt 提取]** 当前飞书硬依赖适配已覆盖 setup、Spec、Tickets 和 Triage;`implement`、`tdd`、`code-review` 等下游流程只是通过公共 issue-tracker 合约消费同一 Base。
所以当前真正的缺口不是“再建一套开发规范”,而是:
1. 实现前如何从 Base 得到当前用户可执行的工作。
2. 如何将选中的 Spec / Ticket 和 Inline 会话或 Trellis task 稳定关联。
3. 实现结束后如何把实际验证证据、代码引用和终态回写 Base。
4. 外部回写失败时如何可见、可重试,而不是把任务误报为已闭环。
### 4.6 本地 Trellis × Matt 覆盖层的实现管理原则
**[Matt 提取]** 本仓库现有 [Trellis × Matt 工作流](<../AI-RD-Workflow/40-workflows/trellis-matt/CN/workflow.md>) 已经把实际开发分成三种模式:
| 模式 | 管理含义 |
|---|---|
| Inline | 简单、局部、根因明确且一个上下文可完成;不创建 Trellis task |
| Matt without Trellis | 不属于 Inline,但仍可单会话完成;用最匹配的工程方法,不为“看起来正式”创建 task |
| Trellis + Matt | 跨会话、多项稳定决策、多交付物或 durable research;Trellis 管生命周期,Matt 管当前阶段方法 |
与本闭环直接相关的管理原则是:
- **一个 lifecycle owner,一个 method owner**:Trellis 管 task 状态与恢复;实现方法不再自建第二套任务状态。
- `prd.md`、条件性的 `design.md` / `implement.md` 是 task-level 执行事实;跨会话任务用 `Current Checkpoint` 记录已完成、证据、下一步和 blocker。
- 实现 agent 不修改 Trellis 状态、requirements 或 acceptance criteria,不执行 Git 写操作;主会话负责完整 diff、最终验收、checkpoint 和用户沟通。
- 飞书的选择、外部写入确认和状态回读也应归主会话,不下放给 coding sub-agent。
- 本地覆盖不使用旧的 commit-first `trellis-finish-work`,而是以 `archive --no-commit` + `add_session.py --no-commit` 记账;commit / push / PR 仍只由用户明确授权。
这里有一个必须在设计中显式消歧的术语冲突:
- 你说的 **Inline 开发** = 不创建 Trellis task。
- Trellis 的 **Codex `dispatch_mode: inline`** = 已经处在 Trellis task 里,只是由主 Codex agent 直接实现。
两者不能共用一个判断条件。
## 5. 设计思考:五个关键问题及已确认解法
以下是可对外审查的设计推理,不是把内部思维过程当成事实。
### 5.1 Spec、Ticket 和 Trellis task 不是同一层概念
| 概念 | 回答的问题 | 推荐角色 |
|---|---|---|
| Spec | “为什么做、做到什么程度才算完成” | 复杂开发的业务聚合根 |
| Ticket | “按什么可验证切片推进,哪些切片已解锁” | 可刷新的实施计划 / 验收单元 |
| Trellis task | “本地这次复杂执行需要什么上下文和生命周期” | 本地执行容器 |
**[已确认]** 严格使用 `1 Spec = 1 Trellis task`。Ticket 只作为该 task 的动态执行计划、frontier 和验收单元,不映射为 Trellis child task。这样只保留一个 task lifecycle,避免 Trellis 任务树和 Base Ticket 依赖图双轨漂移。
### 5.2 两套状态机必须指定单向事实源
**[推荐]** 不做“两边任意修改、互相最后写入覆盖”的双向同步。
| 事实 | 权威来源 | 另一侧如何使用 |
|---|---|---|
| 负责人、优先级、Spec/Ticket 关系、前置依赖 | Feishu Base | Trellis 启动 / 恢复时读取快照 |
| Spec 长文档 | Feishu Wiki | Trellis task 保存链接和执行所需的摘要,不另建权威副本 |
| 当前 session 的 active task、本地 plan / check 上下文 | Trellis | 必要的节点摘要回写 Base |
| 代码、测试、commit / PR 引用 | repo | Base 仅保存 `代码引用` 和 `验证证据` |
| 对外工作状态 | Feishu Base | Trellis lifecycle 作为触发事件,不取代 Base |
### 5.3 需要稳定的关联 ID,不能靠标题回猜
**[推荐]** 用 Feishu Base `record_id` 作为 Spec / Ticket 的稳定关联键。标题只用于展示和启动前查重,不用于收口时反向查找。
Trellis 官方架构文档说 `task.json` 支持“扩展元数据”。**[本机验证]** Trellis 0.6.8 的 task 初始结构实际包含 `meta: {}`,archive 后 `after_archive` 收到的 `TASK_JSON_PATH` 指向已移入 archive 的 `task.json`。因此初步推荐用 `task.json.meta.feishuTracker`保存关联,但实现时仍要在目标项目回读 schema 并跑 archive POC。
候选合约(方案定义,非 Trellis 内置字段):
```json
{
"meta": {
"feishuTracker": {
"schemaVersion": 1,
"specRecordId": "rec_xxx",
"ticketRecordIds": ["rec_aaa", "rec_bbb"],
"selectedRecordIds": ["rec_aaa"],
"specWikiUrl": "https://...",
"trackerContractPath": "docs/agents/issue-tracker.md",
"boundAt": "2026-07-26T00:00:00+08:00",
"lastRefreshAt": "2026-07-26T00:00:00+08:00"
}
}
}
```
不将 Base token、API token、密钥或固定个人 open ID 写入 task 元数据。Base / table 坐标继续由 repo tracker contract 提供;当次人类用户每次通过 `lark-cli auth status --json --verify` 重新验证。
关联基数保持为:
```text
Trellis task
↔ 1 个 Feishu Spec record_id
↔ 0..N 个 Feishu Ticket record_id
↔ Spec Wiki URL
```
Inline 路径不为了保存 mapping 而初始化 Trellis。同会话收口时使用用户已选的 record IDs;若跨会话恢复,收口 skill 重新完整查询“负责人 = 当前用户 且 状态 = 进行中 / 待评审 / 阻塞”的记录,让用户按 record ID 重选,不用标题猜测。
### 5.4 “任务归档”只是 Spec 最终完成的必要条件,不是充分条件
**[已确认]** Ticket 可在 task 进行中通过部分收口逐张完成;`after_archive` 只解决“何时允许尝试 Spec 最终收口”。它不能单独证明每张 Ticket 均已验收。Spec 完成回写仍必须同时满足:
- 对应验收标准已核对。
- 真实验证命令 / 结果已采集;未运行项已明示。
- 需要的 check 已通过,且人类已完成最终 review。
- 要更新的每个 Ticket 都有对应证据,不因父 Spec 任务归档而批量猜测“全部完成”。
### 5.5 Inline 和 Trellis 应共用收口协议
**[推荐]** Inline 和 Trellis 的区别只是“是否需要持久的本地任务容器”,不应导致两套飞书状态规则。
```mermaid
flowchart TD
Queue["当前用户的 Spec / Ticket 队列"] --> Select["用户选择工作项"]
Select --> Route{"是否需要持久的复杂任务上下文?"}
Route -->|"No"| Inline["Inline 实现"]
Route -->|"Yes"| Task["1 Spec ↔ 1 Trellis task\nTickets 作为计划 / 验收单元"]
Inline --> Verify["共用验证与 Base 回写协议"]
Task --> Archive["Trellis 最终验证 + 归档"]
Archive --> Verify
Verify --> Readback["Base 回读 + 收口回执"]
```
## 6. 初步方案
### 6.1 总体架构
**[推荐]** 把新能力分成三层:
| 层 | 职责 | 不应负责的事 |
|---|---|---|
| Skill 编排层 | 身份门禁、列表、用户选择、路由、收口规则、结果汇报 | 不把 API 返回的 `ok:true` 当最终证据 |
| CLI 读写层 | 调用已验证的 `lark-cli`,查询 / patch / record-get | 不负责决定应将哪张 Ticket 置为完成 |
| Trellis lifecycle 集成层 | 提供 task 关联、start / archive 事件和归档上下文 | 不替代 Base 作为业务状态事实源 |
**[本机验证]** `lark-cli 1.0.76` 已提供 V1 所需的基础原语:
- `auth status --json --verify`:验证 bot / user 身份。
- `base +record-list`:结构化 filter、sort、字段投影、`offset` 和单页最大 `limit=200`;skill 必须自行循环到完整结果。
- `base +record-batch-update`:单次最多 200 条的差异化 patch,响应不保证 record ID 存在。
- `base +record-get`:按稳定 record ID 回读,所以 batch update 后仍需逐条或分批验证。
V1 可直接由 skill 编排这些 `lark-cli` 命令;先不为了包装而新建 CLI。只有当分页、frontier 计算、幂等 patch 在两个 skill 中形成重复且已经 POC 验证时,再提取 repo-local helper。
### 6.2 Skill A:工作入队与路由
**[V1 已实现]** 全局显式 skill 名为 `start-work-feishu`,source of truth 位于 `~/.agents/skills/start-work-feishu/`;它不是 Trellis 内置命令。
触发条件:
- 用户表示“开始开发、看我的待办、选一个 Spec / Ticket 实现”。
- 当前无 active Trellis task,或用户明确要恢复已认领工作。
建议流程:
1. 运行已验证的 Feishu 身份检查,冻结当前人类用户 open ID;不把 bot 当用户。
2. 分别完整分页查询两类记录:
- Spec 队列:`产物类型 = PRD/Spec` 且 **Spec 自身** `负责人 = 当前用户`。不因“它的子 Ticket 由当前用户负责”而把该 Spec 追加到 Spec 队列。
- Ticket 队列:`产物类型 = 实现 Ticket` 且 Ticket 自身 `负责人 = 当前用户`。展示时可附带父 Spec 上下文,但不改变 Spec 队列口径。
3. 分组展示:
- **恢复执行**:`进行中` / `阻塞`。
- **现在可开始**:`ready-for-agent` 且 blocker 都已完成的 Ticket,以及符合条件的 Spec。
- **尚未解锁**:存在未完成 blocker 的 Ticket,只展示原因,不默认推荐开工。
4. 每行至少展示:标题、产物类型、Base record ID、状态、优先级、父 Spec、阻塞项、更新时间和建议路由。
5. 用户选择后,用 record ID 重新取得最新记录,防止列表与实际状态之间竞态。
6. 如选 Ticket,沿 `所属父项` 取父 Spec 和 Wiki;如选 Spec,同时取子 Tickets 和依赖图。
7. 根据既有约定路由:
- 范围小、根因 / 方案已知、当前上下文可以完成:Inline。
- 跨模块、需持久计划、多会话、多人 / 多 Agent 或验收链较长:Trellis task。
8. 列表本身只读。用户选择后,skill 一次性展示“工作项 + Inline / Trellis 路由 + 拟写 Base patch”;用户确认“开始”后,同时构成任务路由决定和这一次外部写入授权,不再追加一个纯流程性的“是否创建 Trellis task”问题。
9. 官方 native `no_task` breadcrumb 要求任务创建同意,但本地 Trellis × Matt workflow 明确覆盖为“满足 durable 条件时直接创建,不问 task-consent”。本方案用上一步的“选择并开始”统一两者,不改 Trellis CLI 语义。
10. 当 record ID 关联已成功保存且用户确认开始时,将选中的聚合工作项更新为 `进行中`。选 Spec 时先只写 Spec,其他 Tickets 保持原状态;选 Ticket 时写该 Ticket,并将其父 Spec 写为 `进行中`(如尚未进入),其他 Tickets 不动。这里的语义是“已认领并开始规划 / 执行”,不声称代码已写。
11. 每次写入都带 `最后更新人 = 当前人类用户`,并立即回读核对。
建议列表形式:
| 序号 | 可执行性 | 类型 | 标题 | 状态 | 优先级 | 父 Spec | 阻塞 | 建议路由 |
|---:|---|---|---|---|---|---|---|---|
| 1 | 可恢复 | Spec | … | 进行中 | P1 | — | — | Trellis |
| 2 | 可开始 | Ticket | … | ready-for-agent | P2 | Spec A | 无 | Inline |
| 3 | 被阻塞 | Ticket | … | ready-for-agent | P2 | Spec B | Ticket X | 暂不开工 |
### 6.3 复杂开发的映射规则
**[推荐]**
```text
Feishu Spec record_id
↔ Trellis task
├── prd.md:Spec 链接、必要摘要、验收边界
├── implement.md:按 Ticket frontier 生成 / 刷新的执行计划
├── implement.jsonl / check.jsonl:只收录必要的本地 spec / research
└── task.json.meta.feishuTracker:Spec record_id + Ticket record_ids
```
约束:
- 同一 Spec 同时最多对应一个 active Trellis task。创建前先扫描 active task 的 `meta.feishuTracker.specRecordId`;已存在时恢复原 task,不按标题再建一个。
- Feishu Wiki Spec 仍是长规格事实源;Trellis `prd.md` 是本地执行上下文,应显式记录来源 record ID / URL 和取得时间。
- Ticket 状态和依赖仍以 Base 为准;`implement.md` 是可执行快照,恢复开发时先检查 Base 是否已变化。
- 每次恢复 Trellis task 时,按 `specRecordId` 重取 Spec、子 Tickets、依赖与 `更新时间`,与 `lastRefreshAt` 快照比较。新增 Ticket 可追加为新计划项;验收、范围或依赖发生实质变化时,先向用户展示 diff,不静默改写已 review 的 task artifacts。
- `implement.md` 保存本地 checkpoint,Base Ticket 保存对外状态。二者的重合内容是可重建快照,不形成双向最后写入覆盖。
- Ticket 不映射成 Trellis child task;所有 Tickets 都通过同一 Spec task 的 `implement.md` / checkpoint 管理。
- 不把完整业务文档塞进 JSONL;Trellis 官方建议 JSONL 只列当前任务需要的 spec / research。[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
### 6.4 Skill B:验证与飞书收口
**[V1 已实现]** 全局显式 skill 名为 `close-work-feishu`,source of truth 位于 `~/.agents/skills/close-work-feishu/`;它不是 Trellis 内置命令。
这个 skill 支持三个 routing,而不是只能在整个 Spec 完成后运行:
| Routing | 何时调用 | 允许写入的终态 |
|---|---|---|
| **部分收口** | Inline 或 Trellis task 还在进行中,用户希望先结算已完成切片 | 只能关闭经人类 review 确认的 Tickets;Spec 继续保持 `进行中` |
| **最终收口** | 全部 Ticket 已结算,Spec 验收已完成;Trellis 路径的 task 还必须已 archive | 先确认漏网 Tickets,最后才允许关闭 Spec |
| **对账重放** | 上次写入部分成功、回读失败或记录已处于目标终态 | 只补齐仍有差异的记录;证据完整时返回“已同步”,不重复追加 |
Inline 不等待 Trellis 事件。Trellis 的部分收口可在 active task 期间运行;最终收口才要求 archive。`after_archive` 仍只负责提醒或标记“待对账”,不直接代表 Base 已写回。
共享步骤:
1. 先确定唯一父 Spec。Trellis 从 active / archived task 的 `meta.feishuTracker.specRecordId` 取得;Inline 从当会话选择或一次用户重选取得。不用标题反查。
2. **按 Spec 回读所有未完成 Tickets**,不只分析当前 mapping 快照中的 Ticket IDs。同时重取验收标准、负责人、状态、前置依赖和更新时间,避免漏掉开发中新增或变更的 Tickets。
3. 汇总 repo diff、真实验证命令与结果、未运行项及原因、review 证据、代码引用和 Trellis checkpoint,并逐张映射 Ticket 验收标准。
4. 对未完成 Tickets 分类:
- **建议可收口**:验收条件和直接证据充分,可交给人类 review。
- **待补证 / 待验证**:实现看似已有,但证据或验收映射不足,不建议完成。
- **明确未完成 / 阻塞**:仍有实现项、未满足 blocker 或需要新决策。
5. 向用户展示逐 Ticket 分析:标题、record ID、验收结论、证据、风险、建议动作和拟写 patch。Agent 只推荐,不自行选择终态。
6. **人类 review 是每张 Ticket 写入 `已完成` 的必要条件**。用户可确认全部建议项,也可只选其中一部分;未被确认的 Ticket 不写终态。
7. 对用户确认的 Tickets 构造最小 patch,写入状态、完成度、Ticket 专属的验证证据、代码引用和 `最后更新人`,清理终态不应保留的阻塞字段。不覆盖无关新写入。
8. 先更新 Tickets 并逐条回读。部分收口在此结束:父 Spec 保持 `进行中`,未完成 Tickets 保持原状态,回执中列出最小下一步。
9. 最终收口在 Ticket 回读后重新查询全部子 Tickets。只有当全部 Tickets 已完成、Spec 级验收充分、Trellis task 已 archive(若适用)且人类完成最终 review,才展示 Spec 终态 patch 并再取得一次确认。
10. 更新 Spec 后回读,最终输出收口回执:已更新记录、未收口记录及原因、失败记录、人类 review 结论和回读证据。
```mermaid
flowchart TD
Invoke["用户执行收口 skill"] --> Spec["定位 Spec record ID"]
Spec --> Fetch["取全部未完成 Tickets"]
Fetch --> Analyze["逐 Ticket 分析验收与证据"]
Analyze --> Review["人类 review 并选择可收口 Tickets"]
Review --> CloseTickets["更新 Tickets + 逐条回读"]
CloseTickets --> Remaining{"仍有未完成 Ticket?"}
Remaining -->|"Yes"| Partial["部分收口完成\nSpec 保持进行中"]
Remaining -->|"No"| FinalGate{"Spec 验收 + 人类最终 review\n+ Trellis archive 如适用?"}
FinalGate -->|"No"| Wait["保持 Spec 进行中 / 待评审"]
FinalGate -->|"Yes"| CloseSpec["用户确认 Spec patch"]
CloseSpec --> Done["更新 Spec + 回读"]
```
推荐的完成 patch 语义,不是未验证的 CLI 命令:
| 字段 | 目标值 / 规则 |
|---|---|
| `工作流阶段` | 已完成实现和验收后进入交付,或按团队最终确定的阶段映射 |
| `状态` | `已完成` |
| `完成度` | 100 |
| `验证证据` | 真实命令 + 结果摘要 + 未运行项 |
| `代码引用` | 任务路径、分支、commit、PR 或关键文件,只写真实存在的引用 |
| `阻塞原因` | 清空 |
| `下一步` | 清空,或按团队终态规则写明后续交付动作 |
| `最后更新人` | 当前已验证 CLI 人类用户 |
### 6.5 Trellis 与 Base 状态映射
**[推荐]**
| Trellis / 本地事件 | Base 候选状态 | 备注 |
|---|---|---|
| 已列表、用户未选择 | 不写 | 只读查询不应该改状态 |
| 用户确认“选择并开始”,mapping 保存成功 | `进行中` | 表示已认领并进入 planning / execution;不猜测完成度 |
| Trellis `planning` / `after_start` | 通常不再重复写 | 由入队 skill 完成开始回写;hook 可做对账,不必二次 patch |
| 外部 blocker 出现 | `阻塞` | `阻塞原因` + `下一步` 必填 |
| Agent 分析 Ticket “建议可收口”,尚未人类 review | 保持原状态,或经确认写 `待评审` | 绝不自动写 `已完成` |
| 部分收口:人类 review 并确认部分 Tickets | 被选 Tickets → `已完成` | 其他 Tickets 不动,Spec 保持 `进行中` |
| `after_finish` | 不写 | 只是 session detach |
| `after_archive` | 不直接写 done | 只生成收口提醒 / 待对账信号 |
| 全部 Tickets 完成 + Spec 验收 + 人类最终 review + archive(如适用) | Spec → `已完成` | 最终收口的聚合门禁;任一条不满足都不关 Spec |
| 同步失败 | 保留原状态 | 本地报告“代码 / Trellis 已完成,Base 待对账”,不伪报闭环 |
该映射要在 POC 后才能固化进 skill;特别是 `工作流阶段` 和 Spec 聚合完成语义,需再确认业务预期。
### 6.6 Hook 怎么用:事件触发,不是唯一保障
**[推荐]** 分三阶段导入:
#### V1:skill 显式闭环
- 由工作入队 skill 负责选择、路由和开始状态回写。
- Inline / Trellis 进行中都可显式执行收口 skill 的“部分收口”routing,只关闭经人类 review 确认的 Tickets。
- Inline 整体验收完成,或 Trellis 最终验证和归档后,显式执行“最终收口”routing,才可能关闭 Spec。
- 先证明字段映射、回读和幂等性,不先扩大到全自动 hook。
#### V1.5:Workflow routing 固化入口
- 在 `.trellis/workflow.md` 的 Skill Routing 中加入“开始飞书工作”与“实现完成后闭环飞书”的意图路由。
- 在 Finish 阶段明确“archive 成功后运行飞书收口 / 对账”。
- 保持 Trellis 解析器依赖的 block 标签、Phase / step 标题层级和真实命令名不变。[官方:定制 Workflow](https://docs.trytrellis.app/zh/advanced/custom-workflow)
#### V2:lifecycle hook + 可重放对账
- `after_start` 可选用于检查绑定工作项是否已是 `进行中`,不与入队 skill 重复 patch。
- `after_archive` 只输出明确提醒,或写一个不含凭证的本地待对账标记;后续仍由用户显式运行收口 skill。
- `after_finish` 不更新 Base 完成态。
- Hook 里不放 Base token、user open ID 或机器绝对路径;`.trellis/config.yaml` 只保存团队共享的相对命令。[官方:`config.yaml` 配置](https://docs.trytrellis.app/zh/advanced/configuration)
- 收口 skill 扫描已 archive 任务的 `meta.feishuTracker`,并回读 Base 判断是否待同步;不需要依赖 hook 的“成功标记”。
- 由于 hook 失败不阻断 archive,同一收口核心必须支持手动重放 / 对账;skill 作为唯一声称“Base 已闭环”的入口。
### 6.7 幂等、竞态和失败处理
**[推荐]** 收口设计必须包含:
1. **稳定键**:只按 record ID 更新。
2. **写前回读**:责任人、状态或父子关系已变更时停止并报告,不强制覆盖。
3. **最小 patch**:只写当前状态转移需要的字段。
4. **幂等重放**:目标已是相同终态且证据一致时,结果为“已同步”,不再创建新记录或重复附加证据。
5. **部分成功可见**:返回逐条成功 / 失败列表,不用一个总体 `ok` 遮蔽部分失败。
6. **回读才是成功**:只有回读字段符合预期才报告“Base 已闭环”。
7. **外部失败不篡改本地事实**:代码 / Trellis 已完成与 Base 同步失败必须分开汇报。
### 6.8 为什么暂不需要新 sub-agent
**[推荐]** Spec / Ticket 选择、外部写入确认和收口回执都依赖当前主会话,并不需要隔离的编码角色。Trellis 支持由主会话通过 skill 读取任务上下文,而本地 Trellis × Matt 覆盖已把 lifecycle、最终验收和用户沟通明确交给主会话。[官方:定制 Sub-agent](https://docs.trytrellis.app/zh/advanced/custom-agents)
可以继续使用 Trellis 原生 implement / check / research 角色处理各自的执行职责,但不应该让它们各自直接决定 Base 终态。
## 7. 建议的端到端时序
### 7.1 Inline 路径
```mermaid
sequenceDiagram
participant U as 用户
participant S as 工作入队 skill
participant F as Feishu Base / Wiki
participant C as Coding Agent
participant X as 工作收口 skill
U->>S: 显示我的可实现工作
S->>F: 验证身份 + 完整查询
F-->>S: Spec / Ticket + 关系 + 状态
S-->>U: 可恢复 / 可开始 / 被阻塞列表
U->>S: 选择工作项
S-->>U: 展示 Inline 路由 + Base 开始 patch
U->>S: 确认选择并开始
S->>F: 重读后写进行中,再回读
S->>C: record IDs + Spec + Tickets + 验收边界
C->>C: Inline 实现与验证
C->>X: 完成证据 + 代码引用
X-->>U: 展示逐 Ticket / Spec patch
U->>X: 确认外部写入
X->>F: 按 record ID 最小 patch
X->>F: 逐条回读
X-->>U: 收口回执
```
### 7.2 Trellis 路径
```mermaid
sequenceDiagram
participant U as 用户
participant S as 工作入队 skill
participant F as Feishu Base / Wiki
participant T as Trellis
participant X as 工作收口 / 对账
U->>S: 选择复杂 Spec / Ticket
S->>F: 取最新 Spec、Tickets、依赖
S-->>U: 展示 1 Spec ↔ 1 task 路由 + Base 开始 patch
U->>S: 确认选择并开始
S->>T: 建立 task 与 Base record IDs 的稳定关联
S->>F: 写进行中 + 最后更新人,再回读
T->>T: planning
T->>T: start → in_progress
T->>T: implement → check → update-spec → 最终验证
U->>T: 按目标项目合约执行 finish-work / archive
T->>T: archive + journal
T-->>X: after_archive 提醒 + archived TASK_JSON_PATH
U->>X: 显式运行收口 skill
X-->>U: 展示逐 Ticket / Spec patch
U->>X: 确认外部写入
X->>F: 按逐 Ticket 证据幂等收口,Spec 最后写
X->>F: 回读
X-->>U: 收口成功,或 Base 待对账
```
图中 `after_archive` 和 `TASK_JSON_PATH` 是 Trellis 官方已有事实;它们只触发提醒,不绕过用户确认直接写飞书。具体收口 skill 和 Feishu CLI payload 是本方案待实现部分。[官方:架构全景](https://docs.trytrellis.app/zh/advanced/architecture) [官方:`config.yaml` 配置](https://docs.trytrellis.app/zh/advanced/configuration)
## 8. 建议的最小 POC
本文推荐先不改完整 Trellis workflow,用一个真实但可回收的 Spec + 两张 Ticket 验证下列最小闭环:
1. 当前 Feishu CLI 人类用户可被准确解析。
2. 能完整列出“Spec 自身负责人 = 当前用户”的 Spec,不因子 Ticket 归属扩张 Spec 列表;同时独立列出当前用户负责的 Tickets,并正确计算依赖 frontier。
3. 用户选定后,Trellis 路径能把 Spec / Ticket record IDs 保存到 `task.json.meta.feishuTracker`,且 archive 后仍可读取。
4. 同一个候选工作能分别跑通 Inline 和 Trellis 两条路径。
5. Trellis 的 `after_finish` 不触发 done;`after_archive` 只触发收口提醒,显式 skill 才尝试写 done。
6. 在 task 未 archive 时执行部分收口,skill 能按 Spec 取得全部未完成 Tickets,给出“建议可收口 / 待补证 / 未完成”分析,并只关闭用户 review 后确认的 Tickets。
7. 部分收口后 Spec 仍为 `进行中`;未被用户确认的 Tickets 不被误关闭。
8. 最终收口只在全部 Tickets 完成、Spec 验收通过、人类最终 review 通过且 Trellis task 已 archive(如适用)时关闭 Spec。
9. 重复执行部分 / 最终收口不会创建重复记录、重复证据或错误状态。
10. 刻意让一次 hook 失败,archive 仍成功,收口 skill 仍能通过 archive task mapping + Base 回读发现待同步工作。
11. 刻意让一次 Base batch update 部分失败,skill 能保留逐条成功 / 失败证据并幂等重放,不误关父 Spec。
12. 所有 Base 更新都显示 `最后更新人 = 当前真实用户`,且已逐条回读。
13. POC 验收后将测试记录标记为 `已取代`,保留审计证据,不删除。
POC 前先仅定义读取和预演模式,展示将修改的 record IDs 和字段;真实写飞书应在用户确认后进行。
## 9. 导入顺序建议
### 阶段 A:只读队列
- 实现身份验证、完整分页、负责人过滤、状态分组和 frontier 计算。
- 不改 Base、不建 Trellis task。
- 验收:列表与 Base UI 人工核对一致。
### 阶段 B:选择与开始同步
- 用户选择后重读 record。
- 实现 Inline / Trellis 路由和稳定 ID 绑定。
- 用户一次确认“选择并开始”后,写 `进行中`并回读;Trellis `after_start` 只做可选对账。
### 阶段 C:显式收口
- Inline 和 Trellis 共用一个收口核心。
- 先做“部分收口”:按 Spec 分析所有未完成 Tickets,人类 review 后选择收口集合。
- 再做“最终收口”:验证全 Ticket 完成、Spec 聚合验收、人类最终 review 和幂等重放。
### 阶段 D:Workflow 与 hook 集成
- 把 skill 意图写入 `.trellis/workflow.md` Skill Routing。
- 再接 `after_start` / `after_archive` 的对账 / 提醒能力,不在 hook 中无确认写飞书。
- 保留手动对账入口,并演练 hook 失败。
## 10. 已确认的设计决策
| # | 已确认决策 | 对实现的直接约束 |
|---:|---|---|
| 1 | 使用两个 skill:入队 skill + 收口 skill | 对账重放是收口 skill 的 routing,不新增第三个 skill |
| 2 | 严格 `1 Spec = 1 Trellis task` | Ticket 不建 Trellis task / child task,只作为同一 task 内的动态计划和验收单元 |
| 3 | 用户确认“选择并开始”且 mapping 保存后,立即写 `进行中` | 不新增“规划中”状态;`after_start` 不重复 patch |
| 4 | 必须人类 review 才能完成 | Agent check、测试通过和 archive 都只是证据,不能单独产生 Ticket / Spec 终态 |
| 5 | 收口 skill 支持部分收口 | 每次按 Spec 重取所有未完成 Tickets,分析可收口集合,用户 review / 选择后只关闭被确认 Tickets;Spec 保持进行中,直到最终聚合门禁通过 |
| 6 | 采用本文推荐的 mapping | 使用 `task.json.meta.feishuTracker`,不写 token、密钥或固定个人 open ID |
| 7 | Hook 按本文推荐边界 | `after_archive` 只提醒 / 待对账,不直接写 Feishu;显式收口 skill 才能声称 Base 闭环 |
| 8 | Spec 队列只列 Spec 自身负责人 = 当前用户 | 不因子 Ticket 归属扩张 Spec 队列;当前用户负责的 Tickets 仍作为独立 Ticket 队列展示 |
## 11. 风险与非目标
### 当前风险
- **状态双写风险**:若 Base 和 Trellis 都被当成业务状态权威源,会出现覆盖与逆向跳转。
- **误用 `after_finish`**:会在其他 session 仍工作时提前完成 Base。
- **hook 假成功**:archive 成功不代表 hook 成功,必须有重放 / 对账。
- **Spec 批量误关闭**:父 task 归档不能无证据地把所有 Ticket 置为已完成。
- **用标题做关联**:改名、重名和模糊查询会导致更新错记录。
- **身份混淆**:bot、`负责人`、`最后更新人` 和 Trellis developer 是不同概念。
- **敏感信息进仓**:不能把 token / API key / 个人 open ID 硬编码进 `.trellis/config.yaml`。
### 非目标
- 本文不设计新的 coding / TDD / review 规则。
- V1 已实现两个显式 skill,但不实现 hook、CLI wrapper、helper script 或 Trellis workflow 定制。
- 本文不修改飞书 Base schema 或真实业务记录。
- 本文不把本地结构 / fixture 验证等同于真实 Base POC。
## 12. 资料索引
### Trellis 官方一手资料
1. [架构全景](https://docs.trytrellis.app/zh/advanced/architecture)
2. [定制 Workflow](https://docs.trytrellis.app/zh/advanced/custom-workflow)
3. [定制 Skill](https://docs.trytrellis.app/zh/advanced/custom-skills)
4. [定制 Sub-agent](https://docs.trytrellis.app/zh/advanced/custom-agents)
5. [配置 `.trellis/config.yaml`](https://docs.trytrellis.app/zh/advanced/configuration)
### 本地已验证资料
- [Matt 工作流 × 飞书 CLI 全流程总结](<./Matt 工作流 × 飞书 CLI 全流程总结.md>)
- [Trellis × Matt 全局 Agent 规则](<../AI-RD-Workflow/40-workflows/trellis-matt/CN/AGENTS.md>)
- [Trellis × Matt 项目 Workflow](<../AI-RD-Workflow/40-workflows/trellis-matt/CN/workflow.md>)
- 本机命令验证:`trellis 0.6.8`、`lark-cli 1.0.76`、本机 Trellis 0.6.8 已安装 task / config 模板。
## 13. V1 实现状态与下一步
### 13.1 已实现
- `~/.agents/skills/start-work-feishu/`:`SKILL.md`、`references/feishu.md`、`agents/openai.yaml`。
- `~/.agents/skills/close-work-feishu/`:`SKILL.md`、`references/feishu.md`、`agents/openai.yaml`。
- 两个 skill 均设置 `policy.allow_implicit_invocation: false`,只允许用户显式调用。
- V1 直接编排项目内 Trellis 脚本与 `lark-cli`,没有新增 helper、hook、workflow 修改或 Base schema 写入。
### 13.2 已完成的本地验证
- 两个目录均通过 `skill-creator/scripts/quick_validate.py`。
- `agents/openai.yaml` 已解析并确认显式调用策略及 `$skill-name` 默认提示。
- 暂存目录与全局安装目录逐字节一致,无 `TODO` / 模板占位残留。
- 契约 fixture 覆盖 11 类场景:负责人过滤、父 Spec 上下文、完整 frontier、非完成终态、待评审分流、重复 task mapping、部分收口、缺失证据、`finish` 非 archive、Spec 最终门禁和幂等回读规则。
- 独立前向测试只使用脱敏 fixture,未调用 `lark-cli`、未写 Base;验证结果见本次实施回执。
### 13.3 尚未运行与后续顺序
当前机器没有发现任何项目级 `docs/agents/issue-tracker.md`,因此不能安全定位真实 Base,真实只读 POC 标记为 `未运行`。后续按以下顺序继续:
1. 用户指定一个已配置飞书 tracker contract 的 Trellis 项目。
2. 运行 `start-work-feishu` 只读队列 POC,与 Base UI 人工核对负责人、分页和 frontier。
3. 单独展示 record IDs 与开始 patch,经确认后验证 mapping、`进行中` 写入和回读。
4. 依次验证部分收口、最终收口和对账重放;每批真实写入仍单独确认。
5. V1 真实 POC 稳定后,再讨论是否抽取 helper,以及是否进入 workflow / hook 提醒集成。