返回博客

Linear 替代方案:本地优先的 Issue 追踪比你想象的更快

Linear 很快。这一点值得肯定。他们在感知性能上投入了大量精力,对大多数团队来说,它是最好的 SaaS issue 追踪器。但"最好的 SaaS"附带一些开发者无法接受的限制:你的数据存在别人的服务器上,你的工作流需要迁就他们的设计理念,每次交互都要付出网络往返的开销。

这篇文章是写给那些碰到了这些墙的开发者。也许你在管理一支每小时提交 50 个 issue 的 AI agent 团队。也许你在断网或离线优先的环境中工作。也许你只是不想在你和你的 issue 之间隔着一个登录页面。以下是我们在构建 Beadbox(一个将所有数据保存在本地的原生桌面 issue 追踪器)过程中的经验。

跳转到你关心的内容:

为什么开发者在寻找 Linear 的替代方案

常见的回答是"Linear 太固执己见了"。这是对的,但不够精确。Linear 强制要求周期、团队结构和工作流状态,假设你是一个按两周节奏发版的产品团队。如果你的情况确实如此,Linear 很好。如果你是一个协调 AI agent 的独立开发者,或者一个有非标准迭代模式的研究团队,或者一个需要把 issue 绑定到 git commit 而不是 Slack 对话的 DevOps 团队,Linear 的固执就变成了阻力。

更深层的问题是架构层面的。Linear 是一个云优先的 SaaS 产品。每次修改都要到他们的服务器走一个来回。每次查询都依赖于他们的正常运行时间。你的 issue 数据存在他们的数据库中,通过他们的 API 查询,按照他们的规则。对大多数团队来说,这是个可以接受的交换。对于关心数据主权、离线访问或大数据集原始查询速度的开发者来说,这是不可接受的。

Beadbox 做不到什么

在介绍 Beadbox 的优势之前,先说说它不适合的场景。跳过这个章节对你没有好处;在采用工具之后才碰到这些限制才是真正的损失。

没有多用户权限或访问控制。 没有用户账号、没有角色、没有按 issue 的可见性限制。任何对 .beads/ 目录(或 Dolt 服务器)有文件系统访问权限的人都可以读写一切。如果你需要限制谁能看什么,目前 Beadbox 不适合你。

有限的实时协作。 两个人可以在同一组 issue 上工作,但协作模型是推/拉(类似 Git),不是实时光标和在线状态指示器。在服务器模式下,Beadbox 每 3-5 秒轮询一次变更。在嵌入式模式下,文件系统监听检测变更更快(亚秒级),但两个进程并发写入同一个 Dolt 数据库可能会崩溃。安全模式是:一次只有一个写入者,或者使用服务器模式让 Dolt 处理并发。

没有与 Slack、GitHub、Figma 或其他 SaaS 工具的集成。 扩展点是 bd CLI 和 shell 脚本。如果你的工作流依赖于"issue 关闭时触发 Slack 消息",你需要自己搭建这个胶水层。

规模上限是真实存在的,但还比较远。 我们在万级和两万级 issue 数据集上进行测试(见下方基准测试)。这些都处理得很好。我们还没有在十万级以上的 issue 上进行压力测试。如果你是一个每年生成数十万个 issue 的大型组织,这不是经过验证的领域。

没有非技术利益相关者的访问方式。 没有 web 门户、没有访客查看器、没有可分享的仪表盘 URL。Beadbox 是一个读取本地数据库的桌面应用。向不使用你的机器的 PM 展示进度意味着屏幕共享或一个生成报告的 bd 脚本。

Beadbox 如何工作(30 秒版本)

在基准测试有意义之前,先了解架构:

Beadbox 架构:Tauri 应用带有 WebView、WebSocket 服务器、bd CLI、本地 Dolt 数据库和可选远程同步

嵌入式模式: Dolt 数据库存在于你文件系统上的 .beads/ 目录中。没有服务器进程,没有守护程序。bd CLI 直接读写。Beadbox 通过 fs.watch() 检测变更,带有 250ms 的防抖,通过 WebSocket 广播到 UI。这是零配置的路径。

服务器模式: 一个 dolt sql-server 进程单独运行(本地或局域网)。bd CLI 通过 MySQL 协议连接。Beadbox 每 3-5 秒轮询服务器查询变更,而不是监听文件系统。此模式支持多个并发写入者。

GUI 执行的每个操作都通过 bd CLI 路由。Beadbox 从不直接触碰数据库。如果 bd show 和 Beadbox 不一致,那是 Beadbox 的 bug。

性能:万级 issue 数据集上的真实基准测试

beads CLI 发布了基准测试,你可以在自己的硬件上复现。以下是在 M2 Pro 上对 10,000 个 issue 的 Dolt 数据库运行 Go 基准测试套件的真实数据:

操作 耗时 内存 数据集
过滤就绪工作(未阻塞 issue) 30ms 16.8 MB 10K issue
搜索(全部开放,无过滤) 12.5ms 6.3 MB 10K issue
创建 issue 2.5ms 8.9 KB 10K issue
更新 issue(状态变更) 18ms 17 KB 10K issue
循环检测(5K 线性链) 70ms 15 KB 5K 依赖
批量关闭(100 个 issue) 1.9s 1.2 MB 顺序写入
同步合并(10 创建 + 10 更新) 29ms 198 KB 批量操作

这些是 CLI 级别的基准测试:bd 从本地 Dolt 数据库读取或写入的时间。Beadbox UI 在此基础上增加了渲染开销。我们全栈的设计目标(CLI 调用 + React 渲染 + WebSocket 传播)是:

UI 操作 设计目标
Epic 树渲染(100+ issue) < 500ms
应用/清除过滤器 < 200ms
工作区切换 < 1 秒
实时更新传播(嵌入式) < 2 秒
冷启动到可用 < 5 秒

我们不发布与 Linear 或其他追踪器的对比基准测试,因为我们没有做过受控比较,而精挑细选的数据也不诚实。我们能说的是:整个数据路径是本地的。从点击过滤器到看到结果之间没有网络跳转。这对你是否重要取决于你的基线。如果 Linear 对你的数据集规模和网络条件来说足够快,那它可能确实够了。如果你在酒店 WiFi 上用 500 个 issue 的积压感受过延迟,你就知道这些数字解决的是什么问题。

要复现:克隆 beads,运行 go test -tags=bench -bench=. -benchmem ./internal/storage/dolt/...,然后与你的硬件进行对比。缓存的数据集在 /tmp/beads-bench-cache/

Git 集成深度:不仅仅是把 commit 关联到 issue

大多数 issue 追踪器把 Git 集成当作功能清单上的一个勾选项:在 commit message 中提及 issue ID,issue 上就会出现一个链接。这很有用,但很浅。

Beadbox 建立在 beads 之上,这是一个以 Git 语义为存储层的 issue 追踪器,而不是后加的集成。底层数据库 Dolt 为结构化数据实现了 Git 的 merkle 树数据模型。每次 issue 变更都是一次 commit。每次 commit 都有父节点。你可以对 issue 历史使用 dolt diffdolt logdolt merge,语义与你在代码上使用的完全相同。

实际意义是什么:

你的 issue 历史是可审计的。 数据库本身就是一个 commit 图谱。你可以 diff 任意两个时间点,精确地看到哪些 issue 的哪些字段发生了变更。这不是后加的"审计日志功能"。存储格式本身就是审计轨迹。

分支不仅适用于代码,也适用于 issue。 Dolt 原生支持分支。你可以为 issue 数据库创建分支来试验重新组织,然后合并回去或直接丢弃。

同步是推/拉,不是 API 调用。 多机协作就像 git pushgit pull 一样。不需要 API token、不需要 webhook、不需要 OAuth 流程。把 Dolt remote 指向一个服务器(或 DoltHub)然后推送。另一台机器拉取。

关于冲突的说明: Dolt 使用三路合并,与 Git 相同。如果两个人编辑了同一个 issue 的不同字段,合并会自动解决。如果两个人编辑了同一个 issue 的同一个字段,你会得到一个需要通过 Dolt CLI(dolt conflicts resolve)手动解决的冲突。Beadbox 还没有冲突解决 UI;你在 dolt 层处理冲突。实际上,当每个人(或 agent)处理不同的 issue 时,冲突很少发生,这也是典型模式。但如果你的团队经常同时编辑相同的 issue,这是一个你应该知道的摩擦点。Dolt 合并文档 详细介绍了冲突解决工作流。

原生渲染:为什么我们在 Tauri 中打包 Node.js

Linear 在浏览器标签中运行。Jira、Asana 和其他所有 SaaS 追踪器也是如此。浏览器标签竞争内存、被操作系统挂起,并通过合成器渲染,增加帧延迟。

Beadbox 作为基于 Tauri 的原生桌面应用运行。Tauri 应用通常很小(Tauri 运行时本身只有个位数兆字节),因为它们使用操作系统原生 WebView 而不是打包 Chromium。我们的包比典型的 Tauri 应用大,约 160MB,这是一个值得解释的有意取舍。

其中 84MB 是嵌入的 Node.js 运行时。我们使用 sidecar 架构:Tauri 将 Next.js 服务器作为子进程启动,处理服务端渲染、Server Actions 和用于实时更新的 WebSocket 层。Tauri WebView 指向这个本地服务器。我们选择这个方案而不是纯 Rust 后端,因为 Next.js 生态系统提供了 React Server Components、Server Actions 和 UI 层的快速迭代速度。代价是包体积。等效的 Electron 应用会超过 400MB。纯 Rust + Tauri 应用会不到 10MB,但开发时间要多 3 倍,而且会失去 React 生态。

与浏览器标签的实际区别:Beadbox 在一个专用的 WebView 进程中渲染,不与你的其他 47 个浏览器标签共享内存。展开一个包含 100 多个嵌套 issue 的 epic 树、在完整积压上应用过滤器、在工作区之间切换:当渲染器不需要竞争资源时,这些操作的感觉有质的不同。

通过 CLI 扩展,而不是 REST API

Linear 有一个 GraphQL API。设计得很好。但扩展 Linear 意味着编写与他们服务器通信、使用他们的 token 认证、处理他们速率限制的代码。

Beadbox 采用了不同的方式:bd CLI 就是 API。GUI 执行的每个操作都通过 bd,你在终端中使用的同一个命令行工具。

以下是三个你今天就可以复制粘贴的工作流:

批量更新优先级用于分拣扫描:

# 将所有开放的 bug 设置为优先级 1(关键)
bd list --status=open --type=bug --json | \
  jq -r '.[].id' | \
  xargs -I{} bd update {} --priority=1

生成每日状态摘要:

# 过去 24 小时有什么变化?
echo "=== Closed today ==="
bd list --status=closed --json | \
  jq -r '.[] | select(.updated > (now - 86400 | todate)) | "\(.id) \(.title)"'

echo "=== Currently blocked ==="
bd blocked --json | \
  jq -r '.[] | "\(.id) \(.title) (blocked by: \(.blocked_by | join(", ")))"'

echo "=== Ready to work ==="
bd ready --json | jq -r '.[] | "\(.id) [P\(.priority)] \(.title)"'

AI agent 创建并认领工作:

# Agent 发现一个 bug,提交并认领
ISSUE_ID=$(bd create \
  --title "Fix race condition in auth middleware" \
  --type bug \
  --priority 1 \
  --json | jq -r '.id')

bd update "$ISSUE_ID" --status=in_progress --assignee=agent-3

# ... agent 完成工作 ...

bd update "$ISSUE_ID" --status=closed
bd comments add "$ISSUE_ID" --author agent-3 \
  "Fixed in commit abc1234. Root cause: mutex not held during token refresh."

如果你在运行 AI 编码 agent(Claude Code、Cursor、Copilot Workspace),它们已经知道怎么运行 CLI 命令。不需要 API 客户端库,不需要认证流程。只是 Unix 管道和 shell 脚本。

试试 Beadbox 看看这些工作流在 agent 执行时的实时可视化效果。

离线优先不是一个功能,而是一种架构

一些云端追踪器提供"离线模式",缓存最近的数据并在重新连接时同步。这是在根本上在线的架构上附加的功能。故障模式是可预见的:过期缓存、同步冲突、静默排队后来又失败的操作。

Beadbox 能离线工作,因为它从一开始就不是在线的。在嵌入式模式下,你的整个 issue 数据库就是文件系统上的一个目录。没有服务器进程。没有守护程序。没有网络套接字。bd CLI 直接读写那个目录。Beadbox 用 fs.watch() 监听它并渲染发现的内容。

没有什么需要同步,因为根本没有远程端。如果你后来选择协作,Dolt 的推/拉提供了明确的、可见的同步。但默认是本地的。默认是你自己的。

安全性如何? 如果你在为断网或敏感环境评估 Beadbox,以下是具体的安全态势:

  • 静态加密: Beadbox 本身不加密 .beads/ 目录。它依赖操作系统级别的磁盘加密(macOS 的 FileVault、Linux 的 LUKS、Windows 的 BitLocker)。如果你的威胁模型要求按数据库加密,这是一个缺口。
  • 备份: 你的 .beads/ 目录就是一个普通目录。cp -rrsync、Time Machine 或 dolt push 到远程都可以。Dolt 的 commit 历史也意味着意外更改可以用 dolt reset 回滚。
  • 什么会离开机器: 在嵌入式模式下,什么也不会。零网络调用。在桌面应用中,存在两个可选的出站连接:GitHub API 检查 Beadbox 更新(可在设置中禁用),以及 PostHog 分析(如果你选择开启;默认禁用,不收集 PII)。两者都不传输 issue 数据。

对于断网环境、机密项目或在飞机和火车上工作的开发者来说,这不是锦上添花。这是唯一可行的架构。

为你的团队选择合适的工具

没有一种工具在所有情况下都是正确的。以下是诚实的分析:

选择 Linear 如果:

  • 你的团队有 10 人以上,需要集中式项目管理
  • 你依赖 Slack/GitHub/Figma 集成
  • 非技术利益相关者需要访问你的 issue 追踪器
  • 你想要托管基础设施,零运维开销
  • 你是一个按固定周期发版的产品团队

选择 Beadbox 如果:

  • 你重视数据主权(issue 永远不离开你的机器)
  • 你经常离线工作或在受限网络环境中
  • 你管理需要以编程方式读写 issue 的 AI agent
  • 你想要 Git 原生的 issue 历史(对 issue 进行分支、diff、合并)
  • 你偏好 CLI 优先的工作流,在需要时有可视化辅助
  • 你是独立开发者或小团队(1-10 人),不需要企业级功能

继续使用当前工具如果:

  • 切换成本超过你正在经历的摩擦
  • 你的团队已经在依赖当前追踪器 API 的集成上投入了大量精力
  • 你的工作流已经适应了你工具的设计理念

从 Linear(或其他追踪器)迁移

直说吧:目前没有自动化的 Linear 到 Beadbox 迁移工具。没有 CSV 导入向导,没有 API 桥接,没有状态映射 UI。

如果你从零开始,没问题。bd init,开始创建 issue,Beadbox 立即就能看到。零摩擦。

如果你有一个现有的 Linear 项目想迁移过来,目前可行的路径是脚本化的:从 Linear 的 API 导出(他们支持 CSV 和 API 导出),转换数据,然后循环调用 bd create 重新创建 issue。你会丢失 Linear 特有的元数据(周期、项目视图、SLA 计时器),但能保留标题、描述、优先级和状态。迁移脚本是一个周末项目,不是一个季度的集成工程。

我们知道这对于有数千个 issue 和多年历史的团队来说还不够好。构建一个合适的导入管道在我们的路线图上,但还没有完成。如果迁移摩擦是你的主要顾虑,等我们构建好后再来,或者评估从零开始对你的场景是否可接受。

开始使用

Beadbox 在 beta 期间免费。用 Homebrew 安装:

brew tap beadbox/cask && brew install --cask beadbox

如果你已经在使用 beads,Beadbox 会自动检测你现有的 .beads/ 工作区。打开应用,你的 issue 就在那里。无需导入步骤。无需创建账号。

如果你是 beads 新用户,Beadbox 会引导你初始化第一个工作区。不到 60 秒你就能看到你的 issue。

下载 Beadbox 或查看 beads 来看看本地优先的 issue 追踪是否适合你的工作流。