mirror of
https://github.com/Narcooo/inkos.git
synced 2026-09-01 15:08:51 +08:00
705555faf0
* chore(studio): remove dead BookFormCard create_book toolCall path 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> * feat(studio): 侧边栏摘要卡改用 Streamdown 渲染 markdown 世界观/角色卡之前是 <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> * fix(studio): use-hash-route 在非 hash 页面切换时同步 React state 之前 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> * feat(core): add outline-paths helper for Phase 5 双路径 fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(core): 读架构稿硬路径统一改用 outline-paths helper 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> * feat(core): architect 升级到段落式架构稿,剥离 Phase 7 伏笔扩展列 - 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> * feat(core): state-manager 新建书籍时创建 outline/ 和 roles/ 目录 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> * feat(core): 旧书架构稿升级路径 — architect revise + pipeline.reviseFoundation 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> * feat(core): context-transform 检测旧书时注入升级提示 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> * refactor(test): 新测试里移除硬编码的 maxTokens / temperature 数字 此前 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> * fix(core): 分离 LLMClient maxTokens (fallback) 与 maxTokensCap (cap) 语义 ## 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> * revert(docs): CLAUDE.md 的 maxTokens 设计细节回退到单行索引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> * chore(core): architect writeFoundationFiles 加布局分支 debug log 新建书或 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> * fix(core): reviseFoundation 正确处理运行时文件 / Phase 5 二次升级 / 删除角色清理 ## 三个 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> * fix(core): revise 模式 + legacy 输出时抛错,避免数据混乱 ## Bug corner case 上一个 fix commit (81e6665) 里把 writeFoundationFiles 的 roles 清理逻辑放在 if (isPhase5Output) 分支内,漏掉了一个 corner case:revise 模式下,如果 LLM 意外回退 legacy 输出(storyFrame 为空),会走 else 分支把 LLM 产出的 legacy 内容写到 story_bible.md / volume_outline.md 等 flat 文件,但 outline/ 和 roles/ 保持不变——Phase 5 书立刻进入"outline 新 + shim 老内容 + roles 残留" 的混乱状态,读取端 fallback 读到互相矛盾的内容。 ## 修法 两步: 1. 在 writeFoundationFiles 开头检查:mode === "revise" && !isPhase5Output → 立即抛错,不写任何文件。让用户感知 LLM 响应异常,避免数据默默被破坏。 2. roles 清理逻辑从 if (isPhase5Output) 内部移到外面,变成 mode 级行为。 放在抛错检查之后,保证 revise 场景永远清空 roles(但抛错场景下不会执行 到,因为先抛错了——旧 role 文件保留,加上 runner 里的 backup 双重保险)。 init 模式下 else 分支的 legacy 输出路径保持原样(老 prompt / 测试 stub 的 合法场景)。 ## 测试 revise-foundation.test.ts 新增 "revise 模式 + LLM 意外输出 legacy 格式 → 抛错且不动架构稿文件" 用例: - 构造 Phase 5 书(outline/ + roles/ 有原始内容、story_bible.md 是 shim) - Stub architect 返回 legacy 格式输出(storyFrame 空) - 期望 reviseFoundation 抛错含 "legacy-format output.*NOT been modified" - 验证 outline/story_frame.md、outline/volume_map.md、story_bible.md 内容 保持不变 全仓 786 passed / build 通过。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>