"应该可以"和"已验证可以"是两种不同的状态。LingCode 可能会以自信的语气宣告代码完成,但代码未必能编译、测试未必能运行、功能未必真的符合预期。解决方案不是更聪明的模型,而是一种需要明确践行的纪律:先有证据,再做断言。
编程 Agent——包括默认设置下的 LingCode——普遍遵循同一个模式:写代码、扫一眼、觉得没问题、宣告完成。这个模式有三种常见的出错方式:
这些都不是模型的失败,而是流程上的失败——在没有运行验证步骤的情况下就声称完成。解决办法是把验证作为说出"完成"、"修好了"、"通过了"这些词的硬性前提条件。
在 LingCode(或你)说出以下任何一句话之前:
同一对话中必须有工具调用的输出,显示相关命令已运行并产生了预期结果。不是"我认为它能编译"——要的是构建输出;不是"测试应该能通过"——要的是测试运行器的通过数量。
这就是本教程的全部内容。接下来讲的是如何真正落实它。
将每一项完成声明对应到能够证明它的命令。如果对话记录中没有这条命令,这个声明就缺乏依据。
| 声明 | 必需的证据 |
|---|---|
| "构建通过了。" | swift build、xcodebuild build、cargo build、npm run build、tsc --noEmit——输出以成功结束,且其上没有任何错误。 |
| "测试通过了。" | 测试运行器的输出,显示非零数量的测试已运行且失败数为零。"0 个测试,0 次失败"不算通过——那是空跑。 |
| "代码检查干净。" | Linter 输出,无警告也无错误。添加抑制注释算作未完成工作的证据,而非完成的证明。 |
| "功能正常工作了。" | 手动复现用户路径——实际点击、实际请求、实际输出——并捕获结果(截图、命令输出,或"我运行了 X,观察到了 Y")。 |
当 LingCode 完成一项任务并说"这应该可以"或"测试应该能通过"时,正确的跟进只需一句话:
Run the build and tests and paste the output before declaring
done.
这一个提示就能把对话结尾的 AI 回复从叙述性语言("我添加了这个函数,更新了调用处,应该能编译")变为可验证的实证(实际的构建输出)。如果输出显示有错误,你就回到循环中,手里有了真正的线索。如果输出显示成功,你就拿到了证据。
最尴尬的失败模式:LingCode 写了一个测试,跑了运行器,运行器以退出码 0 结束——因为没有发现任何测试——然后 LingCode 宣称"测试通过了"。
防御这种情况的方法是检查数量,而不是退出码:
Run the tests. Tell me how many tests executed and how many
passed. If zero tests executed, the test file isn't wired in — fix
that first.
测试运行器发现零个测试的常见原因:
*Test.swift、*.test.ts、test_*.py)。test、it、describe)。以上每一种情况都会产生一条绿色的"所有测试通过"提示,但它什么都不能说明。
构建绿了、测试也绿了,仍然无法证明行为正确。对"功能正常工作"真正诚实的检验,是手动复现用户路径。在 LingCode 中强制执行这一点的三种轻量方式:
lingcode ask --provider claude 'hello',把响应粘贴出来。"curl 新端点,给我看状态码和响应体。"验证步骤的职责就是发现问题。当它发现问题时,正确的应对是:
错误的应对方式,按危害程度递增排列:
// swiftlint:disable、// @ts-ignore、# noqa,让 linter 对症状闭嘴。).skip、删除断言、降低阈值。)每一种做法都把真实信号变成永久的技术债务。如果 LingCode 提议这样做,要明确反对:"不要抑制——解释为什么失败,然后修好它。"
只有验证足够省力,它才会成为习惯。三件事能让它变得轻松:
npm test 或 swift build 都需要审批,LingCode(和你)都会跳过它。tsc --noEmit 或 swift build 的 post-edit hook,意味着验证会自动发生,不依赖任何人记得去问。在 LingCode 提交代码、发起 PR 或打出"完成"这个词之前,在对话中逐项确认:
如果有任何一项缺失,工作就还没完成——它处于"待验证"状态。诚实地说清楚当前状态,是软件开发中最省成本的一种修复。
这个纪律已被封装为一个 skill——把它放入你的 skills 目录,然后让 LingCode 使用"verify before completion",或者直接依赖 LingCode 在检测到"done"/"fixed"/"passing"等声明时自动触发它:
---
name: verification-before-completion
description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs. Triggers: 'this is done', 'I fixed it', 'tests pass', 'it builds clean', 'ready to commit', 'all green', 'ship it'. Actions: require evidence per claim — build output for 'builds', test count + zero failures for 'tests pass', linter output for 'lints clean', manual reproduction for 'feature works'. Anti-patterns: 'should work' without running, 'tests pass' with zero tests run, suppressing/skipping/lowering test thresholds. Discipline: evidence before assertion, always.
---
Verify before claiming done. Required output in the conversation
before saying "done", "fixed", "passing", "ready", or
"works":
1. Build evidence
Run the project's build command (swift build / xcodebuild build /
cargo build / npm run build / tsc --noEmit). Output must end
clean with no errors. Paste the relevant tail.
2. Test evidence
Run the test runner. Confirm the output shows a NON-ZERO number
of tests executed and zero failures. "0 tests, 0 failures" is
empty — diagnose why the runner found nothing.
3. Behavior evidence
For user-facing features, exercise the actual path (CLI command +
output, HTTP call + response, button click + log line). Capture
the observation.
4. No suppression
If a check failed, do not silence it (no // @ts-ignore, no
.skip, no disable comments, no lowered thresholds). Fix the root
cause and re-run.
When the user asks "is it done?" or you'd say "it should work":
stop and run the verifications first. Report what you ran, what
you saw, and only then claim status. If any check is missing, name
the work as "pending verification" — not done.
Cheap when commands are allowlisted in settings.json (see
fewer-permission-prompts skill) and / or wrapped in a post-edit
hook that auto-runs the typecheck.
保存为 ~/.lingcode/skills/verification-before-completion/SKILL.md——关于具体位置以及 skill 如何被发现,请参见安装 skill。