@cloud411716/fancy-webnovel 2.2.0 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/client.js +201 -15
- package/cordis.patch.yml +5 -1
- package/docs/douyin-plugin.md +118 -0
- package/docs/material-library/README.md +404 -0
- package/docs/material-library/import.js +437 -0
- package/docs/material-library/prompt.md +370 -0
- package/docs/material-library/queries.surql +94 -0
- package/docs/material-library/sample.jsonl +14 -0
- package/docs/material-library/schema.surql +46 -0
- package/docs/mcp-profile.example.yml +24 -0
- package/docs/story-graph-design.md +602 -0
- package/docs/story-graph-validation.md +81 -0
- package/docs/workspace-mcp.md +284 -0
- package/index.js +12 -7
- package/lib/atomic-write.js +109 -0
- package/lib/chapter-order.js +100 -0
- package/lib/confirmation.js +80 -0
- package/lib/db-init.js +123 -0
- package/lib/deployed-state.js +11 -11
- package/lib/direction-labels.js +265 -0
- package/lib/error-codes.js +31 -31
- package/lib/infra.js +29 -4
- package/lib/material-db.js +149 -0
- package/lib/path-guard.js +150 -0
- package/lib/projects.js +400 -0
- package/lib/scraper-utils.js +140 -0
- package/lib/surreal-server.js +338 -0
- package/lib/topic-decision.js +860 -0
- package/lib/topic-fields.js +251 -0
- package/lib/topic-schema.js +225 -0
- package/lib/workspace.js +501 -0
- package/package.json +31 -7
- package/plugins/fancy-analyze/analyze-definitions.md +519 -0
- package/plugins/fancy-analyze/index.js +1128 -77
- package/plugins/fancy-analyze/ingest-coverage.js +107 -0
- package/plugins/fancy-analyze/schema-analyze.md +355 -107
- package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
- package/plugins/fancy-analyze/summary-progress.js +71 -7
- package/plugins/fancy-analyze/templates.js +437 -139
- package/plugins/fancy-bootstrap/index.js +276 -131
- package/plugins/fancy-bootstrap/scripts/init.js +59 -25
- package/plugins/fancy-bootstrap/templates.js +35 -18
- package/plugins/fancy-db/index.js +172 -0
- package/plugins/fancy-douyin/deploy.js +134 -0
- package/plugins/fancy-douyin/index.js +551 -0
- package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
- package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
- package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
- package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
- package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
- package/plugins/fancy-douyin/state.js +197 -0
- package/plugins/fancy-douyin/templates.js +127 -0
- package/plugins/fancy-export/exporter.js +805 -0
- package/plugins/fancy-export/index.js +63 -0
- package/plugins/fancy-import/import-driver.js +29 -6
- package/plugins/fancy-import/index.js +13 -5
- package/plugins/fancy-import/prompts/01-import-prompt.md +39 -14
- package/plugins/fancy-import/templates.js +17 -8
- package/plugins/fancy-material/exporter.js +153 -0
- package/plugins/fancy-material/genres.js +256 -0
- package/plugins/fancy-material/index.js +835 -0
- package/plugins/fancy-material/inventory.js +145 -0
- package/plugins/fancy-material/kinds.js +313 -0
- package/plugins/fancy-material/queue.js +55 -0
- package/plugins/fancy-material/scripts/query.js +254 -0
- package/plugins/fancy-material/templates.js +258 -0
- package/plugins/fancy-material/tool-deploy.js +138 -0
- package/plugins/fancy-material/validate.js +149 -0
- package/plugins/fancy-project/index.js +133 -0
- package/plugins/fancy-scan/existing-block.js +100 -0
- package/plugins/fancy-scan/index.js +410 -21
- package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
- package/plugins/fancy-scan/scripts/append-block.js +725 -0
- package/plugins/fancy-scan/scripts/report-validate.js +824 -0
- package/plugins/fancy-scan/scripts/run-scan.js +169 -22
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +145 -16
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +179 -49
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +38 -20
- package/plugins/fancy-scan/templates/blocks.js +155 -0
- package/plugins/fancy-scan/templates/platforms.js +372 -0
- package/plugins/fancy-scan/templates/report-schema.js +90 -0
- package/plugins/fancy-scan/templates.js +545 -146
- package/plugins/fancy-scan/tool-deploy.js +335 -0
- package/plugins/fancy-scan/wait-turn.js +46 -0
- package/plugins/fancy-story-builder/db/README.md +106 -0
- package/plugins/fancy-story-builder/db/client.js +228 -0
- package/plugins/fancy-story-builder/db/commands.js +145 -0
- package/plugins/fancy-story-builder/db/ids.js +39 -0
- package/plugins/fancy-story-builder/db/import.js +798 -0
- package/plugins/fancy-story-builder/db/project.js +299 -0
- package/plugins/fancy-story-builder/db/query.js +361 -0
- package/plugins/fancy-story-builder/db/schema.js +417 -0
- package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
- package/plugins/fancy-story-builder/flesh-output.js +19 -6
- package/plugins/fancy-story-builder/index.js +4105 -396
- package/plugins/fancy-story-builder/patch-driver.js +390 -0
- package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +29 -19
- package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +72 -10
- package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +40 -5
- package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +37 -0
- package/plugins/fancy-story-builder/purpose-template.md +42 -66
- package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -56
- package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
- package/plugins/fancy-story-builder/references/glossary.md +15 -6
- package/plugins/fancy-story-builder/references/index.md +97 -6
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +1 -1
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +25 -7
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +5 -1
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +6 -2
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +15 -5
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +1 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +2 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +1 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +19 -1
- package/plugins/fancy-story-builder/schema-setting.json +1100 -0
- package/plugins/fancy-story-builder/schema-setting.md +201 -293
- package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
- package/plugins/fancy-story-builder/scripts/content-quality.js +4 -1
- package/plugins/fancy-story-builder/scripts/flesh-validate.js +135 -12
- package/plugins/fancy-story-builder/scripts/graph-utils.js +146 -47
- package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
- package/plugins/fancy-story-builder/scripts/rhythm-validate.js +100 -4
- package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
- package/plugins/fancy-story-builder/scripts/semantic-lint.js +5 -1
- package/plugins/fancy-story-builder/scripts/skeleton-validate.js +158 -17
- package/plugins/fancy-story-builder/scripts/stage-batches.js +36 -2
- package/plugins/fancy-story-builder/scripts/stage-contract.js +12 -0
- package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
- package/plugins/fancy-story-builder/scripts/upstream-query.js +1 -1
- package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
- package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
- package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
- package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
- package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
- package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
- package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
- package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
- package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
- package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
- package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
- package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
- package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
- package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
- package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
- package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
- package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
- package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
- package/plugins/fancy-story-builder/storydb/README.md +271 -0
- package/plugins/fancy-story-builder/storydb/cli.js +458 -0
- package/plugins/fancy-story-builder/storydb/client.js +351 -0
- package/plugins/fancy-story-builder/storydb/compile.js +452 -0
- package/plugins/fancy-story-builder/storydb/context.js +288 -0
- package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
- package/plugins/fancy-story-builder/storydb/driver.js +106 -0
- package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
- package/plugins/fancy-story-builder/storydb/export.js +175 -0
- package/plugins/fancy-story-builder/storydb/ids.js +31 -0
- package/plugins/fancy-story-builder/storydb/impact.js +170 -0
- package/plugins/fancy-story-builder/storydb/import.js +559 -0
- package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
- package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
- package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
- package/plugins/fancy-story-builder/storydb/stage.js +426 -0
- package/plugins/fancy-story-builder/storydb/store.js +891 -0
- package/plugins/fancy-story-builder/storydb/tools.js +592 -0
- package/plugins/fancy-story-builder/storydb/validation.js +183 -0
- package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
- package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
- package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
- package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
- package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +267 -44
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +121 -19
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +129 -21
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/266/344/273/226/345/256/236/344/275/223/346/250/241/346/235/277.json +62 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +76 -17
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +56 -8
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +63 -13
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +34 -6
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +81 -18
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +202 -42
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +160 -27
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +217 -41
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +215 -41
- package/plugins/fancy-story-builder/templates.js +272 -74
- package/plugins/fancy-story-builder/wiki-references.js +429 -93
- package/plugins/fancy-story-builder/writing-driver.js +336 -9
- package/plugins/fancy-topic/index.js +414 -61
- package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
- package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
- package/plugins/fancy-topic/scripts/select-topic.js +106 -63
- package/plugins/fancy-topic/templates.js +225 -122
- package/plugins/fancy-topic/tool-deploy.js +200 -0
- package/lib/safe-rm.js +0 -94
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
> 2026-11 架构更新:项目已改为“工作区 A + projects/<uuid> + 共享 SurrealDB server”。本文关于故事图的设计仍然有效,但连接方式和目录布局以 [workspace-mcp.md](./workspace-mcp.md) 为准。
|
|
2
|
+
|
|
3
|
+
# story-graph:DB 工作台设计稿
|
|
4
|
+
|
|
5
|
+
> 分支:`feat/story-graph`
|
|
6
|
+
> 目标:把 `fancy-story-builder` 从“LLM 写 JSON、脚本导入 DB”改成“LLM 直接在故事图上工作”。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 0. 一句话
|
|
11
|
+
|
|
12
|
+
**LLM 不再生成阶段文件,而是通过工具读写一张带生命周期、待办占位和事务边界的故事图。**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. 当前方案的问题
|
|
17
|
+
|
|
18
|
+
| 问题 | 现状 | 后果 |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| 二段式写入 | LLM 写 JSON → 脚本导入 DB | 失败晚、原因不返回给 LLM、返工大 |
|
|
21
|
+
| 上下文靠读文件 | 每个 chunk 注入整本书摘要 | token 浪费、容易压缩失忆 |
|
|
22
|
+
| 校验是批处理 | 全书写完才跑 | 500 章后才发现 579 个错误 |
|
|
23
|
+
| 缺细节 = 失败 | 字段不齐就报错 | 无法“先立伏笔,后补细节” |
|
|
24
|
+
| 决策日志 append-only | `.reference-decisions.md` 只追加 | 返工后残留旧决策,无法回滚 |
|
|
25
|
+
| 阶段僵化 | scale→macro→cast→rhythm→… | LLM 不能按“当前缺什么”工作 |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. 目标与非目标
|
|
30
|
+
|
|
31
|
+
### 目标
|
|
32
|
+
|
|
33
|
+
1. **单一写路径**:LLM → 工具 → DB,没有 JSON 中转。
|
|
34
|
+
2. **写入即校验**:硬约束当场拒绝,错误原文返回给 LLM。
|
|
35
|
+
3. **按需查询**:想知道某个伏笔,只取那条伏笔和它的关系,不读整份文件。
|
|
36
|
+
4. **渐进完整**:允许 `seed` 占位,缺细节是 `pending`,不是 `error`。
|
|
37
|
+
5. **事务化决策**:决策和数据在同一个事务里提交/回滚;返工不会残留旧决策。
|
|
38
|
+
6. **图优先**:关系、时间线、影响分析都是图查询。
|
|
39
|
+
7. **阶段退化成工作流**:数据模型不再绑定 scale/macro/cast。
|
|
40
|
+
|
|
41
|
+
### 非目标
|
|
42
|
+
|
|
43
|
+
- 不做通用低代码平台;只服务小说构建。
|
|
44
|
+
- 不把文风/节奏/“AI 味”写成数据库触发器;这些是 warning/LLM 审校。
|
|
45
|
+
- 首版不做多用户权限和分布式;嵌入式单进程。
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. 核心模型
|
|
50
|
+
|
|
51
|
+
### 3.1 节点
|
|
52
|
+
|
|
53
|
+
| 类型 | 说明 | 生命周期 |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `story` | 故事根 | seed → active → locked |
|
|
56
|
+
| `volume` / `act` / `chapter` | 结构骨架 | seed → draft → complete |
|
|
57
|
+
| `character` / `faction` / `location` / `item` | 实体 | seed → draft → complete |
|
|
58
|
+
| `conflict` / `foreshadow` / `payoff` / `climax` / `information` | 叙事实体 | seed → draft → complete |
|
|
59
|
+
| `outline` / `scene` | 章节计划 | seed → draft → complete |
|
|
60
|
+
| `chapter_text` | 正文 | draft → final |
|
|
61
|
+
| `requirement` | 待办/占位 | pending → filled / waived |
|
|
62
|
+
| `change_set` | 一次写入事务 | committed / rolled_back / superseded |
|
|
63
|
+
| `decision` | 设计决策/来源 | active / superseded / rolled_back |
|
|
64
|
+
| `validation_issue` | 校验结果 | open / resolved / ignored |
|
|
65
|
+
|
|
66
|
+
### 3.2 边
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
story -contains-> volume -contains-> act -contains-> chapter
|
|
70
|
+
chapter -chapter_cast-> character (role: pov/cast/mentioned)
|
|
71
|
+
chapter -chapter_location-> location
|
|
72
|
+
character -relation-> character (kind, from_chapter, to_chapter)
|
|
73
|
+
faction -faction_relation-> faction
|
|
74
|
+
character -membership-> faction
|
|
75
|
+
conflict -conflict_participant-> character
|
|
76
|
+
climax -climax_participant-> character
|
|
77
|
+
chapter -foreshadow_action-> foreshadow (plant/progress/reveal)
|
|
78
|
+
chapter -payoff_action-> payoff (setup/payoff)
|
|
79
|
+
chapter -info_action-> information (created/progressed/resolved)
|
|
80
|
+
chapter -ending_action-> ending
|
|
81
|
+
requirement -requirement_for-> subject
|
|
82
|
+
decision -decision_about-> subject
|
|
83
|
+
change_set -supersedes-> change_set
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`knowledge_event` 是事件表,不直接做边;它链接 `chapter`、`character`、`subject`。
|
|
87
|
+
|
|
88
|
+
### 3.3 状态不是装饰
|
|
89
|
+
|
|
90
|
+
`status` 决定“缺字段算不算错误”:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
seed 只要求 id + name;缺细节是正常的
|
|
94
|
+
draft 关键字段开始补;允许 pending
|
|
95
|
+
complete 所有 hard requirement 必须 filled
|
|
96
|
+
locked 不允许普通更新,只能新 change_set 覆盖
|
|
97
|
+
archived 历史版本,不参与默认查询
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 4. 占位与渐进完整
|
|
103
|
+
|
|
104
|
+
### 4.1 requirement 节点
|
|
105
|
+
|
|
106
|
+
不把“缺失”藏在空字段里,而是显式建待办:
|
|
107
|
+
|
|
108
|
+
```surql
|
|
109
|
+
DEFINE TABLE requirement SCHEMAFULL;
|
|
110
|
+
DEFINE FIELD story ON requirement TYPE record<story>;
|
|
111
|
+
DEFINE FIELD kind ON requirement TYPE string;
|
|
112
|
+
DEFINE FIELD subject ON requirement TYPE string; -- "foreshadow:FS012"
|
|
113
|
+
DEFINE FIELD status ON requirement TYPE string
|
|
114
|
+
ASSERT $value IN ['pending','filled','waived'];
|
|
115
|
+
DEFINE FIELD due_chapter ON requirement TYPE option<int>;
|
|
116
|
+
DEFINE FIELD filled_by ON requirement TYPE option<string>;
|
|
117
|
+
DEFINE FIELD change_set ON requirement TYPE record<change_set>;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4.2 伏笔例子
|
|
121
|
+
|
|
122
|
+
创建时只写:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{ "id": "FS012", "name": "空白的来源栏", "status": "seed" }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
工具自动建:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
foreshadow_plant pending due 341
|
|
132
|
+
foreshadow_progress pending due 360
|
|
133
|
+
foreshadow_reveal pending due 400
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
后续:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
story.link foreshadow_action chapter:CH341 foreshadow:FS012 {action: plant}
|
|
140
|
+
→ event 把 foreshadow_plant 标成 filled
|
|
141
|
+
→ 仍然 pending: progress, reveal
|
|
142
|
+
→ story.validate 返回 pending,不是 error
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
当 LLM 决定完成:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
story.update foreshadow FS012 {status: complete}
|
|
149
|
+
→ 校验器检查:还有 pending 就报 error
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 4.3 为什么不用“特殊字符占位”
|
|
153
|
+
|
|
154
|
+
- 特殊字符会被当成数据,查询/校验都要特殊处理。
|
|
155
|
+
- `requirement` 是图上的真实节点:可查、可排序、可设 due、可 waive、可追溯 filled_by。
|
|
156
|
+
- 以后要加“章节细纲待办”“角色弧光待办”,只是多一种 `kind`,不用改 schema。
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 5. 事务与决策:解决 `.reference-decisions.md` 残留
|
|
161
|
+
|
|
162
|
+
### 5.1 核心原则
|
|
163
|
+
|
|
164
|
+
**决策不是日志文件,是数据的一部分;它必须和数据在同一个事务里。**
|
|
165
|
+
|
|
166
|
+
### 5.2 change_set
|
|
167
|
+
|
|
168
|
+
```surql
|
|
169
|
+
DEFINE TABLE change_set SCHEMAFULL;
|
|
170
|
+
DEFINE FIELD story ON change_set TYPE record<story>;
|
|
171
|
+
DEFINE FIELD workflow ON change_set TYPE string; -- cast/rhythm/manual
|
|
172
|
+
DEFINE FIELD status ON change_set TYPE string
|
|
173
|
+
ASSERT $value IN ['committed','rolled_back','superseded'];
|
|
174
|
+
DEFINE FIELD parent ON change_set TYPE option<record<change_set>>;
|
|
175
|
+
DEFINE FIELD summary ON change_set TYPE string;
|
|
176
|
+
DEFINE FIELD created_at ON change_set TYPE datetime VALUE time::now();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
一次 `story.apply(ops, decisions)`:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
BEGIN
|
|
183
|
+
CREATE change_set
|
|
184
|
+
apply 所有 ops
|
|
185
|
+
CREATE decision[],全部指向这个 change_set
|
|
186
|
+
DB 约束/事件校验
|
|
187
|
+
COMMIT
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
任何一条失败:
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
ROLLBACK
|
|
194
|
+
→ 数据没有
|
|
195
|
+
→ 决策也没有
|
|
196
|
+
→ 不会出现“数据回滚了、决策留下”的残留
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 5.3 decision
|
|
200
|
+
|
|
201
|
+
```surql
|
|
202
|
+
DEFINE TABLE decision SCHEMAFULL;
|
|
203
|
+
DEFINE FIELD story ON decision TYPE record<story>;
|
|
204
|
+
DEFINE FIELD change_set ON decision TYPE record<change_set>;
|
|
205
|
+
DEFINE FIELD scope ON decision TYPE string; -- cast/rhythm/chapter:CH341
|
|
206
|
+
DEFINE FIELD subject ON decision TYPE string; -- "foreshadow:FS012"
|
|
207
|
+
DEFINE FIELD text ON decision TYPE string;
|
|
208
|
+
DEFINE FIELD source ON decision TYPE option<string>;
|
|
209
|
+
DEFINE FIELD reference ON decision TYPE option<string>;
|
|
210
|
+
DEFINE FIELD reason ON decision TYPE option<string>;
|
|
211
|
+
DEFINE FIELD status ON decision TYPE string
|
|
212
|
+
ASSERT $value IN ['active','superseded','rolled_back'];
|
|
213
|
+
DEFINE FIELD supersedes ON decision TYPE option<record<decision>>;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 5.4 返工
|
|
217
|
+
|
|
218
|
+
返工不是“再追加一行”,而是新 change_set:
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
change_set#2 (parent = #1)
|
|
222
|
+
ops: 覆盖 chapter:CH341 的 outline
|
|
223
|
+
decisions: 新决策
|
|
224
|
+
旧 decisions: status = superseded, supersedes 指向新决策
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
默认查询只取 `status='active'` 的决策。历史仍可查:
|
|
228
|
+
|
|
229
|
+
```text
|
|
230
|
+
story.why foreshadow:FS012 → 当前生效的决策
|
|
231
|
+
story.history foreshadow:FS012 → 所有版本,含 superseded/rolled_back
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 5.5 `.reference-decisions.md`
|
|
235
|
+
|
|
236
|
+
从“事实源”降级为“导出物”:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
storydb export decisions
|
|
240
|
+
→ 只导出 active 决策,按 scope 分组
|
|
241
|
+
→ 文件可删、可重建,不再是 append-only 日志
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 6. Schema 编译器
|
|
247
|
+
|
|
248
|
+
一份定义,生成五样东西,避免漂移:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
schema/definitions.js
|
|
252
|
+
│
|
|
253
|
+
├─ generated/schema.surql DDL
|
|
254
|
+
├─ generated/tools.json LLM 工具参数 JSON Schema
|
|
255
|
+
├─ generated/schema-card.md LLM 看的紧凑 schema
|
|
256
|
+
├─ generated/validators.js 字段/枚举/必填
|
|
257
|
+
└─ generated/export-md.js Markdown 导出映射
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
定义示例:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
foreshadow: {
|
|
264
|
+
table: 'foreshadow',
|
|
265
|
+
idPrefix: 'FS',
|
|
266
|
+
fields: {
|
|
267
|
+
name: { type: 'string', required: true },
|
|
268
|
+
type: { type: 'enum', values: ['small', 'medium', 'large'] },
|
|
269
|
+
status: { type: 'enum', values: ['seed', 'draft', 'complete'], default: 'seed' },
|
|
270
|
+
scope: { type: 'enum', values: ['chapter', 'volume', 'book'] },
|
|
271
|
+
setup: { type: 'text' },
|
|
272
|
+
},
|
|
273
|
+
requirements: [
|
|
274
|
+
{ kind: 'plant', due: 'chapter', hard: true },
|
|
275
|
+
{ kind: 'progress', hard: false },
|
|
276
|
+
{ kind: 'reveal', due: 'chapter', hard: true },
|
|
277
|
+
],
|
|
278
|
+
edges: {
|
|
279
|
+
foreshadow_action: {
|
|
280
|
+
in: 'chapter', out: 'foreshadow',
|
|
281
|
+
fields: {
|
|
282
|
+
action: { enum: ['plant', 'progress', 'reveal'] },
|
|
283
|
+
chapter_no: { type: 'int' },
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
LLM 看到的是 schema card:
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
foreshadow
|
|
294
|
+
id: string, 必填, 唯一(FS001)
|
|
295
|
+
name: string, 必填
|
|
296
|
+
type: enum[small, medium, large]
|
|
297
|
+
status: enum[seed, draft, complete]
|
|
298
|
+
scope: enum[chapter, volume, book]
|
|
299
|
+
边: chapter -foreshadow_action-> foreshadow
|
|
300
|
+
action: enum[plant, progress, reveal]
|
|
301
|
+
待办: plant / progress / reveal
|
|
302
|
+
示例: {"id":"FS012","name":"空白的来源栏","type":"medium","status":"seed"}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 7. 工具层
|
|
308
|
+
|
|
309
|
+
首版用 CLI(LLM 通过 bash 调用),后续包成 MCP。
|
|
310
|
+
|
|
311
|
+
```text
|
|
312
|
+
storydb schema [table]
|
|
313
|
+
storydb get <kind> <id> [--fields a,b,c]
|
|
314
|
+
storydb search <kind> [--where k=v] [--limit N]
|
|
315
|
+
storydb context --chapter N | --entity kind:id | --chapters a-b
|
|
316
|
+
storydb create <kind> '<json>'
|
|
317
|
+
storydb update <kind> <id> '<json>'
|
|
318
|
+
storydb link <edge> <from> <to> '<json>'
|
|
319
|
+
storydb unlink <edge> <from> <to>
|
|
320
|
+
storydb apply '<json>' # 批量 ops + decisions,一个事务
|
|
321
|
+
storydb validate [--scope ...]
|
|
322
|
+
storydb next [--limit N]
|
|
323
|
+
storydb why <kind>:<id>
|
|
324
|
+
storydb history <kind>:<id>
|
|
325
|
+
storydb query '<read-only SurrealQL>'
|
|
326
|
+
storydb export md <stage|all>
|
|
327
|
+
storydb export decisions
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 返回格式
|
|
331
|
+
|
|
332
|
+
成功:
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{ "ok": true, "change_set": "change_set:...", "ids": ["foreshadow:FS012"] }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
失败:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"ok": false,
|
|
343
|
+
"error": {
|
|
344
|
+
"code": "KNOWLEDGE_ORDER",
|
|
345
|
+
"table": "knowledge_event",
|
|
346
|
+
"field": "change",
|
|
347
|
+
"message": "C001 已经知道 FS034,不能再写成 learns",
|
|
348
|
+
"hint": "改用 confirms,或先写 forgets/corrects"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 权限
|
|
354
|
+
|
|
355
|
+
- `create/update/link` 只允许故事表。
|
|
356
|
+
- 禁止 DDL、用户、权限、`REMOVE`。
|
|
357
|
+
- `query` 只读。
|
|
358
|
+
- 每次写入必须带 `change_set`,可追溯。
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 8. 上下文组装
|
|
363
|
+
|
|
364
|
+
`story.context` 是省 token 的核心。
|
|
365
|
+
|
|
366
|
+
### 8.1 写章节时
|
|
367
|
+
|
|
368
|
+
```text
|
|
369
|
+
story.context --chapter 341
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
返回:
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
{
|
|
376
|
+
"chapter": { "id": "CH341", "title": "...", "act": "A012", "pov": "C001" },
|
|
377
|
+
"cast": [{ "id": "C001", "name": "沈砚" }, { "id": "C002", "name": "韩雨棠" }],
|
|
378
|
+
"locations": [{ "id": "L001", "name": "旧堤泵房" }],
|
|
379
|
+
"foreshadows_due": [
|
|
380
|
+
{ "id": "FS012", "name": "空白的来源栏", "status": "seed", "next": "reveal", "due": 400 }
|
|
381
|
+
],
|
|
382
|
+
"payoffs_due": [],
|
|
383
|
+
"knowledge": [{ "character": "C001", "subject": "FS012", "state": "partial" }],
|
|
384
|
+
"continuity": { "prev": "CH340 重建的第一课是修配额", "next": "CH342 ..." },
|
|
385
|
+
"requirements": [{ "kind": "chapter_outline", "status": "pending" }]
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
只给相关实体,不给全书 88 个伏笔。
|
|
390
|
+
|
|
391
|
+
### 8.2 查伏笔时
|
|
392
|
+
|
|
393
|
+
```text
|
|
394
|
+
story.get foreshadow FS012
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
返回:
|
|
398
|
+
|
|
399
|
+
```json
|
|
400
|
+
{
|
|
401
|
+
"id": "FS012",
|
|
402
|
+
"name": "空白的来源栏",
|
|
403
|
+
"status": "draft",
|
|
404
|
+
"timeline": [{ "chapter": 341, "action": "plant" }],
|
|
405
|
+
"pending": ["reveal"],
|
|
406
|
+
"linked_payoffs": ["PL072"],
|
|
407
|
+
"knowledge": [{ "character": "C001", "state": "partial" }]
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 8.3 token 预算
|
|
412
|
+
|
|
413
|
+
- `context` 默认上限 8k tokens;超了按优先级裁剪:chapter → cast → due foreshadows → knowledge → continuity。
|
|
414
|
+
- `get` 默认只返回核心字段 + 关系 ID,不返回长文本。
|
|
415
|
+
- `search` 默认只返回 `id/name/status`。
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 9. 校验分层
|
|
420
|
+
|
|
421
|
+
| 层 | 机制 | 失败后果 | 例子 |
|
|
422
|
+
|---|---|---|---|
|
|
423
|
+
| **硬约束** | `ASSERT` / `EVENT` / 事务 | 写入失败,整批回滚 | 关系端点不存在;reveal 早于 plant;`full` 再 learns |
|
|
424
|
+
| **待办** | `requirement` | 写入成功,`next` 列出 | FS012 还差 reveal;CH341 还差细纲 |
|
|
425
|
+
| **质量** | 查询 → `validation_issue` | 写入成功,warning | 情绪平流;黄金三章;钩子重复;POV 密度 |
|
|
426
|
+
|
|
427
|
+
`story.validate --scope chapter:CH341` 只返回本章相关问题:
|
|
428
|
+
|
|
429
|
+
```json
|
|
430
|
+
{
|
|
431
|
+
"errors": [],
|
|
432
|
+
"pending": [{ "kind": "chapter_outline", "status": "pending" }],
|
|
433
|
+
"warnings": [{ "code": "EMOTION_FLAT", "message": "第 339-341 章情绪值连续相同" }]
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
关键:
|
|
438
|
+
|
|
439
|
+
- **缺细节不是错误**,是 pending。
|
|
440
|
+
- **质量问题不阻断**,只提示。
|
|
441
|
+
- **只有事实/状态机错误才回滚。**
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 10. 工作流(阶段退化成批量操作)
|
|
446
|
+
|
|
447
|
+
```text
|
|
448
|
+
workflow.scale
|
|
449
|
+
→ 创建 story/volume/act + 配额 + story.requirements
|
|
450
|
+
|
|
451
|
+
workflow.macro
|
|
452
|
+
→ 创建 world/story/line/line_node/ending + requirements
|
|
453
|
+
|
|
454
|
+
workflow.cast
|
|
455
|
+
→ 创建 character/faction/location/item/conflict/foreshadow/payoff/information
|
|
456
|
+
→ 建 relation/membership/foreshadow_payoff 边
|
|
457
|
+
→ 给每个实体建 requirement
|
|
458
|
+
|
|
459
|
+
workflow.rhythm
|
|
460
|
+
→ 批量创建 chapter + chapter.requirements
|
|
461
|
+
→ 建 chapter_cast/location/foreshadow_action/payoff_action/info_action
|
|
462
|
+
→ 写 knowledge_event
|
|
463
|
+
|
|
464
|
+
workflow.outline
|
|
465
|
+
→ 填充 chapter_outline requirement
|
|
466
|
+
→ 创建 outline/scene
|
|
467
|
+
|
|
468
|
+
workflow.writing
|
|
469
|
+
→ 填充 chapter_draft requirement
|
|
470
|
+
→ 创建 chapter_text/ledger
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
但它们不再是“必须按顺序通过的门”,而是**批量 seed/填充**。LLM 也可以直接说:
|
|
474
|
+
|
|
475
|
+
```text
|
|
476
|
+
story.next
|
|
477
|
+
→ 当前最该补的是 CH341 的细纲和 FS012 的 reveal
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## 11. LLM 交互协议
|
|
483
|
+
|
|
484
|
+
### 系统提示词只放三样
|
|
485
|
+
|
|
486
|
+
1. 角色和硬规则(不写库外文件、只通过 storydb、每次写入必须带决策)。
|
|
487
|
+
2. 工具清单 + 何时用哪个。
|
|
488
|
+
3. 一张总览 schema card(表名 + 关键字段 + 边),详细字段按需 `storydb schema <table>`。
|
|
489
|
+
|
|
490
|
+
### 一次典型回合
|
|
491
|
+
|
|
492
|
+
```text
|
|
493
|
+
LLM: storydb context --chapter 341
|
|
494
|
+
工具: 返回本章相关子图(2-5KB)
|
|
495
|
+
|
|
496
|
+
LLM: storydb create outline '{"chapter":"CH341","status":"draft",...}'
|
|
497
|
+
工具: ok 或具体字段错误
|
|
498
|
+
|
|
499
|
+
LLM: storydb link chapter_cast chapter:CH341 character:C001 '{"role":"pov"}'
|
|
500
|
+
工具: ok
|
|
501
|
+
|
|
502
|
+
LLM: storydb create knowledge_event '{...}'
|
|
503
|
+
工具: error KNOWLEDGE_ORDER,附 hint
|
|
504
|
+
|
|
505
|
+
LLM: 改 change/coverage,重新 create
|
|
506
|
+
工具: ok
|
|
507
|
+
|
|
508
|
+
LLM: storydb validate --scope chapter:CH341
|
|
509
|
+
工具: pending: [] / warnings: [...]
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### 重试策略
|
|
513
|
+
|
|
514
|
+
- 硬错误:同一操作最多 3 次,错误原文在 tool result 里。
|
|
515
|
+
- pending:不重试,留给 `story.next`。
|
|
516
|
+
- warning:不阻断,记录到 `validation_issue`。
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## 12. 实施计划
|
|
521
|
+
|
|
522
|
+
### P0:地基
|
|
523
|
+
|
|
524
|
+
1. `schema/definitions.js` + 编译器(DDL/tools/schema card/validators)。
|
|
525
|
+
2. `storydb` CLI:`schema/get/create/update/link/validate/next/context/apply/query`.
|
|
526
|
+
3. `change_set` + `decision` 事务模型。
|
|
527
|
+
4. `requirement` 模型 + `next` 查询。
|
|
528
|
+
5. 单测:编译、事务回滚、决策不残留、requirement 生命周期。
|
|
529
|
+
|
|
530
|
+
### P1:纵向切片(cast + rhythm)
|
|
531
|
+
|
|
532
|
+
1. `workflow.cast`:seed 实体 + 边 + requirements。
|
|
533
|
+
2. `workflow.rhythm`:seed chapter + actions + knowledge events。
|
|
534
|
+
3. LLM 用 `context` + `apply` 写 5 章一个小块;DB 错误当场返回。
|
|
535
|
+
4. 对比 token/失败率与当前 `feat/surrealdb`。
|
|
536
|
+
|
|
537
|
+
### P2:scale / macro / outline / writing
|
|
538
|
+
|
|
539
|
+
按同一套节点/边/requirement 接。
|
|
540
|
+
|
|
541
|
+
### P3:影响分析、MCP、Markdown 导出
|
|
542
|
+
|
|
543
|
+
- `storydb impact relation/foreshadow`。
|
|
544
|
+
- MCP server 包装 CLI。
|
|
545
|
+
- `export md` 从 DB 生成。
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 13. 风险与取舍
|
|
550
|
+
|
|
551
|
+
| 风险 | 应对 |
|
|
552
|
+
|---|---|
|
|
553
|
+
| 工具调用次数增加 | 每次只传相关子图;批量 `apply`;上下文更小 |
|
|
554
|
+
| LLM 不按 schema 写 | schema card + 工具 JSON Schema + 错误 hint |
|
|
555
|
+
| requirement 泛滥 | 创建时去重;支持 `waived`;只对 hard 项阻断 |
|
|
556
|
+
| 事务太大 | 一章/一个 chunk 一个 change_set;批量用 `apply` |
|
|
557
|
+
| 嵌入单进程 | 首版接受;后续切 server |
|
|
558
|
+
| 旧数据迁移 | `feat/surrealdb` 保留为 fallback;新分支从空库开始 |
|
|
559
|
+
| 质量规则误判 | 只做 warning,不阻断;可 `ignore` |
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## 14. 和当前 `feat/surrealdb` 的关系
|
|
564
|
+
|
|
565
|
+
保留:
|
|
566
|
+
|
|
567
|
+
- 嵌入式 SurrealDB 连接、schema 迁移、图/事件/时序模型。
|
|
568
|
+
- `knowledge_event` + `knowledge_state` 回放。
|
|
569
|
+
- 影响分析的图查询思路。
|
|
570
|
+
|
|
571
|
+
替换:
|
|
572
|
+
|
|
573
|
+
- 阶段 JSON 投影 → 直接写 DB。
|
|
574
|
+
- `importStage` → `story.apply`。
|
|
575
|
+
- 文件校验器为主 → DB 硬约束 + requirement + 查询式 warning。
|
|
576
|
+
- `.reference-decisions.md` append-only → `decision` + `change_set` 事务。
|
|
577
|
+
|
|
578
|
+
`feat/surrealdb` 继续作为可用备份;`feat/story-graph` 是下一版。
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## 15. 实现状态(P0–P3)
|
|
583
|
+
|
|
584
|
+
- [x] `schema/definitions.js` + 编译器(DDL / schema card / tool schema)
|
|
585
|
+
- [x] `storydb` CLI:schema / get / search / context / create / update / link / unlink / apply / validate / next / why / history / query / impact / export / import
|
|
586
|
+
- [x] `change_set` + `decision` 同事务;`why` / `history`;`export decisions`
|
|
587
|
+
- [x] `requirement` 占位 + `next` 待办队列
|
|
588
|
+
- [x] `stage-check`:scale/macro/cast/rhythm/outline/writing 的数据库完成度检查
|
|
589
|
+
- [x] 6 个 `build` 命令切到 storydb 提示词 + stage-check 驱动
|
|
590
|
+
- [x] `tools query` / `tools analyze` / `tools export` 在 graph 模式下直连故事图
|
|
591
|
+
- [x] `FANCY_STORY_GRAPH=0` 回退旧文件管线
|
|
592
|
+
- [x] 硬约束:悬空边、知识时间线、钩子连续重复、时间线倒退、`complete` 守卫
|
|
593
|
+
- [x] 软待办:伏笔 reveal、章节细纲/正文、爽点 setup
|
|
594
|
+
- [x] 质量 warning:情绪平流、黄金三章、主线/冲突覆盖
|
|
595
|
+
- [x] `context`:章节 + 细纲 + 场景 + 出场人物 + 待办 + 前三章 handoff/结尾 + 全文检索
|
|
596
|
+
- [x] 中文全文索引:`TOKENIZERS class FILTERS lowercase, ngram(1,2)`
|
|
597
|
+
- [x] 影响分析:relation / foreshadow
|
|
598
|
+
- [x] 旧阶段 JSON 导入:scale / macro / cast / rhythm / outline / writing
|
|
599
|
+
- [x] Markdown 导出:10-scale ~ 50-writing + decisions
|
|
600
|
+
- [x] MCP stdio server(12 个工具)
|
|
601
|
+
- [x] 插件入口:`/fancy-story-builder tools graph ...`
|
|
602
|
+
- [x] 测试:storydb-core 7 个用例 + 全量 1883 通过
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
> 2026-11 架构更新:校验正在下沉到数据库。`fancy-db init` 会生成 `fn::fancy::validate($scope)`;写入硬规则仍由 `DEFINE EVENT` 执行。目录与命令以 [workspace-mcp.md](./workspace-mcp.md) 为准。
|
|
2
|
+
|
|
3
|
+
# 故事图校验:谁校验、校验什么、信任边界
|
|
4
|
+
|
|
5
|
+
## 1. 谁执行校验
|
|
6
|
+
|
|
7
|
+
有三层,职责不同:
|
|
8
|
+
|
|
9
|
+
| 层 | 谁执行 | 作用 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| 写入事件 | SurrealDB 自己 | 硬约束,写入时立即拒绝并回滚 |
|
|
12
|
+
| `storydb validate` / `stage-check` | LLM 或用户通过 CLI | 自查、看缺口;不是最终裁决 |
|
|
13
|
+
| `runStageLoop` 的 `stageCheck` | 插件进程(`driver.js` / `stage-command.js`) | 最终门禁;LLM 回合结束后由插件执行 |
|
|
14
|
+
|
|
15
|
+
关键点:**最终门禁不是 LLM 跑出来的结果**。LLM 回合结束后,插件关闭数据库、重新打开,调用 `stageCheck`;不通过就把 `errors` 原文回灌给 LLM。LLM 自己运行的 `stage-check` 只是提前发现问题。
|
|
16
|
+
|
|
17
|
+
## 2. 校验什么
|
|
18
|
+
|
|
19
|
+
### 写入时(数据库事件)
|
|
20
|
+
|
|
21
|
+
- `chapter_guard`:卷/幕必须存在;`emotion.value` 1-10;连续 3 章同 `hook.type`;`time.story_day` 不能倒退(闪回要标 `flashback=true`)。
|
|
22
|
+
- `foreshadow_order`:`plant` 必须早于 `progress` / `reveal`;`reveal` 后不能再 `plant`。
|
|
23
|
+
- `payoff_order`:`setup` 必须早于 `payoff`;已有更晚的 setup 时不能补更早的 payoff。
|
|
24
|
+
- `knowledge_rules`:`change` / `coverage` 映射、`how=told` 的来源、知识状态不能从 `full` 再学、误信必须先 `corrects`。
|
|
25
|
+
- `complete_*`:有 hard `requirement` 未完成时,不能把实体标成 `complete`。
|
|
26
|
+
- `fill_*` / `unfill_*`:章节动作、细纲、正文写入/删除时同步 requirement。
|
|
27
|
+
|
|
28
|
+
### `validate`(全局/范围检查)
|
|
29
|
+
|
|
30
|
+
- hard requirement 仍在 pending 却已标 `complete` → error。
|
|
31
|
+
- 连续 3 章同钩子、时间线倒退 → error。
|
|
32
|
+
- 伏笔回收早于埋设、爽点兑现早于铺垫 → error。
|
|
33
|
+
- 伏笔/爽点还没有任何章节动作 → pending。
|
|
34
|
+
- 主线节点/冲突没有章节落点、情绪连续平流、黄金三章钩子弱 → warning。
|
|
35
|
+
|
|
36
|
+
### `stage-check`
|
|
37
|
+
|
|
38
|
+
| 阶段 | 额外检查 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `scale` | scale 存在;有卷/幕;卷覆盖 1..target;卷起止不倒置 |
|
|
41
|
+
| `macro` | world_doc / story_doc / line / line_node / ending / role_slot 均存在 |
|
|
42
|
+
| `cast` | 人物按类型、势力、势力关系、地点、道具、冲突、伏笔、高潮、爽点达到 scale 配额 |
|
|
43
|
+
| `rhythm` | 章节数达到 target、章号无缺口、每章至少 1 个 cast,加上 `validate` 的非 pending 错误 |
|
|
44
|
+
| `outline` | 每章有 outline、每个 outline 至少 1 个 scene,加上 `validate` 的错误 |
|
|
45
|
+
| `writing` | 每章有 chapter_text、word_count > 0,加上 `validate` 的错误 |
|
|
46
|
+
|
|
47
|
+
## 3. 信任边界:LLM 改脚本怎么办
|
|
48
|
+
|
|
49
|
+
- `storydb` CLI 现在通过 **writer 会话** 访问数据库:`story_writer` 是 `VIEWER` 角色,但数据表通过 `ALTER TABLE ... PERMISSIONS FOR select, create, update, delete FULL` 开放 CRUD。
|
|
50
|
+
- 这个会话**不能** `DEFINE` / `REMOVE` 表、函数、事件,也不能改 schema;schema 初始化只发生在插件 root 会话。
|
|
51
|
+
- 硬规则本身是数据库事件,LLM 即使改 CLI 代码,也不能让数据库接受非法写入。
|
|
52
|
+
- 最终 `stageCheck` 在插件进程里执行。插件进程在 LLM 回合开始前已经加载了 `stage.js` / `store.js`,本轮内编辑磁盘文件不会替换已加载的模块。
|
|
53
|
+
- 仍然存在的边界:LLM 如果有任意 shell 且能直接运行 Node,它可以绕过 CLI 用 root 打开嵌入式数据库。要彻底防住,需要 OS 级沙箱/只读挂载/独立用户;仅靠插件代码做不到。
|
|
54
|
+
|
|
55
|
+
## 4. SurrealDB 函数/存储过程研究结论
|
|
56
|
+
|
|
57
|
+
在本机嵌入模式下已验证:
|
|
58
|
+
|
|
59
|
+
```sql
|
|
60
|
+
DEFINE FUNCTION fn::story::check_scale() {
|
|
61
|
+
RETURN SELECT count() FROM volume GROUP ALL;
|
|
62
|
+
};
|
|
63
|
+
RETURN fn::story::check_scale();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
可以创建并调用用户函数;事件触发器也已经在用。SurrealDB 还支持 `DEFINE USER`:
|
|
67
|
+
|
|
68
|
+
```sql
|
|
69
|
+
DEFINE USER story_writer ON ROOT PASSWORD '...' ROLES VIEWER;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
并验证了:`VIEWER` + 表级 `PERMISSIONS ... FULL` 可以读写记录,但不能 `DEFINE FUNCTION` / `DEFINE TABLE`。CLI 已切到这个 writer 会话。
|
|
73
|
+
|
|
74
|
+
### 为什么不把所有校验都搬进函数
|
|
75
|
+
|
|
76
|
+
`scale` / `cast` 的计数检查可以很容易写成 SQL;但 `validate` 里有跨 500 章的循环(连续钩子、时间线、覆盖)和带优先级的错误/警告分类,用 SurrealQL 写会明显更难维护。当前策略是:
|
|
77
|
+
|
|
78
|
+
1. 硬约束留在事件(已经是数据库内)。
|
|
79
|
+
2. 最终门禁留在插件进程(不信任 LLM)。
|
|
80
|
+
3. writer 会话限制 schema/函数修改。
|
|
81
|
+
4. 后续可把 `stage-check` 的计数部分做成 `fn::story::stage_metrics($stage)`,复杂规则继续保留在插件侧。
|