ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

codegraph CG-30:为 explore 的超大簇成员设定 1.5 倍上界 —— 基准数据、A/B 评测与源码实现

codegraph CG-30:为 explore 的超大簇成员设定 1.5 倍上界 —— 基准数据、A/B 评测与源码实现 codegraph CG-30:为 explore 的超大簇成员设定 1.5 倍上界 —— 基准数据、A/B 评测与源码实现【免费下载链接】codegraphPre-indexed code knowledge graph, auto syncs on code changes, for Claude Code, Codex, Gemini, Cursor, OpenCode, AntiGravity, Kiro, CoPilot, and Hermes Agent — fewer tokens, fewer tool calls, 100% local项目地址: https://gitcode.com/GitHub_Trending/co0degr/codegraphCG-30 是 codegraphexplore检索管道的一次预算治理变更:当某个文件簇(cluster)里排名第一的成员(通常是单个超长函数)体积远超该文件被分配到的预算时,旧实现会把整个成员原样输出,挤爆响应信封;CG-30 为其引入1.5 倍预算的封顶——超过上界后按整行开窗(windowed)而不是整体输出,从而把字节让渡给排名更低的文件。本文完整复现该变更的 A/B 基准报告:先看确定性测量中上界真实生效的位置,再看 agent 跑批的行为代价与充分性(bar),最后深入 src/mcp/tools.ts 中renderCluster/windowToCeiling的实现,并说明回归测试与夹具如何把这次修复永久钉住。变更背景与评测条件变更日期:2026-08-06 ·分支:bugfix/CG-30·基线:maind6d1728评测脚本:scripts/agent-eval/ab-new-vs-baseline.sh,--model sonnet --effort high,两臂均为 codegraph 开启状态,CLI 被阻塞(所有运行中 0 污染),并设置CODEGRAPH_NO_PROMPT_HOOK1。问题本身一句话:一个簇的头部成员可以在多大程度上超出其文件可花费的预算?在 CG-30 之前,答案是没有上限。shrinkCluster有意保留超大簇中重要性最高的成员整体输出——因为空的文件段落会把 agent 逼去 Read,这正是explore要防止的结局;但永不为空不等于任意大小。CLAUDE.md 点名的风险正在于此:一个不再充分的章节会把 agent 引向 Read,而一两次这样的经历就会教会它彻底不再调用 codegraph。A/B 脚本 ab-new-vs-baseline.sh 的设计值得注意:两臂都挂载 codegraph,因此它隔离的是检索变更本身,而不是 codegraph 的采用与否;CLI 被 no-cli-shim.sh 阻塞的原因也是归因性的——通过 Bash 发出的 explore 会被记到 Bash 名下,永远不会进入充分性与分配效率的解析。脚本会为每臂预热一个持久化 codegraph daemon,并以CODEGRAPH_WASM_RELAUNCHED1跳过启动重执行,避免 agent 在 codegraph 完成约 2–3 秒启动前就扑进 Read/grep。评测脚本踩坑记录(原文报告专门保留,因为它代价是一次重跑):ab-new-vs-baseline.sh在基线臂运行期间会把引擎 checkout 到 BASELINE ref,退出时再恢复。运行期间不要 commit——中途的 commit 会捕获到基线源码。django/gin 的第一批数据正是因此作废(它们的changed:行只列出explore-diagnostics.ts,即两臂跑的是同一套检索代码),随后被重跑。相信这份评测工具的任何 A/B 结论之前,先检查那一行。确定性测量:上界究竟在哪里生效同一份索引、同一个查询、两个构建。这是主要证据;下面的 agent 跑批只是给风险定价。django:签名缺陷的复现与关闭查询:codegraph explore How does a QuerySet turn into SQL and fetch rows from the database?文件baselinenewdjango/db/models/query.py3,669 预算上输出 7,784 字符 ——2.12x5,464 ——1.49x,已开窗django/contrib/admin/filters.py3,633(继承 2,271 可花费)8,057(继承 9,160)交付源码17,929 字符,5 个文件20,033字符,5 个文件这就是 CG-30 的签名缺陷,在公开仓库上复现后被关闭:排名第 1 的文件花了预算的 2.12 倍,其下方的文件继承了这份亏空(下文的 CG-31 会处理继承的另一半)。封顶之后,这些字节直接沿排名顺序让渡——响应携带同样的 5 个文件,并且多出2,104 字符的真实源码。gin:对照仓库,字节级一致的证明两个构建对路由分发查询产生字节级一致的 explore 输出(13,457 字符)。gin 中没有任何成员大到能让上界生效(观察到的最大值仅占可花费预算的 0.94x),这正是对照仓库应该呈现的样子——它同时意味着 agent 表中所有 gin 数字都是逐次运行的方差,而不是变更本身的效果。夹具:同一个缺陷的两个侧面夹具tests/fixtures/oversize-member-ts/ 构造了三个报表构建器争抢同一个信封的场景,每个文件都是单个超长函数(如 monthly.ts 约 509 行、核心是约 490 行的buildMonthlyReport,quarterly.ts约 235 行、核心约 200 行):文件baselinenewmonthly.ts(24.5K,单个 ~490 行函数)3,334 预算上输出 12,391 字符 ——3.7x4,941 ——1.48x,已开窗quarterly.ts(11.4K,单个 ~200 行函数)被丢弃——budget-clusters,没有剩余空间交付 4,004响应19,223 字符,3 个文件15,852 字符,4 个文件两行是同一个缺陷的正反两面:成员大于文件份额会吃满信封;成员大于整个响应上限则会直接让文件消失。这个行为由tests/explore-oversize-member.test.ts 钉住(9 个测试;在main上有 4 个失败)。Agent 跑批行为数据django newdjango basegin newgin baseexcalidraw newexcalidraw baseruns553322duration (s)39 [36–71]35 [35–60]39 [37–51]34 [28–46]52 [43–60]41 [40–42]tool calls3 [3–10]4 [3–23]4 [3–4]34 [3–5]4 [3–4]codegraph calls2 [2–3]2 [0–3]2 [2–3]23 [2–4]3 [2–3]Read0 [0–5]0 [0–13]0 [0–1]000Grep/Glob000000occupancy share33.1% [30.7%–49.5%]34.4% [29.3%–47.3%]28.9% [26.4%–37.3%]30.1% [28.6%–31.9%]43.0%39.3%allocation efficiency100.0%98.6%88.5%98.3%85.8%90.5%django 数据跨两个批次合并(n2 n3)。查询分别是:django How does a QuerySet turn into SQL and fetch rows from the database? Trace the flow end to end.;gin How does a registered route handler get invoked for an incoming HTTP request?…;excalidraw How does updating an element re-render the canvas on screen?…。充分性——按调用池化,这才是真正要看的 bar。django:new 臂 10 次应答调用中出现 1 次Read 了我们返回过的文件(开窗会首先触发的分配未命中信号),而 baseline 是 10 次中 1 次Read 了我们未返回的文件 1 次 Grep——这是未命中类型的转移,不是次数的增加。gin:7 次中 1 次分配未命中,对 6 次中 0 次;而两个构建在 gin 上输出的是相同字节,所以按构造这就是方差。excalidraw:两臂 0 未命中。新臂看起来更差的地方,以及为什么不构成回归:django 时延中位数慢约 10%。n5 下区间重叠(36–71 对 35–60),且 baseline 臂有一次运行完全丢失了 codegraph 挂载(0 次 codegraph 调用、13 次 Read、23 次工具调用),这同时向两个方向扭曲了该臂的分布。gin 分配效率 88.5% 对 98.3%。两个构建在 gin 上字节一致。这是该指标文档记载的相对性——归因基于引用,而 agent 的后续查询逐次不同——不是变更的效果。excalidraw 的占用率/时延。调用次数噪声:new 臂两次运行中有一次做了第 4 次 explore 调用(baseline 是 2–3 次),时延、信封和占用率都跟着它走。按调用计的信封是平的(20,015 对 19,446 字符/次),Read/Grep 保持 0,工具调用数一致。CLAUDE.md 自带的 worked example 在这个 prompt 上记录的也是 3–10 次 codegraph 调用。源码实现:上界从哪里来、窗口如何切1.5 倍上界与SPINE_CEILING在 src/mcp/tools.ts 的簇选择循环里,每文件的预算是该文件的预留量(reservation)被硬上限前的剩余值封顶:const fileBudget Math.min(allowance, fundedHeadroom); // 调用路径簇可以超出预留量,但有界 —— 1.5 倍预留且绝不超过 ceiling const SPINE_CEILING Math.min(Math.round(allowance * 1.5), fundedHeadroom); ... const cap rc.c.hasSpine ? SPINE_CEILING : fileBudget; // CG-30: shrinking keeps the top member whole however big it is, so bound // how far that member may overshoot — the same 1.5x-of-reservation bound const ceiling Math.max(cap, SPINE_CEILING);注意几个从源码可确认的事实:1.5 这个数字与既有规则同源。SPINE_CEILING早在 CG-30 之前就已存在(为调用路径簇画的界),CG-30 把簇内单个超大成员的开窗上界复用了同一个 1.5 倍预留量,并保证ceiling cap,所以已经装得下的簇永远不会被动到。上界从共享信封中抽取。注释(src/mcp/tools.ts)明确:1.5 倍是从共享信封里抽的,因此它恰好等于位移守卫需要资助的超额部分;超过fundedHeadroom之后,多出来的那半个预留量是别的文件的,不是空闲房间。这也是它读取fundedHeadroom而非headroom的原因(CG-31 的一半:后者包含每个未到达文件的全部预留量,花掉它们正是一个簇化文件让五个被准入的同行归零的机制)。保留头部成员的永不整体丢弃原则被保留。shrinkCluster中(src/mcp/tools.ts)仍然总是保留最重要的范围,即使它自己就是超大的——空段落会把 agent 送去 Read,代价高得多;改变的是它可以超出的幅度被调用方的 ceiling 封顶,失控成员被开窗而非丢弃。windowToCeiling:按整行开窗的裁剪器真正的开窗逻辑在 src/mcp/tools.ts 的windowToCeiling。几个关键设计:成本按渲染行计算。lineCost把行号本身的宽度也算进预算,所以装得下的判定是精确的,不是近似。永远不为空。第一个超界的段会被截成头部窗口(head window),并且允许突破room的下限是MIN_WINDOW_LINES(12 行)——注释给出的理由很直接:短于 12 行的碎片不值得输出,反而有害,因为会话记录会声称持有 4 行的碎块,而下一次调用的去重要么把整块切碎、要么重发它。低于地板时该段直接丢弃——除非什么都还没输出,那里地板优先于上限,因为空段落比超大段落更差是唯一更糟的结局。焦点行保护。focusLines(来自focusLinesOf,src/mcp/tools.ts)是裁剪不能丢失的行:调用路径的下一跳调用点,以及查询点名的成员定义(importance 9,最多 6 个)。若头部填充够不到焦点行,先用 60% 的额度填头,再把剩余均分给未覆盖的焦点行(按源顺序贪婪分配正是这个守卫防的 bug 本身:提问点名的定义在文件尾部时,总是第一个被超限渲染切掉的跨度)。renderCluster(src/mcp/tools.ts)把这一切串起来:先按cap收缩(丢弃低重要性成员),若仍超过ceiling,交给windowToCeiling开窗;返回的shrunk标记会参与后续的文件级anyClusterShrunk记账。而 tools.ts 的主注释把 CG-30 的语义写成了文件级不变量:一个超大的单个成员(一个长而整体化的函数)在装得下有界超额时保持完整——半个方法没用,agent 只会 Read 剩下的,而这正是 explore 存在的 fallback 要防止的;超过该界之后它按整行开窗而不是被丢弃(CG-30),因此 god-method 既不能被静默丢失,也不能花掉整个响应的信封。回归测试:如何证明有界且交付tests/explore-oversize-member.test.ts 通过CODEGRAPH_EXPLORE_DEBUG侧车拿到每文件预算,再逐条断言:GATE:巨型文件monthly.ts的输出 ≤round(spendable × 1.5) 1(修复前是 3.7x);所有render clusters的文件都不超过 1.5 倍可花费量;CG-31 联动:曾被饿死的quarterly.tsskipped为null且实际交付了字节;永不为空:所有簇化文件emittedChars 0,且开窗文件仍以查询点名的符号(export function buildMonthlyReport)领头;整行切断:响应中为该文件编号的每一行都逐字符等于源文件整行(验证超过 20 行);报告clipped: true——被切了就说被切了,而不是把窗口冒充整个文件;响应总长不超过report.budget.hardCeiling。夹具形状测试(单个符号 180 行、文件 220 行从而走clusters渲染路径)被放在最前面——测试注释直言如果这些坏了,下面的 gate 就没有意义。结论与遗留事项判定:无回归,且确定性收益无歧义。行为 bar 成立(Read/Grep ~0、无弃用、在上界真实生效的仓库上分配效率 100%),代价是 django 在区间重叠下中位时延增加约 10%,新臂产生的一次分配未命中调用与 baseline 的两次召回未命中调用相互对消。随变更带入的 caveat:scripts/agent-eval/allocation-fixtures.json 中的self-query探针夹具在本变更下翻转为 FAIL。两臂的分配不变、tools.ts 交付相同的 8,282 字符——变化在于一个超额预留的附带文件现在被交付而不是被硬上限截断,而它此前的 PASS 恰恰依赖那次截断。该现象已记录为该夹具的afterCG30块。超额预留本身是 epic CG-24 的主题;正确的修法不是放松这个上界,而是按 docs/benchmarks/explore-noise-epic-cg24.md 的方向处理。对想复核这套数据的读者,入口是:A/B 评测脚本与 compare-arms.mjs 做逐次运行对比,tests/explore-oversize-member.test.ts 可以在任意构建上直接运行(在修复前的构建上会有 4 个用例失败),夹具源码在tests/fixtures/oversize-member-ts/。【免费下载链接】codegraphPre-indexed code knowledge graph, auto syncs on code changes, for Claude Code, Codex, Gemini, Cursor, OpenCode, AntiGravity, Kiro, CoPilot, and Hermes Agent — fewer tokens, fewer tool calls, 100% local项目地址: https://gitcode.com/GitHub_Trending/co0degr/codegraph创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表