BugBuster 实战指南:用 Botpress SDK 构建 Linear Issue 代码规范检查与 GitHub 双向同步机器人
BugBuster 实战指南:用 Botpress SDK 构建 Linear Issue 代码规范检查与 GitHub 双向同步机器人
导读
BugBuster 是构建于 Botpress SDK 之上的开源机器人示例,核心使命是为 Linear 议题(Issue)执行自动化的 Lint 规范检查,并将 GitHub 新议题自动同步为对应的 Linear 议题。阅读本文后,你将掌握:BugBuster 的完整事件驱动架构与定时工作流设计、全部 Lint 规则的判定逻辑、基于评论 Marker 的自动解决机制、Slack/Telegram 交互命令,以及如何通过环境变量与集成配置将其部署运行。
一、功能定位:两条核心自动化链路
根据 readme.md 的定义,BugBuster 围绕两条主线工作:
- Linear 链路(Lint 检查):连接 Linear 工作区后,当议题被创建或更新时,机器人检测 Lint 错误并在议题上评论;一旦错误被修复,评论会被自动解决(resolve)。
- GitHub 链路(议题同步):连接 GitHub 仓库后,机器人监听新议题事件,自动在 Linear 中以 Triage(待处理)状态创建对应议题,并带上 GitHub 议题的标题、描述与链接。
从 bootstrap.ts 的依赖装配可以看到,这两条链路由 IssueProcessor(Lint 处理)与 IssueStateChecker(状态滞留检测)等核心服务支撑,并通过 Botpress 的 client、logger、ctx 标准处理器属性完成集成调用。
二、事件驱动的机器人骨架
机器人的事件绑定集中定义在 src/index.ts,它是理解整个机器人行为的入口:
bot.on.event('github:issueOpened', handlers.handleGithubIssueOpened)
bot.on.event('linear:issueUpdated', handlers.handleLinearIssueUpdated)
bot.on.event('linear:issueCreated', handlers.handleLinearIssueCreated)
bot.on.event('timeToLintAll', handlers.handleTimeToLintAll)
bot.on.event('timeToCheckIssuesState', handlers.handleTimeToCheckIssuesState)
bot.on.message('*', handlers.handleMessageCreated)
bot.on.workflowStart('lintAll', handlers.handleLintAll)
bot.on.workflowContinue('lintAll', handlers.handleLintAll)
bot.on.workflowTimeout('lintAll', handlers.handleLintAllTimeout)
可见机器人由三类触发源驱动:
- 集成事件:GitHub 的
issueOpened、Linear 的issueCreated/issueUpdated; - 定时事件:
timeToLintAll与timeToCheckIssuesState; - 用户消息:任意消息(
*)进入命令处理流程; - 工作流:
lintAll支持启动(start)、续跑(continue)与超时(timeout)三种生命周期钩子。
2.1 定时调度的 Cron 配置
定时事件在 bot.definition.ts 中通过 recurringEvents 声明:
recurringEvents: {
timeToLintAll: {
payload: sdk.z.object({}),
type: 'timeToLintAll',
schedule: { cron: '0 13 * * 1' }, // 每周一 8AM EST 运行
},
timeToCheckIssuesState: {
payload: sdk.z.object({}),
type: 'timeToCheckIssuesState',
schedule: { cron: '0 * * * *' }, // 每小时整点运行
},
},
timeToLintAll:每周一次触发全量 Lint(源码注释标注为每周一 8AM EST);timeToCheckIssuesState:每小时检查一次议题状态滞留情况。
事件到达后,time-to-lint-all.ts 会通过 client.getOrCreateWorkflow({ name: 'lintAll', discriminateByStatusGroup: 'active', status: 'pending' }) 启动全量 Lint 工作流;time-to-check-issues-state.ts 则遍历各团队、逐条状态规则调用 IssueStateChecker 处理。
三、Linear Lint 规则全解析
文档中列出的三条规则是 Lint 体系的核心,而完整实现位于 lint-issue.ts 的 lintIssue 函数中,它在读取议题元数据(标签、关联关系、状态、优先级、估算、项目、负责人)后产出错误消息列表:
| 规则 | 触发条件 | 错误消息示例 |
|---|---|---|
| 必须有类型标签 | 议题缺少父分类为 type 的标签 |
Issue ABC-123 is missing a type label. |
| Blocked 状态必须有阻塞依据 | 状态为 BLOCKED 但既无 blocked/blocked-reason 标签,也无 blocks 关联议题 |
Issue ABC-123 is blocked but missing a "blocked-reason" label or a blocking issue. |
| Backlog 中不能有负责人 | 状态为 backlog 且已分配 assignee |
Issue ABC-123 has an assignee but is still in the backlog. |
| In Progress 必须有负责人 | 状态为 started 但无 assignee |
Issue ABC-123 is started but has no assignee. |
| 必须有 area 标签 | 标签中不存在以 area. 前缀命名的标签 |
Issue ABC-123 is missing an "area." label. |
| 必须有优先级 | issue.priority 为空 |
Issue ABC-123 is missing a priority. |
| 必须有估算值 | estimate === null 且状态不是 BLOCKED(被阻塞议题允许不估算) |
Issue ABC-123 is missing an estimate. |
| 估算不宜过大 | 估算大于 8 且不是 epic 类型标签 |
Issue ABC-123 has an estimate greater than 8 (...). Consider breaking it down or making it an epic. |
| 不能关联已完成项目 | 议题项目 completedAt 非空 |
Issue ABC-123 is associated with a completed project (...). |
| 不能指派给已停用用户 | assignee?.active === false |
Issue ABC-123 is assigned to a deactivated user. Consider reassigning the issue. |
文档中强调的"议题必须拥有 title、description、priority、estimate"以及"Blocked 必须有阻塞原因标签或阻塞议题""In Progress 必须有 assignee",在源码中均有对应实现;标题与描述缺失的检查体现在 GitHub 同步与 lint 消息的完整性问题处理中。
3.1 状态归一化:不同团队状态名如何统一比较
Lint 规则依赖"通用状态名"(如 BLOCKED、started),因为不同 Linear 团队可自定义状态名。核心实现在 common-state-name.ts,它把各团队状态按类型映射为统一名称:
started类型下的in progress、in review、in pr review、blocked、staging、monitoring、in prod testing分别归为IN_PROGRESS、IN_REVIEW、BLOCKED、STAGING、MONITORING;completed下的done、production (done)归为DONE;canceled下的stale归为STALE等。
StateService 会缓存全部状态并以该映射为每个状态附加 commonName,供 Lint 与状态滞留检查统一消费。
四、Lint 结果处理:评论 Upsert、自动解决与标签标记
4.1 忽略机制
IssueProcessor 定义了两个关键常量:
const IGNORED_STATES: lin.StateType[] = ['triage', 'backlog', 'completed', 'canceled', 'duplicate']
const LINTIGNORE_LABEL_NAME = 'lintignore'
const LINTDETECTED_LABEL_NAME = 'lintdetected'
当议题处于上述忽略状态、或带有 lintignore 标签时,Lint 直接返回 ignored 结果,并清理旧的 lint 评论与 lintdetected 标签,避免噪音。
4.2 评论的 Upsert 与自动解决
这是文档所述"修复后自动解决评论"的底层实现。关键点在于 comment-service.ts 中的评论 Marker 机制:
const COMMENT_MARKERS: Record<types.CommentType, string> = {
lint: 'BugBuster · lint',
stale: 'BugBuster · stale',
}
const _buildFooter = (marker: string): string => `---\n_🐛 via ${marker}_`
机器人发布的每条评论末尾都带有固定的 Marker 标识,upsertComment 通过"查找现有评论 → 内容相同则跳过、不同则更新、不存在则新建"实现幂等;resolveComments 则只解决匹配 Marker 的评论(同时兼容无 Marker 的旧版本评论,避免孤儿评论)。
LinearApi 进一步提供了"自愈"能力:当并发导致出现多条重复评论时,保留最新一条、自动 resolve 其余重复项,并始终按分页拉取机器人自己的评论(按 viewer 过滤),确保忙碌线程不会把机器人评论挤出视野造成重复发帖。
4.3 完整 Lint 判定流程
IssueProcessor.lintIssue(issue-processor/index.ts)执行:
- 判断忽略状态或
lintignore标签 → resolve 评论、移除lintdetected标签,返回ignored; - 执行
lintIssue(issue, state)得到错误列表; - 无错误 → resolve 评论、移除标签,返回
succeeded; - 有错误 → 默认在议题上评论错误明细(
comment: false可关闭),并打上lintdetected标签,返回failed。
由此形成闭环:创建/更新时打上 lintdetected 并评论 → 修复后再次触发事件 → 检查通过 → 自动 resolve 评论并移除标签。
五、lintAll 工作流:全量 Lint 与进度持久化
lintAll 工作流(lint-all.ts)用于对所有被监控团队的相关议题执行全量扫描,其输入参数在 bot.definition.ts 中定义:
verbose(可选,默认false):是否在对话中实时播报每页 Lint 进度;comment(可选,默认true):是否在 Linear 议题上评论 Lint 结果。
工作流通过两个 workflow 类型状态实现断点续跑(见 bot.definition.ts):
lastLintedId:记录最后成功 Lint 的议题 ID,作为分页游标;lintResults:累积全部 Lint 结果(failed/succeeded/ignored),每处理一页即持久化。
执行时按每页 50 条议题(ISSUES_PER_PAGE,见 client.ts)分页遍历,自动跳过 triage、backlog、completed、canceled、duplicate 状态(listRelevantIssues)。结束时汇总消息:
- 全部通过 →
Linting complete. All issues passed lint checks. - 失败 1 条 → 给出该议题链接;
- 失败不超过 5 条 → 列出链接;超过 5 条 → 列出前 5 条并提示查看
lintdetected标签全集链接(RESULT_MESSAGE_LIMIT = 5用于防止刷屏)。
若从聊天发起,结果回复给当前会话;若是定时触发的无人会话,则按 notificationChannels 状态(bot 级)中的配置,把结果推送到与各团队相关的 Slack 频道。超时时由 handleLintAllTimeout 回复 Error: the 'lintAll' operation timed out。
六、定期状态滞留检查:Stale 提醒
timeToCheckIssuesState 每小时运行,检查两类滞留场景(见 time-to-check-issues-state.ts):
| 状态 | 最长滞留时间 | 提醒内容 |
|---|---|---|
STAGING |
-P1W(1 周) |
该议题在 staging 超过一周 |
BLOCKED |
-P1M(1 个月) |
该议题被阻塞超过一个月 |
IssueStateChecker 按状态 ID 与 updatedAt < 阈值 过滤议题并分页遍历,对每条滞留议题以 stale 类型 Marker 发出一条提醒评论(同样走 Upsert 幂等逻辑)。这也是 CommentType = 'lint' | 'stale' 两种评论类型之一的来源(见 types.ts)。
七、GitHub → Linear 同步链路
github-issue-opened.ts 实现了文档所述的 GitHub 新议题同步:
const TEAM_NAME_FOR_NEW_ISSUES = 'Engineering'
...
await props.client.callAction({
type: 'linear:createIssue',
input: {
teamName: TEAM_NAME_FOR_NEW_ISSUES,
description: githubIssue.issue.body,
title: githubIssue.issue.name,
},
})
流程为:收到 github:issueOpened 事件 → 调用 linear:createIssue 在 Engineering 团队创建议题(携带 GitHub 议题的标题与描述)→ 在新建的 Linear 议题上追加一条评论,说明来源并附 GitHub 议题链接:
This issue was created from GitHub by BugBuster Bot.
GitHub Issue: <议题标题>
新创建的议题落入 Linear 的默认工作流起始状态(即 Triage),等待后续 Lint 流程接管。
八、聊天命令:Slack 与 Telegram 交互控制
机器人监听 Telegram 与 Slack 私聊消息(message-created.ts 中 MESSAGING_INTEGRATIONS = <a href="https://link.gitcode.com/i/da59d93ed00999b8dd7c86ea031bd047" target="_blank">'telegram', 'slack'],Slack 的频道/线程消息被忽略以避免刷屏)。命令表定义在 [command-processor.ts:
| 命令 | 必选参数 | 可选参数 | 作用 |
|---|---|---|---|
#listTeams |
— | — | 列出当前被监控的团队 |
#addTeam |
teamName |
— | 把团队加入监控列表 |
#removeTeam |
teamName |
— | 从监控列表移除团队 |
#lintAll |
— | --verbose、--no-comments |
启动全量 Lint 工作流 |
#addNotifChannel |
channelName、teamName1 |
teamName2 ... |
配置 Slack 通知频道与团队 |
#removeNotifChannel |
channelName |
— | 移除通知频道 |
#removeNotifChannelTeam |
channelName、teamName |
— | 从通知频道移除某团队 |
#listNotifChannels |
— | — | 列出全部通知频道 |
#version |
— | — | 显示机器人版本 |
其中 #lintAll --verbose --no-comments 可实时播报进度且不在 Linear 上留评论;#addNotifChannel 要求所有团队必须先被监控(watchedTeams),并通过 slack:getOrCreateChannelConversation 建立频道会话;被监控团队列表持久化在 bot 级状态 watchedTeams 中(见 teams-manager.ts)。
九、部署配置:环境变量与集成装配
9.1 环境变量
机器人依赖 5 个环境变量,通过 genenv 在 postinstall 阶段生成类型安全的 .genenv(见 package.json):
BUGBUSTER_GITHUB_TOKEN:GitHub 个人访问令牌(手动 PAT 模式);BUGBUSTER_GITHUB_WEBHOOK_SECRET:GitHub Webhook 签名密钥;BUGBUSTER_LINEAR_API_KEY:Linear API Key;BUGBUSTER_LINEAR_WEBHOOK_SIGNING_SECRET:Linear Webhook 签名密钥;BUGBUSTER_TELEGRAM_BOT_TOKEN:Telegram Bot Token。
9.2 集成装配
bot.definition.ts 通过链式 .addIntegration(...) 装配四个官方集成:
- GitHub:
configurationType: 'manualPAT',注入personalAccessToken与 webhook secret; - Telegram:注入
botToken,并启用typingIndicatorEmoji; - Linear:
configurationType: 'apiKey',注入apiKey与 webhook 签名密钥; - Slack:关闭
typingIndicatorEmoji,回复行为配置为location: 'channel'且onlyOnBotMention: false。
集成包通过 bpDependencies 指向仓库内的 integrations/github、integrations/linear、integrations/slack、integrations/telegram,因此需要在 monorepo 中先安装集成依赖。
9.3 构建与检查命令
"check:type": "tsc --noEmit",
"check:bplint": "bp lint",
"build": "bp add -y && bp build",
"test": "vitest --run"
bp add生成bp_modules与.botpress等绑定代码;bp build产出可部署的机器人包;vitest --run运行单元测试(例如 common-state-name.test.ts 验证状态名归一化逻辑)。
9.4 底层 API 封装要点
LinearApi 是机器人操作 Linear 的统一封装,值得关注的设计包括:
- 通过
linear:sendRawGraphqlQuery执行 GraphQL 查询(graphql-queries.ts),因为linear:listStates动作输出不含状态type字段; - 团队、状态、标签 ID 均带内存缓存;
- 议题按 50/页、状态按 200/页、评论按 100/页分页;
- 评论/标签的增删改查全部基于 GraphQL 与
createMessage(以 bot 身份在议题会话中发文本消息)。
十、小结
从 readme.md 的定位出发,BugBuster 展示了在 Botpress SDK 之上构建"规范治理型"机器人所需的完整范式:事件驱动 + 定时工作流 + 幂等评论 + 标签状态机 + 多通道交互。读者可以以此为模板,将 Lint 规则、状态滞留阈值、团队监控列表与通知频道按自身工作流调整,快速落地一套"议题质量守门员"。