Files
obsidian-vault/output/研究文章/Matt 工作流 × 飞书 Skills × Trellis Coding 闭环总结.md
2026-08-31 09:21:12 +08:00

760 lines
40 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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["用户<br/>选择、review、确认外部写入"] --> Skills["六个 Feishu workflow skills<br/>流程编排与门禁"]
Skills --> Auth["lark-cli 身份门禁<br/>bot + user verified"]
Auth -->|"--as bot"| Base["Feishu Base<br/>状态、关系、队列、责任"]
Auth -->|"--as bot"| Wiki["Feishu Wiki / Docs<br/>Spec 与长叙述"]
Skills --> Trellis["Trellis<br/>复杂任务生命周期与本地上下文"]
Skills --> Repo["Repository<br/>代码、测试、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/<skill-name>/`。它们都配置了 `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<br/>建立项目级 tracker 合约"] --> Intake{"工作从哪里进入?"}
Intake -->|"已澄清想法 / 对话"| Spec["to-spec-feishu<br/>Wiki Spec + Base Spec"]
Intake -->|"Issue / PR / 表单"| Triage["triage-feishu<br/>分类、验证、澄清、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<br/>Base Ticket 依赖图"]
Spec --> Start["start-work-feishu<br/>选择并开始"]
Tickets --> Start
Start --> Coding["Inline 或 Trellis Coding"]
Coding --> Close["close-work-feishu<br/>部分 / 最终 / 对账"]
Close -->|"仍有未完成 Ticket"| Coding
Close -->|"全部门禁满足"| Done["Base Spec 已完成<br/>工作流闭环"]
```
## 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<br/>等待反馈"]
D2 --> D
D1 -->|"ready-for-human"| D3["Human Brief / 人类处理"]
D1 -->|"wontfix"| D4["关闭说明<br/>必要时 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<br/>持久化 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<br/>Spec 来源与验收边界"]
Task --> Design["design.md<br/>复杂任务技术设计"]
Task --> Impl["implement.md<br/>Ticket frontier + checkpoint"]
Task --> JSONL["implement.jsonl / check.jsonl<br/>必要 spec / research 清单"]
Task --> Archive["archive task.json<br/>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<br/>暂不写 blocker"]
Pass1 --> IDs["冻结返回的 record IDs"]
IDs --> Pass2["Pass 2:写所属父项 + 前置依赖"]
Pass2 --> Verify["逐条回读父项、blocker 集、验收和最后更新人"]
Verify --> Frontier["frontier = 状态可开始<br/>且每个 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": "<CURRENT_USER_OPEN_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<br/>并选择允许关闭的 record IDs"]
J --> K["写前逐条重读,检查 owner、状态、父项、依赖、验收、证据、更新时间"]
K --> L{"是否漂移?"}
L -->|"是"| I
L -->|"否"| M["批量最小 patch,最多 200 条且串行"]
M --> N["逐条 record-get 回读"]
N --> O{"失败 / ignored / mismatch?"}
O -->|"是"| Reconcile["输出成功、失败、待对账清单<br/>禁止关闭 Spec"]
O -->|"否"| P["重新完整查询所有子 Tickets"]
P --> Q{"是否全部严格为已完成?"}
Q -->|"否"| Partial["部分收口完成<br/>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": "<CURRENT_USER_OPEN_ID>"}]
}
```
每个 closure section 至少记录:人类 review、验收到证据的映射、真实验证命令和结果、明确未运行项及原因。不能用 Agent review、测试通过、Trellis `finish`或 archive 单独替代人类 review。
### 8.4 Spec 最终门禁
```mermaid
flowchart LR
A["全部子 Tickets<br/>状态恰好为已完成"] --> Gate{"Spec 最终门禁"}
B["Spec 每条验收标准<br/>都有直接证据"] --> Gate
C["用户完成 Spec<br/>最终代码/功能 review"] --> Gate
D["Trellis 路径:task 位于 archive<br/>且 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<br/>最后更新人=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<br/>报告 mapping 已保存但未同步"]
B -->|"Base 成功,Trellis start 失败"| G["Base 已认领<br/>Trellis 待启动,不自动回滚"]
B -->|"Ticket batch 部分失败"| H["保留逐条成功/失败<br/>禁止关闭 Spec"]
B -->|"write ok 但回读 mismatch"| I["标记 mismatched,不宣称完成"]
B -->|"记录已完整同步"| J["already synchronized<br/>不重复追加证据"]
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<br/>要解决什么"] --> Spec["Spec<br/>为什么做、验收边界"]
Spec --> Tickets["Tickets<br/>可独立交付的垂直切片与依赖"]
Tickets --> Start["Start<br/>选择、路由、认领"]
Start --> Coding["Coding<br/>Inline 或 1 Spec = 1 Trellis task"]
Coding --> Evidence["Evidence<br/>代码、测试、浏览器、review"]
Evidence --> Close["Close<br/>Ticket 部分收口 → Spec 最终收口"]
Close --> Done["Done<br/>写后回读的 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)