StoryMoss 调试手册
铁律:先建反馈回路再下结论。本项目大多数“卡死/重复/无输出”都是竞态或同步阻塞,肉眼盯代码无效。参考 diagnose 技能 Phase 1 构造回路;本项目特有回路见下文。
修复必须走门禁:下表给出的是历史已验证的修复方向(用于快速分诊),不是允许跳过门禁。任何新修复在合并前必须走
sf-change-control(变更门禁)+sf-validation-and-qa(证据标准,含回归测试)+ 推送门(docs 同步 + CI 必查)。
项目特有的反馈回路构造法
creative_workflow.log时间线对照法(本项目最有效):读<app_data_dir>/logs/creative_workflow.log,把用户感知的“卡住时刻”对齐到日志的阶段标记(genesis.first_chapter.generated/smart_execute.start/trishot.call3.done/trishot.bgp4.spawn/done/llm.record_call.spawn)。卡点 = 时间线断点。v0.26.23 续写卡死就是用creative_workflow.log时间线定位到auto_contract阻塞 6 分钟。注:候选模型实时探测(5s)走标准log::debug!([Gateway] 候选 [N] 实时探测通过/失败/超时),不进creative_workflow.log——查探测去标准日志 sink。- 前端诊断日志:
[DEBUG-dup]/[DEBUG-act]console.warn;frontstage:rich_editor_diag(前 20 帧 + 幽灵状态变化 + 200ms IPC 节流)。 - 诊断卡片:幕前/幕后设置页显示当前模型、最后提示词全文、最后调用模型。
- 纯函数提取 + 单测:竞态路径提取纯函数(
select_first_chapter_content/world_concept_for_character_prompt模式)写契约测试。 - 跨层 golden 双跑:算法 bug 用
tests/fixtures/trim_golden.jsonRust+TS 双跑锁定。 - E2E:仅前端行为;
genesis-duplicate.spec.ts验证自动接受后幽灵段落隐藏。
症状 → 分诊表
| 症状 | 最可能根因 | 区分实验 / 首查 | 优先级 |
|---|---|---|---|
| 续写卡死,正文已返回但界面不动 | 后台 LLM 调用未静默化,进度事件覆盖主活动 | 查 is_silent_background 白名单(llm/service.rs)是否含该 context_label;读日志看是否后台 label 触发前端 activity |
P0 |
| 续写结果新旧竞争 / 幽灵文本混乱 | handleSmartGeneration 重入,旧幽灵未丢弃 |
入口重入守卫:存在未接受幽灵时先丢弃并提示(v0.26.23 Bug D) | P0 |
| 幽灵文本 10s 后才消失 | force-hide-ghost 类移除未触发重渲染 |
RichTextEditor 用 bodyForceHideGhost state 镜像类(v0.26.23 Bug A) |
P1 |
| 新小说第一章内容重复 | 多路径并发叠加:DB 正文 + 幽灵 + smart_execute final_content 竞态;或 LLM 输出本身首尾回环;或 format.ts 正则翻倍 |
先看日志确认是数据层重复、LLM 自重复、还是精确 2× 翻倍(splitChineseSentences lastIndex 重置,v0.26.18);v0.26.14 日志证实数据层只追加一次,重复来自 LLM 正文自身 |
P0 |
| 第一章字数精确翻倍(≈2×) | src-frontend/src/utils/format.ts splitChineseSentences 全局正则 lastIndex 在 exec 返回 null 时重置为 0,循环外读取导致整段被当末句再 push |
看日志 afterLen ≈ 2×beforeLen;修复在循环内捕获 lastIndex(v0.26.18) |
P0 |
| 第一章 LLM 输出首尾段落相同 | 模型自重复 | trimSelfRepetition/trim_self_repetition(KMP 最长 border,≥30 字且 ≥8% 裁尾);生成侧 8% 闸门重试 |
P0 |
生成无输出 / 空 {} JSON |
推理模型思考链里花括号被 find('{') 误判 |
strip_reasoning_blocks + extract_first_json_object 跳过空对象(v0.23.49) |
P1 |
| JSON 解析 trailing characters | rfind('}') 在 JSON 后含 } 文本时误提取 |
括号匹配精确提取(v0.23.48) | P1 |
| 600s 超时 | record_llm_call DB 写入阻塞 tokio worker / pool.get() 无限阻塞 |
连接池 .connection_timeout(5s);record_llm_call 改 spawn_blocking fire-and-forget(v0.23.19) |
P0 |
| select_candidates / Call3 必死锁 | std::sync::Mutex 不可重入,持锁期间再 lock |
health 锁移入嵌套块作用域(v0.23.34);中毒锁 unwrap_or_else(|e| e.into_inner()) |
P0 |
| BGP-4 卡住 / 自死锁 | spawn_blocking().await 等 DB 与 BGP-1/3 竞争同 Mutex |
改 tokio::spawn fire-and-forget(v0.23.42) |
P0 |
术语:BGP = Background Pipeline(后台管线阶段,BGP-1/2/3/4 为创世后台的 4 个阶段,如 BGP-2 自动改写、BGP-3 后台 Ingest、BGP-4 后台审计+洞察)。 | macOS 启动 panic
state() called before manage()| VectorStore State 注入顺序 |LanceVectorStore创建与app.manage提前(v0.23.6) | P0 | | 启动 panic / Windows 闪退(Issue #4) |init_db不可写目录失败;GatewayExecutor::new读未 manage 的 pool |setup显式传 pool,仅 pool 可用时初始化(v0.26.16);打包迁移资源(v0.26.17) | P0 | | 幕前白屏 |useCharacters返回 null 时访问.length| null 防护(v0.26.12);ErrorBoundary增强诊断 | P1 | | React #185 无限循环 |FrontstageApppipeline-complete effect 回调不稳定化 | 回调稳定化 + Zustand selector 同步 isGenerating(v0.26.2–.7) | P0 | | Windows MSI 构建挂掉 | 迁移文件名含中文/全角/破折号/过长 | 重命名 ASCII 短名(v0.26.21) | P0 | | macOS 公证失败 | 协议过期 / 证书密码尾随空格 | 续签;Secret 必须 28 字符无尾随 | P0(外部) | | CIcargo +nightly fmt -- --check跨平台不一致 |format_stringsmacOS/Win nightly 不同 | 不要启用format_strings| P1 | | 本地模型并发崩溃页面空白 | IngestPipeline 并发 LLM 未静默 | 三个 label 加入is_silent_background(v0.23.45) | P0 |
卡时间的陷阱(每个都真实踩过)
- 盯代码找竞态:第一章重复 saga 从 v0.26.7 到 v0.26.16 反复 9 次,每次都“看起来修了”。根因是单一基准(
editorRef.getText())滞后 DOM + 多写者并发。教训:竞态必须有单写者状态机 + 双重基准,散布布尔守卫必复发。 - 不读日志就猜:v0.26.14 用户感知“第一章重复”,不读日志会以为是前端追加了两次;日志证实数据层只追加一次,重复来自 LLM 正文自身首尾回环。
- 把后台当同步:后台 LLM/DB 调用同步化是本项目卡死的最常见根因(600s、BGP-4、Ingest、auto_contract)。
- 改 fmt 配置:动
rustfmt.toml的format_strings会引入跨平台 CI 差异。
何时 NOT 用本技能
- 通用调试纪律(建回路→复现→假设→仪器化→修+回归)→
diagnose技能。 - 已修复战役的完整编年史 →
sf-failure-archaeology。 - 架构不变量 →
sf-architecture-contract。 - 诊断工具具体用法 →
sf-diagnostics-and-tooling。
出处与维护
- 重验证命令:
rg -n 'is_silent_background|BACKGROUND_LLM_SEMAPHORE|connection_timeout' src-tauri/src/llmrg -n 'spawn_blocking|tokio::spawn' src-tauri/src/llm src-tauri/src/task_system | headls tests/fixtures/(golden 在仓库根 tests/,非 src-tauri/)git log --oneline --all | rg 'fix|revert|重复|卡死|死锁|panic' | head -30
- 易漂移项:白名单 label、超时常量、State 注入顺序、rustfmt 配置。
- 最后核对:2026-07-07,v0.26.23。