在 IntelliJ IDEA 里复刻 Cursor 式内联审查:架构复盘
写给谁 :读过就能知道项目发生了什么,无需打开仓库里其它 md 。
截至 :2026-06-02 · 项目状态 :停更 / 放弃继续用 IDEA 做块级审阅 (维护者决定不再折腾插件)
最后可用产品版本 :
3.0.61(tagcc-gui-review-3.0.61,commit3dd76a6)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 和按钮才冒出来,体验像「事后诸葛亮」。
当时的想法(很合理)
- 在 Node 里挂 Claude Code 的 PreToolUse / PostToolUse(生态里 hook 就是 JS/TS 最成熟)。
- 在 AI 落盘前 解析
old_string/new_string,拼好changes[]和newFileContent。 - 用
cc-gui-bridge在进程外算 zones、记bridge.db,经:9876POST给插件里的ChangeReceiver。 - 甚至可选
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 失败了所以赌气删包」,而是想通了两件事:
- 块级审阅的权威状态必须在 IDEA 进程内------跟 Document、CLM、EDT 同生共死。
- 展示必须每次从
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 再把权威状态收到 IdeaReviewStore ,3.0.3 删除 cc-gui-bridge npm 包 ;3.0.1 加 FileWriteGate (600ms 写稳再 UI),终端 CC 与 CC GUI 统一 Git CLM + VCS 捕获。
第三阶段:3.0.50--3.0.61(最接近成功)
- 3.0.54 / 3.0.55 :
SessionStoreDriftPolicy等改动引入回归 → 整批回滚。 - 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→再重启 |
减少「同版本缓存」误判 |
| 人工验收 | 反复用 LogQueueSimpleRabbitListener 、ApiJobApiApplication 做 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 业务场景
- Claude Code(终端) 或 CC GUI(IDE 内) 修改 Java 工程(如
eb-service-api)。 - 一次任务常改 多个文件 (例如
ApiJobApiApplication.java+LogQueueSimpleRabbitListener.java)。 - 用户希望在 当前编辑器 里:
- 看到 绿增 (真正新加的行)、红删(被替换掉的旧行);
- 对 每一块 点 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 插件比想象中复杂」。实现以代码为准(ReviewSessionHeaderBar、ReviewStartup、FileWriteGate 等)。
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」+ 全套顶栏 | resetOnProjectOpen → BridgeReviewStore.clearAll() + 清绿线/顶栏 |
| 不要 startup 补种 | reset 后 ~5s 不要用 CLM 里已有 modified 做 startup-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+K(Ctrl+Shift+K)可 强制清 session(应急)。
2.2.3 安装 / 升级插件
| 步骤 | 要求 |
|---|---|
| 打包 | ./gradlew buildPlugin → cc-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 Next (SFN) |
| 是 | 否 | Reject All | Keep All | Review Next (S-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=2仍 SFN → 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 工具窗不影响 | --- |
| 悬停 | 鼠标在块上显示工具条(与顶栏独立) | --- |
块级决策后:ReviewDecisionHandler → IdeaReviewStore.decide → 刷新绿/红/inlay。
2.5 Diff 与展示(用户坚持的规则)
| 需求 | 说明 |
|---|---|
| 官方 API | 必须 ComparisonManager / DiffUtil,禁止手写 diff |
| 只绿真插入 | @Value、未改行、下移复制行 不能绿 |
| 绿颜色 | 浅绿 #E8F5E9,非 IDE 默认亮绿 |
| 红删 | 删除行用 红色 inlay(锚点行上方),非行内红底 |
| Javadoc | /**、* 行、*/、@param 无 * 等都要覆盖 |
| 验收文件 | LogQueueSimpleRabbitListener 、ApiJobApiApplication 为主 |
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 File 时 focusEditor=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 | ChangeCaptureGuard 对 gate-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 + ReviewHeaderState;3.0.60 起 filePending 必须看 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_SEED、refreshAndShow |
环境变量: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 ≠ git,ensureCaptureOnGateReady 用 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.59 :syncStoreWhenSessionUiExhausted 在 liveZoneCount>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-live;resolveTargetHunkWithLiveReconcile。 - 用户:要求回滚 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 继续)
- 对照
cc-gui-bridgedecideHunk× Kotlindecide() - 收敛
BridgeReviewStore - 统一 DISPLAY replace 语义
- decide 只认 zone
- 删除多余 suppress 补丁
- 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
勿随意回退 :ensureCaptureOnGateReady、ChangeCaptureGuard 对 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 的平台时:
- 打开 https://mermaid.live
- 粘贴代码块导出 PNG/SVG
- 替换到公众号/掘金正文
附录 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 输入)。
- Review Next :仅当其它文件有待审,与当前文件是否审完无关。
- Reject All / Keep All (需求文档曾写「撤销所有 / 保存所有」):与 session 级 S 位组合;当前文件无 live pending 时不显示 -F- 两钮。
- 当前文件审完 → 顶栏干净(或只剩 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 缓存架构 | ❌ 未验收 |
当前 master :be0c87b 产品 = 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 并入)
- 改行为前读
~/logs/cc-gui-review-<version>/,核对[STARTUP] version=。 - 禁止 恢复 3.0.54/55:
SessionStoreDriftPolicy、STALE-UI 强制 rematerialize、zonesFor双算等。 - 单次 diff 只修日志证实的一条根因。
- 未 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:卸载插件(可选)
- IDEA → Settings → Plugins → 禁用或卸载
cc-gui-review。 - Restart IDEA。
- 可选删除日志:
rm -rf ~/logs/cc-gui-review-*。
本文档为自包含终稿:不依赖仓库内其它 md 链接即可阅读全貌。Git:https://gitee.com/quyixiao/cc-idea-code-review.git · 停更于 2026-06-02 · 从入门到放弃的原因见文首。