接一个 feishu_docs 来源:同步一个飞书云文档空间,每篇文档连同它的版本序列
成为知识库里的文档。
判断依据与前两个工单来源共用一篇决策记录:
docs/decisions/0012 。
动手前先读它——核心判断是「别取现在,取变化」 ,只抓当前版本的话这个来源
就退化成了又一个抓网页的。
已有两个可照抄的实现
文件
形状
GitHub 工单
crates/utopia-server/src/github_issues.rs
三次拉取 + 逐工单事件
Jira 工单
crates/utopia-server/src/jira_issues.rs
一次调用取全(expand=changelog)
两边都是同一个骨架,照着走即可:
纯函数 render() 把一条记录排成 Markdown——不联网,于是测得动
fetch_all() 负责分页与鉴权
在 ingest_sources.rs 里加一个 sync_* 分支,调 ingest_item()
(身份、sha256 去重、版本记录都由它负责,不用自己写)
sources::KINDS 白名单 + 前端三处(api.ts 的 SourceView["kind"]、
Library.tsx 的建来源对话框、SourcesRail.tsx 的图标与 SYNCING_KINDS)
第 4 步容易漏:漏了的症状是界面上选得到、建的时候报 kind must be one of… ,
而单元测试与 tsc 都看不见。只有端到端会撞上。
三条硬要求
一、正文要写成带日期的陈述句,不是键值对。 Opened by X on 2026-08-18 能抽出带
valid_from 的事实;created_at: 2026-08-18 则要模型自己猜这是什么意思。
二、截断要说出来。 分页有上限时,取回数小于服务端 total 就落一条 warn。
不报的话界面上「同步完成」是一句误导(Jira 那边是 500 / 14506)。
三、拿真实响应做夹具。 手写 JSON 只能证明「我以为的形状」。前两个来源的夹具
分别取自 deeplethe/utopia 与 issues.apache.org(都匿名可读),字段裁到声明的那些。
它当场抓到过一个手写测试全绿、真实数据报错的 bug。
飞书没有公开可匿名读的实例 ,所以这一条大概率做不到。那就明说「未经真实实例
验证」 ,别跟前两个混为一谈——这是 0012 里写死的要求。
三个已知的未知,需要先答
凭据与租户 :飞书是 tenant_access_token 那套,与前两个的 auth_header
直传不同,可能要在 config 里存 app_id/app_secret 并自己换 token。
注意凭据只进不出(编辑来源时留空 = 保留库里原值,见 sync_custom 的写法)。
富文本不是字符串 :飞书文档是块树(docx 的 block),需要一个
「块树 → 纯文本」的渲染器。Notion / Confluence Cloud(ADF)是同一类问题,
Jira Cloud v3 也是——本轮走 v2 绕开了。做出来的渲染器最好能被复用。
文档的「变更史」是版本序列,不是事件流 :这更接近 A corpus where the graph grows because the articles did #122 那份维基语料的做法。
要不要每个版本都取?大概不要——那一份的经验是按「变了多少」采样 ,
而且要挡掉「有人把页面清空了」这种伪变更(见
scripts/bench/fetch-wiki-history.mjs 的 persists())。
验收
单元测试覆盖 render()(含空评论不留空节这类边界)
若能拿到真实响应,加夹具测试;拿不到就在 PR 里明说
端到端:建来源 → 同步 → 文档带版本/变更信息 → 二次同步幂等(新增 0)
cargo clippy --workspace --all-targets 与 npm run typecheck 干净
Confluence / Notion 是同一类,谁想接也欢迎——三个的第 2 条未知是共通的。
接一个
feishu_docs来源:同步一个飞书云文档空间,每篇文档连同它的版本序列成为知识库里的文档。
判断依据与前两个工单来源共用一篇决策记录:
docs/decisions/0012。
动手前先读它——核心判断是「别取现在,取变化」,只抓当前版本的话这个来源
就退化成了又一个抓网页的。
已有两个可照抄的实现
crates/utopia-server/src/github_issues.rscrates/utopia-server/src/jira_issues.rsexpand=changelog)两边都是同一个骨架,照着走即可:
render()把一条记录排成 Markdown——不联网,于是测得动fetch_all()负责分页与鉴权ingest_sources.rs里加一个sync_*分支,调ingest_item()(身份、sha256 去重、版本记录都由它负责,不用自己写)
sources::KINDS白名单 + 前端三处(api.ts的SourceView["kind"]、Library.tsx的建来源对话框、SourcesRail.tsx的图标与SYNCING_KINDS)第 4 步容易漏:漏了的症状是界面上选得到、建的时候报
kind must be one of…,而单元测试与 tsc 都看不见。只有端到端会撞上。
三条硬要求
一、正文要写成带日期的陈述句,不是键值对。
Opened by X on 2026-08-18能抽出带valid_from的事实;created_at: 2026-08-18则要模型自己猜这是什么意思。二、截断要说出来。 分页有上限时,取回数小于服务端
total就落一条 warn。不报的话界面上「同步完成」是一句误导(Jira 那边是 500 / 14506)。
三、拿真实响应做夹具。 手写 JSON 只能证明「我以为的形状」。前两个来源的夹具
分别取自
deeplethe/utopia与issues.apache.org(都匿名可读),字段裁到声明的那些。它当场抓到过一个手写测试全绿、真实数据报错的 bug。
飞书没有公开可匿名读的实例,所以这一条大概率做不到。那就明说「未经真实实例
验证」,别跟前两个混为一谈——这是 0012 里写死的要求。
三个已知的未知,需要先答
auth_header直传不同,可能要在
config里存 app_id/app_secret 并自己换 token。注意凭据只进不出(编辑来源时留空 = 保留库里原值,见
sync_custom的写法)。docx的 block),需要一个「块树 → 纯文本」的渲染器。Notion / Confluence Cloud(ADF)是同一类问题,
Jira Cloud v3 也是——本轮走 v2 绕开了。做出来的渲染器最好能被复用。
要不要每个版本都取?大概不要——那一份的经验是按「变了多少」采样,
而且要挡掉「有人把页面清空了」这种伪变更(见
scripts/bench/fetch-wiki-history.mjs的persists())。验收
render()(含空评论不留空节这类边界)cargo clippy --workspace --all-targets与npm run typecheck干净Confluence / Notion 是同一类,谁想接也欢迎——三个的第 2 条未知是共通的。