Skip to content

fix(eval): actually capture exploration steps, across all their path shapes - #424

Merged
ceilf6 merged 1 commit into
developfrom
fix/exploration-metric
Aug 1, 2026
Merged

fix(eval): actually capture exploration steps, across all their path shapes#424
ceilf6 merged 1 commit into
developfrom
fix/exploration-metric

Conversation

@ceilf6

@ceilf6 ceilf6 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Follow-up to #423. The localization metric it added recorded zeroes — the capture block never landed in the file, and the event shape I wrote for it was wrong.

Measured, not assumed: read_file / list_directory carry the target under path; search_code uses filePattern (glob discovery) or directory. Keying on path alone dropped every search_code step — the main way query tasks explore now that #419 lets them plan at all.

Glob patterns count separately (globExplored) instead of as off-target: a pattern names no directory, and scoring **/*Route*.tsx as a miss would penalise the glob-then-narrow workflow the plan prompt teaches.

Verified on deep-query-route-source:

explored: 2   [search_code "*route*.ts", read_file "src/app/routes.ts"]
glob: 1
offTarget: 0   — the file read is under the declared target dir src/app

Two steps, one to narrow and one to read, landing on target. Before #419 this task planned no exploration at all.

Impact Scope

benchmarks/eval/run-eval.mjs only. No production code.

GitNexus Impact Summary

  • Risk level: LOW
  • Critical skeleton changes: none — benchmarks/ matches no critical contract rule.
  • GitNexus impact: harness-only; no packages/** or apps/** change.
  • Verification: pnpm quality:precommit exit 0, plus the measured run above.

Verification

  • pnpm quality:precommit — exit 0.
  • Single-task run against a real backend confirming non-zero, correctly attributed metrics.

Checklist

  • I have linked an issue or explained why this PR stands alone.
  • I have kept the diff focused on the stated change.
  • I have run pnpm quality:precommit, or explained why it could not run.
  • I have run pnpm quality:local for critical skeleton changes, or explained why it could not run — there are none.
  • I have updated docs or tests when behavior, public APIs, or Harness contracts changed.
  • For critical skeleton changes, I have filled the GitNexus impact summary with concrete results — none in this diff.

🤖 Generated with Claude Code

…shapes

The localization metric added in #423 recorded zeroes: the capture block never
made it into the file (an earlier patch asserted out and I fixed only the
aggregation half), and the shape I had written for it was wrong anyway.

Exploration steps carry their target under different keys depending on the
action — measured, not assumed: `read_file` / `list_directory` use `path`,
`search_code` uses `filePattern` for glob discovery or `directory`. Keying on
`path` alone would have dropped every `search_code` step, which is the main way
query tasks explore now that #419 lets them plan at all.

Glob patterns are counted separately (`globExplored`) rather than as off-target.
A pattern does not name a directory, so scoring `**/*Route*.tsx` as a miss would
penalise the glob-then-narrow workflow the plan prompt explicitly teaches.

Verified on `deep-query-route-source`:

```
explored: 2   [search_code "*route*.ts", read_file "src/app/routes.ts"]
glob: 1
offTarget: 0   (the file read is under the declared target dir src/app)
```

Two steps, one to narrow and one to read, landing on target. Before #419 this
task planned no exploration at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 12:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ceilf6
ceilf6 merged commit 5f2bd3d into develop Aug 1, 2026
7 checks passed
@ceilf6
ceilf6 deleted the fix/exploration-metric branch August 1, 2026 12:30

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ ceilf6/repo-guard

代码评审报告: fix(eval): actually capture exploration steps, across all their path shapes

风险等级:
处理建议: 请求修改
决策摘要: ** 修复方向正确且明显优于现状,但捕获条件仍会静默漏掉一整类 search_code 步骤(只带 query/pattern 无路径字段的内容搜索),而 harness 又没有对 step 事件做形状断言——这正是本 PR 要根治的「空结果被当成证据」;改动很小,建议补齐后合入。

级联分析

  • 变更符号: 原始模型未提供结构化级联字段。
  • 受影响流程: 原始模型未提供结构化级联字段。
  • 变更集外调用方: unknown
  • 置信度: degraded

问题发现

  1. [高] 只带 query / patternsearch_code 步骤仍被整条丢弃,且无声
    • 证据: search_code 的 schema 只有 query / pattern / filePattern / globOnly / maxResults / contextLinespackages/mcp-file/src/tools/search-code.ts:181-217),filePattern 有默认值、可以不传。规则回退路径明确发出无路径参数的步骤:params: { query: task.description, maxResults: 20 }packages/core/src/planner.ts:532,另见 :568)。此时 whereundefined,步骤既不进 explored 也不报错。
    • 受影响调用方/流程: exploredCountreport-filesense.mjs:169「平均探索步数/任务」的唯一来源。一个大量用内容搜索定位的任务会显示成「探索步数低、脱靶 0」,读起来像导航很准,实际是没采到——与 #423 的失败模式同类,只是换了个参数形状。
    • 最小可行修复: 探索类动作无论是否带路径字段都入账,用 path: null 表示「无路径归属」,并在 offTargetExplored / globExplored 的 filter 里显式排除 null(两处都已按字符串取值,改动约 3 行)。
  2. [高] 新增的跨包字段读取没有接入 harnessFailures 契约断言
    • 证据: 本文件对 filesense_navigated 的每个字段都做 typeof 断言并记账中止本臂(run-eval.mjs:170-181),理由写在 :160-169:「core 一旦改名,entries 会静默变成 0……宁可吵,不可静默」。新捕获块用 event.step?.actionevent.step?.params ?? {}:217-218),step 改名或 params 变形只会让 explored 恒为空数组,报告照常出「探索 0 步、脱靶 0」。
    • 受影响调用方/流程: 同上,直接污染 report-filesense.mjs 的主指标;report-filesense.mjs 现有的旧版 harness 守卫(:43-50)只检查字段存在性,exploredCount: 0 是合法值,拦不住。
    • 最小可行修复: 在两类 step 事件上断言 typeof event.step?.action === 'string'event.step?.params 为对象,不符则 harnessFailures.push(...),与 filesense 分支同构。
  3. [中] globExplored 写进了 JSONL,但报告里没有任何位置呈现它
    • 证据: report-filesense.mjs:167-171 的定位精度表只有 exploredCountoffTargetExplored;正文 :162-165 声称统计的是「探索类步骤落在 targetDirs 之外的次数」,未提 glob 排除。改动后 exploredCount 含 glob 步、offTargetExplored 不含,两个数字的口径不再一致,读者无法从报告还原分母。
    • 受影响调用方/流程: benchmarks/results/ 下要作为证据提交的消融报告。
    • 最小可行修复: 在同一张表加一行 globExplored,并在 :162-165 的说明里补一句「glob 模式不计入脱靶」。
  4. [中] 注释块插在 rollback 注释与其 if 之间,注释与代码错位
    • 证据: run-eval.mjs:206-209 是 rollback 分支的说明(「三者一起记……」),其 if:230-234;新块 :210-228 插在两者中间,自上而下读时 rollback 的理由会被当成 step 捕获块的说明。同时新块结束后没有 return;,与本文件其他分支「判定即 return」的分发风格不一致(:191:204)。
    • 受影响调用方/流程: 仅可读性,但本文件的注释就是它的规格说明。
    • 最小可行修复: 把新块整体移到 :205 之后、:206 之前,或移到 rollback 分支之后;并补 return;
  5. [低] touched 采集超出本 PR 声明范围,且当前无任何消费者
    • 证据: :224-226 采集 create_file / apply_patch:251 初始化;全仓 benchmarks/**/*.mjs 搜索 touched 只有这两处定义,无读取方。PR 描述与标题只讲 exploration 捕获,未提写入捕获。
    • 受影响调用方/流程: 无行为影响,仅进 eventDetails 原始载荷。
    • 最小可行修复: 要么在描述里说明它是后续写入定位指标的原始数据,要么拆出本 PR。

行级发现

  • [benchmarks/eval/run-eval.mjs:219] params.directoryread_file/list_directory/search_code 三个工具的 schema 里都不存在(search-code.ts:181-217list-directory.ts:11-15),是恒不命中的分支;真正的缺口是无路径字段的 search_codeplanner.ts:532就发这种步骤)会让where 为 undefined 而被整条丢弃。建议改为「探索类动作一律入账,path: null` 表示无路径归属」,并在下游 filter 里排除 null。
  • [benchmarks/eval/run-eval.mjs:217] 这里跨包读 event.step.action / event.step.params 但没有像 filesense_navigated 分支(:170-181)那样断言形状;core 改名后 explored 会静默恒空、报告照出「探索 0 步」。建议对 action 为 string、params 为对象各加一条 harnessFailures.push
  • [benchmarks/eval/run-eval.mjs:214] 注释称「search_code 用 filePattern(glob 发现)或 directory」并标注为实测,但 search_code 没有 directory 参数;请按实际 schema 修正这句,否则后来者会据此推断错误的事件形状。
  • [benchmarks/eval/run-eval.mjs:210] 这段注释插在 :206-209 的 rollback 说明与其 :230if 之间,导致 rollback 的理由读起来像在解释本块;建议整块移到 rollback 分支之前或之后,并在块尾补 return; 以保持本文件「判定即 return」的分发风格。
  • [benchmarks/eval/run-eval.mjs:225] touched` 只写不读:全仓无消费者,且写入捕获不在本 PR 声明范围内;请在描述中说明其用途,或拆出本 PR。
  • [benchmarks/eval/run-eval.mjs:346] 新增的 globExplored 没有进入 report-filesense.mjs:167-171 的定位精度表,而 exploredCount(含 glob)与 offTargetExplored(不含 glob)口径已不一致;建议同步加一行并在正文说明中补一句 glob 排除规则。
  • [report-filesense.mjs:63] 64avg/sumoffTargetExplored: null(未声明 targetDirs` 的任务)当作 0 参与平均,会稀释均值。属既有问题、非本 PR 引入,但本 PR 让该指标首次真正有值,值得一并确认口径。

Karpathy 评审

  • 假设: 模型输出需要归一化为固定 Markdown 契约。
  • 简洁性: 已提取 summary、finding、evidence 与 fix;原始 prose 不再附在评论中,避免占用下游解析与代理上下文。
  • 变更范围: 原始模型未提供结构化范围字段。
  • 验证: 需要查看 CI、测试或人工 CR 证据补强合并信心。

缺失覆盖

  • 输出未命中 Repo Guard Markdown 契约;建议补充真实模型质量评估覆盖。

if (event.type === 'step_completed' || event.type === 'step_failed') {
const action = event.step?.action;
const params = event.step?.params ?? {};
const where = params.path ?? params.directory ?? params.filePattern;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

params.directoryread_file/list_directory/search_code 三个工具的 schema 里都不存在(search-code.ts:181-217list-directory.ts:11-15),是恒不命中的分支;真正的缺口是无路径字段的 search_codeplanner.ts:532就发这种步骤)会让where 为 undefined 而被整条丢弃。建议改为「探索类动作一律入账,path: null` 表示无路径归属」,并在下游 filter 里排除 null。

// search_code 用 `filePattern`(glob 发现)或 `directory`。只认 `path`
// 会把 search_code 全漏掉,而那正是 #419 修好后 query 任务的主要探索手段。
if (event.type === 'step_completed' || event.type === 'step_failed') {
const action = event.step?.action;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里跨包读 event.step.action / event.step.params 但没有像 filesense_navigated 分支(:170-181)那样断言形状;core 改名后 explored 会静默恒空、报告照出「探索 0 步」。建议对 action 为 string、params 为对象各加一条 harnessFailures.push

// search_code 慢慢摸出来,结果一样、代价不同,而代价才是导航能力的直接体现。
//
// 路径字段因动作而异(实测):read_file / list_directory 用 `path`,
// search_code 用 `filePattern`(glob 发现)或 `directory`。只认 `path`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释称「search_code 用 filePattern(glob 发现)或 directory」并标注为实测,但 search_code 没有 directory 参数;请按实际 schema 修正这句,否则后来者会据此推断错误的事件形状。

// `rollback_failed` 随 #402 才进入 AgentEvent 联合类型,在它合入前不会出现。
// 三者一起记,才能判定「撤销成功」与「拦到了但没撤销掉」——只看 started
// 分不出这两种,而后者意味着坏文件还在工作区里。
// 定位精度:通过率对「找得准不准」不敏感——两臂都能靠 list_directory /

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段注释插在 :206-209 的 rollback 说明与其 :230if 之间,导致 rollback 的理由读起来像在解释本块;建议整块移到 rollback 分支之前或之后,并在块尾补 return; 以保持本文件「判定即 return」的分发风格。

details.explored.push({ action, path: String(where) });
}
if (['create_file', 'apply_patch'].includes(action)) {
details.touched.push({ action, path: String(where) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

touched` 只写不读:全仓无消费者,且写入捕获不在本 PR 声明范围内;请在描述中说明其用途,或拆出本 PR。

(e) => !e.path.includes('*') && !task.targetDirs.some((d) => e.path.startsWith(d)),
).length
: null,
globExplored: eventDetails.explored.filter((e) => e.path.includes('*')).length,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的 globExplored 没有进入 report-filesense.mjs:167-171 的定位精度表,而 exploredCount(含 glob)与 offTargetExplored(不含 glob)口径已不一致;建议同步加一行并在正文说明中补一句 glob 排除规则。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants