# Matt 工作流 × 飞书 Skills × Trellis Coding 闭环总结
> 状态:六个 Feishu workflow skills 已落地;Coding 入队、Trellis 映射、Ticket 部分收口和 Spec 最终收口已通过真实 Base POC。
> 更新时间:2026-07-27
> 实际验证项目:`/Users/yuxuanhui/Documents/ai-workflow/project/经销商政策`
> 范围:工作进入、需求澄清、Spec、Tickets、Triage、Inline/Trellis Coding 路由、实现证据、部分收口、归档和最终对账。具体编码规范仍由目标项目自己的 Trellis specs 和 coding skills 决定。
## 1. 最终结论
这次工作补齐了 Matt 工作流在飞书上的实现侧闭环。现在不只是把需求和计划放进 Base/Wiki,还可以从 Base 选择当前用户负责的工作,路由到 Inline 或 Trellis,实现后按 Ticket 逐步验收,最后在严格门禁下关闭父 Spec。
当前完整模型是:
- **Feishu Base** 管业务状态、责任人、父子关系、依赖、进度和可查询队列。
- **Feishu Wiki / Docs** 管 Spec、Triage Notes、Agent Brief、Human Brief 等长文档。
- **Trellis** 管复杂开发的本地任务生命周期、规划产物、执行快照、checkpoint、归档和 journal。
- **repo** 管代码、测试、commit、ADR、领域知识和可回放的实现证据。
- **用户** 决定开始哪项工作,并对每张 Ticket 和最终 Spec 分别执行人类 review。
- **Agent** 负责查询、分析、路由、组织证据和提出精确 patch,但不能绕过人类 review 决定终态。
- **lark-cli bot** 执行 API;当前已验证人类用户写入 `最后更新人`;`负责人`始终表示工作责任,不被 bot 或操作归因覆盖。
```mermaid
flowchart LR
U["用户
选择、review、确认外部写入"] --> Skills["六个 Feishu workflow skills
流程编排与门禁"]
Skills --> Auth["lark-cli 身份门禁
bot + user verified"]
Auth -->|"--as bot"| Base["Feishu Base
状态、关系、队列、责任"]
Auth -->|"--as bot"| Wiki["Feishu Wiki / Docs
Spec 与长叙述"]
Skills --> Trellis["Trellis
复杂任务生命周期与本地上下文"]
Skills --> Repo["Repository
代码、测试、commit、ADR"]
Wiki -->|"产物文档"| Base
Base -->|"record IDs + 验收边界"| Trellis
Base -->|"Tickets / frontier"| Trellis
Trellis -->|"checkpoint / archive"| Skills
Repo -->|"验证证据 / 代码引用"| Skills
Skills -->|"最小 patch + 回读"| Base
```
## 2. 六个 Feishu workflow skills
六个 skills 都是全局 skill,source of truth 位于 `~/.agents/skills//`。它们都配置了 `policy.allow_implicit_invocation: false`,因此涉及真实飞书读写时要求显式调用,不靠模糊意图静默修改外部系统。
| Skill | 负责的阶段 | 主要输入 | 主要产物 | 明确不负责 |
|---|---|---|---|---|
| `setup-workflow-skills-feishu` | 仓库接入 | Base URL、Wiki 根 URL、Reuse/Bootstrap 选择 | repo tracker 合约;Bootstrap 时创建 schema、setup Wiki 文档和 POC 记录 | 不在 Reuse 模式修 schema;不猜资源地址 |
| `to-spec-feishu` | 对话 → 可执行规格 | 已澄清对话、代码库、领域词汇、测试 seam | 一份 Wiki Spec + 一条 Base `PRD/Spec` | 不采访式重新澄清;不在 Wiki 未回读前创建 Base Spec |
| `to-tickets-feishu` | Spec → 垂直切片 | 已批准 Spec、Ticket 粒度、依赖边 | 每个切片一条 Base `实现 Ticket` | V1 不为 Ticket 创建 Wiki;不关闭或修改父 Spec |
| `triage-feishu` | Issue/PR 分诊 | 外部请求、代码验证、维护者决定、报告人反馈 | Base Issue 状态 + 可选 Wiki Triage dossier | 不在推荐阶段写状态;不重复创建 dossier |
| `start-work-feishu` | Coding 入队 | 当前用户、Spec/Ticket 队列、用户选择、路由确认 | Inline 会话绑定,或 `1 Spec = 1 task` 的 Trellis mapping;Base `进行中`回写 | 不实现代码;不初始化 Trellis;不启动 sibling Tickets |
| `close-work-feishu` | Coding 收口 | Spec/Tickets、repo/Trellis 证据、人类 review、确认 patch | Ticket 部分收口、Spec 最终收口、失败后的幂等对账 | 不根据测试或 archive 自动完成;不替用户选择完成项 |
### 2.1 Skill 之间的调用关系
```mermaid
flowchart TD
Setup["setup-workflow-skills-feishu
建立项目级 tracker 合约"] --> Intake{"工作从哪里进入?"}
Intake -->|"已澄清想法 / 对话"| Spec["to-spec-feishu
Wiki Spec + Base Spec"]
Intake -->|"Issue / PR / 表单"| Triage["triage-feishu
分类、验证、澄清、Brief"]
Triage -->|"needs-info"| Feedback["等待报告人反馈"]
Feedback -->|"最后反馈时间 > 最后分诊时间"| Triage
Triage -->|"ready-for-agent 且需要正式规格"| Spec
Triage -->|"ready-for-agent 且已足够明确"| Start
Triage -->|"ready-for-human"| Human["人类处理"]
Triage -->|"wontfix / out-of-scope"| Stop["保留关闭原因与审计记录"]
Spec --> Tickets["to-tickets-feishu
Base Ticket 依赖图"]
Spec --> Start["start-work-feishu
选择并开始"]
Tickets --> Start
Start --> Coding["Inline 或 Trellis Coding"]
Coding --> Close["close-work-feishu
部分 / 最终 / 对账"]
Close -->|"仍有未完成 Ticket"| Coding
Close -->|"全部门禁满足"| Done["Base Spec 已完成
工作流闭环"]
```
## 3. 端到端工作流总图
下面这张图把需求侧、规划侧、实现侧和收口侧放在同一条链路中。
```mermaid
flowchart TD
A["仓库首次接入"] --> B["Setup:验证 Base/Wiki、生成 docs/agents 合约"]
B --> C{"请求入口"}
C -->|"外部 Issue / PR / 表单"| D["Triage:验证事实与分类"]
D --> D1{"维护者 outcome"}
D1 -->|"needs-info"| D2["Wiki Triage Notes
等待反馈"]
D2 --> D
D1 -->|"ready-for-human"| D3["Human Brief / 人类处理"]
D1 -->|"wontfix"| D4["关闭说明
必要时 repo .out-of-scope"]
D1 -->|"ready-for-agent"| E{"是否需要正式 Spec?"}
C -->|"已澄清对话"| F["to-spec:确认测试 seam"]
E -->|"是"| F
E -->|"否,范围足够小"| J
F --> G["Wiki Spec 写入并 fetch 回读"]
G --> H["Base Spec:ready-for-agent"]
H --> I{"是否需要拆 Ticket?"}
I -->|"是"| I1["to-tickets:垂直切片 + blocker 图"]
I -->|"否,简单 Spec"| J["start-work:工作队列"]
I1 --> J
J --> K["用户选择 record ID"]
K --> L["写前重读:负责人、状态、父项、依赖、更新时间"]
L --> M{"Inline 还是 Trellis?"}
M -->|"Inline"| N["冻结当前会话 record IDs"]
M -->|"Trellis"| O["查找/创建唯一 task
持久化 mapping 与 artifacts"]
N --> P["用户确认选择、路由和开始 patch"]
O --> P
P --> Q["Base 写进行中 + 实现 + 最后更新人"]
Q --> R["record-get 回读"]
R -->|"Trellis 新任务"| S["task.py start → in_progress"]
R -->|"Inline"| T["Coding 与验证"]
S --> T
T --> U["收集 diff、测试、浏览器验收、未运行项、代码引用"]
U --> V["close-work:逐 Ticket 验收映射"]
V --> W["人类 review 并选择可关闭 Ticket IDs"]
W --> X["Ticket 最小 patch + 逐条回读"]
X --> Y{"所有子 Tickets 已完成?"}
Y -->|"否"| T
Y -->|"是"| Z{"最终 Spec 门禁"}
Z -->|"Inline:Spec 证据 + 人类最终 review"| Z1["展示 Spec-only patch"]
Z -->|"Trellis:再加 archive + status=completed"| Z1
Z1 --> Z2["用户第二次确认 Spec patch"]
Z2 --> Z3["更新 Spec + record-get 回读"]
Z3 --> End["最终闭环完成"]
```
## 4. 四类事实源与产物关系
### 4.1 产物关系总图
```mermaid
flowchart LR
External["外部 Issue / PR / 表单"] -->|"来源链接 / 外部编号 / 报告人"| Issue["Base:需求/Issue"]
Issue -->|"产物文档"| TriageDoc["Wiki:Triage — 标题"]
Spec["Base:PRD/Spec"] -->|"所属父项"| Issue
Spec -->|"产物文档"| SpecDoc["Wiki:Spec — 标题"]
Ticket1["Base:实现 Ticket A"] -->|"所属父项"| Spec
Ticket2["Base:实现 Ticket B"] -->|"所属父项"| Spec
Ticket2 -->|"前置依赖"| Ticket1
Spec -->|"specRecordId,一对一"| Task["Trellis task"]
Ticket1 -.->|"ticketRecordIds / snapshot"| Task
Ticket2 -.->|"ticketRecordIds / snapshot"| Task
Task --> PRD["prd.md
Spec 来源与验收边界"]
Task --> Design["design.md
复杂任务技术设计"]
Task --> Impl["implement.md
Ticket frontier + checkpoint"]
Task --> JSONL["implement.jsonl / check.jsonl
必要 spec / research 清单"]
Task --> Archive["archive task.json
status=completed"]
Repo["repo:代码 / 测试 / ADR / commits"] -->|"验证证据 / 代码引用"| Ticket1
Repo -->|"验证证据 / 代码引用"| Ticket2
Archive -->|"最终生命周期门禁"| Spec
Ticket1 -->|"全部严格已完成"| Spec
Ticket2 -->|"全部严格已完成"| Spec
```
### 4.2 哪个系统保存什么
| 信息 | 事实来源 | 说明 |
|---|---|---|
| 当前业务状态、负责人、完成度、优先级、时间 | Base | 面向队列、筛选、自动化和团队可见性 |
| 父子层级和 blocker 边 | Base `所属父项`、`前置依赖` | 使用真实 record ID;用于计算 frontier |
| Spec 与 Triage 长叙述 | Wiki / Docs | 可读、可追加、可审计 |
| 复杂任务的本地生命周期 | Trellis | planning、in_progress、archive、journal |
| 当前复杂任务的执行快照 | Trellis `prd.md`、`implement.md` | 可从 Base/Wiki 刷新,不反向覆盖 Base 业务事实 |
| 代码、测试、commit、ADR、领域知识 | repo | 版本控制事实来源 |
| 完成证明 | Base `验证证据` + `代码引用` | 内容来自 repo、真实命令、浏览器验收和 Trellis checkpoint |
| 被拒绝 enhancement 的持久决定 | repo `.out-of-scope/` | Wiki 只记录 outcome/link,不复制第二份 canonical decision |
## 5. Setup、Spec、Tickets 与 Triage 的逻辑
### 5.1 Setup:建立可执行 tracker 合约
```mermaid
flowchart TD
A["读取仓库现状"] --> B["用户提供 Base URL + Wiki 根 URL"]
B --> C{"Setup 模式"}
C -->|"Reuse,推荐"| D["只读验证身份、资源和完整 schema"]
C -->|"Bootstrap"| E["展示缺失字段、Wiki setup 标题、POC 标题"]
D --> F{"29 字段兼容?"}
F -->|"否"| G["停止;询问切换 Bootstrap 或单独 repair 授权"]
F -->|"是"| H["展示 repo-local 合约草案"]
E --> I["用户确认外部写入"]
I --> J["只创建缺失字段"]
J --> K["创建 Wiki setup 文档并 fetch"]
K --> L["创建 POC Base 记录并 record-get"]
L --> H
H --> M["生成 AGENTS/CLAUDE Agent skills 区块"]
M --> N["生成 docs/agents/issue-tracker.md"]
N --> O["生成 domain / triage 合约"]
```
Setup 的关键产物是项目级 `docs/agents/issue-tracker.md`。后续所有 Feishu skills 必须先读它,从中取得真实 Base、table、view、主字段、Wiki root、字段取值、身份和完成门禁,不能从别的项目或相似标题推断资源。
### 5.2 to-spec:一份长规格 + 一条可查询记录
```mermaid
sequenceDiagram
participant U as 用户
participant S as to-spec-feishu
participant W as Wiki / Docs
participant B as Base
S->>S: 读取对话、代码库、领域词汇和 ADR
S-->>U: 提议最高可用测试 seam
U->>S: 确认 seam
S->>B: 按真实主字段精确查重
S->>W: 创建 Spec — 标题
S->>W: append 完整 Spec XML
S->>W: docs +fetch
alt Wiki 内容完整
S->>B: 创建 PRD/Spec 记录
S->>B: record-get 回读全部字段
else Wiki 写入或 fetch 不完整
S-->>U: 停止,不创建 Base Spec
end
```
Wiki Spec 包含 Problem Statement、Solution、User Stories、Implementation Decisions、Testing Decisions、Out of Scope 和 Further Notes。Base Spec 保存 `ready-for-agent`、Wiki 链接、摘要、验收标准和可选来源 Issue。
### 5.3 to-tickets:两遍发布依赖图
```mermaid
flowchart TD
Spec["已批准 Spec"] --> Slice["拆成单上下文可验证的 tracer-bullet"]
Slice --> Edge["声明真实 blocker"]
Edge --> Quiz["用户确认粒度、拆分、合并和依赖"]
Quiz -->|"调整"| Slice
Quiz -->|"批准"| Parent["解析父 Spec record ID"]
Parent --> Search["逐标题查重"]
Search --> Pass1["Pass 1:按依赖顺序创建 Ticket
暂不写 blocker"]
Pass1 --> IDs["冻结返回的 record IDs"]
IDs --> Pass2["Pass 2:写所属父项 + 前置依赖"]
Pass2 --> Verify["逐条回读父项、blocker 集、验收和最后更新人"]
Verify --> Frontier["frontier = 状态可开始
且每个 blocker 恰好为已完成"]
```
Ticket V1 不创建 Wiki 文档,`产物文档`留空。实现者通过 `所属父项`找到父 Spec,再从父 Spec 的 `产物文档`取得完整规格。
### 5.4 triage:请求进入工程流程的 on-ramp
```mermaid
stateDiagram-v2
[*] --> 待分诊
待分诊 --> NeedsTriage: 维护者开始评估
state "needs-triage" as NeedsTriage
state "needs-info" as NeedsInfo
state "ready-for-agent" as ReadyAgent
state "ready-for-human" as ReadyHuman
NeedsTriage --> NeedsInfo: 信息不足,写 Triage Notes
NeedsInfo --> NeedsTriage: 最后反馈时间晚于最后分诊时间
NeedsTriage --> ReadyAgent: 事实与验收足够,可委派
NeedsTriage --> ReadyHuman: 需要判断、权限或手工工作
NeedsTriage --> wontfix: 不实施
ReadyAgent --> 进行中: 被 start-work 认领
ReadyHuman --> 进行中: 人类认领
wontfix --> [*]
```
Triage 的推荐与验证阶段只读。维护者确认 outcome 后,才写唯一 `类别`、唯一 canonical `状态`、`最后分诊时间`、摘要、证据、下一步和 `最后更新人`。需要长叙述时,一个 Issue 只创建并持续 append 一份 `Triage — <标题>` Wiki dossier。
## 6. `start-work-feishu`:Coding 入队与路由
### 6.1 队列口径
Skill 会冻结当前已验证用户的 open ID,然后分别完整分页查询:
- Spec:`产物类型=PRD/Spec`,且 **Spec 自身** `负责人=当前用户`。
- Ticket:`产物类型=实现 Ticket`,且 **Ticket 自身** `负责人=当前用户`。
子 Ticket 归当前用户不会让父 Spec 自动进入 Spec 队列。父 Spec 只作为 Ticket 的上下文展示。
| 分组 | 条件 | 是否可选择 |
|---|---|---|
| 可恢复 | `进行中`或`阻塞` | 可以,但阻塞项必须展示原因,不暗示已解除 |
| 可开始 | `ready-for-agent`;Ticket 无 blocker 或所有 blocker 都恰好为`已完成` | 可以 |
| 待收口 | `待评审` | 不重新开始,转 `close-work-feishu` |
| 被阻塞 | `ready-for-agent`,但任一 blocker 不是`已完成` | 不可开始 |
| 排除 | 草拟、triage、人类专属和终态记录 | 不进入 coding queue |
`wontfix`、`out-of-scope`、`已取代`虽然是终态,但不能自动证明依赖要求已交付,因此不算满足 blocker。
### 6.2 Start skill 内部逻辑
```mermaid
flowchart TD
A["显式调用 start-work-feishu"] --> B["读取项目 tracker 合约与 Trellis workflow"]
B --> C["验证 bot + user,冻结当前 user open ID"]
C --> D["field-list 验证 29 字段和枚举"]
D --> E["完整分页查询 Spec 队列"]
E --> F["完整分页查询 Ticket 队列"]
F --> G["按 record ID 读取父项与 blocker"]
G --> H["计算可恢复 / 可开始 / 待收口 / 被阻塞"]
H --> I["用户选择 number 或 record ID"]
I --> J["重读选中项、父 Spec、全部子 Tickets、blockers"]
J --> K{"负责人、状态、关系或更新时间漂移?"}
K -->|"是"| H
K -->|"否"| L{"建议路由"}
L -->|"小、明确、单上下文"| M["Inline:冻结本会话 IDs 与快照"]
L -->|"跨模块、多会话、长验收链"| N["Trellis:扫描现有 mapping"]
N --> O{"匹配 task 数量"}
O -->|"多个"| Stop["停止,报告冲突"]
O -->|"一个"| Resume["恢复已有 task"]
O -->|"零个"| Create["task.py create --no-start"]
Resume --> Persist["刷新 prd / implement / mapping"]
Create --> Persist
Persist --> ReadMap["回读完整 meta.feishuTracker"]
M --> Confirm["一次确认:工作项 + 路由 + Base patch"]
ReadMap --> Confirm
Confirm --> Patch["按 record ID 写进行中 / 实现 / 最后更新人"]
Patch --> ReadBase["逐条 record-get 回读"]
ReadBase --> Route{"路由"}
Route -->|"Inline"| Coding["交给当前会话实现"]
Route -->|"新建或 planning task"| Start["task.py start"]
Start --> VerifyTask["回读 task.json status=in_progress"]
```
### 6.3 Inline 与 Trellis 的判断
| 路由 | 适用情况 | 本地持久化 |
|---|---|---|
| Inline | 范围窄、方案已知、一个健康上下文可完成、最低验证可在当前会话完成 | 只在当前对话冻结 record IDs,不创建 mapping 文件 |
| Trellis | 跨模块、多阶段、多个稳定决策、durable research、多会话或验收链较长 | 创建或恢复唯一 task,持久化 Spec mapping、规划产物和 Ticket snapshot |
这里的 Inline 是“完全不创建 Trellis task”。它和 Trellis 内部 Codex `dispatch_mode=inline`不是同一概念;后者仍然已经处在 Trellis task 内。
### 6.4 `1 Spec = 1 Trellis task` mapping
```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": "ISO-8601",
"lastRefreshAt": "ISO-8601"
}
}
}
```
- `specRecordId` 是一对一绑定的稳定键。
- `ticketRecordIds` 是刷新时全部子 Tickets 的排序去重快照,不是 Base 的替代事实源。
- `selectedRecordIds` 是这次用户直接选择的 Spec 或 Ticket。
- `boundAt` 首次绑定后不变;`lastRefreshAt` 随成功刷新更新。
- mapping 不保存 Base token、凭证或固定个人 open ID。
- 只更新 `meta.feishuTracker`,保留 `task.json`其他字段和未知未来键。
### 6.5 开始 patch
```json
{
"状态": "进行中",
"工作流阶段": "实现",
"最后更新人": [{"id": ""}]
}
```
- 选择 Spec:只写 Spec。
- 选择 Ticket:写选中的 Ticket 和它的父 Spec。
- 不修改`负责人`、`完成度`、sibling Tickets、证据、blocker 或下一步。
- Trellis 新任务只有在 mapping 持久化成功、Base 写入成功且回读一致后才执行 `task.py start`。
## 7. Trellis Coding 生命周期
```mermaid
stateDiagram-v2
[*] --> planning: task.py create --no-start
planning --> in_progress: task.py start
in_progress --> in_progress: implement / check / update-spec / commit
in_progress --> Detached: task.py finish
state "session detached\n任务未完成" as Detached
Detached --> in_progress: 其他 session 可继续
in_progress --> completed: task.py archive
completed --> Archived: 移入 archive tree
state "archive/task.json\nstatus=completed" as Archived
```
关键边界:
- `task.py finish`只清除当前 session 的任务指针,不能作为 Base 完成信号。
- 复杂任务通常经历 `implement → check → update-spec → 工作 commit → finish-work/archive → journal`。
- `after_archive`最多作为提醒或待对账信号;显式 `close-work-feishu`才有资格在回读后声称 Base 已闭环。
- Ticket 部分收口不要求 archive;父 Spec 最终收口才要求归档 task 且`status=completed`。
## 8. `close-work-feishu`:部分收口、最终收口与对账
### 8.1 三个内部 routing
| Routing | 进入条件 | 结果 |
|---|---|---|
| 部分收口 | 有一个或多个 Ticket 已有直接证据,并完成对应人类 review | 只关闭用户选中的 Ticket;Spec 保持`进行中` |
| 最终收口 | 全部 Ticket 已完成,Spec 验收有证据,人类完成最终 review;Trellis 路径还必须 archive | 用户再次确认 Spec-only patch 后关闭 Spec |
| 对账重放 | 上次写入部分成功、回读失败或记录已在目标终态 | 只补差异;证据完整时返回`already synchronized`,不重复追加 |
### 8.2 Close skill 完整逻辑
```mermaid
flowchart TD
A["显式调用 close-work-feishu"] --> B["用 active/archived mapping 或 Inline 会话定位 Spec ID"]
B --> C["验证身份、schema、Spec 类型"]
C --> D["按父 Spec 完整分页查询全部子 Tickets"]
D --> E["比较 live Ticket 集与 Trellis snapshot"]
E --> F["汇总验收标准、repo diff、测试、浏览器验收、未运行项、代码引用、checkpoint"]
F --> G["逐 Ticket 映射验收证据"]
G --> H{"分类"}
H --> H1["建议可收口"]
H --> H2["待补证 / 待验证"]
H --> H3["明确未完成 / 阻塞"]
H1 --> I["展示精确 Ticket patch"]
H2 --> Wait["保持原状态,说明最小下一步"]
H3 --> Wait
I --> J["用户声明已完成人类 review
并选择允许关闭的 record IDs"]
J --> K["写前逐条重读,检查 owner、状态、父项、依赖、验收、证据、更新时间"]
K --> L{"是否漂移?"}
L -->|"是"| I
L -->|"否"| M["批量最小 patch,最多 200 条且串行"]
M --> N["逐条 record-get 回读"]
N --> O{"失败 / ignored / mismatch?"}
O -->|"是"| Reconcile["输出成功、失败、待对账清单
禁止关闭 Spec"]
O -->|"否"| P["重新完整查询所有子 Tickets"]
P --> Q{"是否全部严格为已完成?"}
Q -->|"否"| Partial["部分收口完成
Spec 保持进行中"]
Q -->|"是"| R{"Spec 级最终门禁"}
R -->|"未满足"| Hold["Spec 不变,报告缺失门禁"]
R -->|"满足"| S["展示独立 Spec-only patch"]
S --> T["用户第二次确认"]
T --> U["立即重读 Spec;漂移则确认失效"]
U --> V["写 Spec + record-get 回读"]
V --> Done["最终收口回执"]
```
### 8.3 Ticket 完成 patch
```json
{
"状态": "已完成",
"工作流阶段": "交付",
"完成度": 1,
"验证证据": "<保留旧内容并追加一次 Ticket 专属 closure section>",
"代码引用": "<保留并去重的真实引用>",
"阻塞原因": null,
"下一步": null,
"最后更新人": [{"id": ""}]
}
```
每个 closure section 至少记录:人类 review、验收到证据的映射、真实验证命令和结果、明确未运行项及原因。不能用 Agent review、测试通过、Trellis `finish`或 archive 单独替代人类 review。
### 8.4 Spec 最终门禁
```mermaid
flowchart LR
A["全部子 Tickets
状态恰好为已完成"] --> Gate{"Spec 最终门禁"}
B["Spec 每条验收标准
都有直接证据"] --> Gate
C["用户完成 Spec
最终代码/功能 review"] --> Gate
D["Trellis 路径:task 位于 archive
且 status=completed"] --> Gate
E["不存在 Ticket write/read-back 失败"] --> Gate
Gate -->|"全部满足"| Patch["展示 Spec-only patch"]
Patch --> Confirm["用户单独确认"]
Confirm --> Done["写入 + 回读后才算闭环"]
```
`wontfix`、`out-of-scope`、`已取代`不自动算作“全部 Ticket 已完成”。无 Ticket 的简单 Spec 可以跳过 Ticket 门禁,但仍需 Spec 验收证据、人类最终 review,以及适用的 Trellis archive 门禁。
## 9. 身份、确认与写后回读协议
```mermaid
sequenceDiagram
participant U as 用户
participant S as Feishu skill
participant A as lark-cli auth
participant B as Feishu Base
U->>S: 显式调用 skill
S->>A: auth status --json --verify
A-->>S: bot verified + user verified + user.openId
S->>S: 冻结 CURRENT_USER_OPEN_ID
S->>B: --as bot 读取 schema / records
B-->>S: 当前快照
S-->>U: 展示稳定 record IDs 与精确 patch
U->>S: 明确确认外部写入
S->>B: 写前按 record ID 重读
B-->>S: 无漂移
S->>B: --as bot 最小 patch
最后更新人=user.openId
B-->>S: ok:true
S->>B: record-get / 完整分页回读
B-->>S: 字段与关系符合预期
S-->>U: 完成回执
```
统一规则:
1. 每条 Base read/write 都使用 `--as bot --format json`,失败后不降级为 user。
2. `负责人`是 assignee;`最后更新人`是最近一次通过 CLI 推进记录的真实人类;bot 只是 API caller。
3. 标题是展示文本,record ID 才是更新键。
4. 写前展示目标记录和精确 patch;写后必须回读。
5. `ok:true`不证明 record 存在或字段已生效;`ignored_fields`和 read-back mismatch 都算失败。
6. schema、视图和只读操作没有行级归因目标,不写`最后更新人`。
## 10. Base 主状态机与 Trellis 映射
### 10.1 Base 主状态机
```mermaid
stateDiagram-v2
[*] --> 草拟中
草拟中 --> 待确认
待确认 --> ReadyAgent: 可由 Agent 执行
待确认 --> ReadyHuman: 需要人类执行
state "ready-for-agent" as ReadyAgent
state "ready-for-human" as ReadyHuman
ReadyAgent --> 进行中: start-work 确认并回写
ReadyHuman --> 进行中: 人类认领
进行中 --> 阻塞: 发生 blocker
阻塞 --> 进行中: blocker 解除
进行中 --> 待评审: 进入 review
待评审 --> 进行中: review 要求修改
待评审 --> 已完成: 人类 review + 直接证据
草拟中 --> 已取代
待确认 --> 已取代
ReadyAgent --> 已取代
进行中 --> 已取代
ReadyAgent --> OutOfScope: 范围外
state "out-of-scope" as OutOfScope
已完成 --> [*]
已取代 --> [*]
OutOfScope --> [*]
```
### 10.2 Trellis 事件到 Base 的业务含义
| Trellis / 本地事件 | Base 动作 | 原因 |
|---|---|---|
| 只读列队、用户未选择 | 不写 | 浏览队列不是认领 |
| mapping 保存成功,用户确认“选择并开始” | Spec 或 Ticket/父 Spec → `进行中`、`实现` | 表示进入规划/执行,不猜完成度 |
| `planning` / `after_start` | 通常不重复写 | Start skill 已完成开始回写 |
| 发生外部 blocker | `阻塞`,并填写`阻塞原因`和`下一步` | 状态与解释必须一致 |
| Agent 建议可收口但无人类 review | 保持原状态 | Agent 只能推荐 |
| 部分收口 | 仅用户选中的 Tickets → `已完成` | 父 Spec 保持`进行中` |
| `task.py finish` / `after_finish` | 不写完成态 | 只代表 session detach |
| `archive` / `after_archive` | 不自动写完成态 | 只满足 Spec 生命周期门禁之一 |
| 全 Ticket 完成 + Spec 验收 + 人类最终 review + archive | Spec → `已完成`、`交付`、`完成度=1` | 最终聚合收口 |
| Base 同步失败 | 保留真实现状并输出待对账 | 代码/Trellis 完成与外部同步分开报告 |
## 11. Base 完整字段字典(29 个)
以下字段来自“经销商政策”项目配置的真实 Base `field-list`回读。`文本`是当前表的主字段。
| # | 字段 | Field ID | 类型 / 当前取值 | 含义与规则 |
|---:|---|---|---|---|
| 1 | 文本 | `fldzLHLTca` | text,主字段 | 记录的业务标题。标题可用于创建前精确查重,但更新必须使用 record ID。 |
| 2 | 产物类型 | `fldlwQ46Ks` | single select:需求/Issue、PRD/Spec、实现 Ticket、Wayfinder Map、决策 Ticket、原型、研究、Handoff、ADR、领域词汇、Bug 诊断、代码评审、架构候选、教学资产 | 说明该记录代表哪类工作流产物。 |
| 3 | 来源技能 | `fldUMyVtoS` | multi-select:setup-matt-pocock-skills、setup-workflow-skills-feishu、grill-with-docs、grill-me、triage、diagnosing-bugs、wayfinder、to-spec、to-tickets、implement、tdd、code-review、improve-codebase-architecture、domain-modeling、prototype、research、handoff、teach | 记录创建或推进产物的逻辑流程。业务记录通常写逻辑 skill 名;当前表也保留 setup 的 Feishu 适配器选项。 |
| 4 | 工作流阶段 | `fldjAkNPqD` | single select:探索/澄清、决策、规格、拆票/规划、实现、验证/评审、交付、维护/学习、分诊 | 表示产物处于端到端流程哪一阶段;与`状态`是不同维度。 |
| 5 | 状态 | `fldHjFrlwJ` | single select:草拟中、待确认、待分诊、needs-triage、needs-info、ready-for-agent、ready-for-human、进行中、阻塞、待评审、已完成、wontfix、out-of-scope、已取代 | 统一业务状态机。英文值保留 Matt triage canonical role。 |
| 6 | 类别 | `fldQWuceDX` | single select:bug、enhancement | Triage 分类;每个已分诊项必须恰好一个。 |
| 7 | 决策票类型 | `fldFpkeDkj` | single select:research、prototype、grilling、task | Wayfinder 子决策票的解决方式;非决策票通常留空。 |
| 8 | 协作模式 | `fldVwgWNRU` | single select:HITL、AFK | HITL 需要人类实时参与;AFK 表示 Agent 可以独立推进到下一个门禁。 |
| 9 | 优先级 | `fldJv2ts2l` | single select:P0、P1、P2、P3 | 业务/执行优先级;不替代 blocker frontier。 |
| 10 | 负责人 | `fldGlPa1B1` | user,单值 | 当前直接执行责任人。必须是真实飞书用户;Start/Close skills 不覆盖它。 |
| 11 | 最后更新人 | `fld6nvDu18` | user,单值 | 最近一次通过 lark-cli 创建或更新记录的真实人类用户;不是 bot,也不是飞书系统更新时间。 |
| 12 | 完成度 | `fldqNJjr0g` | number/progress,0–1 | 业务进度。Base 显示为百分比;最终完成写 JSON number `1`。 |
| 13 | 产物文档 | `fldorpkrbf` | text/plain | canonical Wiki 或长文档链接。Spec/Triage 使用;V1 Ticket 留空并沿父 Spec 取文档。 |
| 14 | 验收标准 | `fldwbSY2pq` | text | 可验证的完成条件。实现 Ticket 必填;Spec 保存聚合验收边界。 |
| 15 | 验证证据 | `fldIQ0NQPO` | text | 真实命令、测试结果、浏览器验收、review、read-back 事实和未运行原因。没有具体证据不能完成。 |
| 16 | 结论/摘要 | `fldmdDUJHh` | text | Problem/Solution 摘要、决策结论、诊断根因、研究摘要或交付结果;长分析放 Wiki。 |
| 17 | 阻塞原因 | `fldUsFDsTS` | text | 仅`阻塞`或`needs-info`等无法继续的状态使用;说明为什么不能推进。 |
| 18 | 下一步 | `fldMY6XATm` | text | 解除阻塞或进入下一阶段的最小动作;完成态清空。 |
| 19 | 代码引用 | `fldNylIQ5y` | text | 分支、commit、PR、文件:行号、Trellis checkpoint、review fixed point 或`.out-of-scope/`路径。只写真实存在的引用。 |
| 20 | 截止时间 | `fldZP6Cm30` | datetime,`yyyy-MM-dd HH:mm` | 人工承诺的截止时间,不由 Agent 自动推算。 |
| 21 | 创建时间 | `fldnPF52hP` | created_at,只读 | Base 自动记录的创建时间;Triage 关注队列按它从旧到新排序。 |
| 22 | 更新时间 | `fldd3rTk0I` | updated_at,只读 | Base 自动记录的行更新时间;用于竞态检测,不等于`最后更新人`。 |
| 23 | 所属父项 | `flduS0iiPy` | self-link,双向 | Spec 指向来源 Issue;Ticket 指向父 Spec;决策 Ticket 指向 Wayfinder Map。写真实 record ID。 |
| 24 | 前置依赖 | `fldYkwZVu1` | self-link,双向 | 指向阻塞当前项的记录。只有所有 blocker 都恰好为`已完成`时 Ticket 才进入 frontier。 |
| 25 | 来源链接 | `fldp4F1S6m` | text/url | 原始 Issue、PR、表单或外部系统 URL。 |
| 26 | 外部编号 | `fldpv8O5OW` | text | 外部系统编号。它不是 Base record ID,不能用作更新键或 link CellValue。 |
| 27 | 报告人 | `fld37AxlZf` | text | 报告人的显示名或外部标识;兼容报告人不是飞书用户。 |
| 28 | 最后反馈时间 | `fldTwNj5Uz` | datetime,`yyyy-MM-dd HH:mm` | 报告人或外部来源最近一次新增反馈时间。只更新时间,不自动改状态。 |
| 29 | 最后分诊时间 | `fldNy9eAgM` | datetime,`yyyy-MM-dd HH:mm` | 维护者最近一次确认并写回分诊结果的时间。`最后反馈时间 > 最后分诊时间`时,needs-info 重新进入关注队列。 |
### 11.1 Link 字段边界
`所属父项`和`前置依赖`的正向字段是自动化 source of truth。`lark-cli 1.0.76`可能返回同表双向关系的 reverse field ID,但 reverse 字段不一定独立出现在`field-list`,因此当前实现不依赖反向字段寻址。
## 12. 真实 POC:经销商政策项目
### 12.1 POC 对象
| 产物 | Record ID / 路径 | 最终状态 |
|---|---|---|
| Spec:历史报告多条件检索 | `recvquNpDDOafu` | `已完成 / 交付 / 1` |
| Ticket A:按事业部和分析类型筛选历史报告 | `recvquNtBMcQ2V` | `已完成 / 交付 / 1` |
| Ticket B:按关键词搜索并完善筛选反馈 | `recvquNxjmWwth` | `已完成 / 交付 / 1` |
| Trellis task | `.trellis/tasks/archive/2026-07/07-27-feishu-recvqunpddoafu` | archive 中,`task.json.status=completed` |
| 工作 commit | `e7270cf` | `feat: 支持历史报告多条件检索` |
| 规范 commit | `1f00f51` | `docs: 沉淀报告检索状态同步契约` |
| 归档 commit | `30b9c8f` | `chore(task): archive 07-27-feishu-recvqunpddoafu` |
| journal commit | `3e91199` | `chore: record journal` |
### 12.2 真实执行时序
```mermaid
sequenceDiagram
participant U as 于选辉
participant S as start-work-feishu
participant B as Feishu Base
participant T as Trellis
participant C as Coding / Check
participant X as close-work-feishu
U->>S: 选择历史报告多条件检索工作
S->>B: 查询 Spec、Tickets、父项和 blocker
S->>T: 创建并保存 1 Spec = 1 task mapping
S->>B: 写 Spec/Ticket 进行中并回读
S->>T: task.py start
T->>C: 实现 Ticket A
C-->>U: 代码、测试和构建证据
U->>X: 已完成 Ticket A 人类 review
X->>B: 关闭 recvquNtBMcQ2V 并回读
B-->>X: Ticket A 已完成,Ticket B blocker 解锁
T->>C: 实现 Ticket B
C-->>U: API、前端、构建、真实浏览器与竞态证据
U->>X: 已完成 Ticket B 人类 review
X->>B: 关闭 recvquNxjmWwth 并回读
B-->>X: 两张 Ticket 全部已完成
C->>T: update-spec + commits
U->>T: 授权 finish-work
T->>T: archive task + journal
U->>X: 已完成 Spec 最终 review
X->>B: 重新查询全部 Tickets 与 Spec
X-->>U: 展示 Spec-only 精确 patch
U->>X: 第二次确认最终 patch
X->>B: 更新 Spec 已完成并 record-get
B-->>X: 已完成 / 交付 / 1,负责人和验收保持不变
```
### 12.3 实现与验证结果
本次 Spec 实现了:事业部、分析类型、标题/文件名关键词组合检索;条件使用 AND 语义;关键词去除首尾空白并对拉丁字符大小写不敏感;无筛选兼容旧行为;筛选后详情与列表保持一致;区分空仓库和筛选无结果;支持一键清空;旧请求不能覆盖新筛选结果。
真实验证证据:
- `pytest -q tests/test_api.py -k reports`:`9 passed, 11 deselected`。
- 可执行后端回归:`37 passed, 1 deselected`。
- 前端 `npm test`:`4 passed`。
- 前端 `npm run build`:通过,仅保留既有 large-chunk advisory。
- Trellis task validation:通过。
- `git diff --check`:通过。
- 真实浏览器验收:文件名关键词、大小写不敏感三条件组合、当前条件/结果数反馈、筛选无结果、详情清空与恢复、一键清空全部通过。
- 受控请求竞态:较早的延迟响应未覆盖较新的筛选结果。
- 浏览器 console 仅出现与本功能无关的 `favicon.ico` 404。
- 完整 pytest 未运行成功的原因被明确保留:既有缺失模块/import collection errors,以及本次 diff 外的`policy_data` 404;没有把未运行项伪报为通过。
### 12.4 POC 证明了什么
```mermaid
flowchart LR
A["当前用户负责人过滤"] --> Proof["真实 POC 通过"]
B["Ticket blocker frontier"] --> Proof
C["稳定 record ID 与 Trellis mapping"] --> Proof
D["1 Spec = 1 task"] --> Proof
E["Ticket 逐张人类 review"] --> Proof
F["部分收口不关闭 Spec"] --> Proof
G["archive 不是自动完成授权"] --> Proof
H["Spec 第二次确认"] --> Proof
I["写前重读 + 写后回读"] --> Proof
J["最后更新人归因"] --> Proof
K["未运行项显式记录"] --> Proof
```
本次 demo POC 仅对“planning artifacts 必须经人类 review 后才能 `task.py start`”放宽了一次;Ticket review、Spec 最终 review、外部写入确认和回读门禁均未放宽。最终 Spec 的 Base 回读时间为 `2026-07-27 10:04:12`,`负责人`和`最后更新人`均为于选辉,验收标准和关联字段保持不变。未执行 push。
## 13. 失败处理与幂等对账
```mermaid
flowchart TD
A["任一步失败"] --> B{"失败位置"}
B -->|"身份 / schema / 查询"| C["不做任何本地或外部写入"]
B -->|"选择后发生漂移"| D["旧确认失效,重新展示队列或 patch"]
B -->|"task / artifact / mapping"| E["不写 Base,保留明确失败原因"]
B -->|"Base 开始写失败"| F["task 保持 planning
报告 mapping 已保存但未同步"]
B -->|"Base 成功,Trellis start 失败"| G["Base 已认领
Trellis 待启动,不自动回滚"]
B -->|"Ticket batch 部分失败"| H["保留逐条成功/失败
禁止关闭 Spec"]
B -->|"write ok 但回读 mismatch"| I["标记 mismatched,不宣称完成"]
B -->|"记录已完整同步"| J["already synchronized
不重复追加证据"]
H --> K["下次对账只补仍有差异的记录"]
I --> K
```
核心原则:外部系统的部分成功不能被一个总体`ok:true`遮蔽;也不能因为 Base 同步失败就篡改已经完成的代码/Trellis事实。回执必须把“代码完成”“Trellis lifecycle 完成”“Base 同步完成”分开报告。
## 14. 当前边界与后续演进
V1 当前边界:
- 两个 Coding 闭环 skills 已实现并通过真实 POC。
- 直接编排项目内`.trellis/scripts/task.py`和`lark-cli`,没有新增 helper CLI。
- 没有修改 Trellis workflow、hooks 或 Base schema。
- 不自动 commit、push 或创建 PR;Git 写操作继续遵守用户授权边界。
- 不把 coding sub-agent 设为 Base 状态决策者;外部选择、review、确认和回读都留在主会话。
建议的后续顺序:
1. 在第二个真实项目重放一次 Inline 路径,验证跨会话时按 record ID 重选的体验。
2. 演练一次故意的 Base batch 部分失败,验证对账 routing 的逐条补偿。
3. 演练一次 mapping 冲突和记录竞态,验证确认失效路径。
4. 稳定后再考虑抽取共享分页/frontier/helper;在两个 skills 尚未形成稳定重复前不提前封装。
5. 如果接入 lifecycle hook,`after_archive`只提醒或生成不含凭证的 pending 标记,不直接写 Base 完成态。
## 15. 一句话心智模型
```mermaid
flowchart LR
Issue["Issue
要解决什么"] --> Spec["Spec
为什么做、验收边界"]
Spec --> Tickets["Tickets
可独立交付的垂直切片与依赖"]
Tickets --> Start["Start
选择、路由、认领"]
Start --> Coding["Coding
Inline 或 1 Spec = 1 Trellis task"]
Coding --> Evidence["Evidence
代码、测试、浏览器、review"]
Evidence --> Close["Close
Ticket 部分收口 → Spec 最终收口"]
Close --> Done["Done
写后回读的 Base 闭环"]
```
**Base 回答“现在是什么状态、由谁负责、依赖谁”;Wiki 回答“为什么做、具体要求是什么”;Trellis 回答“复杂工作如何规划、执行和归档”;repo 回答“到底实现了什么、验证过什么”;六个 Feishu skills 负责把这些事实源连接成一条可确认、可回读、可重放的工作流。**
## 16. 相关资料
- [Matt 工作流 × 飞书 CLI 全流程总结](<./Matt 工作流 × 飞书 CLI 全流程总结.md>)
- [Trellis × 飞书实现闭环初步方案](<./Trellis × 飞书实现闭环初步方案.md>)
- 经销商政策项目 tracker 合约:`/Users/yuxuanhui/Documents/ai-workflow/project/经销商政策/docs/agents/issue-tracker.md`
- 真实 POC 归档任务:`/Users/yuxuanhui/Documents/ai-workflow/project/经销商政策/.trellis/tasks/archive/2026-07/07-27-feishu-recvqunpddoafu`
- Trellis 官方文档:[架构](https://docs.trytrellis.app/zh/advanced/architecture)、[自定义 Workflow](https://docs.trytrellis.app/zh/advanced/custom-workflow)、[自定义 Skills](https://docs.trytrellis.app/zh/advanced/custom-skills)、[自定义 Agents](https://docs.trytrellis.app/zh/advanced/custom-agents)、[配置](https://docs.trytrellis.app/zh/advanced/configuration)