在 IntelliJ IDEA 里复刻 Cursor 式内联审查:一篇够长的架构复盘-从入门到放弃

在 IntelliJ IDEA 里复刻 Cursor 式内联审查:架构复盘

写给谁 :读过就能知道项目发生了什么,无需打开仓库里其它 md

截至 :2026-06-02 · 项目状态停更 / 放弃继续用 IDEA 做块级审阅 (维护者决定不再折腾插件)

最后可用产品版本3.0.61(tag cc-gui-review-3.0.61,commit 3dd76a6

master 代码be0c87b --- 已从 3.0.63 回滚产品逻辑至 3.0.61

代码仓库(Git)

远程地址 https://gitee.com/quyixiao/cc-idea-code-review.git
插件目录 仓库内 cc-gui-review/
稳定标签 cc-gui-review-3.0.61
克隆 git clone https://gitee.com/quyixiao/cc-idea-code-review.git
bash 复制代码
git clone https://gitee.com/quyixiao/cc-idea-code-review.git
cd cc-idea-code-review/cc-gui-review
git checkout cc-gui-review-3.0.61   # 仅产品逻辑;或 master 含回滚提交 be0c87b
./gradlew buildPlugin
# → build/distributions/cc-gui-review-3.0.61.zip

从入门到放弃:为什么做完又停下

当前的效果

起点(入门)

团队在 IntelliJ IDEA 里用 Claude Code / CC GUI 改 Java。Cursor 里 AI 改完代码,绿块、红删、Keep/Undo 就在光标旁边------审阅是改代码的一部分 。切到 IDEA,同一批改动却落在 Local Changes 整文件 Diff 里:能看,但不能「这一块要、那一块不要」。

立项 cc-gui-review 时,目标说得很直白:把 Cursor 那套内联体验搬进 IDEA------绿增、红删、块级 Accept/Reject、多文件顶栏。

早期还做过右侧 ToolWindow 卡片 +「查看 Diff」弹窗。你一眼否定:「这不是我要的 Cursor 效果。」 从此定调:100% 编辑器内联,没有第二条 UI 路线。

技术路线演进:先只有 IDEA 插件 → 再加 npm → 再收回插件 → 最后放弃

这是整篇博客的故事主线 。很多人以为我们「一开始就用 Node 桥」------其实不是。我们像盖房子一样:先盖一层(插件)→ 觉得地基不够又搭脚手架(npm)→ 发现脚手架和楼体对不齐 → 拆掉脚手架只留楼体 → 楼体盖到顶楼仍漏雨,最后停工

实际走过 五个时代(下图从左到右):
#mermaid-svg-PZ6OcSn3wtIrdTVK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PZ6OcSn3wtIrdTVK .error-icon{fill:#552222;}#mermaid-svg-PZ6OcSn3wtIrdTVK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PZ6OcSn3wtIrdTVK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .marker.cross{stroke:#333333;}#mermaid-svg-PZ6OcSn3wtIrdTVK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PZ6OcSn3wtIrdTVK p{margin:0;}#mermaid-svg-PZ6OcSn3wtIrdTVK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .cluster-label text{fill:#333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .cluster-label span{color:#333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .cluster-label span p{background-color:transparent;}#mermaid-svg-PZ6OcSn3wtIrdTVK .label text,#mermaid-svg-PZ6OcSn3wtIrdTVK span{fill:#333;color:#333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .node rect,#mermaid-svg-PZ6OcSn3wtIrdTVK .node circle,#mermaid-svg-PZ6OcSn3wtIrdTVK .node ellipse,#mermaid-svg-PZ6OcSn3wtIrdTVK .node polygon,#mermaid-svg-PZ6OcSn3wtIrdTVK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PZ6OcSn3wtIrdTVK .rough-node .label text,#mermaid-svg-PZ6OcSn3wtIrdTVK .node .label text,#mermaid-svg-PZ6OcSn3wtIrdTVK .image-shape .label,#mermaid-svg-PZ6OcSn3wtIrdTVK .icon-shape .label{text-anchor:middle;}#mermaid-svg-PZ6OcSn3wtIrdTVK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PZ6OcSn3wtIrdTVK .rough-node .label,#mermaid-svg-PZ6OcSn3wtIrdTVK .node .label,#mermaid-svg-PZ6OcSn3wtIrdTVK .image-shape .label,#mermaid-svg-PZ6OcSn3wtIrdTVK .icon-shape .label{text-align:center;}#mermaid-svg-PZ6OcSn3wtIrdTVK .node.clickable{cursor:pointer;}#mermaid-svg-PZ6OcSn3wtIrdTVK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .arrowheadPath{fill:#333333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PZ6OcSn3wtIrdTVK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZ6OcSn3wtIrdTVK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PZ6OcSn3wtIrdTVK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZ6OcSn3wtIrdTVK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PZ6OcSn3wtIrdTVK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PZ6OcSn3wtIrdTVK .cluster text{fill:#333;}#mermaid-svg-PZ6OcSn3wtIrdTVK .cluster span{color:#333;}#mermaid-svg-PZ6OcSn3wtIrdTVK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PZ6OcSn3wtIrdTVK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PZ6OcSn3wtIrdTVK rect.text{fill:none;stroke-width:0;}#mermaid-svg-PZ6OcSn3wtIrdTVK .icon-shape,#mermaid-svg-PZ6OcSn3wtIrdTVK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZ6OcSn3wtIrdTVK .icon-shape p,#mermaid-svg-PZ6OcSn3wtIrdTVK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PZ6OcSn3wtIrdTVK .icon-shape .label rect,#mermaid-svg-PZ6OcSn3wtIrdTVK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZ6OcSn3wtIrdTVK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PZ6OcSn3wtIrdTVK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PZ6OcSn3wtIrdTVK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 时代一

纯 IDEA 插件

1.0.x
时代二

npm cc-gui-bridge

  • IDEA :9876

v2 / 2.0
时代三

问题爆发

双真相/双端口
时代四

审查收束回插件

1.0.88 live-diff

3.0.x 删 npm
时代五

3.0.61 顶峰

→ 停更放弃


时代一:只有 IDEA 插件------「差的是 diff 算法吧?」(约 1.0.0 -- 1.0.87)

那时的世界

安装包里只有一样东西:cc-gui-review-x.x.zip。装完重启 IDEA,绿线、红删 inlay、块旁「拒绝/同意」、顶栏「保存所有/查看下一个文件」------全都发生在编辑器里 。1.0.3 定调内联;1.0.73 接上 Git Changelist,CC GUI 写盘终于能被「看见」。

当时的乐观

我们真心以为:Cursor 能画出来,无非是 IntelliJ 的 ComparisonManager 用对 + Javadoc 闭合行 extend 写全 。版本号从 1.0.30 一路打到 1.0.87,大量 commit 在调配色、顶栏右对齐、浮层别盖住 Settings、@Value 不要被误标绿

残酷的现实(用户侧)

你点「同意」一块,绿线闪回来了 ------像没点过。工具条锚在 L72,绿线却铺在 L65。新加的 Javadoc,**/ 闭合行没绿底**,而上面 @Value 却绿了。Git 回滚之后,顶栏还挂着 「2 files pending」,按钮还能点。

技术翻译过来就是:插件里缓存了一套 hunk(ReviewSession),Git 里 BASE 没动,编辑器又在变 。保存、VCS 刷新、捕获线程随时 replacePendingHunksForFile 整表替换------四套坐标(Git BASE、session baseline、pending hunks、Document)各说各话。

台账上 1.0.77--1.0.87 没有一次完整验收 。那时得到的结论很硬:不是再写一行 extendToCommentClose 能救的,是状态机错了。

此阶段没有 npm 包;问题已经够痛,但还没意识到「加一层 Node」也救不了状态机。


时代二:为什么又做成「npm + IDEA 插件」------「hook 必须在盘外拦住 AI」(v2 / 2.0.x)

一次典型的终端 Claude 写文件

AI 在终端里调 Edit / Write,磁盘上的 Foo.java 还没变------若只靠在 IDEA 里听 VFS,往往已经晚了:字都写进去了,diff 和按钮才冒出来,体验像「事后诸葛亮」。

当时的想法(很合理)

  1. Node 里挂 Claude Code 的 PreToolUse / PostToolUse(生态里 hook 就是 JS/TS 最成熟)。
  2. 在 AI 落盘前 解析 old_string / new_string,拼好 changes[]newFileContent
  3. cc-gui-bridge 在进程外算 zones、记 bridge.db,经 :9876 POST 给插件里的 ChangeReceiver
  4. 甚至可选 CC_GUI_BRIDGE_BLOCK=1 :先弹 diff tab,你 Deny 了,hook exit 2 直接拦住写盘------这是 jetbrains-cc-gui 式审阅,不是 Cursor 内联,但「能拦住 AI」很诱人。

于是安装说明变成两段咒语

bash 复制代码
cd cc-gui-bridge && npm install && npm link && cc-gui-bridge init
cd cc-gui-review && ./gradlew buildPlugin
# 还要对齐 hook 超时与 block 超时,否则 AI 卡死或审查永远不出现

我们以为赚到的

  • 终端 CC 与 IDEA 解耦:桥挂了,插件只收 HTTP。
  • diff / zone 合并可以在 TS 里快速迭代(display-zones.ts 对标 Cursor)。
  • bridge.db 能「记住」一轮 Agent 的 hunks。

没算进去的

团队日常主战场是 CC GUI 直接写盘 ------没有 PreToolUse,只有磁盘字节在变。桥再漂亮,GUI 路径仍要靠 VCS + DocumentListener 硬啃。等于造了两条入口,却只想修其中一条。


时代三:npm + IDEA 并行------「到底信谁?信 bridge 还是信 IDEA?」

这一阶段最折磨人的不是某个 bug,而是认知分裂:同一个文件,你要同时相信三份东西。

场景 1:早上开机,审查 UI 全没有

同事问:「插件坏了吗?」你查了半天------:9875 daemon 没起 ,或 hook 没 init ,或 9876 被占用 。日志一个在 ~/.cc-gui-bridge/logs/,一个在 ~/logs/cc-gui-review-3.0.xx/。两边都「正常」,合在一起就是不正常

场景 2:按钮在,点下去没反应

日志像讲故事:

复制代码
resolveBridgeHunkId MISS synthetic=i-421106772-40-41 zone=L41-42
APPROVE_FAIL ... msg=hunk not found

屏幕上 live diff 合并出的 zone 是 L41--42 ,capture 时 store 里记的 hunk 却是 L42--42 。TS 算 zones、Kotlin 再 HunkZoneMapper 对一遍------同一块改动,两个坐标系 。你点 Keep,插件在找一把叫 i-xxx 的钥匙,仓库里根本没有这把锁。

场景 3:CC GUI 写完了,你在看 A,顶栏却在说 B

桥的设计是 pre_write 优先 ;GUI 是 写完后 CLM 才 modified 。为了让它俩不打架,插件里不断加 suppress、grace、open-not-in-bridge、gate-ready------每加一条,就多一种「为什么这次又不一样」。

场景 4:关 IDEA 前你还没审完,重启后世界变了

旧 npm 路径曾在关工程时 keepAllInProject() ------像帮你点了全体 Accept。删 bridge 之后,又要争论 AUTO_KEEP_ALL_ON_IDE_LIFECYCLE 到底是 true 还是 false (代码与文档还曾不一致)。重启 IDEA 不该复活昨天的 pending ,却曾出现 「2 files pending」顶栏复活 ------用户只会觉得:「这插件在擅自替我做完决定。」

更扎心的一点

就算 bridge 把 hunks 传得完美1.0.77--1.0.87 那套 session 架构债仍在 。绿线闪回、Git 回滚残留------npm 解决的是「数据从哪进 IDEA」 ,没解决 「进了 IDEA 之后,块级同意怎样推进 baseline」

一句话总结这个时代:

我们多养了一个进程、两份 store、两种 diff 实现,却还是没有单一真相。


时代四:为什么又收束成「只信 IDEA 插件」------「把桥拆掉,把脑子放进 Kotlin」(1.0.88 → v3 → 3.0.3)

这不是「npm 失败了所以赌气删包」,而是想通了两件事

  1. 块级审阅的权威状态必须在 IDEA 进程内------跟 Document、CLM、EDT 同生共死。
  2. 展示必须每次从 effectiveBaseline 现算------像官方 Diff 一样,不能缓存「展示用 hunk」当真相。

1.0.88(2026-05)------第一次敢动大手术

砍掉 replacePendingHunksForFile 那条老路,上 LiveDiffModel 。用户点 Accept,推进的是 effective ,不是改 session 里某个列表项。那一刻团队情绪是:「终于对准 Cursor 的语义了。」 npm 包还在仓库里,但心里已经降级成 legacy 转发

v3 / 3.0.0------名字还带 bridge,热路径已不是 bridge

IdeaReviewStore 掌权;:9875 的 display/decide HTTP 下线BridgeReviewStore 只剩 UI 索引------名字是历史,别被名字骗

3.0.1 FileWriteGate------为 CC GUI 流式写盘量身定做

AI 一秒改十行时,若在 WRITING 就画绿线,你会看到按钮疯狂闪烁 ,像坏掉的霓虹灯。我们定了 600ms 文档静默 + 「别的文件开始写了 → 当前文件算写完」 的组合门控(见 §5.1)。这是 纯插件时代 最像「懂 CC GUI」的设计------桥时代没有这条灵魂规则

3.0.3(2026-06-01)------物理删除 cc-gui-bridge/

scripts/build-review.sh 成为唯一入口;:9876 默认关,除非 CC_GUI_REVIEW_HTTP=1。安装回归一句话:卸载旧 zip → 重启 → 装新 zip → 再重启。不再对新人解释 npm link。

附录 G 里那张表,是给审计用的尸检报告:TS 的每一行逻辑,在 Kotlin 里都有了墓地上的墓碑。


时代五:纯插件冲刺到顶楼,然后停工(3.0.4 -- 3.0.63 → 停更)

拆掉 npm 之后,以为战场会缩小------实际上战场只是从「两个进程」缩成「一个进程里的五套坐标」

高光:3.0.61

AI 改完 LogQueueSimpleRabbitListener,你打开文件:绿底有了,块按钮有了 。原话:「这个版本非常好了。」 团队真的以为进入「只修边角」------顶栏 SFN、gate-ready 补种、ensureCaptureOnGateReady,像给房子补防水涂层。

急转:3.0.63 与回滚

为修「Accept 连点没反应」,上了 reconcile / stale-inline 策略。你反馈:红删没了 ------日志却老实写着 APPLY_DECOR ... red=5算法说有,眼睛说没有------这比「 outright 坏了」更让人绝望:你不知道该信日志还是信屏幕。

回滚到 3.0.61 同一份产品逻辑后,行号/绿线又不对了 。同 tag、不同天、不同 tab 顺序------稳定感没有回来

停工那句话

不弄了。以后不用 IDEA 做这条「块级内联审阅」路线。

不是恨 IDEA,是承认:在 Platform API + Git CLM + CC GUI 写盘 + 块级状态机 的夹缝里,我们已经把能想的招都试过了------npm 加过、删过;live-diff 换过;3.0.x 打了六十多个版本;1000 条矩阵跑 34 分钟仍红。再往下修,是在用人生换一条 Cursor 已经免费给你的体验。


路线试错:一张表收束(给赶时间的人)

时代 我们以为的问题 实际学到的事
纯插件 1.0.x diff 算法 / Javadoc extend 状态机错了(session 缓存)
+npm v2 hook 与 pre_write 不够快 多进程没带来单一真相
npm 并行 TS 与 Kotlin 对齐就行 CC GUI 主路径与 bridge-first 根本拧巴
收回 3.0.x 删 bridge 就能稳 IDEA 内仍有 zone/hunk/CLM 漂移
3.0.61→停更 再修一两个 bug 「非常好」与「又不对了」可以同时成立

第一阶段:1.0.x 与「四套状态」------像四个人各记一本账

想象四个人同时记「这份文件改到哪了」:

记什么 相信什么
Git beforeRevision(BASE) 上次提交
ReviewSession 缓存的 CodeBlockChange 插件自己的 pending 列表
session baseline 某次 capture 的快照 往往和 effective 脱节
编辑器 Document 你眼前正在滚动的字 实时真相

你点「同意」一块,往往只改了 session 里的一条记录 ,Git BASE 纹丝不动 。接着你来了一次保存、VCS 刷新、或 CC 又写了一行------捕获线程 replacePendingHunksForFile 整表替换 ,刚才你同意过的块又从列表里长回来了

用户侧就是:

  • 同意一块后绿线 闪回(「我明明点过了!」);
  • 工具条说 L72,绿线铺在 L65(「按钮和线不在一块!」);
  • Javadoc 的 */ 漏绿(「注释块没审全!」);
  • Git 回滚了,顶栏 还在(「我都 rollback 了你怎么还在?」)。

台账上 1.0.77--1.0.87 没有一次完整验收 。那时得到的结论很硬:不是再写一行 extend 能救的,是架构错了。

第二阶段:1.0.88 live-diff + v3 收束(重燃希望,npm 此阶段退场)

1.0.88 在插件内改为 effectiveBaseline + 每次现算 LiveDiffModel ,Accept 推进 baseline ------ 先解决「四套状态」根因(见上 时代四)。

v3 / 3.0.x 再把权威状态收到 IdeaReviewStore3.0.3 删除 cc-gui-bridge npm 包3.0.1FileWriteGate (600ms 写稳再 UI),终端 CC 与 CC GUI 统一 Git CLM + VCS 捕获

第三阶段:3.0.50--3.0.61(最接近成功)

  • 3.0.54 / 3.0.55SessionStoreDriftPolicy 等改动引入回归 → 整批回滚
  • 3.0.57--3.0.58:修 Reject 清全文件红删、partial Accept 后红删消失。
  • 3.0.59--3.0.60:修多文件 orphan-accept、顶栏 SFN 残留。
  • 3.0.61(2026-06-02) :用户原话 「这个版本非常好了」 --- AI 写完后 LogQueue 有绿底和块按钮ensureCaptureOnGateReady + gate-ready 绕过 8s suppress)。

以为终于站稳。

我们在这条路上做过什么(努力清单)

这不是「试了两三天就放弃」的小实验,而是长期、可审计的工程投入。下面按类别记一笔,方便后人判断值不值得 fork。

类别 做了什么 规模感
产品定义 从右侧卡片审查否定,到 100% 内联;整理 USER_REQUIREMENTS、顶栏 SFN 规则、与 Cursor 差异 需求文档 + 本篇 §2
路线试错 纯 IDEA → npm+插件(v2) → 双真相/双端口问题 → 收束回纯插件(3.0.3 删 bridge) 见上文「技术路线演进」
架构换代 1.0.x 四套状态失败 → 1.0.88 live-diff + IdeaReviewStore 整仓重构,非补丁
CC GUI 适配 FileWriteGate (600ms 写稳)、gate-ready 补种、ensureCaptureOnGateReady、CC 占焦点时的 NO_FOCUS 补刷(不抢焦点) 3.0.x 主线
代码体量 INGEST / STATE / SHIM / DISPLAY / UI 分层,约 72 个 Kotlin 源文件 非 demo 插件
版本台账 每个小版本改了什么、验没验过 --- VERSION_HISTORY.md 900+ 行 ,3.0.x alone 60+ 个版本 可 grep 追溯
日志体系 每装一版独立目录 ~/logs/cc-gui-review-<version>/RENDER / GATE / APPROVE / HEADER 等同构关键字 3.0.61 vs 3.0.63 对账靠它
自动化 场景夹具、ReviewMatrix1000Catalog 1000 条 纯逻辑路径;全量跑约 34 分钟 (4 测例里仍 1 失败 兜不住 IDE 目视
发版纪律 pre-install-check.sh、release-governance、安装前要求 卸载→重启→装 zip→再重启 减少「同版本缓存」误判
人工验收 反复用 LogQueueSimpleRabbitListenerApiJobApiApplication 做 Javadoc / 多文件 / 连点 Accept 用户原话驱动
回滚与诚实 3.0.54/55 整批回滚 ;3.0.63 试修「点不动」后用户要 回滚 3.0.61;未验过不说「改好了」 避免假稳定

#mermaid-svg-w7lzrDnawyE5Ls5v{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w7lzrDnawyE5Ls5v .error-icon{fill:#552222;}#mermaid-svg-w7lzrDnawyE5Ls5v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w7lzrDnawyE5Ls5v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w7lzrDnawyE5Ls5v .marker.cross{stroke:#333333;}#mermaid-svg-w7lzrDnawyE5Ls5v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w7lzrDnawyE5Ls5v p{margin:0;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge{stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .section--1 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section--1 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section--1 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section--1 text{fill:#ffffff;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth--1{stroke-width:17;}#mermaid-svg-w7lzrDnawyE5Ls5v .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:#ffffff;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-0 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-0 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-0 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-0 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-0{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-0{stroke-width:14;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-1 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-1 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-1 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-1 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-1{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-1{stroke-width:11;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-2 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-2 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-2 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-2 text{fill:#ffffff;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-2{stroke-width:8;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:#ffffff;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-3 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-3 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-3 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-3 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-3{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-3{stroke-width:5;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-4 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-4 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-4 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-4 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-4{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-4{stroke-width:2;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-5 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-5 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-5 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-5 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-5{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-5{stroke-width:-1;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-6 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-6 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-6 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-6 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-6{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-6{stroke-width:-4;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-7 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-7 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-7 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-7 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-7{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-7{stroke-width:-7;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-8 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-8 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-8 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-8 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-8{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-8{stroke-width:-10;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-9 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-9 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-9 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-9 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-9{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-9{stroke-width:-13;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-10 rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-10 path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-10 circle,#mermaid-svg-w7lzrDnawyE5Ls5v .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-10 text{fill:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .node-icon-10{font-size:40px;color:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .edge-depth-10{stroke-width:-16;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-w7lzrDnawyE5Ls5v .lineWrapper line{stroke:black;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled circle,#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:lightgray;}#mermaid-svg-w7lzrDnawyE5Ls5v .disabled text{fill:#efefef;}#mermaid-svg-w7lzrDnawyE5Ls5v .section-root rect,#mermaid-svg-w7lzrDnawyE5Ls5v .section-root path,#mermaid-svg-w7lzrDnawyE5Ls5v .section-root circle{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-w7lzrDnawyE5Ls5v .section-root text{fill:#ffffff;}#mermaid-svg-w7lzrDnawyE5Ls5v .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-w7lzrDnawyE5Ls5v .edge{fill:none;}#mermaid-svg-w7lzrDnawyE5Ls5v .eventWrapper{filter:brightness(120%);}#mermaid-svg-w7lzrDnawyE5Ls5v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 纯插件 1.0.0--1.0.87 内联 UI + VCS 捕获 + session 架构失败 npm加插件 v2 cc-gui-bridge hooks + :9875/:9876 + bridge.db 收回插件 1.0.88 live-diff IdeaReviewStore 权威 3.0.3 删除 npm 包 纯插件冲刺 3.0.50--3.0.61 FileWriteGate + 顶栏 SFN 顶峰与放弃 3.0.61 用户「非常好了」 LogQueue 有绿有钮 3.0.63 试修 → 回滚 行号又错 → 停更 cc-gui-review 投入时间线(摘要)

一度最接近成功的时刻 :2026-06-02,3.0.61 --- AI 写完后 LogQueue 有绿底和块按钮,维护者以为可以进入「只修边角」阶段;随即 3.0.62/63、红删争议、行号再次错位,主观体验回到「又不对了」,投入产出比击穿心理底线。

第四阶段:再次崩盘与放弃

时间线 发生了什么
3.0.62 只加 1000 矩阵测试与文档,产品同 3.0.61
3.0.63 修「连续 Accept 点不动」;用户反馈 红删不显示 (日志却有 red=5
回滚 master → 3.0.61;3.0.63 撤销
复测 行号/绿线又标不对 ;用户:不弄了,以后不用 IDEA(这条路线)

最后为什么还是放弃了(心声式归纳)

先澄清一件事 :上文「努力清单」不是自我感动。版本台账 900+ 行72 个 Kotlin 源文件、1000 条矩阵、六十多个 3.0.x 小版本------数字都在。放弃不是因为「懒得做」,是因为做到 3.0.61 仍无法向自己保证「明天还一样好」

那种「差一点就对了」的折磨

3.0.61 那天,LogQueue 上绿底和块按钮都在,你说 「这个版本非常好了」 。团队心里松了一口气:npm 拆了、live-diff 上了、FileWriteGate 接上了 CC GUI------路线是对的

可紧接着:

  • 3.0.63 想修「Accept 连点没反应」,你却发现 红删不见了 ;日志却写 red=5------像医生指着 CT 说「有病灶」,你睁眼却看不见。
  • 回滚 到 3.0.61 的同一份产品逻辑 ,行号/绿线又错了 。不是「新版本坏了」,是 「同一版 zip,不同打开顺序、不同 tab、不同写盘节奏,结果不同」

这比「一直烂」更耗人:你会反复问自己------是不是我操作不对?是不是又装错 zip?是不是 CC GUI 又占焦点了? 日志能 grep 出答案,业务方只要一句:「到底能不能用?」 你答不出

五句「放弃原因」

1. 我们要的,比 IDEA 官方难一整档

官方 Local Changes:整文件 BASE vs CURRENT,没有 「同意这一块、拒绝那一块」。我们要:块级决策 + 只绿真插入 + 红删 inlay + 多文件 SFN 顶栏 + AI 写盘中别闪按钮 。这是在 Git 文件模型 上硬叠 Cursor 产品------不是官方设计边界里的事。

2. 状态机像五条绳子拽一个木偶

就算删了 npm,仍同时存在:Session 块、IdeaReviewStore hunk、LiveDiff zone、Git CLM、编辑器 Document 。修 orphan-accept,顶栏 SFN 又错;修 gate-ready,8s suppress 又挡 capture;修 stale-inline,绿线又被误清。修 A 几乎必然碰 B------3.0.54/55 整批回滚就是血淋淋的例子。

3. 版本号在飞,信心追不上

3.0.x 六十多个 小版本,很多是「看你昨晚日志 里一行 APPROVE_FAIL 就发一版」。发版纪律再严,你也无法对同事说「装 3.0.61 就一劳永逸」 ------因为你自己经历过 同 tag 复测仍错

4. 自动化在鼓掌,眼睛在摇头

1000 矩阵全量 跑 34 分钟,仍 1 失败 。子集能绿,挡不住 「LogQueue 上目视不对」。测试证明的是模拟器里的管线 ;你要的是CC GUI 写盘 + 你切 tab + 你点 Keep 那一刻------鸿沟一直在

5. 机会成本终于算不过账

同一批改动,在 Cursor 里审阅已经顺手。在 IDEA 里维护 72 个文件 + Platform API + CLM 时序 ,是为了一条 「非用 IDEA 不可」 的路径------而你们已经说了:以后不用 IDEA 走这条路线 。继续烧版本,烧的是业务代码的时间

停更决定(维护者口径,2026-06-02 前后):

不弄了。以后不用 IDEA 做这条「块级内联审阅」路线。仓库保留 3.0.61、博客与测试,供查阅;日常改码审阅用 Cursor / CC GUI 自带 diff ,或 IDEA 官方整文件 Diff

这不是否定 IDEA,也不是否定那几十天的通宵。是承认:在你要的体验与 Platform 能稳定给的体验之间,我们已经把桥搭了、拆了、换了 live-diff、打了 3.0.61 高光------仍然填不平那条缝。

放弃的直接原因(技术摘要,给 fork 的人)

# 原因 若要继续 fork,意味着什么
1 块级 + 真绿 + inlay 红删 + 多文件 + 写盘门控 先接受「比官方 Diff 难一个数量级」
2 zone / hunk / session / Git 漂移 需要单一坐标系设计,不是再加 reconcile
3 3.0.x 高密度迭代仍挡不住组合爆炸 需要可复现的 IDE 级 E2E,不是只有纯逻辑矩阵
4 日志 red=5 与屏幕不一致 需要 inlay/markup EDT 层测试,不是只测 LiveDiffModel
5 Cursor 侧体验已够用 产品上要回答「为什么非 IDEA 不可」

以后怎么走(维护者口径)

  • 不再以「在 IDEA 里复刻 Cursor」为目标继续发版。
  • 仓库保留:3.0.61 标签、本篇博客、测试与版本史,供以后查阅或 fork。
  • 若团队审阅:优先用 Cursor / CC GUI 自带 diff ,或 IDEA 官方整文件 Diff,不再依赖本插件。

当前仍存在的各种问题(停更时快照)

以下问题在回滚 3.0.61 后 并未宣称全部解决;用户停更时主观感受是「又不对了」。

类别 现象 技术原因(摘要)
行号 / 绿线 绿线标在行尾、块边界错、未改行偶发绿 zone 行号(live)≠ hunk 行号(capture);trulyInserted 与整段 fallback 历史债
红删 日志 red=5 但界面无红;或 Accept 后红删全没 inlay 未挂上屏 / clearDeleteInlays / pure-insert 推进 baseline 收掉配对红
块按钮 点 Accept 无反应、APPROVE_FAIL hunk not found HunkZoneMapper 匹配失败;UI zone L65 与 store hunk L43 漂移
顶栏 审完仍 SFN、或该有时没有 hasReviewablePending vs session 计数;stale header 缓存
多文件 A 文件 Accept 后 B 就绪时 A 被清光 曾误 orphan-accept(3.0.59 修过,组合仍脆)
写盘中 曾无按钮/无绿(3.0.60 前) store 未 seed;timed-until 挡 capture(3.0.61 修)
写完后 又出现行号不对 回滚后仍属架构层漂移,非单点回归
Git Rollback 后顶栏/绿线残留 历史多次报告;清 session 依赖快捷键/流程
性能 applyDecorations SLOW 100--200ms EDT 全量重绘 LogQueue 等大文件
测试 1000 矩阵未全绿 模拟器与真实 IDE 仍有鸿沟

用户验收表(诚实) --- 见 [附录 C](#附录 C);完整需求条文见 §2。勿把 3.0.61 理解成「所有项 ✅」。


目录

  • 从入门到放弃
  • 当前仍存在的各种问题
  • [0. 项目到底有多大](#0. 项目到底有多大)
  • [1. 需求背景:我们要造什么](#1. 需求背景:我们要造什么)
  • [技术路线:纯 IDEA → npm+插件 → 收回插件 → 放弃](#技术路线:纯 IDEA → npm+插件 → 收回插件 → 放弃)
  • [从入门到放弃 · 努力清单与停更决定](#从入门到放弃 · 努力清单与停更决定)
  • [2. 用户设定的产品需求(与 Cursor 的差异)](#2. 用户设定的产品需求(与 Cursor 的差异)) --- 启动/重启、顶栏 SFN、[2.10 交互禁忌](#启动/重启、顶栏 SFN、2.10 交互禁忌)
  • [3. 架构演进:从四套状态到 live-diff](#3. 架构演进:从四套状态到 live-diff)
  • [4. 当前架构(v3)全景](#4. 当前架构(v3)全景)
  • [5. 核心原理透明化](#5. 核心原理透明化)
  • [6. 技术栈与依赖](#6. 技术栈与依赖)
  • [7. 与 Cursor 的对比(总表)](#7. 与 Cursor 的对比(总表))
  • [8. 与 IntelliJ IDEA 官方的对比](#8. 与 IntelliJ IDEA 官方的对比)
  • [9. 版本战史](#9. 版本战史)
  • [10. 真实日志案例](#10. 真实日志案例)
  • [11. 测试体系](#11. 测试体系)
  • [12. 仍未解决的问题](#12. 仍未解决的问题)
  • [13. 后续路线与纪律](#13. 后续路线与纪律)
  • [附录 C:验收状态表](#附录 C:验收状态表)

0. 项目到底有多大

这不是「给 IDEA 加两个按钮」的小插件。

维度 数量级
主代码 ~72 Kotlin 源文件,分层 INGEST / STATE / SHIM / DISPLAY / UI
版本台账 VERSION_HISTORY.md 900+ 行 ,3.0.x alone 就迭代了 60+ 个小版本
自动化 场景夹具 + ReviewMatrix1000Catalog 1000 条 人为点击路径(全量跑约 33 分钟
日志 每次安装版本独立目录 ~/logs/cc-gui-review-<version>/
历史架构 1.0.77--1.0.87 多轮未验收;1.0.88 切 live-diff;3.0.x 接 CC GUI + FileWriteGate

一句话 :我们在 Git 文件模型 之上,硬做了一套 Cursor 块级审阅 ,还要接 AI 流式写盘IntelliJ 官方 diff 语义 --- 三者天然不在一个粒度上。


1. 需求背景:我们要造什么

1.1 业务场景

  1. Claude Code(终端)CC GUI(IDE 内) 修改 Java 工程(如 eb-service-api)。
  2. 一次任务常改 多个文件 (例如 ApiJobApiApplication.java + LogQueueSimpleRabbitListener.java)。
  3. 用户希望在 当前编辑器 里:
    • 看到 绿增 (真正新加的行)、红删(被替换掉的旧行);
    • 每一块Reject | Keep(或 拒绝 | 同意);
    • 用顶栏 Reject All / Keep All / Review Next File 收尾。

1.2 明确否定的方案

方案 用户态度
右侧 ToolWindow 卡片列表 + View Diff 弹窗 ❌ 早期试过,明确不要
只靠 Git 提交前整文件 diff ❌ 无法块级决策
手写 Myers diff 算法 ❌ 要求用 IDEA ComparisonManager

1.3 需求契约(摘录)

完整需求与验收状态见本文 「附录:用户需求与验收状态」。核心几条:

ID 需求 难点
R-UI 按钮锚定在块侧,不跟鼠标;滚出视口隐藏 Editor inlay + 自研 hit-test
R-DIFF 未改行绝不能绿@Value、空行、下移复制行) 官方 diff 标的是 MODIFY 整块
R-DEL 删除行可见(红) 要用 inlay,不能污染 document
R-HEADER 当前文件无 pending → 不显示 Reject/Keep;其它文件有 pending → Review Next hasReviewablePending vs session 计数
R-GATE AI 流式写时不要满屏闪;写完后当前 tab 必须有绿底+按钮 FileWriteGate + gate-ready 补种
R-ACCEPT Accept 后该块按钮不再出现;绿线不无故闪回 effective 推进 + 禁错误 reconcile

1.4 用户原话驱动的验收

  • 3.0.61 :「这个版本非常好了」--- AI 写完后 LogQueue 等文件有块按钮和绿底
  • 红删 :用户反馈 3.0.61 截图里 L110/L112 一带有红删+绿增 ;3.0.63 同场景「什么都没点就看不见红」--- 属 显示层 争议,已回滚产品逻辑到 3.0.61。

2. 用户设定的产品需求(与 Cursor 的差异)

本节把你们明确要求、且与 Cursor 默认行为不一致 的设定写全,便于后人理解「为什么 IDEA 插件比想象中复杂」。实现以代码为准(ReviewSessionHeaderBarReviewStartupFileWriteGate 等)。

2.1 与 Cursor 的第一性差异

维度 Cursor / CC GUI 内建 我们(cc-gui-review)
宿主 Cursor 编辑器闭源 UI IntelliJ IDEA + Platform API
审阅时机 模型流式更新 diff 写稳门控 600ms 后才出块按钮
启动工程 无「重启后旧 session 复活」问题 必须 清 session,禁止 startup 误补种
顶栏 无等价五钮状态机 SFN / -F- / S-N 三档组合
块按钮文案 Undo / Keep Reject / Keep(或中文 拒绝/同意)
块按钮位置 产品内嵌 锚定块侧,不跟鼠标;Settings 模态时隐藏
Git 弱耦合 CLM + disk vs git 驱动捕获
关 IDE 无「关 IDE 自动 Accept 全项目」争议 曾要求自动 Keep All,后改为 默认不自动(见下)

2.2 IDEA 启动、重启、关工程(生命周期需求)

这是 Cursor 完全没有的一类需求:必须定义「打开工程 / 重启 IDEA / 关闭 IDE」时审查状态怎么办。
#mermaid-svg-OFswZIy2JXmkj6nx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OFswZIy2JXmkj6nx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OFswZIy2JXmkj6nx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OFswZIy2JXmkj6nx .error-icon{fill:#552222;}#mermaid-svg-OFswZIy2JXmkj6nx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OFswZIy2JXmkj6nx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OFswZIy2JXmkj6nx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OFswZIy2JXmkj6nx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OFswZIy2JXmkj6nx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OFswZIy2JXmkj6nx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OFswZIy2JXmkj6nx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OFswZIy2JXmkj6nx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OFswZIy2JXmkj6nx .marker.cross{stroke:#333333;}#mermaid-svg-OFswZIy2JXmkj6nx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OFswZIy2JXmkj6nx p{margin:0;}#mermaid-svg-OFswZIy2JXmkj6nx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OFswZIy2JXmkj6nx .cluster-label text{fill:#333;}#mermaid-svg-OFswZIy2JXmkj6nx .cluster-label span{color:#333;}#mermaid-svg-OFswZIy2JXmkj6nx .cluster-label span p{background-color:transparent;}#mermaid-svg-OFswZIy2JXmkj6nx .label text,#mermaid-svg-OFswZIy2JXmkj6nx span{fill:#333;color:#333;}#mermaid-svg-OFswZIy2JXmkj6nx .node rect,#mermaid-svg-OFswZIy2JXmkj6nx .node circle,#mermaid-svg-OFswZIy2JXmkj6nx .node ellipse,#mermaid-svg-OFswZIy2JXmkj6nx .node polygon,#mermaid-svg-OFswZIy2JXmkj6nx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OFswZIy2JXmkj6nx .rough-node .label text,#mermaid-svg-OFswZIy2JXmkj6nx .node .label text,#mermaid-svg-OFswZIy2JXmkj6nx .image-shape .label,#mermaid-svg-OFswZIy2JXmkj6nx .icon-shape .label{text-anchor:middle;}#mermaid-svg-OFswZIy2JXmkj6nx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OFswZIy2JXmkj6nx .rough-node .label,#mermaid-svg-OFswZIy2JXmkj6nx .node .label,#mermaid-svg-OFswZIy2JXmkj6nx .image-shape .label,#mermaid-svg-OFswZIy2JXmkj6nx .icon-shape .label{text-align:center;}#mermaid-svg-OFswZIy2JXmkj6nx .node.clickable{cursor:pointer;}#mermaid-svg-OFswZIy2JXmkj6nx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OFswZIy2JXmkj6nx .arrowheadPath{fill:#333333;}#mermaid-svg-OFswZIy2JXmkj6nx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OFswZIy2JXmkj6nx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OFswZIy2JXmkj6nx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OFswZIy2JXmkj6nx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OFswZIy2JXmkj6nx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OFswZIy2JXmkj6nx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OFswZIy2JXmkj6nx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OFswZIy2JXmkj6nx .cluster text{fill:#333;}#mermaid-svg-OFswZIy2JXmkj6nx .cluster span{color:#333;}#mermaid-svg-OFswZIy2JXmkj6nx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OFswZIy2JXmkj6nx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OFswZIy2JXmkj6nx rect.text{fill:none;stroke-width:0;}#mermaid-svg-OFswZIy2JXmkj6nx .icon-shape,#mermaid-svg-OFswZIy2JXmkj6nx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OFswZIy2JXmkj6nx .icon-shape p,#mermaid-svg-OFswZIy2JXmkj6nx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OFswZIy2JXmkj6nx .icon-shape .label rect,#mermaid-svg-OFswZIy2JXmkj6nx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OFswZIy2JXmkj6nx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OFswZIy2JXmkj6nx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OFswZIy2JXmkj6nx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 关闭 IDE / 关工程
true
false
onProjectLifecycleShutdown
AUTO_KEEP_ALL_ON_IDE_LIFECYCLE

代码常量 = true
阻塞 keepAllBridgeProjectBlocking

全项目 accept_all
仅 clearAll

磁盘不变,重启可再 capture
打开工程 / Restart IDEA


PluginStartupActivity
resetOnProjectOpen
clearAll session + UI
90s 被动 VCS 抑制

startup-passive-suppress
5s capture grace

挡 changelist 风暴
CLEAR_SESSION_ON_PROJECT_OPEN

当前 = true
跳过 startup-initial

不对 CLM 旧改动补种
grace 后可能 Keep All

(历史行为)

2.2.1 打开工程 / 重启 IDEA(你们要的)
需求 说明 实现要点
重启后不要假 pending 上次审完或关 IDE 后,重启不应立刻出现「2 files pending」+ 全套顶栏 resetOnProjectOpenBridgeReviewStore.clearAll() + 清绿线/顶栏
不要 startup 补种 reset 后 ~5s 不要用 CLM 里已有 modifiedstartup-initial,否则顶栏 SFN「复活」 CLEAR_SESSION_ON_PROJECT_OPEN = true,跳过 startup-initial
90s 被动抑制 打开工程后一段时间内,切 tab / open-not-in-bridge 不被动 VCS 补种 STARTUP_PASSIVE_CAPTURE_SUPPRESS_MS = 90_000
5s 捕获宽限 工程打开后 5s 内 suppressCaptureFor,避免 changelist 风暴 STARTUP_CAPTURE_GRACE_MS = 5000
新 AI 写盘仍要审 抑制只挡「旧改动」;document-cold / 新 CLM newlyModified 仍走正常 capture 日志 gate-ready / GATE_READY_SEED

与 Cursor 对比 :Cursor 不管理「Git 里已 modified 的文件在重启后是否自动进审阅」;我们在 IDE 工程生命周期 上显式清状态。

2.2.2 关闭 IDE / 关工程(曾争论、后定型)
阶段 用户诉求 当前代码倾向
早期 3.0.20 每次启动/关闭/重启 IDEA 自动 Keep All AUTO_KEEP_ALL_ON_IDE_LIFECYCLE
3.0.15 后 不要 关 IDE 自动 Accept;重启后绿块由 VCS 正常出现,须手动 Keep 文档写 AUTO_KEEP_ALL=false注意ReviewStartup.kt 里常量仍为 true,以仓库代码为准,安装前应用 grep 确认
关 IDE 不丢盘 Keep All 只改内存 effective,不要求关 IDE 时写盘 shutdown 可 keepAll 或仅 clear

验收期望(用户原话)

  • 重启 IDEA → 不应残留上次「2 files pending」除非磁盘上真有新 AI 改动。
  • Git Rollback → 顶栏/绿线/按钮 全部清除(此项长期 ⚠️❌)。
  • Cmd+Shift+KCtrl+Shift+K)可 强制清 session(应急)。
2.2.3 安装 / 升级插件
步骤 要求
打包 ./gradlew buildPlugincc-gui-review-x.y.z.zip
安装 Settings → Plugins → Install from Disk
升级 卸载旧版 → Restart → 装新 zip → 再 Restart(避免同版本缓存)
验证 日志 [STARTUP] version=x.y.z 与 zip 文件名一致

2.3 编辑器顶栏:按钮、文案与状态(核心差异)

顶栏在编辑器右上角 ,由 ReviewSessionHeaderBar 绘制。按钮可见性由三个布尔量组合,日志里叫 flags ,模式串如 SFN-F-S-N

含义 为真时显示的按钮
S (session) showSessionActions Reject All · Keep All
F (file) showFileActions Reject · Keep(当前文件)
N (next) showReviewNext Review Next File

- 表示该位不显示。例:SFN = 五个按钮全显示;-F- = 仅当前文件 Reject | Keep。

2.3.1 你们规定的显示逻辑(原需求原文)

1)Review Next File(查看下一个文件)

  • 显示条件 :有没有其他文件 还有待审(与当前文件是否审完无关)。
  • 顺序:按文件顺序跳转,不是随便挑一个。
  • 不显示 :其它文件都没有 pending 时,不出现此按钮。

2)Reject All / Keep All(需求文档曾写「撤销所有 / 保存所有」)

  • 产品实现文案为 Reject All / Keep All(语义:整 session 拒绝 / 整 session 接受)。
  • 显示条件 :当前文件还有待审改动块时,与 session 级条件组合(见下表)。
  • 当前文件块全部处理完 → 不显示 Reject / Keep(-file 位为假),通常进入 S-N(只剩 All + Next)。

3)当前文件处理完后

  • 若还有其它文件待审 → 可 Review Next File
  • 若没有了 → 顶栏干净,无多余按钮,绿线/红删也应消失。
2.3.2 顶栏三档对照表(实现 ReviewSessionHeaderBar 注释)
session 其它文件仍有 pending 当前文件有未确认块 顶栏显示
Reject | Keep (2 钮,-F-
Reject All | Keep All | Reject | Keep | Review NextSFN
Reject All | Keep All | Review NextS-N,当前文件已审完)
无顶栏---

与 Cursor:Cursor 无「多文件 session + 五钮顶栏」;只有块级 Undo/Keep 与全局流程。

2.3.3 顶栏状态如何计算(为何常出 bug)

ReviewSessionState.computeHeaderFlags + BridgeReviewStore.hasReviewablePending 共同决定,不是单纯数 session 里 hunk 个数:

输入 作用
filePhase PENDING / CONFIRMED / NONE
hasReviewablePending(path) live diff 是否还有可审 zone(3.0.60:勿用 store pending alone)
writeGate.isUiReady(path) 文件须 READY(写稳),WRITING 时不显示 -F-
batchScopeCount >= 2 本会话多文件 → 倾向显示 S
gateSessionReadyPending 其它文件 gate READY 但 nav 未映射时的兜底
otherPendingInSession 其它路径 disk 上真有 pending

#mermaid-svg-yExzsUwAWMFyciaT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yExzsUwAWMFyciaT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yExzsUwAWMFyciaT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yExzsUwAWMFyciaT .error-icon{fill:#552222;}#mermaid-svg-yExzsUwAWMFyciaT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yExzsUwAWMFyciaT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yExzsUwAWMFyciaT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yExzsUwAWMFyciaT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yExzsUwAWMFyciaT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yExzsUwAWMFyciaT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yExzsUwAWMFyciaT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yExzsUwAWMFyciaT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yExzsUwAWMFyciaT .marker.cross{stroke:#333333;}#mermaid-svg-yExzsUwAWMFyciaT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yExzsUwAWMFyciaT p{margin:0;}#mermaid-svg-yExzsUwAWMFyciaT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yExzsUwAWMFyciaT .cluster-label text{fill:#333;}#mermaid-svg-yExzsUwAWMFyciaT .cluster-label span{color:#333;}#mermaid-svg-yExzsUwAWMFyciaT .cluster-label span p{background-color:transparent;}#mermaid-svg-yExzsUwAWMFyciaT .label text,#mermaid-svg-yExzsUwAWMFyciaT span{fill:#333;color:#333;}#mermaid-svg-yExzsUwAWMFyciaT .node rect,#mermaid-svg-yExzsUwAWMFyciaT .node circle,#mermaid-svg-yExzsUwAWMFyciaT .node ellipse,#mermaid-svg-yExzsUwAWMFyciaT .node polygon,#mermaid-svg-yExzsUwAWMFyciaT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yExzsUwAWMFyciaT .rough-node .label text,#mermaid-svg-yExzsUwAWMFyciaT .node .label text,#mermaid-svg-yExzsUwAWMFyciaT .image-shape .label,#mermaid-svg-yExzsUwAWMFyciaT .icon-shape .label{text-anchor:middle;}#mermaid-svg-yExzsUwAWMFyciaT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yExzsUwAWMFyciaT .rough-node .label,#mermaid-svg-yExzsUwAWMFyciaT .node .label,#mermaid-svg-yExzsUwAWMFyciaT .image-shape .label,#mermaid-svg-yExzsUwAWMFyciaT .icon-shape .label{text-align:center;}#mermaid-svg-yExzsUwAWMFyciaT .node.clickable{cursor:pointer;}#mermaid-svg-yExzsUwAWMFyciaT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yExzsUwAWMFyciaT .arrowheadPath{fill:#333333;}#mermaid-svg-yExzsUwAWMFyciaT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yExzsUwAWMFyciaT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yExzsUwAWMFyciaT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yExzsUwAWMFyciaT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yExzsUwAWMFyciaT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yExzsUwAWMFyciaT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yExzsUwAWMFyciaT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yExzsUwAWMFyciaT .cluster text{fill:#333;}#mermaid-svg-yExzsUwAWMFyciaT .cluster span{color:#333;}#mermaid-svg-yExzsUwAWMFyciaT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yExzsUwAWMFyciaT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yExzsUwAWMFyciaT rect.text{fill:none;stroke-width:0;}#mermaid-svg-yExzsUwAWMFyciaT .icon-shape,#mermaid-svg-yExzsUwAWMFyciaT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yExzsUwAWMFyciaT .icon-shape p,#mermaid-svg-yExzsUwAWMFyciaT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yExzsUwAWMFyciaT .icon-shape .label rect,#mermaid-svg-yExzsUwAWMFyciaT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yExzsUwAWMFyciaT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yExzsUwAWMFyciaT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yExzsUwAWMFyciaT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是





计算 flags
当前文件 WRITING?
showFileActions=false

不显示块级顶栏钮
hasReviewablePending?
showFileActions=true
showFileActions=false
session ≥2 文件

或其它文件 pending?
showSessionActions=true

  • Review Next
    仅 gate READY 等兜底

典型事故(版本台账)

  • zones=0 storePending=2SFN → 3.0.60 改 hasReviewablePending
  • 开文件 1 只有 -F- ,切 tab 才变 SFN → gate-ready 跨文件升级。
  • cache=-F- 屏幕仍显示旧 SFN 五钮 → 强制 invalidate 重绘 panel。
2.3.4 顶栏操作语义
按钮 行为
Reject 当前文件:拒绝当前块(或走文件级 reject 快路径)
Keep 当前文件:接受当前块 / 文件级 keep
Reject All 全项目所有 pending 文件 reject(批处理,清 UI)
Keep All 全项目所有 pending 文件 accept_all(批处理,清 UI)
Review Next File 按顺序打开下一个有待审的文件

Badge:「N file(s) pending」 显示 session 内未确认文件数(非 CONFIRMED)。

2.4 块级内联按钮(编辑器内,非顶栏)

需求 说明 Cursor 对比
位置 每块改动顶/底右侧 锚定,不跟随鼠标 Cursor 类似但实现不同
文案 Reject | Keep(宽按钮、可见背景) Cursor: Undo | Keep
滚动 块滚出视口则隐藏;顶底 flip ---
浮层范围 不得盖住其它应用窗口 IDEA 编辑器内
决策后 该块按钮不应再出现 曾闪回 ⚠️
模态 IDEA Settings 等模态打开时隐藏;CC GUI 工具窗不影响 ---
悬停 鼠标在块上显示工具条(与顶栏独立) ---

块级决策后:ReviewDecisionHandlerIdeaReviewStore.decide → 刷新绿/红/inlay。

2.5 Diff 与展示(用户坚持的规则)

需求 说明
官方 API 必须 ComparisonManager / DiffUtil,禁止手写 diff
只绿真插入 @Value、未改行、下移复制行 不能绿
绿颜色 浅绿 #E8F5E9,非 IDE 默认亮绿
红删 删除行用 红色 inlay(锚点行上方),非行内红底
Javadoc /*** 行、*/@param* 等都要覆盖
验收文件 LogQueueSimpleRabbitListenerApiJobApiApplication 为主

2.6 多文件审查流程(用户描述的剧本)

复制代码
AI 改了多个文件
    ↓
用户打开某文件 → 绿线 + 内联按钮 + 顶栏(若满足 flags)
    ↓
逐块 Reject / Keep(或顶栏 Reject All / Keep All)
    ↓
当前文件块审完 → 顶栏剩 Review Next(若其它文件还有待审)
    ↓
Review Next File → 按顺序打开下一文件
    ↓
全部审完 → 顶栏干净,无绿/红
需求 状态(停更前)
多文件 session
顺序 Review Next ⚠️ 曾报跳转怪、被拉回
当前文件审完顶栏逻辑 ⚠️→3.0.60 改善
侧栏 ReviewPanel 与内联一致 ⚠️

2.7 明确不要的东西(需求第八节)

  • 右侧 ToolWindow 卡片审查
  • 整段 hunk 全绿、未改行被标绿
  • 同意后绿线闪回
  • Git 回滚后顶栏/按钮残留
  • 浮层跑到其它应用窗口
  • 手写 diff
  • 未验证就声称「已修复」
  • live-diff 上再叠 hunk 缓存补丁

2.8 工程与质量约束(需求第七节)

约束 说明
架构 live-diff:gitBase + effectiveBaseline,每次渲染现算
单路径绿线 唯一来源 collectInsertedLineRanges0(effective, current)
版本台账 每版变更与是否验收必须记录
安装前 ./scripts/pre-install-check.sh./gradlew test 通过再装 zip
诚实 无本地/自动化验证不说「改好了」
Gradle 保持 8.5,不随意升到 8.14+

2.9 本地验收清单(安装前建议逐项)

  • CC 写盘后:当前文件 绿线 + 顶栏 + 块旁 Reject/Keep
  • @Value 等未改行 不绿
  • 新 Javadoc 的 */ 有绿底(LogQueue / ApiJob)
  • 同意一块后绿线 不闪回
  • 拒绝一块后内容 还原
  • Keep All 后行为符合预期(整 session accept)
  • Reject All 后 Git/内容符合预期
  • Git 回滚 后顶栏/绿线/按钮 全清
  • 多文件时 Review Next 按顺序跳转
  • 无其它待审文件时不显示 Review Next
  • 重启 IDEA 后不无故出现旧 pending 顶栏
  • Cmd+Shift+K 可清残留 session
  • 日志 version= 与安装 zip 一致

2.10 交互禁忌(与 Cursor 不同、必须守住的体验边界)

下列条目来自用户反复投诉USER_REQUIREMENTS / TECHNICAL_SPEC / 版本台账的交集。违反任一条,即使用户没说「架构错了」,也会主观感受为「插件在跟我作对」。Cursor 默认不面对 其中多数场景(无 Git CLM、无 CC GUI 抢焦点、无 IDEA 重启复活 session)。

2.10.1 导航与焦点(最常被骂的「交互太怪」)
禁忌 正确做法 曾犯过的错
禁止强制跳回审阅文件 用户主动切到其它 tab / 其它窗口时,插件不得把编辑器焦点抢回「当前待审文件」 捕获或 refresh 时 openEditor(focus=true),用户正在看 B 却被拉回 A
Review Next 才允许抢焦点 仅用户点击 Review Next FilefocusEditor=true,按文件顺序打开下一待审文件 随便跳第一个 pending、或后台静默 showFile 切 tab
禁止抢 CC GUI 焦点 CC GUI 工具窗占焦点时:不 requestFocus 到编辑器;用 notification 条 / NO_FOCUS 补刷 更新顶栏(见 EditorReviewUiFlush 1.0.51 前自动转移焦点;或 2.5.67 仅 async repaint 导致「要截图才见顶栏」
对齐官方 Local Changes 被动 VCS / gate-ready 刷新当前文件 UI 时,默认不切换用户正在编辑的 tab startup-initial 在错误时机补种 → 顶栏 SFN「复活」
2.10.2 写盘与审阅时机
禁忌 说明
AI 流式写盘中不出块按钮 WRITING 阶段不显示 -F- 顶栏钮、不画易闪的假绿;须 FileWriteGate READY(默认 600ms idle)
写完后当前 tab 必须有 UI gate-ready 须 ensureCaptureOnGateReady / seed store,避免「盘写完了编辑器仍空白」
禁止 8s suppress 挡掉 gate-ready ChangeCaptureGuardgate-ready-*editor-activated 须 bypass(3.0.61 关键修复)
2.10.3 顶栏与块按钮
禁忌 说明
无 pending 不挂五钮 zones=0 且 live 无可审区时,不得仍显示 SFN (3.0.60:hasReviewablePending
当前文件审完不出现 Reject/Keep -F- 为假;至多 S-N(All + Review Next)
无其它文件待审不出现 Review Next 与「当前文件是否审完」无关,只看其它路径是否 pending
顶栏 cache 不得 stale 算出来 -F- 屏幕仍显示旧 SFN → 必须 invalidate 重绘 panel
Settings 等模态打开时隐藏块工具条 CC GUI 工具窗不算模态,不因此藏钮
块按钮不跟鼠标 锚定块顶/底右侧;滚出视口隐藏
2.10.4 决策与 Git 联动
禁忌 说明
同意/拒绝后该块钮不再出现 禁止绿线「闪回」、禁止 orphan-accept 清掉用户已在 A 文件审完的内容
Git Rollback 后 UI 全清 顶栏、绿线、红删 inlay、session --- 一项都不能留(长期 ⚠️❌,仍属禁忌)
Reject 勿误清全文件红删 单块 reject 不能 clearDeleteInlays 把整个文件红删抹掉(3.0.57)
partial Accept 后保留其它块红删 接受一块后,其它 pending 块的红删仍要可见(3.0.58)
2.10.5 生命周期与「悄悄改你盘」
禁忌 说明
重启 IDEA 不要假 pending CLEAR_SESSION_ON_PROJECT_OPEN;跳过 startup-initial 对 CLM 旧 modified 补种
关 IDE 不要悄悄 Auto Keep All(用户后期要求) AUTO_KEEP_ALL_ON_IDE_LIFECYCLE 争论;装 zip 前 grep 确认常量
Keep All 不等于未经确认的写盘承诺 语义是 session 级 accept_all;用户仍要理解 Git 状态
2.10.6 工程与沟通禁忌
禁忌 说明
未本地/自动化验证不说「改好了」 避免 3.0.54/55 类「以为修了、一装又崩」
禁止恢复 3.0.54/55 路线 SessionStoreDriftPolicy、STALE-UI 强制 rematerialize、zonesFor 双算 --- 明确写入发版纪律
禁止 live-diff 上再叠 hunk 缓存补丁 replacePendingHunksForFile 一类路径已废弃
浮层不得盖住其它应用 审查 UI 仅在 IDEA 编辑器内
日志与 zip 版本必须一致 [STARTUP] version= 对不上则整次验收无效

#mermaid-svg-8SmNwjNBNzrwlzDO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8SmNwjNBNzrwlzDO .error-icon{fill:#552222;}#mermaid-svg-8SmNwjNBNzrwlzDO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8SmNwjNBNzrwlzDO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8SmNwjNBNzrwlzDO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8SmNwjNBNzrwlzDO .marker.cross{stroke:#333333;}#mermaid-svg-8SmNwjNBNzrwlzDO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8SmNwjNBNzrwlzDO p{margin:0;}#mermaid-svg-8SmNwjNBNzrwlzDO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8SmNwjNBNzrwlzDO .cluster-label text{fill:#333;}#mermaid-svg-8SmNwjNBNzrwlzDO .cluster-label span{color:#333;}#mermaid-svg-8SmNwjNBNzrwlzDO .cluster-label span p{background-color:transparent;}#mermaid-svg-8SmNwjNBNzrwlzDO .label text,#mermaid-svg-8SmNwjNBNzrwlzDO span{fill:#333;color:#333;}#mermaid-svg-8SmNwjNBNzrwlzDO .node rect,#mermaid-svg-8SmNwjNBNzrwlzDO .node circle,#mermaid-svg-8SmNwjNBNzrwlzDO .node ellipse,#mermaid-svg-8SmNwjNBNzrwlzDO .node polygon,#mermaid-svg-8SmNwjNBNzrwlzDO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8SmNwjNBNzrwlzDO .rough-node .label text,#mermaid-svg-8SmNwjNBNzrwlzDO .node .label text,#mermaid-svg-8SmNwjNBNzrwlzDO .image-shape .label,#mermaid-svg-8SmNwjNBNzrwlzDO .icon-shape .label{text-anchor:middle;}#mermaid-svg-8SmNwjNBNzrwlzDO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8SmNwjNBNzrwlzDO .rough-node .label,#mermaid-svg-8SmNwjNBNzrwlzDO .node .label,#mermaid-svg-8SmNwjNBNzrwlzDO .image-shape .label,#mermaid-svg-8SmNwjNBNzrwlzDO .icon-shape .label{text-align:center;}#mermaid-svg-8SmNwjNBNzrwlzDO .node.clickable{cursor:pointer;}#mermaid-svg-8SmNwjNBNzrwlzDO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8SmNwjNBNzrwlzDO .arrowheadPath{fill:#333333;}#mermaid-svg-8SmNwjNBNzrwlzDO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8SmNwjNBNzrwlzDO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8SmNwjNBNzrwlzDO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8SmNwjNBNzrwlzDO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8SmNwjNBNzrwlzDO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8SmNwjNBNzrwlzDO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8SmNwjNBNzrwlzDO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8SmNwjNBNzrwlzDO .cluster text{fill:#333;}#mermaid-svg-8SmNwjNBNzrwlzDO .cluster span{color:#333;}#mermaid-svg-8SmNwjNBNzrwlzDO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8SmNwjNBNzrwlzDO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8SmNwjNBNzrwlzDO rect.text{fill:none;stroke-width:0;}#mermaid-svg-8SmNwjNBNzrwlzDO .icon-shape,#mermaid-svg-8SmNwjNBNzrwlzDO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8SmNwjNBNzrwlzDO .icon-shape p,#mermaid-svg-8SmNwjNBNzrwlzDO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8SmNwjNBNzrwlzDO .icon-shape .label rect,#mermaid-svg-8SmNwjNBNzrwlzDO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8SmNwjNBNzrwlzDO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8SmNwjNBNzrwlzDO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8SmNwjNBNzrwlzDO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 守住的边界
主动 Review Next 才跳 tab
READY 后才 SFN
live pending=0 → ---
Cmd+Shift+K 应急清 session
触犯禁忌时的用户感受
被拉回别的文件
写盘中按钮乱闪
审完了顶栏还在
Git 回滚了绿线还在

§2.7「明确不要的东西」 的关系:§2.7 偏产品形态 (不要卡片、不要手写 diff);§2.10运行时交互 (插件不得干扰用户正在做的事)。二者都违反,项目就会像最后几天那样------日志看起来合理,人却不想再开 IDEA


3. 架构演进:从四套状态到 live-diff

3.1 1.0.x 的根本问题(必读)

架构缺口原文摘要如下:

复制代码
CC 写盘
  → VcsChangeCaptureService 拆 CodeBlockChange → ReviewSession
  → InlineReviewUiService 从 session 画绿线
  → 用户点「同意」→ 只改 session,Git BASE 不变
  → save / VFS / Git 事件 → 再次 VCS capture → replacePendingHunksForFile
  → session、baseline、Git、编辑器 四套状态不一致
状态 存在位置 典型事故
Git BASE CLM beforeRevision Accept 一块后 BASE 仍是旧提交
session baseline ReviewSessionManager 与 effective 不同步
pending hunks ReviewSession.changes VCS 重捕获整表替换
编辑器 Document.text 行号差一拍

IntelliJ 官方只有 BASE vs CURRENT,没有 session 缓存。

3.2 1.0.88 / v3:方案 A

原则 实现
不缓存展示 zone 每次 LiveDiffModel.computeZones(effective, document)
Accept = 推进 effective EffectiveBaselineUtil.advanceFromZone
权威状态单点 IdeaReviewStore
bridge.db 退出热路径 BridgeReviewStore 降为缓存(命名历史遗留)

3.3 3.0.x:CC GUI 与 FileWriteGate

里程碑 内容
3.0.0 pipeline=idea-v3,架构图见本文 §3 与附录「已知架构问题编号」
3.0.51+ ensureCaptureOnGateReady 雏形
3.0.56 回滚 3.0.54/55 漂移策略;保留 Accept FULL、红删不 purge
3.0.61 ✅ gate-ready 补种 + capture 绕过 8s suppress
3.0.62 🔧 1000 矩阵测试,产品同 3.0.61
3.0.63 两处 drift 修复 → 用户要求回滚
master be0c87b 产品回滚 3.0.61

#mermaid-svg-1Zn6JSSE6EwnLleu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1Zn6JSSE6EwnLleu .error-icon{fill:#552222;}#mermaid-svg-1Zn6JSSE6EwnLleu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1Zn6JSSE6EwnLleu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1Zn6JSSE6EwnLleu .marker.cross{stroke:#333333;}#mermaid-svg-1Zn6JSSE6EwnLleu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1Zn6JSSE6EwnLleu p{margin:0;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge{stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .section--1 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section--1 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section--1 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section--1 text{fill:#ffffff;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth--1{stroke-width:17;}#mermaid-svg-1Zn6JSSE6EwnLleu .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:#ffffff;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-0 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-0 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-0 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-0 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-0{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-0{stroke-width:14;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-1 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-1 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-1 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-1 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-1{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-1{stroke-width:11;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-2 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-2 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-2 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-2 text{fill:#ffffff;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-2{stroke-width:8;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:#ffffff;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-3 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-3 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-3 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-3 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-3{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-3{stroke-width:5;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-4 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-4 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-4 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-4 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-4{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-4{stroke-width:2;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-5 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-5 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-5 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-5 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-5{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-5{stroke-width:-1;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-6 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-6 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-6 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-6 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-6{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-6{stroke-width:-4;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-7 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-7 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-7 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-7 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-7{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-7{stroke-width:-7;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-8 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-8 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-8 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-8 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-8{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-8{stroke-width:-10;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-9 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-9 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-9 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-9 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-9{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-9{stroke-width:-13;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-10 rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-10 path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-10 circle,#mermaid-svg-1Zn6JSSE6EwnLleu .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-10 text{fill:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .node-icon-10{font-size:40px;color:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .edge-depth-10{stroke-width:-16;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-1Zn6JSSE6EwnLleu .lineWrapper line{stroke:black;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled circle,#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:lightgray;}#mermaid-svg-1Zn6JSSE6EwnLleu .disabled text{fill:#efefef;}#mermaid-svg-1Zn6JSSE6EwnLleu .section-root rect,#mermaid-svg-1Zn6JSSE6EwnLleu .section-root path,#mermaid-svg-1Zn6JSSE6EwnLleu .section-root circle{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-1Zn6JSSE6EwnLleu .section-root text{fill:#ffffff;}#mermaid-svg-1Zn6JSSE6EwnLleu .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-1Zn6JSSE6EwnLleu .edge{fill:none;}#mermaid-svg-1Zn6JSSE6EwnLleu .eventWrapper{filter:brightness(120%);}#mermaid-svg-1Zn6JSSE6EwnLleu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1.0.x 1.0.77-87 session 缓存 hunk 1.0.88 live-diff 方案 A 3.0.x 3.0.0 v3 pipeline 3.0.54-55 SessionStoreDrift(已撤) 3.0.61 用户验收稳定 3.0.63 已回滚 cc-gui-review 架构阶段


4. 当前架构(v3)全景

4.1 系统上下文

#mermaid-svg-8J3ROecR5VqzlnAA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8J3ROecR5VqzlnAA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8J3ROecR5VqzlnAA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8J3ROecR5VqzlnAA .error-icon{fill:#552222;}#mermaid-svg-8J3ROecR5VqzlnAA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8J3ROecR5VqzlnAA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8J3ROecR5VqzlnAA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8J3ROecR5VqzlnAA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8J3ROecR5VqzlnAA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8J3ROecR5VqzlnAA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8J3ROecR5VqzlnAA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8J3ROecR5VqzlnAA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8J3ROecR5VqzlnAA .marker.cross{stroke:#333333;}#mermaid-svg-8J3ROecR5VqzlnAA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8J3ROecR5VqzlnAA p{margin:0;}#mermaid-svg-8J3ROecR5VqzlnAA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8J3ROecR5VqzlnAA .cluster-label text{fill:#333;}#mermaid-svg-8J3ROecR5VqzlnAA .cluster-label span{color:#333;}#mermaid-svg-8J3ROecR5VqzlnAA .cluster-label span p{background-color:transparent;}#mermaid-svg-8J3ROecR5VqzlnAA .label text,#mermaid-svg-8J3ROecR5VqzlnAA span{fill:#333;color:#333;}#mermaid-svg-8J3ROecR5VqzlnAA .node rect,#mermaid-svg-8J3ROecR5VqzlnAA .node circle,#mermaid-svg-8J3ROecR5VqzlnAA .node ellipse,#mermaid-svg-8J3ROecR5VqzlnAA .node polygon,#mermaid-svg-8J3ROecR5VqzlnAA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8J3ROecR5VqzlnAA .rough-node .label text,#mermaid-svg-8J3ROecR5VqzlnAA .node .label text,#mermaid-svg-8J3ROecR5VqzlnAA .image-shape .label,#mermaid-svg-8J3ROecR5VqzlnAA .icon-shape .label{text-anchor:middle;}#mermaid-svg-8J3ROecR5VqzlnAA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8J3ROecR5VqzlnAA .rough-node .label,#mermaid-svg-8J3ROecR5VqzlnAA .node .label,#mermaid-svg-8J3ROecR5VqzlnAA .image-shape .label,#mermaid-svg-8J3ROecR5VqzlnAA .icon-shape .label{text-align:center;}#mermaid-svg-8J3ROecR5VqzlnAA .node.clickable{cursor:pointer;}#mermaid-svg-8J3ROecR5VqzlnAA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8J3ROecR5VqzlnAA .arrowheadPath{fill:#333333;}#mermaid-svg-8J3ROecR5VqzlnAA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8J3ROecR5VqzlnAA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8J3ROecR5VqzlnAA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8J3ROecR5VqzlnAA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8J3ROecR5VqzlnAA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8J3ROecR5VqzlnAA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8J3ROecR5VqzlnAA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8J3ROecR5VqzlnAA .cluster text{fill:#333;}#mermaid-svg-8J3ROecR5VqzlnAA .cluster span{color:#333;}#mermaid-svg-8J3ROecR5VqzlnAA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8J3ROecR5VqzlnAA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8J3ROecR5VqzlnAA rect.text{fill:none;stroke-width:0;}#mermaid-svg-8J3ROecR5VqzlnAA .icon-shape,#mermaid-svg-8J3ROecR5VqzlnAA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8J3ROecR5VqzlnAA .icon-shape p,#mermaid-svg-8J3ROecR5VqzlnAA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8J3ROecR5VqzlnAA .icon-shape .label rect,#mermaid-svg-8J3ROecR5VqzlnAA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8J3ROecR5VqzlnAA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8J3ROecR5VqzlnAA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8J3ROecR5VqzlnAA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} cc-gui-review
可选 · 非热路径
外部
终端 Claude Code
CC GUI 写盘
用户
Git HEAD / CLM
cc-gui-bridge hooks
ChangeReceiver :9876
FileWriteGate
VcsChangeCaptureService
CursorReviewPipeline
IdeaReviewStore ★
BridgeReviewStore
LiveDiffModel
InlineReviewUiService
ReviewZoneRenderer

4.2 分层职责表

代表类 职责 能否直接改 UI
L1 INGEST VcsChangeCaptureService, ChangeReceiver, ReviewDocumentListenerService 把 git/ws 变成 store 记录
L2 STATE IdeaReviewStore, ReviewSessionState 权威 pending / effective / hunks
L3 SHIM BridgeReviewStore, BridgeReviewSync display 缓存、TTL、tab 协调 间接
L4 DISPLAY LiveDiffModel, CursorZoneMerge, HunkZoneMapper, RedGhostGrouper 只读 现算 zones
L5 UI InlineReviewUiService, ReviewZoneRenderer, ReviewDecisionHandler 绿/红/inlay/按钮

4.3 单文件状态机

#mermaid-svg-80IQG7LJWVXU2omT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-80IQG7LJWVXU2omT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-80IQG7LJWVXU2omT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-80IQG7LJWVXU2omT .error-icon{fill:#552222;}#mermaid-svg-80IQG7LJWVXU2omT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-80IQG7LJWVXU2omT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-80IQG7LJWVXU2omT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-80IQG7LJWVXU2omT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-80IQG7LJWVXU2omT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-80IQG7LJWVXU2omT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-80IQG7LJWVXU2omT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-80IQG7LJWVXU2omT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-80IQG7LJWVXU2omT .marker.cross{stroke:#333333;}#mermaid-svg-80IQG7LJWVXU2omT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-80IQG7LJWVXU2omT p{margin:0;}#mermaid-svg-80IQG7LJWVXU2omT defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-80IQG7LJWVXU2omT g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-80IQG7LJWVXU2omT g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-80IQG7LJWVXU2omT g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-80IQG7LJWVXU2omT g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-80IQG7LJWVXU2omT g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-80IQG7LJWVXU2omT .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-80IQG7LJWVXU2omT .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-80IQG7LJWVXU2omT .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-80IQG7LJWVXU2omT .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-80IQG7LJWVXU2omT .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-80IQG7LJWVXU2omT .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-80IQG7LJWVXU2omT .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-80IQG7LJWVXU2omT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-80IQG7LJWVXU2omT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-80IQG7LJWVXU2omT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-80IQG7LJWVXU2omT .edgeLabel .label text{fill:#333;}#mermaid-svg-80IQG7LJWVXU2omT .label div .edgeLabel{color:#333;}#mermaid-svg-80IQG7LJWVXU2omT .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-80IQG7LJWVXU2omT .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-80IQG7LJWVXU2omT .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-80IQG7LJWVXU2omT .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-80IQG7LJWVXU2omT .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-80IQG7LJWVXU2omT .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-80IQG7LJWVXU2omT .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-80IQG7LJWVXU2omT #statediagram-barbEnd{fill:#333333;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-80IQG7LJWVXU2omT .cluster-label,#mermaid-svg-80IQG7LJWVXU2omT .nodeLabel{color:#131300;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-80IQG7LJWVXU2omT .note-edge{stroke-dasharray:5;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-note text{fill:black;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram-note .nodeLabel{color:black;}#mermaid-svg-80IQG7LJWVXU2omT .statediagram .edgeLabel{color:red;}#mermaid-svg-80IQG7LJWVXU2omT #dependencyStart,#mermaid-svg-80IQG7LJWVXU2omT #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-80IQG7LJWVXU2omT .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-80IQG7LJWVXU2omT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP pre_write
disk_ready
VCS capture / gate READY seed
decide reject
全部处理完
AWAITING_DISK
PENDING
RESOLVED

IdeaReviewFileRecord 字段语义

复制代码
gitBaseText            ← 对齐 Git,rollback 检测
effectiveBaselineText  ← diff 左端 ★ Accept 推进
expectedWorkspaceText  ← accept_all 目标全文
hunks[]                ← 捕获时行号+正文(decide 匹配)
status                 ← AWAITING_DISK | PENDING | RESOLVED

4.4 顶栏按钮(Session 规则,与 §2.3 呼应)

#mermaid-svg-7V9cu5XznMaHPQO0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7V9cu5XznMaHPQO0 .error-icon{fill:#552222;}#mermaid-svg-7V9cu5XznMaHPQO0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7V9cu5XznMaHPQO0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7V9cu5XznMaHPQO0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7V9cu5XznMaHPQO0 .marker.cross{stroke:#333333;}#mermaid-svg-7V9cu5XznMaHPQO0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7V9cu5XznMaHPQO0 p{margin:0;}#mermaid-svg-7V9cu5XznMaHPQO0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7V9cu5XznMaHPQO0 .cluster-label text{fill:#333;}#mermaid-svg-7V9cu5XznMaHPQO0 .cluster-label span{color:#333;}#mermaid-svg-7V9cu5XznMaHPQO0 .cluster-label span p{background-color:transparent;}#mermaid-svg-7V9cu5XznMaHPQO0 .label text,#mermaid-svg-7V9cu5XznMaHPQO0 span{fill:#333;color:#333;}#mermaid-svg-7V9cu5XznMaHPQO0 .node rect,#mermaid-svg-7V9cu5XznMaHPQO0 .node circle,#mermaid-svg-7V9cu5XznMaHPQO0 .node ellipse,#mermaid-svg-7V9cu5XznMaHPQO0 .node polygon,#mermaid-svg-7V9cu5XznMaHPQO0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7V9cu5XznMaHPQO0 .rough-node .label text,#mermaid-svg-7V9cu5XznMaHPQO0 .node .label text,#mermaid-svg-7V9cu5XznMaHPQO0 .image-shape .label,#mermaid-svg-7V9cu5XznMaHPQO0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-7V9cu5XznMaHPQO0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7V9cu5XznMaHPQO0 .rough-node .label,#mermaid-svg-7V9cu5XznMaHPQO0 .node .label,#mermaid-svg-7V9cu5XznMaHPQO0 .image-shape .label,#mermaid-svg-7V9cu5XznMaHPQO0 .icon-shape .label{text-align:center;}#mermaid-svg-7V9cu5XznMaHPQO0 .node.clickable{cursor:pointer;}#mermaid-svg-7V9cu5XznMaHPQO0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7V9cu5XznMaHPQO0 .arrowheadPath{fill:#333333;}#mermaid-svg-7V9cu5XznMaHPQO0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7V9cu5XznMaHPQO0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7V9cu5XznMaHPQO0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7V9cu5XznMaHPQO0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7V9cu5XznMaHPQO0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7V9cu5XznMaHPQO0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7V9cu5XznMaHPQO0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7V9cu5XznMaHPQO0 .cluster text{fill:#333;}#mermaid-svg-7V9cu5XznMaHPQO0 .cluster span{color:#333;}#mermaid-svg-7V9cu5XznMaHPQO0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7V9cu5XznMaHPQO0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7V9cu5XznMaHPQO0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-7V9cu5XznMaHPQO0 .icon-shape,#mermaid-svg-7V9cu5XznMaHPQO0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7V9cu5XznMaHPQO0 .icon-shape p,#mermaid-svg-7V9cu5XznMaHPQO0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7V9cu5XznMaHPQO0 .icon-shape .label rect,#mermaid-svg-7V9cu5XznMaHPQO0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7V9cu5XznMaHPQO0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7V9cu5XznMaHPQO0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7V9cu5XznMaHPQO0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否





Session 有 reviewable pending?
顶栏 0 按钮
多文件 session?
SFN 五条

Reject All | Keep All | Reject | Keep | Review Next
当前文件有 pending?
Reject | Keep
S-N:Session 条 + Review Next

实现:ReviewHeaderController + ReviewHeaderState3.0.60filePending 必须看 hasReviewablePending,不能只看 session pendingHunkCount


5. 核心原理透明化

5.1 FileWriteGate:为什么 AI 写的时候不能出按钮

CC GUI 写盘会连续触发 CLM / Document 事件。若在 WRITING 就画绿线,会:

  • 每帧 diff 变、按钮闪烁;
  • capture 到一半 effective 与 ws 不一致 → 假绿、假删

门控规则 (见 FileWriteGate.kt 类注释):

信号 行为
S1 单文件 fileIdleMs(默认 600ms )无新写入 → READY
S2 其它文件开始写 → 当前 WRITING 文件立即 READY(多文件交错)
WRITING 不展示 inline 背景 / 块按钮 / 顶栏 SFN
READY 允许 GATE_READY_SEEDrefreshAndShow

环境变量:CC_GUI_FILE_IDLE_MS(毫秒)。
InlineReviewUi IdeaReviewStore VcsChangeCapture FileWriteGate Git CLM CC GUI InlineReviewUi IdeaReviewStore VcsChangeCapture FileWriteGate Git CLM CC GUI #mermaid-svg-5BZy5QE3szpzVGcr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5BZy5QE3szpzVGcr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5BZy5QE3szpzVGcr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5BZy5QE3szpzVGcr .error-icon{fill:#552222;}#mermaid-svg-5BZy5QE3szpzVGcr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5BZy5QE3szpzVGcr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5BZy5QE3szpzVGcr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5BZy5QE3szpzVGcr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5BZy5QE3szpzVGcr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5BZy5QE3szpzVGcr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5BZy5QE3szpzVGcr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5BZy5QE3szpzVGcr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5BZy5QE3szpzVGcr .marker.cross{stroke:#333333;}#mermaid-svg-5BZy5QE3szpzVGcr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5BZy5QE3szpzVGcr p{margin:0;}#mermaid-svg-5BZy5QE3szpzVGcr .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5BZy5QE3szpzVGcr text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-5BZy5QE3szpzVGcr .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5BZy5QE3szpzVGcr .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-5BZy5QE3szpzVGcr .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5BZy5QE3szpzVGcr .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5BZy5QE3szpzVGcr #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5BZy5QE3szpzVGcr .sequenceNumber{fill:white;}#mermaid-svg-5BZy5QE3szpzVGcr #sequencenumber{fill:#333;}#mermaid-svg-5BZy5QE3szpzVGcr #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5BZy5QE3szpzVGcr .messageText{fill:#333;stroke:none;}#mermaid-svg-5BZy5QE3szpzVGcr .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5BZy5QE3szpzVGcr .labelText,#mermaid-svg-5BZy5QE3szpzVGcr .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-5BZy5QE3szpzVGcr .loopText,#mermaid-svg-5BZy5QE3szpzVGcr .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-5BZy5QE3szpzVGcr .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5BZy5QE3szpzVGcr .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5BZy5QE3szpzVGcr .noteText,#mermaid-svg-5BZy5QE3szpzVGcr .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-5BZy5QE3szpzVGcr .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5BZy5QE3szpzVGcr .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5BZy5QE3szpzVGcr .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5BZy5QE3szpzVGcr .actorPopupMenu{position:absolute;}#mermaid-svg-5BZy5QE3szpzVGcr .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5BZy5QE3szpzVGcr .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5BZy5QE3szpzVGcr .actor-man circle,#mermaid-svg-5BZy5QE3szpzVGcr line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5BZy5QE3szpzVGcr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不画块按钮 连续写盘 noteWriting → WRITING 停顿 >600ms READY schedule capture capture + reconcile GATE_READY_PAINT

3.0.61 关键补丁 :即使 gate READY,若 store 仍空且 disk ≠ gitensureCaptureOnGateReady 用 git↔workspace 补种 ,避免 LogQueue 上出现 CONTENT_SKIP no ideaStore → 无按钮。


5.2 绿线:怎样才算「真新增」

数据源PlatformDiffUtil / ComparisonManager(effectiveBaseline, editor.text) 做行级 diff。

插入行判定(简化):

  • 行在 effective 中 找不到等价行 → INSERTED → 可绿。
  • 陷阱 :旧文件任意位置已有 */,新 Javadoc 的 */ 会被判「非插入」→ 需 extendToCommentClose0 扫描补范围。

禁止 :整段 hunk newContent 行号范围全绿(3.0.52 曾误标「原有行也绿」→ FALSE_GREEN_SKIP)。
#mermaid-svg-S1Fr9XpBxJu52ucd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-S1Fr9XpBxJu52ucd .error-icon{fill:#552222;}#mermaid-svg-S1Fr9XpBxJu52ucd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-S1Fr9XpBxJu52ucd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-S1Fr9XpBxJu52ucd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-S1Fr9XpBxJu52ucd .marker.cross{stroke:#333333;}#mermaid-svg-S1Fr9XpBxJu52ucd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-S1Fr9XpBxJu52ucd p{margin:0;}#mermaid-svg-S1Fr9XpBxJu52ucd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-S1Fr9XpBxJu52ucd .cluster-label text{fill:#333;}#mermaid-svg-S1Fr9XpBxJu52ucd .cluster-label span{color:#333;}#mermaid-svg-S1Fr9XpBxJu52ucd .cluster-label span p{background-color:transparent;}#mermaid-svg-S1Fr9XpBxJu52ucd .label text,#mermaid-svg-S1Fr9XpBxJu52ucd span{fill:#333;color:#333;}#mermaid-svg-S1Fr9XpBxJu52ucd .node rect,#mermaid-svg-S1Fr9XpBxJu52ucd .node circle,#mermaid-svg-S1Fr9XpBxJu52ucd .node ellipse,#mermaid-svg-S1Fr9XpBxJu52ucd .node polygon,#mermaid-svg-S1Fr9XpBxJu52ucd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-S1Fr9XpBxJu52ucd .rough-node .label text,#mermaid-svg-S1Fr9XpBxJu52ucd .node .label text,#mermaid-svg-S1Fr9XpBxJu52ucd .image-shape .label,#mermaid-svg-S1Fr9XpBxJu52ucd .icon-shape .label{text-anchor:middle;}#mermaid-svg-S1Fr9XpBxJu52ucd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-S1Fr9XpBxJu52ucd .rough-node .label,#mermaid-svg-S1Fr9XpBxJu52ucd .node .label,#mermaid-svg-S1Fr9XpBxJu52ucd .image-shape .label,#mermaid-svg-S1Fr9XpBxJu52ucd .icon-shape .label{text-align:center;}#mermaid-svg-S1Fr9XpBxJu52ucd .node.clickable{cursor:pointer;}#mermaid-svg-S1Fr9XpBxJu52ucd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-S1Fr9XpBxJu52ucd .arrowheadPath{fill:#333333;}#mermaid-svg-S1Fr9XpBxJu52ucd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-S1Fr9XpBxJu52ucd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-S1Fr9XpBxJu52ucd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S1Fr9XpBxJu52ucd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-S1Fr9XpBxJu52ucd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S1Fr9XpBxJu52ucd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-S1Fr9XpBxJu52ucd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-S1Fr9XpBxJu52ucd .cluster text{fill:#333;}#mermaid-svg-S1Fr9XpBxJu52ucd .cluster span{color:#333;}#mermaid-svg-S1Fr9XpBxJu52ucd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-S1Fr9XpBxJu52ucd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-S1Fr9XpBxJu52ucd rect.text{fill:none;stroke-width:0;}#mermaid-svg-S1Fr9XpBxJu52ucd .icon-shape,#mermaid-svg-S1Fr9XpBxJu52ucd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S1Fr9XpBxJu52ucd .icon-shape p,#mermaid-svg-S1Fr9XpBxJu52ucd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-S1Fr9XpBxJu52ucd .icon-shape .label rect,#mermaid-svg-S1Fr9XpBxJu52ucd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S1Fr9XpBxJu52ucd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-S1Fr9XpBxJu52ucd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-S1Fr9XpBxJu52ucd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} effectiveBaseline
ComparisonManager

行级 fragment
document.text
trulyInsertedLineRanges0
extendToCommentClose0

Javadoc */
RangeHighlighter

#E8F5E9

与官方差异 :IDEA Diff 窗口标的是 MODIFY 区域整块黄 ,不承诺「只绿新增行」。这是我们 自研语义层,bug 面比官方大。


5.3 红删:不是行内红底,是 inlay ghost

这是用户最容易「以为坏了」的一点。

Cursor / 用户直觉 cc-gui-review 实现
行内红底 / 删除线 Block inlay 浮在锚点行 上方
与绿在同一行叠加 showAbove(true),避免红块画进绿块下面

ReviewDeletedLinesInlayRenderer 注释原文要点:

  • 使用 EditorCustomElementRenderer + block inlay;
  • 必须 InlayProperties.showAbove(true),否则 replace 场景红嵌绿;
  • 绘制 − ${line} 前缀 + 浅红背景 colors.blockBg

管线
#mermaid-svg-94vDXxmQYs5pibRI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-94vDXxmQYs5pibRI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-94vDXxmQYs5pibRI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-94vDXxmQYs5pibRI .error-icon{fill:#552222;}#mermaid-svg-94vDXxmQYs5pibRI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-94vDXxmQYs5pibRI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-94vDXxmQYs5pibRI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-94vDXxmQYs5pibRI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-94vDXxmQYs5pibRI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-94vDXxmQYs5pibRI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-94vDXxmQYs5pibRI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-94vDXxmQYs5pibRI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-94vDXxmQYs5pibRI .marker.cross{stroke:#333333;}#mermaid-svg-94vDXxmQYs5pibRI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-94vDXxmQYs5pibRI p{margin:0;}#mermaid-svg-94vDXxmQYs5pibRI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-94vDXxmQYs5pibRI .cluster-label text{fill:#333;}#mermaid-svg-94vDXxmQYs5pibRI .cluster-label span{color:#333;}#mermaid-svg-94vDXxmQYs5pibRI .cluster-label span p{background-color:transparent;}#mermaid-svg-94vDXxmQYs5pibRI .label text,#mermaid-svg-94vDXxmQYs5pibRI span{fill:#333;color:#333;}#mermaid-svg-94vDXxmQYs5pibRI .node rect,#mermaid-svg-94vDXxmQYs5pibRI .node circle,#mermaid-svg-94vDXxmQYs5pibRI .node ellipse,#mermaid-svg-94vDXxmQYs5pibRI .node polygon,#mermaid-svg-94vDXxmQYs5pibRI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-94vDXxmQYs5pibRI .rough-node .label text,#mermaid-svg-94vDXxmQYs5pibRI .node .label text,#mermaid-svg-94vDXxmQYs5pibRI .image-shape .label,#mermaid-svg-94vDXxmQYs5pibRI .icon-shape .label{text-anchor:middle;}#mermaid-svg-94vDXxmQYs5pibRI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-94vDXxmQYs5pibRI .rough-node .label,#mermaid-svg-94vDXxmQYs5pibRI .node .label,#mermaid-svg-94vDXxmQYs5pibRI .image-shape .label,#mermaid-svg-94vDXxmQYs5pibRI .icon-shape .label{text-align:center;}#mermaid-svg-94vDXxmQYs5pibRI .node.clickable{cursor:pointer;}#mermaid-svg-94vDXxmQYs5pibRI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-94vDXxmQYs5pibRI .arrowheadPath{fill:#333333;}#mermaid-svg-94vDXxmQYs5pibRI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-94vDXxmQYs5pibRI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-94vDXxmQYs5pibRI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94vDXxmQYs5pibRI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-94vDXxmQYs5pibRI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94vDXxmQYs5pibRI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-94vDXxmQYs5pibRI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-94vDXxmQYs5pibRI .cluster text{fill:#333;}#mermaid-svg-94vDXxmQYs5pibRI .cluster span{color:#333;}#mermaid-svg-94vDXxmQYs5pibRI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-94vDXxmQYs5pibRI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-94vDXxmQYs5pibRI rect.text{fill:none;stroke-width:0;}#mermaid-svg-94vDXxmQYs5pibRI .icon-shape,#mermaid-svg-94vDXxmQYs5pibRI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94vDXxmQYs5pibRI .icon-shape p,#mermaid-svg-94vDXxmQYs5pibRI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-94vDXxmQYs5pibRI .icon-shape .label rect,#mermaid-svg-94vDXxmQYs5pibRI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94vDXxmQYs5pibRI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-94vDXxmQYs5pibRI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-94vDXxmQYs5pibRI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} delete-only 与 insert 配对
假删:内容仍在 ws
insert 无 live 行但 store 有 removedLines
通过
effective
collectDeleteInlays
workspace
insert zones
RedGhostGrouper.groupByMergedZones
过滤器
不单独画
suppressedMisaligned
shouldKeepRedGhostDespiteEmptyLiveInsert
addDeletedInlay

日志字段APPLY_DECOR ... red=5 redDetail=@L110[旧 log.info...] 表示 算法层 找到 5 处删除锚点;不等于 肉眼一定能看到 --- inlay 可能因 EDT 重绘、clearDeleteInlays、折叠区、主题对比度未显示。

3.0.57--3.0.58 战史 :partial Accept 后 liveInserts.isEmpty() 误走 suppressedFallback,把其它块的配对红删清掉 → shouldKeepRedGhostDespiteEmptyLiveInsert + skip-purge-red


5.4 Accept / Reject:effective baseline 推进

EffectiveBaselineUtil IdeaReviewStore HunkZoneMapper ReviewDecisionHandler 用户 EffectiveBaselineUtil IdeaReviewStore HunkZoneMapper ReviewDecisionHandler 用户 #mermaid-svg-McpsaZ2vVP1nyVTj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-McpsaZ2vVP1nyVTj .error-icon{fill:#552222;}#mermaid-svg-McpsaZ2vVP1nyVTj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-McpsaZ2vVP1nyVTj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-McpsaZ2vVP1nyVTj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-McpsaZ2vVP1nyVTj .marker.cross{stroke:#333333;}#mermaid-svg-McpsaZ2vVP1nyVTj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-McpsaZ2vVP1nyVTj p{margin:0;}#mermaid-svg-McpsaZ2vVP1nyVTj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-McpsaZ2vVP1nyVTj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-McpsaZ2vVP1nyVTj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-McpsaZ2vVP1nyVTj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-McpsaZ2vVP1nyVTj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-McpsaZ2vVP1nyVTj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-McpsaZ2vVP1nyVTj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-McpsaZ2vVP1nyVTj .sequenceNumber{fill:white;}#mermaid-svg-McpsaZ2vVP1nyVTj #sequencenumber{fill:#333;}#mermaid-svg-McpsaZ2vVP1nyVTj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-McpsaZ2vVP1nyVTj .messageText{fill:#333;stroke:none;}#mermaid-svg-McpsaZ2vVP1nyVTj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-McpsaZ2vVP1nyVTj .labelText,#mermaid-svg-McpsaZ2vVP1nyVTj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-McpsaZ2vVP1nyVTj .loopText,#mermaid-svg-McpsaZ2vVP1nyVTj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-McpsaZ2vVP1nyVTj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-McpsaZ2vVP1nyVTj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-McpsaZ2vVP1nyVTj .noteText,#mermaid-svg-McpsaZ2vVP1nyVTj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-McpsaZ2vVP1nyVTj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-McpsaZ2vVP1nyVTj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-McpsaZ2vVP1nyVTj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-McpsaZ2vVP1nyVTj .actorPopupMenu{position:absolute;}#mermaid-svg-McpsaZ2vVP1nyVTj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-McpsaZ2vVP1nyVTj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-McpsaZ2vVP1nyVTj .actor-man circle,#mermaid-svg-McpsaZ2vVP1nyVTj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-McpsaZ2vVP1nyVTj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} effective 含已接受块 Accept(zone) findPendingHunkForZone ★ zone 优先 decide(accept) enrichZoneForAdvance advanceFromZone\nworkspace 不变 refresh → 重算 zones

操作 effective workspace Git BASE
Accept 一块 推进 不变 不变
Reject insert 不变 删行/恢复 不变
Keep All = expectedWorkspace 同步 不变
Reject All 不变 revert 到 effective 不变

pure-insert 路径(日志常见):

text 复制代码
decide accept pure-insert ... workspace 切片推进 baseline,避免假 DEL

大块 insert Accept 时,用 workspace 切片推进 effective,避免 把 replace 误当成 delete 再画假红删。副作用:Accept 后 red 计数可能从 5 降到 1 --- 不是 bug 时也是预期


5.5 Session 块 vs Store Hunk:两套坐标系

Session blocks Store hunks
来源 ReviewSessionState.syncWorkspaceBlocks VCS capture / reconcile
用途 工具条 zone id、块级导航 decide() 匹配、orphan 策略
漂移 partial Accept 后 blocks 耗尽 粗 hunk 行号仍 L43-45
日志 sessionBlocks=0 pending=3 + liveZones=8

3.0.59syncStoreWhenSessionUiExhaustedliveZoneCount>0禁止 orphan-accept(勿误把剩余 store 当 Keep File)。

3.0.63(已回滚)apply-no-pending stale-inline 在 SKIP orphan-accept 后仍清 UI → 用户「点不动」。


6. 技术栈与依赖

6.1 构建与运行时

技术 版本/说明
Kotlin 2.3.x
JVM 21
IntelliJ Platform Gradle Plugin 2.2.x
Gson JSON(会话等)
JUnit 4 离线测试

6.2 IntelliJ API(核心)

API 用途
ComparisonManager / DiffUtil 行级 diff
ChangeListManager Git BASE / 变更列表
MarkupModel.addRangeHighlighter 绿底
InlayModel.addBlockElement 红删 ghost
WriteCommandAction Reject 写文档
ApplicationManager.invokeLater EDT 刷新

6.3 已删除的 Node 桥(历史)

v3-a 起 不再依赖 cc-gui-bridge npm 包、:9875 daemon、bridge.db 热路径。TS→Kotlin 对照见本文 附录:Bridge 迁移对照

可选:CC_GUI_REVIEW_HTTP=1 开启 :9876 给终端 Claude。

6.4 日志与调试

bash 复制代码
export CC_GUI_REVIEW_DEBUG=1
# 或 idea.vmoptions: -DCC_GUI_REVIEW_DEBUG=1

路径:~/logs/cc-gui-review-<version>/<工程名>.log

必须 grep [STARTUP] PluginStartupActivity version= 确认装的是哪一版 zip。


7. 与 Cursor 的对比(总表)

7.1 能力矩阵

能力 Cursor / CC GUI cc-gui-review
内联 Keep/Undo ✅ 原生 ✅ Reject | Keep
流式生成时 UI 模型驱动更新 FileWriteGate 等 READY
红删展示 产品内嵌,用户熟悉 inlay ghost,位置在锚点 上方
只绿真新增 用户预期 自研 trulyInserted + Javadoc extend
多文件 Agent Session SFN + Review Next(见 §2.3)
重启 IDE / 开工程 无 session 复活问题 清 session + 90s 被动抑制 + 5s grace(见 §2.2)
顶栏状态机 无五钮 SFN S / F / N 三档组合(见 §2.3)
与 Git 集成 弱/旁路 强依赖 CLM、disk、gitLen
闭源 API 无,需逆向对齐行为

7.2 为什么我们不能「直接抄 Cursor」

#mermaid-svg-nLxBmrzrpxbOm2sy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nLxBmrzrpxbOm2sy .error-icon{fill:#552222;}#mermaid-svg-nLxBmrzrpxbOm2sy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nLxBmrzrpxbOm2sy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nLxBmrzrpxbOm2sy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nLxBmrzrpxbOm2sy .marker.cross{stroke:#333333;}#mermaid-svg-nLxBmrzrpxbOm2sy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nLxBmrzrpxbOm2sy p{margin:0;}#mermaid-svg-nLxBmrzrpxbOm2sy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nLxBmrzrpxbOm2sy .cluster-label text{fill:#333;}#mermaid-svg-nLxBmrzrpxbOm2sy .cluster-label span{color:#333;}#mermaid-svg-nLxBmrzrpxbOm2sy .cluster-label span p{background-color:transparent;}#mermaid-svg-nLxBmrzrpxbOm2sy .label text,#mermaid-svg-nLxBmrzrpxbOm2sy span{fill:#333;color:#333;}#mermaid-svg-nLxBmrzrpxbOm2sy .node rect,#mermaid-svg-nLxBmrzrpxbOm2sy .node circle,#mermaid-svg-nLxBmrzrpxbOm2sy .node ellipse,#mermaid-svg-nLxBmrzrpxbOm2sy .node polygon,#mermaid-svg-nLxBmrzrpxbOm2sy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nLxBmrzrpxbOm2sy .rough-node .label text,#mermaid-svg-nLxBmrzrpxbOm2sy .node .label text,#mermaid-svg-nLxBmrzrpxbOm2sy .image-shape .label,#mermaid-svg-nLxBmrzrpxbOm2sy .icon-shape .label{text-anchor:middle;}#mermaid-svg-nLxBmrzrpxbOm2sy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nLxBmrzrpxbOm2sy .rough-node .label,#mermaid-svg-nLxBmrzrpxbOm2sy .node .label,#mermaid-svg-nLxBmrzrpxbOm2sy .image-shape .label,#mermaid-svg-nLxBmrzrpxbOm2sy .icon-shape .label{text-align:center;}#mermaid-svg-nLxBmrzrpxbOm2sy .node.clickable{cursor:pointer;}#mermaid-svg-nLxBmrzrpxbOm2sy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nLxBmrzrpxbOm2sy .arrowheadPath{fill:#333333;}#mermaid-svg-nLxBmrzrpxbOm2sy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nLxBmrzrpxbOm2sy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nLxBmrzrpxbOm2sy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nLxBmrzrpxbOm2sy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nLxBmrzrpxbOm2sy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nLxBmrzrpxbOm2sy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nLxBmrzrpxbOm2sy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nLxBmrzrpxbOm2sy .cluster text{fill:#333;}#mermaid-svg-nLxBmrzrpxbOm2sy .cluster span{color:#333;}#mermaid-svg-nLxBmrzrpxbOm2sy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nLxBmrzrpxbOm2sy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nLxBmrzrpxbOm2sy rect.text{fill:none;stroke-width:0;}#mermaid-svg-nLxBmrzrpxbOm2sy .icon-shape,#mermaid-svg-nLxBmrzrpxbOm2sy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nLxBmrzrpxbOm2sy .icon-shape p,#mermaid-svg-nLxBmrzrpxbOm2sy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nLxBmrzrpxbOm2sy .icon-shape .label rect,#mermaid-svg-nLxBmrzrpxbOm2sy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nLxBmrzrpxbOm2sy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nLxBmrzrpxbOm2sy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nLxBmrzrpxbOm2sy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} cc-gui-review
Git + CLM
effectiveBaseline
IDE Document
自研 UI

开源 IDEA API
Cursor
模型 diff chunks
内联 UI

闭源

Cursor 控制 模型输出 + 编辑器 + diff 展示 闭环。我们只有 IDEA 公开 API ,中间还要插入 Git 文件状态600ms 写稳门控


8. 与 IntelliJ IDEA 官方的对比

8.1 官方怎么做(为什么它稳)

维度 IntelliJ Local Changes / Diff
数据 BASE vs CURRENT
计算 每次展示时现算
块级 Accept
回滚 CLM 更新 → UI 消失
着色 Diff 窗 MODIFY 黄;gutter 绿条另一套

8.2 我们多出来的东西(= 多出来的 bug)

增量 代价
块级 effective 推进 与 Git 文件粒度冲突
只绿插入行 */、空行、折叠
红删 inlay 挂载/清理/配对逻辑
双入口捕获 HTTP + VCS + DocumentListener
Session + Store 双坐标 hunk not found、orphan-accept
FileWriteGate READY 与 capture 竞态

8.3 对照图

#mermaid-svg-blD8yb2oz3WxtiXH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-blD8yb2oz3WxtiXH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-blD8yb2oz3WxtiXH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-blD8yb2oz3WxtiXH .error-icon{fill:#552222;}#mermaid-svg-blD8yb2oz3WxtiXH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-blD8yb2oz3WxtiXH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-blD8yb2oz3WxtiXH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-blD8yb2oz3WxtiXH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-blD8yb2oz3WxtiXH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-blD8yb2oz3WxtiXH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-blD8yb2oz3WxtiXH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-blD8yb2oz3WxtiXH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-blD8yb2oz3WxtiXH .marker.cross{stroke:#333333;}#mermaid-svg-blD8yb2oz3WxtiXH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-blD8yb2oz3WxtiXH p{margin:0;}#mermaid-svg-blD8yb2oz3WxtiXH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-blD8yb2oz3WxtiXH .cluster-label text{fill:#333;}#mermaid-svg-blD8yb2oz3WxtiXH .cluster-label span{color:#333;}#mermaid-svg-blD8yb2oz3WxtiXH .cluster-label span p{background-color:transparent;}#mermaid-svg-blD8yb2oz3WxtiXH .label text,#mermaid-svg-blD8yb2oz3WxtiXH span{fill:#333;color:#333;}#mermaid-svg-blD8yb2oz3WxtiXH .node rect,#mermaid-svg-blD8yb2oz3WxtiXH .node circle,#mermaid-svg-blD8yb2oz3WxtiXH .node ellipse,#mermaid-svg-blD8yb2oz3WxtiXH .node polygon,#mermaid-svg-blD8yb2oz3WxtiXH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-blD8yb2oz3WxtiXH .rough-node .label text,#mermaid-svg-blD8yb2oz3WxtiXH .node .label text,#mermaid-svg-blD8yb2oz3WxtiXH .image-shape .label,#mermaid-svg-blD8yb2oz3WxtiXH .icon-shape .label{text-anchor:middle;}#mermaid-svg-blD8yb2oz3WxtiXH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-blD8yb2oz3WxtiXH .rough-node .label,#mermaid-svg-blD8yb2oz3WxtiXH .node .label,#mermaid-svg-blD8yb2oz3WxtiXH .image-shape .label,#mermaid-svg-blD8yb2oz3WxtiXH .icon-shape .label{text-align:center;}#mermaid-svg-blD8yb2oz3WxtiXH .node.clickable{cursor:pointer;}#mermaid-svg-blD8yb2oz3WxtiXH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-blD8yb2oz3WxtiXH .arrowheadPath{fill:#333333;}#mermaid-svg-blD8yb2oz3WxtiXH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-blD8yb2oz3WxtiXH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-blD8yb2oz3WxtiXH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-blD8yb2oz3WxtiXH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-blD8yb2oz3WxtiXH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-blD8yb2oz3WxtiXH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-blD8yb2oz3WxtiXH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-blD8yb2oz3WxtiXH .cluster text{fill:#333;}#mermaid-svg-blD8yb2oz3WxtiXH .cluster span{color:#333;}#mermaid-svg-blD8yb2oz3WxtiXH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-blD8yb2oz3WxtiXH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-blD8yb2oz3WxtiXH rect.text{fill:none;stroke-width:0;}#mermaid-svg-blD8yb2oz3WxtiXH .icon-shape,#mermaid-svg-blD8yb2oz3WxtiXH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-blD8yb2oz3WxtiXH .icon-shape p,#mermaid-svg-blD8yb2oz3WxtiXH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-blD8yb2oz3WxtiXH .icon-shape .label rect,#mermaid-svg-blD8yb2oz3WxtiXH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-blD8yb2oz3WxtiXH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-blD8yb2oz3WxtiXH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-blD8yb2oz3WxtiXH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} cc-gui-review
gitBase
effective ★
LiveDiffModel
workspace
内联 + 顶栏
IDEA 官方
Git BASE
Diff Tool

现算
CURRENT


9. 版本战史:我们踩过的坑(3.0.50--3.0.63)

9.1 3.0.54 / 3.0.55 --- 明确勿用

版本 改动 后果
3.0.54 SessionStoreDriftPolicy、禁止 orphan-accept Accept 后其它块 UI 消失
3.0.55 zonesFor 双算、STALE-UI 强制 rematerialize Accept 变慢、行为怪

已整批回滚到 3.0.53 基线路径,见 3.0.56 台账。

9.2 3.0.57 --- Reject 勿清全文件红删

  • 现象 :Reject 一次删掉 24 行 Javadoc;clearDeleteInlays 清掉 其它块 红删。
  • :Reject 不再全清 inlay;REJECT_SPLICE 日志对齐 ui/applied/delete 行界。

9.3 3.0.58 --- partial Accept 后保留其它块红删

  • 现象 :Accept L79-102 后 red=6→1,其它块红删没了。
  • shouldKeepRedGhostDespiteEmptyLiveInsert

9.4 3.0.59 --- 多文件 A Accept 后 B 就绪,A 不应全清

  • 现象:ApiJob Accept 完 → orphan-accept → A 顶栏全没。
  • liveZoneCount>0 时 SKIP orphan-accept。

9.5 3.0.60 --- 顶栏 SFN 残留

  • 现象 :块审完仍 flags=SFN
  • hasReviewablePending 驱动顶栏。

9.6 3.0.61 ✅ --- 用户验收

  • 现象:LogQueue AI 写完后无按钮/无绿。
  • ensureCaptureOnGateReady + isCaptureBlocked 对 gate-ready 放行。

9.7 3.0.63 --- 已回滚

  • apply-no-pending keep-liveresolveTargetHunkWithLiveReconcile
  • 用户:要求回滚 3.0.61;红删显示争议未在 3.0.63 验收通过。

10. 真实日志案例(可 grep 复现)

10.1 正常:LogQueue 首次画 UI(应有 red=5)

text 复制代码
APPLY_DECOR ... file=LogQueueSimpleRabbitListener.java ...
  zones=13[INS L3-34, ..., INS L125-125]
  red=5 blankRed=0
  redDetail=@L110[log.info("accountLogNameQueue...")];
           @L112[String newTraceId = ...];
           @L118[log.error(...)]

读法 :replace 的旧行锚在 L110/L112/L118 上方 应有 inlay;绿在 L112--116 是 代码。

10.2 异常:red 有、界面无(显示层)

3.0.63 用户「什么都没点」--- 日志仍 red=5,与 3.0.61 同条日志 一致

结论:算法层算了,UI 层未挂上 --- 后续应查 installPendingVisuals / inlay / TAB_PENDING_NO_TOOLBAR force reapply 时序,而非回滚 effective 逻辑。

10.3 异常:点不动(hunk not found)

text 复制代码
decide hunk-not-found ... zone=L65-72 ... pending=2 hunks=[L43-45, L47-51]
APPROVE_FAIL ... msg=hunk not found

UI zone 行号与 store 粗 hunk 漂移;工具条还在 → 体感「点了没反应」。

10.4 异常:stale-inline 误清绿线

text 复制代码
syncStoreWhenSessionUiExhausted SKIP orphan-accept ... liveZones=8 sessionBlocks=0
apply-no-pending GUARD stale-inline ... storePending=3 zones=0

策略说 不要 orphan-accept,下一行却 清 UI --- 3.0.63 曾修,已随回滚撤销。

10.5 门控:写完了才 READY

text 复制代码
DOC_CHANGE path=LogQueue ... diskLen=4429 gitLen=2292 diskEqGit=false
READY path=LogQueue ... reason=s1-idle-document-cold
GATE_READY_SEED path=LogQueue ... pending=13

11. 测试体系:从手点到 1000 矩阵

11.1 三层

#mermaid-svg-a7L1nyZ5H3iRCRuB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a7L1nyZ5H3iRCRuB .error-icon{fill:#552222;}#mermaid-svg-a7L1nyZ5H3iRCRuB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a7L1nyZ5H3iRCRuB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .marker.cross{stroke:#333333;}#mermaid-svg-a7L1nyZ5H3iRCRuB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a7L1nyZ5H3iRCRuB p{margin:0;}#mermaid-svg-a7L1nyZ5H3iRCRuB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .cluster-label text{fill:#333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .cluster-label span{color:#333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .cluster-label span p{background-color:transparent;}#mermaid-svg-a7L1nyZ5H3iRCRuB .label text,#mermaid-svg-a7L1nyZ5H3iRCRuB span{fill:#333;color:#333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .node rect,#mermaid-svg-a7L1nyZ5H3iRCRuB .node circle,#mermaid-svg-a7L1nyZ5H3iRCRuB .node ellipse,#mermaid-svg-a7L1nyZ5H3iRCRuB .node polygon,#mermaid-svg-a7L1nyZ5H3iRCRuB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a7L1nyZ5H3iRCRuB .rough-node .label text,#mermaid-svg-a7L1nyZ5H3iRCRuB .node .label text,#mermaid-svg-a7L1nyZ5H3iRCRuB .image-shape .label,#mermaid-svg-a7L1nyZ5H3iRCRuB .icon-shape .label{text-anchor:middle;}#mermaid-svg-a7L1nyZ5H3iRCRuB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a7L1nyZ5H3iRCRuB .rough-node .label,#mermaid-svg-a7L1nyZ5H3iRCRuB .node .label,#mermaid-svg-a7L1nyZ5H3iRCRuB .image-shape .label,#mermaid-svg-a7L1nyZ5H3iRCRuB .icon-shape .label{text-align:center;}#mermaid-svg-a7L1nyZ5H3iRCRuB .node.clickable{cursor:pointer;}#mermaid-svg-a7L1nyZ5H3iRCRuB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .arrowheadPath{fill:#333333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a7L1nyZ5H3iRCRuB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a7L1nyZ5H3iRCRuB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a7L1nyZ5H3iRCRuB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a7L1nyZ5H3iRCRuB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a7L1nyZ5H3iRCRuB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a7L1nyZ5H3iRCRuB .cluster text{fill:#333;}#mermaid-svg-a7L1nyZ5H3iRCRuB .cluster span{color:#333;}#mermaid-svg-a7L1nyZ5H3iRCRuB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a7L1nyZ5H3iRCRuB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a7L1nyZ5H3iRCRuB rect.text{fill:none;stroke-width:0;}#mermaid-svg-a7L1nyZ5H3iRCRuB .icon-shape,#mermaid-svg-a7L1nyZ5H3iRCRuB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a7L1nyZ5H3iRCRuB .icon-shape p,#mermaid-svg-a7L1nyZ5H3iRCRuB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a7L1nyZ5H3iRCRuB .icon-shape .label rect,#mermaid-svg-a7L1nyZ5H3iRCRuB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a7L1nyZ5H3iRCRuB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a7L1nyZ5H3iRCRuB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a7L1nyZ5H3iRCRuB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} L1: ./gradlew test

JUnit 纯内存
L2: ReviewMatrix1000

K0001-K1000
L3: 安装 zip + IDEA 手工

  • ~/logs

11.2 场景矩阵(摘录)

场景 ID 摘要如下(原 REVIEW_SCENARIO_MATRIX):

  • AI-1...AI-9:捕获形态(纯增、纯删、replace、多文件...)
  • U-1...U-12:单文件 Keep/Reject 顺序
  • S-1...:多文件顶栏 SFN

11.3 1000 矩阵结果(诚实)

运行 结果
快/慢子集各 50 ✅ 通过
全量 1000 ❌ 约 32m37s 失败,ReviewMatrix1000PureTest.kt:75
定位 -Dmatrix.repro=Kxxxx 单例重放

失败 不阻塞 3.0.61 用户验收,但阻塞「宣称矩阵全绿」。


12. 仍未解决的问题

# 问题 原因 建议方向
1 红删日志有、界面无 inlay 未挂载或被清;非 3.0.61/63 算法分叉 单测 inlay 计数 + 首帧 paint 日志
2 连续 Accept 点不动 zone vs store hunk 漂移 zone-first reconcile(3.0.63 已回滚)
3 Javadoc */ 漏绿 官方 equality + extend 边界 夹具 logqueue 固化
4 同意后绿线闪回 save → VCS 重捕获 approved 过滤 / 禁 decide 后 reconcile
5 BridgeReviewStore 双状态 历史 shim 收敛为只读索引
6 Kotlin vs bridge TS display 不同构 replace 语义 对齐 display-zones.ts 或弃用 TS
7 1000 矩阵未全绿 用例/模拟器边界 repro K 编号逐个修

已知架构问题编号(来自架构图 §8):A 行号漂移 · B 三套 advance · C LiveDiff≠bridge · D 双份 cache · E reconcile 边界 · F 合成 zone id · G bridge 残留。


13. 后续路线与纪律

13.1 建议修复顺序(若有人 fork 继续)

  1. 对照 cc-gui-bridge decideHunk × Kotlin decide()
  2. 收敛 BridgeReviewStore
  3. 统一 DISPLAY replace 语义
  4. decide 只认 zone
  5. 删除多余 suppress 补丁
  6. 6 场景 IDEA 验收

维护者已不执行上述路线。

13.2 发版纪律(历史约定,见附录)

改行为 → 递增版本 + 台账 + tag;先读 ~/logs/cc-gui-review-<version>/;禁止未经论证恢复 3.0.54/55 漂移策略。

13.3 稳定基线(回滚对照)

插件版本 3.0.61
Git 标签 cc-gui-review-3.0.61
功能提交 3dd76a6(2026-06-02 09:36:47 +0800)
用户验收 「这个版本非常好了」
安装包 build/distributions/cc-gui-review-3.0.61.zip
bash 复制代码
git clone https://gitee.com/quyixiao/cc-idea-code-review.git
cd cc-idea-code-review
git checkout cc-gui-review-3.0.61
cd cc-gui-review && ./gradlew buildPlugin
# Restart IDEA → grep '[STARTUP] version=3.0.61' ~/logs/cc-gui-review-3.0.61/*.log

勿随意回退ensureCaptureOnGateReadyChangeCaptureGuard 对 gate-ready 的 bypass。


附录 A:组件清单(72 文件归类)

文件(代表)
启动 PluginStartupActivity, ReviewStartup
INGEST ChangeReceiver, VcsChangeCaptureService, GitReviewSyncService, FileWriteGate, ChangeCaptureGuard
STATE IdeaReviewStore, ReviewSessionState
SHIM BridgeReviewStore, BridgeReviewClient, BridgeReviewSync
DISPLAY LiveDiffModel, CursorZoneMerge, HunkZoneMapper, RedGhostGrouper, PlatformDiffUtil
UI InlineReviewUiService, ReviewZoneRenderer, ReviewDecisionHandler, ReviewHeaderController, ReviewToolbarController

附录 B:Mermaid 图导出

本文含 10+ 张 Mermaid 图。发布到不支持 Mermaid 的平台时:

  1. 打开 https://mermaid.live
  2. 粘贴代码块导出 PNG/SVG
  3. 替换到公众号/掘金正文

附录 C:用户需求与验收状态(原 USER_REQUIREMENTS 并入)

正文 §2 已展开:IDEA 启动/重启/关工程、顶栏 SFN / -F- / S-N§2.10 交互禁忌 、块级 Reject/Keep、FileWriteGate、多文件流程及与 Cursor 差异。努力与停更见文首 从入门到放弃。本附录只做 ✅/⚠️/❌ 速查

产品定位

在 IDEA 实现 Cursor 风格内联审查 :Claude / CC GUI 改码后编辑器内 diff,逐块同意/拒绝,视觉效果接近 Side-by-Side(绿增、红删),不要右侧 ToolWindow 卡片。

Diff 与 Javadoc(你最在意的部分)

需求 状态(停更前台账)
官方 ComparisonManager,不手写 diff
只标真新增行,整段 hunk 不全绿 ✅ 自动化;👤 IDE 仍偶发错
未改行不绿(@Value 等) ✅ REQ-3.1.2
红删 inlay ✅ 算法;👤 目视争议
Javadoc /** */ @param* ✅ 多条 REQ;👤 曾漏 */
典型验收文件 LogQueue / ApiJob 人工主路径

顶栏逻辑(原话规则)

详见 §2.3 (含三档对照表与 computeHeaderFlags 输入)。

  1. Review Next :仅当其它文件有待审,与当前文件是否审完无关。
  2. Reject All / Keep All (需求文档曾写「撤销所有 / 保存所有」):与 session 级 S 位组合;当前文件无 live pending 时不显示 -F- 两钮。
  3. 当前文件审完 → 顶栏干净(或只剩 Review Next,即 S-N)。

仍未验收或曾失败项

需求 状态
同意后绿线不闪回 ⚠️❌ 多次报告
Git 回滚后顶栏/绿线全清
保存所有 / 撤销所有 ⚠️
不强制跳回当前审阅文件 ❌ 曾报告(见 §2.10.1
交互禁忌全集 §2.10

附录 D:架构缺口与官方对比(原 ARCHITECTURE_GAPS 并入)

给开发者的一句话 :用了官方 diff API,没有 官方状态模型。在 session 缓存 hunk + 块级同意 + 多次 VCS 重捕获下,局部修复会在另一事件顺序里复现。应先 effective baseline + 渲染时现算 diff ,再谈 */ 和按钮。

官方 vs 我们

维度 IntelliJ 我们
数据 BASE vs CURRENT,每次现算 effective vs document + store hunks
块级 Accept
只绿新增 不承诺 自研,bug 面大

备选方案(未采用)

  • 方案 C:只做文件级保存/撤销,绿线整文件 diff --- bug 最小,与 Cursor 产品目标不符。

附录 E:版本台账摘要(原 VERSION_HISTORY 顶栏)

版本 要点 状态
3.0.61 gate-ready 补种 + suppress bypass ✅ 用户「非常好」
3.0.62 1000 矩阵测试,产品同 3.0.61 🔧
3.0.63 apply-no-pending / decide reconcile 已回滚
3.0.54--55 SessionStoreDrift ❌ 勿用
3.0.57--58 红删 Reject/partial Accept ⚠️
1.0.77--87 session 缓存架构 ❌ 未验收

当前 masterbe0c87b 产品 = 3.0.61。

当前推荐安装 :仅 3.0.61 zip,勿装 3.0.63。


附录 F:自动化测试原则(原 AUTOMATED_TESTING 并入)

bash 复制代码
cd cc-gui-review
./gradlew test --offline    # 约 1--3 分钟
阶段 不做
Gradle test 夹具 + 模拟 Accept/Reject 不点真按钮
日志形态 grep RENDER 同构 不要求先读 logs
IDEA 手工 test 全绿后再装 zip 避免手点几百次

1000 矩阵:ReviewMatrix1000PureTest --- 快/慢子集 50+50 ✅;全量约 33min 失败


附录 G:Bridge 迁移对照(原 BRIDGE_MIGRATION 并入)

cc-gui-bridge (TS) cc-gui-review (Kotlin)
display-zones.ts LiveDiffModel + CursorZoneMerge
review-store.ts decide IdeaReviewStore.decide
advanceEffectiveBaseline EffectiveBaselineUtil
600ms 静默 FileWriteGate
hunk-builder VCS VcsDiffToBridge
:9876 可选 ChangeReceiver,默认关

已删除:9875 daemon、bridge.db 热路径、lumencode 统计。


附录 H:发版与日志纪律(原 release-governance 并入)

  1. 改行为前读 ~/logs/cc-gui-review-<version>/,核对 [STARTUP] version=
  2. 禁止 恢复 3.0.54/55:SessionStoreDriftPolicy、STALE-UI 强制 rematerialize、zonesFor 双算等。
  3. 单次 diff 只修日志证实的一条根因。
  4. 未 Restart IDEA 不算验证新包。

日志判读速查

日志 含义
GUARD suppressed timed-until 连点防抖,后台仍可能 decide
APPLY_DECOR ... red=N 算法层删除锚点数
APPROVE_FAIL hunk not found zone/store 漂移
GATE_READY_SEED 3.0.61 补种成功

附录 I:卸载插件(可选)

  1. IDEA → Settings → Plugins → 禁用或卸载 cc-gui-review
  2. Restart IDEA。
  3. 可选删除日志:rm -rf ~/logs/cc-gui-review-*

本文档为自包含终稿:不依赖仓库内其它 md 链接即可阅读全貌。Git:https://gitee.com/quyixiao/cc-idea-code-review.git · 停更于 2026-06-02 · 从入门到放弃的原因见文首。

相关推荐
码不停蹄的玄黓1 小时前
Java 频繁GC 完整排查流程
java·开发语言
源码宝1 小时前
基于SpringBoot+Vue+小程序+Android的智慧校园电子班牌系统源码示例
vue.js·spring boot·架构·智慧校园·电子班牌·源码·代码
IronMurphy1 小时前
微服务拷打最后一讲!!!
java·微服务·架构
凤山老林1 小时前
73-Java ListIterator 接口
java·开发语言
Roy_Sashulin1 小时前
灵杉Java编程平台与传统开发工具区别
java·开发语言
lauo1 小时前
从算力消耗到Token生产:ibbot手机如何重构AI时代的移动终端价值范式
人工智能·智能手机·重构·架构·开源·github
聚铭网络1 小时前
聚铭网络荣获《一种分层架构的安全运营平台的数据保护方法及系统》发明专利
网络·安全·架构
切糕师学AI1 小时前
深度解密现代零信任 Full-Mesh 安全网络:架构演进、NAT 穿透原理与企业私有网络实践
网络·安全·架构
土狗TuGou1 小时前
SQL进阶笔记 · 第1篇:存储引擎
java·数据库·笔记·后端·sql·mysql