Skip to content

feat(eval): filesense ablation arm, deep fixture, and event payloads (#411) - #412

Merged
ceilf6 merged 8 commits into
developfrom
feat/filesense-ablation-eval
Aug 1, 2026
Merged

feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)#412
ceilf6 merged 8 commits into
developfrom
feat/filesense-ablation-eval

Conversation

@ceilf6

@ceilf6 ceilf6 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Linked Issue Or Context

Summary

The 2026-07-12 ablation cannot say anything about filesense. Its two arms toggle SDD and the guard; filesense is enabled in both. Recomputing the committed JSONL confirms it: filesense_navigated fires on 11/27 tasks in each arm, identically.

This PR adds the instrument needed to measure it. It makes no pass-rate claim — no run is reported here.

There is no production change. Everything is under benchmarks/eval/.

A correction: #411 was my error

Mid-way through this work I saw --arm no-filesense still emitting filesense_navigated: 1, concluded that runFrontAgentTask was dropping the option, filed #411 as a fourth instance of the "declared but never wired" class (#386 / #400 / #403), and added a nested filesense?: FilesenseConfig field to run.ts.

That was wrong. RuntimeConfigInput already exposes filesenseEnabled, filesenseOutput, filesenseWriteMode, filesenseMaxEntries, filesenseMaxBytes and filesenseTimeoutMs, and RunFrontAgentTaskOptions extends RuntimeConfigInput. Verified against a build with my change reverted:

resolveRuntimeConfig({ filesenseEnabled: false }, '/tmp')  ->  filesense = { enabled: false }

and end-to-end on the deep fixture with run.ts at develop's version:

arm filesense_navigated
full 1
no-filesense 0

I had passed a nested object where the API takes flat fields. The run.ts and run.test.ts changes are reverted and #411 is closed as invalid with the same explanation. I reached for a new seam without checking whether the existing one already reached the same place — the same failure of verification this series keeps finding in the code, this time in my own work.

What this PR actually contains

1. A third arm

--arm no-filesense differs from full in exactly one field. Strictly it stops the planner injecting the navigate step rather than making filesense unavailable — the tool stays registered, but the plan prompt and schema expose no filesense action, so the model cannot reach it. The report's off-arm guard verifies that empirically.

The whole config surface is pinned in all three arms, not just enabled. planner-skills.ts lets a configured budget override trigger-policy's default, and "was the budget truncated" is one of the report's conclusions — leaving those to FRONTAGENT_FILESENSE_* would publish a fact about the operator's machine. The effective config is written into every record and the report refuses to run if the arms disagree.

2. Event payloads in the JSONL

The harness stored only event counts — which is exactly why the 2026-07-31 report could not classify its own two validation_failed events and had to withdraw the classification.

Now also stored: filesense_navigated payloads (intent / paths / entries / truncated / candidateCount), validation_failed payloads (which checks failed), rollback_started|completed|failed, plus per-task filesenseEntries and filesenseTruncated. A shape mismatch against core's event union aborts the arm before the record is written — writing it first would let resume-dedup skip the poisoned row on a re-run and fix the bad data in place.

Note validation_failed and rollback_failed have no emit site until #402 lands, so those arrays are empty on develop. An empty array means not wired, not zero interceptions; this is stated at the collection site.

3. A fixture where the budget gate actually closes

Measured under filesense's own prepare_create budget (src, depth 2, maxEntries 180):

fixture entries scanned truncated repo total
fixture (flat) 17 false 18
fixture-deep 180 true 430

On the flat fixture filesense supplies essentially the entire repository — one ls -R is the whole project, so budgeted navigation and a plain command are indistinguishable by construction, and any result there is a fact about the fixture, not the capability.

fixture-deep is feature-sliced: 242 files / 188 directories / 430 entries at depth 5, with format.ts, Button.tsx and useToggle.ts each duplicated across 24 features so filenames alone cannot identify the target. Its 12 tasks deliberately omit the files pre-seed the flat tasks use — pre-seeding hands the agent the exact path and erases the thing under test.

The 228 noise modules come from a deterministic generator (which measures and reports the tree it produced, rather than repeating a hardcoded number) and are gitignored; only the 20 committed files are in the diff. Three carry planted bugs, guarded by a new file_unchanged check kind — without it a bugfix task could be passed by editing the test, and the two arms would not take that shortcut symmetrically.

4. report-filesense.mjs

Reports full-vs-off, separating the triggered subset — filesense fires on ~40% of tasks by design (trigger-policy.ts skips known single-file work), so a whole-set average dilutes the effect. It refuses to emit a report when the arms disagree on fixture, task set, or budget; when the on arm never triggered; or when the off arm triggered at all.

It states the read discipline explicitly: same-arm "used vs not used" is confounded by task type (create tasks always trigger and are the hardest); only the cross-arm same-task comparison is a valid control.

Impact Scope

  • benchmarks/eval/ only — harness, new fixture, new task set, new check kind, new report generator.
  • No production code, no config, no workflow changes.

GitNexus Impact Summary

  • Risk level: LOW
  • Critical skeleton changes: none. The diff touches no path matching a critical contract rule; the earlier packages/runtime-node/src/run.ts change was reverted.
  • GitNexus impact: nothing under packages/** or apps/** is modified, so there are no downstream execution flows to assess. benchmarks/eval/run-eval.mjs reads core's AgentEvent shape at runtime; that coupling is unchecked by the type system and is now guarded explicitly.
  • Verification: pnpm quality:precommit exit 0.

Verification

  • pnpm quality:precommit — exit 0.
  • Both arms run end-to-end on fixture-deep against develop's run.ts; the arm switch and the pinned budgets are confirmed from recorded telemetry, not by inspection.
  • fixture-deep: tsc --noEmit clean; vitest run → 4 failures, all in the three planted-bug modules, 11 passing.
  • The budget-gate figures above were measured directly against mcp-filesense's navigate.

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

The 2026-07-12 ablation cannot say anything about filesense: its two arms
(`full`/`ablation`) toggle SDD and the guard, and leave filesense enabled in
both. Recomputing the committed JSONL confirms it — `filesense_navigated` fires
on 11/27 tasks in *each* arm. This adds the three things needed to actually
measure it.

**A third arm.** `--arm no-filesense` differs from `full` in exactly one field.
Wiring it surfaced a fourth instance of this repo's "declared but never wired"
defect class (cf. #386/#400/#403): `filesense.enabled` is live config in core
(`planner-skills.ts:134` reads it) but `RunFrontAgentTaskOptions` had no
`filesense` field, so the harness's option was silently dropped — the arm would
have degraded to "both arms on", exactly as #386 voided July's guard arm.
`run.ts` now threads it at both assembly sites, with tests pinning both the
passthrough and the omitted-case fallback.

**Event payloads.** The harness stored only event counts, which is why the
2026-07-31 report could not classify its own `validation_failed` events. It now
also stores the payloads of `filesense_navigated` (intent, paths, entries,
truncated, candidates), `validation_failed` (which checks actually failed —
the only way to separate real interceptions from tool errors) and
`rollback_failed`, plus per-task `filesenseEntries` / `filesenseTruncated`.

**A fixture where the budget gate actually closes.** The flat fixture is 14
source files deep 2; measured, filesense scans 17 of the repo's 18 entries and
never truncates — one `ls -R` is the whole project, so navigation and a plain
command are indistinguishable by construction, and any result is a fact about
the fixture, not the capability. `fixture-deep` is feature-sliced: 236 source
files / 188 directories / depth 5, with `format.ts`, `Button.tsx` and
`useToggle.ts` each duplicated across 24 features so filenames alone cannot
locate the target. Measured under the same budget: 180 entries then truncated,
out of 424. Its 12 tasks deliberately omit the `files` pre-seed the flat tasks
use — pre-seeding hands the agent the path and erases the thing under test.

The 228 noise modules are generated by `fixture-deep/generate.mjs` (deterministic)
and gitignored; only the 19 hand-written anchor files are committed.

`report-filesense.mjs` reports the full-vs-off comparison, and separates the
triggered subset — filesense fires on ~40% of tasks by design
(`trigger-policy.ts` skips known single-file work), so a whole-set average
dilutes the effect. It also states the read discipline: same-arm
"used vs not used" comparisons are confounded by task type; only the
cross-arm same-task comparison is a valid control.

No pass-rate claim is made here — this commit is the instrument, not a result.

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

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.

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 请求修改
决策摘要: run.ts 的接线修复本身正确、覆盖完整、对既有调用方零影响,可以合并;但本 PR 的交付物是「测量仪器」,而仪器里有三处会产出「看起来像证据的空结果」——validation_failed / rollback_failed 在本仓库根本没有 emit 点、report 把任务集硬编码成 tasks.json、JSONL 输出文件名不含 fixture 导致两套夹具会静默混进同一份报告。这正是本 PR 立论要消除的失效模式,修复成本都很低。

级联分析

  • 变更符号: RunFrontAgentTaskOptions.filesense(新增可选字段)、runFrontAgentTask / planFrontAgentTaskAgentConfig 组装、run-eval.mjs 的 arm/fixture 选择与事件采集。
  • 受影响流程: 运行时 API → AgentConfig.filesenseplanner-skills.tsphase.filesense-navigate 注入;benchmarks 评测编排与报告生成。
  • 变更集外调用方: 已用文本检索穷举 filesense: (resolved|options),全仓仅三处——run.ts:187run.ts:458(本 PR 均已改),以及 mcp-server.ts:552;后者是 frontagent_status 的诊断输出,不是 AgentConfig 组装点,不受影响。RuntimeConfigInput 用的是扁平的 filesenseEnabled 等字段,与新字段无命名冲突。新字段可选且缺省回落 resolved.filesense,现有调用方行为不变。
  • 置信度: medium(text search + 直接读取源码,无代码图谱证据;已确认受影响组装点在本变更集中全部更新)

问题发现

  1. [高] validation_failedrollback_failed 在本仓库没有 emit 点,新增的载荷采集永远为空

    • 证据: 全仓检索 rollback_failed 只命中 benchmarks/eval/run-eval.mjs 一处——它不在 packages/core/src/types.ts:775-797AgentEvent 联合类型里,event.snapshotId / event.error 的形状是凭空假定的。检索 validation_failed 只命中 5 个文件(types.ts 的类型定义、desktop reducer、benchmarks),没有任何 emit 站点benchmarks/results/2026-07-12-sdd-ablation.md:39-40 已把这一点记为缺陷 3,其后续项 3「在执行器校验路径上补 validation_failed 事件」至今未落地。
    • 受影响调用方/流程: 每条 JSONL 记录里 eventDetails.validationFailed / rollbackFailed 恒为 []。下一份报告读到空数组时无法区分「guard 一次都没拦」与「事件压根没接线」——与 PR 描述「now also stored: which checks failed,the only way to separate a real interception from a tool error」不符。PR 正文称 2026-07-31 遥测报告有两次 validation_failed 事件,但在本 checkout 上该事件不可能被 runFrontAgentTask 发出,两者需要对齐。
    • 最小可行修复: 删除 rollback_failed 分支(或改采集确实存在的 rollback_started / rollback_completed);在 validation_failed 分支上写明「该事件当前无 emit 点,见 2026-07-12 §缺陷3 / 后续项3」,并在 report-filesense.mjs 里把「0 条」显式标注为「未接线」而非「零拦截」。
  2. [高] 报告器把任务集硬编码成 tasks.json,对 deep 夹具的产物会自述错误的方法

    • 证据: report-filesense.mjs:58 输出 冻结的 N 条任务(benchmarks/eval/tasks.json),而本 PR 的主用例是 --fixture deep(走 tasks-deep.jsonfixture-deep)。报告器没有任何 fixture 概念,> 口径 段还断言「任务集、夹具全部相同」,却无从校验。
    • 受影响调用方/流程: 按 PR 推荐路径跑完 deep 两臂后生成的 Markdown 会声称用的是 flat 任务集——一份自述失真的报告,正是 #410 撤回结论的同一类问题。
    • 最小可行修复: 在 JSONL 记录里加 fixture / taskSet 字段(run-eval.mjs:190 附近),报告器从记录读取并断言两臂一致,不一致时直接报错退出。
  3. [中] 输出文件名只含 arm,不含 fixture,flat 与 deep 的结果会静默合并

    • 证据: run-eval.mjs:26OUT = out/${ARM}.jsonl 未随新增的 --fixturerun-eval.mjs:39-42)变化;断点续跑的去重只按 taskIdrun-eval.mjs:84-89),而 deep 任务 id 带 deep- 前缀与 flat 不冲突,因此先跑 flat 再跑 deep 会追加进同一文件而不报错。report-filesense.mjs:24commonIds 会把两套夹具的任务并成一个「冻结任务集」。
    • 受影响调用方/流程: 跨夹具的通过率被混合平均,filesenseEntries / filesenseTruncated 汇总同样被污染。
    • 最小可行修复: OUT 改为 ${ARM}${FIXTURE_KIND === 'deep' ? '-deep' : ''}.jsonl,并在记录里补 fixture: FIXTURE_KIND
  4. [中] deep 夹具的前置检查漏掉 node_modules,且没有锁文件

    • 证据: run-eval.mjs:43-45 只校验生成树是否存在;setupWorkspacerun-eval.mjs:71)无条件 symlinkSync(join(FIXTURE, 'node_modules'), …)。flat 夹具提交了 benchmarks/eval/fixture/pnpm-lock.yamlfixture-deep/ 只有 package.json,没有锁文件。
    • 受影响调用方/流程: 未安装依赖时会建出悬空软链,checks.mjs:24-32typecheckcmd 全部返回非 0,整臂输出一串 FAIL 记录;而唯一的中止启发式是「零成功 LLM 调用」(run-eval.mjs:206),LLM 正常时不会触发——又一次「失败被记成数据」。
    • 最小可行修复: 前置检查追加 existsSync(join(FIXTURE, 'node_modules')),并提交 fixture-deep 的锁文件以对齐 flat 夹具。
  5. [中] fixture-deep/sdd.yaml 是 flat 夹具的逐字拷贝,约束指向不存在的目录

    • 证据: 深夹具是 feature-sliced(src/features / src/entities / src/shared / src/app),但 sdd.yaml 的 directory_structure 仍是 src/components / src/hooks / src/utilsmodule_boundaries 仍是 src/components/* → src/api/*project.name 仍是 eval-fixture——与 benchmarks/eval/fixture/sdd.yaml 第 23-40 行完全一致。这些路径在深夹具中一个都不存在。
    • 受影响调用方/流程: 两臂共用同一份 sdd.yaml,filesense 的对照仍然成立;但 deep 轮名义上「SDD 开」,SDD 却对真实目录树无任何约束,而 deep-create-shared-truncate / deep-create-checkout-hook / deep-create-entity-guard 三条任务恰恰在考「遵循既有分层约定」——即 directory_structure 本该编码的东西。
    • 最小可行修复: 按 feature-sliced 布局重写 directory_structuremodule_boundaries,并更新 project.name / description
  6. [中] deep-query-format-convention 的验收无法区分两臂

    • 证据: tasks-deep.json:32 唯一的 check 是 result_contains: "checkout",而任务文本本身就是「checkout 这个 feature 自己的 format 函数……」。任何复述问题的回答都会命中。
    • 受影响调用方/流程: 这条任务是专为「24 份同名 format.ts」设计的定位测试,却对两臂都白送一分,直接稀释本次消融要测的效应。
    • 最小可行修复: 改为断言可区分的内容,如返回串形态 checkout: 配合 toFixed,或路径 features/checkout/lib/format\.ts
  7. [中] options.filesense 是整体替换而非合并,会丢掉环境变量解析出的兄弟字段

    • 证据: FilesenseConfigpackages/core/src/types.ts:165-178)全部字段可选;run.ts:187 / run.ts:458options.filesense ?? resolved.filesense。调用方传 { enabled: true } 时,config.ts:341-361FRONTAGENT_FILESENSE_MAX_ENTRIES 等解析出的 output / writeMode / maxEntries / maxBytes / timeoutMs 会一并被丢弃。
    • 受影响调用方/流程: 本 PR 自己的 no-filesense 臂(run-eval.mjs:60)不受影响(enabled: false 后其余字段无意义),但这与 run.test.ts:423 注释「省略时不得变成 undefined:那会连环境变量解析出来的配置一起丢掉」担心的是同一类丢失,只是发生在部分覆盖而非省略时。
    • 最小可行修复: 改为 { ...resolved.filesense, ...options.filesense },并补一条「部分覆盖保留其余已解析字段」的测试;若坚持整体替换语义,在 JSDoc 里写明这一点。
  8. [低] README 未同步新增的臂与夹具,checklist 勾了「已更新文档」

    • 证据: README.md:1301-1312 仍只写两臂 + report.mjsscripts/tests/workflow-rules.test.mjs:560-562 把这段 README 钉为评测入口的文档面(该测试只断言既有两行存在,因此不会失败)。新增的 --arm no-filesense--fixture deep、必须先跑 generate.mjs 这一前置步骤、report-filesense.mjs 都无处可查。
    • 最小可行修复: 在同一节补三条命令与 generate 前置步骤。
  9. [低] 生成器导入了未使用的 rmSync,且没有清理步骤,"确定性" 只在首次生成成立

    • 证据: generate.mjs:23 导入 rmSync 但全文未用;生成前不清理旧产物。改动 FEATURES / ENTITIES 后重跑会留下孤儿文件,条目数与截断行为随之漂移,而这两个数正是本夹具存在的理由。biome.json:61**/benchmarks 排除在 lint 之外,所以 pnpm quality:precommit 不会报这个未使用导入。
    • 最小可行修复: 删除未使用导入;或在写入前只清理生成子树,注意不要误删 features/{checkout,cart,shipping}/lib 下已提交的锚点文件。

行级发现

  • [packages/runtime-node/src/run.ts:187] options.filesense ?? resolved.filesense 是整体替换:调用方传部分对象时会丢掉 env 解析出的 output/writeMode/maxEntries/maxBytes/timeoutMs;建议改为 { ...resolved.filesense, ...options.filesense }(458 行同)。
  • [benchmarks/eval/run-eval.mjs:116] validation_failed 在全仓没有 emit 站点(2026-07-12 报告 §缺陷3 的后续项 3 未落地),该数组恒为空;请在此注明未接线,否则空数组会被读成「零拦截」。
  • [benchmarks/eval/run-eval.mjs:126] rollback_failed 不在 AgentEvent(types.ts:775-797)里,全仓也无发出方,snapshotId/error 形状是假定的——删除此分支,或改采集确实存在的 rollback_started/rollback_completed
  • [benchmarks/eval/run-eval.mjs:42] 记录里没有 fixture/taskSet 字段,而输出文件名只含 arm,flat 与 deep 的结果会被追加进同一个 ${ARM}.jsonl;请把 fixture 写进文件名并落进记录。
  • [benchmarks/eval/run-eval.mjs:43] 前置检查只验生成树;缺 node_modules 时 71 行会建出悬空软链,整臂 check 全 FAIL 却不中止——请一并校验 existsSync(join(FIXTURE, 'node_modules'))
  • [benchmarks/eval/report-filesense.mjs:58] 任务集硬编码为 benchmarks/eval/tasks.json,用于 deep 产物时报告会自述错误方法;改为从记录的 fixture/taskSet 字段读取,并在两臂不一致时报错退出。
  • [benchmarks/eval/tasks-deep.json:32] 唯一 check result_contains: "checkout" 与任务文本重合,两臂必过、无法区分同名 format.ts 的定位能力;改断言 checkout: 返回串形态或 features/checkout/lib/format\.ts 路径。
  • [benchmarks/eval/fixture-deep/sdd.yaml:24] src/components/src/hooks/src/utils 在 feature-sliced 深夹具里都不存在,SDD 对真实目录树不构成任何约束;请按 src/features|entities|shared|app 重写并更新 project.name
  • [benchmarks/eval/fixture-deep/generate.mjs:23] rmSync 导入后未使用且无清理步骤,改动 FEATURES/ENTITIES 后重跑会留孤儿文件、条目数漂移;删除导入或只清理生成子树(勿删已提交锚点)。

Karpathy 评审

  • 假设: 三处隐藏假设未被验证——validation_failed/rollback_failed 会被发出(实际没有发出方)、评测输出目录里只会有一套夹具的数据(文件名不区分)、深夹具的依赖已安装(无锁文件、无检查)。run.ts 侧的假设是干净的:新字段可选、缺省回落,run.test.ts:400-425 两个方向都钉住了。
  • 简洁性: run.ts 改动是本类缺陷(#386/#400/#403)已有的最小形状,没有引入新抽象;--fixture flat|deep 用两个三元表达式选路径,没有过度配置。report-filesense.mjsreport.mjs 有约 40 行加载/统计 helper 重复,但两者叙事确实不同,现阶段不值得抽公共模块——不作为阻塞项。
  • 结构质量: 没有发现文件膨胀、错误分层、薄 wrapper 或共享路径特例。collectEventDetail 是一个内聚的纯函数,位置合适;问题在于它有两个分支对应的事件不存在,属正确性而非结构问题。
  • 变更范围: 与既定目标一致,无无关重构与格式噪音。生产侧只有 run.ts 的 +14/-2,其余全在 benchmarks/
  • 验证: run.ts 的验证强度与其风险匹配(双向测试 + 两次真实跑)。但评测工具本身几乎没有验证:夹具前置条件、事件接线、报告自述口径都无自检,而这些恰恰是本 PR 要交付的东西。

缺失覆盖

  • options.filesense 部分覆盖(如只传 { enabled: true })时其余已解析字段应保留的测试——当前两条测试只覆盖「全量覆盖」与「完全省略」。
  • report-filesense.mjs 对两臂 fixture/taskSet 不一致时应拒绝出报告的断言。
  • 深夹具的可再生性检查:重跑 generate.mjs 后条目数/截断结果应与 PR 表格中的 180 / truncated=true 一致(当前无清理步骤,无法保证)。
  • --fixture deep 路径的一次冒烟(--tasks smoke,共 4 条 smoke 任务)作为 PR 内可复现证据;PR 声明「no run is reported here」是合理的克制,但仪器自检与结果宣称是两回事。

Comment thread packages/runtime-node/src/run.ts Outdated
rag: resolved.rag,
filesense: resolved.filesense,
// 调用方显式给了就用调用方的(消融臂靠这条),否则沿用解析出来的默认配置
filesense: options.filesense ?? resolved.filesense,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

options.filesense ?? resolved.filesense 是整体替换:调用方传部分对象时会丢掉 env 解析出的 output/writeMode/maxEntries/maxBytes/timeoutMs;建议改为 { ...resolved.filesense, ...options.filesense }(458 行同)。

});
return;
}
if (event.type === 'validation_failed') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

validation_failed 在全仓没有 emit 站点(2026-07-12 报告 §缺陷3 的后续项 3 未落地),该数组恒为空;请在此注明未接线,否则空数组会被读成「零拦截」。

Comment thread benchmarks/eval/run-eval.mjs Outdated
});
return;
}
if (event.type === 'rollback_failed') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

rollback_failed 不在 AgentEvent(types.ts:775-797)里,全仓也无发出方,snapshotId/error 形状是假定的——删除此分支,或改采集确实存在的 rollback_started/rollback_completed

const FIXTURE_KIND = args.fixture ?? 'flat';
if (!['flat', 'deep'].includes(FIXTURE_KIND)) throw new Error('--fixture flat|deep');
const FIXTURE = join(HERE, FIXTURE_KIND === 'deep' ? 'fixture-deep' : 'fixture');
const TASKS_FILE = join(HERE, FIXTURE_KIND === 'deep' ? 'tasks-deep.json' : 'tasks.json');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

记录里没有 fixture/taskSet 字段,而输出文件名只含 arm,flat 与 deep 的结果会被追加进同一个 ${ARM}.jsonl;请把 fixture 写进文件名并落进记录。

if (!['flat', 'deep'].includes(FIXTURE_KIND)) throw new Error('--fixture flat|deep');
const FIXTURE = join(HERE, FIXTURE_KIND === 'deep' ? 'fixture-deep' : 'fixture');
const TASKS_FILE = join(HERE, FIXTURE_KIND === 'deep' ? 'tasks-deep.json' : 'tasks.json');
if (FIXTURE_KIND === 'deep' && !existsSync(join(FIXTURE, 'src', 'features', 'catalog'))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

前置检查只验生成树;缺 node_modules 时 71 行会建出悬空软链,整臂 check 全 FAIL 却不中止——请一并校验 existsSync(join(FIXTURE, 'node_modules'))

Comment thread benchmarks/eval/report-filesense.mjs Outdated

## 方法

- **任务集**:冻结的 ${commonIds.length} 条任务(\`benchmarks/eval/tasks.json\`)。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

任务集硬编码为 benchmarks/eval/tasks.json,用于 deep 产物时报告会自述错误方法;改为从记录的 fixture/taskSet 字段读取,并在两臂不一致时报错退出。

Comment thread benchmarks/eval/tasks-deep.json Outdated
"smoke": false,
"task": "checkout 这个 feature 自己的 format 函数返回什么格式的字符串?注意项目里有多个同名 format。",
"type": "query",
"checks": [{ "kind": "result_contains", "pattern": "checkout" }]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

唯一 check result_contains: "checkout" 与任务文本重合,两臂必过、无法区分同名 format.ts 的定位能力;改断言 checkout: 返回串形态或 features/checkout/lib/format\.ts 路径。

Comment thread benchmarks/eval/fixture-deep/sdd.yaml Outdated

# 目录结构约束
directory_structure:
src/components:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/components/src/hooks/src/utils 在 feature-sliced 深夹具里都不存在,SDD 对真实目录树不构成任何约束;请按 src/features|entities|shared|app 重写并更新 project.name

* 用法:node benchmarks/eval/fixture-deep/generate.mjs
*/

import { mkdirSync, rmSync, writeFileSync } from 'node:fs';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

rmSync 导入后未使用且无清理步骤,改动 FEATURES/ENTITIES 后重跑会留孤儿文件、条目数漂移;删除导入或只清理生成子树(勿删已提交锚点)。

…esults

repo-guard's review on #412: the deliverable is a measuring instrument, and it
had three ways to emit results that look like findings but are artifacts.

**Provenance is now recorded and checked.** Records carry `fixture` and
`taskSet`, output files are `<arm>-deep.jsonl` for the deep fixture, and
`report-filesense.mjs` reads both and refuses to emit a report if the two arms
disagree. Previously the filename carried only the arm, resume-dedup keyed only
on `taskId`, and deep task ids do not collide with flat ones — so running both
fixtures appended into one file and the report averaged across them while
claiming, in its own methodology section, to have used `tasks.json`.

**Missing dependencies fail fast.** Without `node_modules`, `setupWorkspace`
built a dangling symlink and every `typecheck`/`cmd` check returned non-zero; the
only abort heuristic is "zero successful LLM calls", which does not fire when the
model is fine. An entire arm of FAIL records would have been recorded as data.
A lockfile is committed to match the flat fixture.

**The unwired events are labelled.** `validation_failed` and `rollback_failed`
have no emit site until #402 lands, so those arrays are necessarily empty on
develop — an empty array means "not wired", not "zero interceptions", which is
the exact misreading this PR exists to prevent. Rollback collection widened to
`rollback_started`/`completed`/`failed` so "undone" and "intercepted but still on
disk" are distinguishable.

**`options.filesense` now merges instead of replacing.** Whole-object
replacement dropped every env-resolved sibling field when a caller overrode one
field — the same loss the omitted-case test already guarded against, just via a
different door. Regression test included.

**The deep fixture's SDD config was a verbatim copy of the flat one**, so its
`directory_structure` and `module_boundaries` named `src/components|hooks|utils`
— none of which exist here. The deep round would have been nominally "SDD on"
with SDD constraining nothing, while three of its tasks test exactly the layering
convention that config is supposed to encode. Rewritten for the feature-sliced
layout with one-way boundaries.

Also: `deep-query-format-convention`'s only assertion echoed its own prompt and
would have passed in both arms; the generator now cleans its own subtree (while
preserving the committed anchors) instead of leaving orphans that drift the entry
count; README documents the arm, the fixture flag, and the generator
prerequisite, with a contract test; counts corrected to 242 files / 188 dirs /
430 entries after the planted-bug anchors were added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 评论
决策摘要: 生产侧改动(run.ts 一个可选字段 + 逐字段合并)正确、向后兼容且有测试覆盖,可以合并;但评测仪器本身仍有几处会让「跑出来的数」失真的缺口——最关键的是对照臂 full 的 filesense 状态没有被钉死,仍取自环境变量,跑任何一轮并发布数字之前应先补上。

级联分析

  • 变更符号: RunFrontAgentTaskOptions(新增可选 filesense?: FilesenseConfig)、runFrontAgentTaskplanFrontAgentTask 内的两处 AgentConfig 组装(run.ts:189 / run.ts:461)。
  • 受影响流程: 运行时 → core AgentConfig.filesenseplanner-skills.ts:134filesense?.enabled !== false 决定是否注入 phase.filesense-navigate);评测编排器 run-eval.mjs 的三臂配置。
  • 变更集外调用方: 全仓 grep runFrontAgentTask|planFrontAgentTask 命中 18 个文件(cli/headless、vscode、desktop、mcp-server、benchmarks)。新字段是可选的,缺省走 { ...resolved.filesense },与改动前的 resolved.filesense 等价,无调用方破坏mcp-server.ts:552filesense: resolved.filesensefrontagent_status 的报告载荷、不是第三处 AgentConfig 组装点,PR 所称「两处组装点」属实;MCP 参数也够不到新字段,status 输出仍准确。(text search + 直接阅读源码,未跑测试)
  • 置信度: medium

问题发现

  1. [中] 对照臂 full 的 filesense 状态是环境态、不是钉死的配置

    • 证据: run-eval.mjs:57full: { sddPath: 'sdd.yaml' } 未声明 filesense,而 resolveRuntimeConfig 会读 FRONTAGENT_FILESENSE_ENABLEDpackages/runtime-node/src/config.ts:342-345);新臂 run-eval.mjs:69 却把 enabled:false 钉死。两臂一个钉死、一个随环境。
    • 受影响调用方/流程: 两臂对比的全部结论。操作者环境里存在该变量(或将来默认值翻转)时,会得到「两臂都关」的空结果,与 #386 让 guard 臂失效、#411 让本臂失效属于同一类失真——正是本 PR 立论要消灭的那类。
    • 最小可行修复: full: { sddPath: 'sdd.yaml', filesense: { enabled: true } }。这正是本 PR 新增的 seam 才做得到的事。
  2. [中] deep 的 bugfix 任务可以靠改测试文件通过

    • 证据: tasks-deep.json 三条 bugfix 的唯一实质验收是 npx vitest run <该测试文件> 退出 0;deep 任务集刻意不给 files 预置(PR 正文说明),任务描述也不点名源文件;fixture-deep/sdd.yamlprotected_files 只保护 package.json / tsconfig.json。改测试与改源码在验收上不可区分。平坦任务集靠 files: ["src/utils/formatDate.ts"] + 任务里直接点名源文件形成事实锚定,deep 把这层锚定去掉了。
    • 受影响调用方/流程: report-filesense.mjs 的通过率与「仅触发子集」两个口径;两臂不一定对称受影响(导航被关的一臂更可能走捷径),会直接偏向结论方向。
    • 最小可行修复: 落盘时记录工作区内 *.test.ts 是否与夹具副本一致(一个布尔字段即可),让报告能剔除这类任务;或在 checks.mjs 增加 file_unchanged check kind。平坦任务集同样受益,改在 checks.mjs 是正确的层。
  3. [低] 报告器对「开臂零导航」只有散文警告,没有拒绝

    • 证据: report-filesense.mjs:22-33 对夹具/任务集不一致是硬拒绝(process.exit(1)),但 triggered.length === 0truncTasks == 0 只在正文里写了提醒,仍会照常输出主结果表和「全量差值 X 个百分点」这样的结论式表述。
    • 受影响调用方/流程: 报告产物会被提交进 benchmarks/results/ 当证据。
    • 最小可行修复: triggered 计算之后(第 60 行附近)比照 provenance 守卫,零触发时拒绝出报告,或在文首强制打上作废横幅。
  4. [低] 事件载荷映射跨包耦合,既无类型检查也无测试

    • 证据: run-eval.mjs:114-122.mjs 直接读 event.entries / event.truncated / event.candidateCount,对端是 TS 联合类型 packages/core/src/types.ts:775-784。当前字段名完全对得上(已逐字段核对),今天不是缺陷;但 core 一旦改名,filesenseEntries 静默变 0、filesenseTruncated 静默变 false,报告照样出「累计扫描条目 0」——恰是上一个提交 "stop the instrument from producing evidence-shaped empty results" 要根治的形态。
    • 最小可行修复: 把 collectEventDetail 导出并加一条最小单测钉住字段映射,或在 entries 非 number 时显式告警而不是记 0。
  5. [低] README 的 deep 流程缺少夹具依赖安装步骤,而本 PR 新加的守卫会硬失败

    • 证据: run-eval.mjs:48-50 新增 node_modules 缺失即 throw(这个守卫本身是对的);README:1319-1325 只写了 generate.mjs,没有安装步骤,仓库任何地方也没有文档化过夹具装依赖。照 README 逐条执行的读者会停在第二条命令。该守卫不区分夹具,平坦夹具那段(README:1305-1310)同样受影响。
    • 最小可行修复: 在代码块里补一行安装命令,并把它加进 workflow-rules.test.mjs:569 那条新断言——理由与该测试注释里给 generate.mjs 的理由完全相同。
  6. [低] 生成器的清理逻辑做不到注释所声称的事

    • 证据: generate.mjs:95-106 只遍历当前的 FEATURES / ENTITIESrmSync,而注释说清理是为了「改动 FEATURES / ENTITIES 后重跑会留下孤儿文件,条目数与截断行为随之漂移」。从列表里删掉一个条目时,它的目录会永久残留,条目数照样漂移。
    • 最小可行修复: 改为 readdirSync(SRC/features) / entities 实际枚举,删除不在 ANCHORED_FEATURES 里的目录。
  7. [低] 已提交的计数与 PR 描述互相不一致

    • 证据: fixture-deep/sdd.yaml:9 写「236 源文件 / 188 目录」,run-eval.mjs:33-35 写「242 个源文件 …… 全仓 430 条」,PR 正文表格写「repo total 424」。按生成器推算:24 features × 8 + 12 entities × 3 = 228 生成文件 + 14 个已提交 src 文件 = 242 文件,188 目录(我按生成器逐层数过,188 无误),合计 430 条;236/424 这一组恰好少了 6 个已提交的 *.test.ts——而真实 navigate 扫描是会数上这 6 个的。另外 PR 正文与 issue #411 都把修复描述成 options.filesense ?? resolved.filesense,实际合入的是逐字段合并 { ...resolved.filesense, ...options.filesense }run.ts:189/461,并由 run.test.ts:417-431 钉住)。合并语义更好,但关键骨架变更的 GitNexus 影响摘要描述的是另一份代码。
    • 最小可行修复: 两处注释统一成扫描器口径(文件+目录)的同一个数;PR 正文与 #411 的 Fix 段改为「逐字段合并」。

行级发现

  • [benchmarks/eval/run-eval.mjs:69] 新臂把 enabled:false 钉死,但对照臂 full 未钉 enabled:true,仍取自 FRONTAGENT_FILESENSE_ENABLED;给 full 也加上 filesense: { enabled: true },让两臂都由显式配置定义。
  • [benchmarks/eval/tasks-deep.json:144] 该 bugfix 的唯一实质验收是跑这个测试文件,agent 改测试即可通过;补一个「测试文件未被修改」的落盘标记或 file_unchanged check。
  • [benchmarks/eval/run-eval.mjs:117] event.entries 等字段与 core 的 AgentEvent 联合类型无类型约束也无测试,core 改名会静默变成 0/false;非 number 时应显式告警而不是计 0。
  • [benchmarks/eval/report-filesense.mjs:60] triggered 为 0 时应比照上面的夹具 provenance 守卫拒绝出报告(或强制作废横幅),而不是继续输出「全量差值」结论表。
  • [README.md:1321] 这段流程缺少夹具依赖安装步骤,而 run-eval.mjs 新增的 node_modules 守卫会在下一条命令直接抛错;补一行安装命令并加进 workflow-rules 断言。
  • [benchmarks/eval/fixture-deep/generate.mjs:102] 清理只遍历当前 FEATURES,从列表里删除的条目会留下孤儿目录、让条目数漂移;改为枚举磁盘上实际存在的目录并跳过锚点。
  • [benchmarks/eval/fixture-deep/sdd.yaml:9] 「236 源文件」与 run-eval.mjs:33-35 的「242 源文件 / 全仓 430 条」口径不一致(差值恰为 6 个已提交测试文件);统一到扫描器实际会数到的口径。

Karpathy 评审

  • 假设: 对照臂假设操作者环境是干净的(见发现 1);报告器假设文件名与臂一一对应(OUTARM 生成,实际成立);deep 任务假设生成树是最新的(已由 run-eval.mjs:42-44 的存在性守卫兜住,但不校验新鲜度)。
  • 简洁性: run.ts 的改动是最小外科手术式的——一个可选字段 + 两处组装点合并,且合并优于 PR 描述里的 ??(不会丢掉环境变量解析出的兄弟字段)。collectEventDetail 是一个单一分发函数,没有引入框架化抽象。
  • 结构质量: 无退化。report-filesense.mjs:1-56report.mjs:1-34 的 loader 与 6 个统计一行函数近乎逐字重复(约 30 行),但都是稳定的小工具、且 PR 对「为何要独立报告器」给出了可信理由,不构成阻塞项;若之后再加第三个报告器,值得抽一个 report-lib.mjsrun.ts 两处组装点连同 6 行注释一起复制——这是既有的组装重复,不是本 PR 引入。
  • 变更范围: 与既定目标一致。夹具、任务集、报告器、README、workflow 测试都服务于「让这一臂可测量」这一件事;没有夹带无关重构。
  • 验证: 生产侧 seam 三条测试覆盖了「透传 / 部分覆盖合并 / 省略不丢失」三个方向,方向完整。夹具的埋雷可信:我核对了三个模块的实现与断言,computeTotal 2 条、mergeLines 1 条、estimateEta 1 条失败,其余 11 条通过——与 PR 声称的 4 失败 / 11 通过一致。评测侧无自动化验证(见下)。

缺失覆盖

  • 「本臂与 full 只差一个字段」是本臂的定义性质,却只靠人工阅读保证;scripts/tests/ 里加一条对 ARM_OPTIONS 的断言即可长期钉住(顺带能挡住发现 1 那类不对称)。
  • report-filesense.mjs 的 provenance 守卫是全 PR 唯一防止「拿一套夹具的方法学描述另一套产物」的逻辑,本身没有任何测试;它是纯函数式脚本,用两个临时 JSONL 就能测。
  • collectEventDetail 的字段映射未被任何测试钉住(发现 4)。
  • 无端到端回归证明 --arm no-filesense 确实产出 filesense_navigated: 0——PR 正文给了人工跑的数字,但该证据不在仓库里、也不可重跑校验;这一条属于评测流程的固有限制,记录在此供人工判断。

Comment thread benchmarks/eval/run-eval.mjs Outdated
// filesense 单独消融:SDD 与 guard 与 full 臂完全相同,唯一变量是导航能力。
// 这是回答「filesense 有没有用」的臂——full/ablation 两臂 filesense 都开着,
// 它们之间的差异说明不了 filesense 的任何事情。
'no-filesense': { sddPath: 'sdd.yaml', filesense: { enabled: false } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

新臂把 enabled:false 钉死,但对照臂 full 未钉 enabled:true,仍取自 FRONTAGENT_FILESENSE_ENABLED;给 full 也加上 filesense: { enabled: true },让两臂都由显式配置定义。

"type": "modify",
"checks": [
{
"kind": "cmd",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

该 bugfix 的唯一实质验收是跑这个测试文件,agent 改测试即可通过;补一个「测试文件未被修改」的落盘标记或 file_unchanged check。

details.filesense.push({
intent: event.intent ?? null,
paths: event.paths ?? [],
entries: event.entries,

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.entries 等字段与 core 的 AgentEvent 联合类型无类型约束也无测试,core 改名会静默变成 0/false;非 number 时应显式告警而不是计 0。


// 触发率是本报告的第一读数:filesense 只在触发的任务上可能起作用,
// 不触发的任务被算进平均值只会把效应稀释成噪音。
const triggered = navTasks(arms.full);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

triggered 为 0 时应比照上面的夹具 provenance 守卫拒绝出报告(或强制作废横幅),而不是继续输出「全量差值」结论表。

Comment thread README.md

```bash
# One-time: materialise the deep fixture's generated module tree
node benchmarks/eval/fixture-deep/generate.mjs

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这段流程缺少夹具依赖安装步骤,而 run-eval.mjs 新增的 node_modules 守卫会在下一条命令直接抛错;补一行安装命令并加进 workflow-rules 断言。

}
continue;
}
rmSync(join(SRC, 'features', feature), { recursive: true, force: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

清理只遍历当前 FEATURES,从列表里删除的条目会留下孤儿目录、让条目数漂移;改为枚举磁盘上实际存在的目录并跳过锚点。

Comment thread benchmarks/eval/fixture-deep/sdd.yaml Outdated
project:
name: "eval-fixture-deep"
type: "react-spa"
description: "filesense 消融评测的冻结深夹具工程(feature-sliced,236 源文件 / 188 目录)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

「236 源文件」与 run-eval.mjs:33-35 的「242 源文件 / 全仓 430 条」口径不一致(差值恰为 6 个已提交测试文件);统一到扫描器实际会数到的口径。

… editing the test

Third repo-guard round on #412.

**The control arm was not pinned.** `no-filesense` hard-set `enabled: false`
while `full` declared nothing and inherited `FRONTAGENT_FILESENSE_ENABLED` from
the operator's environment. One arm pinned, one ambient: on a machine with that
variable set, both arms run with filesense off and the comparison returns a
clean-looking null — the same failure shape as #386 voiding the guard arm and
#411 voiding this one. `full` now pins `enabled: true`, which is only expressible
because of the seam this PR adds.

**The deep bugfix tasks could be passed by editing the test.** Their only
substantive check is `vitest run <that test file>`, and the deep task set
deliberately omits the `files` pre-seed and does not name the source file —
pre-seeding hands over the answer. So "fix the source" and "edit the test" were
indistinguishable at acceptance, and the two arms would not take that shortcut
symmetrically: the arm without navigation is likelier to, which biases the result
in a specific direction. New `file_unchanged` check kind in `checks.mjs`
(diffing against the fixture original), applied to all three. Verified
end-to-end.

**The report now refuses zero-trigger runs.** Provenance mismatch was already a
hard exit, but a run where filesense never fired still printed the main table and
a "difference: X pp" conclusion — and these reports get committed as evidence.

**A field-shape mismatch now throws instead of counting zero.** `run-eval.mjs`
reads core's `AgentEvent` fields from `.mjs`, with no type checking. A rename in
core would have silently produced `filesenseEntries: 0` and a report claiming
"total entries scanned: 0" — the exact evidence-shaped-empty-result failure the
previous commit set out to remove.

Also: the generator's cleanup enumerates directories on disk rather than the
current `FEATURES`/`ENTITIES` lists, so removing an entry no longer leaves an
orphan that drifts the entry count; README documents the fixture dependency
install that the new `node_modules` guard requires (with a contract assertion);
and the 236/424 counts are corrected to the scanner's own 242/430.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 请求修改
决策摘要: 生产侧改动(run.ts 一个可选字段 + 逐字段合并)正确且有测试覆盖,可以合并;但评测 harness 里两处缺陷正属于本 PR 声称要根治的「空结果被当成证据」——事件形状守卫被 Agent.emit 的 try/catch 静默吞掉,且 ablation 臂未钉死 filesense,修掉这两处即可合入。

级联分析

  • 变更符号: RunFrontAgentTaskOptions.filesense(新增可选字段)、runFrontAgentTask / planFrontAgentTaskAgentConfig 组装(packages/runtime-node/src/run.ts:187:459)、runChecks(checks, {…, fixtureRoot})benchmarks/eval/checks.mjs:10)、run-eval.mjsARM_OPTIONS / OUT 命名 / JSONL 记录形状。
  • 受影响流程: 运行时任务装配 → core planner-skills.tsfilesense.enabled;评测编排 → checks → report-filesense.mjs / report.mjs
  • 变更集外调用方: runFrontAgentTask 的 15 个调用点(cli/headless、desktop、vscode、mcp-server、旧 benchmark)均未传 filesense,取到 {...resolved.filesense} —— 值等同、仅对象身份变化,无调用方依赖身份(text search)。runChecks 唯一调用方是 run-eval.mjs,已同步传 fixtureRoot(text search)。filesense_navigated 载荷字段与 packages/core/src/types.ts:776-783 逐字段吻合;rollback_failed 确实尚未进入 AgentEvent 联合类型,注释与现状一致(graph 不可用,读源码确认)。
  • 置信度: medium(无代码图谱,结论来自 diff review + 只读源码核对;关联 issue #411/#386/#400 上下文完整)

问题发现

  1. [高] filesense_navigated 形状守卫被静默吞掉,仍会产出「静默为 0」的假数据

    • 证据: collectEventDetailentries 非数字时 throwrun-eval.mjs:120-125),但该回调经 run.ts:253-256 注册进 agent.addEventListener,而 Agent.emit 对 listener 异常只做 try { listener(event) } catch { this.debugError(...) }packages/core/src/agent/agent.ts:304-311)。评测跑在非 debug + filterConsole: true 下,异常完全不可见。
    • 受影响调用方/流程: 抛出发生在 events[e.type] 自增之后,于是 JSONL 会记下 filesense_navigated: N 却带着空 eventDetails.filesensefilesenseEntries: 0filesenseTruncated: falsereport-filesense.mjs 的零触发守卫用的是 events.filesense_navigated 计数(report-filesense.mjs:52,60,65),因此照常出报告,正文印出「累计扫描条目 0 / 预算闸触发 0」。这正是注释里写的「宁可吵,不可静默」要防的失真,守卫目前只是给了虚假的安全感。
    • 最小可行修复: 不要在 listener 里抛。把形状不符记成 details.shapeErrors.push(...),在每条任务落盘后检查——非空即 console.error + process.exit(2)(与「零成功 LLM 调用」中止同一处理);同时在 report-filesense.mjs 增加一条守卫:events.filesense_navigated > 0eventDetails.filesense 为空时拒绝出报告。
  2. [中] ablation 臂未钉死 filesense,full 却钉死了 —— 新引入的两臂环境不对称

    • 证据: run-eval.mjs:57-59 的注释明确写「对照臂也必须把 filesense 钉死」,full 拿到 filesense: { enabled: true }:60),但 ablation:61-68)没有该字段,仍取 FRONTAGENT_FILESENSE_ENABLEDpackages/runtime-node/src/config.ts:341-345)。
    • 受影响调用方/流程: report.mjs 的 full vs ablation 对比。本 PR 之前两臂同取环境变量、至少是对称的;改动后操作者环境里存在 FRONTAGENT_FILESENSE_ENABLED=0 就会得到「full 开、ablation 关」,SDD 消融的差值里混进 filesense 变量。按 PR 自己的论证(#386 废掉 guard 臂),这是同一类失真。
    • 最小可行修复: 给 ablation 补上 filesense: { enabled: true },或抽一个 const PINNED = { filesense: { enabled: true } } 基底展开进三个臂,让「唯一变量」这条前提在配置里可见。
  3. [低] 实现语义与 PR 描述 / issue #411 / GitNexus 影响摘要不一致

    • 证据: 三处文本都写 options.filesense ?? resolved.filesense、并称「与 #389 给 hallucinationGuard 用的形状相同」,实际代码是逐字段合并 { ...resolved.filesense, ...options.filesense }run.ts:189:461),与 hallucinationGuard: options.hallucinationGuardrun.ts:184,直接透传、无 fallback)并不同形。
    • 受影响调用方/流程: 合并语义本身更好(保住 FRONTAGENT_FILESENSE_MAX_ENTRIES 等兄弟字段,run.test.ts 已 pin),但关键骨架变更的影响摘要描述了不存在的代码,后续评审与 #411 的读者会按 ?? 语义推理。另有一个未 pin 的边界:调用方显式传 { maxEntries: undefined } 会把已解析值覆盖成 undefined?? 语义下不会。
    • 最小可行修复: 更新 PR 描述与 #411 正文为「逐字段合并」,并在 run.test.ts 补一条「显式 undefined 字段」的期望(保留覆盖或忽略,二选一并写死)。
  4. [低] 两个 reporter 复制了同一组汇总 helper

    • 证据: report-filesense.mjs:11-20,46-51,103-115load / rate / avg / sum / pct / count 与失败清单循环,与 report.mjs:14-34,103-117 基本同形。
    • 受影响调用方/流程: 口径漂移风险——某一侧改了 avg 的取整或 pass 判定,两份提交进 benchmarks/results/ 的报告会用不同口径描述同一批 JSONL。
    • 最小可行修复: 抽 benchmarks/eval/report-lib.mjs 存这几个纯函数,两个 reporter 各自只保留叙事。非阻塞,可后续 PR 处理。
  5. [低] 夹具规模数字在三处不一致

    • 证据: PR 正文写「236 source files / repo total 424」,run-eval.mjs:33-35fixture-deep/sdd.yaml:9 写「242 源文件 / 430 条目」。按生成器算:24×8 + 12×3 = 228 噪音文件 + 14 个已提交 src 文件 = 242;242 + 188 目录 = 430。两组数差 6,恰好等于 6 个 .test.ts,疑似统计口径不同(含/不含测试文件)。
    • 受影响调用方/流程: 这些数字是「预算闸真的会关」这条方法学主张的证据,会被引用进 results 文档。
    • 最小可行修复: 统一到一处口径,并注明是否计入 .test.ts

行级发现

  • [benchmarks/eval/run-eval.mjs:121] 这个 throwagent.addEventListener 的回调里,被 Agent.emit 的 try/catch 吞掉(packages/core/src/agent/agent.ts:304-311),非 debug 下无任何输出;结果仍是 filesenseEntries: 0 的静默假数据。改为把形状错误记进 details,任务落盘后统一 process.exit(2)
  • [benchmarks/eval/run-eval.mjs:60] full 臂钉死了 filesense,但紧邻的 ablation 臂没有,仍随 FRONTAGENT_FILESENSE_ENABLED 变动;按本行上方注释自己的理由,给 ablationfilesense: { enabled: true }(或抽公共基底展开进三个臂)。
  • [benchmarks/eval/report-filesense.mjs:60] 触发判定只看 events.filesense_navigated 计数,与 filesenseEntries(来自 eventDetails)不是同一数据源;补一条守卫:计数 > 0 而 eventDetails.filesense 为空时拒绝出报告,否则会印出「累计扫描条目 0」当结论。
  • [packages/runtime-node/src/run.ts:189] 实际是逐字段合并,而 PR 描述 / issue #411 / GitNexus 摘要都写的是 options.filesense ?? resolved.filesense;请更新文本,并补一条测试钉住调用方显式传 undefined 字段时的预期行为。

Karpathy 评审

  • 假设: 「抛异常就能吵醒操作者」这条假设未经验证,实际被 emit 的 try/catch 否定(发现 1)。「两臂唯一变量是 filesense」在 full/no-filesense 之间成立,但被引入到 full/ablation 之间时不成立(发现 2)。rollback_failed / validation_failed 依赖未合入的 #402,注释已如实标注,符合预期。
  • 简洁性: 合并语义、arm/fixture 参数化、file_unchanged 都是与问题规模匹配的最小手段,未见猜测性抽象。两个 reporter 的 helper 复制是唯一的多余重复(发现 4)。
  • 结构质量: 无文件跨界膨胀(run-eval.mjs 249 行、report-filesense.mjs 174 行),新逻辑都落在自己拥有的层;fixtureRoot 作为显式参数传入 runChecks 而非藏进全局,边界干净。生成器只清理磁盘上实际存在的目录(而非遍历常量列表)是正确的选择。
  • 变更范围: 与 #411 的目标一致,无夹带重构。README + workflow-rules.test.mjs 的文档断言与运行时新增的硬失败(缺 node_modules 即拒跑)配套,属于必要范围。已核对 flat 夹具确实有 package.json / pnpm-lock.yaml,README 新增的 install 命令可执行。
  • 验证: run.ts 三条测试方向完整(覆盖、部分合并、省略回退),且 toEqual({ enabled: false })normalizeFilesense* 默认返回 undefined 的前提下成立。深夹具的「4 失败 / 11 通过」我按埋点逐条复算与 PR 声明一致(computeTotal 2 + mergeLines 1 + estimateEta 1),三个 bug 均可被 Math.ceil / 累加 / 乘数量修复,任务可解。

缺失覆盖

  • 事件形状不符时 harness 行为的回归:目前无任何测试或运行时断言能捕捉「计数 > 0 但载荷为空」这一状态(发现 1 的直接后果)。
  • checks.mjsfile_unchanged 没有任何单测,而它现在是 bugfix 任务的通过/失败闸门:建议补 node:test 覆盖未改动、已改动、文件缺失、fixtureRoot 未提供四种分支。
  • 平坦任务集 tasks.json 的 bugfix 任务仍没有 file_unchanged,与 tasks-deep.json 验收口径不对称;若两套结果曾被并列引用,需在报告里说明差异(PR 已论证平坦任务预置了 files,风险较低,但差异本身未记录)。
  • 无测试钉住 filesense 覆盖对象里显式 undefined 字段的合并结果(发现 3)。

Comment thread benchmarks/eval/run-eval.mjs Outdated
// core 一旦改名,entries 会静默变成 0、报告照样出「累计扫描条目 0」——
// 正是本 harness 要根治的「空结果被当成证据」。宁可吵,不可静默。
if (typeof event.entries !== 'number') {
throw new Error(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这个 throwagent.addEventListener 的回调里,被 Agent.emit 的 try/catch 吞掉(packages/core/src/agent/agent.ts:304-311),非 debug 下无任何输出;结果仍是 filesenseEntries: 0 的静默假数据。改为把形状错误记进 details,任务落盘后统一 process.exit(2)

Comment thread benchmarks/eval/run-eval.mjs Outdated
// 对照臂也必须把 filesense 钉死。不写的话它取自 FRONTAGENT_FILESENSE_ENABLED——
// 一臂钉死、一臂随环境,操作者环境里存在该变量就会得到「两臂都关」的空结果,
// 与 #386 废掉 guard 臂、#411 废掉本臂是同一类失真。
full: { sddPath: 'sdd.yaml', filesense: { enabled: true } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

full 臂钉死了 filesense,但紧邻的 ablation 臂没有,仍随 FRONTAGENT_FILESENSE_ENABLED 变动;按本行上方注释自己的理由,给 ablationfilesense: { enabled: true }(或抽公共基底展开进三个臂)。


// 触发率是本报告的第一读数:filesense 只在触发的任务上可能起作用,
// 不触发的任务被算进平均值只会把效应稀释成噪音。
const triggered = navTasks(arms.full);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

触发判定只看 events.filesense_navigated 计数,与 filesenseEntries(来自 eventDetails)不是同一数据源;补一条守卫:计数 > 0 而 eventDetails.filesense 为空时拒绝出报告,否则会印出「累计扫描条目 0」当结论。

Comment thread packages/runtime-node/src/run.ts Outdated
filesense: resolved.filesense,
// 合并而非整体替换:调用方常常只覆盖一个字段(消融臂只给 enabled: false),
// 整体替换会把 FRONTAGENT_FILESENSE_MAX_ENTRIES 等解析出来的兄弟字段一起丢掉。
filesense: { ...resolved.filesense, ...options.filesense },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

实际是逐字段合并,而 PR 描述 / issue #411 / GitNexus 摘要都写的是 options.filesense ?? resolved.filesense;请更新文本,并补一条测试钉住调用方显式传 undefined 字段时的预期行为。

… was unpinned

Fourth repo-guard round on #412 — and the first finding is the same failure mode
this PR exists to remove, committed by the fix for it.

**The guard threw, and `Agent.emit` swallows it.** `agent.ts:307-315` wraps every
listener call in try/catch and reports at debug level, so a `throw` from
`collectEventDetail` would have vanished and the run would have carried on
producing the silently-zero data the guard was added to prevent. Shape mismatches
are now accumulated and the arm aborts after the record is written, alongside the
existing "zero successful LLM calls" abort.

**The `ablation` arm was not pinned either.** The last round pinned `full`, but
`ablation` still inherited `FRONTAGENT_FILESENSE_ENABLED`. With three arms, one
of them ambient means arm-to-arm differences are no longer attributable to the
one thing being ablated.

Also: the generator now measures the tree it produced and prints it in the
scanner's own units, instead of prose repeating a hardcoded number that drifts
whenever `FEATURES`/`ENTITIES` change — which is what put three different figures
in three places.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ceilf6 added a commit that referenced this pull request Aug 1, 2026
229 files from the deep-fixture work (PR #412) were swept into this branch by a
`git add benchmarks/` while they sat untracked in the worktree. This PR is
docs/data only — the report, its raw JSONL, the probe, and the README qualifier
with its contract test. Nothing here references fixture-deep, and a committed
`node_modules` symlink pointed at a gitignored path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 请求修改
决策摘要: ** 评测侧(deep 夹具、事件载荷、report-filesense)质量高且自洽,但 run.ts 新增的 filesense?: FilesenseConfig同一个 options 对象上已经接通的 filesenseEnabled / filesenseMaxEntries 等六个扁平字段完全重叠——消融臂本可零production改动实现,合并前需要先决定保留哪一套公共配置面。

级联分析

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

问题发现

  1. [高] filesense 是第二套重叠的公共配置面;#411 的「消融臂无法存在」前提不成立
    • 证据: RunFrontAgentTaskOptions extends RuntimeConfigInputrun.ts:43),而 RuntimeConfigInput 已声明 filesenseEnabled?: boolean | stringconfig.ts:68)等六个字段;resolveRuntimeConfig 优先读 input.filesenseEnabled,再回落到环境变量config.ts:342-345)。即 runFrontAgentTask({ filesenseEnabled: false }) 早就能让 planner-skills.ts:134 跳过注入。该扁平面还已暴露到 MCP 工具 schema(mcp-server.ts:268,373)并有测试(config.test.ts:134mcp-server.test.ts:92,150)。本 PR 自己的合并测试正是用 filesenseMaxEntries: 42 作为「已解析配置」——说明扁平面可用且作者在用。
    • 受影响调用方/流程: RunFrontAgentTaskOptions 是 runtime-node 导出的公共契约,加进去就很难再删。合并后同一件事有两个入口、优先级未定义(嵌套覆盖扁平),下一个「同一 defect class 第五例」会从这里长出来。
    • 最小可行修复: 二选一并说清楚。(a) 删掉 filesense?: FilesenseConfigARM_OPTIONS 改用 filesenseEnabled: false / filesenseEnabled: true——production diff 归零,消融臂照样成立,#411 需相应改写为「文档缺失」而非「死配置」;(b) 若确要保留嵌套面,补一条测试钉住 { filesenseEnabled: true, filesense: { enabled: false } } 的优先级,并在 JSDoc 里写明嵌套优先于扁平。
  2. [中] 事件契约守卫会把被污染的记录先落盘再中止,断点续跑时静默复活
    • 证据: run-eval.mjs:245appendFileSync(OUT, record)run-eval.mjs:249 才检查 harnessFailuresexit(3)。此时 filesenseEntries 已按 ?? 0 汇总为 0 写入 JSONL。
    • 受影响调用方/流程: 续跑去重只按 taskIdrun-eval.mjs:98-103),修好后重跑会跳过这条,report-filesense 照常把它计入「累计扫描条目」。这正是该守卫注释里要根治的「空结果被当成证据」,只是换了条路径。
    • 最小可行修复: 把 harnessFailures.length > 0 的判断提到 appendFileSync 之前,失败时不写记录直接 exit(3)
  3. [中] report-filesense 只守「开臂零触发」,不守「关臂非零触发」
    • 证据: report-filesense.mjs:60,65arms.full 有零触发拒绝出报告的守卫,但全文没有任何一处断言 navTasks(arms.off).length === 0;分类表的「该类触发导航数」列也只统计 f(开臂)。
    • 受影响调用方/流程: filesense_navigate 工具在 agent.ts:232-253无条件注册的,filesense.enabled 只关掉 planner 技能注入(planner-skills.ts:134)。PR 用一条任务人工核对了关臂为 0,但报告器本身不会在后续每轮复核这个不变量——而「关臂其实没关」正是 #386/#411 的失效形态,且判定所需数据(events.filesense_navigated)已经在 JSONL 里。
    • 最小可行修复: 在零触发守卫旁加对称检查,关臂出现任何 filesense_navigatedconsole.error + exit(1),或至少在方法学章节印出关臂触发数。
  4. [低] 同一对象字面量内 hallucinationGuard 整体替换、filesense 逐字段合并
    • 证据: run.ts:184run.ts:189 相邻两行语义不同;run.test.ts 现有用例还专门钉住 hallucinationGuard 省略时为 undefined
    • 受影响调用方/流程: 无行为风险,但两个消融开关的覆盖语义不一致,后续加第三个开关时无既定规则可循。
    • 最小可行修复: 在 filesense 那条注释里补一句说明为何两者语义不同(guard 无扁平面、无 env 兄弟字段可丢),避免读者误以为是疏漏。

行级发现

  • [packages/runtime-node/src/run.ts:69] 该字段与 RuntimeConfigInput.filesenseEnabled/filesenseMaxEntries/filesenseOutput/filesenseWriteMode/filesenseMaxBytes/filesenseTimeoutMsconfig.ts:68-73,已在 config.ts:342-360 接通并经 MCP schema 暴露)功能完全重叠;建议删除本字段并让消融臂改用 filesenseEnabled: false,或保留但在此处写明嵌套优先于扁平的优先级规则。
  • [packages/runtime-node/src/run.ts:189] 合并语义正确,但未定义 options.filesense.enabledoptions.filesenseEnabled 冲突时的优先级;若保留嵌套面,请补一条同时传两者的测试把「嵌套胜出」钉死。
  • [benchmarks/eval/run-eval.mjs:74] 此处用新增的嵌套入口;改成 { sddPath: 'sdd.yaml', filesenseEnabled: false } 可让本 PR 的 production diff 归零,消融臂效果等价。
  • [benchmarks/eval/run-eval.mjs:249] 守卫在 appendFileSync(第 245 行)之后才触发,被污染的记录已落盘;续跑按 taskId 去重会跳过它,报告仍会把 filesenseEntries: 0 计入。请把这段前移到写盘之前。
  • [benchmarks/eval/report-filesense.mjs:60] 只对开臂做了零触发守卫;请一并断言关臂 navTasks(arms.off).length === 0,否则「关臂其实没关」这类失真仍需人工发现。

Karpathy 评审

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

缺失覆盖

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

Comment thread packages/runtime-node/src/run.ts Outdated
* (`planner-skills.ts` 真读),但从运行时 API 根本够不着——
* filesense 消融臂会像 #386 废掉 guard 臂那样,静默退化成「两臂都开着」。
*/
filesense?: FilesenseConfig;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

该字段与 RuntimeConfigInput.filesenseEnabled/filesenseMaxEntries/filesenseOutput/filesenseWriteMode/filesenseMaxBytes/filesenseTimeoutMsconfig.ts:68-73,已在 config.ts:342-360 接通并经 MCP schema 暴露)功能完全重叠;建议删除本字段并让消融臂改用 filesenseEnabled: false,或保留但在此处写明嵌套优先于扁平的优先级规则。

Comment thread packages/runtime-node/src/run.ts Outdated
filesense: resolved.filesense,
// 合并而非整体替换:调用方常常只覆盖一个字段(消融臂只给 enabled: false),
// 整体替换会把 FRONTAGENT_FILESENSE_MAX_ENTRIES 等解析出来的兄弟字段一起丢掉。
filesense: { ...resolved.filesense, ...options.filesense },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

合并语义正确,但未定义 options.filesense.enabledoptions.filesenseEnabled 冲突时的优先级;若保留嵌套面,请补一条同时传两者的测试把「嵌套胜出」钉死。

Comment thread benchmarks/eval/run-eval.mjs Outdated
// filesense 单独消融:SDD 与 guard 与 full 臂完全相同,唯一变量是导航能力。
// 这是回答「filesense 有没有用」的臂——full/ablation 两臂 filesense 都开着,
// 它们之间的差异说明不了 filesense 的任何事情。
'no-filesense': { sddPath: 'sdd.yaml', filesense: { enabled: false } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

此处用新增的嵌套入口;改成 { sddPath: 'sdd.yaml', filesenseEnabled: false } 可让本 PR 的 production diff 归零,消融臂效果等价。

console.log(
`[${ARM}] ${task.id}: ${pass ? 'PASS' : 'FAIL'} (${record.elapsedMs}ms, ${llmCalls} calls, ${llmFailures} failures, events=${JSON.stringify(events)})`,
);
if (harnessFailures.length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

守卫在 appendFileSync(第 245 行)之后才触发,被污染的记录已落盘;续跑按 taskId 去重会跳过它,报告仍会把 filesenseEntries: 0 计入。请把这段前移到写盘之前。


// 触发率是本报告的第一读数:filesense 只在触发的任务上可能起作用,
// 不触发的任务被算进平均值只会把效应稀释成噪音。
const triggered = navTasks(arms.full);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

只对开臂做了零触发守卫;请一并断言关臂 navTasks(arms.off).length === 0,否则「关臂其实没关」这类失真仍需人工发现。

…s already worked

Fifth repo-guard round on #412, and this one invalidates the premise I filed
issue #411 on.

`RuntimeConfigInput` already exposes `filesenseEnabled`, `filesenseOutput`,
`filesenseWriteMode`, `filesenseMaxEntries`, `filesenseMaxBytes` and
`filesenseTimeoutMs`, and `RunFrontAgentTaskOptions extends RuntimeConfigInput`.
`resolveRuntimeConfig` wires all six. Verified directly against a build with my
change reverted:

    resolveRuntimeConfig({ filesenseEnabled: false }, '/tmp')
    -> filesense = { enabled: false }

and end-to-end on the deep fixture with `run.ts` at develop's version:

    full          filesense_navigated = 1
    no-filesense  filesense_navigated = 0

So the ablation arm needed **zero production change**. The `filesense?:
FilesenseConfig` field I added was a second, overlapping public config surface
for settings that already had one, and #411's claim that the arm "cannot exist"
was wrong. `run.ts` and `run.test.ts` are reverted to develop; the harness uses
`filesenseEnabled`.

I reached for a new seam without checking whether the existing one reached the
same place — the same failure of verification the rest of this series keeps
finding in the code.

Also from this round:
- The event-contract guard aborted *after* appending the record. Resume-dedup
  keys on `taskId`, so a re-run after the fix would have **skipped** the poisoned
  row and the report would have counted its zeroes — the guard's own failure mode
  by another route. It now aborts before the write.
- `report-filesense.mjs` guards the off arm too. It refused a zero-trigger *on*
  arm but never checked that the *off* arm is actually zero; a non-zero there
  means filesense was never disabled, which is equally fatal in the opposite
  direction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ceilf6 added a commit that referenced this pull request Aug 1, 2026
229 files from PR #412's deep fixture were swept in by `git add benchmarks/`
while they sat untracked in the worktree. They came back a second time because
the first removal only untracked them — the files stayed on disk, and this
branch carries no .gitignore for that path, so the next `git add benchmarks/`
re-staged them.

This PR is docs/data only: the report, its raw JSONL, the probe, and the README
qualifier with its contract test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 请求修改
决策摘要: 代码本身可合并——我逐层验证了 --arm no-filesense 在当前 develop 上确实生效,无需任何 runtime 改动;但 PR 正文、Impact Scope 与 GitNexus 影响摘要仍在描述一个已被最后一次 revert 撤掉、且前提本就不成立的 run.ts 修改,而 #411 已 CLOSED 并宣称了一个仓库里不存在的修复。这份记录一旦合入,就是这个 PR 自己反对的那类"看起来有证据的空结论",修正成本只是文字与 issue 状态。

级联分析

  • 变更符号: runChecks(新增 fixtureRoot 参数与 file_unchanged 分支)、run-eval.mjs 的 ARM_OPTIONS / JSONL record 形状(新增 fixture / taskSet / eventDetails / filesenseEntries / filesenseTruncated)、新增 report-filesense.mjs、新增 deep 夹具与任务集。
  • 受影响流程: run-eval.mjsrunFrontAgentTaskfilesenseEnabled 扁平字段)→ resolveRuntimeConfigconfig.ts:341-345)→ run.ts:176 filesense: resolved.filesenseplanner-skills.ts:134 filesense?.enabled !== false。链路完整,no-filesense 臂确实不会注入 phase.filesense-navigate
  • 变更集外调用方: runChecks 仅被 run-eval.mjs:214 调用(text search,无其他调用点),签名扩展为可选参数、旧调用不存在,无破坏。report.mjs(2026-07-12 报告器)读 full.jsonl / ablation.jsonl,deep 产物落 *-deep.jsonl,两者不互相污染。AgentEvent.filesense_navigated 的字段(types.ts:775-784entries / truncated / candidateCount / warnings)与 harness 的读取一致。夹具的 .gitignore 不影响导航——filesense 用的是 .filesignoremcp-filesense/src/types.ts:146),生成树对扫描器可见,430/180 的实测口径成立。
  • 置信度: medium(无代码图谱,结论来自直接读取 run.ts / config.ts / planner-skills.ts / types.ts 与 grep;未实际执行两臂评测)。

问题发现

  1. [高] PR 记录与实际 diff 不符:宣称的 run.ts 关键骨架变更不在本次变更中

    • 证据: 26 个变更文件中没有 packages/runtime-node/**;仓库当前 run.ts:176run.ts:446 仍是 filesense: resolved.filesense,不存在 options.filesense ?? resolved.filesenserun.test.ts 也没有 issue 里承诺的两条测试。而 PR 的 Impact Scope、GitNexus 影响摘要("Critical skeleton changes: yes")、以及 checklist 里"已为关键骨架变更跑 pnpm quality:local"都在描述这个不存在的变更。分支最后一次提交 revert(runtime): drop the redundant filesense option 已说明原因:RunFrontAgentTaskOptions extends RuntimeConfigInputrun.ts:42),后者声明了 filesenseEnabledconfig.ts:68)并在 config.ts:343 解析——扁平字段本来就能到达 core,#411 的前提("caller 传入的 filesense 被静默丢弃")对这条路径不成立。
    • 受影响调用方/流程: 合并记录 / issue #411(状态 CLOSED,正文给出的 filesense: { ...resolved.filesense, ...options.filesense } 修复在树里不存在)。后续读者按 issue 去 run.ts 找不到该代码,很可能"重新修一遍",把整对象替换或合并语义再引入一次;同时"第四例 declared-but-never-wired"这个说法会带着 #386/#400/#403 一起进入 benchmark 叙事而无对应证据。
    • 最小可行修复: 把 Impact Scope / GitNexus 摘要改为"仅 benchmarks + README + scripts/tests,无生产代码变更",删除 quality:local 一项或注明不适用;把"Fixes #411"改为说明 #411 已被本分支自证为误报,并在 #411 上补一条纠正(扁平 filesenseEnabled 一直有效,最初被丢弃的是嵌套 filesense 对象),或将其重开为"嵌套 filesense 选项缺失"的准确描述。表格里 full=1 / no-filesense=0 的实测无需改动——那个观测仍然成立。
  2. [中] 只有 filesense.enabled 被钉死,预算字段仍随操作者环境浮动,而报告把预算截断当结论印出来

    • 证据: ARM_OPTIONS(run-eval.mjs:60/65/74)只固定 filesenseEnabledoutput / writeMode / maxEntries / maxBytes / timeoutMs 仍取自 FRONTAGENT_FILESENSE_*config.ts:346-361),且 planner-skills.ts:147-151filesense?.maxEntries ?? decision.maxEntries 让配置值压过 trigger-policy 的默认预算。JSONL 记录了 fixture / taskSet 做溯源,却没有记录生效的 filesense 配置。
    • 受影响调用方/流程: report-filesense.mjs 的"预算闸被触发(truncated)的任务数"与"累计扫描条目"——这两个数正是 deep 夹具存在的理由。操作者环境里存在 FRONTAGENT_FILESENSE_MAX_ENTRIES=1000 时,两臂对称地不截断,报告会照常印出 truncated = 0,读者按正文的读数纪律会把它理解为"夹具太小",而真实原因是环境变量。这与本 PR 亲自给 enabled 加钉死注释所防的是同一类失真。
    • 最小可行修复: 在 ARM_OPTIONS 里一并钉死 filesenseMaxEntries / filesenseMaxBytes / filesenseOutput / filesenseWriteMode / filesenseTimeoutMs;或把 resolved.filesense 随 record 落盘,并在 report-filesense.mjs 的 provenance 守卫旁加一条"两臂预算配置一致且为已知值"的断言。
  3. [低] 关臂并非"filesense 不可用",而是"不注入导航步骤"

    • 证据: agent.ts:247 无条件把 filesense_navigate 注册进 file 工具映射;enabled: false 只影响 planner-skills.ts:133 的注入判定。我已确认 LLM 计划 prompt / schema 未暴露 filesense 动作(llm/prompts.tsllm/schemas.ts 中无匹配),因此实际泄漏概率很低。
    • 受影响调用方/流程: report-filesense.mjs 对关臂任一次触发直接 exit 1 拒绝出报告——一旦发生,代价是丢弃一整轮已跑完的两臂评测。
    • 最小可行修复: 保留守卫但降级为"剔除泄漏任务并在报告中显式列出",或在正文把口径写成"关闭注入式导航"而非"关闭 filesense"。

行级发现

  • [benchmarks/eval/run-eval.mjs:60] 与 filesenseEnabled 同理,filesenseMaxEntries / filesenseMaxBytes / filesenseOutput / filesenseWriteMode / filesenseTimeoutMs 也应在三臂里钉死(或落盘进 record 供报告断言),否则报告的"预算闸被触发数"随操作者环境静默改变。
  • [benchmarks/eval/run-eval.mjs:128] 注释说"由主循环在写完本条记录后中止本臂",但 245-253 行是先中止、不写记录(且那才是正确行为)。改成"在写盘前中止",避免下次有人按注释把 process.exit 挪到 appendFileSync 之后。
  • [benchmarks/eval/run-eval.mjs:225] TASKS_FILE.split('/').pop() 在非 POSIX 分隔符下会把整条绝对路径写进 taskSet,报告随后拼成 benchmarks/eval/<绝对路径>;改用 node:pathbasename(TASKS_FILE)(该文件已从 node:path 导入其他函数)。

Karpathy 评审

  • 假设: 最大的隐藏假设不在代码里而在正文——"#411 是真 bug 且已在本 PR 修复"。实际链路 filesenseEnabled → resolveRuntimeConfig → resolved.filesense → planner-skills:134 一直通畅,本分支自己 revert 时也承认了这点。次要假设:deep 夹具的 430/180 口径依赖 FEATURES/ENTITIES 长度,任何改动都会让 run-eval.mjs:33-35 与 sdd.yaml 描述里的数字漂移,而没有任何检查会因此失败(generate.mjs 只 console.log 实测值)。
  • 简洁性: 与问题规模匹配。file_unchanged 是最小可行的"防改测试"验收;report-filesense.mjsreport.mjs 分开而不是加 mode 分支,是正确的取舍(两者叙事不同,合并只会造出模式 flag)。事件载荷只截存必要字段并做长度截断,没有过度通用化。
  • 结构质量: 没有退化。新增逻辑全部落在新文件或既有 switch 的新 case 上;collectEventDetail 从主循环抽出为纯函数,主循环没有变长成难扫描的块;无薄 wrapper、无重复的既有 helper、无文件跨越可扫描边界。
  • 变更范围: 与目标一致,无无关重构与格式噪音。唯一可讨论的是 flat 夹具的 pnpm --dir ... install 文档行——但它是 runner 新增硬失败的直接后果,属于必要范围。
  • 验证: 三条 harness 守卫(provenance / 零触发 / 关臂泄漏)与 file_unchanged 是本 PR 最关键的新逻辑,却没有任何自动化测试;新增的 scripts/tests 用例只断言 README 文案。埋 bug 的 4 失败 / 11 通过与 estimateEta 的 Math.ceil 语义我按 diff 复核一致(floor(1000/500)+1=3 仅被"整数倍"那条用例捕获,600km 那条在修复前后都通过)。

缺失覆盖

  • checks.mjsfile_unchanged:无单测。fixtureRoot 传错或缺失时它返回 ok:false,会把全部 deep bugfix 任务判为 FAIL——一个静默把整臂推向失败的路径,值得一条与 README 文案检查同级的测试。
  • report-filesense.mjs 的三条守卫(fixture/taskSet 不一致、开臂零触发、关臂泄漏):都是"防止空结果被当证据"的承重逻辑,均无测试。用几行合成 JSONL 即可覆盖退出码与退出原因。
  • deep 夹具规模(242 文件 / 188 目录 / 430 条目)与截断阈值:目前只写在注释、sdd.yaml 描述与 README 散文里。generate.mjs 已经实测并打印,建议让它在实测值与文档值不符时非零退出,否则调整 FEATURES/ENTITIES 会让这个夹具的存在理由静默失效。
  • --fixture deep--arm ablation 的组合未被文档或校验覆盖:deep 夹具没有 sdd.disabled.yaml(这正是该臂期望的"文件不存在"),但也没有任何地方说明这个组合是否受支持。

Comment thread benchmarks/eval/run-eval.mjs Outdated
// 对照臂也必须把 filesense 钉死。不写的话它取自 FRONTAGENT_FILESENSE_ENABLED——
// 一臂钉死、一臂随环境,操作者环境里存在该变量就会得到「两臂都关」的空结果,
// 与 #386 废掉 guard 臂、#411 废掉本臂是同一类失真。
full: { sddPath: 'sdd.yaml', filesenseEnabled: true },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

filesenseEnabled 同理,filesenseMaxEntries / filesenseMaxBytes / filesenseOutput / filesenseWriteMode / filesenseTimeoutMs 也应在三臂里钉死(或落盘进 record 供报告断言),否则报告的"预算闸被触发数"随操作者环境静默改变。

Comment thread benchmarks/eval/run-eval.mjs Outdated
if (typeof event.entries !== 'number') {
// 不能 throw:`Agent.emit` 对监听器异常是 try/catch + debug 级日志
// (`agent.ts:307-315`),抛出去只会被静默吞掉——正是本 harness 要防的那种
// 「守卫看起来加了、实际没生效」。改为记账,由主循环在写完本条记录后中止本臂。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

注释说"由主循环在写完本条记录后中止本臂",但 245-253 行是先中止、不写记录(且那才是正确行为)。改成"在写盘前中止",避免下次有人按注释把 process.exit 挪到 appendFileSync 之后。

Comment thread benchmarks/eval/run-eval.mjs Outdated
// 报告器据此断言两臂用的是同一套夹具与任务集——否则会拿 flat 的方法学
// 去描述 deep 的产物,正是 #410 撤回结论的同一类失真。
fixture: FIXTURE_KIND,
taskSet: TASKS_FILE.split('/').pop(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TASKS_FILE.split('/').pop() 在非 POSIX 分隔符下会把整条绝对路径写进 taskSet,报告随后拼成 benchmarks/eval/<绝对路径>;改用 node:pathbasename(TASKS_FILE)(该文件已从 node:path 导入其他函数)。

Sixth repo-guard round on #412.

Only `filesenseEnabled` was pinned; `output` / `writeMode` / `maxEntries` /
`maxBytes` / `timeoutMs` still came from `FRONTAGENT_FILESENSE_*`. That matters
more than it looks: `planner-skills.ts` lets a configured value **override**
trigger-policy's default budget, and "was the budget truncated" is one of the
conclusions this report prints. An operator with `FRONTAGENT_FILESENSE_MAX_ENTRIES`
set would have published a fact about their machine.

All five are pinned now, the effective config is written into every JSONL record,
and `report-filesense.mjs` refuses to emit if the two arms disagree — the same
shape as the existing fixture/task-set provenance guard.

Verified end-to-end: both arms carry identical budgets, differ only in `enabled`,
and the arm switch still reads 1 vs 0.

Also corrected the framing: the off arm does not make filesense *unavailable*,
it stops the planner *injecting* the navigate step. The tool stays in the
registry; the plan prompt and schema expose no filesense action, so the model
cannot reach it — which is what the off-arm zero-trigger guard verifies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ceilf6 added a commit that referenced this pull request Aug 1, 2026
They belong to PR #412. Swept in by `git add` while sitting untracked in a
shared worktree; this is the third time, so the generated tree is now kept out
of the worktree entirely rather than re-untracked after the fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ceilf6 added a commit that referenced this pull request Aug 1, 2026
They belong to PR #412. Swept in by `git add` while sitting untracked in a
shared worktree; this is the third time, so the generated tree is now kept out
of the worktree entirely rather than re-untracked after the fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 请求修改
决策摘要: 无生产代码变更、no-filesense 臂的构造有效性经查证成立、#411 的回退判断正确;但 FILESENSE_PINNED 用一组常量顶替了 trigger-policy 的按 intent 预算,而报告把「预算闸被触发」当结论印出,合入后会产出一份描述合成预算而非产品行为的证据文件——先修这一项再合。

级联分析

  • 变更符号: runChecks(checks, {workspace, resultText, fixtureRoot})(新增第 3 个字段)、run-eval.mjs--arm / --fixture CLI 契约、JSONL 记录 schema(新增 fixture / taskSet / filesenseConfig / eventDetails / filesenseEntries / filesenseTruncated)、新增 check kind file_unchanged
  • 受影响流程: benchmarks/eval/ 评测编排 → JSONL 产出 → report-filesense.mjs;README 文档块 → scripts/tests/workflow-rules.test.mjs 断言。
  • 变更集外调用方: runChecksrun-eval.mjs:233 一处调用,签名向后兼容(fixtureRoot 可选,缺失时 file_unchanged 返回 ok:false)(text search)。.github/** 无任何 run-eval 引用,新增的 node_modules 硬失败仅影响开发者手动流程 (text search)。report.mjs(2026-07-12 那轮)读 full.jsonl / ablation.jsonl,deep 产物落 -deep.jsonl,互不干扰。
  • 置信度: medium —— 无代码图谱,结论由 diff + 全仓文本检索得出;packages/** 未被触碰,跨包耦合只有 run-eval.mjs 运行时读取 core 的 AgentEvent 字段,我已逐字段核对 packages/core/src/types.ts:776-784packages/shared/src/types/validation.ts:9-13,当前名称全部对得上。

问题发现

  1. [高] 钉死的预算顶替了 trigger-policy 的按 intent 预算,而截断数是报告的结论行

    • 证据: run-eval.mjs:64-70 三臂统一钉死 filesenseMaxEntries: 300 / filesenseMaxBytes: 131072 / filesenseTimeoutMs: 3000packages/core/src/skills/planner-skills.ts:147-149filesense?.maxEntries ?? decision.maxEntries,即配置值替换而非兜底策略预算。packages/core/src/filesense/trigger-policy.ts 的预算是按 intent 分配的:validate_freshness 120/48KB/1500ms(:153-156)、query 的 understand_structure 250/96KB(:164-165)、prepare_create 180/64KB(:188-189)、prepare_refactor 500/160KB(:201-202)。钉死后全部塌成 300/128KB/3000ms —— prepare_refactor收紧 500→300、160KB→128KB,prepare_create 被放宽 180→300,validate_freshness 被放宽 120→300 且超时翻倍。
    • 受影响调用方/流程: report-filesense.mjs 把「预算闸被触发(truncated)的任务数」作为独立结论行印出,并在「读数纪律」里据此解释夹具规模是否足够。该数字现在描述的是产品从不使用的扁平预算:deep 夹具 430 条目下,两条 refactor 任务会被харness 制造出策略本不会产生的截断,而 create 任务的截断则被抑制。PR 描述里的实测表(prepare_create、maxEntries 180 → 扫 180、truncated true)与 run-eval.mjs:33-35 的「实测同样预算下扫 180 条即截断」,说的正是随后被这段配置覆盖掉的那个预算。
    • 最小可行修复: filesenseOutput / filesenseWriteMode 保留钉死(这两项是忠实的 no-op:config.ts:144-154 未设时返回 undefinedplanner-skills.ts:150-151 的兜底恰好就是 'summary' / 'cache');把三个数值预算从 FILESENSE_PINNED 移除,改为在 harness 启动时删除或断言未设 FRONTAGENT_FILESENSE_MAX_ENTRIES / _MAX_BYTES / _TIMEOUT_MS。这样同时拿到臂间确定性与产品真实的按 intent 预算。eventDetails.filesense[].intent 已经记了 intent,报告可据此把截断归因到具体 intent。若扁平化是刻意选择,就在报告的「读数纪律」里写明预算已被压平为 300、不再反映出厂策略,并停止把截断呈现为夹具属性。
  2. [中] filesenseConfig 记的是请求值不是生效值,报告的预算守卫是同义反复

    • 证据: run-eval.mjs:247-250 用传入时的同一组 ARM_OPTIONS / FILESENSE_PINNED 常量构造该字段,全程没有回读 resolveRuntimeConfig 实际产出的 FilesenseConfigreport-filesense.mjs 的预算一致性守卫比较的正是这些自证字段。
    • 受影响调用方/流程: 这恰好是本系列在追的缺陷类(#411 / #386 / #400 / #403:「参数在一层被接受,从未到达读它的那一层」)—— 真发生时 JSONL 仍会照常宣称钉死的预算。该守卫实际只能捕获「两臂 JSONL 来自不同版本 harness」这一种情形。PR 描述的「The effective config is written into every record」高于实际。注意 enabled 是被独立验证过的:报告的关臂零触发守卫构成真实证据;预算没有对应证据。
    • 最小可行修复: 字段更名为 filesenseConfigRequested,并在报告里注明预算一致性是同进程同常量的构造性结论、非遥测验证;或让 core 在 filesense_navigated 载荷里回显生效的 navigate 参数,再据此断言。
  3. [中] 形状守卫只覆盖一个事件的一个字段,其余新增载荷仍会静默降级

    • 证据: run-eval.mjs:144-151 只校验 filesense_navigated.entries 是数字。同一函数还读 event.truncatedevent.candidateCount(:157-158)与 event.result?.results / event.result?.blockedBy(:169-172),全部无守卫。我已核对这些名称当前与 types.ts:776-784validation.ts:9-13 一致。
    • 受影响调用方/流程: truncated 是报告头条截断行的唯一输入。core 一旦改名,每条记录 truncated: undefinedfilesenseTruncated: false → 报告印出「预算闸被触发 0」,正是 :141-143 注释声称要根治的「空结果被当成证据」,只是换了个字段。
    • 最小可行修复: 把同一条守卫扩展到 typeof event.truncated === 'boolean'typeof event.candidateCount === 'number',复用已有的 harnessFailures 中止路径。
  4. [低] 手写 basename

    • 证据: run-eval.mjs:244TASKS_FILE.split('/').pop(),而 node:pathjoin / resolve / dirname 已在 :5 导入。
    • 受影响调用方/流程: 非 POSIX 分隔符下会记入绝对路径,report-filesense.mjs 随后对 fixture/taskSet 拼串做 split('/') 解构会取错分段。
    • 最小可行修复: 改用 basename(TASKS_FILE)

行级发现

  • [benchmarks/eval/run-eval.mjs:67] 这个 300 会经 planner-skills.ts:147 顶替 trigger-policy 的按 intent 预算(prepare_create 180、prepare_refactor 500、validate_freshness 120),使报告的「预算闸被触发」描述一套产品不使用的扁平预算;改为清除/断言 FRONTAGENT_FILESENSE_MAX_ENTRIES 等环境变量并让本字段保持未设,即可同时拿到臂间确定性与真实策略预算。
  • [benchmarks/eval/run-eval.mjs:157] truncated 与下一行的 candidateCount 未纳入上面那条形状守卫,改名后会静默变成 false/undefined 并让报告印出「预算闸被触发 0」;按 entries 的写法补两条 typeof 校验进 harnessFailures
  • [benchmarks/eval/run-eval.mjs:247] 这里写的是请求值而非 resolveRuntimeConfig 的生效值,报告据此做的预算一致性校验对「配置在中途被丢弃」(#411 那一类)完全不敏感;更名为 filesenseConfigRequested 并在报告中降格该守卫的表述,或让 core 回显生效参数后再断言。
  • [benchmarks/eval/run-eval.mjs:244] 手写 split('/').pop() 在非 POSIX 分隔符下会记入绝对路径并破坏报告端的 split('/') 解构;node:path 已导入,改用 basename(TASKS_FILE)

Karpathy 评审

  • 假设: PR 对自身假设的处理是本次评审见到的正面样本——#411 被自查证伪并连同 run.ts / run.test.ts 一起回退,我核对 packages/runtime-node/src/config.ts:68-73:341-361 确认 RuntimeConfigInput 确实已暴露六个扁平字段,回退判断成立,diff 中确无 packages/** 文件。关臂的构造有效性也成立:packages/core/src/llm/schemas.ts / prompts.ts / plan-generation.ts)全无 filesense 字样,模型无法自行选中 filesense_navigate,注入是 planner-skills.ts:133-134 这一条路径且受 enabled !== false 控制 (text search)。剩下的未言明假设只有一条:预算钉死等价于「排除操作者环境」,实际上它同时排除了产品自己的预算策略(问题 1)。
  • 简洁性: 三臂共享 FILESENSE_PINNED 展开、collectEventDetail 按事件类型早返回、报告的守卫逐条独立且各带失败原因,没有猜测性抽象。file_unchanged 是最小可行的新增 check kind,而不是一套通用快照框架。
  • 结构质量: 无退化。checks.mjs 新增一个 case(+22 行,全文件 60 行);run-eval.mjs 增至 291 行,远未越过可扫描边界;新增文件各自职责单一,report-filesense.mjsreport.mjs 的分工在文件头写明了。夹具生成器把「实测规模」而非硬编码数字打印出来,避免了文档漂移。
  • 变更范围: 与既定目标一致,无夹带重构。README 顺带补上平坦夹具的 pnpm --dir benchmarks/eval/fixture install,这是新增的 node_modules 硬失败带来的必要文档修正,不是范围蔓延。
  • 验证: 夹具算术可复核并成立——24 features × 8 + 12 entities × 3 + 14 个已提交锚点 = 242 文件,188 目录,430 条目,与 generate.mjs / sdd.yaml / README / run-eval.mjs:33-35 的说法一致。三处埋 bug 逐条核对无误:computeTotal 丢弃 quantity(2 条失败)、mergeLines 用赋值而非累加(1 条)、estimateEtafloor(d/500)+1 在整数倍处多一天(1 条),合计 4 失败 / 11 通过,与 PR 声明相符,且每条都存在有效修法(eta 用 Math.ceildistanceKm <= 0 早返回已护住 0)。.gitignore 的否定链正确(每一层父目录先取消忽略再忽略其内容)。缺口在于报告自身的四条拒绝路径无任何测试(见下)。

缺失覆盖

  • report-filesense.mjs 的四条拒绝路径(两臂夹具/任务集不一致、预算配置不一致、开臂零触发、关臂有泄漏)是这份报告全部的完整性保障,目前没有任何测试;scripts/tests/workflow-rules.test.mjs 只断言 README 字符串。建议对 load 之后的判定逻辑抽出纯函数并补最小单测,覆盖四条 process.exit(1)
  • 新增 check kind file_unchanged 无测试:至少覆盖缺 fixtureRoot、原件或工作区文件缺失、内容一致、内容被改四种返回。
  • 修问题 1 后,需要一条断言「未向 runFrontAgentTask 传入数值预算时,filesense_navigated 的扫描量随 intent 变化」的证据,否则无法确认策略预算确实生效。
  • README 中新钉进 workflow-rules.test.mjs:583pnpm --dir benchmarks/eval/fixture-deep install 本身未被执行验证;fixture-deep 与既有平坦夹具一样位于 pnpm-workspace.yamlpackages/* / apps/*)之外,沿用既有实践,属残余风险而非发现。

Comment thread benchmarks/eval/run-eval.mjs Outdated
const FILESENSE_PINNED = {
filesenseOutput: 'summary',
filesenseWriteMode: 'cache',
filesenseMaxEntries: 300,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这个 300 会经 planner-skills.ts:147 顶替 trigger-policy 的按 intent 预算(prepare_create 180、prepare_refactor 500、validate_freshness 120),使报告的「预算闸被触发」描述一套产品不使用的扁平预算;改为清除/断言 FRONTAGENT_FILESENSE_MAX_ENTRIES 等环境变量并让本字段保持未设,即可同时拿到臂间确定性与真实策略预算。

paths: event.paths ?? [],
entries: event.entries,
elapsedMs: event.elapsedMs,
truncated: event.truncated,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

truncated 与下一行的 candidateCount 未纳入上面那条形状守卫,改名后会静默变成 false/undefined 并让报告印出「预算闸被触发 0」;按 entries 的写法补两条 typeof 校验进 harnessFailures

Comment thread benchmarks/eval/run-eval.mjs Outdated
taskSet: TASKS_FILE.split('/').pop(),
// 生效的 filesense 配置随记录落盘:没有它,「预算被截断」这个结论
// 无法与产生它的预算值对应,报告也就无从校验两臂是否同一套预算。
filesenseConfig: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这里写的是请求值而非 resolveRuntimeConfig 的生效值,报告据此做的预算一致性校验对「配置在中途被丢弃」(#411 那一类)完全不敏感;更名为 filesenseConfigRequested 并在报告中降格该守卫的表述,或让 core 回显生效参数后再断言。

Comment thread benchmarks/eval/run-eval.mjs Outdated
// 报告器据此断言两臂用的是同一套夹具与任务集——否则会拿 flat 的方法学
// 去描述 deep 的产物,正是 #410 撤回结论的同一类失真。
fixture: FIXTURE_KIND,
taskSet: TASKS_FILE.split('/').pop(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

手写 split('/').pop() 在非 POSIX 分隔符下会记入绝对路径并破坏报告端的 split('/') 解构;node:path 已导入,改用 basename(TASKS_FILE)

…licy's per-intent values

Seventh repo-guard round on #412, and a consequence of my own previous round.

Pinning `maxEntries` / `maxBytes` / `timeoutMs` to make the arms deterministic
**flattened the product's per-intent budgets**. `planner-skills.ts` uses
`filesense?.maxEntries ?? decision.maxEntries` — configuration *replaces* the
policy value rather than backing it up. trigger-policy allocates by intent:
`validate_freshness` 120, `prepare_create` 180, `understand_structure` 250,
`prepare_refactor` 500. Pinning 300 **tightened** `prepare_refactor` and loosened
`validate_freshness` while doubling its timeout — and "was the budget truncated"
is a conclusion line of this report. I would have been measuring a budget the
product never uses.

Determinism now comes from asserting the three environment variables are unset,
which the harness refuses to start without. The arms stay reproducible and the
per-intent budgets stay real; `eventDetails.filesense[].intent` is already
recorded, so truncation can be attributed to the intent that caused it.

`output` / `writeMode` stay pinned — those are faithful no-ops: unset they
resolve to `undefined`, and `planner-skills.ts` falls back to exactly `'summary'`
/ `'cache'`.

**`filesenseConfig` renamed to `filesenseConfigRequested`.** It read back the
same in-process constants it was passing in, so the report's consistency check
was tautological. The name and the report now say so; a genuine check needs core
to echo the effective navigate parameters in the event payload.

**The shape guard covered one field of one event.** Checking only `entries` left
`truncated` degrading to `false` and `candidateCount` to `0` — the same silent
degradation, one field over. All four collected fields are checked now.

Verified end-to-end: the navigate step runs under `understand_structure`'s own
budget again, and the arm switch still reads 1 vs 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ceilf6 added a commit that referenced this pull request Aug 1, 2026
…swept in

Fourth occurrence. Untracking after the fact does not work: the generated tree
stays in the shared worktree, this branch carries no ignore rule for it, and the
next `git add benchmarks/` re-stages all 229 files.

The ignore rule is the fix — #412 exempts the 20 files it genuinely commits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 评论
决策摘要: 可以合并——变更全部位于 benchmarks/eval/,无生产代码,消融臂的机制已按仓库实际接口核实为真;四个非阻塞问题都关于「空数据会被误读成零事件」,建议合入前后修掉其中前两条注释/守卫级问题。

级联分析

  • 变更符号: runChecks(新增可选 fixtureRootfile_unchanged check kind)、run-eval.mjs CLI 契约(新增 --fixture、第三臂 no-filesense、输出文件名后缀)、JSONL 记录 schema(新增 fixture / taskSet / filesenseConfigRequested / eventDetails / filesenseEntries / filesenseTruncated)、新增 report-filesense.mjs、新增夹具与任务集。
  • 受影响流程: 评测编排 → 检查执行 → JSONL 落盘 → 报告生成;README 与 scripts/tests/workflow-rules.test.mjs 的文档断言。
  • 变更集外调用方: runChecks 全仓仅 run-eval.mjs 一处调用,已同步更新 (text search)。benchmarks/eval/report.mjs 仍读 full.jsonl / ablation.jsonl,deep 跑产出 -deep 后缀、记录字段为纯新增,故不受影响 (已直接读 report.mjs:1-34 确认)。packages/**apps/** 无改动,PR 描述里「run.ts 变更已回滚」与仓库现状一致:packages/runtime-node/src/run.ts:42RunFrontAgentTaskOptions extends RuntimeConfigInputconfig.ts:68-73 已暴露 filesenseEnabled 等扁平字段,planner-skills.ts:134filesense?.enabled !== false——即消融臂无需新 seam,#411 关为 invalid 的判断成立。
  • 置信度: medium(无代码图谱;结论来自直接阅读被调用文件 + 全仓文本搜索,packages/** 零改动使下游执行流不需评估)

问题发现

  1. [中] rollback_started / rollback_completed 在 develop 上同样没有 emit 站点,注释声称「一定会到」与仓库事实不符

    • 证据: 全仓搜索这两个事件名,只命中 packages/core/src/types.ts:794-795(联合类型声明)与 apps/desktop/src/state/executionReducer.ts:286,289(消费方),packages/** 源码中没有任何 emit 站点;validation_failed 同样只有 types.ts:793 一处声明。而 run-eval.mjs:206 的注释写的是「rollback_started / rollback_completed 是既有事件,一定会到」,只把 rollback_failed 标为未接线。PR 描述里的清单同样只列了 validation_failedrollback_failed
    • 受影响调用方/流程: 每条记录的 eventDetails.rollback 在 develop 上恒为 []。后续读 JSONL 写报告的人按相邻注释会把它读成「没有发生回滚」,而不是「事件未接线」——正是本 PR 为 validation_failedrun-eval.mjs:193-195 明确防住的那类误读,也是 #410 撤回分类的同一根因。
    • 最小可行修复: 把 run-eval.mjs:206-207 的注释改为「三者在 develop 上均无 emit 站点,空数组含义是未接线」,与 193 行同一措辞;并同步 PR 描述里的未接线清单。
  2. [中] validation_failed 载荷提取没有形状守卫,与同一函数内 filesense 分支的标准不对称

    • 证据: run-eval.mjs:170-181filesense_navigated 的四个字段逐个断言类型并记入 harnessFailures(主循环据此在写盘前中止),理由写得很清楚是「宁可吵,不可静默」;但 run-eval.mjs:199-202validation_failed 用的是 (event.result?.results ?? []) / (event.result?.blockedBy ?? []) 纯可选链。当前形状确实匹配 packages/shared/src/types/validation.ts:9-14pass / results[].pass|type|severity|message / blockedBy?),所以今天是对的。
    • 受影响调用方/流程: 一旦 ValidationResult 改名或嵌套层级变化,failedChecks 会静默变成 [],报告照样能出「零拦截」。而 #402 落地后的第一轮跑正是这个字段最有说服力、也最经不起静默降级的时刻。
    • 最小可行修复: 在 validation_failed 分支加同样的记账——typeof event.result?.pass !== 'boolean'!Array.isArray(event.result?.results) 时 push 进 harnessFailures
  3. [低] 报告器对「平坦夹具」不设闸,而 PR 自己论证平坦夹具上的对比按构造不可解释

    • 证据: report-filesense.mjs:47 只在 FIXTURE_KIND === 'unknown' 时拒绝出报告。用 --fixture flat 跑出的 full.jsonl / no-filesense.jsonl 会通过全部守卫(两臂夹具一致、开臂有触发、关臂零触发),正常输出一份含「全量差值 X 个百分点」的报告;而 PR 描述与 run-eval.mjs:30-32 都论证该夹具上「预算闸从不关闸、导航与 ls -R 不可区分」。
    • 受影响调用方/流程: 报告产物是要提交进 benchmarks/results/ 当证据的。正文虽然会印出夹具名、「读数纪律」也提示了 truncated=0 的外推限制,但结论行仍是一个看起来可引用的百分点差值。
    • 最小可行修复: 在 FIXTURE_KIND !== 'deep'truncTasks(arms.full).length === 0 时,与零触发守卫同级拒绝出报告(或至少在标题下方插入一条不可省略的横幅),复用已有的 process.exit(1) 路径。
  4. [低] cleanGeneratedTree 对锚点 feature 只清 4 个硬编码子目录,遗漏了它自己生成的 lib/format.tsindex.ts

    • 证据: generate.mjs:104 对锚点 feature 只 rmSync ['api','model','hooks','ui'],但生成循环对每个 feature(含 checkout/cart/shipping)都会写 features/<f>/lib/format.tsgenerate.mjs:138-142)和 features/<f>/index.ts。这与该函数上方 generate.mjs:92-96 自述的不变量(「不清会留下孤儿文件,条目数与截断行为随之漂移——而这两个数正是这个夹具存在的理由」)不一致。
    • 受影响调用方/流程: 生成器结构调整后,3 个锚点 feature 下会残留约 5 个孤儿文件,条目总数漂移;而条目数与是否截断正是报告的结论行与 README 中引用的实测数字。
    • 最小可行修复: 锚点分支改为「删除该目录下除已提交锚点文件外的一切」,或把豁免清单与 .gitignore 的白名单收敛到同一处常量,避免两份手写清单继续分叉。

行级发现

  • [benchmarks/eval/run-eval.mjs:206] rollback_started / rollback_completed 在 develop 源码中同样没有 emit 站点(仅 types.ts:794-795 声明 + desktop reducer 消费),注释断言「一定会到」会让读者把恒空的 eventDetails.rollback 读成「零回滚」;改为与 193 行同一措辞,声明三者当前均未接线。
  • [benchmarks/eval/run-eval.mjs:199] 这里用可选链兜底提取 ValidationResult,缺少 filesense 分支那样的形状断言;result.results 一旦改名会静默产出 failedChecks: []。加一条 !Array.isArray(event.result?.results)harnessFailures.push(...) 即可与同函数标准对齐。
  • [benchmarks/eval/report-filesense.mjs:47] 守卫只拦 unknown 夹具;flat 夹具的对比按本 PR 自己的论证不可解释,却能正常出一份带百分点结论的报告。建议在 FIXTURE_KIND !== 'deep' 且零截断时按同一路径拒绝出报告。
  • [benchmarks/eval/fixture-deep/generate.mjs:104] 硬编码的 ['api','model','hooks','ui'] 漏掉了本文件同样会写入锚点 feature 的 lib/format.tsindex.ts,与 92-96 行自述的「不留孤儿、条目数不漂移」不符;改为保留白名单式清理。

Karpathy 评审

  • 假设: 关键假设已被验证而非声称——filesenseEnabled 确实经 RuntimeConfigInputresolved.filesenseplanner-skills.ts:134 生效,filesenseOutput/WriteMode 钉死为 'summary'/'cache'planner-skills.ts:150-151 的兜底一致,属忠实 no-op;数值预算改为「断言环境变量未设」而非钉死,与 trigger-policy.ts 的按 intent 分档(120/180/250/300/500)一致。剩余未验证假设是「关臂模型确实点不到已注册的 filesense_navigate 工具」,PR 用关臂零触发守卫把它转成了可证伪项,处理得当。
  • 简洁性: 无投机抽象。file_unchanged 是最小新增 check kind,fixtureRoot 为可选参数、老调用不受影响。两个报告器复制了 6 个一行 helper(rate/avg/sum/pct/count),叙事确实不同,量级不足以要求抽公共模块。
  • 结构质量: 未见退化。run-eval.mjs 增至 322 行、report-filesense.mjs 204 行,均在可扫描范围;没有共享路径的 ad-hoc 特例,--fixture 分支集中在文件头部的三个常量上。-deep 输出后缀避免了两套夹具静默混入同一 JSONL,是真正删除了一类误用而非搬运复杂度。
  • 变更范围: 与既定目标一致。回滚 run.ts / run.test.ts 后 diff 不含生产代码;README 与 workflow 测试的新增断言与新命令一一对应(--arm no-filesense--fixture deepgenerate.mjsreport-filesense.mjspnpm --dir ... install 均已在 README 1313-1331 出现)。给平坦夹具补的 pnpm --dir benchmarks/eval/fixture install 是新增硬失败守卫的必要配套,不算范围蔓延。
  • 验证: 计划外的三处已核实:biome.json:61 忽略 benchmarks,故新增夹具文件不进 lint;pnpm testturbo test,只覆盖 workspace(pnpm-workspace.yamlpackages/* / apps/*),fixture-deep 的 4 条埋雷失败测试不会进 CI;平坦夹具已有同类埋雷(fixture/src/utils/parseQuery.tsdecodeURIComponent),提交失败测试属既有惯例。PR 自报的 4 failures / 11 passing 与三个埋雷模块的断言逐条对得上(computeTotal 2、mergeLines 1、estimateEta 1;总计 15 条)。

缺失覆盖

  • checks.mjsfile_unchanged 没有任何自动化测试:全仓 benchmarks/eval/ 下无脚本级测试,scripts/tests/ 也只有 README 断言。至少应覆盖三条分支——基准与工作区一致、被改动、以及 fixtureRoot 缺失时返回 ok:false(后者目前只有它自己会静默把任务判失败)。
  • report-filesense.mjs 的四道完整性闸(两臂夹具/任务集不一致、预算不一致、开臂零触发、关臂泄漏)是这份证据的把关点,目前只能靠手工触发验证;建议补一组喂假 JSONL 的脚本级用例,断言各自 exit 1
  • run-eval.mjs 的事件契约中止路径(harnessFailures → 不写盘 → exit 3)同样无覆盖;这条路径的价值恰恰在于「平时不跑」,一旦回归会以静默写入污染记录的形式表现出来。

});
return;
}
// `rollback_started` / `rollback_completed` 是既有事件,一定会到;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

rollback_started / rollback_completed 在 develop 源码中同样没有 emit 站点(仅 types.ts:794-795 声明 + desktop reducer 消费),注释断言「一定会到」会让读者把恒空的 eventDetails.rollback 读成「零回滚」;改为与 193 行同一措辞,声明三者当前均未接线。

if (event.type === 'validation_failed') {
details.validationFailed.push({
// 判失败的检查项:区分「真·内容拦截」与「纯工具失败」的唯一依据
failedChecks: (event.result?.results ?? [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这里用可选链兜底提取 ValidationResult,缺少 filesense 分支那样的形状断言;result.results 一旦改名会静默产出 failedChecks: []。加一条 !Array.isArray(event.result?.results)harnessFailures.push(...) 即可与同函数标准对齐。

console.error('JSONL 缺少 filesenseConfigRequested 字段——来自旧版 harness,无法确认预算口径,拒绝出报告');
process.exit(1);
}
if (FIXTURE_KIND === 'unknown') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

守卫只拦 unknown 夹具;flat 夹具的对比按本 PR 自己的论证不可解释,却能正常出一份带百分点结论的报告。建议在 FIXTURE_KIND !== 'deep' 且零截断时按同一路径拒绝出报告。

if (!entry.isDirectory()) continue;
if (anchored.has(entry.name)) {
// 锚点目录只清生成的子目录,保留 lib/ 下已提交的文件
for (const sub of ['api', 'model', 'hooks', 'ui']) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

硬编码的 ['api','model','hooks','ui'] 漏掉了本文件同样会写入锚点 feature 的 lib/format.tsindex.ts,与 92-96 行自述的「不留孤儿、条目数不漂移」不符;改为保留白名单式清理。

Conflict in run-eval.mjs's onEvent: #414 added per-stage counting, this branch
added payload collection. Both are needed — the stage counter without payloads
cannot tell a real interception from a tool error, and payloads without the
stage counter leave the aggregate unusable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ceilf6
ceilf6 merged commit 1b698e8 into develop Aug 1, 2026
7 checks passed
@ceilf6
ceilf6 deleted the feat/filesense-ablation-eval branch August 1, 2026 09:06

@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

代码评审报告: feat(eval): filesense ablation arm, deep fixture, and event payloads (#411)

风险等级:
处理建议: 请求修改
决策摘要: 无生产代码风险,工具设计扎实且自证性强;但生成的报告正文有一句可验证为假的溯源声明(report-filesense.mjs:109),而「预算闸会关上」这条核心方法学结论是在 trigger-policy 从不发出的 depth 2 + maxEntries 180 组合下实测的——两处都是文本/度量口径修正,改完即可合。

级联分析

  • 变更符号: runChecks(新增 fixtureRoot 参数与 file_unchanged 分支)、run-eval.mjsARM_OPTIONS / collectEventDetail / JSONL record schema;新增 report-filesense.mjsfixture-deep/*tasks-deep.json
  • 受影响流程: 仅 benchmarks/eval/ 评测编排;packages/**apps/** 无改动。已直接确认 packages/runtime-node/src/run.ts:176,446 仍为 develop 版本(filesense: resolved.filesense,无嵌套 filesense 选项),PR 所述的回退是真实的。
  • 变更集外调用方: runChecks 全仓仅 run-eval.mjs:269 一处调用,新参数为可选、向后兼容。report.mjsfull.jsonl/ablation.jsonl,deep 产物落 -deep.jsonl,无覆盖。fixture-deep/package.json 不在 pnpm-workspace.yaml(仅 packages/*apps/*)内,嵌套 lockfile 与既有 fixture/ 同构,不污染根安装。
  • 跨包契约核对(逐字段直读,非图谱遍历): filesense_navigatedintent/paths/entries/elapsedMs/truncated/candidateCount/warningspackages/core/src/types.ts:796-805 完全一致;validation_failed.result.{results,blockedBy}packages/shared/src/types/validation.ts:9-14 一致;rollback_failed 确实尚未进入联合类型,PR 的说明属实。filesenseEnabled/Output/WriteMode 确实存在于 RuntimeConfigInputconfig.ts:68-73),planner-skills.ts:156filesense?.enabled !== false 就是关臂的真实开关,output ?? 'summary' / writeMode ?? 'cache':172-173:149)证实钉死这两项是忠实 no-op。
  • 置信度: medium(结论来自直接读文件而非代码图谱;受影响面已完整枚举且很小)

问题发现

  1. [高] 报告正文声称的校验范围与 harness 实际记录的内容不符

    • 证据: report-filesense.mjs:109 写入产物:"SDD、guard、RAG、模型、任务集、夹具、以及 filesense 的全部预算参数都相同且被显式钉死(已从 JSONL 校验)"。但 JSONL record(run-eval.mjs:273-306)只含 fixture / taskSet / filesenseConfigRequested{enabled,output,writeMode},不含 SDD、guard、RAG、模型任一字段;而数值预算恰恰是 run-eval.mjs:56-75 明确刻意不钉的("数值预算刻意不钉"),仅靠启动时的环境变量断言(:79-89),该断言结果同样未落进 JSONL。
    • 受影响调用方/流程: 该字符串是要提交进 benchmarks/results/ 当证据的产物首屏。读者会据此认为臂间同质性已由数据验证,而实际上是构造性假设。这正是 #410 撤回结论的同一类失真,只是这次出现在用来防止它的工具里。
    • 最小可行修复: 把该句拆成两句——"夹具、任务集、filesense 的 enabled/output/writeMode 已从 JSONL 校验;SDD、guard、RAG、模型与数值预算由 harness 单进程常量保证,未落盘校验,数值预算来自 trigger-policy 按 intent 分档且启动时断言相关环境变量未设"。
  2. [中] 「深夹具让预算闸关上」是在 trigger-policy 不会发出的 depth/预算组合下实测的

    • 证据: PR 正文与 run-eval.mjs:30-36、README 均以 "src, depth 2, maxEntries 180 → 180 条截断" 作为深夹具存在的理由。但 trigger-policy.ts:184-190prepare_createdepth 1 / maxEntries 180;depth 2 只出现在 understand_structure(250)、locate(300)、prepare_refactor(500)。(depth 2, 180) 不是任何 intent 会产生的组合。按 engine.ts:730 + walkDirectories:252-287 的 depth 语义(depth N 仍列举第 N 层目录的子项)估算本夹具:从 src depth 1 ≈ 43 条,depth 2 ≈ 219 条。于是 prepare_create(depth 1/180)不截断,locate(depth 2/300)不截断,understand_structure(depth 2/250)只有在任务文案恰好含英文字面量 srctrigger-policy.ts:113-133 的 FOCUS_DIRS 匹配,12 条任务中仅 deep-query-structure 命中)时才可能越过 250。
    • 受影响调用方/流程: 报告的结论行「预算闸被触发(truncated)的任务数」很可能是 0–1,而报告自己的读数纪律会把 0 解释为"夹具太小",与 README 的"deep 夹具会截断"直接冲突。夹具确实显著更大、同名文件干扰也确实成立,受影响的只是截断这一条主张。
    • 最小可行修复: 用每个 intent 实际会发出的 (paths, depth, maxEntries) 重跑一次探针,把表格换成按 intent 的真实值;若真实预算下均不截断,就把 README 与代码注释里的理由从"预算闸会关闸"改为"同名文件使文件名不足以定位",不要保留未被产品配置支持的截断承诺。
  3. [中] 事件形状守卫挡不住它自己描述的那种静默降级

    • 证据: run-eval.mjs:160-181 的注释声称"core 一旦改名,entries 会静默变成 0、报告照样出「累计扫描条目 0」——宁可吵,不可静默"。但事件载荷在到达监听器前先经 packages/core/src/context/helpers.ts:56-63normalizeFilesenseNavigation 归一化:entries 非 number 时补 0elapsedMs0truncated=== true 强制成 boolean,candidates 缺失时为 []candidateCount0。所以只要漂移发生在 MCP 返回载荷这一侧(最可能的一侧),类型检查全部通过,报告照样印出「累计扫描条目 0 / 预算闸触发 0」。守卫真正能捕获的只有 AgentEvent 联合类型自身改名。
    • 受影响调用方/流程: 报告没有零条目守卫(只有零触发、关臂泄漏两个守卫),零条目会被读数纪律解释成"夹具太小",与真实原因(载荷通路断了)无法区分。
    • 最小可行修复: 把注释改为准确描述守卫覆盖面(只覆盖事件联合类型漂移),并在报告侧补一条守卫:开臂有触发但 sum(filesenseEntries) === 0 时拒绝出报告——这才是对应本条风险的检查点。
  4. [低] 报告未断言每臂的 enabledarm 字段

    • 证据: report-filesense.mjs:36-38 的一致性比较显式剔除了 enabled,而记录里的 arm 字段(run-eval.mjs:276)从未被读取。臂身份完全依赖文件名与"开臂必有触发 / 关臂必须零触发"两个间接守卫。
    • 受影响调用方/流程: 文件被重命名或错拷时,只有当触发行为恰好异常才会被发现;在这个以溯源守卫为卖点的文件里属于可补的缺口。
    • 最小可行修复: 在既有守卫旁加两行——fullAll 全部 filesenseConfigRequested.enabled === true && arm === 'full'offAll 全部 === false && arm === 'no-filesense',否则拒绝出报告。

行级发现

  • [benchmarks/eval/report-filesense.mjs:109] 该句把 SDD/guard/RAG/模型与"filesense 全部预算参数"都算作"已从 JSONL 校验",但 JSONL 只记录 fixture/taskSet/filesenseConfigRequested{enabled,output,writeMode},数值预算按 run-eval.mjs:56-75 是刻意不钉的;拆成"已校验项"与"构造性保证项"两句。
  • [benchmarks/eval/run-eval.mjs:35] "实测同样预算下扫 180 条即截断"对应的是 (depth 2, maxEntries 180),而 trigger-policy.ts:184-190prepare_create 是 depth 1/180,depth 2 的档位是 250/300/500;请按各 intent 真实发出的组合重测,或改写这条理由。
  • [benchmarks/eval/run-eval.mjs:176] 该类型检查无法捕获注释所述的静默归零:context/helpers.ts:56-63 已把 entries/elapsedMs 归一为 0truncated 归一为 falsecandidateCount 归一为 0;请缩小注释声称的覆盖面,并把零条目守卫加到报告侧。
  • [benchmarks/eval/report-filesense.mjs:37] 一致性比较剔除 enabled 后,全程没有任何地方断言开臂 enabled===true、关臂 enabled===false,记录里的 arm 字段也从未被读;补两行断言即可闭合臂身份。

Karpathy 评审

  • 假设: 最关键的隐藏假设是"深夹具会让预算闸关闸"(发现 2)。次要假设是"事件形状守卫能挡住静默降级"(发现 3)。filesenseConfigRequested 是请求值而非生效值这一点,作者已在 run-eval.mjs:281-283 和报告注释里主动标注,处理得当。关臂"不注入"而非"不可用"的口径差异也已显式说明并配了经验守卫。
  • 简洁性: 与问题规模匹配。file_unchanged 是 deep 任务集刻意不预置 files 后新出现的真实需求(对照 tasks.json:443-445 的 flat bugfix 任务确实点名了源文件),不是投机抽象。FILESENSE_PINNED 只钉两个经核实的 no-op、用环境变量断言替代钉死数值预算,是比"全钉死"更克制也更正确的选择。generate.mjs 的清理逻辑按磁盘实际目录枚举而非按列表枚举,避免了删列表项后的孤儿残留,考虑周到。
  • 结构质量: 报告器与 report.mjs 分立而非加叙事开关,是正确的边界选择。collectEventDetail 作为独立函数、harnessFailures 记账而非抛出(因 Agent.emit 会吞掉监听器异常)都合理。未见薄 wrapper、重复 helper 或文件膨胀问题。
  • 变更范围: 严格限定在 benchmarks/eval/ 加一条 README 与配套 workflow 测试;错误的 run.ts 改动已回退并已核实。新增测试与 README 改动一一对应(--arm no-filesense--fixture deepgenerate.mjsreport-filesense.mjspnpm --dir ... install 五条断言均在 README 中存在)。无范围蔓延。
  • 验证: 夹具规模数字可独立复算并全部吻合——24 features × 8 + 12 entities × 3 = 228 生成文件,加 14 个提交源文件 = 242;目录 144+36+2+3+2 = 187,dirs+1 = 188;条目 242+187+1 = 430。三处埋 bug 均可解且与"4 失败 / 11 通过"精确对应(computeTotal 2 失败、mergeLines 1、estimateEta 1;总 15 个用例)。这部分验证强度很高。缺口在发现 2、3 指出的两条未被实测支撑的主张。

缺失覆盖

  • checks.mjsfile_unchanged 无任何测试:fixtureRoot 缺失、原件/副本任一不存在、内容相同/不同四条分支目前只靠人工阅读保证,而它是三条 bugfix 任务能否被"改测试"绕过的唯一屏障。
  • report-filesense.mjs 的四条拒绝出报告守卫(夹具不一致、预算不一致、开臂零触发、关臂泄漏)是这个文件的承重逻辑,全部未被测试覆盖;仓库已有 scripts/tests/ 的 node:test 先例,补一个针对合成 JSONL 的用例成本很低。
  • 若采纳发现 2 的重测建议,建议把"各 intent 真实预算下的扫描条目/是否截断"作为一次性探针结果记进 PR 或 README,使"深夹具的必要性"这条主张与其余数字一样可复算。

console.log(`# FrontAgent 消融评测:filesense 目录导航对一次通过率的影响

> **口径**:两臂唯一的差异是**是否注入 filesense 导航步骤**(\`filesenseEnabled\`)。
> SDD、guard、RAG、模型、任务集、夹具、以及 filesense 的全部预算参数都相同且被显式钉死(已从 JSONL 校验)。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

该句把 SDD/guard/RAG/模型与"filesense 全部预算参数"都算作"已从 JSONL 校验",但 JSONL 只记录 fixture/taskSet/filesenseConfigRequested{enabled,output,writeMode},数值预算按 run-eval.mjs:56-75 是刻意不钉的;拆成"已校验项"与"构造性保证项"两句。

* 而全仓一共 18 条),一次 `ls -R` 就能塞进上下文——按构造测不出按需导航的价值。
* - `deep`:feature-sliced,242 个源文件 / 188 个目录 / 最深 5 层,且 `format.ts`、
* `Button.tsx`、`useToggle.ts` 各有 24 份散落在不同 feature 下。实测同样预算下
* 扫 180 条即截断(全仓 430 条),定位必须靠语义而非文件名。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"实测同样预算下扫 180 条即截断"对应的是 (depth 2, maxEntries 180),而 trigger-policy.ts:184-190prepare_create 是 depth 1/180,depth 2 的档位是 250/300/500;请按各 intent 真实发出的组合重测,或改写这条理由。

['truncated', 'boolean'],
['candidateCount', 'number'],
]) {
if (typeof event[field] !== expected) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

该类型检查无法捕获注释所述的静默归零:context/helpers.ts:56-63 已把 entries/elapsedMs 归一为 0truncated 归一为 falsecandidateCount 归一为 0;请缩小注释声称的覆盖面,并把零条目守卫加到报告侧。

// 同进程同一组常量的回读,不是遥测到的生效值。数值预算不在其中——那些来自
// trigger-policy 的按 intent 分档,harness 在启动时断言相关环境变量未设。
const budgets = [...fullAll, ...offAll].map((r) =>
JSON.stringify({ ...(r.filesenseConfigRequested ?? {}), enabled: undefined }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

一致性比较剔除 enabled 后,全程没有任何地方断言开臂 enabled===true、关臂 enabled===false,记录里的 arm 字段也从未被读;补两行断言即可闭合臂身份。

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