主流 Agent 入门教程 / Claude Code
16. 实战二批量升级旅行笔记
原文备份 · 第 18 / 28 篇 · 查看极客时间原知识库 ↗
本篇目录 · 18 节
来源:Claude Code
预计用时:70~90 分钟
本篇目标:在 12 篇固定个人旅行笔记上完成“只读盘点 → 规则审计 → dry-run → 两篇小批量 → 全量 → 幂等 → 报告与回滚”闭环,并用字节级证据证明正文、异常项和既有新格式没有被误改。
单文件修复的风险通常集中在一处;批量迁移的风险会乘以文件数量。最危险的结果不是脚本报错,而是它安静地猜测缺失字段、误把正文当元数据,或第二次运行继续改写已经完成的文件。
本篇不让 Claude 临场生成 10~20 篇随机笔记,也不把重点放在发明 YAML 解析器。课程提供固定笔记、窄范围迁移工具、工具自测、分批清单、状态检查器和重置脚本。你要练习的是如何让 Claude 审计并操作一条可恢复的迁移流程。
开始状态
本练习独立于 MealCraft 和第 15 篇博客仓库。需要:
- Claude Code、Git 和 Python 3.11+ 可用;
~/claude-code-course/travel-notes-lab尚不存在;- 知道修订版教程目录的实际位置;
- 理解这是模拟个人笔记,不能把“中国”默认值直接套到真实旅行资料。
练习不安装第三方 YAML 包,不读取你的真实笔记目录,也不需要网络。
一、创建固定练习仓库
macOS、Linux 或 WSL2:
export COURSE_SOURCE="/替换为实际路径/claude-code-tutorial-v0.2"
export TRAVEL_SOURCE="$COURSE_SOURCE/exercise-assets/travel-notes-lab"
test -f "$TRAVEL_SOURCE/scripts/verify_travel_lab.py"
cd ~/claude-code-course
test ! -e travel-notes-lab
mkdir travel-notes-lab
cp -R "$TRAVEL_SOURCE/." travel-notes-lab/
cd travel-notes-lab
git init
Windows PowerShell:
$CourseSource = "C:\替换为实际路径\claude-code-tutorial-v0.2"
$TravelSource = "$CourseSource\exercise-assets\travel-notes-lab"
if (-not (Test-Path "$TravelSource\scripts\verify_travel_lab.py")) { throw "旅行笔记素材路径错误" }
Set-Location "$HOME\claude-code-course"
if (Test-Path travel-notes-lab) { throw "travel-notes-lab 已存在,请先检查旧目录" }
New-Item -ItemType Directory travel-notes-lab | Out-Null
Copy-Item "$TravelSource\*" -Destination travel-notes-lab -Recurse -Force
Copy-Item "$TravelSource\.gitignore" -Destination travel-notes-lab -Force
Copy-Item "$TravelSource\.gitattributes" -Destination travel-notes-lab -Force
Set-Location travel-notes-lab
git init
先检查固定输入和工具本身:
macOS、Linux 或 WSL2:
python3 -B scripts/verify_travel_lab.py --expect-original
python3 -B scripts/test_migration_tool.py -v
python3 -B scripts/inventory_notes.py --root notes
Windows PowerShell:
py -B scripts\verify_travel_lab.py --expect-original
py -B scripts\test_migration_tool.py -v
py -B scripts\inventory_notes.py --root notes
关键输出应为:
PASS travel lab state=original: files=12 bodies=unchanged protected=unchanged
Ran 8 tests ... OK
SUMMARY total=12 old=6 new=2 blocked=4
12 篇笔记覆盖以下情况:
- 6 篇可迁移旧格式,其中包含中文、英文、空格文件名、额外 frontmatter 字段和
2025/08/14这种必须原样保留的日期; - 2 篇已经是新格式,其中京都笔记的
country: Japan绝不能被“中国”覆盖; - 4 篇阻塞项:缺日期、空城市、无 frontmatter、缺城市;
- 成都笔记正文中有一行
city:,只能保留,不能当作元数据处理。
工具自测会在系统临时目录复制 baseline,验证 dry-run 不写入、batch 只改两篇、正文字节不变、保护项不变、完整统计、二次 apply 幂等和路径穿越拒绝。它不会修改当前 notes/。
保存练习基线:
git add .
git diff --cached --check
git commit -m "create deterministic travel notes migration lab"
git status --short
预期工作区为空。.gitattributes 固定 Markdown、Python 和清单为 LF,避免跨平台换行转换破坏字节级验收;.gitignore 只忽略 reports/、.snapshots/ 和 Python 缓存。
二、理解“退出 0”不等于“全部迁移”
本篇工具遵守 MIGRATION_SPEC.md 的窄契约。旧格式:
---
city: 杭州
date: 2025-04-03
---
会变成:
---
location:
city: 杭州
country: 中国
travel_date: 2025-04-03
---
工具只解析文件开头由 --- 包围的 frontmatter,只转换顶层 city 与 date。日期值、其他元数据行及结束分隔线后的正文按原字节保留。缺字段不会猜;混合新旧格式也会阻塞。
阻塞项是正常业务结果,所以命令扫描成功时仍退出 0。脚本调用者必须同时检查 JSON 报告中的 blocked 和具体原因,不能只看退出码。
工具已由课程固定哈希保护。本篇不修改 scripts/migrate_notes.py、测试、baseline 或分批清单;Claude 负责审计和执行,而不是边迁移边改工具。真实项目如果规则不同,应先在另一份工具副本和数据副本中重新测试。
三、让 Claude 做只读盘点和规则审计
从练习根目录以 Plan Mode 启动:
claude --permission-mode plan
发送:
这是 12 篇模拟个人旅行笔记的格式迁移练习。先只读调查,不要修改。
请:
1. 阅读 MIGRATION_SPEC.md、manifests/batch-1.txt、scripts/migrate_notes.py、工具自测和状态检查器;
2. 运行原始态检查、8 项工具自测和只读 inventory;
3. 按 old/new/blocked 列出 12 篇文件、城市、日期和原因;
4. 解释工具怎样限定 frontmatter、保护正文、保持日期、拒绝路径穿越并默认 dry-run;
5. 指出退出 0、blocked=0、正文未变和幂等分别需要什么证据;
6. 给出 dry-run、batch-1、全量、第二次 apply、报告和 Git 回滚的顺序。
不要修改 notes、baseline、scripts、manifests 或任何配置。
盘点必须得到 old=6, new=2, blocked=4。如果 Claude 把正文中的 city: 算成元数据、把京都归为可迁移旧格式,或建议为缺失字段自动补值,要求它回到检查器的实际输出。
四、把计划写入磁盘并单独提交
确认口头计划后切回 Manual 模式,让 Claude 只创建 MIGRATION_PLAN.md:
只创建 MIGRATION_PLAN.md,不改其他文件。
计划必须记录:
- 固定范围是 notes 下 12 篇模拟笔记;
- 原始统计 old=6、new=2、blocked=4;
- 中国默认值仅适用于本练习六篇已知国内旧笔记;
- dry-run 默认不写文件;
- batch-1 只含清单中的两篇;
- 异常项跳过,不猜城市或日期;
- 正文、文件名、额外元数据和日期值保持;
- 每阶段的命令、预期统计、独立检查与 Git 回滚方法;
- 第二次 apply 的完整文件哈希必须不变。
不要实施迁移。
退出 Claude Code,审查并提交计划:
git diff --check
git diff --name-only
git add MIGRATION_PLAN.md
git commit -m "document travel notes migration plan"
git status --short
Diff 应只有 MIGRATION_PLAN.md,提交后工作区为空。把计划与数据迁移分开提交,后面回滚数据时可以保留决策记录。
五、先运行全量 dry-run
dry-run 默认不写笔记,因此没有 --apply:
macOS、Linux 或 WSL2:
python3 -B scripts/migrate_notes.py \
--root notes \
--report reports/dry-run.json
Windows PowerShell:
py -B scripts\migrate_notes.py `
--root notes `
--report reports\dry-run.json
预期:
PASS mode=dry-run total=12 would_migrate=6 migrated=0 already_new=2 blocked=4
REPORT reports/dry-run.json
查看完整报告:
macOS、Linux 或 WSL2:
python3 -m json.tool reports/dry-run.json
python3 -B scripts/verify_travel_lab.py --expect-original
git status --short
Windows PowerShell:
py -m json.tool reports\dry-run.json
py -B scripts\verify_travel_lab.py --expect-original
git status --short
状态检查仍必须是 original,Git 仍为空;报告文件被忽略。逐项确认 6 个 would_migrate、2 个 already_new 和 4 个 blocked 的文件名与理由,不要因为总数正确就跳过清单。
六、只执行两篇 batch-1
manifests/batch-1.txt 固定包含杭州和带空格文件名的苏州笔记。先显示清单:
git status --short
macOS、Linux 或 WSL2:
python3 -B scripts/migrate_notes.py \
--root notes \
--include-from manifests/batch-1.txt \
--apply \
--report reports/batch-1.json
python3 -B scripts/verify_travel_lab.py --expect-batch1
Windows PowerShell:
py -B scripts\migrate_notes.py `
--root notes `
--include-from manifests\batch-1.txt `
--apply `
--report reports\batch-1.json
py -B scripts\verify_travel_lab.py --expect-batch1
预期:
PASS mode=apply total=2 would_migrate=0 migrated=2 already_new=0 blocked=0
PASS travel lab state=batch1: files=12 bodies=unchanged protected=unchanged
检查实际范围。core.quotepath=false 只改善本次中文文件名显示,不修改仓库配置:
git -c core.quotepath=false diff --name-only
git diff --check
git diff -- notes
必须只出现 notes/01-杭州春日.md 与 notes/02-苏州 夜游.md。Diff 应仅改变 frontmatter;检查器已经逐篇比较结束分隔线后的正文字节。
保存小批量检查点:
git add notes
git commit -m "migrate travel notes batch 1"
git status --short
七、全量执行剩余可迁移项
再次运行全量 dry-run。此时应显示 4 篇仍可迁移、4 篇已经是新格式、4 篇阻塞:
python3 -B scripts/migrate_notes.py --root notes --report reports/after-batch-dry-run.json
Windows PowerShell 把 python3 换成 py,路径也可使用正斜杠;确认输出为 would_migrate=4 already_new=4 blocked=4。
确认报告后才执行:
macOS、Linux 或 WSL2:
python3 -B scripts/migrate_notes.py \
--root notes \
--apply \
--report reports/full.json
python3 -B scripts/verify_travel_lab.py --expect-complete
Windows PowerShell:
py -B scripts\migrate_notes.py `
--root notes `
--apply `
--report reports\full.json
py -B scripts\verify_travel_lab.py --expect-complete
预期:
PASS mode=apply total=12 would_migrate=0 migrated=4 already_new=4 blocked=4
PASS travel lab state=complete: files=12 bodies=unchanged protected=unchanged
为什么不是 migrated=6?杭州和苏州已经在 batch-1 中迁移,加上两篇原本的新格式,现在有 4 篇 already_new;全量阶段只需修改剩余 4 篇。
检查 Diff,必须只剩西安、成都、上海、厦门四篇:
git -c core.quotepath=false diff --name-only
git diff --check
git diff -- notes
确认成都正文里的 city: 这只是我给照片写的标签 仍在,厦门的 travel_date 仍为 2025/08/14,京都仍为 country: Japan。然后提交:
git add notes
git commit -m "complete travel notes metadata migration"
git status --short
八、用完整文件哈希证明幂等
“dry-run 没有可迁移项”很有用,但二次 apply 的完整文件哈希不变是更强的证据。
macOS、Linux 或 WSL2:
python3 -B scripts/hash_notes.py \
--root notes \
--output .snapshots/before-second-apply.json
python3 -B scripts/migrate_notes.py \
--root notes \
--apply \
--report reports/second-apply.json
python3 -B scripts/verify_travel_lab.py \
--expect-idempotent \
--snapshot .snapshots/before-second-apply.json
Windows PowerShell:
py -B scripts\hash_notes.py `
--root notes `
--output .snapshots\before-second-apply.json
py -B scripts\migrate_notes.py `
--root notes `
--apply `
--report reports\second-apply.json
py -B scripts\verify_travel_lab.py `
--expect-idempotent `
--snapshot .snapshots\before-second-apply.json
预期:
PASS snapshot: 12 notes -> ...before-second-apply.json
PASS mode=apply total=12 would_migrate=0 migrated=0 already_new=8 blocked=4
PASS travel lab state=idempotent: files=12 bodies=unchanged protected=unchanged
再运行 git status --short,应为空。幂等不是“脚本没有报错”,而是第二次运行迁移数为 0 且 12 个完整文件哈希都没有变化。
九、生成有证据的迁移报告
重新进入 Claude Code,让它根据四份 JSON 报告和 Git 证据只创建 MIGRATION_REPORT.md:
只创建 MIGRATION_REPORT.md,不修改 notes、scripts、baseline、manifests 或配置。
报告必须使用以下标题和证据文字:
## 统计
- migrated: 6
- already_new: 2
- blocked: 4
## 阻塞项
逐项记录:
- 09-青岛待补日期.md — missing_or_empty_date
- 10-空城市.md — missing_or_empty_city
- 11-无头部笔记.md — no_or_malformed_frontmatter
- 12-只有日期.md — missing_or_empty_city
## 验证
明确写出:12 notes、bodies unchanged、second apply migrated=0,并列出对应命令。
## 回滚
说明先查看 git log 与 diff,再对 batch 和 full 两个迁移提交按倒序使用 git revert;不要建议 reset --hard。还要说明真实笔记必须先备份,不能沿用本练习的 country 默认值。
不要声称四个阻塞项已经迁移。
退出后验证并提交:
macOS、Linux 或 WSL2:
python3 -B scripts/verify_migration_report.py --report MIGRATION_REPORT.md
git diff --check
git add MIGRATION_REPORT.md
git commit -m "record travel notes migration evidence"
git status --short
Windows 把 python3 换成 py。预期报告检查通过,最终工作区为空。
十、回滚边界
如果 batch-1 尚未提交且验收失败,先查看 git diff -- notes,确认只有练习笔记后,可以用课程重置脚本恢复全部 12 篇原始模拟数据:
python3 -B scripts/reset_travel_lab.py
python3 -B scripts/verify_travel_lab.py --expect-original
它会替换当前练习仓库的 notes/,所以不要把这个脚本复制到真实笔记唯一副本中运行。
已经提交后,先定位提交:
以下为参数模板,执行前先将尖括号占位符替换为实际值:
git log --oneline -5
git show --stat <迁移提交ID>
需要保留可审计历史时,对报告中记录的迁移提交按倒序执行:
以下为参数模板,执行前先将尖括号占位符替换为实际值:
git revert <全量迁移提交ID>
git revert <batch-1提交ID>
提交 ID 必须来自你自己的 git log,不能照抄占位符。git revert 创建反向提交;不要用 git reset --hard 清掉不相关工作。Claude Code 的 Checkpoint 也不等同于备份:本篇写入来自外部 Python 进程,Git 提交和独立副本才是可恢复边界。
常见阻塞
原始统计不是 6 / 2 / 4
运行 git status --short 和原始态检查。如果只是 notes/ 被上次练习改过,确认不含要保留的工作后运行重置脚本。不要修改检查器去迎合错误数据。
dry-run 后 Git 出现笔记修改
立即停止。默认命令不含 --apply,应完全不写 notes/。检查实际运行的是课程固定工具和正确目录;固定哈希失败时重新复制素材。
命令退出 0,但 blocked=4
这是预期。退出 0 表示扫描与报告成功,不代表每个文件都可迁移。四个异常项必须保留供人工补充事实;不要在提示词里让 Claude“合理猜测”。
中文文件名在 Git 中显示为转义序列
使用:
git -c core.quotepath=false status --short
git -c core.quotepath=false diff --name-only
这只影响本次显示,不改变文件名或全局 Git 配置。
真实笔记规则比练习复杂
停止复用这个工具。真实 YAML 可能包含引号、多行字符串、数组、注释、多个国家和不同编码。先复制数据、建立真实 schema 与 country 映射,再用成熟解析器和新的测试重做工具;课程脚本只保证当前固定子集。
文章级验收
- 初始状态固定为 12 篇,old=6、new=2、blocked=4;
- 8 项工具自测通过,固定素材哈希未变;
MIGRATION_PLAN.md在任何数据写入前单独提交;- dry-run 报告 6 个 would_migrate,且原始态仍通过;
- batch-1 只修改杭州和苏州两篇;
- 全量阶段只修改剩余四篇可迁移笔记;
- 2 篇原有新格式和 4 篇阻塞项字节不变;
- 12 篇正文逐字节不变,日期值未格式化;
- 第二次 apply 为 migrated=0,完整文件哈希不变;
- 迁移报告记录统计、阻塞项、证据和非破坏性回滚;
- 最终 Git 工作区为空,真实个人笔记从未进入练习范围。
这套顺序比“一次性全改完再看 Diff”慢几分钟,却把最危险的猜测和静默损坏变成可观察证据。第 17 篇会继续迁移这套方法:面对跨平台偶发测试,不把重复运行当成证明,而是让互斥假设分别寻找日志和代码证据。
官方参考:Claude Code common workflows、Claude Code commands、Git revert。