Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
- finding 卡的「编辑 / 拒绝」由 anchor 行的文字按钮改为头部右上角图标栏的图标(评论气泡 / 圆形禁止),排在「引用」转发箭头左侧,与标题同排成组;anchor 行仅保留草稿状态 / 复评关闭 chip。
- 删除按钮统一为高饱和危险红:单条记录删除(垃圾桶)与顶部「清空历史」hover 由偏浅鲑红改为 `$color-danger-strong`,与拒绝 finding / `.btn-icon-danger` 一致。
- `/review` 输出隐藏「评估工作量」(effort)段——实用价值低,不再展示。
- 复评 `/ask` 取代 / 撤销改为静默自动关闭:引用某条 review/improve 评论发起的复评 `/ask`,裁决为「取代 / 撤销」时**自动**关闭被引用的原 finding(建立关闭关系),无需再手动点「关闭原」;裁决「取代」时把建议提升为**带代码定位**的代码反馈卡(取原评论的 anchor),渲染 / 采纳同 `/review` 代码反馈(点头部评论图标即转为锚定原位置的行内评论草稿)。裁决「保留」不动、且不再展示「保留原评论」标记(无破坏性动作,标记冗余)。自动关闭失败时仍回退到结果卡的手动动作
- 复评 `/ask` 取代 / 撤销改为静默自动关闭:引用某条 review/improve 评论发起的复评 `/ask`,裁决为「取代 / 撤销」时**自动**关闭被引用的原 finding(建立关闭关系),无需再手动点「关闭原」;裁决「取代」时把建议提升为**带代码定位**的代码反馈卡(取原评论的 anchor),渲染 / 采纳同 `/review` 代码反馈(点头部评论图标即转为锚定原位置的行内评论草稿)。裁决「保留」不动、且不再展示「保留原评论」标记(无破坏性动作,标记冗余)。关闭纯由后端裁决驱动:移除结果卡面向用户的「采纳并关闭原 / 仅关闭原 / 关闭原评论」与原 finding 卡的「撤销关闭」按钮,前端仅**只读**展示「已被复评取代/关闭」chip + 「查看复评」导航(引用发起复评、点击引用徽标定位高亮原卡仍在)
- `/ask` 引用展示简化与文案微调:复评结果卡顶部徽标与输入框引用 chip 不再用「复评自 / 复评 …」文案,直接显示引用定位(结果卡:转发箭头 + **完整路径:行号**,换行规则同代码建议定位、点击回链原评论;输入框 chip:只显示**文件名**),删除 `chipLabel` / `reviewedFrom` / `reviewedFromTitle` 三个 i18n key 减少维护;`/ask` 结构化「分析过程」段标签改为「分析解读」,其正文 H2 小节标题字号调小(靠加粗区分)、小节间用分割线隔开;该段展开时在 chip 行下加一条分割线,与下方富文本内容衔接更自然。
- agent「评审总结」聚焦 PR 整体结论:`/ask` 改富文本后,追问答案(表格 / 代码块 / 逐条建议)此前被整段灌进总结输入、诱导模型照搬明细,背离「总结=控制篇幅的整体结论」初衷。现总结只吃每条追问的**结论**(ask-summary),提示词重写为「综合描述 / 评审发现 / 追问结论 → 输出 PR 整体结论,不复制明细」,并允许总结内适度用表格 / 引用 / emoji;「概述 / 发现 / 建议」三段间加分割线。
- `/ask` 结论段标签由「概述」改为「结论」(四语言对齐:Conclusion / 結論 / Fazit),更贴合其「直接结论」定位。
Expand All @@ -72,6 +72,10 @@
- 设置面板「连接 / LLM 配置」模态复用首启向导的左右布局:左侧选集成平台 / LLM provider、右侧填表单(复用的 `PlatformPicker` / `LlmProviderPicker` 统一在 settings 域维护,首启向导同步复用)。LLM 模态与向导 LLM 步改为固定高度、两栏各自滚动——切换 provider 不再抖动、provider 列表后续扩展也不撑高,向导两子步等高对齐;CLI 模式补「实验性」标记、名称 / 命令占位提示 `claude / codex`、文案精简(去品牌名与内部细节);必填校验改为只标红框(去错误文案、消除控件位置抖动);「测试连接」按钮收窄为 `btn-sm` 并与结果文案垂直对齐。
- 危险按钮统一为高饱和度红描边:新增 `$color-danger-strong` token,删除评论(评论 tab + 行内)/ 删除草稿 / 删除连接 / LLM、停止、拒绝 finding 等按钮 hover 由偏浅鲑红改为与模态删除按钮同色系的饱和红,警示力更强、全局一致(保留 ghost 描边风格,不改为实底)。

- PR 提交列表 / 活动时间线按 first-parent 过滤合入的他人提交:平台 `/commits` 返回 `target..source` 全集,长期分支 / fork 同步分支历史上反复把别的分支 merge 进源分支,会带出大量 merge 提交与合入的他人提交、淹没本 PR 真正引入的提交。改用本地镜像 `git rev-list --first-parent --no-merges merge-base..source` 算「本 PR 自产提交」SHA 集合对平台返回做交集过滤;提交数角标同口径对齐;镜像未就位 / 算不出时回退未过滤列表、不丢信息(三平台统一收口)。

- ChatPane / Diff 评审界面一批交互打磨:评审总结卡与 finding 卡同宽、加蓝色左条(与蓝色淡底成一套);可折叠卡(分析解读 / 已拒绝 / 被复评关闭的代码反馈)整行标题区即展开 / 收起热区、收起态上下内边距对称、折叠 / 展开带高度过渡动画(尊重「减少动效」);已有评论的行也可 hover「+」继续追加行内评论(新评论按时间序展示在已有评论下方);点击复评引用徽标精确定位到原 finding 卡并按其类别色闪烁高亮(已关闭 / 拒绝卡用中性灰),引用徽标图标随首行、行号跟随路径末行排版修正;diff 头部 reviewer 打勾角标缩小一号并去描边环;「原始输出」折叠标题去掉「(xx chars)」字数;判定解析失败的兜底不再输出「无法解析建议,转人工复核」灰字(仅保留判定徽标);「思路建议」折叠方案标题支持内联 markdown(`代码` / **强调**,shim 提示词同步放开标题禁用反引号的限制);评审总结正文行距 / 字号与其它卡片统一($fs-md → $fs-lg / $lh-normal)。

### Fixed

- 复评 `/ask` 取代裁决的「改进建议」改为可直接发布的评论本身:此前 `<suggestions>` 常被写成「建议将原评论替换为…/请确认…」这类**关于评论的元讨论**(还出现「原评论」概念),无法被评审者直接采用。现提示词要求 `replace` 裁决下 `<suggestions>` **只包含替代评论本身**——以标准 review 评论的口吻直接针对代码、按问题 → 影响 → 建议分段、可原样发布,不提「原评论 / 替换 / 请确认」等元信息;被取代的原评论仍按 `replace`/`drop` 裁决自动关闭。
Expand All @@ -92,9 +96,11 @@
- PR 主面板各 tab(diff / 评论 / 草稿 / 提交 / 信息)切换抖动:此前 tab 内容按条件渲染,每次切换旧面板卸载、新面板重挂 → 重新拉数据、闪「加载中」、内嵌 Monaco 重建。改为 keep-alive——tab 首访才挂载(保留懒加载)、之后保活仅 CSS 显隐不卸载,切走再切回瞬时、无重拉、滚动位置与展开态保留;配合 Monaco `automaticLayout` 处理显隐后的重排。
- 刷新(后台轮询 / 窗口聚焦)时编辑器渲染抖动:评论页内嵌代码片段(Monaco)与 diff 编辑器此前每次刷新都重渲染 / 重建。根因有二——其一,i18n 语言切换 effect 依赖整个 boot 对象,poll 刷新 setBoot 后对同一语言反复 `changeLanguage`,触发 `languageChanged` 致所有 `useTranslation` 的 `t` 换新引用,凡 effect 依赖 `t` 的组件(如内嵌代码片段抓取逻辑)都被无谓重跑、连带 Monaco 卸载重建;其二,DiffEditor 的 `options` 为渲染期新建对象,被 `@monaco-editor/react` 按引用判变而反复 `updateOptions`。现语言 effect 仅在语言真正变化时切换、DiffEditor options 稳定化,刷新不再抖动。
- PR 详情页与评论页排版:正文限宽 960px 并居中,滚动条回到外层容器右缘(此前 max-width 加在滚动容器上,滚动条停在中部);详情页 reviewers 列表按字典序固定排序,刷新不再随平台返回顺序抖动。
- 拉取变更文件列表偶发失败(`ENOENT … diff-base.json`):状态存储对同一 key 的并发写共用同一临时文件,先完成者 rename 后,后完成者 rename 即 ENOENT。临时文件名追加进程内自增序号去重,并发写各用独立临时文件。
- 拉取变更文件列表偶发失败(`ENOENT … diff-base.json`):状态存储对同一 key 的并发写共用同一临时文件,先完成者 rename 后,后完成者 rename 即 ENOENT。临时文件名追加进程内自增序号去重,并发写各用独立临时文件。Windows 上并发写同一 key 还会撞 `fs.rename` 覆盖既有文件的瞬时 EPERM/EACCES/EBUSY(打开 / 切换 PR 时多 handler 同写 diff-base.json)→ rename 加退避重试自愈(并打 warn 定位日志),同时对 diff-base 解析按 PR 去重、从源头收敛并发写。
- Agent 评审 / 规划步骤行的固定文案(如「判断是否存在需追问的严重问题」「严重,追问 N 个」)此前在 `@meebox/agent` 层写死中文、被渲染层逐字显示,日 / 英 / 德界面下漏出中文;现按会话语言落地(zh-CN / en-US / ja-JP / de-DE,缺省回落英文),与评审总结骨架同策略。
- 设置页手动「检查更新」查到的新版此前不同步到状态栏、也不缓存:手动检查只把结果回给设置页本地,与定时检查各自为政、无共享。现 main 侧统一为单一真相源——手动 / 定时检查都缓存结果并在有新版时广播 `app:updateAvailable`,状态栏即时出现升级 chip;新增只读 `app:getUpdateStatus`,窗口 / 状态栏挂载时水合已知结果,不因重挂载而丢失。
- 合并已合并 / 已关闭的 PR 报错不友好:Bitbucket 对已合并 PR 的合并请求回 409 + `IllegalPullRequestStateException`(本地状态滞后于远端:他人已合 / 重复点击),此前把原始 409 stack 抛给用户。归一为错误码 `EPR0003`,前端按码做 i18n 友好提示(四语言对等);其它 409(冲突 / veto)原样冒泡。
- 评审总结被截断 / 无法解析(回落「无法解析建议」):原把整段 markdown 总结塞进 JSON 字符串字段,正文里的引号 / 换行 / 代码块会破坏 JSON 解析,回退打捞时又在首个内层引号处把正文腰斩、且判定一并丢失 → 回落 manual_review。改为模型直接输出纯 markdown 正文 + 末尾一行扁平判定 JSON,正文走 `stripTrailingJson`(含对被截断 dangling 判定 JSON 的兜底剥除)、判定走新增 `extractTrailingJson` 单独解析(兼容旧嵌套格式),并给收尾 chat 显式输出 token 上限避免被 provider 默认上限截断。

## [0.5.0] - 2026-06-17

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"this exact structure: (1) the intro line \\'The following are alternative approaches to this PR:\\'; "
"(2) then 2-4 plausible ALTERNATIVE implementation approaches, EACH formatted as a <details> block "
"with BLANK LINES inside so the body is parsed as markdown (this exact layout, blank lines required): "
"a line '<details><summary>N. concise PLAIN-TEXT approach title (NO backticks or markdown inside the "
"summary)</summary>', then a blank line, then 1-3 sentences explaining the approach and its main "
"a line '<details><summary>N. concise approach title (you MAY use `inline code` for identifiers in the "
"summary; keep it short)</summary>', then a blank line, then 1-3 sentences explaining the approach and its main "
"trade-off (you MAY use `inline code` for identifiers in this body), then a blank line, then the line "
"'</details>'; (3) after the last </details> leave ONE blank line, then a paragraph starting with "
"**Recommendation:** that compares the current approach against the alternatives and recommends one. "
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/main/i18n/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"judgeNone": "Keine wichtigen Probleme — keine Rückfrage",
"judgeSevere_one": "Wichtig — {{count}} Rückfrage",
"judgeSevere_other": "Wichtig — {{count}} Rückfragen",
"parseFail": "Empfehlung konnte nicht geparst werden — manuelle Prüfung",
"rejectedPrefix": "Abgelehnt: ",
"summary": "Beschreibung und Befunde zu einer Review-Zusammenfassung zusammenfassen"
},
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/main/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"judgeNone": "No important issues — no follow-up",
"judgeSevere_one": "Important — {{count}} follow-up question",
"judgeSevere_other": "Important — {{count}} follow-up questions",
"parseFail": "Could not parse a recommendation — routing to manual review",
"rejectedPrefix": "Rejected: ",
"summary": "Synthesize the description and findings into a review summary"
},
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/main/i18n/locales/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"judge": "追加質問が必要な重要な問題があるか判断",
"judgeNone": "重要な問題なし、追加質問なし",
"judgeSevere_other": "重要、追加質問 {{count}} 件",
"parseFail": "提案を解析できないため、手動レビューに回します",
"rejectedPrefix": "却下:",
"summary": "説明とレビュー指摘を統合してレビュー要約を生成"
},
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/main/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"judge": "判断是否存在需追问的重要问题",
"judgeNone": "无重要问题,不追问",
"judgeSevere_other": "重要,追问 {{count}} 个",
"parseFail": "无法解析建议,转人工复核",
"rejectedPrefix": "拒绝:",
"summary": "综合描述与审查发现,生成评审总结"
},
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/main/services/agent/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export function buildStepLabels(): AgentStepLabels {
judgeSevere: (n) => t('agent.steps.judgeSevere', { count: n }),
judgeNone: t('agent.steps.judgeNone'),
summary: t('agent.steps.summary'),
parseFail: t('agent.steps.parseFail'),
rejectedPrefix: t('agent.steps.rejectedPrefix'),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,23 +212,26 @@ export function ChatPane({

const { runs, error, loadingSession, matchedRule, bodyRef, hasMoreOlder, loadingOlder } = session;

// 复评卡 ↔ 原 finding 卡互链:按 data-run-id 在时间线里滚动定位 + 短暂高亮。
const flash = (el: Element): void => {
// 复评卡 ↔ 原 finding 卡互链:滚动定位 + 短暂高亮。flash class 因目标而异:run 卡用 chat-run-flash
// (背景渐隐,run 卡本身透明底可见);finding 卡用 chat-finding-flash(覆盖式高亮环——finding 卡有
// 实底 $bg-elev,背景渐隐会被洗掉、看不出闪烁)。
const flash = (el: Element, cls: 'chat-run-flash' | 'chat-finding-flash'): void => {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('chat-run-flash');
window.setTimeout(() => el.classList.remove('chat-run-flash'), 1500);
el.classList.add(cls);
window.setTimeout(() => el.classList.remove(cls), 1600);
};
const scrollToRun = (runId: string): void => {
const el = bodyRef.current?.querySelector(`[data-run-id="${CSS.escape(runId)}"]`);
if (el) flash(el);
if (el) flash(el, 'chat-run-flash');
};
// 点击复评卡顶部引用徽标:精确定位到原 run 内被引用的那条 finding 卡片并闪烁高亮(找不到该卡片——
// 如已分页移出 / 折叠——回退到整条 run 高亮,至少给出定位反馈)。
const scrollToFinding = (runId: string, findingId: string): void => {
const runEl = bodyRef.current?.querySelector(`[data-run-id="${CSS.escape(runId)}"]`);
if (!runEl) return;
const el = runEl.querySelector(`[data-finding-id="${CSS.escape(findingId)}"]`) ?? runEl;
flash(el);
const findingEl = runEl.querySelector(`[data-finding-id="${CSS.escape(findingId)}"]`);
if (findingEl) flash(findingEl, 'chat-finding-flash');
else flash(runEl, 'chat-run-flash');
};

return (
Expand Down
Loading
Loading