Webnovel Writer 投影日志 projection_log.jsonl 详解:快速定位 state、index、summary、memory 哪路没同步
【免费下载链接】webnovel-writer基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200 万字量级 连载创作。项目地址: https://gitcode.com/GitHub_Trending/we/webnovel-writer
Webnovel Writer 是一套基于 Claude Code 的长篇网文辅助创作系统,支持 200 万字量级连载,专门解决 AI 写作中的「遗忘」与「幻觉」问题。它的核心思路是:每写完一章,先把「事实」提交为 commit,再由 5 路投影写入器同步到 state、index、summary、memory 等衍生文件——而同步是否成功,就记录在projection_log.jsonl这份投影日志里。本文带你读懂它,并能快速排查「哪路投影没同步」。
一、为什么需要 projection_log.jsonl:把「事实」和「执行记录」分开 📋
传统做法是把各文件的同步状态直接塞在 commit 里。但一个问题随之而来:重试了几次、每次结果如何、哪个写入器在哪一步失败,这些信息在 commit 里无处安放。
Webnovel Writer 的解法来自plugin-runtime-hardening-plan中的设计:
| 数据 | 存放位置 | 角色 |
|---|---|---|
| 提交事实 + 最新投影状态 | .story-system/commits/chapter_NNN.commit.json | 单一事实来源(write facts) |
| 每次投影执行的完整记录 | .webnovel/projection_log.jsonl | 独立执行日志(append-only) |
两者「双写」:每次chapter-commit执行投影后,chapter_commit_service.py 会先回写 commit 内的projection_status,再追加一条日志。这样即使某路投影中途失败、事后重跑,历史轨迹也完整可查。
日志路径由 projection_log.py 定义:
project_root / .webnovel / projection_log.jsonl,schema 版本为webnovel-projection-log/v1。
二、一行日志长什么样:9 个字段逐一看 🔍
日志是标准 JSONL(每行一个 JSON 对象)。每次投影运行由 build_projection_run() 构造,形如:
{ "schema_version": "webnovel-projection-log/v1", "run_id": "3f2a…", "created_at": "2026-06-10T08:30:00+00:00", "chapter": 42, "commit_path": ".story-system/commits/chapter_042.commit.json", "commit_hash": "9c1e…", "commit_status": "accepted", "status": "done", "writers": { "state": {"status": "done"}, "index": {"status": "skipped"}, "...": {} }, "projection_status": { "state": "done", "index": "skipped", "summary": "done", "memory": "done", "vector": "done" } }各字段排查时的用途:
| 字段 | 含义 | 排查价值 |
|---|---|---|
run_id/created_at | 本次运行唯一 ID 与时间戳 | 区分同一章的多次重跑 |
chapter | 所属章节号 | latest_projection_run() 按章节取最新一条 |
commit_hash | commit 内容的 SHA-256 | 校验日志与 commit 是否对应 |
status | 整体状态(见下表) | 一眼判断这轮是否干净 |
writers | 每个写入器的详细结果 | 定位「哪路」失败的直接依据 |
projection_status | 与 commit 内一致的状态快照 | 与 commit 交叉核对 |
整体状态由 _overall_status() 汇总:任一路failed→ 整条failed;全部skipped→skipped;有pending→pending;其余 →done。
三、5 路写入器:state、index、summary、memory、vector 各写什么 🧭
ChapterCommitService._projection_writers() 注册了 5 个投影写入器,各自把 commit 中的事实落到不同衍生文件:
| 写入器 | 同步目标 | 典型内容 |
|---|---|---|
state | .webnovel/state.json(带文件锁 + 原子写 + 备份,见 state_projection_writer.py) | 实体当前状态、章节进度 |
index | 实体/索引数据库 | 人物、物品、关系图更新 |
summary | 章节摘要文件 | 本章 summary_text |
memory | 记忆便签 | 未回收伏笔、承诺、世界观规则 |
vector | 向量数据库 | 供检索的语义索引 |
初始时 5 路全部置为pending(projections.py),跑完投影后逐路更新。
四、状态值速查表:30 秒看懂「哪路没同步」 ✅
这是全文最实用的一张表。打开日志找到对应章节最后一行,看writers字段中各路的状态:
| 状态值 | 含义 | 你该怎么做 |
|---|---|---|
done | 已同步完成 | 无需处理 |
skipped | 本轮不需要这路投影(如该章没有产生摘要文本) | 正常,跳过即可 |
failed:<原因> | 执行异常,原因附在冒号后 | 按原因修复后重跑 |
pending | 还没跑/没跑完 | 直接执行 retry 补齐 |
状态判定逻辑在 _writer_status():not_required归为skipped,以error:开头的归为failed:…。
小技巧:
read_projection_runs()读取时会静默跳过损坏行(projection_log.py),所以即使日志尾部有一行坏数据,也不会影响排查工具工作。
五、事件路由表:为什么这章某些路是 skipped 📊
「这章明明写了新人物,为什么 memory 是 skipped?」——因为不是所有投影都每章必跑。EventProjectionRouter 按本章提取到的故事事件类型决定需要哪几路:
| 事件类型 | 触发的写入器 |
|---|---|
| 角色状态变化 / 境界突破 | state、memory、vector |
| 关系变化 / 获得物品 | index、vector |
| 世界观规则揭示 / 打破规则 | memory、vector |
| 伏笔开启 / 伏笔回收 | state、memory |
| 承诺生成 / 承诺兑现 | memory |
| commit 被拒(rejected) | 仅 state(把章节标记为 rejected) |
| commit 通过(accepted) | state、index 必选,其余按数据触发 |
所以skipped往往是设计如此,只有failed和遗留的pending才需要处理。
六、发现不同步:retry / replay 两步修复 🔧
确认某路失败后,不必重新写章,用 projections.py 提供的 CLI 直接从已有 commit 补跑:
# 补跑单章 python -m data_modules.projections --project-root <项目根目录> retry --chapter 42 --format text # 批量回放一段章节区间 python -m data_modules.projections --project-root <项目根目录> replay --from-chapter 40 --to-chapter 50 --format text- retry_projection():读取该章 commit → 重放全部写入器 → 返回最新
projection_status和最新日志记录 - replay_projections():对区间内每章依次 retry,全部成功才返回
ok: true
重跑成功后,日志里会新增一行新的 run(而非修改旧行),历史保留完整——这正是独立执行日志的价值。
七、doctor 与 Dashboard 如何消费这份日志 🖥️
投影日志不是只给你肉眼看,系统工具会主动读取它:
- doctor 体检:doctor.py 提供
projection_log.present(日志是否存在)与projection_log.latest_run(最新一轮是否 failed/pending)两项检查。后者发现异常时的修复建议原文就是「查看 projection_log.jsonl 的 writers 字段,修复后补跑 projection retry/replay」。 - Dashboard 面板:dashboard/app.py 展示某章投影状态时,优先取日志中最新 run(来源标记
projection_log),读不到才回退 commit 内快照(来源commit),保证面板反映的是最后一次真实执行结果。 - 写保护钩子:guard_runtime_write.py 会把 AI 对
projection_log.jsonl的直接写入视为危险操作并拦截——这份日志只能由系统双写产生,防止「自己改自己的病历」。
八、排查心法:3 步定位不同步 🎯
- 打开
.webnovel/projection_log.jsonl,按chapter找到最后一行; - 看整体
status:非done时逐路检查writers,锁定failed:…或残留pending的那一路; - 按
failed后的原因修复(如文件锁超时、目标文件损坏),执行retry补跑,再回到第 1 步确认新 run 为done。
附:本文涉及的关键文件 📁
| 文件 | 作用 |
|---|---|
| projection_log.py | 日志读写与状态判定核心实现 |
| projections.py | retry / replay CLI 入口 |
| chapter_commit_service.py | 5 路写入器注册与双写时机 |
| event_projection_router.py | 事件 → 写入器路由表 |
| doctor.py | 投影日志体检项 |
| test_projection_log.py | 日志行为测试用例,可当示例读 |
掌握 projection_log.jsonl 后,「哪路投影没同步」这类问题就不再需要翻遍各文件猜测——答案就在那一行 JSON 的writers字段里。
【免费下载链接】webnovel-writer基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200 万字量级 连载创作。项目地址: https://gitcode.com/GitHub_Trending/we/webnovel-writer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考