← 全部极客知识库

主流 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。