@cloud411716/fancy-webnovel 1.0.46 → 2.2.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.
- package/client.js +183 -0
- package/cordis.patch.yml +0 -14
- package/index.js +21 -37
- package/lib/chinese-num.js +111 -0
- package/lib/deployed-state.js +141 -0
- package/lib/error-codes.js +104 -0
- package/lib/infra.js +569 -0
- package/lib/llm-wiki.js +109 -0
- package/lib/safe-rm.js +94 -0
- package/lib/scraper-utils.js +165 -0
- package/package.json +37 -5
- package/plugins/fancy-analyze/command-input.js +65 -0
- package/plugins/fancy-analyze/index.js +973 -0
- package/plugins/fancy-analyze/schema-analyze.md +379 -0
- package/plugins/fancy-analyze/schema-write.md +178 -0
- package/plugins/fancy-analyze/scripts/split_source.js +292 -0
- package/plugins/fancy-analyze/summary-progress.js +431 -0
- package/plugins/fancy-analyze/templates.js +881 -0
- package/plugins/fancy-bootstrap/index.js +168 -0
- package/plugins/fancy-bootstrap/scripts/init.js +143 -0
- package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
- package/plugins/fancy-bootstrap/templates.js +54 -0
- package/plugins/fancy-import/import-core.js +1118 -0
- package/plugins/fancy-import/import-driver.js +581 -0
- package/plugins/fancy-import/index.js +730 -0
- package/plugins/fancy-import/prompts/01-import-prompt.md +106 -0
- package/plugins/fancy-import/templates.js +49 -0
- package/plugins/fancy-scan/index.js +319 -0
- package/plugins/fancy-scan/scripts/run-scan.js +520 -0
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +794 -0
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +339 -0
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +611 -0
- package/plugins/fancy-scan/templates.js +295 -0
- package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
- package/plugins/fancy-story-builder/deslop-driver.js +821 -0
- package/plugins/fancy-story-builder/flesh-output.js +155 -0
- package/plugins/fancy-story-builder/index.js +3152 -0
- package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +553 -0
- package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +540 -0
- package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +504 -0
- package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +188 -0
- package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
- package/plugins/fancy-story-builder/purpose-template.md +107 -0
- package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
- package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -0
- package/plugins/fancy-story-builder/references/glossary.md +585 -0
- package/plugins/fancy-story-builder/references/index.md +490 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +163 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +239 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +181 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +177 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +208 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +142 -0
- package/plugins/fancy-story-builder/rhythm-output.js +140 -0
- package/plugins/fancy-story-builder/schema-setting.md +324 -0
- package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
- package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
- package/plugins/fancy-story-builder/scripts/content-quality.js +413 -0
- package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
- package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
- package/plugins/fancy-story-builder/scripts/flesh-validate.js +1189 -0
- package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
- package/plugins/fancy-story-builder/scripts/graph-utils.js +1385 -0
- package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
- package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
- package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
- package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1216 -0
- package/plugins/fancy-story-builder/scripts/semantic-lint.js +377 -0
- package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
- package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1832 -0
- package/plugins/fancy-story-builder/scripts/stage-batches.js +1371 -0
- package/plugins/fancy-story-builder/scripts/stage-contract.js +417 -0
- package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
- package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
- package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
- package/plugins/fancy-story-builder/skeleton-output.js +199 -0
- package/plugins/fancy-story-builder/stage-driver.js +1049 -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 +204 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/347/273/206/347/272/262/346/250/241/346/235/277.json +34 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/350/247/204/345/210/222/346/250/241/346/235/277.json +24 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//350/212/202/345/245/217/345/217/202/346/225/260/346/250/241/346/235/277.json +27 -0
- 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 +66 -0
- 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 +76 -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 +72 -0
- 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 +37 -0
- 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 +53 -0
- 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 +40 -0
- 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 +79 -0
- 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 +132 -0
- 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 +133 -0
- 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 +160 -0
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/261/202/347/272/247/346/250/241/346/235/277.json +65 -0
- 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 +134 -0
- package/plugins/fancy-story-builder/templates.js +1248 -0
- package/plugins/fancy-story-builder/wiki-references.js +204 -0
- package/plugins/fancy-story-builder/writing-driver.js +1061 -0
- package/plugins/fancy-topic/index.js +704 -0
- package/plugins/fancy-topic/scripts/select-topic.js +274 -0
- package/plugins/fancy-topic/templates.js +373 -0
- package/commands/bootstrap/index.js +0 -164
- package/commands/bootstrap/init-project.js +0 -121
- package/commands/scan/index.js +0 -283
- package/commands/scan/platforms/fanqie.js +0 -146
- package/commands/scan/platforms/jinjiang.js +0 -143
- package/commands/scan/platforms/qidian.js +0 -102
- package/commands/scan/platforms/qimao.js +0 -127
- package/commands/scan/scraper.js +0 -195
- package/infra.js +0 -160
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fancy-analyze templates — 通知模板 + 拆解指引
|
|
3
|
+
*
|
|
4
|
+
* 拆解指引(generateGuideMarkdown())由 LLM 在拆解爆款小说时读取,
|
|
5
|
+
* 告诉它按"消费者"(fancy-bible / fancy-outline / fancy-write)拆解成多份 .md 文件。
|
|
6
|
+
*
|
|
7
|
+
* 拆解后的产物放在 对标/<书名>/ 下:
|
|
8
|
+
* _summary.md
|
|
9
|
+
* for_bible/{characters,relationships,world}.md
|
|
10
|
+
* for_outline/{plot_structure,pacing,chapter_templates,foreshadow_patterns}.md
|
|
11
|
+
* for_write/{voice,sentence_patterns}.md
|
|
12
|
+
* for_write/phrase_bank/{action,psychology,environment,dialogue}.md
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const CONSUMER_DIRS = {
|
|
16
|
+
bible: [
|
|
17
|
+
'characters.md',
|
|
18
|
+
'relationships.md',
|
|
19
|
+
'world.md',
|
|
20
|
+
],
|
|
21
|
+
outline: [
|
|
22
|
+
'plot_structure.md',
|
|
23
|
+
'pacing.md',
|
|
24
|
+
'chapter_templates.md',
|
|
25
|
+
'foreshadow_patterns.md',
|
|
26
|
+
],
|
|
27
|
+
write: [
|
|
28
|
+
'voice.md',
|
|
29
|
+
'sentence_patterns.md',
|
|
30
|
+
'phrase_bank/action.md',
|
|
31
|
+
'phrase_bank/psychology.md',
|
|
32
|
+
'phrase_bank/environment.md',
|
|
33
|
+
'phrase_bank/dialogue.md',
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 人类可读的字节数。
|
|
39
|
+
* @param {number} bytes - 字节数
|
|
40
|
+
* @returns {string} 例如 "8.0 KB"
|
|
41
|
+
*/
|
|
42
|
+
function humanBytes(bytes) {
|
|
43
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
|
44
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
45
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
46
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 相对时间("3 分钟前")。未来时间按"刚刚"处理。
|
|
51
|
+
* @param {number} ms - 时间戳(毫秒)
|
|
52
|
+
* @param {number} now - 当前时间(毫秒)
|
|
53
|
+
* @returns {string} 例如 "1 分钟前"
|
|
54
|
+
*/
|
|
55
|
+
function humanAgo(ms, now) {
|
|
56
|
+
if (!Number.isFinite(ms) || ms <= 0) return '未知';
|
|
57
|
+
const diff = Math.max(0, now - ms);
|
|
58
|
+
const sec = Math.floor(diff / 1000);
|
|
59
|
+
if (sec < 60) return '刚刚';
|
|
60
|
+
const min = Math.floor(sec / 60);
|
|
61
|
+
if (min < 60) return `${min} 分钟前`;
|
|
62
|
+
const hour = Math.floor(min / 60);
|
|
63
|
+
if (hour < 24) return `${hour} 小时前`;
|
|
64
|
+
return `${Math.floor(hour / 24)} 天前`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 渲染技法总结进度报告。
|
|
69
|
+
*
|
|
70
|
+
* 三态来自文件系统事实(见 summary-progress.js):
|
|
71
|
+
* 已完成 = 头部「已总结章节」覆盖了该目录的全部源文件
|
|
72
|
+
* 进行中 = 有总结文件,但还有章节没登记进去
|
|
73
|
+
* 未开始 = 没有总结文件
|
|
74
|
+
*
|
|
75
|
+
* @param {object} progress - scanSummaryProgress 的结果
|
|
76
|
+
* @param {number} now - 当前时间(毫秒)
|
|
77
|
+
* @returns {string} 多行报告
|
|
78
|
+
*/
|
|
79
|
+
function renderSummaryStatus(progress, now) {
|
|
80
|
+
const lines = [];
|
|
81
|
+
const total = progress.total;
|
|
82
|
+
lines.push('📊 技法总结进度(对标/wiki)');
|
|
83
|
+
lines.push('');
|
|
84
|
+
|
|
85
|
+
const nameWidth = Math.max(4, ...progress.dirs.map((d) => d.name.length));
|
|
86
|
+
const pad = (name) => name + ' '.repeat(nameWidth - name.length);
|
|
87
|
+
const lineOf = (p) => {
|
|
88
|
+
const parts = [];
|
|
89
|
+
if (p.sourceCount > 0) {
|
|
90
|
+
parts.push(p.state === 'done'
|
|
91
|
+
? `${p.sourceCount} 章全部已总结`
|
|
92
|
+
: `已总结 ${p.summarized.length}/${p.sourceCount} 章`);
|
|
93
|
+
}
|
|
94
|
+
parts.push(`${p.entries} 条技法`);
|
|
95
|
+
if (p.exists) {
|
|
96
|
+
parts.push(humanBytes(p.bytes));
|
|
97
|
+
parts.push(`${humanAgo(p.updatedAt, now)}更新`);
|
|
98
|
+
}
|
|
99
|
+
if (p.chapterSource === 'inferred') parts.push('章节清单由正文反推');
|
|
100
|
+
if (p.chapterSource === 'none' && p.sourceCount > 0) parts.push('无章节清单');
|
|
101
|
+
return ` · ${pad(p.name)} ${parts.join(' · ')}`;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (total === 0) {
|
|
105
|
+
lines.push('(对标/wiki 下没有技法目录)');
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
lines.push(`✅ 已完成 ${progress.done.length}/${total}`);
|
|
110
|
+
if (progress.done.length === 0) lines.push(' (无)');
|
|
111
|
+
for (const p of progress.done) lines.push(lineOf(p));
|
|
112
|
+
|
|
113
|
+
lines.push('');
|
|
114
|
+
lines.push(`⏳ 进行中 ${progress.running.length}/${total}`);
|
|
115
|
+
if (progress.running.length === 0) lines.push(' (无)');
|
|
116
|
+
for (const p of progress.running) lines.push(lineOf(p));
|
|
117
|
+
|
|
118
|
+
lines.push('');
|
|
119
|
+
lines.push(`⏸ 未开始 ${progress.pending.length}/${total}`);
|
|
120
|
+
if (progress.pending.length === 0) lines.push(' (无)');
|
|
121
|
+
for (const p of progress.pending) lines.push(lineOf(p));
|
|
122
|
+
|
|
123
|
+
if (progress.noInput.length > 0) {
|
|
124
|
+
lines.push('');
|
|
125
|
+
lines.push(`ℹ️ 无输入文件(不会被派发):${progress.noInput.map((p) => p.name).join('、')}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const noList = progress.dirs.filter((p) => p.exists && p.chapterSource === 'none' && p.sourceCount > 0);
|
|
129
|
+
if (noList.length > 0) {
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push(`⚠️ 既无头部「已总结章节」清单、也无法从正文反推:${noList.map((p) => p.name).join('、')}`);
|
|
132
|
+
lines.push(' 这些目录按"全部章节未总结"处理,下次 summary 会重新派发它们。');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
lines.push('');
|
|
136
|
+
lines.push('💡 /fancy-analyze summary 只派发"还有章节没总结"的目录;');
|
|
137
|
+
lines.push(' /fancy-analyze resummary [技法名] 强制重来(会把旧总结移入备份目录)。');
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const analyze = {
|
|
142
|
+
CONSUMER_DIRS,
|
|
143
|
+
|
|
144
|
+
notify({ type, bookName, bookNameClean, projectRoot, refDir, matches, err, missingFiles, queue, extraMessage, summary }) {
|
|
145
|
+
switch (type) {
|
|
146
|
+
case 'cancelled':
|
|
147
|
+
return '🚫 已取消';
|
|
148
|
+
|
|
149
|
+
case 'no_provider':
|
|
150
|
+
return '⚠️ 当前环境不支持交互式提问。请在 dsh-tui 中运行此命令,或确保 web UI 已正确加载交互组件。';
|
|
151
|
+
|
|
152
|
+
case 'no_project_root':
|
|
153
|
+
return '❌ 无法确定项目目录:' + (err || '会话未提供 cwd') + '。请在项目目录下运行该命令。';
|
|
154
|
+
|
|
155
|
+
case 'ask_failed':
|
|
156
|
+
return '❌ 提问失败:' + (err || '未知错误');
|
|
157
|
+
|
|
158
|
+
case 'not_initialized':
|
|
159
|
+
return '⚠️ 当前目录尚未初始化,请先运行 /fancy-bootstrap 进行初始化。';
|
|
160
|
+
|
|
161
|
+
case 'deployed_unreadable':
|
|
162
|
+
// .fancy-deployed 存在但读不了。报"未初始化"会把用户引到
|
|
163
|
+
// /fancy-bootstrap,而 bootstrap 在这个状态下多半也跑不通
|
|
164
|
+
// (文件已存在),等于白跑一趟。
|
|
165
|
+
return `❌ 状态文件 .fancy-deployed 无法读取(${err || '内容异常'})。
|
|
166
|
+
|
|
167
|
+
这不是"未初始化",所以重跑 /fancy-bootstrap 没用。
|
|
168
|
+
请检查该文件是否被手工改坏,或从版本库/备份恢复它。`;
|
|
169
|
+
|
|
170
|
+
case 'source_dir_missing':
|
|
171
|
+
return `❌ 参考资料/ 目录不存在:${refDir || ''}
|
|
172
|
+
|
|
173
|
+
请按以下步骤准备:
|
|
174
|
+
1. 创建 参考资料/ 目录
|
|
175
|
+
2. 手动下载《${bookName}》,并拷贝到 参考资料/${bookName}.txt
|
|
176
|
+
3. 重新运行 /fancy-analyze ${bookName}
|
|
177
|
+
|
|
178
|
+
提示:文件名后缀不限(.txt / .md / .epub 都可以),书名匹配忽略后缀。`;
|
|
179
|
+
|
|
180
|
+
case 'source_missing': {
|
|
181
|
+
const matchList = (matches && matches.length > 0)
|
|
182
|
+
? `\n\n参考资料/ 下已有的条目:\n${matches.map(m => ` - ${m}`).join('\n')}`
|
|
183
|
+
: '';
|
|
184
|
+
return `❌ 找不到《${bookName}》的参考资料${matchList}
|
|
185
|
+
|
|
186
|
+
请按以下步骤准备:
|
|
187
|
+
1. 手动下载《${bookName}》,并拷贝到当前项目的 参考资料/ 目录下
|
|
188
|
+
2. 重新运行 /fancy-analyze ${bookName}
|
|
189
|
+
|
|
190
|
+
提示:匹配书名时忽略后缀。`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case 'source_ambiguous':
|
|
194
|
+
return `❌ 《${bookName}》在参考资料/ 下有多个匹配:\n${(matches || []).map(m => ` - ${m}`).join('\n')}\n\n请精确指定要拆解的文件名(带后缀)。`;
|
|
195
|
+
|
|
196
|
+
case 'write_failed':
|
|
197
|
+
return '❌ 拆解结果写入失败:' + (err || '未知错误');
|
|
198
|
+
|
|
199
|
+
case 'split_failed':
|
|
200
|
+
return '❌ 拆分来源文件失败:' + (err || '未知错误');
|
|
201
|
+
|
|
202
|
+
case 'llm_wiki_not_configured':
|
|
203
|
+
return '❌ llm_wiki 未配置。请在 .fancy-deployed 中配置 llm_wiki_url 和 llm_wiki_token';
|
|
204
|
+
|
|
205
|
+
case 'llm_wiki_failed':
|
|
206
|
+
return '❌ 调用 llm_wiki 失败:' + (err || '未知错误');
|
|
207
|
+
|
|
208
|
+
case 'ingest_failed':
|
|
209
|
+
// ingest 没提交成功 = llm_wiki 那边根本不会开始拆解。
|
|
210
|
+
// 之前这条错误被吞掉,命令照样报"✅ 正在后台拆解中",
|
|
211
|
+
// 用户等半天什么也不会发生。现在必须说出来,并给出补救路径。
|
|
212
|
+
return `❌ 拆书文件已生成,但提交给 llm_wiki 失败:${err || '未知错误'}
|
|
213
|
+
|
|
214
|
+
📁 文件已保留在:对标/raw/sources/${bookNameClean || '<书名>'}/
|
|
215
|
+
|
|
216
|
+
💡 排障后可重试:
|
|
217
|
+
1. /fancy-analyze status 查看 ingest 队列与缺失情况
|
|
218
|
+
2. /fancy-analyze fix 重新提交缺失章节`;
|
|
219
|
+
|
|
220
|
+
case 'no_reference_id':
|
|
221
|
+
return '⚠️ 未设置 reference_id。请先运行 /fancy-analyze <书名> 初始化项目';
|
|
222
|
+
|
|
223
|
+
case 'status_query_failed':
|
|
224
|
+
return '❌ 查询 ingest 状态失败:' + (err || '未知错误');
|
|
225
|
+
|
|
226
|
+
case 'status_result': {
|
|
227
|
+
const lines = [];
|
|
228
|
+
|
|
229
|
+
if (missingFiles && missingFiles.length > 0) {
|
|
230
|
+
lines.push('⚠️ 当前仍有文件未拆解完成:');
|
|
231
|
+
for (const f of missingFiles) {
|
|
232
|
+
lines.push(` - ${f}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (queue) {
|
|
237
|
+
const states = [
|
|
238
|
+
{ key: 'cancelled', label: '取消' },
|
|
239
|
+
{ key: 'done', label: '完成' },
|
|
240
|
+
{ key: 'failed', label: '失败' },
|
|
241
|
+
{ key: 'pending', label: '等待' },
|
|
242
|
+
{ key: 'processing', label: '处理中' },
|
|
243
|
+
];
|
|
244
|
+
for (const s of states) {
|
|
245
|
+
if (queue[s.key] > 0) {
|
|
246
|
+
lines.push(`当前${s.label}状态的任务为 ${queue[s.key]} 个`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 有缺失报告时,不显示"完成"消息
|
|
252
|
+
if (extraMessage) {
|
|
253
|
+
const baseMsg = lines.length > 0 ? lines.join('\n') : '⚠️ ingest 队列状态正常,但检测到以下问题:';
|
|
254
|
+
return baseMsg + '\n' + extraMessage;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (lines.length === 0) {
|
|
258
|
+
return '✅ 所有文件已 ingest 完成,队列空闲';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return lines.join('\n');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
case 'summary_status':
|
|
265
|
+
return renderSummaryStatus(summary, Date.now());
|
|
266
|
+
|
|
267
|
+
case 'summary_dispatched': {
|
|
268
|
+
const tasks = summary?.tasks || [];
|
|
269
|
+
const lines = [];
|
|
270
|
+
lines.push('✅ 已派发技法总结任务');
|
|
271
|
+
lines.push('');
|
|
272
|
+
lines.push(`📋 本次派发 ${tasks.length} 个技法目录:`);
|
|
273
|
+
for (const task of tasks) {
|
|
274
|
+
const parts = [];
|
|
275
|
+
if (Number.isFinite(task.sourceCount)) parts.push(`共 ${task.sourceCount} 章`);
|
|
276
|
+
if (Number.isFinite(task.pendingCount)) parts.push(`还差 ${task.pendingCount} 章`);
|
|
277
|
+
lines.push(` · ${task.name}${parts.length ? ' ' + parts.join(' · ') : ''}`);
|
|
278
|
+
}
|
|
279
|
+
if (summary?.skippedDone?.length) {
|
|
280
|
+
lines.push('');
|
|
281
|
+
lines.push(`✅ 已完成、未重复派发:${summary.skippedDone.join('、')}`);
|
|
282
|
+
}
|
|
283
|
+
if (summary?.skippedNoInput?.length) {
|
|
284
|
+
lines.push(`ℹ️ 无输入文件、未派发:${summary.skippedNoInput.join('、')}`);
|
|
285
|
+
}
|
|
286
|
+
lines.push('');
|
|
287
|
+
if (Number.isFinite(summary?.remainingChapters)) {
|
|
288
|
+
lines.push(`📚 全部待总结 **${summary.remainingChapters} 章**(提示词只给目录,章节由模型自己去目录里找)`);
|
|
289
|
+
}
|
|
290
|
+
lines.push('💡 模型正在后台写文件,用 /fancy-analyze summary-status 查真实进度。');
|
|
291
|
+
lines.push('⚠️ 本命令只代表"提示词已入队",不代表模型已经写完。');
|
|
292
|
+
return lines.join('\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case 'summary_nothing_to_do': {
|
|
296
|
+
const lines = [];
|
|
297
|
+
lines.push('✅ 没有需要总结的章节');
|
|
298
|
+
if (summary?.done?.length) {
|
|
299
|
+
lines.push('');
|
|
300
|
+
lines.push(`已完成:${summary.done.join('、')}`);
|
|
301
|
+
}
|
|
302
|
+
if (summary?.skippedNoInput?.length) {
|
|
303
|
+
lines.push(`ℹ️ 无输入文件:${summary.skippedNoInput.join('、')}`);
|
|
304
|
+
}
|
|
305
|
+
lines.push('');
|
|
306
|
+
lines.push('💡 要强制重来:/fancy-analyze resummary [技法名]');
|
|
307
|
+
return lines.join('\n');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
case 'summary_reset': {
|
|
311
|
+
const lines = [];
|
|
312
|
+
lines.push(`♻️ 已重置 ${summary?.reset?.length || 0} 个技法的总结`);
|
|
313
|
+
lines.push('');
|
|
314
|
+
for (const item of summary?.reset || []) {
|
|
315
|
+
lines.push(` · ${item.name} 旧总结已备份到:${item.backup}`);
|
|
316
|
+
}
|
|
317
|
+
if (summary?.dispatched?.length) {
|
|
318
|
+
lines.push('');
|
|
319
|
+
lines.push(`♻️ 已重新派发:${summary.dispatched.length} 个目录`);
|
|
320
|
+
} else {
|
|
321
|
+
lines.push('');
|
|
322
|
+
lines.push('ℹ️ 没有可派发的目录(源文件为空),只做了重置。');
|
|
323
|
+
}
|
|
324
|
+
lines.push('');
|
|
325
|
+
lines.push('💡 用 /fancy-analyze summary-status 查进度。');
|
|
326
|
+
return lines.join('\n');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
case 'done': {
|
|
330
|
+
// 书名统一用 bookNameClean(它才是真实文件名去后缀的结果,
|
|
331
|
+
// 也是对标/raw/sources/ 下的目录名)——
|
|
332
|
+
// 用 bookName 的话,调用处不传就会显示成《undefined》。
|
|
333
|
+
const doneBook = bookNameClean || bookName || '';
|
|
334
|
+
return `✅ 《${doneBook}》正在后台拆解中......
|
|
335
|
+
|
|
336
|
+
📁 输出位置:${projectRoot}/对标/
|
|
337
|
+
├── schema.md ← LLM Wiki 项目配置
|
|
338
|
+
└── raw/sources/${bookNameClean}/ ← 拆书结果
|
|
339
|
+
|
|
340
|
+
💡 可在 llm_wiki 中查看拆解进度和拆解结果,也可稍后执行 fancy-analyze status 查看进度`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
default:
|
|
344
|
+
return String(type);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
askBookName() {
|
|
349
|
+
return {
|
|
350
|
+
id: 'analyze-book-name',
|
|
351
|
+
// 示例写进 question 文本 —— DSH 的 AskUserQuestionItem 没有
|
|
352
|
+
// placeholder 字段(契约字段只有 id / question / detail / header /
|
|
353
|
+
// options / multiSelect / intent),传了会被运行时静默忽略。
|
|
354
|
+
question: '📖 请输入要拆解的爆款小说书名:\n例如:诡秘之主',
|
|
355
|
+
};
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* 强制约束:每次拆解必须遵守的规则
|
|
360
|
+
*/
|
|
361
|
+
generateMandatoryRules() {
|
|
362
|
+
return [
|
|
363
|
+
'**必须**忽略历史对话上下文和memory,不读之前的任务结果,重新进行分析。',
|
|
364
|
+
'拆解结果**必须**使用中文撰写。',
|
|
365
|
+
'如果目标文件不存在,则首先进行新建;如果目标文件已存在,则进行覆盖写入,忽略之前已写入的内容。',
|
|
366
|
+
].join('\n\n');
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 拆解指引:LLM 在拆解时读取这个 markdown
|
|
371
|
+
* 告诉它拆什么、怎么组织、每份文件的具体字段
|
|
372
|
+
*/
|
|
373
|
+
generateGuideMarkdown({ projectRoot, bookName, splitDir, sourceLength }) {
|
|
374
|
+
return `# 《${bookName}》拆解任务指引
|
|
375
|
+
|
|
376
|
+
## 0. 资料来源
|
|
377
|
+
|
|
378
|
+
- 对标资料/素材原文(已拆分为多个章节片段):\`${splitDir}\`(共约 ${sourceLength} 字符)
|
|
379
|
+
- 请先读取该目录下的所有文件,了解全文内容
|
|
380
|
+
|
|
381
|
+
## 1. 拆解目标
|
|
382
|
+
|
|
383
|
+
将《${bookName}》按 **下游消费者** 组织成多份 markdown,存到:
|
|
384
|
+
|
|
385
|
+
\`\`\`
|
|
386
|
+
${projectRoot}/对标/${bookName}/
|
|
387
|
+
├── _summary.md
|
|
388
|
+
├── for_bible/
|
|
389
|
+
├── for_outline/
|
|
390
|
+
└── for_write/
|
|
391
|
+
\`\`\`
|
|
392
|
+
|
|
393
|
+
每个下游 plugin(fancy-bible / fancy-outline / fancy-write)只读自己 for_xxx/ 下的内容。
|
|
394
|
+
|
|
395
|
+
## 2. 拆解步骤
|
|
396
|
+
|
|
397
|
+
### Step 1: 通读全文
|
|
398
|
+
1. 读取 拆书/ 目录下的所有文件
|
|
399
|
+
2. 边读边记笔记:主角是谁、反派是谁、核心冲突是什么
|
|
400
|
+
3. 记录关键章节号(章首/章末钩子、伏笔埋设、爽点位置)
|
|
401
|
+
|
|
402
|
+
### Step 2: 写 _summary.md(最重要的浓缩)
|
|
403
|
+
按以下结构:
|
|
404
|
+
|
|
405
|
+
\`\`\`markdown
|
|
406
|
+
# 《${bookName}》一句话核心卖点
|
|
407
|
+
[一句话写清这本书为什么火、读者为什么追更]
|
|
408
|
+
|
|
409
|
+
# 5 个最关键发现(每个下游 plugin 都先读这个)
|
|
410
|
+
1. [主角人设原型 + 共情机制]
|
|
411
|
+
2. [核心爽点链:是什么事件驱动读者追更]
|
|
412
|
+
3. [反派设计:让读者恨得起来的反派是怎么写的]
|
|
413
|
+
4. [节奏密码:该书爽点频率比业界模板高还是低]
|
|
414
|
+
5. [可复用的"金手指/系统/设定"创意模式]
|
|
415
|
+
|
|
416
|
+
# 该书的核心循环
|
|
417
|
+
[读者每次点开下一章时会期待什么]
|
|
418
|
+
\`\`\`
|
|
419
|
+
|
|
420
|
+
### Step 3: 写 for_bible/characters.md(人物全谱)
|
|
421
|
+
**关键:必须包含反派和次要角色**,不只是主角。
|
|
422
|
+
|
|
423
|
+
\`\`\`markdown
|
|
424
|
+
# 角色全谱分析
|
|
425
|
+
|
|
426
|
+
## 主角
|
|
427
|
+
- 原型分类:[无敌流/废柴逆袭/重生/签到/系统/...]
|
|
428
|
+
- 8 项必填:
|
|
429
|
+
1. 姓名:
|
|
430
|
+
2. 类型/职业:
|
|
431
|
+
3. 年龄:
|
|
432
|
+
4. 性别:
|
|
433
|
+
5. 性格特点(多个):
|
|
434
|
+
6. 背景故事:
|
|
435
|
+
7. 核心目标(短/中/长):
|
|
436
|
+
8. 核心缺陷 / 金手指:
|
|
437
|
+
- 读者代入感机制:[为什么读者会共情主角]
|
|
438
|
+
|
|
439
|
+
## 反派
|
|
440
|
+
### 主反派
|
|
441
|
+
- 姓名 / 类型:
|
|
442
|
+
- 核心动机:[一句话]
|
|
443
|
+
- 与主角动机冲突点:[...]
|
|
444
|
+
- "立体化"程度:[脸谱化 / 有成长弧 / 有隐藏过去]
|
|
445
|
+
- 是否有隐藏动机:[Y/N,什么动机]
|
|
446
|
+
|
|
447
|
+
### 次要反派(阶段性 BOSS)
|
|
448
|
+
- [每个列:姓名、动机、与主角冲突点、出现章节范围]
|
|
449
|
+
|
|
450
|
+
## 配角(≥ 3 个)
|
|
451
|
+
| 姓名 | 类型 | 核心动机 | 对主角的作用 | 出场章节范围 |
|
|
452
|
+
|---|---|---|---|---|
|
|
453
|
+
| 师父 | 导师 | [...] | [...] | [...] |
|
|
454
|
+
| ... |
|
|
455
|
+
|
|
456
|
+
## 角色动机交织(最关键!)
|
|
457
|
+
> 这一段是整个 characters.md 的核心:让 fancy-bible 能直接据此设计角色生态
|
|
458
|
+
|
|
459
|
+
- 主角 ↔ 主反派:[冲突的动机点]
|
|
460
|
+
- 主角 ↔ 配角 A:[...]
|
|
461
|
+
- 配角 A ↔ 配角 B:[...]
|
|
462
|
+
- 主反派 ↔ 配角 A:[...]
|
|
463
|
+
- 反派 ↔ 次要反派:[等级关系]
|
|
464
|
+
\`\`\`
|
|
465
|
+
|
|
466
|
+
### Step 4: 写 for_bible/relationships.md
|
|
467
|
+
|
|
468
|
+
\`\`\`markdown
|
|
469
|
+
# 关系网分析
|
|
470
|
+
|
|
471
|
+
## 核心关系表
|
|
472
|
+
| A | B | 关系类型 | 信任度(0-10) | 关系演变(起点→转折→终态) |
|
|
473
|
+
|---|---|---|---|---|
|
|
474
|
+
| ... |
|
|
475
|
+
|
|
476
|
+
## 关系类型清单
|
|
477
|
+
列出本书用到的所有关系类型(导师/朋友/敌人/对手/竞争者/恋人/家人/盟友等)
|
|
478
|
+
|
|
479
|
+
## 关系演变的关键事件
|
|
480
|
+
每个核心关系都列出"转折点章节"
|
|
481
|
+
\`\`\`
|
|
482
|
+
|
|
483
|
+
### Step 5: 写 for_bible/world.md
|
|
484
|
+
|
|
485
|
+
\`\`\`markdown
|
|
486
|
+
# 世界观卖点
|
|
487
|
+
|
|
488
|
+
## 该书世界观为什么吸引人
|
|
489
|
+
[一句话核心卖点]
|
|
490
|
+
|
|
491
|
+
## 设定易理解度
|
|
492
|
+
- 新读者多久能上手(章数):[...]
|
|
493
|
+
- 信息释放节奏:[前期详细 / 后期慢慢展开]
|
|
494
|
+
|
|
495
|
+
## 力量体系
|
|
496
|
+
- 境界链:[...]
|
|
497
|
+
- 力量获取方式:[...]
|
|
498
|
+
- 平衡机制:[...]
|
|
499
|
+
- 代价:[金手指有没有副作用]
|
|
500
|
+
|
|
501
|
+
## 该书世界观的可扩展性
|
|
502
|
+
[能写多长 / 续集空间]
|
|
503
|
+
\`\`\`
|
|
504
|
+
|
|
505
|
+
### Step 6: 写 for_outline/plot_structure.md
|
|
506
|
+
|
|
507
|
+
\`\`\`markdown
|
|
508
|
+
# 剧情结构分析
|
|
509
|
+
|
|
510
|
+
## 主线
|
|
511
|
+
- 一句话:
|
|
512
|
+
- 阶段划分:[阶段1(第 X-Y 章):..., 阶段2:..., 阶段3:..., 阶段4:...]
|
|
513
|
+
- 该书主线与模板的差异:[为什么这样设计能火]
|
|
514
|
+
|
|
515
|
+
## 副线(≥ 3 条)
|
|
516
|
+
| 线 | 名称 | 起点 | 收束 | 主/副 | 与主线交织点 |
|
|
517
|
+
|---|---|---|---|---|---|
|
|
518
|
+
| L2 | [...] | [...] | [...] | 副 | [...] |
|
|
519
|
+
| L3 | [...] | [...] | [...] | 副 | [...] |
|
|
520
|
+
| L4 | [...] | [...] | [...] | 副 | [...] |
|
|
521
|
+
|
|
522
|
+
## 多线交叉点
|
|
523
|
+
| 章节 | L1 | L2 | L3 | L4 | 交叉类型(汇合/分裂/呼应) |
|
|
524
|
+
|---|---|---|---|---|---|
|
|
525
|
+
| [...] | ✓ | ✓ | - | - | [...] |
|
|
526
|
+
\`\`\`
|
|
527
|
+
|
|
528
|
+
### Step 7: 写 for_outline/pacing.md
|
|
529
|
+
|
|
530
|
+
\`\`\`markdown
|
|
531
|
+
# 节奏分析
|
|
532
|
+
|
|
533
|
+
## 30/40/30 分布
|
|
534
|
+
[该书每章铺垫/兑现/余波占比实际是多少]
|
|
535
|
+
|
|
536
|
+
## 爽点频次(必须实际数据)
|
|
537
|
+
- 总爽点数:
|
|
538
|
+
- 平均章数/爽点:
|
|
539
|
+
- 频次区间:[Ch X-Y 是密集爽点期,Ch M-N 是平稳过渡期]
|
|
540
|
+
|
|
541
|
+
## 危机链
|
|
542
|
+
| 序号 | 章节范围 | 危机类型 | 升级幅度 |
|
|
543
|
+
|---|---|---|---|
|
|
544
|
+
| 1 | [...] | [...] | [...] |
|
|
545
|
+
| 2 | [...] | [...] | [...] |
|
|
546
|
+
| ... |
|
|
547
|
+
|
|
548
|
+
## 卷末高潮分布
|
|
549
|
+
| 卷 | 章范围 | 高潮类型 | 与模板的差异 |
|
|
550
|
+
|---|---|---|---|
|
|
551
|
+
| 1 | [...] | [...] | [...] |
|
|
552
|
+
\`\`\`
|
|
553
|
+
|
|
554
|
+
### Step 8: 写 for_outline/chapter_templates.md
|
|
555
|
+
|
|
556
|
+
\`\`\`markdown
|
|
557
|
+
# 章首/章尾钩子实战分析
|
|
558
|
+
|
|
559
|
+
## 章首钩子(7 种)的实际用法
|
|
560
|
+
| 钩子类型 | 使用频次 | 该书的典型案例(章号 + 摘要) |
|
|
561
|
+
|---|---|---|
|
|
562
|
+
| 悬念对话引子 | [...] | Ch X:... |
|
|
563
|
+
| 内心独白引子 | [...] | Ch Y:... |
|
|
564
|
+
| 反差场景引子 | [...] | ... |
|
|
565
|
+
| 未完成动作引子 | [...] | ... |
|
|
566
|
+
| 意象伏笔引子 | [...] | ... |
|
|
567
|
+
| 时间错位引子 | [...] | ... |
|
|
568
|
+
| 物件细节引子 | [...] | ... |
|
|
569
|
+
|
|
570
|
+
## 章尾钩子(13 种)的实际用法
|
|
571
|
+
[同样列表]
|
|
572
|
+
|
|
573
|
+
## 断章点技巧
|
|
574
|
+
[该书在哪一句断章效果最好]
|
|
575
|
+
\`\`\`
|
|
576
|
+
|
|
577
|
+
### Step 9: 写 for_outline/foreshadow_patterns.md
|
|
578
|
+
|
|
579
|
+
\`\`\`markdown
|
|
580
|
+
# 伏笔实战分析
|
|
581
|
+
|
|
582
|
+
## 信息差分布
|
|
583
|
+
| 章节 | 读者知道 | 角色不知道 | 伏笔类型 |
|
|
584
|
+
|---|---|---|---|
|
|
585
|
+
| [...] | ✓ | - | [...] |
|
|
586
|
+
|
|
587
|
+
## 该书的伏笔密度
|
|
588
|
+
- 总伏笔:
|
|
589
|
+
- 平均章数/伏笔:
|
|
590
|
+
- 高频伏笔类型:[最常用的 3-5 种]
|
|
591
|
+
|
|
592
|
+
## 期待感管理
|
|
593
|
+
- 该书什么时候给读者"小兑现":[...]
|
|
594
|
+
- 什么时候给"大兑现":[...]
|
|
595
|
+
- 兑现节奏与模板的差异:[...]
|
|
596
|
+
\`\`\`
|
|
597
|
+
|
|
598
|
+
### Step 10: 写 for_write/voice.md
|
|
599
|
+
|
|
600
|
+
\`\`\`markdown
|
|
601
|
+
# 文风指纹
|
|
602
|
+
|
|
603
|
+
## 句长分布
|
|
604
|
+
- 平均句长:[字数]
|
|
605
|
+
- 短句(<20 字)比例:[%]
|
|
606
|
+
- 中句(20-50 字)比例:[%]
|
|
607
|
+
- 长句(>50 字)比例:[%]
|
|
608
|
+
- 典型长句:[从该书摘 3 个]
|
|
609
|
+
|
|
610
|
+
## 长短句组合
|
|
611
|
+
- 短-长-短模式:[...]
|
|
612
|
+
- 段落平均句数:[...]
|
|
613
|
+
|
|
614
|
+
## 标点节奏
|
|
615
|
+
- 句号 : 逗号 : 问号 : 感叹号 = [比例]
|
|
616
|
+
|
|
617
|
+
## 分段模式
|
|
618
|
+
- 平均段长:[句数]
|
|
619
|
+
- 长段(>5 句)出现频率:[...]
|
|
620
|
+
- 单句成段频率:[...]
|
|
621
|
+
|
|
622
|
+
## 对话 vs 叙述
|
|
623
|
+
- 对话占比:[%]
|
|
624
|
+
- 对话平均字数:[...]
|
|
625
|
+
- 叙述段平均字数:[...]
|
|
626
|
+
|
|
627
|
+
## 标志性修辞
|
|
628
|
+
[列出该书反复用的 3-5 种修辞手法,各给一个例子]
|
|
629
|
+
\`\`\`
|
|
630
|
+
|
|
631
|
+
### Step 11: 写 for_write/sentence_patterns.md
|
|
632
|
+
|
|
633
|
+
\`\`\`markdown
|
|
634
|
+
# 标志性句式
|
|
635
|
+
|
|
636
|
+
## 该书反复用的 5-10 种句式
|
|
637
|
+
[每种句式:模板 + 该书典型例子 + 使用场景]
|
|
638
|
+
|
|
639
|
+
## 长短句组合模式
|
|
640
|
+
[列出该书的典型段落节奏模式]
|
|
641
|
+
|
|
642
|
+
## 段落开头的偏好
|
|
643
|
+
[该书喜欢怎么开段——时间/地点/对话/动作/感受/...]
|
|
644
|
+
\`\`\`
|
|
645
|
+
|
|
646
|
+
### Step 12: 写 for_write/phrase_bank/
|
|
647
|
+
|
|
648
|
+
四个文件:
|
|
649
|
+
- action.md:动作描写库(每种情绪/场景 5-10 种说法)
|
|
650
|
+
- psychology.md:心理描写库
|
|
651
|
+
- environment.md:环境描写库
|
|
652
|
+
- dialogue.md:对话标签库
|
|
653
|
+
|
|
654
|
+
每个文件结构:
|
|
655
|
+
\`\`\`markdown
|
|
656
|
+
# <情绪/场景> 描写库
|
|
657
|
+
|
|
658
|
+
## 紧张
|
|
659
|
+
- [...]
|
|
660
|
+
- [...]
|
|
661
|
+
|
|
662
|
+
## 愤怒
|
|
663
|
+
- [...]
|
|
664
|
+
|
|
665
|
+
## 喜悦
|
|
666
|
+
- [...]
|
|
667
|
+
\`\`\`
|
|
668
|
+
|
|
669
|
+
## 3. 重要约束
|
|
670
|
+
|
|
671
|
+
- 每份文件都必须有实际内容,不要只放标题
|
|
672
|
+
- 每条结论必须引用具体章节号或场景
|
|
673
|
+
- 反派/配角必须和主角一样详细,不能省
|
|
674
|
+
- 角色动机必须能解释"为什么这么做"(不是"做了这个动作")
|
|
675
|
+
- 量化数据必须实际估算(不能写"很多"/"少")
|
|
676
|
+
- 每份文件不少于 200 字
|
|
677
|
+
- 使用中文输出
|
|
678
|
+
|
|
679
|
+
## 4. 完成检查
|
|
680
|
+
|
|
681
|
+
写完所有文件后,检查每个文件存在且非空:
|
|
682
|
+
- 对标/${bookName}/_summary.md(≥ 200 字)
|
|
683
|
+
- 对标/${bookName}/for_bible/characters.md(≥ 500 字)
|
|
684
|
+
- 对标/${bookName}/for_bible/relationships.md(≥ 200 字)
|
|
685
|
+
- 对标/${bookName}/for_bible/world.md(≥ 200 字)
|
|
686
|
+
- 对标/${bookName}/for_outline/plot_structure.md(≥ 200 字)
|
|
687
|
+
- 对标/${bookName}/for_outline/pacing.md(≥ 200 字)
|
|
688
|
+
- 对标/${bookName}/for_outline/chapter_templates.md(≥ 200 字)
|
|
689
|
+
- 对标/${bookName}/for_outline/foreshadow_patterns.md(≥ 200 字)
|
|
690
|
+
- 对标/${bookName}/for_write/voice.md(≥ 200 字)
|
|
691
|
+
- 对标/${bookName}/for_write/sentence_patterns.md(≥ 200 字)
|
|
692
|
+
- 对标/${bookName}/for_write/phrase_bank/action.md(≥ 10 条)
|
|
693
|
+
- 对标/${bookName}/for_write/phrase_bank/psychology.md(≥ 10 条)
|
|
694
|
+
- 对标/${bookName}/for_write/phrase_bank/environment.md(≥ 10 条)
|
|
695
|
+
- 对标/${bookName}/for_write/phrase_bank/dialogue.md(≥ 10 条)
|
|
696
|
+
`;
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* 生成技法总结提示词。
|
|
701
|
+
*
|
|
702
|
+
* ## 为什么按"章节"派发的判定权交给模型
|
|
703
|
+
*
|
|
704
|
+
* 插件算得出"还差哪些章节",但**不能把章节名列进提示词**:
|
|
705
|
+
* 长篇几千章时,16 目录 × 200 章 = 3200 个文件名,中文文件名约 1 字 1 token,
|
|
706
|
+
* 光这一份清单就是 ~50K token 的静态提示词(实测 3200 章 → 71 KB,
|
|
707
|
+
* 16000 章 → 652 KB),而且它每一轮都跟着 agent 循环,占着上下文窗口不放。
|
|
708
|
+
*
|
|
709
|
+
* 所以提示词只给**目录**,并把"怎么判断哪些章节还没总结"写成明确步骤:
|
|
710
|
+
* 1. 读总结文件的**文件头**(两段 `---` 之间的 YAML)
|
|
711
|
+
* 2. 里面的 `已总结章节:` 就是已经总结过的章节名
|
|
712
|
+
* 3. 列出源文件目录下所有 `.md`(排除总结文件本身)
|
|
713
|
+
* 4. 差集 = 这次要总结的章节
|
|
714
|
+
*
|
|
715
|
+
* 提示词体积于是变成 O(目录数) —— 十几个目录就是十几行,
|
|
716
|
+
* 与小说多长**完全无关**。
|
|
717
|
+
*
|
|
718
|
+
* 插件仍然算得出"还差多少章",但那个数字只用来**报给用户看**,
|
|
719
|
+
* 不进提示词。
|
|
720
|
+
*
|
|
721
|
+
* ## 为什么头部清单是唯一事实源
|
|
722
|
+
*
|
|
723
|
+
* 原来是把整个技法目录丢给模型,让它自己读全部文件、自己判断哪些总结过。
|
|
724
|
+
* 那是**软约束** —— 模型不保证真的跳过,于是重复执行 summary 会把
|
|
725
|
+
* 已总结的章节再读一遍、再追加一遍,文件越滚越胖。
|
|
726
|
+
*
|
|
727
|
+
* 现在头部记录「已总结章节」,模型每总结完一章就追加进去。
|
|
728
|
+
* 于是"重复执行 summary 不重复总结"变成**可验证的事实**,
|
|
729
|
+
* 而不是寄望模型自觉。
|
|
730
|
+
*
|
|
731
|
+
* @param {object} opts
|
|
732
|
+
* @param {Array<{name: string, sourceCount?: number, pendingCount?: number}>} opts.tasks
|
|
733
|
+
* 每个要处理的技法目录:name=目录名,sourceCount=该目录共多少章节文件,
|
|
734
|
+
* pendingCount=按现有清单还差多少章(算不出来时省略)
|
|
735
|
+
* @param {number} [opts.remainingChapters] - 全部目录加起来还剩多少章(只用于给用户看)
|
|
736
|
+
* @returns {string} 提示词正文
|
|
737
|
+
*/
|
|
738
|
+
generateSummarySystemPrompt({ tasks, remainingChapters }) {
|
|
739
|
+
const list = Array.isArray(tasks) ? tasks : [];
|
|
740
|
+
const taskBlocks = list.map((task) => {
|
|
741
|
+
const sourceCount = Number.isFinite(task.sourceCount) ? task.sourceCount : null;
|
|
742
|
+
const pendingCount = Number.isFinite(task.pendingCount) ? task.pendingCount : null;
|
|
743
|
+
const scale = [];
|
|
744
|
+
if (sourceCount !== null) scale.push(`共有 ${sourceCount} 个章节文件`);
|
|
745
|
+
if (pendingCount !== null) scale.push(`按现有清单还差 ${pendingCount} 章`);
|
|
746
|
+
const scaleLine = scale.length > 0 ? `- 规模:${scale.join(' · ')}\n` : '';
|
|
747
|
+
const dir = task.name;
|
|
748
|
+
return `#### 技法目录:${dir}
|
|
749
|
+
|
|
750
|
+
- 源文件目录:\`对标/wiki/${dir}/\`
|
|
751
|
+
- 总结文件:\`对标/wiki/${dir}/${dir}-总结.md\`
|
|
752
|
+
${scaleLine}- **怎么找出这次要总结的章节**(每个目录都照这个流程走):
|
|
753
|
+
1. 打开总结文件 \`对标/wiki/${dir}/${dir}-总结.md\`,**只看文件头**(开头两段 \`---\` 之间的 YAML),里面 \`已总结章节:\` 下列的就是**已经总结过**的章节名(章节名 = 源文件名去掉 \`.md\` 后缀)。总结文件还不存在时,这一步跳过(等于一张白纸)
|
|
754
|
+
2. 列出源文件目录 \`对标/wiki/${dir}/\` 下所有 \`.md\` 文件,**排除** \`${dir}-总结.md\` 本身
|
|
755
|
+
3. 第 2 步的清单减掉第 1 步的清单,**差集**就是这次要总结的章节
|
|
756
|
+
4. **不要把章节清单写进你的计划或正文** —— 边读边处理,读一个处理一个`;
|
|
757
|
+
}).join('\n\n');
|
|
758
|
+
|
|
759
|
+
const checkList = list.map((task) => `- \`对标/wiki/${task.name}/${task.name}-总结.md\``).join('\n');
|
|
760
|
+
|
|
761
|
+
const totalPending = Number.isFinite(remainingChapters) ? remainingChapters : null;
|
|
762
|
+
const scaleNote = totalPending !== null
|
|
763
|
+
? `> 全部目录加起来,按现有清单还差 **${totalPending} 章**没总结。`
|
|
764
|
+
: '';
|
|
765
|
+
|
|
766
|
+
return `你是一位经验丰富、资历深厚、在小说领域有着独到见解并且逻辑思维缜密的小说技法分析师。
|
|
767
|
+
|
|
768
|
+
## 核心铁律
|
|
769
|
+
**每次调用都是全新流程**,不读取上下文,不读取之前的选择,不读 memory。
|
|
770
|
+
**只处理下面明确列出的技法目录**,不要自行扩大范围。
|
|
771
|
+
|
|
772
|
+
${scaleNote}
|
|
773
|
+
> ⚠️ 本提示词**故意不列具体章节名** —— 一本长篇可能几千章,列出来会把提示词撑爆。
|
|
774
|
+
> 哪些章节还没总结,由你按每个目录下方写的步骤**自己去目录里判断**。
|
|
775
|
+
|
|
776
|
+
## ⚠️ 语言强制要求
|
|
777
|
+
|
|
778
|
+
**所有输出内容必须是中文,包括:**
|
|
779
|
+
- 文件名:使用中文
|
|
780
|
+
- 标题:使用中文
|
|
781
|
+
- 正文内容:所有文字描述必须使用中文
|
|
782
|
+
- 表格内容:所有表格中的文字必须使用中文
|
|
783
|
+
- YAML 元数据:**键和值都必须用中文**
|
|
784
|
+
|
|
785
|
+
**禁止输出英文内容:**
|
|
786
|
+
- ❌ 英文描述、英文分析
|
|
787
|
+
- ❌ 英文 YAML 键
|
|
788
|
+
- ❌ 英文 YAML 值
|
|
789
|
+
- ❌ 中英混合
|
|
790
|
+
|
|
791
|
+
## ⚠️ 占位符说明
|
|
792
|
+
|
|
793
|
+
下方提示词中用 **\`[\`** 和 **\`]\`** 包裹的内容都是**占位符**,需要你根据读取到的文件内容进行推理和补充,不要直接输出占位符原文。
|
|
794
|
+
|
|
795
|
+
## 任务
|
|
796
|
+
|
|
797
|
+
请对下面每个技法目录里**尚未总结**的章节进行归纳、总结、提炼、挖掘、扩展,
|
|
798
|
+
提炼出通用的、可复用的写作技法。
|
|
799
|
+
|
|
800
|
+
### 本次要总结的技法目录
|
|
801
|
+
|
|
802
|
+
${taskBlocks}
|
|
803
|
+
|
|
804
|
+
### 操作要求
|
|
805
|
+
|
|
806
|
+
**增量写入方式**:为避免超出上下文限制,请采用增量写入方式,每个目录处理流程如下:
|
|
807
|
+
|
|
808
|
+
1. **按上述步骤算出这次要总结的章节**(读总结文件头 → 列源文件 → 求差集)
|
|
809
|
+
2. **逐文件读取提炼**:依次读取算出来的每个文件(文件名去掉 \`.md\` 后缀即为章节名),边读边提炼,**每读完一个文件立即将提炼结果追加写入总结文件**(使用文件路径 \`对标/wiki/[目录名]/[目录名]-总结.md\`)
|
|
810
|
+
3. **登记章节**:**每总结完一个章节,立刻把该章节名追加进总结文件头部的「已总结章节」列表**(见下方写入格式)。这是本次任务的关键产出 —— 插件靠它判断哪些章节已经总结过,下次执行时只派发剩下的
|
|
811
|
+
4. **不要写章节清单**:不要把"待总结章节列表"整体写进计划、进度汇报或正文里(章节可能上千个,写出来会挤爆上下文);只报"处理了 N 个"这类数字即可
|
|
812
|
+
5. **做不完就停**:按你的上下文能力处理,做到快满时停下,剩下的**下次执行 summary 会继续**(已完成的章节已经登记,不会重复)
|
|
813
|
+
6. **去重合并**:当多个文件提炼出相同技法时,在总结文件中合并,只保留一条
|
|
814
|
+
7. **继续下一目录**:完成一个目录后,继续处理下一个目录
|
|
815
|
+
|
|
816
|
+
### 写入格式
|
|
817
|
+
|
|
818
|
+
**第一步:写入文件头(仅当文件不存在时)**
|
|
819
|
+
|
|
820
|
+
每次追加写入前,先检查总结文件是否存在。如果不存在,则先写入以下内容:
|
|
821
|
+
|
|
822
|
+
\`\`\`yaml
|
|
823
|
+
---
|
|
824
|
+
标题: [目录名]-总结
|
|
825
|
+
标签: [总结, 目录名]
|
|
826
|
+
已总结章节:
|
|
827
|
+
- [已总结的章节名,去掉 .md 后缀]
|
|
828
|
+
---
|
|
829
|
+
# [目录名]技法总结
|
|
830
|
+
|
|
831
|
+
## 核心技法
|
|
832
|
+
\`\`\`
|
|
833
|
+
|
|
834
|
+
**第二步:追加技法内容(每次提炼后)**
|
|
835
|
+
|
|
836
|
+
如果文件已存在,则在 \`## 核心技法\` 章节下追加以下内容:
|
|
837
|
+
|
|
838
|
+
\`\`\`yaml
|
|
839
|
+
### [提炼出的技法名称]
|
|
840
|
+
- 来源章节:[来自哪些文件]
|
|
841
|
+
- 核心要点:[技法的核心内容]
|
|
842
|
+
- 使用场景:[在哪些情况下使用]
|
|
843
|
+
- 应用方式:[如何应用]
|
|
844
|
+
\`\`\`
|
|
845
|
+
|
|
846
|
+
**第三步:登记已总结章节(每总结完一章立刻做)**
|
|
847
|
+
|
|
848
|
+
在文件头部的 \`已总结章节:\` 列表下**追加**一行(不要覆盖已有条目,
|
|
849
|
+
不要重复添加同一个章节名):
|
|
850
|
+
|
|
851
|
+
\`\`\`yaml
|
|
852
|
+
已总结章节:
|
|
853
|
+
- [第1个章节名]
|
|
854
|
+
- [第2个章节名]
|
|
855
|
+
\`\`\`
|
|
856
|
+
|
|
857
|
+
章节名 = 源文件名去掉 \`.md\` 后缀(例如源文件 \`[书名]-[技法名]-第3章.md\`
|
|
858
|
+
→ 章节名 \`[书名]-[技法名]-第3章\`)。
|
|
859
|
+
|
|
860
|
+
## 闭环检查
|
|
861
|
+
|
|
862
|
+
在完成所有目录的总结后,必须进行检查:
|
|
863
|
+
|
|
864
|
+
**文件存在性检查**:确认以下每个目录下都存在总结文件:
|
|
865
|
+
${checkList}
|
|
866
|
+
|
|
867
|
+
如果发现某个目录下缺少总结文件,必须立即生成。
|
|
868
|
+
|
|
869
|
+
**章节登记检查**:确认每个总结文件的 \`已总结章节:\` 列表包含了该目录下
|
|
870
|
+
**你已经处理过的全部章节**。少登记一个,下次执行 summary 就会重复总结它。
|
|
871
|
+
|
|
872
|
+
## ⚠️ 重要提醒
|
|
873
|
+
|
|
874
|
+
- **去重原则**:如果多个章节使用了相同技法,只总结一次
|
|
875
|
+
- **不越界**:只处理算出来的差集,不要顺手处理目录里其它章节
|
|
876
|
+
- **格式要求**:每个总结文件必须包含 YAML 元数据
|
|
877
|
+
- **内容要求**:总结内容必须是具体、可操作的写作技法,不是泛泛而谈的描述
|
|
878
|
+
|
|
879
|
+
请开始执行总结任务,完成后输出总结报告。`;
|
|
880
|
+
},
|
|
881
|
+
};
|