跳转到内容
编码 Agent 实操:用说明文件、权限、测试与审阅改真实仓库 · AIScope
你将做出什么
编码 Agent 能读仓库、改多个文件、运行命令,比在聊天框里贴代码快得多,也更容易闯祸:改了不该改的文件、为了让测试通过去改测试、把密钥提交进仓库、说“测试全部通过”其实没跑。本课不教某一款工具的按钮在哪,而是教一套换任何工具都适用的做法。
完成后,你会在自己的一个小仓库里走完一次完整流程:
写好仓库说明文件,告诉 Agent 怎么构建、怎么测试、哪些目录归谁、哪些事不许做;
把“修一个 bug 并补测试”拆成可验证的小步,要求 Agent 先跑测试复现,再修复;
按风险分级给权限:只读探索、写文件、执行命令、联网和推送分别处理;
用检查清单和一个小脚本审阅多文件 diff,找出范围蔓延、删改测试、关闭校验和疑似密钥;
不满意就用 git 撤回,满意再由你自己提交。
交付物是:一份仓库说明文件、一段任务提示词、修复前后的测试输出、一份 diff 审阅记录,以及最终提交(或回滚)的 git 记录。
证据等级:本站没有用任何具体的编码 Agent 产品实跑这套流程。文中两段 Python 代码只用标准库,在本站电脑上实际运行过,输出就是运行结果;示例 diff 是教学虚构,用来练习审阅;git 命令在本站电脑上的临时仓库里实际执行过(git 2.49)。各工具界面与权限选项以官方文档为准,本站 2026-10 未逐一实测。
准备什么
一个你自己的小仓库,最好已经有几条能运行的测试。没有的话,第二步提供一个可以直接用的练习仓库内容。不要拿工作中的重要仓库做第一次练习。
Python 3.12 和 git。确认 git status 能运行,工作区是干净的(没有未提交的改动),这样 Agent 的每一处改动都能被看见、被撤回。
任意一款能读写仓库的编码 Agent,终端型、编辑器插件型都可以。下文只用通用概念描述它的能力;具体叫什么、在哪里设置,以你所用工具的官方文档为准。
先完成开源项目实操 或路线 L2 的 AI 编程单元会更顺手:你需要能看懂测试输出和 diff。
第一步:写仓库说明文件
Agent 每次开始工作时对你的仓库一无所知。仓库说明文件就是给它的“入职说明”:放在仓库根目录,用普通 Markdown 写。常见的文件名是 AGENTS.md,有的工具使用自己的文件名,或支持在子目录再放一份覆盖规则;你的工具读取哪个文件名,以官方文档为准。
写什么:Agent 自己猜不到、猜错代价又大的东西。具体是四类:怎么构建和测试、目录分别负责什么、做事的流程、绝对不能做的事。下面是一份可以直接复制、再按你的仓库修改的模板:
# 仓库说明(给编码 Agent 和新成员)
## 项目是什么
一个把学习记录分页展示的小工具。只用 Python 标准库,Python 3.12。
## 构建与测试
- 运行全部测试:`python -m unittest -v`(在仓库根目录执行)
- 只跑一个测试文件:`python -m unittest -v tests.test_pager`
- 改完任何代码,都要运行全部测试,并把原始输出贴在回复里。没跑过就写“未运行”,不能写“应该能通过”。
## 目录职责
- `src/`:业务代码。修 bug 主要改这里。
- `tests/`:测试。可以新增测试;已有测试的断言不得修改或删除,确实有错时先停下来说明原因,由人决定。
- `docs/`:说明文档。代码行为变化时同步更新。
- 其他文件(配置、CI、依赖清单、.gitignore):本仓库默认不改;需要改时先说明理由并等待确认。
## 做事流程
1. 先读相关代码和测试,用一两句话复述问题。
2. 先写一个能复现问题的失败测试,运行并贴出失败输出。
3. 做最小修复,不顺手重构、不改无关格式。
4. 运行全部测试,贴出输出。
5. 列出改了哪些文件、每处为什么改。
## 禁止事项
- 不提交、不推送、不切换或删除分支,git 写操作由人完成。
- 不把密钥、令牌、密码写进任何文件;需要配置时读环境变量,示例值写 `<YOUR_KEY>`。
- 不新增或修改 `.env`、证书、私钥文件。
- 不为了让测试通过而跳过测试、放宽断言、关闭 lint 或类型检查。
- 不安装新依赖,不访问网络;确实需要时先说明理由。
- 不执行删除大量文件、改写历史的命令(例如批量删除、强制推送、硬重置)。
## 遇到不确定时
停下来提问,说明你看到的证据和两三个可选方案,而不是自己猜一个继续做。
写的时候注意三点:
命令要能直接复制运行。 “运行测试”不够,要写出完整命令和在哪个目录执行。先自己在终端里跑一遍,确认能用。
禁止事项要具体到动作。 “注意安全”没有用;“不把密钥写进任何文件”“不修改已有测试的断言”才能被检查。
说明文件不是权限控制。 它是给 Agent 的约定,Agent 可能理解错或遗漏。真正拦住危险操作的是第四步的权限设置和第五步的人工审阅。
第二步:准备练习仓库,先复现 bug
下面这段代码模拟一个最小仓库:pager.py 里的 paginate 函数有两个 bug(页码从 1 开始却按 0 计算起点;总页数没有向上取整),test_pager.py 有四条测试。它依次运行三种情况:修复前、“反面示范”(不修代码只改断言)、修复后。为了一个文件就能快速运行,示例把两个文件的内容放在字符串里,在同一个进程中加载并运行 unittest,效果相当于在仓库根目录执行 。保存为 demo_repo.py,运行 :
python -m unittest -v
python demo_repo.py
import io
import sys
import textwrap
import types
import unittest
BUGGY = '''
def paginate(items, page, per_page):
"""返回第 page 页(从 1 开始)的条目和总页数。"""
if page < 1 or per_page < 1:
raise ValueError("page 和 per_page 必须从 1 开始")
total_pages = len(items) // per_page
start = page * per_page
return items[start:start + per_page], total_pages
'''
TESTS = '''
import unittest
from pager import paginate
class PaginateTest(unittest.TestCase):
def test_first_page(self):
self.assertEqual(paginate(list(range(10)), 1, 4), ([0, 1, 2, 3], 3))
def test_last_partial_page(self):
self.assertEqual(paginate(list(range(10)), 3, 4), ([8, 9], 3))
def test_empty_list_has_no_pages(self):
self.assertEqual(paginate([], 1, 4), ([], 0))
def test_rejects_page_zero(self):
with self.assertRaises(ValueError):
paginate([1, 2], 0, 4)
'''
# 反面示范:不修代码,把期望值改成错误代码的输出,测试也能“通过”
CHEATED = TESTS.replace("([0, 1, 2, 3], 3)", "([4, 5, 6, 7], 2)").replace("([8, 9], 3)", "([], 2)")
FIXED = BUGGY.replace("len(items) // per_page", "(len(items) + per_page - 1) // per_page").replace(
"start = page * per_page", "start = (page - 1) * per_page")
def load(name, source):
"""把源码字符串加载成模块,相当于仓库里的一个 .py 文件。"""
module = types.ModuleType(name)
exec(textwrap.dedent(source), module.__dict__)
sys.modules[name] = module
return module
def run_tests(code, tests, label):
"""相当于在仓库根目录运行 python -m unittest -v。"""
load("pager", code)
suite = unittest.defaultTestLoader.loadTestsFromModule(load("test_pager", tests))
stream = io.StringIO()
result = unittest.TextTestRunner(stream=stream, verbosity=2).run(suite)
lines = [line for line in stream.getvalue().splitlines() if line.startswith(("test_", "FAILED", "OK", "Ran"))]
print(f"--- {label}({'全部通过' if result.wasSuccessful() else '有失败'})")
print("\n".join(lines))
run_tests(BUGGY, TESTS, "修复前:先复现")
run_tests(BUGGY, CHEATED, "反面示范:改了断言,代码没修")
run_tests(FIXED, TESTS, "修复后:全量测试")
本站实际运行的输出(“Ran 4 tests in …s”里的耗时每次略有差异):
--- 修复前:先复现(有失败)
test_empty_list_has_no_pages (test_pager.PaginateTest.test_empty_list_has_no_pages) ... ok
test_first_page (test_pager.PaginateTest.test_first_page) ... FAIL
test_last_partial_page (test_pager.PaginateTest.test_last_partial_page) ... FAIL
test_rejects_page_zero (test_pager.PaginateTest.test_rejects_page_zero) ... ok
Ran 4 tests in 0.000s
FAILED (failures=2)
--- 反面示范:改了断言,代码没修(全部通过)
test_empty_list_has_no_pages (test_pager.PaginateTest.test_empty_list_has_no_pages) ... ok
test_first_page (test_pager.PaginateTest.test_first_page) ... ok
test_last_partial_page (test_pager.PaginateTest.test_last_partial_page) ... ok
test_rejects_page_zero (test_pager.PaginateTest.test_rejects_page_zero) ... ok
Ran 4 tests in 0.000s
OK
--- 修复后:全量测试(全部通过)
test_empty_list_has_no_pages (test_pager.PaginateTest.test_empty_list_has_no_pages) ... ok
test_first_page (test_pager.PaginateTest.test_first_page) ... ok
test_last_partial_page (test_pager.PaginateTest.test_last_partial_page) ... ok
test_rejects_page_zero (test_pager.PaginateTest.test_rejects_page_zero) ... ok
Ran 4 tests in 0.000s
OK
三段输出说明了本课最重要的一件事:“测试通过”本身不能证明 bug 修好了。 反面示范里代码一行没改,只是把两条断言的期望值改成了错误代码的输出,测试就全部通过。所以审阅时要看 diff 改了什么,而不只看最后那行 OK。
在你自己的仓库里,可以把 BUGGY 和 TESTS 两段字符串的内容分别保存为 src/pager.py 和 tests/test_pager.py(tests 目录里再放一个空的 __init__.py,并把测试里的导入改成 from src.pager import paginate,src 目录同样放一个空的 __init__.py),作为给 Agent 练手的 bug;也可以用你仓库里一个真实的小 bug。
第三步:把任务拆成可验证的小步 给 Agent 的任务要满足两个条件:每一步都有可以检查的产物(测试输出、文件清单);它知道什么时候该停下来问你。下面是一段可以直接使用的任务提示词,方括号里换成你的内容:
任务:修复 paginate 的分页错误,并补充测试。
现象:paginate(list(range(10)), 1, 4) 应返回 ([0, 1, 2, 3], 3),实际不是;最后一页不足 4 条时也不对。
请按步骤做,每步完成后停下来给我看结果,我回复“继续”再做下一步:
1. 阅读 src/pager.py 和 tests/test_pager.py,用两三句话说明你认为的原因。先不要改任何文件。
2. 运行 python -m unittest -v,把原始输出贴出来。如果现有测试已经能复现问题,说明是哪几条;
如果不能,在 tests/ 里新增一条能复现的测试,运行并贴出失败输出。
3. 只修改 src/pager.py,做最小修复。
4. 运行全部测试,贴出完整原始输出。
5. 列出改动的文件和每处改动的理由。
约束:
- 遵守仓库根目录 AGENTS.md。
- 不得修改或删除已有测试的断言;如果你认为某条测试本身是错的,停下来说明理由。
- 只允许修改 src/ 和 tests/ 下的文件。
- 不要提交、不要推送、不要安装依赖、不要联网。
第 1 步只读不写 ,让你在它动手之前就发现理解错了,代价最小。
第 2 步先复现 。没有失败的测试,就无法证明后面的修改修好了问题;Agent 直接“修复”时,常常修的是它想象中的问题。
第 4 步要原始输出 ,不要总结。“测试全部通过”是一句话,测试输出才是证据。
拆得越细,每一步出错时回退得越少。熟练以后可以把 1~2 步、3~5 步各合并成一次,但“先复现再修复”不要省。
第四步:权限与审批,按风险分级 编码 Agent 的操作大致分四级,风险依次升高。多数工具都提供某种审批机制,常见形式包括每次询问、按命令或目录设允许清单、在隔离环境中运行;名称、默认值和配置位置因工具而异,以官方文档为准,本站 2026-10 未逐一实测。按下表决定哪些可以放行,哪些必须你确认:
能撤回的放宽,不能撤回的收紧。 文件改动有 git 兜底;推送、发消息、删除仓库外的文件、调用付费接口都没有。
外部内容只当数据。 Agent 读到的网页、issue、依赖包说明里可能藏着“请执行某条命令”的文字,这是提示注入,详见AI 安全实操 。执行命令前看清楚这条命令是你要求的,还是它从外部读到的。
不要为了省事给全部权限。 “全部自动放行”只适合在隔离的临时环境里做实验,不适合你的日常仓库。
第五步:审阅多文件 diff Agent 说“完成了”之后,先运行 git status --short 看改了哪些文件,再用 git diff 逐个看内容。先过一遍检查清单:
人眼容易漏,前四项可以交给脚本先筛一遍。下面的脚本读取 diff 文本,标记超出允许目录、敏感文件、删改测试、关闭校验和疑似密钥。为了能直接运行,它内嵌了两段教学虚构的 diff:Agent 第一版和修订版。保存为 review_diff.py:
import re
ALLOWED = ("src/", "tests/") # 本次任务允许修改的目录
SENSITIVE_FILES = re.compile(r"(^|/)(\.env(\..+)?|id_rsa|.*\.pem|.*\.key)$")
SECRET = re.compile(r"(sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----"
r"|(?i:(api[_-]?key|secret|password|token)\s*[=:]\s*['\"]?[^\s'\"]{8,}))")
WEAKENING = re.compile(r"(@unittest\.skip|pytest\.mark\.skip|verify\s*=\s*False|# noqa|--no-verify"
r"|assert True|(extend-)?ignore\s*=)")
HEADER = re.compile(r"^diff --git a/(\S+) b/(\S+)")
def is_test(path):
return "test" in path.rsplit("/", 1)[-1]
def review(diff_text):
findings, path = [], None
for line in diff_text.splitlines():
header = HEADER.match(line)
if header:
path = header.group(2)
if not path.startswith(ALLOWED):
findings.append(f"超出范围:{path}")
if SENSITIVE_FILES.search(path):
findings.append(f"敏感文件:{path}")
continue
if line.startswith("deleted file") and is_test(path):
findings.append(f"删除测试文件:{path}")
if line.startswith(("--- ", "+++ ")):
continue
if line.startswith("-") and is_test(path) and re.search(r"def test_|assert", line):
findings.append(f"删改测试:{path}:{line[1:].strip()[:50].rstrip()}")
if line.startswith("+"):
if SECRET.search(line):
findings.append(f"疑似密钥:{path}(内容不打印,避免二次泄露)")
if WEAKENING.search(line):
findings.append(f"关闭校验:{path}:{line[1:].strip()[:50].rstrip()}")
return findings
# 教学虚构:一次“测试全部通过”的 Agent 改动
AGENT_DIFF = """\
diff --git a/src/pager.py b/src/pager.py
--- a/src/pager.py
+++ b/src/pager.py
@@ -4,5 +4,5 @@ def paginate(items, page, per_page):
- total_pages = len(items) // per_page
+ total_pages = (len(items) + per_page - 1) // per_page
diff --git a/tests/test_pager.py b/tests/test_pager.py
--- a/tests/test_pager.py
+++ b/tests/test_pager.py
@@ -9,9 +9,10 @@ class PaginateTest(unittest.TestCase):
def test_last_partial_page(self):
- self.assertEqual(paginate(list(range(10)), 3, 4), ([8, 9], 3))
+ self.assertEqual(paginate(list(range(10)), 3, 4), ([], 3))
- def test_rejects_page_zero(self):
- with self.assertRaises(ValueError):
- paginate([1, 2], 0, 4)
+ @unittest.skip("暂时跳过")
+ def test_first_page(self):
diff --git a/.env b/.env
new file mode 100644
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+MODEL_API_KEY=sk-FAKE0000example0000KEY
diff --git a/setup.cfg b/setup.cfg
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,3 @@
[flake8]
+extend-ignore = E,F
"""
# 修订后:只改源码,并新增一条测试
REVISED_DIFF = """\
diff --git a/src/pager.py b/src/pager.py
--- a/src/pager.py
+++ b/src/pager.py
@@ -4,5 +4,5 @@ def paginate(items, page, per_page):
- total_pages = len(items) // per_page
- start = page * per_page
+ total_pages = (len(items) + per_page - 1) // per_page
+ start = (page - 1) * per_page
diff --git a/tests/test_pager.py b/tests/test_pager.py
--- a/tests/test_pager.py
+++ b/tests/test_pager.py
@@ -20,0 +21,3 @@ class PaginateTest(unittest.TestCase):
+ def test_exact_multiple(self):
+ self.assertEqual(paginate(list(range(8)), 2, 4), ([4, 5, 6, 7], 2))
"""
for name, diff in (("Agent 第一版", AGENT_DIFF), ("修订版", REVISED_DIFF)):
findings = review(diff)
print(f"== {name}:{len(findings)} 个问题")
for item in findings:
print(" -", item)
== Agent 第一版:9 个问题
- 删改测试:tests/test_pager.py:self.assertEqual(paginate(list(range(10)), 3, 4),
- 删改测试:tests/test_pager.py:def test_rejects_page_zero(self):
- 删改测试:tests/test_pager.py:with self.assertRaises(ValueError):
- 关闭校验:tests/test_pager.py:@unittest.skip("暂时跳过")
- 超出范围:.env
- 敏感文件:.env
- 疑似密钥:.env(内容不打印,避免二次泄露)
- 超出范围:setup.cfg
- 关闭校验:setup.cfg:extend-ignore = E,F
== 修订版:0 个问题
第一版 diff 在 Agent 的回复里可能被描述为“修复完成,测试全部通过”,而脚本找出了 9 个问题:一条断言被改成迁就错误结果,一条测试被删除,另一条被加上跳过标记,新增了含密钥的 .env,还在 setup.cfg 里关掉了 lint。修订版只改了源码、新增一条测试,0 个问题。
脚本的局限要清楚:它只按文本规则筛选,漏报和误报都会有。例如它看不出“改了源码但逻辑是错的”,也看不出以不常见格式写的密钥。它是人工审阅前的第一道筛子,不能代替你逐处阅读。
接入自己的仓库时,把最后的循环换成读取真实 diff,例如在脚本开头加上 import subprocess,再用 review(subprocess.run(["git", "diff", "HEAD"], capture_output=True, text=True, encoding="utf-8").stdout)(会包含已暂存和未暂存的改动;新建且未跟踪的文件要先 git add -N 文件名 才会出现在 diff 里),并把 ALLOWED 改成你这次任务允许的目录。想让它在发现问题时阻止后续步骤,就在发现问题时以退出码 1 结束。
第六步:用 git 撤回或保存 撤回之前,先确认自己在哪个分支、有哪些改动。建议每次让 Agent 动手前,自己先建一个分支,这样主分支始终干净:
git switch -c agent/fix-pager
下面是本站在临时仓库中模拟 Agent 改动后实际执行的命令和输出:Agent 改了源码,也改了测试和 setup.cfg,还生成了一个 .env(已被 .gitignore 忽略)。
$ git status --short
M setup.cfg
M src/pager.py
M tests/test_pager.py
$ git diff --stat
setup.cfg | 1 +
src/pager.py | 2 +-
tests/test_pager.py | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
$ git restore setup.cfg tests/test_pager.py
$ git status --short
M src/pager.py
$ git status --short --ignored
M src/pager.py
!! .env
git restore 文件名 把指定文件恢复到上次提交的样子,只留下想要的源码修改。注意 .env 这类被忽略的文件不会出现在普通的 git status 里,要加 --ignored 才能看到;看到后确认内容,不需要就删除。
常用的撤回方式,按“还没提交”和“已经提交”分开:
本站临时仓库里对一次提交执行 revert 的实际输出(提交号和日期每次不同,这里用 <hash> 代替提交号):
$ git revert --no-edit HEAD
[agent/fix-pager <hash>] Revert "修复分页总页数"
Date: Mon Oct 12 22:08:12 2026 +0800
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline
<hash> Revert "修复分页总页数"
<hash> 修复分页总页数
<hash> 初始版本
git reset --hard、git clean -fd、强制推送都会不可恢复地丢掉内容,不要让 Agent 执行,自己用之前也先用 git status 和 git stash 留好退路。
审阅通过后,由你自己提交:git add src/pager.py tests/test_pager.py(写明文件,不用 git add .),再 git commit,提交说明里写清修了什么、怎么验证的。
失败与修订
失败一:Agent 为了让测试通过,改了测试断言 现象:Agent 报告“全部测试通过”,diff 里 test_pager.py 的期望值从 ([8, 9], 3) 变成了别的值,或者某条测试被删掉、被加上跳过标记。第二步的反面示范就是这种情况:代码没修,测试照样 OK。
git restore tests/test_pager.py 撤回测试改动,重新运行测试,确认又能看到失败。
在 AGENTS.md 和任务提示词里同时写明:已有测试的断言不得修改或删除,确有错误时停下来说明。
让 Agent 重做第 3 步,要求“只修改 src/pager.py”。
用 review_diff.py 复查,“删改测试”“关闭校验”两类问题应为 0。
失败二:改动超出范围 现象:只让修一个函数,diff 里却出现了格式化过的十几个文件、改过的 setup.cfg 或 CI 配置、升级过的依赖。即使每一处“看起来都没错”,审阅成本也成倍增加,回滚时还会误伤。
git status --short 列出所有改动文件,对范围外的文件逐个 git restore 文件名。
任务提示词写明允许的目录,并把 review_diff.py 的 ALLOWED 设成同样的目录。
确实需要改配置时,单独开一个任务、单独审阅、单独提交。
失败三:提交了 .env 或把密钥写进代码 现象:diff 里新增了 .env,或者源码里出现 API_KEY = "sk-..." 这样的字符串。
还没提交:删除 .env 或从代码中去掉密钥,改为读环境变量,在 .gitignore 里加上 .env。
已经提交:git rm --cached .env 停止跟踪,在 .gitignore 加上 .env 后提交。但这不会 把它从历史中删除,本站临时仓库里 git log --oneline --all -- .env 仍能看到那次误提交。所以最重要的一步是立刻到服务提供方作废这个密钥并换新的 ;已经推送到远程时,按平台的说明处理,不要以为删掉文件就安全了。
AGENTS.md 写明禁止事项,review_diff.py 每次都跑“疑似密钥”和“敏感文件”检查。
失败四:Agent 说“测试全部通过”,其实没有运行 现象:回复里只有一句“已运行测试,全部通过”,没有原始输出;你自己一跑,有失败。
修订:要求贴出完整原始输出;审阅时自己在终端重新运行一次测试命令,以你看到的结果为准。可以在 AGENTS.md 里写“没跑过就写未运行”。
保存和使用结果 在你的仓库里保留 AGENTS.md,把 review_diff.py 放进一个工具目录。每次让 Agent 改代码,都把这四样东西存进任务记录:任务提示词、修复前的失败测试输出、修复后的全部测试输出、review_diff.py 的结果和你的人工审阅结论。以后回头看,就能分清哪些改动经过了验证,哪些只是“看起来可以”。
AGENTS.md 是活文档:每当 Agent 犯了一个新错误,想一想能不能用一条具体的规则避免,把它加进去。
自测与答案
Agent 修改后测试全部通过,能不能直接提交?不能,先看 diff,确认没有删改测试、没有范围外改动和密钥,再自己运行一次测试。
为什么要求 Agent 先写失败测试再修复?没有失败的测试,就无法证明修改修好了问题,也无法防止以后复发。
运行测试、安装依赖、git push,哪个最需要人工确认?git push,结果离开你的电脑,无法简单撤回;安装依赖其次。
已经提交并推送的 .env,用 git rm --cached 处理后就安全了吗?不安全,历史里仍然有,必须作废并更换密钥。
仓库说明文件写得很详细,还需要权限设置和人工审阅吗?需要,说明文件只是约定,Agent 可能违反或误解。
加练:在 review_diff.py 里加一条规则,标记测试文件中“被注释掉的断言”(新增行去掉开头的加号后,以 # self.assert 或 # assert 开头)。先写一段会触发它的示例 diff,确认能被标记,再确认修订版 diff 仍是 0 个问题。
来源与内容说明 AIScope 中文手册 · AIScope 编辑
本篇为本站原创学习内容,引用资料保留参考来源。
收录:2026年10月19日
待核验
适用范围:尚未标注,请结合原文与实际环境核对 人工核验:尚无核验记录
查看已发布版本历史