Skip to content

Fix/parser backed prewrite validation - #458

Open
NanluQingshi wants to merge 7 commits into
ceilf6:developfrom
NanluQingshi:fix/parser-backed-prewrite-validation
Open

Fix/parser backed prewrite validation#458
NanluQingshi wants to merge 7 commits into
ceilf6:developfrom
NanluQingshi:fix/parser-backed-prewrite-validation

Conversation

@NanluQingshi

Copy link
Copy Markdown
Contributor

Linked Issue Or Context

Summary

  • Replace quote/bracket heuristics with TypeScript parser diagnostics for TypeScript, JavaScript, JSX/TSX, and JSON writes, eliminating false blocks for apostrophes, multiline templates, JSX text, and regex literals.
  • Add a pure shared line-range patch projector so executor preflight and apply_patch validate the same complete projected content.
  • Validate create_file and arbitrary apply_patch results before disk mutation, bind patch execution to the validated original-content hash, and serialize same-target writes so the validated projection cannot race a concurrent write.
  • Preserve guard enabledChecks, validation-failure events, phase accounting, and desktop reducer behavior with regression coverage.

Impact Scope

  • @frontagent/hallucination-guard: parser-backed synchronous syntax validation and syntax/import facades.
  • @frontagent/shared: reusable deterministic patch projection.
  • @frontagent/mcp-file: create/patch pre-write validation and stale-original hash rejection.
  • @frontagent/core: executor preflight, same-target write ordering, internal argument propagation, and validation-failure events.
  • Desktop state: consumes validation failures without leaving phases active.

GitNexus Impact Summary

  • Risk level: HIGH.
  • Critical skeleton changes: Executor.executeStep, PhaseRunner, and the create_file / apply_patch MCP boundary.
  • GitNexus impact: detect_changes({ scope: "compare", base_ref: "develop" }) reported 43 changed symbols across 25 files, 15 affected execution flows, and HIGH aggregate risk. Executor has 18 direct upstream dependents and affects agent execute, planOnly, executeSteps, and constructor flows. HallucinationGuard is MEDIUM (6 direct dependents, 2 agent flows); PhaseRunner, applyPatch, createFile, checkSyntaxValidity, validateSourceSyntax, and applyFilePatches were LOW individually.
  • Verification: context(Executor.executeStep) confirmed pre-write validation sits before callTool and preserves trace/failure paths. Focused executor, phase, guard, file-tool, shared, and desktop tests passed, followed by the full local gate.

Verification

  • pnpm agent:bootstrap — passed.
  • pnpm quality:predev — passed.
  • Focused tests — 10 files / 171 tests passed.
  • pnpm quality:precommit — passed (lint, full typecheck, full tests, 38 workflow tests).
  • pnpm quality:local — passed (Contract Guard, all precommit checks, and 14/14 package builds).

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.
  • 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.

NanluQingshi and others added 3 commits August 6, 2026 00:27
Move line-range patch projection into a reusable pure helper so executor preflight and the file tool evaluate identical projected content.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace quote and bracket heuristics with TypeScript parser diagnostics for TypeScript, JavaScript, JSX/TSX, and JSON. Expose syntax-only validation for deterministic pre-write callers while preserving enabled-check behavior.

Fixes ceilf6#413

Co-Authored-By: Claude <noreply@anthropic.com>
Preflight create_file and arbitrary apply_patch content with the parser-backed guard, bind patch execution to the validated original hash, and serialize same-target writes so validated projections cannot race.

Complete the general syntax-validation path left intentionally narrow in ceilf6#402 and surface validation failures consistently to desktop consumers.

Co-Authored-By: Claude <noreply@anthropic.com>
@NanluQingshi
NanluQingshi requested a review from ceilf6 as a code owner August 5, 2026 17:50

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ ceilf6/repo-guard

代码评审报告: Fix/parser-backed prewrite validation (#413, #387)

风险等级:
处理建议: 请求修改
决策摘要: 解析器替换启发式这条主线是对的、覆盖也扎实,但 create_filewx 失败路径会 unlinkSync 掉并发写入者刚创建的文件(数据丢失),且 PR 声称的「file-tool 边界写盘前校验」与「同目标写序列化」在代码里并不存在——合并前需要先修 race 回滚路径并把描述/CHANGELOG 与实现对齐。

级联分析

  • 变更符号: Executor.executeStep / 新增 validateWriteBeforeExecutionPhaseRunner.executePhaseSequential|ParallelHallucinationGuard.validateSyntax|validateImports|validateCodecheckSyntaxValidity/新增 validateSourceSyntaxapplyPatchcreateFile、新增 applyFilePatchesdetectLanguageAgentEvent.validation_failed.stage
  • 受影响流程: agent execute/planOnly/executeSteps 的写盘链路;phase 顺序/并行执行与 recovery;apply_patch/create_file MCP 边界;desktop consoleReducer 日志;benchmark 遥测聚合。
  • 变更集外调用方: packages/core/src/executor/progress-enforcement.ts:42-47(已有同款 needsRollback 中止语义,本 PR 使 PhaseRunner 与之对齐);benchmarks/eval/report.mjs:89-90(只渲染 pre_execution/post_write 两个 stage,未加 pre_write);benchmarks/eval/run-eval.mjs:287-289(按 type:stage 通用聚合,无需改动);PatchResult.validation 在全仓无消费方(仅 apply-patch.test.ts:370)。
  • 置信度: medium —(graph 证据来自 PR 内 GitNexus 摘要,未由我复核)我的结论基于仓库只读检查与文本搜索:needsRollbacksyntaxValidvalidateCode/Syntax/ImportscollectedContext.files.set 均已全仓 grep 确认。

问题发现

  1. [高] create_filewx 失败路径会删除并发写入者刚创建的文件

    • 证据: create-file.ts:86flag: 'wx' 写入;EEXIST 落入 create-file.ts:95-98 的 catch,调用 snapshotManager.rollback(snapshotId);该快照在 create-file.ts:69-72 因「检查时文件不存在」被建为 'create' 类型,而 SnapshotManager.rollback'create' 快照执行 unlinkSyncsnapshot.ts:113-123)。
    • 受影响调用方/流程: 并行 phase 中两个 create_file 步骤命中同一路径(phase-runner.ts:198-205 同一波内并发);任何在 existsSync 检查与写入之间创建该文件的外部进程。
    • 最小可行修复: 在 catch 中区分 EEXIST——直接返回既有的 File already exists: ... 错误并跳过 rollback(本次调用没有创建该文件,无权删除它);其余错误保持现有回滚。
  2. [高] file-tool 边界并未做「写盘前校验」,PR 描述与实现不符

    • 证据: apply-patch.ts:68 计算 validation 后,apply-patch.ts:71-76 无条件写盘并在 78-83 返回 success: truecreate-file.tswx 外没有任何语法校验。全仓 grep syntaxValid 显示 PatchResult.validation 无消费方(仅 apply-patch.test.ts:370 断言),新加的 apply-patch.test.ts:346-375 用例本身就把「syntaxValid=false 而 success=true」固化为期望。
    • 受影响调用方/流程: 任何不经 Executor.executeStep 的写入(@frontagent/mcp-file 作为独立 MCP server 被外部 client 调用、hook 路径、未来调用方)仍会把无效内容留在磁盘——正是 #387「no bad state ever exists」要消除的状态。附带成本:mcp-file/package.json:26 为此新增 @frontagent/hallucination-guard 运行时依赖(连带 typescript),每次 patch 额外全量解析一遍,收益为零。
    • 最小可行修复: 二选一——(a) 在 applyPatch/createFile 里真正拦截(syntaxValid === false 时不写盘、返回 success: false,并给非代码内容留显式 opt-out);(b) 撤掉 mcp-file 对 guard 的依赖,把 PR 描述与 CHANGELOG 改为「仅 executor preflight 拦截」。
  3. [中] JSON 走严格 JSON.parse 且已升级为写盘否决权,JSONC 文件(tsconfig.json、.vscode/*.json)会被误拦并连带中止整个 phase

    • 证据: executor.ts:577-578 只跳过 yamljson 进入否决路径;guard.ts:69syntaxValidity 默认 truesyntax-validity.ts:178-181JSON.parse。代码生成 prompt 明确要求产出 tsconfig.jsonllm/code-generation.ts:100),而主流 TS 模板的 tsconfig 带注释/尾逗号。失败后 executor.ts:198needsRollback: true,进而触发 phase-runner.ts:295-300 的整段 phase 跳过。
    • 受影响调用方/流程: 所有生成配置文件的 create_file 步骤;这与 #413 要消除的 false block 属同一类,只是换了语言。
    • 最小可行修复: 对 tsconfig*.json / jsconfig*.json / .vscode/*.json 用容忍注释的解析(如 ts.parseConfigFileTextToJson),或把 JSON 的 block 严格限定在纯 JSON 路径、其余降级为 warn。
  4. [中] 「serialize same-target writes」未实现,描述与代码不一致

    • 证据: packages/core/src/executor 下无任何 lock/queue/串行化实现(已 grep mutex|lock|queue|serialize|inFlight|sameTarget);phase-runner.ts:198-205 仍把同一波 ready 步骤全部并发提交。真正起作用的是 apply-patch.ts:49-56 的 hash 校验,它把竞争变成「后手失败」而非「顺序执行」。
    • 受影响调用方/流程: 并行模式下同文件多补丁步骤——其中一个必然以 changed since executor preflight 失败,且 executor-skills.ts:292-294 明确将其定为不可跳过。
    • 最小可行修复: 修正描述为「hash 绑定拒绝陈旧基线」,或若确实要两个写都成功,则补一个按目标路径的顺序化调度。
  5. [中] PhaseRunner 的 phase 级中止范围超出既定目标,且未在描述/CHANGELOG 说明

    • 证据: phase-runner.ts:220-222226-235(并行)与 295-300(顺序)对任意 needsRollback 失败中止整段 phase。needsRollback 的既有来源包括 executeStep 内任何抛错(executor.ts:246-260)与 required validation 失败(executor.ts:244);例如 executor-skills.ts:242 抛出的 Cannot apply patch: file not found in context 过去只失败该步,现在会把 phase 内无依赖关系的步骤一并标为 skipped(recovery 成功时才可能被重跑,phase-runner.ts:399-423)。方向上与 progress-enforcement.ts:42-47 一致,可以接受,但属于未声明的行为变更,两个新测试只覆盖了刻意构造的写失败。
    • 最小可行修复: 把跳过范围收敛为失败步骤的(传递)依赖方,或在 CHANGELOG 写明 phase-abort 语义并补一个「异常路径同样中止 phase」的测试。
  6. [低] 遥测下游未同步:pre_write 阶段在消融报告里不可见

    • 证据: benchmarks/eval/report.mjs:89-90 只渲染 pre_executionpost_write 两行;语法拦截已整体迁移到 pre_writeexecutor.ts:187),该表将对新的主要拦截来源恒显 0,与 #388「可计数的拦截量」目标冲突(run-eval.mjs:287-289 已通用记录该键,无需改)。
    • 最小可行修复: 在 report.mjs 的分阶段表格加一行 pre_write

行级发现

  • [packages/mcp-file/src/tools/create-file.ts:86] wx 的 EEXIST 分支落到 catch 里的 snapshotManager.rollback(),而该快照是 'create' 类型 → unlinkSync 掉并发写入者的文件;请在 catch 中识别 EEXIST,返回 already-exists 错误且不回滚。
  • [packages/mcp-file/src/tools/apply-patch.ts:74] 上一行算出的 validation 从未参与决策,syntaxValid === false 时仍然写盘并返回 success: true;要么在此之前拦截,要么去掉 mcp-file 对 guard 的运行时依赖并修正 PR 声明。
  • [packages/core/src/executor/executor.ts:578] 只排除 yaml,使 .json 获得写盘否决权,而校验用的是严格 JSON.parse;tsconfig/.vscode 这类 JSONC 会被误拦,请对这些路径改用容忍注释的解析。
  • [packages/core/src/executor/phase-runner.ts:295] needsRollback 已被既有异常路径广泛置真,这里的 break 会跳过 phase 内互不依赖的步骤;建议把跳过限定在失败步骤的依赖闭包,或在 CHANGELOG 明确该语义。
  • [packages/core/src/executor/executor.ts:707] 写后校验只剩 import 检查;当 preflight 返回 undefined(如 create_filecontent 非字符串)时,语法校验将完全缺失,而此前 validateCode 会覆盖该情形。

Karpathy 评审

  • 假设: 隐含假设「executor 是 file tool 的唯一调用方」——mcp-file 是独立 MCP server,该假设未被任何机制保证(发现 2)。另一处隐含假设是 collectedContext 中的内容与磁盘一致,现由 hash 校验兜底,方向正确。
  • 简洁性: applyFilePatches 抽到 @frontagent/shared 是本 PR 最好的部分——删掉了 apply-patch 内约 130 行重复的边界/重叠校验,并让 preflight 与实际写入共用同一投影,属于真正减少复杂度的重组。反面是 mcp-file 里那条「算了但不用」的校验链路(发现 2),是纯增复杂度。
  • 结构质量: guard 的 validateSyntax/validateImports 拆分让 validateCode 变成组合调用,enabledChecks 语义保持一致,是干净的边界改进。syntax-validity.ts 由 178 行手写扫描降为解析器驱动,理解成本明显下降。
  • 变更范围: 超出既定目标的部分有两处:PhaseRunner 的 phase 级中止(发现 5)、detectLanguage 新增 mts/cts(无害且被 preflight 需要)。phase-runner.test.ts:335rresolve 重命名属无关噪声。
  • 验证: 单测覆盖到位(TSX/JSX/正则/撇号/模板、TS-in-JS、stale hash、pre_write 事件、reducer 三阶段)。但没有一条测试覆盖本 PR 新引入的失败路径:EEXIST 回滚、JSONC 拦截、非 dryRun 下 syntaxValid=false 仍写盘。

缺失覆盖

  • createFile 在 existsSync 之后、写入之前被并发创建:断言 EEXIST 后目标文件仍然存在(当前会被删除)。
  • applyPatch 非 dryRun、投影结果语法无效:断言磁盘内容与返回值符合期望的契约(现状是写入 + success: true,与 PR 描述矛盾)。
  • create_filetsconfig.json(含注释):断言不被 pre-write 拦截,或明确记录为预期拦截。
  • executeStep 内部抛错(如 apply_patch 缺上下文)时,phase 内无依赖步骤是否应被 skipped——当前无测试固化该新语义。
  • #387 的验收证据来自冻结的 30 任务 benchmark(benchmarks/results/2026-07-12-sdd-ablation.md 的 TS1127 落盘)。PR 的验证只列了单测与本地 gate;full 臂(guard 开启,run-eval.mjs:94)重跑一次并给出围栏不再落盘的结果,会显著提高对该 issue 已修复的信心。

if (overwrite) {
writeFileSync(safePath.fullPath, content, 'utf-8');
} else {
writeFileSync(safePath.fullPath, content, { encoding: 'utf-8', flag: 'wx' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

wx 的 EEXIST 分支落到 catch 里的 snapshotManager.rollback(),而该快照是 'create' 类型 → unlinkSync 掉并发写入者的文件;请在 catch 中识别 EEXIST,返回 already-exists 错误且不回滚。

writeFileSync(safePath.fullPath, newContent, 'utf-8');
snapshotManager.updateSnapshotContent(snapshotId, newContent);
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
writeFileSync(safePath.fullPath, projected.content, 'utf-8');

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 从未参与决策,syntaxValid === false 时仍然写盘并返回 success: true;要么在此之前拦截,要么去掉 mcp-file 对 guard 的运行时依赖并修正 PR 声明。

const path = typeof toolParams.path === 'string' ? toolParams.path : undefined;
if (!path) return undefined;
const language = detectLanguage(path);
if (!language || language === 'yaml') return 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.

只排除 yaml,使 .json 获得写盘否决权,而校验用的是严格 JSON.parse;tsconfig/.vscode 这类 JSONC 会被误拦,请对这些路径改用容忍注释的解析。

onStepComplete(step, output);
}

if (!output.stepResult.success && output.needsRollback) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

needsRollback 已被既有异常路径广泛置真,这里的 break 会跳过 phase 内互不依赖的步骤;建议把跳过限定在失败步骤的依赖闭包,或在 CHANGELOG 明确该语义。

);
return codeValidation;
if (language === 'typescript' || language === 'javascript') {
return this.config.hallucinationGuard.validateImports(content, path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

写后校验只剩 import 检查;当 preflight 返回 undefined(如 create_filecontent 非字符串)时,语法校验将完全缺失,而此前 validateCode 会覆盖该情形。

NanluQingshi and others added 4 commits August 7, 2026 16:39
Use TypeScript JSON parsing for tsconfig, jsconfig, and VS Code settings while keeping package and application JSON strict.

Co-Authored-By: Claude <noreply@anthropic.com>
Enforce parser validation at create and patch tool boundaries, preserve dry-run diagnostics, and discard speculative snapshots when exclusive creation loses an EEXIST race.

Co-Authored-By: Claude <noreply@anthropic.com>
Defer same-target writes into later parallel waves, keep distinct targets concurrent, and let dependency failures skip only dependents instead of aborting unrelated phase work.

Co-Authored-By: Claude <noreply@anthropic.com>
Render the new pre_write telemetry stage and execute the report against distinct arm counts to keep validation metrics complete.

Co-Authored-By: Claude <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

代码评审报告: Fix/parser-backed prewrite validation (#413, #387)

风险等级:
处理建议: 需要人工判断
决策摘要: 工程实现正确、测试扎实,两个 issue 的验收点基本达成;但 #413 明确留给维护者的打包决策在本 PR 中被顺带做掉了——typescript 成为 guard 的运行时依赖后会被整体打进已发布的 CLI bundle(build.mjs 的 external 未同步),且该决策没有任何体积测量或记录,需要维护者签字后再合。

级联分析

  • 变更符号: validateSourceSyntax / checkSyntaxValidityHallucinationGuard.validateSyntax|validateImports|validateCodeapplyFilePatches(新)、applyPatchcreateFileSnapshotManager.discardSnapshotExecutor.executeStep|validateWriteBeforeExecution|validateAfterExecution|getWriteTargetPhaseRunnerDepsAgentEvent.validation_failed.stage
  • 受影响流程: executor 写盘前拦截 → callTool → 写后 import 校验;phase 并行调度的同目标串行化;desktop 事件日志;benchmark 报表;mcp-file server dispatch;CLI/VSCode 打包图。
  • 变更集外调用方(我实际查证,非图谱):
    • packages/runtime-node/src/mcp-clients.ts:38-48 进程内直传 args,__frontagentExpectedOriginalHash 能到达 applyPatch
    • packages/mcp-file/src/server.ts:90 stripInternalArgs 会剥掉 __frontagent*,走 stdio 的外部 MCP 路径上 stale-base 保护静默失效(非降级失败,是"检查不存在")⚠️
    • PhaseRunner 全仓仅 executor.ts:60 一处构造,新增必填 getWriteTarget 无遗漏调用方 ✅
    • executor-skills.ts:292 的新短路先于 'Cannot apply patch' 命中,stale 错误确实是致命而非静默跳过 ✅(新错误串同时含两个子串,顺序是对的)
    • validateBeforeExecution:406-419 会为 apply_patch 自动读文件入 context,因此 preflight 里 "original content unavailable" 分支基本是兜底 ✅
    • HallucinationGuard 新增两个 public 方法会结构性收紧 ExecutorConfig.hallucinationGuard(本 PR 的 executor 测试 fake 全部要补两个方法即为证据),对 @frontagent/core 外部使用者是轻微契约扩宽。
  • 置信度: medium(无代码图谱输出可复核,结论来自 diff + 仓库文本查证;PR 自述的 GitNexus 数据未独立验证)

问题发现

  1. [高] typescript 变成运行时依赖后被整体打进已发布的 CLI 产物

    • 证据: packages/hallucination-guard/package.json:24typescript 移到 dependenciespackages/hallucination-guard/src/checks/syntax-validity.ts:3 是全仓唯一 import ts from 'typescript'build.mjs:15external 仍只有 ['playwright','ts-morph']dist/index.mjspackage.json:files 中发布的唯一 CLI 产物。ts-morph 此前是 external,所以 TS 编译器此前不在 CLI bundle 里。
    • 受影响调用方/流程: pnpm build:verify → npm 发布的 fa CLI;apps/vscode/build.mjs:11 同样不 external,vsix 会多带一份 TS。
    • 影响: 已发布产物体积量级变化(TS 完整编译器约 8–10MB 未压缩)+ sourcemap,且这正是 #413 写明"是维护者的决定"的那一项,PR 描述里没有任何体积数据或取舍说明。
    • 最小可行修复: 二选一并写进 PR 说明——(a) build.mjs/apps/vscode/build.mjstypescript 加入 external,并在根 dependencies 声明;(b) 明确接受内联,附上 dist/index.mjs 变更前后的体积数字。
  2. [中] enabledChecks.syntaxValidity: false 不再能端到端关闭语法否决

    • 证据: packages/mcp-file/src/tools/create-file.ts:65-68apply-patch.ts:58-68 无条件调用 validateFileSyntax;guard 侧的开关(guard.ts:187)只影响 executor preflight。
    • 受影响调用方/流程: 任何以 enabledChecks.syntaxValidity=false 运行的部署,写盘仍会被文件工具拦截;PR 描述"Preserve guard enabledChecks"与实际端到端行为不符。
    • 影响: 解析器一旦对某个合法文件误报,使用者没有任何逃生口(#413 正是因为"检查不可靠时不该有 veto"才被立项)。
    • 最小可行修复: 要么把文件工具的校验做成可关闭参数(默认开),要么在 CHANGELOG/README 明确"文件工具层的语法校验不受 guard 配置控制"。
  3. [中] JSONC 白名单过窄,.json 写入的新误报会直接否决写盘

    • 证据: syntax-validity.ts:212-219 仅放行 tsconfig*/jsconfig*.vscode/*.json,其余 .json 走严格 JSON.parse
    • 受影响调用方/流程: agent 写 .eslintrc.json.devcontainer/devcontainer.jsonturbo.json 等带注释/尾逗号的配置时,现在是 block 而不是"写下去后报失败";空内容 .json 同样被拒。
    • 影响: 拦截误报的代价从"误导性失败"升级为"写不进去",属于 #413 关心的方向。
    • 最小可行修复: 把白名单扩到这几类公认 JSONC 文件并补测试,或对非 manifest 类 .json 的解析失败降级为 warn。
  4. [中] preflight 的管线失败被伪装成 syntax_validity block,污染 pre_write 拦截指标

    • 证据: executor.ts:613-628 把 "patches must be an array" / "original content unavailable" 包成 type: 'syntax_validity', severity: 'block',随后由 emitValidationFailed('pre_write', ...)executor.ts:194)发射;benchmarks/eval/report.mjs:90 新增行会把它们计进"写盘前内容拦截"。
    • 受影响调用方/流程: #388 建立的"拦截量可计数"语义——emitValidationFailed 上方的注释正是说工具/管线失败不能算拦截。
    • 最小可行修复: 给这类失败一个独立 type(如 preflight_unavailable),或在这条路径上不发射 validation_failed
  5. [中] stale-hash 由缓存 context 单点决定,且没有自愈路径

    • 证据: executor.ts:649-654collectedContext.files 里的内容算哈希;validateBeforeExecution:406 只在缺失时才自动读盘;executor-skills.ts:292 把 stale 错误标为致命不可跳过。
    • 受影响调用方/流程: 若同一 run 中 run_command 或外部进程改了该文件,apply_patch 会硬失败,而重试仍复用同一份陈旧缓存 → 重复失败直到 recovery 次数耗尽。
    • 影响: 相比旧行为(静默按行号打到错内容上)更安全,但把可恢复情形变成了不可恢复。
    • 最小可行修复: 收到 stale 错误时清除 collectedContext.files 中该条目(或 apply_patch preflight 前强制重读),让重试有机会成功。
  6. [低] 新增包边界上的松类型与重复映射

    • 证据: packages/mcp-file/src/syntax-validation.ts:11-19 通过 'errors' in result.details + as Array<{...}> 把 guard 结果强转,而 guard 已导出 SyntaxErrorDetail;同文件 43-49 行又复制了一份扩展名→语言映射,与 phase-ordering.ts:144-164 并行存在(本 PR 为了 .mts/.cts 同时改两处,正是漂移成本的实证)。
    • 最小可行修复: guard 侧导出返回 SyntaxErrorDetail[] 的类型化入口;把 detectLanguage 提到 @frontagent/shared 供两侧复用。

行级发现

  • [packages/hallucination-guard/package.json:24] typescript 进 dependencies 后会被 build.mjs 内联进发布的 dist/index.mjs(external 只有 playwright/ts-morph);要么把 typescript 加入 external 并在根 dependencies 声明,要么在 PR 中记录产物体积变化。
  • [packages/mcp-file/src/tools/create-file.ts:65] 这里的语法校验对所有调用方无条件生效,enabledChecks.syntaxValidity: false 无法关闭;补一个可选开关或明确文档化该层不可配置。
  • [packages/hallucination-guard/src/checks/syntax-validity.ts:179] JSONC 放行仅限 tsconfig/jsconfig/.vscode,.eslintrc.jsondevcontainer.jsonturbo.json 等带注释配置会被直接否决写盘;扩展白名单并补测试。
  • [packages/core/src/executor/executor.ts:624] "original content unavailable" 属于管线失败而非语法拦截,却用 syntax_validity/block 上报,会被计进 pre_write 拦截指标;换独立 type 或此路径不发射事件。
  • [packages/core/src/executor/executor.ts:651] 哈希取自缓存 context,命中 stale 后错误被标为致命且缓存不刷新,重试必然复现;失败时清除该文件的 context 条目。
  • [packages/mcp-file/src/syntax-validation.ts:13] result.details.errors as Array<{...}> 在新包边界上绕过类型;改为 guard 侧导出返回 SyntaxErrorDetail[] 的入口。
  • [packages/mcp-file/src/syntax-validation.ts:43] 与 phase-ordering.ts:detectLanguage 重复的扩展名映射,本 PR 已被迫同步两处;下沉到 @frontagent/shared 单点维护。

Karpathy 评审

  • 假设: 隐含假设"executor 与文件工具永远同进程"——runtime-node 成立,但 stdio server 路径上 stripInternalArgs 会让 stale-base 保护静默消失,这一点没有在代码或文档中言明。另一隐含假设是 collectedContext 始终等于磁盘现状(见问题 5)。
  • 简洁性: 主体是净简化——applyPatch 从 275 行降到约 110 行,投影逻辑收敛到 @frontagent/shared 单一实现,executor 与工具共用同一投影;启发式括号/引号计数被解析器取代,删除的是真复杂度而非搬家。保留的 checkOuterMarkdownFence 有正当理由(```ts…``` 会被 TS 当作合法 tagged template 解析通过),是必要例外而非残留。
  • 结构质量: 新增 syntax-validation.ts 属于薄适配层,可接受,但带一处强转和一份重复的扩展名映射(见问题 6)。validateCode 现已无生产调用方,仅测试引用——作为公共 API 保留合理,但值得在文档里标注新入口。
  • 变更范围: 基本贴合目标。phase-runner 的同目标写串行化与 create_filewx 独占写严格说超出 #413 的范围,但它们是"写盘前投影"成立的前提条件,属于必要配套;benchmarks/eval/report.mjs + 新 workflow 测试是可观测性配套,可接受。
  • 验证: 覆盖到位——#413 表格里四个用例(it's、多行模板、JSX 撇号、正则)都有断言,.ts 中 JSX 与 .js 中类型注解的反向用例也有;file tool 层验证了"拒写不留快照不落盘";applyFilePatches 有独立单测。缺口见下。

缺失覆盖

  • 没有端到端用例证明 enabledChecks.syntaxValidity: false 时写盘仍被文件工具拦截(当前测试只覆盖 guard facade 本身),而 PR 描述声称保留了该契约。
  • 没有对发布产物体积的断言或记录,typescript 内联进 dist/index.mjs 属于不可见回归。
  • stdio MCP server 路径(stripInternalArgs 剥掉哈希)下的 stale-base 行为无测试,保护缺失是静默的。
  • .vue / .svelte / .html / .css 写入仍完全不校验(detectLanguage 返回 null),#387 描述的"围栏内容落盘"在这些扩展名上依旧可复现;建议加一条固化该有意缺口的测试或在 CHANGELOG 标注范围。
  • .json 空内容与常见 JSONC 配置(.eslintrc.jsondevcontainer.json)被拒写的行为没有测试固化,属于问题 3 的验证盲区。

"@frontagent/shared": "workspace:*",
"@frontagent/sdd": "workspace:*"
"@frontagent/sdd": "workspace:*",
"typescript": "^6.0.3"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

typescript 进 dependencies 后会被 build.mjs 内联进发布的 dist/index.mjs(external 只有 playwright/ts-morph);要么把 typescript 加入 external 并在根 dependencies 声明,要么在 PR 中记录产物体积变化。

return { success: false, error: 'File content must be a string' };
}

const syntaxValidation = validateFileSyntax(content, filePath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这里的语法校验对所有调用方无条件生效,enabledChecks.syntaxValidity: false 无法关闭;补一个可选开关或明确文档化该层不可配置。

*/
function checkJsonSyntax(code: string): SyntaxError[] {
function checkJsonSyntax(code: string, filePath?: string): SyntaxErrorDetail[] {
if (filePath && isKnownJsoncConfigPath(filePath)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSONC 放行仅限 tsconfig/jsconfig/.vscode,.eslintrc.jsondevcontainer.jsonturbo.json 等带注释配置会被直接否决写盘;扩展白名单并补测试。

return {
path,
content: '',
validation: this.buildWriteValidationFailure(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"original content unavailable" 属于管线失败而非语法拦截,却用 syntax_validity/block 上报,会被计进 pre_write 拦截指标;换独立 type 或此路径不发射事件。

),
toolParams: {
...toolParams,
__frontagentExpectedOriginalHash: createHash('sha256')

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,命中 stale 后错误被标为致命且缓存不刷新,重试必然复现;失败时清除该文件的 context 条目。

const result = validateSourceSyntax({ code: content, language, filePath });
const errors =
result.details && typeof result.details === 'object' && 'errors' in result.details
? (result.details.errors as Array<{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

result.details.errors as Array<{...}> 在新包边界上绕过类型;改为 guard 侧导出返回 SyntaxErrorDetail[] 的入口。

return `Syntax validation failed for ${filePath}:${firstError.line}:${firstError.column}: ${firstError.message}`;
}

function detectSyntaxLanguage(filePath: string): 'typescript' | 'javascript' | 'json' | 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.

phase-ordering.ts:detectLanguage 重复的扩展名映射,本 PR 已被迫同步两处;下沉到 @frontagent/shared 单点维护。

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.

[Bug] syntax_validity flags legitimate code as block — apostrophes, template literals, JSX text

1 participant