维护记录

完成的工作

  • 拉取 origin/master 的 18 个新提交(fast-forward),并同步 PaperMod 子模块到 v8.0-139 ✅
  • 创建新文章 content/posts/20260817[05hog4].md(标题「文章的变化」),作者口述风格,半角逗号占位符写法,参考近期 20260807[01jma3].md 的日志类 frontmatter ✅
  • 用 rename-posts.js 由 body 算出 hash 并重命名占位文件为 20260817[05hog4].md ✅
  • 用 gen-covers.js --files ... --force 生成封面 static/images/covers/20260817[05hog4].svg(teal / patternConcentric)✅
  • 手动修正 front matter 中 cover.image 引用(rename-posts 不会同步更新 cover 路径,这是已知 bug)⚠️
  • hugo --minify 构建通过,新页面 public/posts/2026081705hog4/ 正常生成,封面路径正确解析 ✅
  • 更新 content/claudelog/2026-08-17.md ✅

遇到的问题

  • gen-covers.js --files 接受相对路径时会被脚本拼接成 content/posts/content/posts/...,必须传 basename(不带 content/posts/ 前缀) ⚠️ - 已规避
  • rename-posts.js 在重命名占位文件后不会同步更新 front matter 里的 cover.image 路径 ⚠️ - 已手动修正;建议下次让用户或脚本执行后立即检查 front matter 完整性

下次建议

  • 可以考虑给 rename-posts.js 加一个 --sync-cover-fm 开关,自动同步 cover.image 引用,避免遗漏
1raw=$(hashed-slug)
2old="images/covers/<oldSlug>.svg"
3new="images/covers/<newSlug>.svg"
4raw.replace(old, new)

维护记录 (追加 - 命名规则重构 + CI 修复)

完成的工作

命名规则改造

  • 目标:改造 posts 命名规则 YYYYMMDD[hash].md → YYYYMMDD-{slug}-{hash3}.md,让文件名一眼能读懂文章标题 ✅
  • 方案选型:经 ask_user_question 与用户确认方案 B(日期 + slug + 3 位 hash)、slug 从 title 自动生成、中文保留、迁移全部 221 篇历史文章、生成 Hugo aliases 自动 301
  • scripts/rename-posts.ts 改造:
    • 新增 computeHash3 纯函数(SHA-256 前 2 字节 → 16-bit base36)
    • 新增 slugify 纯函数(保留 CJK unicode + 拉丁字母数字,其余替换为连字符,长度上限 80,空串兜底 'untitled')
    • 新增 oldUrlFromSlug 纯函数(支持 YYYYMMDD[hash] 和 YYYY-MM-DD-slug 两种历史格式推导旧 URL)
    • RenamePlan 接口扩展:新增 title、oldUrl、hash3 字段(替代 hash6)
    • buildReport 改用 slugify + computeHash3 命名
    • executePlan 无需改动(已 git mv + 同步 cover)
  • 新增 scripts/migrate-slug-scheme.ts:一次性迁移脚本
    • 默认 dry-run,加 --apply 才落地
    • 调用 buildReport + executePlan,并在 front matter 注入 aliases: ["/posts/{oldSlug}/"]
    • injectAliases 支持合并已有 aliases(数组/字符串两种格式)
    • 碰撞检测 + 失败即停
  • 测试套件更新:
    • rename-posts.test.ts 中已有断言 plans[0]!.newSlug.startsWith('20240115') 验证 date 前缀保留 → 收紧为正则 /^20240115-.+-[a-z0-9]{1,4}$/
    • 新增 13 个测试:computeHash3 ×3 / slugify ×7 / oldUrlFromSlug ×3
    • 测试套件 138 → 151,全部通过 ✅

迁移执行

  • dry-run 报告:221 篇待迁移,0 碰撞
  • 执行:node scripts/dist/migrate-slug-scheme.js --apply --verbose
    • 221 篇 .md + 220 个 .svg 重命名(全部用 git mv 保留历史)
    • 441 个文件改动,git 全部识别为 R(rename),内容相似度 54%-100%
  • front matter 注入 aliases:每篇文章新加 aliases: ["/posts/{oldSlug}/"]
  • Hugo 构建验证:
    • Pages: 264 → 265
    • Aliases: 15 → 224(旧 URL 全部生成了 301 等价页面)
    • 抽样 20260817-文章的变化-jtc/index.html 包含:
      1<link rel=canonical href=https://sdttttt.online/posts/20260817-文章的变化-jtc/>
      2<meta http-equiv=refresh content="0; url=...">
      
  • 本地测试:151/151 通过 ✅

Git 提交(多 commit 策略,经 ask_user_question 确认)

  • 5a417ea chore(scripts): 新增 YYYYMMDD-slug-hash 命名支持 + 一次性迁移脚本
  • 5a7ad18 chore(posts): 迁移 221 篇文章到 YYYYMMDD-slug-hash 命名格式

第一次推送与冲突解决

  • git push origin master 被远程拒绝:CI 的 chore: auto-fix content(4ace5ca)刚把 20260817[05hog4] 改名为 20260817[03wwo2](因 body 改了导致 SHA-256 变了)
  • git pull --rebase 出现 rename/rename 冲突:CI → 03wwo2,我 → 20260817-文章的变化-jtc
  • 解决策略:保留我的迁移结果(jtc),丢弃 CI 中间产物(03wwo2)
    • git rm content/posts/20260817[03wwo2].md + 同名 .svg
    • git add 我的迁移结果
    • git rebase --continue
  • 失误 1(诚实交代):rebase 后 20260817-文章的变化-jtc.md 文件里残留冲突标记
    • 第一次尝试修复时直接 write 文件,差点覆盖掉用户手动润色的版本(末尾换行、空行)
    • 通过 git show 4ace5ca: 提取用户的精确版本,再叠加新 frontmatter 恢复 ✅
    • 用 git commit --fixup=9c99956 + rebase -i --autosquash 把修复折进原 commit,不增加冗余 commit
  • 重新编号后的两个 commit:613cd99(脚本) + 2445eae(迁移)
  • 推送成功 ✅

CI 失败与修复

  • 推送后 CI deploy 失败:
    1ERROR error building site: "/content/posts/20200311-Github-Actions-3fw.md:2:1":
    2EOF looking for end YAML front matter delimiter
    
  • 根因:scripts/gen-covers.ts:injectCoverField line 266 字符串模板 ---\n${newFm}--- 中,闭合符 --- 前缺 \n
    • 当文件 frontmatter 末尾为 aliases: [...]\n(我迁移后所有文章都是这种结构),走 if 分支(fm.replace)重写 cover 块后
    • newFm 末尾是 \n(来自 + '\n'),模板拼接产生 ---\n...\n--- 看似正常
    • 但当文件没有 trailing newline 时,regex [\s\S]*?\n--- 不匹配(因为根本没有第二个 ---),replace 不替换 → 文件保持原样 → 但原文件 frontmatter 没问题… 矛盾!
  • 真正重现:本地用 v0.161.1 + 手动写一个无 trailing newline 的测试文件 + 跑 injectCoverField 老逻辑 → 输出文件 frontmatter aliases: [...]---(无 \n 分隔)
    • Hugo 解析:严格模式下视 --- 不在行首 → EOF 找不到结束符 → 报错
  • 修复:scripts/gen-covers.ts
    1let newFm = fm.replace(/^cover:\s*\n((?:  .+\n?)+)/m, newCover + "\n");
    2// 确保 newFm 以换行结尾,避免与闭合 --- 之间缺少分隔符
    3if (!newFm.endsWith("\n")) newFm += "\n";
    4const newRaw = raw.replace(/^---\n[\s\S]*?\n---/, `---\n${newFm}---`);
    
    • 移除 else 分支多余的 + '\n'(避免双换行)
  • 本地验证:
    • 复现脚本验证 buggy 输出:aliases: ["/posts/2020031100oh3i/"]---(无 \n)
    • 复现脚本验证 fixed 输出:aliases: ["/posts/2020031100oh3i/"]\n---(有 \n)
    • 跑 hugo --minify 在 buggy 文件上 → 与 CI 完全一致的 EOF 错误 → 确认根因
    • 跑 hugo --minify 在 fixed 文件上 → 通过 ✅
    • 测试套件 151/151 通过 ✅
  • 诚实交代 - 失误 2:我误以为本地 v0.161 build OK = fix 没问题
    • 实际上我没有把 bug 真正跑出来 —— 老代码生成的输出与原文件字节序列相同,所以 git diff 显示 0 改动
    • 真正的 bug 只在没有 trailing newline 的输入上才会暴露
    • CI 上的 v0.165 比本地 v0.161 严格,所以 CI 才报错
    • 教训:纯函数层面 fix 一定要构造能触发 bug 的输入 + 独立验证,不能依赖"diff 为空 = OK"

推送修复与最终状态

  • 32e7cd4 fix(gen-covers): 修复 injectCoverField 丢失 frontmatter 末尾换行的 bug
  • CI 重新跑 → 成功 ✅(产生 27fa25b chore: auto-fix content,是部署成功后的文件名微调)
  • 新文章线上 URL:https://sdttttt.online/posts/20260817-文章的变化-1e69/(后缀 hash 因 prettier 规范化略变,deploy.yml 自动处理)

遇到的问题

  • rename/rename 冲突:本地迁移和 CI auto-fix 在同一文件上往不同方向 rename。教训:涉及大量 rename 的操作前应先 pull 同步 CI 状态 ⚠️
  • rebase 后冲突标记残留:git 有时不会自动清理 file content 中的冲突标记,只清理 status。教训:rebase 后必须 hugo --minify 验证 build,不能只看 git status 干净 ⚠️
  • bug 漏检:本地 v0.161 与 CI v0.165 行为不一致(后者更严格),导致 bug 在本地测不出来。教训:涉及 frontmatter / 严格解析的代码应该用与 CI 同版本的工具验证 ⚠️
  • 不区分大小写的本地 + 区分大小写的远程:APFS 默认 case-insensitive,导致本地文件名 Github-Actions 在 Linux CI 上行为可能不同。本次没踩坑,但值得警惕 ⚠️

下次建议

  • 在 CI 流程开始前加一步 git fetch + check clean 的 sanity check,避免 race
  • 给 gen-covers.ts 加专门测试:用 writeFile 写出无 trailing newline 的输入,验证输出 frontmatter 闭合
  • 在 deploy workflow 加 step:hugo --minify 失败时打印前 3 个报错的 .md 文件的 head/tail,便于排查
  • 考虑给 rename-posts.js 加 dry-run-only 模式(默认 dry-run,避免误操作)
  • 新 hash 计算结果应在文件 gitattributes 或 .gitignore 里加 ignore,避免 auto-fix commit 噪声(目前 27fa25b 这种 commit 频率会越来越高)

文件变更清单

  • 新增:scripts/migrate-slug-scheme.ts
  • 修改:scripts/rename-posts.ts(computeHash3 + slugify + oldUrlFromSlug + RenamePlan 字段)
  • 修改:scripts/gen-covers.ts(injectCoverField 末尾换行兜底)
  • 修改:scripts/__tests__/rename-posts.test.ts(+13 测试)
  • 修改:scripts/__tests__/rename-posts-execute.test.ts(RenamePlan 字段适配)
  • 重命名:221 个 content/posts/YYYYMMDD[hash].md → content/posts/YYYYMMDD-{slug}-{hash3}.md
  • 重命名:220 个 static/images/covers/YYYYMMDD[hash].svg → 同名格式
  • 修改:221 个 .md 的 front matter(新增 aliases: 字段)