知识库到 Hexo 自动发布详细设计

说明:本文件是《知识库到自动发布系统详细设计》的对外分享副本,已做脱敏处理。
以下内容已替换为占位符:本地绝对路径(<知识库目录> / <博客源码目录>)、
代码托管组织与账号(<组织> / <GitHub 账号>)、站点域名、构建主机名。
文中出现的文章标题、目录名与统计数字均取自作者本人的知识库,仅用于说明设计取舍。
需要可执行版本请使用原始文档。

设计结论(v3):采用四仓库、单向数据流架构。

  • OneNote(cnb.cool,私有)——内容与发布策略的唯一权威;
  • cnb-hexo(https://cnb.cool/<组织>/hexo-butterfly-next,私有)——发布源码主仓与控制面,承载同步器、.cnb.yml 与分类对账;
  • github-hexo(github.com/<GitHub 账号>/hexo-butterfly-next,私有)——cnb-hexo 的单向 Git 镜像,GitHub Actions 的唯一构建输入;
  • github-pages(<用户名>.github.io,公开)——只保存已验证的静态产物并承担 Pages 发布。

推荐采用消费者拉取模型:cnb-hexo 通过定时或手动任务读取知识库规则与最新内容;无有效差异时终止,只有生成新的发布源码快照后才继续镜像、构建和部署。

**发布范围(本版关键约束):只有通用 frontmatter(8 字段)**的文档才是发布候选;wiki frontmatter(10 字段)的文档一律不发布。因此 0X0F_知识沙盒/ 下除 原始资料/ 外的 5 个 wiki 目录(实体信息 125、概念专题 66、计划方案 5、对比分析 3、查询存档 1)全部排除。


0. 修订记录

0.1 V4 变更(实现对齐,本次)

实施阶段(2026-10)依据实测数据与运行验证,对 V3 设计做了如下修正:

项 V3 设计 V4 实现 依据
[[双链]] 未命中 降级为纯文本 保留原文(可配 text) shell 的 [[ $a -lt 100 ]] 与双链语法相同,降级会破坏代码
本地图片落盘 source/assets/images/ source/assets/images/_sync/ 父目录同时放主题头像,按目录全量清理曾误删 Avatar_*.jpg
abbrlink 来源 继承已有值 + 自行生成 只由 hexo-abbrlink 在构建期产生,同步器只回读 避免维护两套 crc32 算法
存量 83 篇短链 原样继承 不继承,全部按新文档处理 实测 80 篇可比对文章中 46 篇 URL 变化,已确认接受
提交粒度 单次原子提交 两笔提交(正文 / abbrlink 回填) 构建失败时正文仍可上线,仓库保持自洽
分类对账 已定义 已实现;abbrlink 改按源路径继承 分类迁移后短链不得变化

详细实现见「附录 B.6 实施后实测」。

0.2 V3 变更(实现前的设计基线)

# 变更 说明
1 恢复四仓库架构 重新引入 cnb-hexo(https://cnb.cool/<组织>/hexo-butterfly-next)作为发布源码主仓与控制面;github-hexo 降级为单向镜像与唯一构建入口
2 恢复 “ 构建输入镜像态 “ 四个状态、镜像触发、镜像凭证、镜像失败隔离与回滚路径全部恢复
3 关闭 v2 待决策项 1 同步器运行位置确定为 CNB 侧 cnb-hexo。CNB 对 cnb.cool 知识库有原生访问,不需要再验证 GitHub Runner → cnb.cool 的连通性
4 路由规则格式调整 pathDefaults / fileOverrides 由 “ 路径 → 分类 “ 改为**” 分类 → 路径列表 “**,同一分类的多条路径集中书写
5 新增镜像语义与本地开发迁移的待决项 见 18.12、18.13
6 目录结构重构 A140_杂七杂八 拆为 A140_开发工具 + A150_博客搭建;A135_MySQL数据库/A136_Git/A137_Docker 由 A130_软件开发 上移并重编号为 A141/A142/A143;A138_开源项目 收回为 A135_开源项目
7 0X0A 侧目录级对齐 5 个分支 = 5 个博客分类,pathDefaults 简化为每分类一条;fileOverrides 仅保留 原始资料/网络文章 一处混合目录
8 ddc 全量校准 104 篇 ddc 前缀与所在目录编号 100% 一致;补齐 6 处空值;确立 ddc = <目录编号>.<序号> 约定(见 5.2)

0.3 V2 变更(相对 V1 的现状修正,全部保留)

# v1 的写法 现状事实 v2/v3 的处理
1 include: "docs/**/*.md" 知识库没有 docs/ 目录 改为真实目录:0X0A_知识管理/**、0X0F_知识沙盒/原始资料/**、0X0D_三味书屋/**
2 文章级 blog-category 字段 全库 0 处使用;SCHEMA 两套 frontmatter 都无此字段 取消该字段,路由规则改放 .blog-publish.yml,零 SCHEMA 改动
3 categoryTargets: {devops: "source/devops"} 真实路径是 source/_posts/<中文分类>/,分类由 abbrlink.auto_category(depth:1, over_write:true) 从目录名生成 改为 source/_posts/<分类>;分类清单改为真实 8 个 + manual 白名单
4 分类由 “Hexo 源码一级目录 “ 产生 实际是 source/_posts/ 下的一级目录 措辞修正
5 未提及 wiki / 通用 frontmatter 之分 SCHEMA 明确两套;wiki 套不含 publish 新增《发布范围与内容分级》,wiki 文档命中即整批失败
6 未提及 Obsidian 语法转换 ==高亮== 399 处、[[wikilink]] 860 处、三种图片解析模式 新增《内容转换规范》整章
7 Manifest 无 abbrlink 存量 38 篇博客标题与知识库不同名,按知识库 title 重算会断 38 个 URL Manifest 增加 abbrlink / created,并规定只继承不重算
8 npm ci 项目用 pnpm 10.11.0 + pnpm-workspace.yaml 的 patchedDependencies,依赖 patches/hexo@8.1.2.patch 让 created 优先 改为 pnpm install --frozen-lockfile,Node 24
9 github-pages “ 保留静态产物历史用于回滚 “ 现有 workflow 是 force_orphan: true + keep_files: false,单提交无历史 列为必决项(改 workflow 或删除该要求)
10 隐私门禁为 “ 建议 “ 0X0E_Custom/ 被 Git 跟踪,其中含明文账号口令 门禁升级为阻断式,0X0E_Custom/** 硬排除
12 未区分存量与新增 线上已有 83 篇人工迁移文章 新增《存量接管》整章

1. 文档目的与适用范围

1.1 目的

本文用于评审并指导实现一套从私有知识库向公开 Hexo 博客自动发布内容的系统。重点解决:

  • 知识库文章分散在多级中文目录中,且只有通用 frontmatter 文档才允许公开;
  • 同一目录中的文章可能属于不同博客分类(重组后 0X0A 侧已无混合目录,仅 0X0F_知识沙盒/原始资料/网络文章 仍是混合目录);
  • 博客分类由文章在 source/_posts/ 中的一级目录名产生,不读 front-matter 的 categories 字段;
  • 知识库可高频自动提交,但博客不应每次提交都构建部署;
  • 已存在 83 篇人工迁移并已上线的文章,URL 与发布日期不能因同步而改变;
  • CNB 与 GitHub 分别承担源码管理、镜像、构建与 Pages 部署职责;
  • 整个链路必须可追溯、可重跑、可回滚,任一阶段失败保留上一线上成功版本。

1.2 范围

四仓库职责、发布协议与内容分级、分类路由、Obsidian 语法转换、存量接管、同步算法与 Manifest、触发时机、分支策略、凭证权限、幂等与并发、失败隔离、回滚、可观测性、实施阶段与验收标准。

1.3 非目标

  • 不规定 Hexo 主题与页面视觉设计(沿用 Butterfly + 现有玻璃皮肤层);
  • 不设计通用 CMS 或在线编辑器;
  • 第一阶段不支持一篇文章同时发布到多个渠道;
  • 不允许从公开仓库反向覆盖知识库、cnb-hexo 或 github-hexo;
  • 不允许人工直接编辑自动生成的同步文章或 github-pages 静态产物。

2. 总体架构

数据流单向:OneNote → cnb-hexo → github-hexo → github-pages。任何下游仓库不得反向成为上游内容来源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌────────────────────────────────────────┐
│ OneNote(cnb.cool/<组织>/OneNote) │ 创作态:原稿、图片、发布策略
│ .blog-publish.yml │ 权威:是否发布 + 逻辑分类
└───────────────────┬────────────────────┘
│ 只读拉取(CNB 定时 / 手动)
▼
┌────────────────────────────────────────┐
│ cnb-hexo(cnb.cool/<组织>/ │ 发布源码态:83 篇存量 + 同步文章
│ hexo-butterfly-next) │ 控制面:同步器、.cnb.yml、分类对账
│ tools/sync/ ← 同步器 │
└───────────────────┬────────────────────┘
│ 单向 Git 镜像(保持同一 commit SHA)
▼
┌────────────────────────────────────────┐
│ github-hexo(github.com/<GitHub 账号>/ │ 构建输入镜像态:唯一构建入口
│ hexo-butterfly-next) │ .github/workflows/pages.yaml
└───────────────────┬────────────────────┘
│ 构建 + 验证通过后推送 public
▼
┌────────────────────────────────────────┐
│ github-pages(<用户名>.github.io) │ 线上静态态:HTML/CSS/JS/图片
└────────────────────────────────────────┘

2.1 四个状态

状态 权威载体 说明
创作态 OneNote 私有笔记、草稿、可发布文章、图片与发布策略
发布源码态 cnb-hexo 经过筛选、分类路由、语法转换与资源处理后,可直接交给 Hexo 构建的确定性源码快照
构建输入镜像态 github-hexo cnb-hexo 的 Git 镜像,作为 GitHub Actions 的唯一构建输入
线上静态态 github-pages 只保存成功构建并验证后的公开静态产物

2.2 关键设计原则

  • 单一事实源:原稿以知识库为准;发布源码以 cnb-hexo 为准;构建输入以 github-hexo 指定提交为准;线上内容以 github-pages 当前发布版本为准。
  • 语义与实现分离:知识库只表达 “ 是否发布、属于哪个逻辑分类 “;cnb-hexo 负责校验、映射、语法转换、分类对账与后续构建衔接。
  • 完整状态生成:每次同步基于知识库最新完整状态生成期望发布树,不能只复制最近变更文件。
  • 无变化不推进:同步结果无差异时不提交、不镜像、不构建、不部署。
  • 全批次原子性:任一发布文章或资源校验失败,整批失败,不允许部分成功;已知问题文件通过显式 quarantine 清单隔离,而不是放宽原子性(见 4.6)。
  • 失败不污染下游:同步失败不修改 cnb-hexo;镜像失败不修改 github-hexo;构建失败不修改 github-pages。
  • URL 稳定、内容以知识库为准:已上线的 83 篇文章,abbrlink 与 created 继承不变(URL 与发布日期稳定);但正文与文件名是知识库的镜像——知识库改则博客改,知识库删则博客下线。
  • 自动目录不可手改:Manifest 登记的同步文件与 github-pages 产物由流水线独占维护。

2.3 为何采用四仓库

取舍 收益 代价
CNB 侧承载同步器 CNB 对 cnb.cool 知识库有原生访问,不需要跨平台只读凭据,也没有 GitHub Runner → cnb.cool 的连通性风险 需要 .cnb.yml 与 CNB 侧 Node 运行环境
保留 GitHub 侧构建 沿用已在运行的 GitHub Actions 与 peaceiris/actions-gh-pages 部署链 增加一次镜像、一组凭据与一个监控点
源码双仓 CNB 主仓 + GitHub 镜像,跨平台冗余,CI 与内容源解耦 必须保证镜像方向单一,避免双向编辑
静态产物独立公开仓库 产物与源码隔离,产物历史可审计、可直接回滚 需要跨仓写权限

3. 仓库职责与边界

3.1 OneNote:私有内容与发布策略仓库

  • 保存:原始 Markdown、图片资源、私有笔记与草稿、发布策略文件 .blog-publish.yml。
  • 现状:不含构建与部署类流水线;仅有 .cnb.yml 一个 job,在 push 时执行只读的发布协议 Lint(RESOURCES/scripts/lint.js),不构建、不发布。
  • 不负责:创建或维护 Hexo 物理目录、主题、Node 环境、构建命令、Pages 部署与下游仓库写入。

3.2 cnb-hexo:私有 Hexo 源码主仓与控制面

  • 仓库:https://cnb.cool/<组织>/hexo-butterfly-next。
  • 保存:Hexo 站点配置、Butterfly 配置、玻璃皮肤层、patches/、pnpm-workspace.yaml、83 篇存量文章、同步器实现(建议 tools/sync/)、同步 Manifest、自动生成文章、.cnb.yml(定时同步与源码镜像任务)。
  • 现状关键事实(详见附录 B):
    • 站点 https://<用户名>.github.io;language: zh-CN;permalink: posts/:abbrlink/;
    • 包管理 pnpm 10.11.0(packageManager 锁定),构建侧 Node 24;
    • abbrlink.auto_category = {enable: true, over_write: true, depth: 1} → source/_posts/ 的一级目录名即分类;
    • patches/hexo@8.1.2.patch 使 created 优先于 date,由 pnpm patchedDependencies 自动应用;
    • scripts/ 只能放合法 JS(Hexo 硬编码该目录并递归加载所有文件),非 JS 工具放 tools/。
  • 引导方式:初始内容以现有 github-hexo 的 main 为基线导入;此后 cnb-hexo 为唯一写入源。
  • 负责:读取知识库协议、生成发布源码、执行语法转换、维护 Manifest、执行分类对账、把源码镜像到 github-hexo。
  • 不负责:执行 GitHub Actions,或保存最终静态站点。

3.3 github-hexo:私有 GitHub 源码镜像

  • 仓库:github.com/<GitHub 账号>/hexo-butterfly-next。
  • 是 cnb-hexo 的单向 Git 镜像,也是 GitHub Actions 的构建入口。
  • 原则上禁止直接提交;任何源码变更必须先进入 cnb-hexo。
  • 通过 .github/workflows/pages.yaml 执行唯一一次完整 Hexo 构建。
  • 构建成功后把 public 原子更新到 github-pages;构建失败时不触碰 github-pages。
  • .cnb.yml 会随镜像一同出现在本仓库,但 GitHub 不读取该文件,无副作用。

运维注意:现有本地开发目录 <博客源码目录> 的 origin 目前指向 GitHub。切换为四仓库模型后,本地开发必须改为推送 cnb-hexo,否则直接推送到 GitHub 的提交会被镜像覆盖或造成分叉。见 18.13。

3.4 github-pages:公开静态页面仓库

  • 仓库 <用户名>.github.io,公开;禁止放入源码、私密配置、Token、未公开文章与构建日志。
  • 禁止人工编辑;变更只允许来自 github-hexo 的部署工作流。
  • GitHub Pages 从约定分支(main)与根目录发布。
  • 是否保留静态产物 Git 历史取决于 11.4 的决策。

3.5 职责矩阵

职责 OneNote cnb-hexo github-hexo github-pages
维护文章原稿 负责 否 否 否
定义是否发布、逻辑分类 负责 执行与校验 否 否
校验并执行 Hexo 物理目录映射 否 负责 镜像 否
生成发布源码与语法转换 否 负责 否 否
源码单向镜像 否 负责 接收 否
执行 Hexo 构建 否 否 负责 否
保存并发布静态文件 否 否 部署 负责

4. 知识库发布协议

4.1 协议所有权

知识库团队拥有内容发布策略,Hexo 系统只消费并执行。双方通过稳定字段与显式版本解耦;禁止根据标题、正文关键词或任意深层目录名猜测发布意图。

4.2 发布范围与内容分级(核心约束)

知识库按位置使用两套 frontmatter(见 0X0F_知识沙盒/SCHEMA.md),发布权限与套别严格绑定:

套别 适用位置 字段数 可否发布
通用 frontmatter 0X0A_知识管理/、0X0D_三味书屋/、0X0F_知识沙盒/原始资料/、控制文件 8 可以(需 publish: true)
wiki frontmatter 0X0F_知识沙盒/ 下 实体信息/、概念专题/、对比分析/、查询存档/、计划方案/ 11 一律不发布

判定规则(双重保险):

  1. 路径级:wiki 目录不在 include 中,正常情况根本不会进入候选集;
  2. 类型级:候选文件若含 wiki 专有字段(type / owner / sources / confidence 任一),视为分类错误,整批同步失败并提示 “wiki frontmatter 文档不可发布 “。

该约束由内容负责人明确指定:知识沙盒内的 wiki 页面仅作内部知识沉淀,不上博客。

4.3 文章级 Front Matter

本设计不新增任何 frontmatter 字段,只使用 SCHEMA 通用套别中已存在的 publish:

1
2
3
---
publish: true
---
字段 类型 默认值 语义
publish boolean false 必须显式为 true 才进入发布候选集

**为什么取消 v1 的 blog-category 字段:**SCHEMA 声明自己是 frontmatter 的单一事实源,两套基线字段已固定并规定 “ 除 copyright、publish 外均必填 “。新增字段会破坏该契约,并需要为 300+ 篇文档补字段。分类路由本质属于 “ 发布策略 “,因此改放仓库级协议文件(见 4.4),零 schema 改动。

事实:当前全库没有任何一篇文章是 publish: true。标注发布范围是本项目的前置工作量,见 16.1。

4.4 仓库级协议文件

知识库根目录维护 .blog-publish.yml。现状:文件存在但为 0 字节,需按下述内容填充(示例为按真实目录写就的 v1 协议):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 结构摘要。可执行版本以仓库根 .blog-publish.yml 为准
# (含 7 条 quarantine、10 条 pathDefaults、3 组 fileOverrides、5 条 secretPatterns)
schemaVersion: 1

source:
include: # 窄根 = 唯一的发布范围入口
- "0X0A_知识管理/**/*.md"
- "0X0D_三味书屋/**/*.md"
- "0X0F_知识沙盒/原始资料/**/*.md"
quarantine: # 已知问题文件,不进入候选集
- "0X0F_知识沙盒/原始资料/网络文章/<问题文件>.md"

defaults:
publish: false

rules:
draftPrefix: "_" # 文件名以 _ 开头 = 草稿,不发布
targetNaming: keep-source # 目标文件名与知识库一致,不改写
requirePublishTrue: true
forbidWikiFields: [type, owner, sources, confidence]

content: # 语法转换与附件剥离策略
transforms:
highlight: mark
wikilink: permalink
callout: blockquote
stripComments: true
keepRemoteUrls: true

assets: # 本地图片统一落盘位置
targetDir: "source/assets/images/_sync"
publicPath: "/assets/images/_sync"
copyReferencedOnly: true
onNameCollision: fail

categoryTargets: # 分类 → Hexo 物理目录(10 个分类)
编程语言: "source/_posts/编程语言"
...

pathDefaults: # 目录 → 分类
编程语言:
- "0X0A_知识管理/A100_技术体系/A130_软件开发/**"
...

fileOverrides: # 混合目录逐文件指定
金融投资:
- "0X0F_知识沙盒/原始资料/网络文章/暴跌以后什么时候能抄底.md"

frontmatter: # lint 的字段集事实源,与 SCHEMA.md 对齐
general:
required: [title, description, tags, ddc, created, updated]
wiki:
required: [title, description, tags, ddc, type, owner, sources, confidence, created, updated]

lint: # push 时只读校验,不发布
scope: candidates
blocking: [secrets]
warning: [frontmatter, config, draftPublishConflict, pathExists]
secretPatterns: [...] # 口令 / 私钥 / Token / 内联凭证
allowlist: [...] # 教程示例与占位符

A600_时光余热 当前为空目录,无需规则。

格式约定:

小节 键 值 匹配语义
pathDefaults 逻辑分类 目录前缀模式列表,必须以 /** 结尾 文件路径命中该前缀即视为该分类
fileOverrides 逻辑分类 具体 .md 文件路径列表 精确匹配单个文件,优先级高于 pathDefaults

唯一性约束(硬性):pathDefaults 与 fileOverrides 中出现的每条路径必须在全文件范围内唯一,即不得同时挂在两个分类下,也不得在两个小节重复出现。违反则整批同步失败——否则同一文件会有两个互相矛盾的期望分类。

4.5 解析优先级

  1. 文件必须命中 include(窄根是唯一的发布范围入口,故无需 exclude);
  2. 文件不得命中 quarantine;
  3. 文件名以 _ 开头 → 视为草稿,不参与发布(见下);
  4. publish 必须显式为 true;
  5. 候选文件若带 wiki 专有字段(type/owner/sources/confidence),整批失败;
  6. 分类解析:
    1. 在 fileOverrides 中按精确文件路径查找 → 命中即采用该分类;同一文件命中多个分类(违反唯一性约束)则整批失败;
    2. 否则在 pathDefaults 中收集所有命中该文件路径的模式,取最长前缀者;
    3. 最长前缀并列且分属不同分类 → 整批失败;
    4. 均未命中 → 整批失败。
  7. 解析出的分类必须已出现在 categoryTargets 中,否则整批失败。

下划线草稿约定(已确定,硬规则):文件名以 _ 开头的文章一律不参与发布——不论 publish 取值如何,也不进入差异计算(既不新增,也不因此下线博客已有文章)。

  • 现有 4 篇:_软件测试面试记录.md、_软件测试面试题总结.md(A120_软件测试)、_Clash 电脑端使用教程.md、_Clash 手机端使用教程.md(A140_开发工具)。
  • 需要发布时,先去掉 _ 前缀重命名,再标 publish: true。
  • 若出现「_ 前缀 + publish: true」的矛盾标记,仍按草稿跳过,并在报告中产出告警。
  • 该规则不能由 quarantine 替代:后者是临时隔离;下划线是内容负责人表达 “ 未完成 “ 的语义标记。

4.6 已知问题文件的隔离

设计保留 “ 整批原子性 “,但现实已存在无法一次性修复的问题(见附录 B.4:8 篇网络文章的 54 处本地图片引用全部指向不存在的 images/ 目录)。若直接启用,0X0F/原始资料 这条线第一天即整批失败。

处理方式:

  • source.quarantine 显式列出问题文件,它们不进入候选集,因此不参与校验,也不影响整批;
  • 每次同步必须报告 quarantine 命中数量,并在数量变化时告警;
  • 问题修复后从清单移除。禁止用 “ 自动跳过失败文件 “ 替代该清单——那会静默丢内容。

4.7 协议版本

schemaVersion 是强制字段。cnb-hexo 只接受已声明支持的版本;未知版本必须在写入任何目标文件前失败。协议升级应提供兼容窗口;禁止静默忽略未知字段或把新语义按旧规则执行。

4.8 知识库提交时的前置 Lint

知识库普通提交不触发发布,但建议执行轻量 Lint:

  • YAML 与 frontmatter 可解析;publish 类型为布尔;
  • 未混用两套 frontmatter(wiki 字段与通用字段不得同时出现);
  • pathDefaults / fileOverrides 的每条路径全局唯一;
  • pathDefaults 的模式均以 /** 结尾,fileOverrides 均为具体 .md 路径;
  • 两个小节引用的分类键均已在 categoryTargets 中声明;
  • categoryTargets 的分类键与目标路径唯一且格式合法;
  • quarantine / fileOverrides / pathDefaults 中出现的路径必须在工作树中存在(防止改名或删除后配置静默失效);
  • 敏感信息扫描:明文口令、Token、私钥、内部域名(对应 0X0E_Custom/E100_慢煮生活/账户管理.md 这类文件,必须直接命中阻断)。

该 Lint 仅帮助作者提前发现问题;cnb-hexo 消费时仍必须重新执行全部校验。


5. 分类与路径映射

5.1 真实分类清单

博客现有一级分类(source/_posts/ 下目录,共 8 个、83 篇):

分类 篇数 知识库来源 状态
编程语言 29 A130_软件开发 受管
软件测试 23 A120_软件测试、原始资料/网络文章(AI 测试类) 受管
开发工具 13 A140_开发工具 受管
人工智能 8 A200_人工智能 受管
操作系统 5 A110_操作系统 受管
博客搭建 3 A150_博客搭建 受管
其他内容 1 A400_学习工坊 manual(不受管)
视频剪辑 1 A300_创意工坊/A310_视频剪辑 受管(需确认是否发布)

需新增的分类(不存在于当前博客,启用后由同步器创建目录,Hexo 自动生成分类页):读书笔记、金融投资。

5.2 逻辑分类到物理目录

1
2
categoryTargets:
软件测试: "source/_posts/软件测试"
  • 分类由 source/_posts/ 下的一级目录名生成(auto_category.depth: 1、over_write: true),因此 front-matter 里的 categories 字段会被覆盖——同步器应主动写入正确的 categories 以免误导读者,但不得依赖它决定分类。
  • 知识库不得直接填写 source/_posts/… 等物理路径。

对账语义:知识库策略中的 categoryTargets 是权威期望值,cnb-hexo 中的实际目录是当前值。同步时由第 8 章算法比较并修正:缺失的分类目录补齐、路径不符的修正;不属于任何已声明分类的目录不主动删除(见 8.1)。

目录编号与 ddc 约定(知识库侧):0X0A_知识管理 采用三级编号——A1x0 为分支、A1x1 起为子目录;文章 ddc 统一取 <所在目录编号>.<序号>(如 A141_MySQL数据库 下为 A141.1),与所在目录编号保持一致。当前 A100_技术体系 的 5 个分支与博客分类一一对应:

知识库分支 篇数 博客分类
A110_操作系统 5 操作系统
A120_软件测试 36 软件测试
A130_软件开发(A132–A135,纯语言与开源项目) 29 编程语言
A140_开发工具(A141_MySQL / A142_Git / A143_Docker + 本层 3 篇) 16 开发工具
A150_博客搭建 3 博客搭建

该对齐由 2026-10-17 的目录重构达成:A140_杂七杂八 拆为 A140_开发工具 + A150_博客搭建;MySQL/Git/Docker 三个子目录由 A130_软件开发 上移至 A140_开发工具 并重编号为 A141/A142/A143;A138_开源项目 收回为 A135_开源项目。知识库内 104 篇 ddc 已按此约定全量校准(前缀与目录编号 100% 一致,无空值)。

5.3 文件命名与重名

  • 目标文件名与知识库文件名完全一致,不做任何改写:不剥离 1. 序号前缀、不追加语言前缀、不拼接父目录。
1
2
0X0A_知识管理/A100_技术体系/A130_软件开发/A133_Golang系列/Golang-04:函数详解.md
→ source/_posts/编程语言/Golang-04:函数详解.md
  • 分类目录仍为扁平结构(source/_posts/ 的一级目录 = 分类),但文章名不扁平化。
  • 若两个源文件映射到同一目标路径(同名冲突),整批失败并列出全部冲突源文件;不允许后写覆盖。
  • 校验范围包含存量 83 篇:新文章与已存在博客文章同名(导致 abbrlink 冲突)同样整批失败。
  • 实测:0X0A_知识管理、0X0D_三味书屋、0X0F_知识沙盒/原始资料 三者之间当前不存在文件名冲突。
  • 对存量的影响:博客侧有 38 篇在发布时改过文件名,与知识库不同。按本规则,同步后这些博客文件将被重命名为知识库的名字;abbrlink 按 7.4 规则继承不变,URL 不受影响(对照表见附录 A)。

5.4 图片与本地资源

知识库存在三种图片引用形态,同步器分别处理(实测数据见附录 B.3):

形态 示例 处理方式
远程 URL ![](https://cdn.jsdelivr.net/gh/AlwaysYo/ImageHost/Obsidian/xxx.webp) 保持原链接,不下载、不重写。此类引用占 0X0A 的绝大多数(957 处)
文件相对路径 ![](images/img_001.png) 以该 .md 所在目录为基准解析 → 复制到 source/assets/images/_sync/ → 改写为 /assets/images/_sync/<文件名>
裸文件名(Obsidian 全局附件目录) ![](80509d51c6-20261018-4e0c99.webp) 按 .obsidian/app.json 的 attachmentFolderPath = RESOURCES/images/wiki-images 解析 → 同样复制到 source/assets/images/_sync/ 并改写

规则:

  • 统一落盘目录为 source/assets/images/_sync/,不再按文章隔离子目录。_sync 是同步器独占子目录:可安全地按 “ 是否仍被引用 “ 整体清理;父目录 source/assets/images/ 下还放着主题头像等人工资源,绝不可当作受管目录整体清理(曾误删 Avatar_*.jpg)。若配置指向父目录,清理跳过并告警。
  • 只复制正文实际引用的资源:候选中未被任何发布文章引用的图片不进入发布树;缺失引用(引用了但文件不存在)仍整批失败(已知问题文件走 quarantine)。
  • 同名冲突:文件名相同且内容一致 → 复用同一份;文件名相同但内容不同 → 整批失败并列出冲突来源,禁止静默覆盖。
  • 远程 URL 不得改写(含不得改动查询串);被排除目录(如 RESOURCES/**)的图片引用按 5.5 处理,不复制。
  • 资源缺失、越权引用被排除目录、目标路径逃逸时,整批失败。

5.5 附件与 Git LFS

  • 该目录不在 include 中,且引用行由 content.stripReferencePrefixes 剥离,因此不会出现在 cnb-hexo / github-hexo / github-pages;
  • 同步器不得触发 LFS 拉取(避免私有附件进入构建环境);两个源码仓库均须确认不会把 LFS 指针文件当正文处理;
  • 因此纯附件型文章(正文只有附件链接、无实际内容)发布后为空文,应在发布标注阶段直接排除,不设 publish: true。

5.6 自动管理边界

  • 采用 Manifest 方案(而非 synced 子目录),因为 auto_category.depth: 1 要求文章直接位于分类目录下;
  • Manifest 精确记录每个受管目标文件;
  • 同步器只删除 “Manifest 上一版本登记、本次期望状态不再包含 “ 的文件,禁止对 source/ 或 source/_posts/ 执行无边界删除;
  • 存量 83 篇全部纳入差异计算(见 7.2);Manifest 记录其目标路径与 abbrlink,以便执行重命名、内容覆盖与删除。

6. 内容转换规范

知识库是 Obsidian 库,含大量 Hexo 原生不支持的语法。这是最容易 “ 上线即翻车 “ 的部分,必须先于自动化实现。

6.1 Obsidian 语法转换表

语法 实测数量 Hexo 直接构建的后果 转换规则
==高亮== 0X0A 285 处、0X0F 114 处 —— 不转换。实测这些 == 全是代码比较运算符([ $a == $b ]、currency == 'USD')与 ==> 箭头,无一为真高亮;开启转换会破坏代码,故该逻辑已删除
[[目标]] / [[目标|别名]] 0X0F 855 处、0X0A 5 处 输出字面量 [[…]] 建立「知识库路径 → 目标文章 permalink」索引(来源是 Manifest 的 abbrlink);命中则转 [别名](/posts/<abbrlink>/);未命中默认保留原文(wikilinkFallback: keep),可配 text 降级为纯文本
![[]] 嵌入 2 处,均在行内代码块中 无 无需处理;若出现真实嵌入则按 5.4 处理
> [!quote] callout 0X0A 4 处、0X0D 1 处 显示为 [!quote] 字面量 转 > **标题** + 正文引用块;不依赖主题 tag 插件
<br /> 等行内 HTML 大量 正常(marked 直通 HTML) 原样保留
%%注释%% 0X0A 0 处、0X0E 3 处 原样显示 整段删除
正文中的 #标签 少量 原样显示为文本 第一阶段不处理,仅报告

网络文章还有固定的头部引用块,需转换后保留(不要丢掉来源信息):

1
2
3
> **公众号**: 听玛玛的话
> **发布时间**: 2026-06-09 12:22
> **原文链接**: [https://mp.weixin.qq.com/s/xxx](https://mp.weixin.qq.com/s/xxx)

6.2 Front Matter 输出白名单

博客现用字段(其仓库 AGENTS.md 明确 “ 不要写 summary、copyright、aliases、toc“):

1
2
3
4
5
6
7
8
9
---
title: 标题
description: 摘要
ddc: A124.1
tags: [软件测试]
categories: [软件测试] # 由路由结果写入
abbrlink: a262d6ed # 继承或生成,见 7.4
created: '2025-06-24 11:45'
---

映射规则:

输入(知识库通用 frontmatter) 输出(博客 front-matter) 说明
title title 存量文章保持现有值(见 7.3)
description description 直接映射
tags tags tags: 为空(YAML null)时输出 tags: [] 并发 Lint 警告
ddc ddc 直接映射
created created 存量文章保持博客现值
updated updated 保留;随知识库 bump
publish — 丢弃
copyright copyright: false 源值为布尔(true/false,2026-10-18 起);仅在值为 false 时输出。主题判断 page.copyright !== false,这是按文章关闭版权声明的唯一途径;true 不输出(走主题默认版权声明)
type / owner / sources / confidence — wiki 字段,出现即整批失败

7. 存量接管

7.1 现状

项 数量
线上博客文章总数 83
与知识库文件名同名(可直接对上) 45
文件名不同(发布时改过标题) 38

38 篇的完整映射见附录 A。典型改名模式:

  • 序号前缀被剥离:Golang-01:如何快速运行 Golang 程序 → 如何快速运行 Golang 程序
  • 系列编号被改写:Pytest 测试框架三部曲(一) → Pytest 测试框架三部曲 - 上
  • 序号与副标题被压缩:AI 系列-4:计算机视觉-给机器装上眼睛 → AI 系列:计算机视觉-给机器装上眼睛
  • 错字/漏字:知识库 1.信息系统项目管理师教程-第4版 → 博客 信息系项目管理师教程-第4版(缺” 统” 字)

7.2 三类文章

类别 数量 同步器行为
已接管(publish: true 且已登记映射) 0(起步) 进入 Manifest,完整状态生成
存量未接管(已在线上,但知识库未标 publish 或未映射) 83 参与差异计算,以知识库为准(见下)
新增(知识库 publish: true,博客无对应文件) — 正常生成并新增

「以知识库为准」的具体含义:

  • 博客文章在知识库有对应来源 → 正文以知识库为准覆盖;文件名改为知识库名(见 5.3);abbrlink 与 created 按 7.4 继承,URL 不变。
  • 博客文章在知识库找不到对应来源 → 视为知识库已删除该内容,从博客中移除。判定依据 adoption-map.yml(知识库路径 → 博客文件)而非文件名猜测。
  • 知识库侧为 publish: false、或落入 quarantine → 对应博客文章下线。

实测结论(2026-10-17):博客 83 篇全部能在知识库找到对应来源,0 篇缺失。因此启用「以知识库为准」当前不会删除任何线上文章,可安全切换。该结论必须随每次同步重新计算并写入报告;一旦出现 “ 博客有、知识库无 “ 的条目,须在报告中显式列出并告警。

7.3 接管流程(逐篇显式)

  1. 在 cnb-hexo/tools/sync/adoption-map.yml 中登记:知识库源路径 → 博客现有文件(含分类);
  2. 在知识库对应文章的 frontmatter 中置 publish: true;
  3. 首次同步对已接管文章执行内容一致性检查:以博客现有文件为基线,比对转换后的正文是否一致;
    • 一致的:写入 Manifest,纳入受管;
    • 不一致的(如正文已被人工润色):标记为 manual-diverged,默认采用知识库版本,并在报告中列出差异,供人工决定是否把博客侧改动回补到知识库;
  4. 首批建议只接管 5 篇作为试点,验证 abbrlink、日期、图片、分类、渲染均无变化后再批量。

permalink: posts/:abbrlink/。abbrlink 的唯一来源是 hexo-abbrlink 插件:它挂在 before_post_render 过滤器上,在 hexo generate 阶段读 title 算 crc32 并写回源文件 front-matter。同步器运行在写文件之前,此时插件尚未运行,因此不自行计算 abbrlink,只做「继承」与「回读」。

  • 规则 R1:目标文件不存在 → 由插件在构建期生成,同步器不预置值;
  • 规则 R2:Manifest 中已有 abbrlink 时,同步器写入目标文件;插件检测到该值存在即跳过重写(if (!abbrlink || config.force)),构建后工作区保持干净;
  • 规则 R3:abbrlink 的继承键是知识库源路径(身份稳定),目标路径仅作兜底。分类迁移改变目标路径时短链不得变化;
  • 规则 R4:created 直接取知识库值。存量 83 篇已随 2026-10 清空,不再存在「博客现值优先」的优先级问题;
  • 规则 R5:闭环分两笔提交。第一笔写正文(新文章无 abbrlink);构建后运行 readback 把短链收进 Manifest;第二笔用「规范格式 + abbrlink」重写并提交。此后插件不再重写,状态收敛。构建失败时第一笔仍有效(正文上线、短链缺失、双链保留原文)。

8. 同步算法与 Manifest

8.1 完整期望状态算法

  1. 锁定知识库 main 的具体提交 SHA;
  2. 读取并校验 .blog-publish.yml、schemaVersion、categoryTargets,以及 pathDefaults / fileOverrides 的路径唯一性与分类声明合法性;同时读取 cnb-hexo 当前 source/_posts/ 目录结构与 Manifest;
  3. 扫描 include,排除 quarantine;
  4. 解析全部候选文件的 frontmatter;
  5. 校验 frontmatter 套别(含 wiki 字段则整批失败);
  6. 筛选 publish: true;
  7. 按 4.5 的规则解析逻辑分类(fileOverrides 精确命中优先,否则 pathDefaults 最长前缀);
  8. 应用 categoryTargets 映射目标目录、文件名与资源路径;
  9. 执行第 6 章语法转换与资源复制;
  10. 目标路径冲突检测、图片可解析性校验(缺失即整批失败)、路径安全校验;
  11. 生成完整期望发布树与新 Manifest(contentHash 记录内容指纹);
  12. 执行 8.2 分类对账:补齐声明目录、迁移受管文章、清理仅含受管内容的旧目录;
  13. 与 cnb-hexo 当前状态比对,无差异则成功结束;有差异则增量落盘并提交推送(第一笔提交:正文 + 资源 + Manifest);
  14. 在 CNB 运行一次 hexo generate(不部署),由 hexo-abbrlink 在 before_post_render 阶段把短链写回源文件;
  15. 运行 readback 把短链收进 Manifest;
  16. 再次增量落盘并提交推送(第二笔提交:abbrlink 回填并规范化),随后强制镜像至 github-hexo。

8.2 分类对账算法

在文章分类解析与发布树生成之后、最终 diff 与提交之前执行。它只校准 “ 分类到物理目录 “ 的配置与受管目录。

  1. 读取知识库 categoryTargets(期望)与 cnb-hexo 实际 source/_posts/ 目录(当前);
  2. 比较键集合:期望有而仓库缺少的分类创建目录;
  3. 只增不删:未被 categoryTargets 声明的既有分类目录不主动删除,仅补齐与修正已声明分类;
  4. 同名键目标路径不一致时,以知识库为准迁移该分类下的受管文章;
  5. 期望已删除且仅含受管内容的旧目录删除;
  6. 如待删除目录含人工或未知非托管文件、目标路径越界、两个分类映射到同一路径,整批失败等待人工处理;
  7. 分类修正、目录创建/删除、文章迁移与 Manifest 更新纳入同一个提交。

与 v1 的差异:v1 的「Hexo 有而策略缺少的分类一律删除」在存在人工分类的现状下会误删,故改为只增不删(source/_posts 已在 2026-10-17 清空,后续由同步器按 categoryTargets 重建)。

8.3 Manifest 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"schemaVersion": 1,
"knowledgeCommit": "abc123...",
"protocolVersion": 1,
"categoryTargets": {
"软件测试": "source/_posts/软件测试",
"编程语言": "source/_posts/编程语言"
},
"articles": {
"0X0A_知识管理/A100_技术体系/A120_软件测试/A124_自动化测试/接口测试基础.md": {
"category": "软件测试",
"target": "source/_posts/软件测试/接口测试基础.md",
"routingRule": "pathDefaults:软件测试",
"abbrlink": "a262d6ed",
"created": "2025-06-24 11:45",
"contentHash": "sha256:...",
"assets": [],
"adoption": "matched-by-name"
}
},
"quarantined": ["0X0F_知识沙盒/原始资料/网络文章/腾讯WorkBuddy实践:如何把Agent做成可用产品.md"],
"report": {
"scanned": 0,
"excluded": 0,
"published": 0,
"skipped": 0,
"convertedWikilinks": 0,
"degradedWikilinks": 0,
"warnings": []
}
}

Manifest 用于处理删除、下线、移动、重命名与改分类:

  • publish: true → false:删除旧目标;
  • 源文件删除:删除旧目标;
  • 分类改变:删除旧分类文件、新增新分类文件(abbrlink 不变);
  • categoryTargets 对账结果:记录期望映射、修正前实际映射与操作摘要;
  • 源文件移动但目标不变:更新来源记录,不重复创建页面。

8.4 幂等要求

  • 相同知识库提交、相同发布策略、相同 cnb-hexo 基线重复执行,必须生成完全相同的发布树、对账结果与 Manifest;第二次执行不产生差异;
  • 时间戳不得写入会造成每次 diff 的源码文件;构建时间只写入构建产物元数据;
  • 构建后工作区必须干净(对应 7.4 规则 R5)。

9. 触发模型与端到端流程

9.1 触发总表

触发点 执行位置 动作
知识库普通 push 知识库 CNB 仅执行发布协议 Lint;不发布
每日定时或人工手动 cnb-hexo CNB 拉取知识库最新 main,生成发布源码;有差异才提交
cnb-hexo main 更新 cnb-hexo CNB 将源码单向镜像到 github-hexo main
github-hexo main 更新 GitHub Actions pnpm install --frozen-lockfile → pnpm run build → 验证 → 部署
github-pages 发布分支更新 GitHub Pages 发布新静态版本

9.2 推荐时序

  1. 知识库团队一天内多次提交文章,不触发正式发布;
  2. 每天固定时间,或操作者手动启动 cnb-hexo 的内容同步任务;
  3. cnb-hexo 读取知识库最新 main 并锁定具体提交 SHA;
  4. 同步器生成完整发布源码快照并校验;
  5. 无差异时任务成功结束,后续镜像、构建与部署均不触发;
  6. 有差异时 cnb-hexo 创建一条自动提交并推送 main;
  7. cnb-hexo 的镜像任务将该提交推送到 github-hexo main;
  8. github-hexo 的 GitHub Action 被 push 事件触发,执行唯一一次完整构建;
  9. 构建与产物校验通过后更新 github-pages;
  10. GitHub Pages 发布新版本。

9.3 同步器运行位置

**已确定:同步器在 cnb-hexo 侧由 CNB 流水线执行。**理由:

  • CNB 与知识库同平台,读取 cnb.cool/<组织>/OneNote 无需跨平台凭据,也不存在 GitHub Runner 访问 cnb.cool 的连通性风险;
  • 镜像任务本就需要在 CNB 侧执行,同步与镜像合并为一条流水线,减少一组跨平台凭据;
  • 知识库侧只引入只读 Lint 流水线(不构建、不部署、不写下游),与 3.1 的边界约定一致。

实现要点:

  • .cnb.yml 位于 cnb-hexo 仓库根目录,声明定时触发与手动触发两个入口,二者调用同一套同步实现,禁止维护两套规则;
  • 运行环境需提供 Node(与构建侧一致,建议 24)与 Git;同步器为 JS,放 cnb-hexo/tools/sync/;
  • 流水线内不得触发 LFS 拉取。

实际实现(cnb-hexo/.cnb.yml):

1
2
3
4
5
6
7
8
9
main:
"crontab: 0 3 * * *": # 每日 03:00 自动运行
- *publish-job

$: # 不绑定分支的事件走兜底分支
web_trigger: # 页面手动触发
- *publish-job
api_trigger: # API 触发
- *publish-job

三个入口共用同一份 job 定义与同一把 lock(key: kb-publish、wait: true),

刻意不监听 push —— 流水线自身会向 main 提交,监听 push 会自激成死循环。

单个 job 依次执行:克隆知识库 → sync --apply → push → pnpm install && pnpm build

→ readback → sync --apply → push → git-sync 强制镜像到 github-hexo。

9.4 手动入口

  • 手动同步知识库:紧急发布或定时失败补跑;
  • 手动重跑 github-hexo 构建:源码不变,仅重新构建与部署(现有 workflow 已支持 workflow_dispatch);
  • 手动回滚 github-pages:回退到上一成功静态提交。

9.5 CNB 与 GitHub 配置边界

  • CNB 读取仓库根目录 .cnb.yml,用于定时同步与源码镜像;
  • GitHub 读取 .github/workflows/*.yml,用于 Hexo 构建与静态部署;
  • 两类配置在同一份源码中共存;镜像到 GitHub 后,GitHub 只执行自己的 workflow,忽略 .cnb.yml。

10. 分支与提交策略

10.1 分支

仓库 分支 规则
OneNote main 创作与发布策略稳定基线
cnb-hexo main Hexo 发布源码主分支;接受人工源码变更与自动内容同步提交
github-hexo main 只接受 cnb-hexo 镜像;禁止直接提交
github-pages main 只接受 Action 写入的静态产物(publish_branch: main)

流程变更提醒:现有本地开发目录 <博客源码目录> 的 origin 指向 GitHub。切换到四仓库模型后,日常开发必须先推送 cnb-hexo,再由镜像流向 GitHub。见 18.13。

10.2 自动提交规范

1
2
3
4
5
6
文档修改:同步知识库发布快照

1.原因分析:无
2.修改方法:
1)同步 N 篇可发布文章的期望状态
2)分类对账:无变更 / 补齐 X 项

正文附 Knowledge-Commit: <sha> 与 Protocol-Version: 1 尾注。提交信息风格遵循该仓库 .trae/rules/git-commit-message.md(<type>:<subject> + 原因分析/修改方法)。

10.3 提交一致性

理想情况下 cnb-hexo 与 github-hexo 是同一 Git 提交的镜像,保持相同 commit SHA,不应重新复制文件制造无关提交。

10.4 禁止双向编辑

  • github-hexo 的任何修复必须回到 cnb-hexo 实施;
  • github-pages 的任何修复必须回到 github-hexo 源码或构建流程实施;
  • 同步文章不得在 cnb-hexo 中人工修改,否则下次完整同步会覆盖;
  • 博客侧的人工改动会被知识库覆盖;确有价值的内容必须先回补到知识库再发布(manual-diverged 报告用于发现这类改动)。

11. 构建与部署

11.1 现状对齐(重要修正)

v1 写的 npm ci 不可用:本项目用 pnpm 10.11.0,且依赖 pnpm-workspace.yaml 的 patchedDependencies 应用 patches/hexo@8.1.2.patch(让 created 优先于 date)。用 npm 会安装失败或使日期补丁失效,导致全站日期错误。

现有 workflow(.github/workflows/pages.yaml)实际做法:

项 现值
触发 push: branches: [main] + workflow_dispatch
Runner ubuntu-latest
Node 24
包管理 pnpm(版本由 package.json 的 packageManager 决定),pnpm install --frozen-lockfile
构建 pnpm run build
时区 sudo timedatectl set-timezone "Asia/Shanghai"(日期正确性依赖)
现有校验 test -f public/index.html
部署 peaceiris/actions-gh-pages@v4,publish_branch: main,external_repository: <owner>/<owner>.github.io,keep_files: false,force_orphan: true

11.2 需要补充的构建步骤

  1. 检出 github-hexo 指定提交;
  2. 安装固定 Node(24)与 pnpm;
  3. pnpm install --frozen-lockfile(含 hexo 日期补丁应用校验);
  4. hexo clean 与 hexo generate;
  5. 验证 public/index.html 之外:分类页数量与 source/_posts/ 分类目录数量一致、归档页存在、search.xml 存在、关键静态资源(/glass/*)存在、abbrlink 页面可访问;
  6. 生成构建元数据 public/build-meta.json;
  7. 校验构建后工作区无未提交改动(7.4 R5);
  8. 仅在全部验证通过后更新 github-pages。

11.3 构建元数据

1
2
3
4
5
6
7
8
{
"knowledgeCommit": "abc123...",
"cnbHexoCommit": "def456...",
"githubHexoCommit": "def456...",
"protocolVersion": 1,
"node": "24",
"builtAt": "ISO-8601 timestamp"
}

cnbHexoCommit 与 githubHexoCommit 在镜像正常时应相同;若不同,说明镜像滞后,应告警。该文件不得包含私有仓库地址、Token、个人信息或内部文件路径。

11.4 静态产物历史(必须决策)

v1 要求 “ 静态产物保留 Git 历史用于审计与快速回滚 “,但现有 workflow 使用 force_orphan: true + keep_files: false,每次部署只有一个提交、无历史,两条要求直接矛盾。二选一(见 18.1):

  • 方案 甲:去掉 force_orphan,保留历史 → 满足审计与快速回滚,仓库体积增长;
  • 方案 乙:保持 force_orphan,回滚改为 “ 重跑历史源码提交的 workflow”→ 放弃静态产物历史。

11.5 并发控制

1
2
3
concurrency:
group: hexo-production-deploy
cancel-in-progress: true

github-hexo 连续收到多个提交时,只让最新提交完成部署;已进入写入静态产物的临界阶段不得产生半成品。CNB 侧的同步任务同样应设置并发串行,避免两次同步互相覆盖。


12. 权限、凭证与隐私

12.1 最小权限矩阵

执行方 目标 权限 建议凭证
cnb-hexo 同步任务 OneNote(cnb.cool) 只读 CNB 平台内仓库只读授权(同平台,无需跨平台 Token)
cnb-hexo 镜像任务 github-hexo Contents 写 Fine-grained PAT 或专用 Deploy Key,存于 CNB 密钥
cnb-hexo 同步任务 cnb-hexo Contents 写 平台内置凭据
构建部署 github-pages Contents 写 现有 DEPLOY_TOKEN(仅授权该仓)
构建 checkout github-hexo 读 DEPLOY_TOKEN(现有做法)

各凭证只允许单一目标仓库;禁止跨仓库复用同一 Token。cnb-hexo 侧只需一个 “ 能写 github-hexo“ 的凭据,且不得具备 github-pages 写权限。

12.2 隐私门禁(升级为阻断式)

  • 知识库默认私有,缺少 publish: true 时绝不公开;
  • quarantine 优先级最高,_ 草稿其次;
  • 0X0E_Custom/** 硬排除:该目录被 Git 跟踪(20 个文件),其中 0X0E_Custom/E100_慢煮生活/账户管理.md 含明文账号与口令。除路径排除外,同步器必须执行敏感内容扫描(口令/Token/私钥/内部域名/本地绝对路径),命中即整批失败;
  • 同步前扫描目标内容,禁止包含 Token、私钥、内部域名、未授权个人信息与本地绝对路径;
  • github-pages 为公开仓库,部署前检查产物中不存在源 Markdown、私有配置以及 source map 中的敏感路径;
  • 日志不得打印凭证或包含敏感内容的完整正文;
  • 禁止将 .agents/、.obsidian/、Lark/、AGENTS.md 等仓库元数据纳入发布树;
  • 注意:发布源码快照会同时存在于 cnb-hexo 与 github-hexo 两个私有仓库,” 未公开内容不得进入发布树 “ 这一约束在这两个仓库同样适用。

13. 异常处理、回滚与恢复

13.1 失败隔离

失败位置 系统行为 恢复方式
知识库协议 Lint 阻止错误规则进入稳定分支或告警 修复 frontmatter 或协议配置后重新提交
cnb-hexo 同步校验 不修改 cnb-hexo;整批失败 修复知识库或映射后手动重跑;已知问题文件走 quarantine
语法转换 / 资源解析 同上(失败不落盘) 修复源文件或加入 quarantine
CNB → GitHub 镜像 cnb-hexo 已保存正确版本,github-hexo 保持旧版本 凭据或网络恢复后重试同一提交
Hexo 构建 不更新 github-pages,线上保持上一版本 修复源码或依赖后重新触发
github-pages 写入 / Pages 发布 保留上一成功提交或部署 重试部署,或回滚到已知成功提交

13.2 回滚路径

  • 内容回滚:知识库回退文章或发布策略,下次同步生成新的下线或恢复提交;
  • 源码回滚:cnb-hexo 回退到历史发布源码提交,再镜像并重新构建;
  • 构建重放:对 github-hexo 的历史提交手动运行 workflow(workflow_dispatch),生成相同版本静态站;
  • 线上快速回滚:取决于 11.4 的决策——甲方案直接回退静态产物提交;乙方案重放上一成功源码提交的构建。

应区分 “ 立即恢复线上 “ 的静态回滚与 “ 修复权威源 “ 的内容回滚;静态回滚止损后仍须在知识库或 cnb-hexo 修复事实源,避免下次自动任务再次部署错误版本。


14. 可观测性与审计

14.1 每次同步应记录

  • 知识库提交 SHA 与协议版本;
  • 扫描、排除、quarantine、发布、下线、冲突与失败文章数量;
  • 新增/修改/删除/分类移动的目标文件清单,以及分类对账的补齐、删除与路径修正记录;
  • 每条文章的命中路由(routingRule:fileOverrides:<分类> 或 pathDefaults:<分类>);
  • 语法转换统计:== 转换数、wikilink 命中数、降级为纯文本的 wikilink 数(代表断链风险);
  • 敏感信息扫描结果;
  • cnb-hexo 生成提交 SHA;触发方式(定时/手动)。

14.2 每次镜像应记录

  • 源提交 SHA 与目标提交 SHA 是否一致;
  • 镜像耗时与结果;失败原因。

14.3 每次构建应记录

  • github-hexo 提交 SHA、Node 与 pnpm 版本;
  • 构建耗时、生成文件数量、分类页与关键资源验证结果;
  • github-pages 目标 SHA 与最终 Pages URL。

14.4 告警建议

  • 定时同步连续失败;
  • 协议版本不兼容;
  • 目标重名、分类非法或私有内容泄露风险;
  • quarantine 命中数量增加;
  • wikilink 降级数量突增(内容断链信号);
  • 镜像长期落后于 cnb-hexo(提交 SHA 不一致);
  • 构建失败或 Pages 发布失败;
  • 同步后工作区不干净(abbrlink 写回冲突)。

15. 关键取舍与备选方案

15.1 为何采用四仓库

取舍 收益 代价
知识库与 Hexo 源码分离 私有内容不会自然进入博客源码;创作与发布职责清晰 需要发布协议与同步器
CNB 与 GitHub 源码双仓 CNB 侧原生访问知识库;保留 GitHub Actions 构建入口与跨平台冗余 增加一次镜像、一组凭据与一个监控点
静态产物独立公开仓库 产物与源码隔离,产物历史可审计、可直接回滚 需要跨仓写权限

15.2 已否决方案

  • 每次知识库提交立即发布:构建频繁,草稿过程可能反复上线;
  • 每天首次提交发布:当天后续提交无法及时进入发布结果;
  • 由 GitHub Actions 直接拉取 cnb.cool 知识库:需要跨平台只读凭据并承担连通性风险,且与 CNB 侧镜像任务重复;
  • 新增 blog-category frontmatter 字段:破坏 SCHEMA 单一事实源契约,需为 300+ 篇补字段;
  • 只靠 front-matter 的 categories 字段决定分类:auto_category.over_write: true 会覆盖它;
  • 只按知识库目录决定分类:原始资料/网络文章 是 AI 测试与金融的混合目录,仍须 fileOverrides;
  • 增量复制最近改动文件:无法可靠处理删除、下线、移动与分类变化;
  • 无边界删除:可能误删 83 篇人工内容;
  • 自动跳过校验失败文件:静默丢内容,必须用显式 quarantine 清单;
  • 在 github-hexo 或 github-pages 直接编辑:破坏单向事实链。

16. 实施阶段

16.0 阶段零:仓库引导与基线冻结

  • 确认 cnb-hexo(https://cnb.cool/<组织>/hexo-butterfly-next)的初始状态:空仓则从 github-hexo main 导入;已是镜像则明确把镜像方向改为 cnb-hexo → github-hexo;
  • 记录当前构建耗时与产物文件数,作为回归基线;
  • 冻结存量 83 篇的 abbrlink 快照(source/_posts/** → abbrlink),后续任何同步都以此为准;
  • 切换本地开发流程:本地仓库增加 cnb-hexo 远端并以其为主(见 18.13)。

16.1 阶段一:协议与发布标注(前置工作量最大)

  • 填充 .blog-publish.yml(v1 协议),并纳入 Git 跟踪;
  • 依据 pathDefaults / fileOverrides 为候选文章逐篇标注 publish: true(当前全库为 0 篇);
  • 决定新增分类(读书笔记 / 金融投资)与 其他内容 的归属;
  • 实现 Schema 校验、include/quarantine、路径唯一性校验、分类解析、对账与冲突检查;
  • 以 5 篇试点验证端到端。

16.2 阶段二:内容转换

  • 实现 == → <mark>、wikilink → permalink、callout、注释删除;
  • 实现三种图片引用形态的解析与资源复制;
  • 处理 LFS 附件引用策略;
  • 输出转换报告,确认 wikilink 降级数为 0(或已接受)。

16.3 阶段三:CNB 自动同步与镜像

  • 在 cnb-hexo 配置 .cnb.yml:定时与手动同步入口,无 diff 不提交;
  • 配置 cnb-hexo → github-hexo 单向镜像;
  • 验证同一源码提交在两个仓库的 SHA 一致;
  • 验证镜像失败时 github-hexo 不被修改。

16.4 阶段四:存量接管

  • 建立 adoption-map.yml(附录 A 的 38 篇 + 45 篇同名);
  • 逐批接管,每批后校验线上 URL、日期与渲染无变化;
  • 处理 6 处 created 冲突与 manual-diverged 文章。

16.5 阶段五:可靠性与运营

  • 补齐构建验证、build-meta.json、并发控制、构建后工作区校验;
  • 决策并落实 11.4 静态产物历史方案;
  • 演练协议错误、凭证失效、镜像失败、构建失败与回滚;
  • 建立分类变更、协议升级与密钥轮换流程。

17. 验收标准

17.1 功能验收

  • 只有显式 publish: true 且不是 wiki frontmatter 的文档进入博客;
  • 0X0F_知识沙盒/ 下 wiki 目录的文章(实体信息/概念专题/对比分析/查询存档/计划方案)在任何情况下都不出现在发布树中;
  • fileOverrides 能把同一源目录的文章路由到不同分类(如 原始资料/网络文章 → 软件测试 / 金融投资);
  • pathDefaults 能按最长前缀正确兜底;同一路径挂在两个分类下时整批失败;
  • 分类对账能补齐缺失项、修正路径,且不主动删除未声明的既有分类;
  • 文章删除、publish: false、移动与改分类后旧页面正确下线;
  • 目标重名、分类非法、资源缺失、敏感信息与协议不兼容会阻止整批发布;
  • 同步无差异时,cnb-hexo、github-hexo、github-pages 均不产生提交。

17.2 存量与 URL 验收

  • 83 篇存量文章的 abbrlink 全部不变;
  • 「博客有、知识库无」的文章数为 0(当前实测为 0,见 7.2;每次同步须重新计算并写入报告);
  • 38 篇「博客文件名 ≠ 知识库文件名」的文章同步后按 5.3 重命名,abbrlink 不变且 URL 可访问;
  • 38 篇改过标题的文章,URL 不变;
  • 已接管文章的渲染结果与原文一致(除预期的语法转换外);
  • 6 处 created 冲突按 7.4 R4 处理,线上发布日期不变。

17.3 可靠性验收

  • 相同输入重复执行不产生新提交;
  • 构建后工作区干净(无 abbrlink 写回);
  • 定时与手动同步并发时不互相覆盖;
  • 镜像失败不改变 github-hexo;
  • 构建失败不改变 github-pages;
  • cnb-hexo 与 github-hexo 的对应提交 SHA 一致;
  • 可在不修改知识库的情况下重放历史构建;
  • 可快速回退线上到上一成功版本(依 11.4 决策)。

17.4 安全验收

  • 公开静态产物中不含私有 Markdown、凭证、内部地址或本地绝对路径;
  • 0X0E_Custom/ 内容零泄漏,敏感信息扫描可拦截明文口令;
  • 各凭证只具备单目标仓库最小权限(镜像凭据不可写 github-pages);
  • 同步任务对知识库只有只读访问;
  • 所有自动生成目录禁止人工直接修改。

18. 待决策项

# 事项 建议默认值 决策影响
1 静态产物历史(11.4 甲/乙) 甲(去掉 force_orphan,保留历史) 决定回滚方式与静态仓库体积
2 新增分类是否启用:读书笔记 / 金融投资 全部启用 决定 0X0D_三味书屋、A500_金融投资 的内容能否上线;视频剪辑 分类已存在,只需确认 A300 那篇是否发布
3 其他内容 是否继续作为 manual 分类 保留为 manual 决定 A400 那篇文章是否纳入自动同步
4 wiki 文档命中 publish: true 时的行为 整批失败 也可选择 “ 忽略并告警 “,但会掩盖误标
5 _ 前缀草稿约定(见 4.5) 已决定:启用(硬规则) 文件名以 _ 开头一律不发布、也不参与差异计算;需发布时先重命名去掉前缀再标 publish: true
7 abbrlink 由同步器写回知识库,还是仅写在 cnb-hexo 侧 仅 cnb-hexo 侧 写回知识库会改动源文件、触发额外提交;不写回则知识库无 URL 视图
8 每日同步时间 内容团队停止编辑之后 决定内容最长发布延迟
9 目标文件名是否允许显式 slug 已决定:不需要 文件名与知识库文件名完全一致、不做改写(见 5.3),故不存在 slug 需求
10 8 篇失效图片文章(附录 B.4)的处置 先 quarantine,再逐篇修复 决定 原始资料/网络文章 何时能上线
11 1. / 2. 数字前缀文件名的目标命名 已决定:不剥离 目标文件名沿用知识库原名(见 5.3);知识库侧命名已由内容负责人整理
12 cnb-hexo → github-hexo 镜像语义 强制镜像(mirror),非快进 强制镜像会覆盖 GitHub 侧任何直接提交;若保留人工在 GitHub 打补丁的能力,需改为快进并接受分叉风险
13 本地开发仓库的远端迁移方式 origin 改指 cnb-hexo,GitHub 降为只读远端 决定日常提交路径;不改则本地 push 会与镜像冲突

19. 最终推荐

采用四仓库、单向数据流、消费者定时拉取、完整状态生成、无差异不推进、存量显式接管的架构。

  • OneNote 定义发布策略(.blog-publish.yml)与 publish 标注;wiki frontmatter 文档永不发布;
  • cnb-hexo 读取协议、生成发布源码、执行语法转换与分类对账,并镜像到 GitHub;
  • github-hexo 执行唯一一次完整 Hexo 构建;
  • github-pages 只保存成功构建并验证后的静态产物。

核心契约:每个阶段只有一个权威来源;每次转换确定且可重跑;任一阶段失败都不污染下一阶段;任何线上 URL 与发布日期都不因自动化而改变;任一线上版本都能追溯到知识库提交。


附录 A:38 篇重命名映射表

博客现有文件(source/_posts/<分类>/<文件名>) ↔ 知识库源文件。接管时以此表登记 adoption-map.yml。

# 博客分类 博客现有文件名 知识库源文件(0X0A_知识管理/ 下)
1 人工智能 AI 系列:人工智能的前世今生 & 必知基础原理.md A200_ 人工智能/A220_AI 科普/AI 系列 -1:人工智能的前世今生 & 必知基础原理.md
2 人工智能 AI 系列:揭秘 LLM 大语言模型.md A200_ 人工智能/A220_AI 科普/AI 系列 -2:揭秘 LLM 大语言模型.md
3 人工智能 AI 系列:搜索 - 从迷宫求解看人工智能关键技术.md A200_ 人工智能/A220_AI 科普/AI 系列 -3:搜索 - 从迷宫求解看人工智能关键技术.md
4 人工智能 AI 系列:计算机视觉 - 给机器装上眼睛.md A200_ 人工智能/A220_AI 科普/AI 系列 -4:计算机视觉 - 给机器装上眼睛.md
5 其他内容 信息系项目管理师教程 - 第 4 版.md A400_ 学习工坊/A410_ 信息系统项目管理师/1.信息系统项目管理师教程 - 第 4 版.md
6 编程语言 如何快速运行 Golang 程序.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-01:如何快速运行 Golang 程序.md
7 编程语言 Golang 基础知识 & 数据类型详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-02:基础知识 & 数据类型详解.md
8 编程语言 Golang 条件循环 & 集合类型详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-03:条件循环 & 集合类型详解.md
9 编程语言 Golang 函数详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-04:函数详解.md
10 编程语言 Golang 闭包 & defer 详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-05:闭包 & defer 详解.md
11 编程语言 Golang 错误处理详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-06:错误处理详解.md
12 编程语言 Golang 指针 & 结构体详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-07:指针 & 结构体详解.md
13 编程语言 Golang 接口 & 类型断言详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-08:接口 & 类型断言详解.md
14 编程语言 Golang 反射详解 reflect.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-09:reflect 反射详解.md
15 编程语言 Golang 协程的执行过程.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-10:协程的执行过程.md
16 编程语言 Golang 内存回收 GC 详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-11:内存回收 GC 详解.md
17 编程语言 Golang Mutex 详解.md A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-12:Mutex 详解.md
18 编程语言 Python 介绍及环境搭建.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-01:介绍及环境搭建.md
19 编程语言 Python 编码规范.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-02:编码规范.md
20 编程语言 Python 基础内容.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-03:基础内容.md
21 编程语言 Python 数据结构.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-04:数据结构.md
22 编程语言 Python 控制结构 & 函数.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-05:控制结构 & 函数.md
23 编程语言 Python 文件操作.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-06:文件操作.md
24 编程语言 Python 并发编程.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-07:并发编程.md
25 编程语言 Python 网络编程.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-08:网络编程.md
26 编程语言 Python 包及模块.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-09:包及模块.md
27 编程语言 Python 异常类型 & 正则表达式.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-10:异常类型 & 正则表达式.md
28 编程语言 Python 常见设计模式.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-11:常见设计模式.md
29 编程语言 Python 面向对象.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-12:面向对象.md
30 编程语言 Python 学习路线.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-13:学习路线.md
31 编程语言 Python3 in one pic.md A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-14:Python3 in one pic.md
32 软件测试 Pytest 测试框架三部曲 - 上.md A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Pytest 测试框架三部曲(一).md
33 软件测试 Pytest 测试框架三部曲 - 中.md A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Pytest 测试框架三部曲(二).md
34 软件测试 Pytest 测试框架三部曲 - 下.md A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Pytest 测试框架三部曲(三).md
35 软件测试 Selenium 框架三部曲 - 上.md A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Selenium 框架三部曲(一).md
36 软件测试 Selenium 框架三部曲 - 中.md A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Selenium 框架三部曲(二).md
37 软件测试 Unittest 单元测试框架 - 上.md A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Unittest 单元测试框架(一).md
38 软件测试 Unittest 单元测试框架 - 下.md A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Unittest 单元测试框架(二).md

序号 32–38 的 “ 上/中/下 “ 必须按系列顺序对应 “ 一/二/三 “,匹配算法需按序号而非字符串。
其余 45 篇与知识库文件同名,可直接按文件名建立映射。


附录 B:现状实测数据

B.1 知识库规模

目录 Markdown 数 说明
0X0A_知识管理 104 可发布候选(通用 frontmatter)
0X0F_知识沙盒 218 其中 原始资料 15 篇可发布;其余 203 篇为 wiki 页面或控制文件(SCHEMA.md/index.md/log.md)
0X0D_三味书屋 3 可发布候选
0X0E_Custom 19 禁止发布(含明文口令)
0X00/0X0B/0X0C 7 仪表盘与待办,排除
.agents/.trash/.dashboard-backup 20 仓库元数据,排除
合计 385

0X0A_知识管理 内部:A100_技术体系 89(A110 操作系统 5、A120 软件测试 36、A130 软件开发 29、A140 开发工具 16、A150 博客搭建 3)、A200_人工智能 8、A300_创意工坊 1、A400_学习工坊 1、A500_金融投资 5、A600_时光余热 0。

B.2 博客规模

83 篇,8 个分类:编程语言 29、软件测试 23、开发工具 13、人工智能 8、操作系统 5、博客搭建 3、其他内容 1、视频剪辑 1。

B.3 Obsidian 语法与资源实测

指标 0X0A_知识管理 0X0F_知识沙盒 0X0D_三味书屋
文件数 104 218 3
远程图片引用 957 2 0
本地图片引用 7 106 0
[[wikilink]] 5 855 0
==高亮== 285 114 0
Obsidian callout 4 0 1

关键配置(.obsidian/app.json,已随 Git 跟踪):attachmentFolderPath: "RESOURCES/images/wiki-images"、newLinkFormat: "shortest"、useMarkdownLinks: true。

B.4 已知内容缺陷:网络文章图片失效

0X0F_知识沙盒/原始资料/网络文章/ 下 8 篇顶层文章的本地图片引用全部无法按路径解析(其 images/ 目录在 Git 历史中从未存在):

文件 失效引用数
腾讯 WorkBuddy 实践:如何把 Agent 做成可用产品.md 30
大模型测试用例生成与优化.md 4
提示词工程在智能测试中的实践 - 设计方法.md 4
提示词工程在智能测试中的实践 - 优化技巧.md 4
AI 大模型与智能测试融合 - 基础认知.md 4
AI 大模型与智能测试融合 - 评估体系.md 4
GameDevBench 智能体评测.md 4
合计 54

补充事实:img_030.png 等在全库不存在;img_001.jpeg/img_002.png/img_003.png/img_004.png 仅存在于 原始资料/资源素材/。另有 1 篇(你能做到训练有素的收割吗.md)使用裸文件名引用 资源素材/ 中的 3 张图,可按 5.4 的全局附件规则解析。output/ 下的 2 篇与独立目录形式的 1 篇图片全部可正常解析。

这批文件即 4.6 quarantine 清单的来源;不隔离则整批同步在第一天即失败。

B.5 仓库状态

知识库 OneNote:

  • 远程 https://cnb.cool/<组织>/OneNote,分支 main;
  • 无 CI 配置(不存在 .cnb.yml 或 .github/);
  • .blog-publish.yml 存在但为 0 字节且未纳入 Git 跟踪;
  • .gitignore 已忽略 .trae、.trash、.dashboard-backup、.workbuddy、.sync_temp_dir;
  • 工作区存在未提交改动(AGENTS.md、SCHEMA.md、log.md、frontmatter-规范.md、金融模板.md);
  • 存在外部自动同步工具产生的提交(Auto Sync: <时间> | <主机名>),印证 “ 高频提交但不应每次发布 “ 的前提。

发布源码(本地 <博客源码目录>):

  • origin 当前指向 git@github.com:<GitHub 账号>/hexo-butterfly-next.git,尚未配置 cnb-hexo 远端;
  • 仓库内不存在 .cnb.yml;
  • 本地实测已存在 patches/、pnpm-workspace.yaml、scripts/tag-colors.js、tools/patch-hexo-date.py、source/glass/ 与 .github/workflows/pages.yaml。

B.6 实施后实测(2026-10)

指标 数值
可发布文章 100 篇(候选 119 / _ 草稿 4 / 未发布 15 / quarantine 0)
分类分布 软件测试 38、编程语言 27、开发工具 14、人工智能 9、操作系统 5、博客搭建 3、金融投资 2、三味书屋 1、视频剪辑 1
远程图片引用 939 处,全部原样保留(不下载、不重写)
本地图片引用 0 处(失效引用已全部删除)
callout 转换 4 处
[[双链]] 命中 / 保留原文 0 / 2(后者为 shell 条件表达式)
%%注释%% 删除 0 处
附件行剥离 0 处
同步器测试用例 77 个,全部通过
幂等 连续两次运行产物 sha256 完全一致
构建 pnpm install --frozen-lockfile + hexo generate 通过,生成 270 个文件
abbrlink 100/100 由插件写入,readback 全部回填;二次运行 0 差异

产物位置

产物 路径
同步器 cnb-hexo/tools/sync/(sync.js、readback.js、lib/*.js)
测试套件 cnb-hexo/tools/sync/test/run.js
Manifest cnb-hexo/tools/sync/manifest.json(随每次同步提交)
流水线 cnb-hexo/.cnb.yml
发布树 cnb-hexo/source/_posts/、cnb-hexo/source/assets/images/_sync/

参考资料