@cloud411716/fancy-webnovel 3.0.3 → 4.0.0

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.
Files changed (63) hide show
  1. package/docs/material-library/README.md +6 -5
  2. package/docs/material-library/prompt.md +3 -2
  3. package/docs/workspace-mcp.md +2 -2
  4. package/lib/material-topic.js +248 -0
  5. package/lib/topic-candidates.js +157 -0
  6. package/package.json +4 -2
  7. package/plugins/fancy-analyze/analyze-definitions.md +35 -35
  8. package/plugins/fancy-analyze/templates.js +3 -3
  9. package/plugins/fancy-import/prompts/01-import-prompt.md +20 -20
  10. package/plugins/fancy-material/genres.js +4 -36
  11. package/plugins/fancy-material/index.js +18 -32
  12. package/plugins/fancy-material/templates.js +11 -3
  13. package/plugins/fancy-scan/prompts/01-analysis-prompt.md +16 -23
  14. package/plugins/fancy-scan/scripts/report-validate.js +39 -76
  15. package/plugins/fancy-scan/templates/report-schema.js +11 -8
  16. package/plugins/fancy-scan/templates.js +2 -2
  17. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +40 -28
  18. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +28 -10
  19. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +21 -9
  20. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +15 -15
  21. package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +18 -18
  22. package/plugins/fancy-story-builder/purpose-template.md +1 -1
  23. package/plugins/fancy-story-builder/references/glossary.md +8 -8
  24. package/plugins/fancy-story-builder/references/index.md +2 -2
  25. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +2 -2
  26. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +2 -2
  27. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +2 -2
  28. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +2 -2
  29. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +2 -2
  30. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +2 -2
  31. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +2 -2
  32. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +2 -2
  33. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +2 -2
  34. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +7 -7
  35. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +6 -6
  36. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +2 -2
  37. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +2 -2
  38. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +2 -2
  39. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +2 -2
  40. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +2 -2
  41. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +5 -5
  42. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +2 -2
  43. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +1 -1
  44. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +2 -2
  45. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +2 -2
  46. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +5 -5
  47. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +3 -3
  48. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +3 -3
  49. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +1 -1
  50. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +2 -2
  51. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +1 -1
  52. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +3 -3
  53. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +2 -2
  54. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +2 -2
  55. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +2 -2
  56. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +1 -1
  57. package/plugins/fancy-story-builder/storydb/README.md +4 -4
  58. package/plugins/fancy-story-builder/storydb/prompts.js +1 -1
  59. package/plugins/fancy-topic/index.js +216 -546
  60. package/plugins/fancy-topic/prompts/01-exploration-prompt.md +6 -5
  61. package/plugins/fancy-topic/prompts/02-setup-prompt.md +112 -0
  62. package/plugins/fancy-topic/scripts/save-minimal-topic.js +97 -0
  63. package/plugins/fancy-topic/templates.js +104 -6
@@ -1,18 +1,18 @@
1
1
  # 网文创作分析术语定义(权威)
2
2
 
3
- > 本文件是 `schema.md` 的配套定义文件,和 `schema.md` 一起使用。
4
- >
5
- > **执行任何章节分析前,必须先按本文件确认术语含义。**
6
- > 不要用通用文学理论、学术论文或其它题材的定义替代这里的定义。
7
- >
8
- > 定义中的类型和例子是**判定框架,不是题材清单**;都市/玄幻/悬疑/言情/无限流/历史/科幻/体育/职场等题材的等价现象都算,按源文实际内容判断,不要硬套词表。
9
- >
10
- > 判定顺序:
11
- > 1. 先判断“本章有没有这个现象”;
12
- > 2. 再判断它属于哪个术语;
13
- > 3. 最后才写分析。
14
- >
15
- > 如果拿不准,宁可写“无”,不要硬套术语。硬套会让后续骨架/血肉/节奏阶段学到错误模式。
3
+ 本文件是 `schema.md` 的配套定义文件,和 `schema.md` 一起使用。
4
+
5
+ **执行任何章节分析前,必须先按本文件确认术语含义。**
6
+ 不要用通用文学理论、学术论文或其它题材的定义替代这里的定义。
7
+
8
+ 定义中的类型和例子是**判定框架,不是题材清单**;都市/玄幻/悬疑/言情/无限流/历史/科幻/体育/职场等题材的等价现象都算,按源文实际内容判断,不要硬套词表。
9
+
10
+ 判定顺序:
11
+ 1. 先判断“本章有没有这个现象”;
12
+ 2. 再判断它属于哪个术语;
13
+ 3. 最后才写分析。
14
+
15
+ 如果拿不准,宁可写“无”,不要硬套术语。硬套会让后续骨架/血肉/节奏阶段学到错误模式。
16
16
 
17
17
  ---
18
18
 
@@ -34,7 +34,7 @@
34
34
  - 把作者旁白当成视角人物的认知。
35
35
 
36
36
  **网文例子**:
37
- > 方平不知道“武科”是什么,读者也不知道。信息只从方平的疑问和同学的对话里释放——这是限知视角,不是全知旁白。
37
+ 方平不知道“武科”是什么,读者也不知道。信息只从方平的疑问和同学的对话里释放——这是限知视角,不是全知旁白。
38
38
 
39
39
  ---
40
40
 
@@ -53,7 +53,7 @@
53
53
  - 把整本书的卷结构当成单章结构。
54
54
 
55
55
  **网文例子**:
56
- > 开头方平确认重生 → 同学提到武科 → 方平听不懂 → 张浩带来八品之战消息 → 方平发现“剧本不对”。这是“确认异常—扩大信息—认知崩塌”的单章结构。
56
+ 开头方平确认重生 → 同学提到武科 → 方平听不懂 → 张浩带来八品之战消息 → 方平发现“剧本不对”。这是“确认异常—扩大信息—认知崩塌”的单章结构。
57
57
 
58
58
  ---
59
59
 
@@ -72,7 +72,7 @@
72
72
  - 把“事件多”当成节奏快——事件多但全是说明,读者仍会觉得拖。
73
73
 
74
74
  **网文例子**:
75
- > 前半段课堂闲聊轻快,张浩一句“马宗师突破八品”后信息密度骤增,结尾用短句收束。这是“轻—重—紧”的节奏,不是单纯事件多。
75
+ 前半段课堂闲聊轻快,张浩一句“马宗师突破八品”后信息密度骤增,结尾用短句收束。这是“轻—重—紧”的节奏,不是单纯事件多。
76
76
 
77
77
  ---
78
78
 
@@ -93,7 +93,7 @@
93
93
  - 忽略“沉默”“答非所问”“称呼变化”也是对话。
94
94
 
95
95
  **网文例子**:
96
- > 陈凡说“方平,直呼宗师之名,这是最大的不尊重!”——这句话同时在纠正规则、暴露陈凡对武道礼制的熟悉、建立两人关系里的“懂行/不懂行”落差。
96
+ 陈凡说“方平,直呼宗师之名,这是最大的不尊重!”——这句话同时在纠正规则、暴露陈凡对武道礼制的熟悉、建立两人关系里的“懂行/不懂行”落差。
97
97
 
98
98
  ---
99
99
 
@@ -114,7 +114,7 @@
114
114
  - 环境描写与剧情完全无关也标“有”。
115
115
 
116
116
  **网文例子**:
117
- > “方平满脸懵”是心理/表情描写;“手里只剩十五元”是动作性细节,同时推进资源困境。两者都比“方平很穷”有效。
117
+ “方平满脸懵”是心理/表情描写;“手里只剩十五元”是动作性细节,同时推进资源困境。两者都比“方平很穷”有效。
118
118
 
119
119
  ---
120
120
 
@@ -134,7 +134,7 @@
134
134
  - 机制只出现一次、没有后果,也标“有”。
135
135
 
136
136
  **网文例子**:
137
- > 重生不是“知道未来”这么简单:方平的旧记忆与当前武道世界不匹配,迫使他重新收集信息。这是“信息差 + 认知错位”机制在推动行动。
137
+ 重生不是“知道未来”这么简单:方平的旧记忆与当前武道世界不匹配,迫使他重新收集信息。这是“信息差 + 认知错位”机制在推动行动。
138
138
 
139
139
  ---
140
140
 
@@ -154,7 +154,7 @@
154
154
  - 把“标题”当成开头钩子。
155
155
 
156
156
  **网文例子**:
157
- > “2008 年,4 月 5 日。”——日期本身不钩人,但紧接着“方平确认自己重生”制造了“他为什么回到过去、他要怎么活”的问题,这才是钩子。
157
+ “2008 年,4 月 5 日。”——日期本身不钩人,但紧接着“方平确认自己重生”制造了“他为什么回到过去、他要怎么活”的问题,这才是钩子。
158
158
 
159
159
  ---
160
160
 
@@ -174,7 +174,7 @@
174
174
  - 把“下一章标题”当成结尾收束。
175
175
 
176
176
  **网文例子**:
177
- > “这剧本,好像有些不对劲啊!”——用人物判断点明异常,不解释原因,把“哪里不对”留给下一章。
177
+ “这剧本,好像有些不对劲啊!”——用人物判断点明异常,不解释原因,把“哪里不对”留给下一章。
178
178
 
179
179
  ---
180
180
 
@@ -194,7 +194,7 @@
194
194
  - 全章都是短句也标“有”——那可能是单调,不是变化。
195
195
 
196
196
  **网文例子**:
197
- > 解释性长句写武科制度,紧接着“什么情况?”“啥情况?”两个短句写方平的震惊。长短交替制造认知落差。
197
+ 解释性长句写武科制度,紧接着“什么情况?”“啥情况?”两个短句写方平的震惊。长短交替制造认知落差。
198
198
 
199
199
  ---
200
200
 
@@ -215,7 +215,7 @@
215
215
  - 只看排版,不看段落之间的逻辑推进。
216
216
 
217
217
  **网文例子**:
218
- > 课堂群像用较长段落铺陈,张浩爆料后切成短段和独立句,最后用一句“剧本不对”单独收束。段落长度跟着信息压力变化。
218
+ 课堂群像用较长段落铺陈,张浩爆料后切成短段和独立句,最后用一句“剧本不对”单独收束。段落长度跟着信息压力变化。
219
219
 
220
220
  ---
221
221
 
@@ -236,7 +236,7 @@
236
236
  - 纯心理联想没有时空/焦点变化,不一定算场景转换。
237
237
 
238
238
  **网文例子**:
239
- > 方平在教室听同学聊武科,随后“去蓝天网吧查资料”——地点和行动目标都变了,这是场景转换。仅从方平的思考切到陈凡的台词,不算。
239
+ 方平在教室听同学聊武科,随后“去蓝天网吧查资料”——地点和行动目标都变了,这是场景转换。仅从方平的思考切到陈凡的台词,不算。
240
240
 
241
241
  ---
242
242
 
@@ -256,7 +256,7 @@
256
256
  - 用旁白总结情绪,没有场景支撑。
257
257
 
258
258
  **网文例子**:
259
- > 全班兴奋崇拜,方平却“满脸懵”“整个人都傻了”。用群体情绪反衬主角的孤立和不安,比直接写“方平很困惑”更有渲染力。
259
+ 全班兴奋崇拜,方平却“满脸懵”“整个人都傻了”。用群体情绪反衬主角的孤立和不安,比直接写“方平很困惑”更有渲染力。
260
260
 
261
261
  ---
262
262
 
@@ -276,7 +276,7 @@
276
276
  - 把所有信息都解释完,张力消失。
277
277
 
278
278
  **网文例子**:
279
- > 方平只剩十五元,报名截止只剩两天,陈凡最多借十元——目标、资源、时间三重压力同时存在,读者会想知道他怎么凑钱。
279
+ 方平只剩十五元,报名截止只剩两天,陈凡最多借十元——目标、资源、时间三重压力同时存在,读者会想知道他怎么凑钱。
280
280
 
281
281
  ---
282
282
 
@@ -296,7 +296,7 @@
296
296
  - 只写“过渡章”而不说明过渡了什么。
297
297
 
298
298
  **网文例子**:
299
- > 第 1 章的功能不是“介绍世界观”,而是“用认知错位制造悬念,并让主角确立考武科目标”。这是可被后续章节继承的功能。
299
+ 第 1 章的功能不是“介绍世界观”,而是“用认知错位制造悬念,并让主角确立考武科目标”。这是可被后续章节继承的功能。
300
300
 
301
301
  ---
302
302
 
@@ -319,7 +319,7 @@
319
319
  - 只写“他们是朋友”,不写朋友关系带来的义务和代价。
320
320
 
321
321
  **网文例子**:
322
- > 方平与陈凡不只是“同桌”:陈凡提供武道规则解释和现实提醒,方平提供跨世界视角;两人资源都很紧,但陈凡愿意借出仅有的十元。关系里有信息交换、情谊和资源落差。
322
+ 方平与陈凡不只是“同桌”:陈凡提供武道规则解释和现实提醒,方平提供跨世界视角;两人资源都很紧,但陈凡愿意借出仅有的十元。关系里有信息交换、情谊和资源落差。
323
323
 
324
324
  ---
325
325
 
@@ -341,7 +341,7 @@
341
341
  - 势力只在背景里被提到,没有行动和后果,也不算有效设计。
342
342
 
343
343
  **网文例子**:
344
- > 企鹅集团不是“一个大公司”四个字:它有跨省法人资格、武道强者、商业资源、与谷歌亚太区的竞争关系。这些结构决定了它如何压迫/拉拢主角。
344
+ 企鹅集团不是“一个大公司”四个字:它有跨省法人资格、武道强者、商业资源、与谷歌亚太区的竞争关系。这些结构决定了它如何压迫/拉拢主角。
345
345
 
346
346
  ---
347
347
 
@@ -363,7 +363,7 @@
363
363
  - 规则可以随意打破,没有代价,等于没有规则。
364
364
 
365
365
  **网文例子**:
366
- > “武科”不是一句“有武道考试”:它有报名费、年龄上限、政审、体检、文化课线、区域录取差异、动态录取标准。这些规则共同制造了主角的资源困境。
366
+ “武科”不是一句“有武道考试”:它有报名费、年龄上限、政审、体检、文化课线、区域录取差异、动态录取标准。这些规则共同制造了主角的资源困境。
367
367
 
368
368
  ---
369
369
 
@@ -385,7 +385,7 @@
385
385
  - 冲突解决后没有任何后果,等于没有真正发生。
386
386
 
387
387
  **网文例子**:
388
- > “方平要考武科”与“家里没钱、报名截止、陈凡只能借十元”构成资源冲突;它不是一场吵架,而是持续多章的目标—资源对立。
388
+ “方平要考武科”与“家里没钱、报名截止、陈凡只能借十元”构成资源冲突;它不是一场吵架,而是持续多章的目标—资源对立。
389
389
 
390
390
  ---
391
391
 
@@ -406,7 +406,7 @@
406
406
  - 主线目标中途换掉,却没有解释。
407
407
 
408
408
  **网文例子**:
409
- > 主线是“方平考武科、改变阶层处境”;支线包括“重生身份疑云”“特殊钞票能力”“与陈凡/家人的关系”。支线不是平行小说,它们不断给主线提供资源/阻力/信息。
409
+ 主线是“方平考武科、改变阶层处境”;支线包括“重生身份疑云”“特殊钞票能力”“与陈凡/家人的关系”。支线不是平行小说,它们不断给主线提供资源/阻力/信息。
410
410
 
411
411
  ---
412
412
 
@@ -427,7 +427,7 @@
427
427
  - 卷末高潮与下一卷开篇没有因果连接。
428
428
 
429
429
  **网文例子**:
430
- > 第一卷“阳城备考”以武科报名/体检为小高潮,卷末把舞台推向“南江武大/更大势力”;第二卷再升级资源和对手。这是分卷节奏,不是简单分章。
430
+ 第一卷“阳城备考”以武科报名/体检为小高潮,卷末把舞台推向“南江武大/更大势力”;第二卷再升级资源和对手。这是分卷节奏,不是简单分章。
431
431
 
432
432
  ---
433
433
 
@@ -455,7 +455,7 @@
455
455
  - 伏笔回收时没有改变读者理解,只是补一句说明。
456
456
 
457
457
  **网文例子**:
458
- > “方平直呼马宗师之名,陈凡严肃纠正”——这里既埋了“武道礼制”规则,也埋了“方平为什么不懂”的身份疑云。后续“重生/记忆错位”回收了这条线。
458
+ “方平直呼马宗师之名,陈凡严肃纠正”——这里既埋了“武道礼制”规则,也埋了“方平为什么不懂”的身份疑云。后续“重生/记忆错位”回收了这条线。
459
459
 
460
460
  ---
461
461
 
@@ -486,7 +486,7 @@
486
486
  - 角色每章都变,没有稳定内核,也不可信。
487
487
 
488
488
  **网文例子**:
489
- > 方平从“谨慎旁观、先求证”到“主动报名、承担风险”,中间经过资源困境、家庭责任、知识短板、信息不足的反复检验。这是弧光,不是单纯“变强”。
489
+ 方平从“谨慎旁观、先求证”到“主动报名、承担风险”,中间经过资源困境、家庭责任、知识短板、信息不足的反复检验。这是弧光,不是单纯“变强”。
490
490
 
491
491
  ---
492
492
 
@@ -551,7 +551,7 @@ ${projectRoot}/对标/${bookName}/
551
551
  | ... |
552
552
 
553
553
  ## 角色动机交织(最关键!)
554
- > 这一段是整个 characters.md 的核心:让 fancy-bible 能直接据此设计角色生态
554
+ 这一段是整个 characters.md 的核心:让 fancy-bible 能直接据此设计角色生态
555
555
 
556
556
  - 主角 ↔ 主反派:[冲突的动机点]
557
557
  - 主角 ↔ 配角 A:[...]
@@ -1093,8 +1093,8 @@ FANCY_SUMMARY_EOF
1093
1093
  **只处理下面明确列出的总结目录**,不要自行扩大范围。
1094
1094
 
1095
1095
  ${scaleNote}
1096
- > ⚠️ 本提示词**故意不列具体来源名** —— 一本长篇可能几千章、上千个实体页,列出来会把提示词撑爆。
1097
- > 哪些来源还没总结,由你按每个目录下方写的步骤**自己去目录里判断**。
1096
+ ⚠️ 本提示词**故意不列具体来源名** —— 一本长篇可能几千章、上千个实体页,列出来会把提示词撑爆。
1097
+ 哪些来源还没总结,由你按每个目录下方写的步骤**自己去目录里判断**。
1098
1098
 
1099
1099
  ## ⚠️ 语言强制要求
1100
1100
 
@@ -1,11 +1,11 @@
1
1
  # 01 导入执行须知
2
2
 
3
- > 本文件是导入阶段的**产物契约**:从旧稿反推出什么文件、写成什么格式、什么会被拦下。
4
- > 导入的因果方向和正向四阶段相反:
5
- > **旧稿已经在那里了 → 你把它读成结构化产物**,而不是照配额从零造。
6
- >
7
- > 导入阶段由插件驱动:它扫目录、切章、算配额、跑校验器、把错误发回来。
8
- > **你不需要自己决定写哪些文件,也不需要自己跑校验脚本。**
3
+ 本文件是导入阶段的**产物契约**:从旧稿反推出什么文件、写成什么格式、什么会被拦下。
4
+ 导入的因果方向和正向四阶段相反:
5
+ **旧稿已经在那里了 → 你把它读成结构化产物**,而不是照配额从零造。
6
+
7
+ 导入阶段由插件驱动:它扫目录、切章、算配额、跑校验器、把错误发回来。
8
+ **你不需要自己决定写哪些文件,也不需要自己跑校验脚本。**
9
9
 
10
10
  ---
11
11
 
@@ -19,20 +19,20 @@
19
19
 
20
20
  ## 一、核心铁律
21
21
 
22
- > 1. **只反推,不创作**。旧稿里没有的人物、势力、地点、伏笔**不许新增**。
23
- > 你的任务是"把旧稿里已经有的东西整理成结构化数据",
24
- > 不是"补全一部没写完的小说"。
25
- > 旧稿确实没写到的部分**留空**,由插件在报告里列成待补清单。
26
- >
27
- > 2. **不改旧稿正文**。正文由插件直接落盘(保留原标题),
28
- > 你只负责反推结构化产物。写了正文也算违反契约。
29
- >
30
- > 3. **不修改 `模板/` 和 `references/` 下的任何文件**。这两个目录是只读输入。
31
- >
32
- > 4. **只写本轮指定的文件**。本轮要写哪几个文件,驱动会在派活时列清楚。
33
- > 不要顺手多写,也不要改别的阶段的文件。
34
- >
35
- > 5. **引用的实体只能用 id 引用**(如 `C001`),不要在关系里写名字。
22
+ 1. **只反推,不创作**。旧稿里没有的人物、势力、地点、伏笔**不许新增**。
23
+ 你的任务是"把旧稿里已经有的东西整理成结构化数据",
24
+ 不是"补全一部没写完的小说"。
25
+ 旧稿确实没写到的部分**留空**,由插件在报告里列成待补清单。
26
+
27
+ 2. **不改旧稿正文**。正文由插件直接落盘(保留原标题),
28
+ 你只负责反推结构化产物。写了正文也算违反契约。
29
+
30
+ 3. **不修改 `模板/` 和 `references/` 下的任何文件**。这两个目录是只读输入。
31
+
32
+ 4. **只写本轮指定的文件**。本轮要写哪几个文件,驱动会在派活时列清楚。
33
+ 不要顺手多写,也不要改别的阶段的文件。
34
+
35
+ 5. **引用的实体只能用 id 引用**(如 `C001`),不要在关系里写名字。
36
36
 
37
37
  ---
38
38
 
@@ -15,42 +15,10 @@ import fs from 'fs';
15
15
  import path from 'path';
16
16
  import { workspacePaths } from '#workspace';
17
17
 
18
- export const FALLBACK_GENRES = [
19
- '西方奇幻',
20
- '东方仙侠',
21
- '科幻末世',
22
- '都市日常',
23
- '都市修真',
24
- '都市高武',
25
- '历史古代',
26
- '战神赘婿',
27
- '都市种田',
28
- '传统玄幻',
29
- '历史脑洞',
30
- '悬疑脑洞',
31
- '都市脑洞',
32
- '玄幻脑洞',
33
- '悬疑灵异',
34
- '抗战谍战',
35
- '游戏体育',
36
- '动漫衍生',
37
- '男频衍生',
38
- '古风世情',
39
- '女频衍生',
40
- '玄幻言情',
41
- '种田',
42
- '年代',
43
- '现言脑洞',
44
- '宫斗宅斗',
45
- '古言脑洞',
46
- '快穿',
47
- '青春甜宠',
48
- '星光璀璨',
49
- '女频悬疑',
50
- '职场婚恋',
51
- '豪门总裁',
52
- '民国言情',
53
- ];
18
+ import { BUILTIN_GENRES } from '#material-topic';
19
+
20
+ /** 兼容旧名字:内置全题材列表统一放在 lib/material-topic.js */
21
+ export const FALLBACK_GENRES = BUILTIN_GENRES;
54
22
 
55
23
  function unique(values) {
56
24
  const seen = new Set();
@@ -22,9 +22,10 @@ import {
22
22
  workspacePaths,
23
23
  } from '#workspace';
24
24
  import { connectMaterial, ensureMaterialDatabase, materialDatabaseName, removeMaterialDatabase } from '#material-db';
25
+ import { BUILTIN_GENRES, resolveMaterialTopic } from '#material-topic';
25
26
  import { makeWaitForTurn } from '../fancy-story-builder/writing-driver.js';
26
27
  import { exportMaterials } from './exporter.js';
27
- import { allKnownGenres, listTopicDecisionFiles, parseTopicDecisionGenres, resolveMaterialGenres } from './genres.js';
28
+ import { allKnownGenres } from './genres.js';
28
29
  import { buildExistingIndex, countByKind, countKind } from './inventory.js';
29
30
  import { KIND_LABELS, MATERIAL_KINDS, focusBlock, renderKindTable, resolveFocus } from './kinds.js';
30
31
  import { nextBatchRequest, planMaterialQueue } from './queue.js';
@@ -388,38 +389,27 @@ async function runGenerate(ctx, invocation, session, focusRaw = '') {
388
389
  return notify(session, 'error', '❌ 未识别素材类型:' + focusRaw
389
390
  + '\n可用示例:地名、人名、姓氏、门派、技能、超能力、武器、颜色、特效、脑洞、人物性格、关系、场景。');
390
391
  }
391
- // No-arg generation asks which topic_decision report to use when several
392
- // exist. Focused runs keep the previous behavior (latest report) so a
393
- // targeted one-kind request does not require an extra click.
394
- const topicFiles = !focusRaw ? listTopicDecisionFiles(root) : [];
395
- let selectedTopicFile = null;
396
- if (topicFiles.length > 1) {
397
- const options = topicFiles.map((file) => {
398
- let genres = [];
399
- try { genres = parseTopicDecisionGenres(fs.readFileSync(file, 'utf8')); } catch { /* preview only */ }
400
- return {
401
- label: path.basename(file),
402
- description: genres.length ? genres.join('、') : '未解析到选题方向',
403
- };
404
- });
392
+ // 素材题材只认用户选题链路:
393
+ // _novel_topic.json > _novel_topic_suggestion.json > 内置全题材。
394
+ // 不再从 topic_decision 里猜题材,避免素材和用户最终选题脱节。
395
+ let plan = resolveMaterialTopic(root);
396
+ if (!plan.mode) {
405
397
  const asked = await askUserQuestion(ctx, invocation, [{
406
- id: 'material-topic-file',
407
- header: '选择选题方向',
408
- question: '📊 请选择本次素材库使用的 topic_decision 文件:',
409
- detail: '只会读取选中文件里的「选题方向」,不会合并多个文件。',
410
- options,
398
+ id: 'material-builtin-confirm',
399
+ header: '没有选题记录',
400
+ question: '当前没有执行过扫榜和 /fancy-topic,将使用内置全题材生成素材(男频 + 女频)。是否继续?',
401
+ detail: '内置题材共 ' + BUILTIN_GENRES.length + ' 类;确认后所有 kind 都会按全题材生成。',
402
+ options: [
403
+ { label: '确认', description: '使用内置全题材生成素材' },
404
+ { label: '取消', description: '不生成任何素材' },
405
+ ],
411
406
  }]);
412
407
  if (!asked.ok) {
413
408
  return notify(session, 'error', asked.failure?.message || '❌ 提问失败');
414
409
  }
415
410
  const selected = asked.answer?.answers?.[0]?.selected?.[0];
416
- if (!selected) return notify(session, 'error', '已取消素材生成。');
417
- selectedTopicFile = topicFiles.find((file) => path.basename(file) === selected) || null;
418
- if (!selectedTopicFile) {
419
- return notify(session, 'error', '❌ 未找到选中的选题文件:' + selected);
420
- }
421
- } else if (topicFiles.length === 1) {
422
- selectedTopicFile = topicFiles[0];
411
+ if (selected !== '确认') return notify(session, 'success', '操作已取消。');
412
+ plan = { mode: 'builtin', genres: [...BUILTIN_GENRES], topicFile: null };
423
413
  }
424
414
 
425
415
  const config = generationConfig();
@@ -429,10 +419,6 @@ async function runGenerate(ctx, invocation, session, focusRaw = '') {
429
419
  return notify(session, 'error', '❌ 部署素材查询脚本失败:\n' + (tools.problems.join('\n') || '未知错误'));
430
420
  }
431
421
  const database = materialDatabaseName(readWorkspace(root));
432
- const plan = resolveMaterialGenres(root, { topicFile: selectedTopicFile || undefined });
433
- if (selectedTopicFile && plan.mode !== 'topic_decision') {
434
- return notify(session, 'error', '❌ 选中的文件没有解析到「选题方向」:' + path.basename(selectedTopicFile));
435
- }
436
422
  const counts = await countByKind(root, plan.genres);
437
423
  const kinds = [...new Set(focus.kinds.length ? focus.kinds : MATERIAL_KINDS)];
438
424
  const planned = planMaterialQueue({ kinds, target: config.targetPerKind });
@@ -791,7 +777,7 @@ export async function apply(ctx) {
791
777
  try { root = workspaceOf(session); } catch { /* prompt also works outside a workspace */ }
792
778
  const genres = allKnownGenres(root);
793
779
  const rendered = renderBasePrompt({
794
- mode: 'all_genres',
780
+ mode: 'builtin',
795
781
  genres,
796
782
  topicFile: '(prompt 子命令固定输出全题材)',
797
783
  existingIndex: '(运行 /fancy-material 时由插件提供)',
@@ -17,8 +17,9 @@ export function helpText() {
17
17
  说明:
18
18
  - 素材是原子积木,每条只有 id / kind / name / tags / genres / note / source。
19
19
  - 每次运行:每个 kind 新增 50 条;每批最多 10 条,插件自动分批直到完成。
20
- - 未指定类型:54 个 kind 全部排队;有多份 topic_decision 文件时先弹窗选择。
21
- - 指定类型:只生成指定 kind,沿用最新的 topic_decision 文件。
20
+ - 未指定类型:54 个 kind 全部排队。
21
+ - 题材来源:优先 _novel_topic.json,其次 _novel_topic_suggestion.json;都没有时弹框确认后使用内置全题材。
22
+ - 指定类型:只生成指定 kind,题材来源不变。
22
23
  - 不设库内上限:运行两次就是每个 kind 累计 100 条;已有素材只用于去重。
23
24
  - 中断后重跑会开始新一轮 50 条配额,已经导入的素材不会撤销。
24
25
  - 只追加,不覆盖;同 kind 同 name 的素材会被跳过。
@@ -103,7 +104,14 @@ export function generationPrompt({
103
104
  lines.push('- 工作区目录:' + workspace);
104
105
  lines.push('- 目标文件(**绝对路径**):`' + target + '`');
105
106
  lines.push('- 批次 ID:`' + batchId + '`');
106
- lines.push('- 题材模式:' + (mode === 'topic_decision' ? 'topic_decision(选题方向)' : 'all_genres(番茄全题材)'));
107
+ const modeLabel = mode === 'topic'
108
+ ? 'topic(_novel_topic.json)'
109
+ : mode === 'suggestion'
110
+ ? 'suggestion(_novel_topic_suggestion.json)'
111
+ : mode === 'builtin'
112
+ ? 'builtin(内置全题材)'
113
+ : String(mode || '未指定');
114
+ lines.push('- 题材模式:' + modeLabel);
107
115
  if (Array.isArray(genres) && genres.length) lines.push('- 本次题材:' + genres.join('、'));
108
116
  if (focus && focus.query) {
109
117
  const kinds = (focus.kinds || []).map((item) => `\`${item}\``).join('、');
@@ -121,7 +121,7 @@ ${preflight_command}
121
121
 
122
122
  ### 3.2 读取与取样
123
123
 
124
- > **阶段边界**:3.1 的 `test` / `grep` / `ls` 禁令只约束预检阶段。进入本节后可用只读脚本(grep / awk / python)读取 `data_files` 聚合;但仍不得读取白名单外路径,不得修改或删除任何文件。
124
+ **阶段边界**:3.1 的 `test` / `grep` / `ls` 禁令只约束预检阶段。进入本节后可用只读脚本(grep / awk / python)读取 `data_files` 聚合;但仍不得读取白名单外路径,不得修改或删除任何文件。
125
125
 
126
126
  - 先读每个文件的开头和所有标题/表头,确认字段(书名、作者、排名、题材、${heat_metric_label}、状态、简介等);**不要假设固定格式**
127
127
  - 把每个文件头部的 `抓取时间` **原样**写进"一、数据与方法"(每个数据文件都要写)。
@@ -144,10 +144,10 @@ ${tag_rule_block} - **开篇钩子**:文件里**没有第一章内容**,不
144
144
 
145
145
  ### 3.3 分析口径
146
146
 
147
- > **口径红线:需求规模 / 份额 / 集中度只能使用 ${heat_metric_label};其它指标只做交叉验证,不进入公式。**
147
+ **口径红线:需求规模 / 份额 / 集中度只能使用 ${heat_metric_label};其它指标只做交叉验证,不进入公式。**
148
148
 
149
149
  - **本报告是单日快照**:数据只覆盖这一天${snapshot_scope}。
150
- **结论只能描述当日结构,不能描述趋势** —— 没有历史数据时,不得写"正在上升""持续走高""降温了";确需给出趋势判断的,必须在"一、数据与方法"里写明所依据的对比日期,本次无对比数据则不写趋势。
150
+ **结论只能描述当日结构,不能描述趋势** —— 没有历史数据时,不得写“正在上升、持续走高、降温了”;确需给出趋势判断的,必须在"一、数据与方法"里写明所依据的对比日期,本次无对比数据则不写趋势。
151
151
  ${list_rule_block}- "爆款"不只看排名:结合**${heat_metric_label}规模**、题材内排名、题材集中度
152
152
  ${genre_rule_block}- **每个题材/结构结论至少 2 本书佐证**,写成"《书名》(题材 ${group_scope}第 X 名,${heat_metric_label} ${heat_sample_value})"
153
153
  - **用户画像可以基于题材簇推断**(不必硬凑到两本书),但必须**标注"推断"**,并写明依据的是哪几类书
@@ -180,31 +180,24 @@ ${cross_list_block}- 用户画像从题材、卖点、情绪价值推断;**区
180
180
  ${section_titles_block}
181
181
 
182
182
  - **第二节标题固定为"二、市场结构(单日快照)"** —— 数据只覆盖一天,讲的是当日结构,不是趋势
183
- - 每个选题方向写全 11 项:一句话定位、目标读者与情绪价值、金手指(能力/代价/限制)、核心设定(世界/规则/主线)、爽点模式(前 10 章 / 每 3 章 / 长线各是什么)、情绪价值曲线(压抑—释放—升级)、开篇切入点(第 1 章的场景与钩子)、故事节奏规划(开篇/前期/中期/后期)、人物设定框架(主角/对手/关键配角/关系网)、差异化竞争策略(与榜上同类的具体区别)、主要风险与验证指标
183
+ - 每个选题方向只写 4 项:题材、一句话定位、市场评分、推荐理由
184
184
  - **方向标题用固定格式**:`### 方向 1:<名称>`、`### 方向 2:<名称>`……(序号从 1 连续编号)
185
185
 
186
- 每个方向**照抄下面这 11 行**(内容替换成实际内容,标签一个字都不许改):
186
+ 每个方向**照抄下面这个格式**(内容替换成实际内容,标签一个字都不许改):
187
187
 
188
188
  ```markdown
189
189
  ### 方向 N:<名称>
190
- ${direction_template_block}
190
+ - **题材**:<一个题材名>
191
+ - **一句话定位**:<题材、主角、核心卖点,一句话说清>
192
+ - **市场评分**:<1-10 分,可写小数>
193
+ - **推荐理由**:<为什么这个方向值得写;写清数据依据、竞争度和差异化>
191
194
  ```
192
195
 
193
- > 11 个标签**全部加粗**,冒号用全角 `:`;不要改写、合并或省略。
194
- > **每个标签后面至少 ${min_tag_chars} 个汉字** —— 写个标签名充数不算数。
195
- > **固定格式是为了让写入后的校验能确定性执行**(见 5.2 的 `append-block.js`)。
196
+ 4 个标签**全部加粗**,冒号用全角 `:`;不要改写、合并或省略。
197
+ **题材**至少 2 个汉字;**一句话定位**和**推荐理由**至少 10 个汉字 —— 写个标签名充数不算数。
198
+ **固定格式是为了让写入后的校验能确定性执行**(见 5.2 的 `append-block.js`)。
196
199
  - **方向之间不能是"同一题材换皮"**:${min_directions}-${max_directions} 个方向中,任意两个至少在"题材 / 金手指 / 情绪价值"三项里的**两项**不同
197
- - **优先级排序按这四项打分**(每项写清依据):
198
- - **数据支撑度**(有多少${heat_metric_label}数据支持)—— 权重 40%
199
- - **竞争度**(该题材越挤越靠后)—— 权重 25%
200
- - **差异化空间**(越空白越靠前)—— 权重 25%
201
- - **作者匹配度** —— 权重 10%;**数据里没有作者画像**,没有用户提供的偏好时这一项**标"待确认"**,**不要编一个"擅长什么"**,也不要默认给 3 分
202
- - 每项 1-5 分。**作者匹配度可用时**,总分按
203
- `(数据支撑度×40 + 竞争度×25 + 差异化空间×25 + 作者匹配度×10)÷ 100` 计算;**作者匹配度标"待确认"时不计入总分**,总分按
204
- `(数据支撑度×40 + 竞争度×25 + 差异化空间×25)÷ 90` 计算。四舍五入保留两位小数。
205
- **必须写出各项原始分和算出的总分**,不能只写结论。
206
- - **优先级计算示例**:数据支撑度 4 分、竞争度 2 分、差异化空间 4 分、作者匹配度待确认 → 总分 = `(4×40 + 2×25 + 4×25) ÷ 90 = 3.44` 分。竞争度是反向指标:竞争越激烈,这项分数越低。
207
- - 按总分**从高到低**排;同分时依次比数据支撑度、竞争度,仍同分再比该题材主指标总量,并写明依据。
200
+ - **市场评分**按 1-10 分给出,综合数据支撑度、竞争度、差异化空间;在「推荐理由」里写清依据,不要只给分数。
208
201
 
209
202
  **硬性要求**(写入时会被脚本逐条检查,不合格**写不进去**):
210
203
 
@@ -231,9 +224,9 @@ ${report_headers_block}
231
224
  ${genre_self_check}
232
225
  - **指标用的是${heat_metric_label}数据**,不是恒等于常数的书目数占比
233
226
  - **没有写没有数据支撑的趋势判断**
234
- - ${min_directions}-${max_directions} 个方向,每个 11 项齐全、每项 ≥${min_tag_chars} 汉字,标题格式正确
227
+ - ${min_directions}-${max_directions} 个方向,每个 4 项齐全,题材 ≥2 汉字、定位/理由 ≥10 汉字,标题格式正确
235
228
  - **任意两个方向至少两项不同**
236
- - **有按四项标准的优先级排序**
229
+ - **每个方向都有 1-10 的市场评分和推荐理由**
237
230
  - **字数、表格数与表头都符合上面的硬性要求**
238
231
  - 题材表没有“全部”汇总行,至少有 3 个题材
239
232
  - 结论有"书名 + 排名 + ${heat_metric_label}"佐证,且每个题材/结构结论至少 2 本书佐证
@@ -245,7 +238,7 @@ ${genre_self_check}
245
238
 
246
239
  ## 五、追加写入(校验在脚本里,跑不掉)
247
240
 
248
- > **红线:目标文件只能通过 5.2 的 `append-block.js` 写入。禁止 `cat >>`、write / edit、`touch` 或任何绕过脚本的写法。**
241
+ **红线:目标文件只能通过 5.2 的 `append-block.js` 写入。禁止 `cat >>`、write / edit、`touch` 或任何绕过脚本的写法。**
249
242
 
250
243
  把本次分析报告**追加**到 `${topic_file}` 的**末尾**。
251
244