【免费下载链接】plannotatorAnnotate and review coding agent plans and code diffs visually, share with your team, send feedback to agents with one click.项目地址https://gitcode.com/gh_mirrors/pl/plannotator点击查看免费下载导读本文围绕 Plannotator 仓库中的架构决策记录 adr/decisions/002-pr-context-warm-cache-20260630-110601.md下称“ADR-002”剖析其如何通过“会话级 Promise 缓存 后台预热”机制消除 PR Overview 面板打开时的 “Loading PR...” 闪烁。文章首先还原决策背景与设计约束然后给出完整的实现思路与源码级佐证最后讨论失效语义、演进方向与可验证的实验方法。读完本文你将掌握一套可复用的“服务端预热 共享 in-flight Promise 失败驱逐”缓存模式并能直接对照 Plannotator 的 Bun 主服务器与 Pi 扩展服务器两份实现进行落地。一、决策背景PR 概览数据为什么总是“慢半拍”Plannotator 的 PR Overview 面板集中展示 PR 的 description、comments、review threads、checks、labels、merge state 以及 linked issues 等上下文数据。在 ADR-002 记录时2026-06-30其加载流程存在一个明显的时序缺陷审查 UI 打开 Overview 面板后向服务器发起GET /api/pr-context请求服务器此前并未做任何准备工作直到收到该请求才调用fetchPRContext(prRef)去拉取数据于是在 provider 命令如gh pr view、GraphQL 查询等真正跑完之前面板只能显示 “Loading PR...”。换言之面板的等待时间 provider 冷启动的完整耗时而这一等待完全可以通过在服务器端提前预热来消除或缩短。当时的有利条件是主 Bun 审查服务器与 Pi 审查服务器都已经维护了 PR 模式下的会话级缓存PR 列表缓存、PR 切换缓存、栈树缓存具备成熟的会话缓存先例客户端已经统一收敛为“按 PR URL 键控的单一路径”来获取 PR 上下文因此这个问题可以在服务器端修复UI 完全不需要改动。同时ADR-002 明确了两条硬性约束预热的 fetch不得阻塞服务器启动也不得拖慢 PR 切换/api/pr-switch的响应provider 的临时失败不得污染缓存——UI 的重试路径必须能在瞬时故障后重新发起一次全新请求。二、决策内容会话级 PR 上下文 Promise 缓存ADR-002 的决策非常聚焦在两个审查服务器实现中各加入一个按 PR URL 键控、值为PromisePRContext的会话级缓存并配套“启动预热、切换预热、请求时共享、失败驱逐”四条行为规则。2.1 涉及的文件决策点明了两份需要同步修改的服务器实现packages/server/review.ts —— 主 Bun 审查服务器apps/pi-extension/server/serverReview.ts—— Pi 扩展审查服务器。配套的规范文档 adr/specs/pr-context-warm-cache-20260630-110258.md 进一步把修改范围限定为“仅服务器端React 客户端不改一行代码”。2.2 四个核心行为行为一启动预热。PR 审查会话启动时只要初始的prRef与prMetadata已就绪服务器立即在后台调用fetchPRContext(prRef)把返回的 Promise 存入缓存不 await 它。这样 UI 随后发起请求时数据往往已经拉完或正在拉取。行为二请求时共享。/api/pr-context处理器对“当前活跃 PR 的 URL”await 缓存中的 Promise若缓存中已有该 URL 的 Promise无论是否已完成直接共享它等待同一个 Promise若缓存中不存在例如预热失败被驱逐或从未来过则现场创建一个、先存入缓存、再 await 并返回结果。行为三切换预热。当/api/pr-switch切换活跃 PR 后一旦新的prRef已知服务器立刻为新 PR 的 URL 预热上下文缓存。关键点与行为一相同预热不阻塞切换响应切换返回的 diff 载荷仍然即时下发。行为四失败驱逐。若某个 PR URL 的上下文 fetch reject则在把错误返回给调用方之前先从缓存中删除该 URL 的条目。这样后续请求不会被一个陈旧的 rejected Promise 卡死而是能重新发起全新请求完成重试。2.3 API 契约保持不变ADR-002 与规范文档都强调/api/pr-context的响应契约不变——成功时返回原始的PRContextJSON失败时返回{ error: string }状态码 500非 PR 模式返回{ error: Not in PR mode }状态码 400。这一约束保证服务端预热是“无侵入优化”。三、实现要点从设计图到可运行代码规范文档给出了可直接落地的实现骨架理解它能帮你把这个模式迁移到其他系统。3.1 缓存容器与核心 helperconst prContextCache new Mapstring, PromisePRContext(); const getCachedPRContext (url: string, ref: PRRef): PromisePRContext { const cached prContextCache.get(url); if (cached) return cached; const promise fetchPRContext(ref).catch((error: unknown) { prContextCache.delete(url); // 失败立即驱逐允许后续重试 throw error; }); prContextCache.set(url, promise); // 先存 Promise 再 await天然合并并发 return promise; };这段代码的精妙之处在于以 Promise 为缓存值使“还在飞行中的请求”也能被后续调用方共享避免了重复的 provider 调用.catch中先驱逐再抛出实现“失败不缓存”的语义先set再返回保证并发请求拿到的是同一个 Promise等价于单飞行合并。3.2 启动预热与切换预热两处预热都使用void ... .catch(() {})显式“不等待、不吞错”确保任何异常都不会冒泡到启动或切换主流程// 启动预热初始 prRef 与 prMetadata 已就绪时 if (prRef prMetadata) { void getCachedPRContext(prMetadata.url, prRef).catch(() {}); } // 切换预热新 PR 的 metadata.url 与 prRef 已知后 void getCachedPRContext(pr.metadata.url, prRef).catch(() {});3.3 端点改造if (!isPRMode || !prRef || !prMetadata) { return Response.json({ error: Not in PR mode }, { status: 400 }); } const context await getCachedPRContext(prMetadata.url, prRef); return Response.json(context);规范文档还提示了两个运行时差异主服务器可直接从./pr导入PRContext与PRRef类型Pi 服务器则从../generated/pr-types.js导入且 Pi 实现要使用其自身的json(res, ...)辅助函数。这正对应“两个实现必须同步演进”的决策要求。3.4 源码佐证当前的 PRContextLiveCache 正是该决策的落地演进打开当前仓库源码可以看到该决策已演进为共享包 packages/shared/pr-context-live.ts 中的PRContextLiveCache类其设计完整继承了 ADR-002 的四条行为会话级entries new Mapstring, PRContextCacheEntry()对应“按 PR URL 键控的会话缓存”warm(url, ref)方法调用runDetached(...)发起best-effort 后台预热且不等待对应行为一与行为三见 pr-context-live.tsgetContext(url, ref)中“若存在 in-flight 则返回该 Promise否则刷新并共享”对应行为二见 pr-context-live.ts每条 entry 记录error与失败冷却时间failureCooldownMs默认 60s从机制上保证失败可重试而非永久污染。两个服务器端点在当前代码中均已切换到这一统一入口主服务器在 review.ts 通过prContextLive.getContext(prMetadata.url, prRef)处理/api/pr-contextPi 服务器在apps/pi-extension/server/serverReview.ts的对应路由中采用相同调用。fetchPRContext本身则是两个运行时各自的薄封装主服务器见 packages/server/pr.ts内部调用共享的plannotator/shared/pr-provider核心实现Pi 服务器见apps/pi-extension/server/pr.ts。3.5 底层 provider 的失败形态为什么需要驱逐要理解“失败驱逐”为何必要需要看底层 provider 的失败行为。根据研究文档 adr/research/SPIKE-pr-context-warm-cache-20260630-110258.md 的发现GitHub 的 PR context 在gh pr view失败时会 throw对应packages/shared/pr-github.ts的实现这是必须可重试的硬错误GitHub review threads 在 GraphQL 失败时会降级为空列表GitLab 的 context 会并行发起多个只读调用对应packages/shared/pr-gitlab.ts的实现并把部分失败大多降级为空切片。也就是说一部分失败是“软失败”已降级为可用数据一部分是“硬失败”会 reject。缓存策略对硬失败必须做到“驱逐即重试”这正是.catch里先delete再throw的设计动机。四、预期的收益与已知代价ADR-002 在 Consequences 部分对收益与代价做了非常克制的陈述值得逐条还原收益加载闪烁基本消失PR Overview 通常在其数据被 UI 请求时已经就绪消除重复 provider 调用即使预热仍未完成UI 等待的是“已开始的同一份工作”而不是再触发一次重复 fetch失败可重试rejected Promise 会被驱逐出缓存后续请求能重新发起。代价与已知边界会话级缓存可能变陈旧成功获取的 PR context 会在整个审查服务器会话生命周期内被缓存若服务器保持打开期间 comments 或 checks 发生变化数据可能过期。ADR 明确“当前接受这一点”并说明这与既有的 PR list / switch / stack tree 会话缓存风格一致慢 provider 下仍可能显示 loading如果 provider 调用特别慢预热仍可能在 Overview 挂载前未完成此时 UI 仍显示加载但等待的是已在跑的 fetch而非新开一次一次多余的只读调用对“从未打开过 Overview 上下文”的 PR 审查服务器会多执行一次 provider 调用。规范文档认为这是可接受的因为 PR Overview 现在默认打开且该调用是只读的。五、验证方式与演进脉络5.1 验证手段规范文档给出两条验证路径类型检查bun run typecheck根目录 package.json 中定义了bun test与bun run typecheck等脚本可选的手动聚焦检查启动一个 PR 审查 → 立即打开 PR Overview → 确认/api/pr-context正常返回切换到另一个 PR → 确认新 PR 的 Overview 仍能加载且 provider fetch 失败时重试可用。5.2 从 ADR 到实时更新决策的后续演进ADR-002 之后仓库又产生了相邻决策 adr/decisions/003-live-pr-context-updates-20260630-114643.md把“会话级快照缓存”进一步演进为“实时 PR 上下文更新”。这也是当前PRContextLiveCache类如此之大的原因——它在保留 ADR-002 的 warm / in-flight 共享 / 失败冷却语义之上增加了watch订阅、SSE 事件流/api/pr-context/stream端点、周期刷新默认refreshIntervalMs为 30 秒、失败冷却默认 60 秒以及“写操作后立即刷新refreshAfterWrite”等能力。如果你要阅读这段演进建议按此顺序先读 002 决策本文主题 → 读 003 决策 → 再读 pr-context-live.ts 的getContext与warm实现 → 最后对照两个服务器端点。这样你能完整看到“简单的 Promise 缓存”如何成长为“带 TTL、冷却、订阅与 SSE 的实时缓存”。六、可迁移的工程模式总结抛开 Plannotator 的具体业务ADR-002 沉淀的是一套通用且克制的最佳实践可直接迁移到任何“面板类 UI 服务端拉取”的架构中服务端预热而非客户端等待让数据准备发生在“用户可能请求之前”而不是“用户请求之时”缓存 Promise 而非结果缓存值取 Promise天然支持 in-flight 共享多请求自动合并为一次 provider 调用预热必须 detach用void promise.catch(() {})或等价手段发起后台任务绝不阻塞启动与主响应路径失败立即驱逐rejected 的 Promise 绝不留驻缓存保证瞬时故障后重试仍然有效API 契约冻结所有优化都发生在服务端内部对外响应结构一字不改从而把改动风险控制在单侧双实现同步当同一能力存在于两套服务器实现时把共享逻辑下沉到公共包如plannotator/shared/pr-context-live避免两份代码漂移——这正是当前仓库最终的走向。这六条原则合在一起回答了 ADR-002 的核心问题如何在不改 UI、不拖慢启动与切换、不污染缓存的前提下让“PR 概览数据”在用户看到面板之前就已准备就绪。赞分享【免费下载链接】plannotatorAnnotate and review coding agent plans and code diffs visually, share with your team, send feedback to agents with one click.项目地址https://gitcode.com/gh_mirrors/pl/plannotator点击查看免费下载相关推荐Plannotator PR 上下文预热缓存用会话级 Promise 缓存消除 Overview 加载闪烁Plannotator PR 上下文预热缓存用会话级 Promise 缓存消除 Overview 加载闪烁 导读 本文围绕 Plannotator 仓库中的技Plannotator Live PR Context Updates基于 SSE 的服务端 PR 上下文实时刷新机制解析Plannotator Live PR Context Updates基于 SSE 的服务端 PR 上下文实时刷新机制解析 导读 本文剖析 Plannotat用 MCP Agent 搭建 GitHub PR 自动评审工作流从一次 UI 面板闪烁修复看 AI 代码审查实践用 MCP Agent 搭建 GitHub PR 自动评审工作流从一次 UI 面板闪烁修复看 AI 代码审查实践 本指南以 tarko/mcp agent h人工智能大模型AI Agent桌面应用GUI 自动化浏览器控制MCP 服务MCP Clients上一篇5 分钟搭建网站变更监控用 changedetection.io 盯住降价与补货的完整实操下一篇LevelDB 打开数据库报比较器名称不匹配does not match existing comparator怎么排查创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考