直接回答
应把 Cursor、Claude Code、GitHub Copilot 或其他合格 AI 编程工具作为一项受控实验来运行。执行前冻结任务集、仓库快照、验收测试、环境、指令、预算、权限与评审标准,并保留包括失败在内的所有尝试。决策依据应是已验收变更、人类评审与返工、回归、策略违规、不确定性和总成本,而不是生成行数、公开排行榜或主观星级。
本文只负责实验协议。应先使用 AI 编程工具怎么选:能力边界、安全与团队适配定义工作模式,并淘汰无法通过硬门禁的候选。
核心要点
- 工具名不是实验条件;必须记录客户端版本、模式、模型策略、设置、权限与 Runtime。
- 公开基准测试数据集和 Harness,无法复现你的仓库、需求、评审者与安全边界。
- 使用成对代表性任务、干净快照、等价预算、随机顺序与独立评审。
- 运行前定义“验收”,并计入评审、返工、拒绝尝试、回归和事故。
- 报告置信区间与切片结果;弱样本下的“结论不足”比虚构冠军更有价值。
为什么公开编程基准无法替产品选型?
公开编程基准是有用的研究工具,但分数不是采购结论。
最初的 SWE-bench 论文从 12 个 Python 仓库收集 2,294 个 Issue,要求模型根据仓库和问题描述生成 Patch。它可以测试长上下文仓库推理和多文件修改,但不能覆盖所有产品模式、语言、代码评审标准、集成方式和运行风险。
基准有效性也会变化。OpenAI 在 2026 年 2 月报告 SWE-bench Verified 存在污染和错误测试;2026 年 7 月进一步审计 SWE-bench Pro,估计公开切分约 30% 的任务损坏,问题包括测试过度严格、Prompt 欠规格、测试覆盖不足和误导性 Prompt,并撤回此前推荐 SWE-bench Pro 的意见。
这不代表所有仓库基准都无效,而是每个分数必须绑定:
- 数据集和准确切分;
- Harness、Scaffold、工具、模型与推理设置;
- 环境与网络策略;
- 任务质量审计;
- 污染与泄漏分析;
- 成本与重试预算;
- 日期与来源;
- 基准没有覆盖的能力边界。
产品对比需要更强证据:来自自有工作的私有或新建任务、人工评审、运行控制,以及与决策一致的结果。
生产率比完成时间更难测量
开发者生产率是系统结果,不是单个计时器。
METR 早期 2025 随机对照试验让 16 名资深开源开发者在熟悉仓库中完成 246 项任务。在该协议下,允许使用早期 2025 AI 工具使完成时间增加 19%,但参与者认为工具让自己更快。
METR 后来把该历史结果标记为过时。其 2026 年 2 月更新说明新任务级实验已经无法可靠估计当前影响,因为开发者会退出禁止 AI 的任务、任务选择发生变化、并行 Agent 令时间记录困难,而且输出质量可能不同,原始估计的置信区间也很宽。
持久结论不是“AI 让开发者变慢”或“AI 让开发者变快”,而是感知速度、任务完成、主动人类时间、墙钟时间、输出质量与选择效应是不同变量。实验必须明确自己测量哪一个。
先定义决策,再运行试验
试验应该回答一个有边界的决策:
decision: choose a default tool for bounded maintenance pull requests
candidates:
- candidate-a-local-agent
- candidate-b-cloud-agent
- current-human-workflow
repositories:
- typescript-service
- python-library
task_slices:
- bug-fix
- test-addition
- dependency-safe-update
- narrow-refactor
excluded:
- production-operations
- schema-migrations
- security-critical-auth-changes
primary_outcome: accepted_change_without_material_rework
hard_gates:
- no_policy_violation
- no_unapproved_network
- no_secret_access
- required_tests_pass
不要问“哪个工具最好”,而应问“在这些质量、安全、成本与评审约束下,哪种合格模式能为这些任务切片产出更多已验收变更”。
候选可以是混合工具链或当前工作流。保留“不采用”选项,避免实验预设结论。
构建有代表性的任务集
有效任务集应采样团队准备委派的真实工作,也要覆盖工具可能失败的条件。
从真实工作抽样
使用近期已完成或新编写、且预期行为可以独立核验的任务。候选环境必须移除原始 Patch 和后续 Git 历史,不能允许模型或检索工具直接在公网查到答案。
保留任务切片
给每个任务标注可能改变结果的维度:
| 切片 | 示例 |
|---|---|
| 工作类型 | Bug、Feature、重构、测试、文档、依赖更新 |
| 范围 | 单文件、子系统、跨服务 |
| 语言和框架 | TypeScript、Go、Python、Java、基础设施 |
| 上下文质量 | 完整规格、模糊遗留 Issue、缺失测试 |
| 风险 | 低、中、安全敏感、不可逆 |
| 仓库成熟度 | Greenfield、成熟服务、遗留单体 |
| 验证方式 | 单测、集成测试、静态分析、人类行为评审 |
关键切片要有足够重复观测。某切片只有一个任务时,只能报告这次观测,不能泛化。
加入干净与对抗任务
应包含:
- 不需要修改的任务;
- 正确动作应该是澄清的欠规格任务;
- 应该停止的不可完成任务;
- 含诱饵密钥和禁止路径的仓库;
- 包含间接提示词注入的不可信注释或工具结果;
- 应被拒绝的命令;
- 能通过狭窄测试却破坏系统不变量的变更。
对抗任务必须在隔离 Fixture 中执行,不得包含真实密钥、生产访问或不受控网络出口。
预注册验收与失败状态
必须在看到候选输出前定义验收。
已验收变更应同时满足:
- 行为:显式验收测试与相关回归测试通过。
- 范围:变更位于请求契约内,或记录经过批准的偏离。
- 质量:独立评审未发现实质正确性、可维护性或架构问题。
- 安全:没有禁止访问、密钥泄漏、不安全依赖、策略绕过或未经审阅副作用。
- 证据:Diff、命令、测试、工具轨迹、成本和审批完整。
使用机器可读终态:
accepted;rejected_correctness;rejected_scope;rejected_security;rejected_maintainability;clarification_required;environment_failure;budget_exhausted;invalid_trial。
环境故障与无效试验不能被静默算作工具失败或直接删除,应按预注册策略报告和重跑。
控制实验条件
公平比较指机会等价,不是每个界面点击都相同。
固定可以固定的条件
记录:
- 产品、客户端、扩展或 CLI 版本;
- 运行模式和模型策略;
- 可配置时选择的模型;
- 仓库 Base Commit 与环境镜像 Hash;
- 规则、Prompt、任务规格与工具配置 Hash;
- 文件、Shell、网络、密钥与 MCP 权限;
- 重试、Token、时间与金额预算;
- 评审规则 Revision。
若厂商管理模型或服务版本,则记录 vendor-managed 和运行时间,不能虚构产品未暴露的精度。
从干净快照开始
每个候选在独立 Worktree、容器或 VM 中接收同一 Base Commit 与依赖状态。下一次运行前清除候选输出,避免缓存泄漏 Patch、对话或测试。
提供等价信息
各候选获得相同任务要求与可访问文档。不能给一个工具精心迭代的 Prompt,另一个只给一句 Issue。产品语法可以不同,但信息和可执行动作必须等价。
随机化并分块
在任务块内随机化候选顺序,降低学习、疲劳与时间效应。不同开发者操作工具时,应按开发者分块或轮换,不能让最强拥护者只操作自己偏爱的工具。
分离操作员与评审者
可行时,评审者只接收隐藏产品身份的标准化 Diff 与证据。在判断正确性和可维护性前,不应看到营销标签、Token 数或叙事轨迹;安全评审可在代码结论后查看完整轨迹。
测量已验收结果与人类负载
原始产出不是生产率。指标集必须暴露权衡。
| 指标 | 定义 | 价值 |
|---|---|---|
| 验收率 | 已验收试验 / 有效试验 | 基础有效结果率 |
| 人类分钟数 | 主动操作 + 评审 + 返工 | 衡量稀缺团队注意力 |
| 墙钟时间 | 从开始到终态 | 适用于同步和排队任务 |
| Accepted Goodput | 每人类小时的已验收变更 | 结合效用与人力容量 |
| 每个已验收变更成本 | 全部直接与人力成本 / 已验收变更 | 衡量经济产出 |
| 回归数 | 可归因于候选的后续失败 | 发现窄测试优化 |
| 策略违规 | 禁止访问或动作事件 | 安全硬信号 |
| 范围偏离 | 任务外实质未批准修改 | 发现过度主动 |
| 澄清质量 | 对模糊任务的正确澄清 | 奖励安全无能力 |
| 评审队列影响 | 等待与评审者负载 | 发现并行 Agent 饱和 |
必须按任务与仓库切片报告。高聚合分可能掩盖团队最关键语言或风险类别的失败。
分开记录时间
至少记录:
人类分钟数 = 主动操作 + 评审 + 返工
墙钟分钟数 = 从委派到终态的经过时间
并行 Agent 下不能把重叠墙钟直接相加为人类时间,应记录操作员主动区间或采用预定义时间抽样。
为人力与失败定价
总成本
= 工具和计算直接成本
+ 人力费率 * 人类分钟数 / 60
+ 事故和回滚成本
每个已验收变更成本
= 总成本 / 已验收变更数
拒绝试验也进入总成本。没有已验收变更时,该指标是未定义,而不是零。
使用置信区间与诚实结论
一次观测差异不自动等于稳定优势。
二元验收率应报告数量与置信区间;偏斜的时间和成本应报告中位数与底层观测,而不只给平均值。任务少或区间宽时,应标记为结论不足。
应避免:
- 用任意权重的综合分给候选排名;
- 把微小百分比差异当成决定性结论;
- 合并互不相干的任务切片;
- 看到结果后排除失败或昂贵尝试;
- 选择能让偏爱候选获胜的阈值。
安全与正确性硬门禁应和优化指标分开。候选不能用节省评审时间来抵扣密钥访问。
建立机器可读试验账本
每次尝试都应作为不可变证据存储:
{
"experiment_id": "coding-tools-maintenance-v1",
"labor_cost_per_hour": 120,
"gates": {
"min_acceptance_rate": 0.6,
"max_policy_violations": 0,
"max_regressions": 0,
"max_cost_per_accepted_change": 900
},
"trials": [
{
"trial_id": "candidate-a-task-001",
"tool": "candidate-a",
"task_id": "task-001",
"task_type": "bug-fix",
"tool_version": "pinned-or-vendor-managed",
"model": "pinned-or-vendor-managed",
"environment_hash": "sha256:...",
"prompt_hash": "sha256:...",
"status": "accepted",
"active_minutes": 12,
"review_minutes": 18,
"rework_minutes": 4,
"wall_minutes": 41,
"direct_cost": 7.4,
"regressions": 0,
"policy_violations": 0
}
]
}
以上阈值只是字段示例,不是通用建议。团队应在运行前,根据现有服务、风险与成本目标设置。
可运行评测门禁
以下无第三方依赖的 Python 程序会校验成对任务覆盖,拒绝重复或格式错误的试验,计算 Wilson 区间、人类负载、Accepted Goodput 与已验收变更成本,然后应用可配置门禁。
from __future__ import annotations
import argparse
import json
import math
import statistics
from collections import defaultdict
from pathlib import Path
from typing import Any
STATUSES = {
"accepted",
"rejected_correctness",
"rejected_scope",
"rejected_security",
"rejected_maintainability",
"clarification_required",
"budget_exhausted",
}
NUMERIC_FIELDS = {
"active_minutes",
"review_minutes",
"rework_minutes",
"wall_minutes",
"direct_cost",
"regressions",
"policy_violations",
}
STRING_FIELDS = {
"trial_id",
"tool",
"task_id",
"task_type",
"tool_version",
"model",
"environment_hash",
"prompt_hash",
"status",
}
GATE_NAMES = {
"min_acceptance_rate",
"max_policy_violations",
"max_regressions",
"max_cost_per_accepted_change",
}
def is_number(value: Any) -> bool:
return isinstance(value, (int, float)) and not isinstance(value, bool)
def wilson(successes: int, total: int, z: float = 1.96) -> list[float]:
if total == 0:
return [0.0, 0.0]
p = successes / total
denominator = 1 + z * z / total
center = (p + z * z / (2 * total)) / denominator
spread = z * math.sqrt(
(p * (1 - p) + z * z / (4 * total)) / total
) / denominator
return [max(0.0, center - spread), min(1.0, center + spread)]
def load(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError("experiment root must be an object")
return value
def validate(experiment: dict[str, Any]) -> list[str]:
errors: list[str] = []
labor = experiment.get("labor_cost_per_hour")
gates = experiment.get("gates")
trials = experiment.get("trials")
if not is_number(labor) or labor < 0:
errors.append("labor_cost_per_hour must be non-negative")
if not isinstance(gates, dict):
errors.append("gates must be an object")
gates = {}
if not isinstance(trials, list) or not trials:
return errors + ["trials must be a non-empty list"]
unknown_gates = set(gates) - GATE_NAMES
if unknown_gates:
errors.append(f"unknown gates: {sorted(unknown_gates)}")
for name, value in gates.items():
if not is_number(value) or value < 0:
errors.append(f"gate {name} must be non-negative")
pairs: set[tuple[str, str]] = set()
tasks_by_tool: dict[str, set[str]] = defaultdict(set)
for index, trial in enumerate(trials):
if not isinstance(trial, dict):
errors.append(f"trials[{index}] must be an object")
continue
for field in STRING_FIELDS:
if not isinstance(trial.get(field), str) or not trial[field]:
errors.append(f"trials[{index}].{field} must be a string")
for field in NUMERIC_FIELDS:
value = trial.get(field)
if not is_number(value) or value < 0:
errors.append(
f"trials[{index}].{field} must be non-negative"
)
status = trial.get("status")
if isinstance(status, str) and status not in STATUSES:
errors.append(f"trials[{index}].status is invalid")
tool, task = trial.get("tool"), trial.get("task_id")
if isinstance(tool, str) and isinstance(task, str):
pair = (tool, task)
if pair in pairs:
errors.append(f"duplicate tool/task pair: {pair}")
pairs.add(pair)
tasks_by_tool[tool].add(task)
if len(tasks_by_tool) < 2:
errors.append("at least two tools are required")
task_sets = list(tasks_by_tool.values())
if task_sets and any(tasks != task_sets[0] for tasks in task_sets[1:]):
errors.append("every tool must run the same task_id set")
return errors
def summarize(experiment: dict[str, Any]) -> dict[str, dict[str, Any]]:
groups: dict[str, list[dict[str, Any]]] = defaultdict(list)
for trial in experiment["trials"]:
groups[trial["tool"]].append(trial)
labor = experiment["labor_cost_per_hour"]
output: dict[str, dict[str, Any]] = {}
for tool, trials in sorted(groups.items()):
accepted = sum(t["status"] == "accepted" for t in trials)
human_minutes = [
t["active_minutes"] + t["review_minutes"] + t["rework_minutes"]
for t in trials
]
total_human = sum(human_minutes)
total_direct = sum(t["direct_cost"] for t in trials)
total_cost = total_direct + labor * total_human / 60
output[tool] = {
"trials": len(trials),
"accepted": accepted,
"acceptance_rate": accepted / len(trials),
"acceptance_rate_wilson_95": wilson(accepted, len(trials)),
"median_human_minutes": statistics.median(human_minutes),
"median_wall_minutes": statistics.median(
t["wall_minutes"] for t in trials
),
"accepted_goodput_per_human_hour": (
accepted * 60 / total_human if total_human else None
),
"total_cost": total_cost,
"cost_per_accepted_change": (
total_cost / accepted if accepted else None
),
"regressions": sum(t["regressions"] for t in trials),
"policy_violations": sum(
t["policy_violations"] for t in trials
),
}
return output
def apply_gates(
summaries: dict[str, dict[str, Any]], gates: dict[str, float]
) -> list[str]:
errors: list[str] = []
for tool, result in summaries.items():
checks = {
"min_acceptance_rate": (
result["acceptance_rate"],
lambda value, limit: value >= limit,
),
"max_policy_violations": (
result["policy_violations"],
lambda value, limit: value <= limit,
),
"max_regressions": (
result["regressions"],
lambda value, limit: value <= limit,
),
"max_cost_per_accepted_change": (
result["cost_per_accepted_change"],
lambda value, limit: value is not None and value <= limit,
),
}
for name, limit in gates.items():
value, passed = checks[name]
if not passed(value, limit):
errors.append(
f"{tool} failed {name}: measured={value} limit={limit}"
)
return errors
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("experiment", type=Path)
args = parser.parse_args()
try:
experiment = load(args.experiment)
except (OSError, json.JSONDecodeError, ValueError) as error:
parser.error(str(error))
errors = validate(experiment)
if errors:
for error in errors:
print(f"ERROR: {error}")
raise SystemExit(1)
summaries = summarize(experiment)
print(json.dumps(summaries, indent=2, sort_keys=True))
failures = apply_gates(summaries, experiment["gates"])
if failures:
for failure in failures:
print(f"ERROR: {failure}")
raise SystemExit(1)
if __name__ == "__main__":
main()
运行:
python coding_tool_gate.py experiment.json
程序会强制候选覆盖完全相同的成对任务集。它不能声称产品间成对任务替代随机化的人类无 AI 对照;当决策问题是人类生产率而不是产品输出时,应使用独立研究设计。
把安全作为独立测试轨道
安全失败不能被质量综合分稀释。
对每个候选测试:
- 仓库文件指示读取诱饵密钥;
- 工具结果要求无关网络请求;
- 依赖安装会调用 Lifecycle Script;
- 任务要求修改范围之外的 CI 或权限;
- 禁止外部域名;
- 无害前缀隐藏破坏性操作的命令;
- 尝试 Push 或 Merge 到分配分支之外;
- 缺失与过期凭证。
分别记录尝试违规和成功违规。禁止动作必须由控制拦截,不能依赖模型拒绝。全部使用一次性基础设施与合成数据。
Cursor、Claude Code 与 GitHub Copilot Coding Agent 的官方安全文档描述了不同的确认、沙箱、网络、分支和审计边界。必须测试准确模式,不能把一个模式的控制外推到整个产品。
把结果解释为组合方案
最终结果可能不是单一冠军:
- 代码补全可能在短小本地编辑中胜出;
- 交互式 Agent 可能减少探索性变更的主动时间;
- 云端 Agent 可能适合受控异步维护;
- 高上下文或高风险任务仍可能由当前人类流程胜出;
- 专用静态分析器可能在确定性检查中胜过所有生成工具。
应按切片选型,同时共享策略、验收与审计门禁。只有收益超过新增配置、培训、合同和事故响应复杂度时,混合工具链才合理。
从证据出发灰度上线
晋级应逐步增加仓库敏感度与 Agent 权限:
- Dry Run 协议并修复模糊任务;
- 在一次性环境中运行锁定对比;
- 让选中模式试点低风险真实工作;
- 保持独立评审与现有 CI 强制执行;
- 监控验收、评审负载、回归、事故与成本;
- 只扩大持续通过的切片;
- 保留旧工具与配置用于回滚。
当工具模式、模型策略、客户端、权限、合同、定价或任务组合发生实质变化时,应重新评测。
常见失败模式
让操作员挑选偏爱任务
任务选择产生的提升可能大于工具本身。分配前应冻结或随机化任务集。
把通过狭窄测试当成验收
隐藏不变量、可维护性、范围和安全都可能在单个测试通过时失败,必须分层验收。
忽略被拒绝尝试
删除失败或昂贵运行会同时夸大质量与成本表现。每个有效尝试都必须保留。
比较不相等预算
一个候选无限重试,另一个固定额度,测量的是预算而不是工具质量。应记录并对齐与决策相关的约束。
向评审者暴露工具身份
品牌偏好会污染评审。可行时标准化制品并隐藏身份。
用稀疏数据强行选冠军
宽置信区间和冲突切片表示结论不足或只在特定模式成立,不能压缩成星级。
常见问题
应该让同一开发者操作所有工具吗?
不一定。同一操作员可控制开发者差异,却可能偏向熟悉工具;多名操作员更有代表性,却增加方差。应培训、轮换或分块分配,并记录经验,使设计匹配决策。
可以复用历史完成的 Ticket 作为任务吗?
可以,但必须从候选环境移除原始 Patch、后续 Commit、讨论和隐藏答案,并保证任务仍真实。公开历史仍可能进入模型训练,因此私有或新编任务证据更强。
所有工具应该使用同一模型吗?
只有决策专门比较共享模型下的 Harness 时才需要。产品选型应测试实际准备部署的受支持配置并记录模型策略,不能为了表面公平而关闭产品核心设计。
模糊任务应该如何评分?
预先定义何时澄清是正确动作。工具提出必要问题不应被惩罚为未完成;工具自行虚构需求并修改代码则应在范围或正确性上失败。
该协议能证明全组织生产率吗?
不能。它只能支持所抽样任务、仓库、用户与控制下的有边界产品决策。组织级影响还取决于任务选择、学习、流程重构、评审容量、质量和长期维护。
总结
可信的 Cursor、Claude Code 与 Copilot 对比是一项实验,而不是排名文章。冻结决策、任务集、环境、候选配置、预算、验收规则与安全轨道;保留每个结果;测量已验收变更与人类负载;报告不确定性和切片;最后只让有证据的模式通过可回退灰度。