Skip to content

feat(core): v13 书籍创建流程迁移 — 段落式架构稿 + 旧书升级路径 + maxTokens bug fix#207

Open
SttFang wants to merge 14 commits intomasterfrom
feat/v13-book-creation-migration
Open

feat(core): v13 书籍创建流程迁移 — 段落式架构稿 + 旧书升级路径 + maxTokens bug fix#207
SttFang wants to merge 14 commits intomasterfrom
feat/v13-book-creation-migration

Conversation

@SttFang
Copy link
Copy Markdown
Collaborator

@SttFang SttFang commented Apr 20, 2026

Summary

把 inkos-team 的 Phase 5 段落式架构稿格式迁到 inkos,让新建书籍得到段落式 story_frame / volume_map + 一人一卡 roles/ 目录;旧书零改造继续用,也支持作者显式把条目式架构稿升级成段落式。顺手修了一个生产 bug:LLMClientmaxTokens / maxTokensCap 语义混用,导致 architect per-call 16384 被用户 config 的 8192 裁到 8192。

8 个 commit 按阶段组织:

阶段 commit 作用
1.1 21ed8f9 新增 outline-paths helper(Phase 5 双路径 fallback)+ 13 个单测
1.2 0b690ab writer / continuity / reviser / chapter-analyzer / consolidator / planner / runner / memory-retrieval 读架构稿统一走 helper
2.2 7efd910 architect 升级到段落式:ArchitectOutput 新增 storyFrame / volumeMap / roles;zh/en prompt 重写;parseSections + writeFoundationFiles 双路径落盘;剥 Phase 7 伏笔扩展列;文案 "弧线" → "角色线"
2.3 9bbb362 state-manager 新建书时创建 outline/roles/{主要,次要}角色/ 目录
3.1 + 3.2 e4d94ca sub_agent 的 architect 参数新增 revise + feedback;pipeline 新增 reviseFoundation(bookId, feedback) 把旧书升级成段落式 + 备份到 .backup-phase4-<ts>/
3.3 4a8c2cc context-transform 检测旧书时往注入的 user message 里加升级提示
test cleanup cf5a38d 新加测试里移除硬编码的 maxTokens / temperature 数字,改用 as unknown as LLMClient stub
bugfix 1f43f6a LLMClientmaxTokens(fallback)和 maxTokensCap(cap)彻底分离,5 个回归测试 + CLAUDE.md 规范

关键改动

  • 不改 sub_agent tool 的 agent 枚举(依然 5 个:architect/writer/auditor/reviser/exporter)
  • 不改 foundation-reviewer 审核循环(generateAndReviewFoundation 已在 main 分支就绪)
  • 不引入 Phase 7 伏笔治理系统(hook depends_on / core_hook / half_life 相关代码全部剥除)
  • inkos 自有改动保留:writer.ts +252 行 diff vs inkos-team、hook-policy.ts +40 行 diff 均保持

旧书向后兼容

  • 旧书(只有 legacy story_bible.md / volume_outline.md / character_matrix.md)继续可读可写——所有读取点走 outline-paths helper 自动 fallback
  • 交互式 agent 对话时,注入的真相文件 body 会加一段升级提示(不主动触发)
  • 作者同意升级时通过 sub_agent(architect, { revise: true, bookId, feedback })pipeline.reviseFoundation,原文件备份到 story/.backup-phase4-<ts>/

maxTokens 生产 bug

旧实现 createLLMClientmaxTokensCap: config.maxTokens ?? null 让 cap 永远等于 fallback。chatCompletionMath.min(perCall, cap) 把 architect per-call 16384 裁到 config.maxTokens = 8192。基础设定输出被截断。

fix 后:

  • LLMConfig.maxTokens = per-call 没传时的 fallback
  • LLMConfig.maxTokensCap = 新增的 optional 硬上限字段,默认 undefined → defaults.maxTokensCap = null = 不封顶
  • 5 个回归测试:createLLMClient maxTokensCap regression describe block
  • CLAUDE.md "防止 maxTokens 回归" 条款改写,列明设计规则 + 错误实现的样子 + 回归测试路径

规模

22 files changed, 1690 insertions(+), 260 deletions(-)

Core 侧 +1690 大头是 architect.ts 段落式 prompt 重写(zh+en 共 ~600 行 prompt)+ outline-paths helper(275 行)+ 各类回归/专项测试。

Test plan

  • pnpm test 全绿
    • core: 781 passed (77 files)
    • studio: 138 passed (18 files)
    • cli: 169 passed (34 files)
  • pnpm build 全通过
  • 类型检查 tsc --noEmit 无错误
  • Phase 7 引用在 src(非测试)零命中(grep -rn "Phase 7|depends_on.*hook|core_hook" packages/core/src 零命中)
  • inkos 自有改动 diff 行数保持不变
  • 手动烟雾测试(留给 reviewer 验收):
    • pnpm dev 新建一本书,确认 outline/story_frame.md + outline/volume_map.md + roles/主要角色/*.md + shim 文件都落盘
    • 构造一本 legacy 旧书,观察注入到对话的真相文件 body 是否含升级提示
    • 让 agent 调 sub_agent(architect, { revise: true, bookId, feedback }),确认旧文件备份 + 新结构产出

后续

Follow-up(独立 PR):把 createLLMClient 接到 pi-ai MODELS 注册表,按模型真实 maxTokens 查;可能再补一份 LobeHub model-bank 的中文 provider 数据(DeepSeek / 通义 / SiliconFlow 在 pi-ai 里完全没收录)。

相关 spec:`docs/superpowers/specs/2026-04-19-v13-book-creation-migration-design.md`
相关 plan:`docs/superpowers/plans/2026-04-19-v13-book-creation-migration.md`

🤖 Generated with Claude Code

SttFang and others added 13 commits April 18, 2026 23:16
ChatPage 通过 /api/v1/agent 走的是 pi-agent loop,工具集是
sub_agent / write_truth_file / read / edit / grep / ls 等,从来不会
返回 toolCall.name === "create_book"。BookFormCard 的渲染条件因此
永远不成立,相关的 pendingBookArgs / bookCreating / handleCreateBook /
createProgress 一整套 store 状态也都是死代码。

实际建书走 BookCreate 独立表单页 → POST /api/v1/books/create,与
本次清理无关;CLI 仍在用的 develop_book / CREATE_BOOK_TOOL /
developBookDraft 全都在 core 端,未触动。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
世界观/角色卡之前是 <p> 纯文本 + line-clamp,遇到 story_bible 里的
**加粗**、列表、小标题会原样显示为字面字符。现在接入 Streamdown
并带 cjk/code/math/mermaid 插件,同时用 Tailwind arbitrary variant
把标题/段落/列表的字号和间距压到卡片密度。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
之前 setRoute 只在非 hash 页面分支里 setRouteState,hash 页面完全
依赖 hashchange 事件回调触发。当用户走 services → logs → services
这类路径时,中间的 logs 不写 URL,URL 一直停在 #/services;再次
赋值同一个 hash 不触发 hashchange,React state 就永远停在 logs,
表现为"点 services 没反应"。

现在进 setRoute 一律先 setRouteState,再按需写 URL,并在写 URL 前
判重避免多余 hashchange。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
writer / continuity / reviser / chapter-analyzer / consolidator / planner /
runner / memory-retrieval 里读 story_bible.md / volume_outline.md /
character_matrix.md / current_state.md 的点都改成 readStoryFrame /
readVolumeMap / readCharacterContext / readCurrentStateWithFallback,
让 Phase 5 新书走 outline/ + roles/ 权威路径,旧书自动 fallback 到 legacy。

runner.ts 里做 before/after 对比的 old state 读取保留原样(需要磁盘原始内容);
book_rules.md 没有新路径替代,所有点也保留原 readFile 调用。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ArchitectOutput 新增 Optional 字段 storyFrame/volumeMap/rhythmPrinciples/roles,
  并引入 ArchitectRole 类型。老字段保留让外部消费者向后兼容。
- buildChineseFoundationPrompt / buildEnglishFoundationPrompt 方法移植自 inkos-team
  的段落式 prompt(4+5 段结构 + 一人一卡 roles),剥离 Phase 7 扩展列相关要求,
  pending_hooks 保留 inkos 侧的 8 列基础版。
- parseSections 识别新 section(story_frame/volume_map/roles),legacy
  section(story_bible/volume_outline)仍被接受做 fallback;新增 parseRoles
  解析 ---ROLE--- 块。
- writeFoundationFiles 按 storyFrame 是否非空分两条路径:Phase 5 输出下产出
  outline/story_frame.md + outline/volume_map.md + roles/{主要,次要}角色/*.md +
  各个 legacy shim;legacy 输出下照老方式写扁平 md 文件。运行时 append log 文件
  (particle_ledger/subplot_board/emotional_arcs)保留初始化,兼容下游。
- extractYamlFrontmatter 顶层 helper 负责把 book_rules 的 YAML frontmatter
  拼到 story_frame.md 顶部,让读取点有单一权威位置。
- 文案:generateFoundationFromImport prompt 里 "叙事弧线" → "故事线"。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ensureControlDocumentsAt 在现有 story/ + runtime/ 之外,同时创建
story/outline/、story/roles/主要角色/、story/roles/次要角色/ 三个 Phase 5
目录。这样 initBook 完成后磁盘就有完整的 Phase 5 目录结构,
架构师产出的 outline/*.md 和 roles/**/*.md 有落脚处。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Task 3.1:sub_agent 的 architect 参数新增 Optional revise + feedback 字段。
architect case 在 revise=true 时走 pipeline.reviseFoundation 分支,其他参数不变。

Task 3.2:
- architect.generateFoundation 新增第 4 个 options 参数,可带 reviseFrom 把
  老的条目式架构稿全文 + 用户反馈传进来。buildRevisePrompt 把这些拼成转换
  前缀注入到 system prompt 头部。原 3-参调用点(initBook / initFanficBook)
  保持不变。
- runner.reviseFoundation(bookId, feedback) 方法:先把 story_bible.md /
  volume_outline.md / book_rules.md / character_matrix.md 备份到
  story/.backup-phase4-<timestamp>/,读原内容喂给 architect(reviseFrom
  分支产出 Phase 5 结构),跑一次 foundation-reviewer(失败只 warn 不阻断),
  最后用 writeFoundationFiles 覆盖旧文件为 shim。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
createBookContextTransform 在读 truth files 之前先用 isNewLayoutBook(bookDir)
判断当前书的架构稿是否已经是 Phase 5 段落式。如果还是旧的条目式格式,
把一段 UPGRADE_HINT 拼到注入的 user message body 前面,提示 LLM 可以调
sub_agent(architect, { revise: true, ... }) 做一次升级——同时强调不要在
作者没明确同意前主动触发。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
此前 Phase 5 专项测试和 revise-foundation 测试按仓库历史惯例在 client.defaults
里写了 temperature: 0.7 / maxTokens: 4096 / thinkingBudget: 0 等数字。这些值:
- 对测试本身无意义——chat 方法被 vi.spyOn 拦截,defaults 运行时不被读取
- 会误导阅读者以为这是生产推荐配置,尤其 maxTokens: 4096 真被抄到生产会导致
  一章写到一半被截断(CLAUDE.md 明令禁止的 maxTokens 回归)

改成 `as unknown as LLMClient` 的 stub cast + 注释说明,保留类型满足度,
去掉所有具体数字。现有 architect.test.ts 其它测试里的老硬编码块不动,这次
只收敛新加的代码。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Bug

createLLMClient 里 `maxTokensCap: config.maxTokens ?? null` 的实现意图是
"只有用户显式设 maxTokens 时才封顶 per-call"。但 LLMConfigSchema.maxTokens 有
zod default 8192,config.maxTokens 永远非 undefined,cap 永远等于 maxTokens。
结果 chatCompletion 里 `Math.min(perCallMax, cap)` 会把 architect 的
per-call 16384 裁到 config.maxTokens=8192,基础设定输出被截断——这正是
CLAUDE.md "防止 maxTokens 回归" 那条条款在防范的。

## 修复

把"fallback"和"cap"这两个语义彻底分到两个字段:

- `LLMConfig.maxTokens` → agent 没传 per-call 时的 fallback(不变)
- `LLMConfig.maxTokensCap` → per-call 硬上限,**新字段**,默认 undefined →
  `client.defaults.maxTokensCap = null` = 不封顶
- createLLMClient 不再从 maxTokens 推导 cap;严禁再把两者合并

## 测试

packages/core/src/__tests__/provider.test.ts 新增 "createLLMClient
maxTokensCap regression" describe:
- 只设 maxTokens → cap 为 null(不封顶)
- 显式设 maxTokensCap → cap 生效
- 什么都不设 → cap 为 null
- per-call 16384 原样到达下游,不被 config.maxTokens=8192 裁(回归核心)
- per-call 16384 被显式 maxTokensCap=4096 裁(正常 cap 语义)

## 文档

CLAUDE.md 的 "防止 maxTokens 回归" 条款改写,把两个字段的设计规则、错误
实现的样子、回归测试的路径都写清楚,让后续改动一眼看得到边界。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1f43f6a 里给 CLAUDE.md 的"防止 maxTokens 回归"条款加了 5 行
fallback/cap 字段设计细节,违反 CLAUDE.md 的渐进式披露原则
(CLAUDE.md 是 spec 索引,设计细节应该在代码注释 / 独立 spec 里)。

LLMClient 的 maxTokens / maxTokensCap 语义规则已经在
packages/core/src/llm/provider.ts 的 type definition 注释里写清楚;
回归测试在 provider.test.ts 的 "createLLMClient maxTokensCap
regression" describe block。后续改动者读到那两处即可。

CLAUDE.md 恢复到单行"防止 maxTokens 回归"的索引条款。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
新建书或 reviseFoundation 落盘时,往 logger.info 输出一行记录本次走的是
phase5 还是 legacy 分支、storyFrame/volumeMap 字符数、roles 数量。

用途:当用户发现建书后磁盘只有 story_bible.md 没有 outline/ 时,看这行
能直接判断是 LLM 没按新 prompt 输出(走 legacy 分支)还是 writeFoundationFiles
本身的问题,不用读代码排查。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## 三个 Bug

**1 (high) writeFoundationFiles 无条件重置运行时状态文件**
之前在 reviseFoundation 场景下会把 current_state.md / pending_hooks.md /
particle_ledger.md / subplot_board.md / emotional_arcs.md 全部重置成初始模板。
对已经写过章节的书这是灾难——consolidator 累积的伏笔推进、角色位置、资源
账本、情感曲线都被清零,违反 context-transform 给 LLM 的 upgrade hint 承诺
"升级只改架构稿,不动已写的章节"。

修复:writeFoundationFiles 加 mode: "init" | "revise" 参数。init 模式下
初始化所有运行时文件(现有行为),revise 模式下**完全不动**运行时文件。
runner.reviseFoundation 调用时传 mode="revise"。initBook / initFanficBook
用默认 mode="init" 向后兼容。

**2 (high) reviseFoundation 第二次使用时喂 shim 给 architect**
Phase 5 书的 story_bible.md / character_matrix.md 是 shim(只有指针 + 摘录),
原 reviseFoundation 无条件读这些 flat 文件作为"原内容"。第一次升级后再用
sub_agent(architect, { revise: true, ... }) 做细节调整时,架构师拿到的
"原书内容"已经是丢信息的 shim——容易把角色线、世界观细节洗掉。

修复:reviseFoundation 用 isNewLayoutBook 检测当前书状态:
- Phase 5 书 → 从 outline/story_frame.md + outline/volume_map.md +
  roles/**/*.md(走 outline-paths helper)读权威全文
- legacy 书 → 从 story_bible.md 等 flat 文件读(现有行为)

**3 (medium) 删除/改名角色后旧 role 卡残留成幽灵**
writeFoundationFiles 只写本次 architect 输出的 role 文件,不清理
roles/**/*.md 里的旧文件。如果某次 revise 改名、删人、合并角色,旧卡会继续
被 readRoleCards 注入,planner / writer / auditor 看到 ghost 角色导致上下文
污染。

修复:writeFoundationFiles 在 mode="revise" 下,写 role 前先清空
roles/主要角色/ 和 roles/次要角色/ 两个目录。备份由 runner.reviseFoundation
在调用前完成(Phase 5 书会把 outline/ + roles/ 一并备份到 .backup-phase5-
<ts>/),清空安全。

## 测试

packages/core/src/__tests__/revise-foundation.test.ts 新增 4 个回归用例:

- revise 不重置运行时状态文件(构造已写 20 章的书,验证 5 个运行时文件
  内容保持不变)
- Phase 5 书二次 revise 从权威源读(验证传给 architect 的 reviseFrom
  是完整 outline + roles 内容,不含 shim 指针)
- revise 清空旧 role 文件(构造 5 个老角色,revise 后只输出 2 个新角色,
  验证另外 3 个旧文件被删除)
- Phase 5 revise 备份带 phase5 tag 且包含 outline/ + roles/

全仓测试 785 passed(core 785 / studio 138 / cli 169)。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant