@cloud411716/fancy-webnovel 2.2.0 → 3.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.
- package/client.js +201 -15
- package/cordis.patch.yml +5 -0
- 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 +11 -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 +29 -4
- 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 +143 -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
|
@@ -8,7 +8,9 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
import { renderInputSnapshot } from './scripts/graph-utils.js';
|
|
9
9
|
import { pickContractSections } from './scripts/stage-contract.js';
|
|
10
10
|
import { readingPowerLegend } from './scripts/reading-power.js';
|
|
11
|
+
import { referenceLogPromptBlock } from './scripts/reference-log.js';
|
|
11
12
|
import { deslopLegend } from './scripts/ai-patterns.js';
|
|
13
|
+
import { normalizeLegacyTopicData, TOPIC_REQUIRED_FIELDS } from '#topic-fields';
|
|
12
14
|
|
|
13
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
16
|
const REFERENCES_DIR = join(__dirname, 'references');
|
|
@@ -89,17 +91,53 @@ function joinField(v, sep = ';', fallback = '无') {
|
|
|
89
91
|
* 那份快照是骨架校验器验过的,更权威。这里只在"骨架产物还没落盘"时兜底。
|
|
90
92
|
*/
|
|
91
93
|
function snapshotFromTopicData(topicData) {
|
|
92
|
-
const t = topicData || {};
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
94
|
+
const t = normalizeLegacyTopicData(topicData || {});
|
|
95
|
+
const out = {};
|
|
96
|
+
for (const id of TOPIC_REQUIRED_FIELDS) {
|
|
97
|
+
if (id === 'hard_constraints' || id === 'anti_tropes') {
|
|
98
|
+
out[id] = Array.isArray(t[id]) ? t[id] : [];
|
|
99
|
+
} else {
|
|
100
|
+
out[id] = t[id];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 把提示词里**行内反引号**包着的项目内路径改成绝对路径。
|
|
108
|
+
*
|
|
109
|
+
* ## 为什么必须改
|
|
110
|
+
*
|
|
111
|
+
* 插件把参考文件(词表 / 公式 / 阶段指导 / 字段模板)拷到**项目根**的
|
|
112
|
+
* `references/` 与 `模板/` 下,但提示词里写的是相对路径
|
|
113
|
+
* (`references/formulas-skeleton.json`)。
|
|
114
|
+
*
|
|
115
|
+
* 相对路径的前提是"当前目录 = 项目根"。这个前提一旦不成立,
|
|
116
|
+
* 路径就会被拼到别的目录下 —— 典型的是拼成
|
|
117
|
+
* `<项目根>/设定/references/formulas-skeleton.json`:
|
|
118
|
+
* `设定/` 是 llm-wiki 的项目目录,插件**从没**往那里放过参考文件,
|
|
119
|
+
* 于是读不到,模型要么空手写、要么以为参考文件缺失。
|
|
120
|
+
*
|
|
121
|
+
* 插件知道项目根,所以在运行时把路径换成绝对的:基准被钉死,
|
|
122
|
+
* 模型不用猜,工具用哪种读法都对。
|
|
123
|
+
*
|
|
124
|
+
* ## 只改行内反引号里的
|
|
125
|
+
*
|
|
126
|
+
* - **代码块里的命令保持相对**:`node references/upstream-query.js …`
|
|
127
|
+
* 是在项目根下执行的命令,绝对化会与"在项目根运行"的语义打架;
|
|
128
|
+
* - **产物路径保持相对**(`设定/raw/sources/…`):那是模型要写的地方,
|
|
129
|
+
* 相对项目根,和校验器读的路径一致;
|
|
130
|
+
* - **`对标/wiki/` 保持相对**:那是 llm-wiki 的项目,相对路径才是它要的形态。
|
|
131
|
+
*
|
|
132
|
+
* @param {string} text 提示词正文
|
|
133
|
+
* @param {string} projectRoot 项目根绝对路径(没有就原样返回)
|
|
134
|
+
* @returns {string} 绝对化后的正文
|
|
135
|
+
*/
|
|
136
|
+
export function absolutizeInlinePaths(text, projectRoot) {
|
|
137
|
+
if (!text || !projectRoot) return text;
|
|
138
|
+
const root = String(projectRoot).replace(/\/+$/, '');
|
|
139
|
+
if (!root) return text;
|
|
140
|
+
return text.replace(/`((?:references|模板)\/[^`\n]*)`/g, (m, p) => '`' + root + '/' + p + '`');
|
|
103
141
|
}
|
|
104
142
|
|
|
105
143
|
/** 标量字段的安全取值:undefined/null 会导致 prompt 里出现字面量 "undefined" */
|
|
@@ -123,43 +161,67 @@ export const storyBuilder = {
|
|
|
123
161
|
*
|
|
124
162
|
* 数值给默认值,是为了让"调用方忘了传某个字段"退化成 0 而不是崩。
|
|
125
163
|
*/
|
|
126
|
-
notify({ type, err, phase, id, fileCount, missingCount, queueCount, detail, source, extra,
|
|
164
|
+
notify({ type, err, phase, id, fileCount, missingCount, queueCount, detail, source, extra, stage, missing, next,
|
|
127
165
|
count = 0, failed = [], written = 0, clean = 0, passed = 0, rolledBack = 0, errored = 0,
|
|
128
166
|
reused = 0 }) {
|
|
129
167
|
switch (type) {
|
|
168
|
+
case 'help':
|
|
169
|
+
return `📖 故事构建 用法
|
|
170
|
+
|
|
171
|
+
/fancy-build scale - 故事规模:总章数、总字数、卷、幕、配额
|
|
172
|
+
/fancy-build macro - 世界观、故事、主线/支线、结局、角色功能位
|
|
173
|
+
/fancy-build cast - 人物、势力、地点、道具、冲突、伏笔、爽点、高潮、信息差
|
|
174
|
+
/fancy-build rhythm - 章节地图:逐章写人物、地点、冲突、钩子、伏笔/爽点动作
|
|
175
|
+
/fancy-build outline - 细纲和场景;示例:outline CH001 / outline 1-10 / outline all
|
|
176
|
+
/fancy-build write - 正文和章末交接;示例:write CH001 / write 1-10 / write all
|
|
177
|
+
/fancy-build deslop - 去 AI 味;示例:deslop CH001 / deslop 1-10 / deslop all
|
|
178
|
+
/fancy-build help - 显示本帮助
|
|
179
|
+
`;
|
|
180
|
+
|
|
181
|
+
case 'v2_done':
|
|
182
|
+
return err || extra || '✅ 阶段完成。';
|
|
183
|
+
|
|
184
|
+
case 'v2_failed': {
|
|
185
|
+
const text = err || extra || '阶段未完成';
|
|
186
|
+
return /^[❌⚠️]/.test(text) ? text : '❌ ' + text;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case 'v2_info':
|
|
190
|
+
return err || extra || '';
|
|
191
|
+
|
|
130
192
|
case 'cancelled':
|
|
131
|
-
return '🚫
|
|
193
|
+
return '🚫 操作已取消。';
|
|
132
194
|
|
|
133
195
|
case 'no_provider':
|
|
134
|
-
return '⚠️
|
|
196
|
+
return '⚠️ 当前运行环境不支持交互式提问。请在 dsh-tui 中运行该命令,或确认 Web UI 已正确加载交互组件。';
|
|
135
197
|
|
|
136
198
|
case 'no_project_root':
|
|
137
|
-
return '❌
|
|
199
|
+
return '❌ 无法确定当前项目:' + (err || '会话未提供当前工作目录') + '。请在项目目录下运行,或用 /fancy-project use <名字|UUID> 指定当前项目。';
|
|
138
200
|
|
|
139
201
|
case 'ask_failed':
|
|
140
|
-
return '❌
|
|
202
|
+
return '❌ 交互提问失败:' + (err || '未知错误');
|
|
141
203
|
|
|
142
204
|
case 'not_initialized':
|
|
143
|
-
return '⚠️
|
|
205
|
+
return '⚠️ 当前没有可用项目。请先运行 /fancy-bootstrap 初始化工作区,再运行 /fancy-topic setup 创建项目。';
|
|
144
206
|
|
|
145
207
|
case 'config_corrupt':
|
|
146
|
-
return '❌ ' + (err || '.fancy-deployed
|
|
208
|
+
return '❌ ' + (err || '无法解析 .fancy-deployed 状态文件。') + ' 请检查文件内容,或重新运行 /fancy-bootstrap 初始化工作区。';
|
|
147
209
|
|
|
148
210
|
case 'no_project_root':
|
|
149
211
|
return '❌ ' + (err || '无法确定项目目录');
|
|
150
212
|
|
|
151
213
|
case 'no_topic':
|
|
152
|
-
return '⚠️ 未找到选题信息。请先运行 /fancy-topic
|
|
214
|
+
return '⚠️ 未找到选题信息。请先运行 /fancy-topic 完成选题。';
|
|
153
215
|
|
|
154
216
|
case 'setup_id_unavailable': {
|
|
155
217
|
// 分类原因 → 人话 + **针对该原因**的排查步骤。
|
|
156
|
-
// 此前这里只有一句"未设置 setup_id,请先运行 /fancy-
|
|
218
|
+
// 此前这里只有一句"未设置 setup_id,请先运行 /fancy-build skeleton",
|
|
157
219
|
// 而用户正是运行着这条命令 —— 死胡同,且把真实原因(token 无效 / 服务没起)藏了。
|
|
158
220
|
// 注意:步骤必须按原因分流。给"连不上服务"的人列一堆 token 步骤,
|
|
159
221
|
// 比不给步骤更糟 —— 会把人引到错误的方向上去。
|
|
160
222
|
const reasonText = {
|
|
161
223
|
unauthorized: 'llm-wiki 拒绝了请求(token 无效或服务端未配置 token)',
|
|
162
|
-
network: '
|
|
224
|
+
network: '无法连接 llm-wiki 服务',
|
|
163
225
|
api_error: 'llm-wiki 返回了错误',
|
|
164
226
|
unexpected_response: 'llm-wiki 返回了非预期结构'
|
|
165
227
|
};
|
|
@@ -167,15 +229,15 @@ export const storyBuilder = {
|
|
|
167
229
|
unauthorized: [
|
|
168
230
|
'1. 在 llm-wiki 的「设置 → API + MCP」里填「访问令牌」(可直接点「生成新 token」)',
|
|
169
231
|
'2. 保存设置后,把同一个 token 写进本项目 .fancy-deployed 的 llm_wiki_token 字段',
|
|
170
|
-
'3. 确认 llm-wiki
|
|
232
|
+
'3. 确认 llm-wiki 正在运行:curl -s -o /dev/null -w "%{http_code}" http://localhost:19828/api/v1/health'
|
|
171
233
|
],
|
|
172
234
|
network: [
|
|
173
|
-
'1. 确认 llm-wiki
|
|
235
|
+
'1. 确认 llm-wiki 正在运行:curl -s -o /dev/null -w "%{http_code}" http://localhost:19828/api/v1/health',
|
|
174
236
|
'2. 确认 .fancy-deployed 里的 llm_wiki_url 指向正确地址(默认 http://localhost:19828/api/v1)',
|
|
175
237
|
'3. 启动 llm-wiki 后重试本命令'
|
|
176
238
|
],
|
|
177
239
|
api_error: [
|
|
178
|
-
'1.
|
|
240
|
+
'1. 查看 llm-wiki 日志或控制台输出,确认服务端返回的具体错误',
|
|
179
241
|
'2. 确认 .fancy-deployed 里的 llm_wiki_url 与 llm_wiki_token 正确'
|
|
180
242
|
],
|
|
181
243
|
unexpected_response: [
|
|
@@ -190,10 +252,10 @@ export const storyBuilder = {
|
|
|
190
252
|
const head = '❌ 未能建立 llm-wiki 设定项目:' + (reasonText[err] || err || '未知原因');
|
|
191
253
|
const detailLine = detail ? '\n详情:' + detail : '';
|
|
192
254
|
const consequence =
|
|
193
|
-
'\n\n后果:llm-wiki
|
|
194
|
-
'本阶段产物不会被 ingest 纳管,后续 /fancy-
|
|
255
|
+
'\n\n后果:llm-wiki 尚未识别本项目的「设定」目录,' +
|
|
256
|
+
'本阶段产物不会被 ingest 纳管,后续 /fancy-build maintain sync 与 maintain lint 也会失败。';
|
|
195
257
|
const tail = '\n' + (steps.length + 1) +
|
|
196
|
-
'.
|
|
258
|
+
'. 处理完成后重新执行本命令,插件会自动将「设定」目录创建为 llm-wiki 项目。';
|
|
197
259
|
return head + detailLine + consequence + '\n处理办法:\n' + steps.join('\n') + tail;
|
|
198
260
|
}
|
|
199
261
|
|
|
@@ -207,10 +269,12 @@ export const storyBuilder = {
|
|
|
207
269
|
}
|
|
208
270
|
|
|
209
271
|
case 'deploy_failed':
|
|
210
|
-
return '❌ 阶段文件部署失败:' + (err || '未知错误') + '
|
|
272
|
+
return '❌ 阶段文件部署失败:' + (err || '未知错误') + '。请检查磁盘空间和目录权限后重试。';
|
|
211
273
|
|
|
212
274
|
case 'schema_copy_failed':
|
|
213
|
-
return '
|
|
275
|
+
return '❌ 设定目录准备失败:' + (detail || '未知原因') +
|
|
276
|
+
'。\n\n请确认 llm-wiki 正在运行,且 .fancy-deployed 里的 llm_wiki_url / llm_wiki_token 正确。' +
|
|
277
|
+
'插件会先通过 llm-wiki 重建“设定”项目;如果服务不可用,插件不会以空目录继续执行。';
|
|
214
278
|
|
|
215
279
|
case 'llm_wiki_not_configured':
|
|
216
280
|
return '⚠️ llm_wiki 未配置。请在 .fancy-deployed 中配置 llm_wiki_url 和 llm_wiki_token';
|
|
@@ -224,14 +288,14 @@ export const storyBuilder = {
|
|
|
224
288
|
// "does not identify the settlement of any particular message")。
|
|
225
289
|
// 说"阶段开始"会让用户以为模型已经在写;实际 DeepSeek 侧 401 / 限流 / 超时,
|
|
226
290
|
// 插件这一侧完全无感,错误只会出现在会话流里。
|
|
227
|
-
return `✅ ${phase}
|
|
291
|
+
return `✅ ${phase}提示词已发送至会话模型,产物将由模型生成。` + (extra ? '\n\n' + extra : '');
|
|
228
292
|
|
|
229
293
|
case 'flesh_incomplete':
|
|
230
294
|
// 血肉阶段没跑完时不能用"阶段开始"的说法(更不能用 ✅):
|
|
231
295
|
// 2026-10 tt5 实测过静默失败——少产出一个文件、全量校验从没跑过,
|
|
232
296
|
// 而插件早就返回了"阶段开始"。现在血肉阶段改成插件逐批派活,
|
|
233
297
|
// 没过的批次由插件用脚本判出来并回喂重试,仍没过就把清单摆出来。
|
|
234
|
-
return '
|
|
298
|
+
return '❌ 血肉阶段未完成:部分文件未通过校验,或全量校验(--all)未通过。'
|
|
235
299
|
+ (extra ? '\n\n' + extra : '');
|
|
236
300
|
|
|
237
301
|
case 'flesh_complete':
|
|
@@ -239,17 +303,22 @@ export const storyBuilder = {
|
|
|
239
303
|
return '✅ 血肉阶段完成:8 个 JSON 齐全,全量校验(--all)已通过。'
|
|
240
304
|
+ (extra ? '\n\n' + extra : '');
|
|
241
305
|
|
|
306
|
+
case 'stage_dependency_missing':
|
|
307
|
+
return '❌ ' + (stage || '当前') + '阶段未启动:前置的' + (missing || '上游') + '阶段尚未生成。'
|
|
308
|
+
+ (detail ? '(' + detail + ')' : '')
|
|
309
|
+
+ '\n\n请先执行 `' + (next || '/fancy-build help') + '`,等该阶段完成并通过校验后,再执行当前命令。';
|
|
310
|
+
|
|
242
311
|
case 'upstream_not_ready':
|
|
243
312
|
// 节奏阶段的上游门禁没过:继续跑只会产出废数据,必须在这里挡住
|
|
244
|
-
return '
|
|
245
|
-
+ '\n\n
|
|
246
|
-
+ '
|
|
313
|
+
return '❌ 节奏阶段未启动:' + (err || '上游阶段未通过全量校验')
|
|
314
|
+
+ '\n\n节奏阶段依赖骨架和血肉两个上游,且两者的全量校验(--all)都必须已通过。'
|
|
315
|
+
+ '请先完成上游阶段,并确认各自的 .stage-status.json 中 all_check.status = PASSED,再执行节奏阶段。'
|
|
247
316
|
+ (extra ? '\n\n' + extra : '');
|
|
248
317
|
|
|
249
318
|
case 'rhythm_incomplete':
|
|
250
319
|
// 节奏阶段没跑完时不能用"阶段开始"的说法(更不能用 ✅):
|
|
251
320
|
// 节奏的绝大多数校验是跨文件口径的,单文件过了不代表阶段过了。
|
|
252
|
-
return '
|
|
321
|
+
return '❌ 节奏阶段未完成:部分批次未通过校验,或全量校验(--all)未通过。'
|
|
253
322
|
+ (extra ? '\n\n' + extra : '');
|
|
254
323
|
|
|
255
324
|
case 'rhythm_complete':
|
|
@@ -259,7 +328,7 @@ export const storyBuilder = {
|
|
|
259
328
|
case 'skeleton_incomplete':
|
|
260
329
|
// 骨架阶段没跑完时不能用"阶段开始"的说法(更不能用 ✅):
|
|
261
330
|
// 引用完整性 / 图连通性 / 配额一致性都要全量校验才判得了。
|
|
262
|
-
return '
|
|
331
|
+
return '❌ 骨架阶段未完成:部分批次未通过校验,或全量校验(--all)未通过。'
|
|
263
332
|
+ (extra ? '\n\n' + extra : '');
|
|
264
333
|
|
|
265
334
|
case 'skeleton_complete':
|
|
@@ -272,46 +341,48 @@ export const storyBuilder = {
|
|
|
272
341
|
// 用户会以为没有需要写的章,实际可能是全书都没写。
|
|
273
342
|
return '✅ 写作阶段完成:新写 ' + count + ' 章' +
|
|
274
343
|
(reused > 0 ? ',复用 ' + reused + ' 章(已复检通过)' : '') +
|
|
275
|
-
'
|
|
344
|
+
';全部章节逐章校验通过。' + (extra ? '\n\n' + extra : '');
|
|
276
345
|
|
|
277
346
|
case 'writing_incomplete':
|
|
278
|
-
return '
|
|
347
|
+
return '❌ 写作阶段结束,但有 ' + failed.length + ' 章未通过校验(已写 ' + written + ' 章' +
|
|
279
348
|
(reused > 0 ? ',复用 ' + reused + ' 章(已复检通过)' : '') +
|
|
280
349
|
')。未通过章号:' + failed.join('、') +
|
|
281
|
-
'
|
|
350
|
+
'。请修复对应章节后重新执行本命令;已完成的章节将自动跳过。' + (extra ? '\n\n' + extra : '');
|
|
282
351
|
|
|
283
352
|
case 'deslop_no_chapters':
|
|
284
|
-
return '
|
|
285
|
-
+ '(目录:' + (extra || '未知') + ')请先运行 /fancy-
|
|
353
|
+
return '❌ 去味阶段没有可处理的章节:写作目录中未找到 `第NNN章.md` 文件。'
|
|
354
|
+
+ '(目录:' + (extra || '未知') + ')请先运行 /fancy-build write all 生成正文。';
|
|
286
355
|
|
|
287
356
|
case 'deslop_done':
|
|
288
|
-
return `✅ 去味阶段完成:${count} 章已处理(${clean}
|
|
357
|
+
return `✅ 去味阶段完成:${count} 章已处理(${clean} 章原本已符合要求,${passed} 章改写后复扫通过)。`
|
|
289
358
|
+ (extra ? '\n\n' + extra : '');
|
|
290
359
|
|
|
291
360
|
case 'deslop_incomplete':
|
|
292
|
-
return
|
|
361
|
+
return `❌ 去味阶段结束,但有 ${failed.length} 章未通过去味校验(其中 ${rolledBack} 章改动超 35% 已被回滚,${errored} 章执行出错)。`
|
|
293
362
|
+ '问题章号:' + (failed.length ? failed.join('、') : '无') + '。'
|
|
294
363
|
+ '原文已备份在 `设定/raw/sources/写作/.revisions/`,报告见 `审查报告/`。'
|
|
295
364
|
+ (extra ? '\n\n' + extra : '');
|
|
296
365
|
|
|
297
366
|
case 'no_subcommand':
|
|
298
|
-
return `⚠️
|
|
299
|
-
|
|
300
|
-
/fancy-
|
|
301
|
-
/fancy-
|
|
302
|
-
/fancy-
|
|
303
|
-
/fancy-
|
|
304
|
-
/fancy-
|
|
305
|
-
/fancy-
|
|
367
|
+
return `⚠️ 请指定阶段:
|
|
368
|
+
|
|
369
|
+
/fancy-build scale
|
|
370
|
+
/fancy-build macro
|
|
371
|
+
/fancy-build cast
|
|
372
|
+
/fancy-build rhythm
|
|
373
|
+
/fancy-build outline [CH001 | 1-10 | all]
|
|
374
|
+
/fancy-build write [CH001 | 1-10 | all]
|
|
375
|
+
/fancy-build deslop [CH001 | 1-10 | all]
|
|
376
|
+
/fancy-build help`;
|
|
306
377
|
|
|
307
378
|
case 'no_sources_dir':
|
|
308
|
-
return '⚠️ 未找到 raw/sources
|
|
379
|
+
return '⚠️ 未找到 raw/sources 目录。请先执行骨架、血肉或节奏阶段,生成设定文件。';
|
|
309
380
|
|
|
310
381
|
case 'no_files_to_ingest':
|
|
311
|
-
return '⚠️ raw/sources
|
|
382
|
+
return '⚠️ raw/sources 目录中未找到可 ingest 的文件。';
|
|
312
383
|
|
|
313
384
|
case 'ingest_started':
|
|
314
|
-
return `✅ 已启动 ingest,共 ${fileCount}
|
|
385
|
+
return `✅ 已启动 ingest,共 ${fileCount} 个文件正在处理。稍后可执行 /fancy-build maintain lint 进行检查。` + (extra ? '\n\n' + extra : '');
|
|
315
386
|
|
|
316
387
|
case 'ingest_failed':
|
|
317
388
|
return '❌ ingest 失败:' + (err || '未知错误');
|
|
@@ -319,6 +390,32 @@ export const storyBuilder = {
|
|
|
319
390
|
case 'ingest_not_ready':
|
|
320
391
|
return `⚠️ ingest 尚未完成。当前有 ${missingCount} 个文件未处理,${queueCount} 个任务进行中。请等待 ingest 结束后再执行分析。`;
|
|
321
392
|
|
|
393
|
+
case 'sync_done':
|
|
394
|
+
return '✅ 故事对象已同步到 llm_wiki。' + (extra ? '\n\n' + extra : '');
|
|
395
|
+
|
|
396
|
+
case 'sync_failed':
|
|
397
|
+
return '❌ 同步到 llm_wiki 失败。' + (extra ? '\n\n' + extra : '');
|
|
398
|
+
|
|
399
|
+
case 'query_done':
|
|
400
|
+
return extra || '💬 查询完成';
|
|
401
|
+
|
|
402
|
+
case 'lint_done':
|
|
403
|
+
return '❌ 检查发现问题' + (extra ? '\n\n' + extra : '') +
|
|
404
|
+
'\n\n💡 可执行 `/fancy-build fix`:插件会把问题归因到骨架/血肉/节奏,发给 LLM 修复、跑阶段校验并重新 sync。';
|
|
405
|
+
|
|
406
|
+
case 'lint_clean':
|
|
407
|
+
return '✅ 检查通过' + (extra ? '\n\n' + extra : '');
|
|
408
|
+
|
|
409
|
+
case 'stage_materialize_failed':
|
|
410
|
+
return '❌ ' + (phase || '当前') + '阶段数据已完成,但同步到 llm_wiki 失败。'
|
|
411
|
+
+ (extra ? '\n\n' + extra : '');
|
|
412
|
+
|
|
413
|
+
case 'patch_done':
|
|
414
|
+
return extra || '✅ patch 完成';
|
|
415
|
+
|
|
416
|
+
case 'patch_cancelled':
|
|
417
|
+
return '已取消 patch,未修改任何文件。';
|
|
418
|
+
|
|
322
419
|
default:
|
|
323
420
|
return String(type);
|
|
324
421
|
}
|
|
@@ -332,10 +429,17 @@ export const storyBuilder = {
|
|
|
332
429
|
id: 'subcommand',
|
|
333
430
|
question: '请选择要执行的阶段:',
|
|
334
431
|
options: [
|
|
335
|
-
{ label: '
|
|
336
|
-
{ label: '
|
|
337
|
-
{ label: '
|
|
338
|
-
{ label: '
|
|
432
|
+
{ label: 'scale', description: '10-scale:只算章/卷/幕/配额/节奏预算' },
|
|
433
|
+
{ label: 'macro', description: '20-macro:世界观、卷纲、幕纲、主支线、结局、角色功能位' },
|
|
434
|
+
{ label: 'cast', description: '30-cast:人物、势力、地点、冲突、伏笔、爽点、关系、信息差' },
|
|
435
|
+
{ label: 'rhythm', description: '40-rhythm:章节地图;--full 同时生成全部细纲' },
|
|
436
|
+
{ label: 'outline', description: '40-rhythm:单章细纲 outline CH001 / outline all' },
|
|
437
|
+
{ label: 'writing', description: '50-writing:按单章细纲写正文' },
|
|
438
|
+
{ label: 'sync', description: '同步阶段闭包到 llm_wiki(首次正式同步在 30-cast)' },
|
|
439
|
+
{ label: 'query', description: '向 llm_wiki 提问:query <问题>' },
|
|
440
|
+
{ label: 'lint', description: 'schema / link / 语义检查(30-cast 通过后)' },
|
|
441
|
+
{ label: 'patch', description: '按 manifest 依赖图修订:patch <意见>' },
|
|
442
|
+
{ label: 'help', description: '显示全部子命令说明' },
|
|
339
443
|
],
|
|
340
444
|
};
|
|
341
445
|
},
|
|
@@ -349,7 +453,7 @@ export const storyBuilder = {
|
|
|
349
453
|
genre,
|
|
350
454
|
target_words,
|
|
351
455
|
target_chapters,
|
|
352
|
-
|
|
456
|
+
positioning,
|
|
353
457
|
core_conflict,
|
|
354
458
|
hard_constraints,
|
|
355
459
|
anti_tropes,
|
|
@@ -363,7 +467,7 @@ export const storyBuilder = {
|
|
|
363
467
|
prompt = prompt.replace(/\$\{book_name\}/g, textField(book_name));
|
|
364
468
|
prompt = prompt.replace(/\$\{target_words\}/g, textField(target_words));
|
|
365
469
|
prompt = prompt.replace(/\$\{target_chapters\}/g, textField(target_chapters));
|
|
366
|
-
prompt = prompt.replace(/\$\{
|
|
470
|
+
prompt = prompt.replace(/\$\{positioning\}/g, textField(positioning));
|
|
367
471
|
prompt = prompt.replace(/\$\{core_conflict\}/g, textField(core_conflict));
|
|
368
472
|
prompt = prompt.replace(/\$\{hard_constraints\}/g, joinField(hard_constraints));
|
|
369
473
|
prompt = prompt.replace(/\$\{anti_tropes\}/g, joinField(anti_tropes));
|
|
@@ -379,7 +483,7 @@ export const storyBuilder = {
|
|
|
379
483
|
* 缺省时退回 topicData 拼一份(骨架产物还没落盘的兜底)。
|
|
380
484
|
*/
|
|
381
485
|
generateFleshSystemPrompt({ topicData, wikiReferences, inputSnapshot }) {
|
|
382
|
-
const { book_name, genre, target_chapters,
|
|
486
|
+
const { book_name, genre, target_chapters, positioning, hard_constraints, anti_tropes } = topicData;
|
|
383
487
|
|
|
384
488
|
// 读取血肉阶段提示词模板(prompts/02-flesh-prompt.md)
|
|
385
489
|
let prompt = cleanPromptForLLM(readBuiltInReference('02-flesh-prompt.md'));
|
|
@@ -388,7 +492,6 @@ export const storyBuilder = {
|
|
|
388
492
|
prompt = prompt.replace(/\$\{book_name\}/g, textField(book_name));
|
|
389
493
|
prompt = prompt.replace(/\$\{genre\}/g, textField(genre));
|
|
390
494
|
prompt = prompt.replace(/\$\{target_chapters\}/g, textField(target_chapters));
|
|
391
|
-
prompt = prompt.replace(/\$\{core_summary\}/g, textField(core_summary));
|
|
392
495
|
// 血肉 prompt 文件暂未引用,但保持幂等
|
|
393
496
|
prompt = prompt.replace(/\$\{hard_constraints\}/g, joinField(hard_constraints));
|
|
394
497
|
prompt = prompt.replace(/\$\{anti_tropes\}/g, joinField(anti_tropes));
|
|
@@ -432,9 +535,12 @@ export const storyBuilder = {
|
|
|
432
535
|
|
|
433
536
|
const contract = readBuiltInReference('02-flesh-prompt.md');
|
|
434
537
|
const snapshot = o.inputSnapshot || snapshotFromTopicData(o.topicData || {});
|
|
435
|
-
const withSnapshot =
|
|
436
|
-
|
|
437
|
-
|
|
538
|
+
const withSnapshot = absolutizeInlinePaths(
|
|
539
|
+
contract
|
|
540
|
+
.replace(/\$\{input_snapshot_block\}/g, renderInputSnapshot(snapshot))
|
|
541
|
+
.replace(/\$\{benchmark_refs_block\}/g, o.benchmarkRefs || ''),
|
|
542
|
+
o.projectRoot
|
|
543
|
+
);
|
|
438
544
|
|
|
439
545
|
// 首轮带全量契约;重试轮只带"本文件字段契约 + 质量 + 错误码速查",
|
|
440
546
|
// 任务输入在首轮已经建立,后续轮不必重复。
|
|
@@ -451,6 +557,13 @@ export const storyBuilder = {
|
|
|
451
557
|
parts.push('');
|
|
452
558
|
parts.push('---');
|
|
453
559
|
parts.push('');
|
|
560
|
+
if (o.benchmarkDigest) {
|
|
561
|
+
// 摘要正文必须每轮都在:重试轮不抽 refs 小节,但参考不能跟着消失。
|
|
562
|
+
parts.push(String(o.benchmarkDigest).trim());
|
|
563
|
+
parts.push('');
|
|
564
|
+
parts.push('---');
|
|
565
|
+
parts.push('');
|
|
566
|
+
}
|
|
454
567
|
parts.push(this.buildFleshBatchTask({ batch, attempt, failures: o.failures, relations: o.relations, stageDir: o.stageDir, buildDir: o.buildDir }));
|
|
455
568
|
|
|
456
569
|
return parts.join('\n');
|
|
@@ -484,9 +597,12 @@ export const storyBuilder = {
|
|
|
484
597
|
|
|
485
598
|
const contract = readBuiltInReference('03-rhythm-prompt.md');
|
|
486
599
|
const snapshot = o.inputSnapshot || snapshotFromTopicData(o.topicData || {});
|
|
487
|
-
const withSnapshot =
|
|
488
|
-
|
|
489
|
-
|
|
600
|
+
const withSnapshot = absolutizeInlinePaths(
|
|
601
|
+
contract
|
|
602
|
+
.replace(/\$\{input_snapshot_block\}/g, renderInputSnapshot(snapshot))
|
|
603
|
+
.replace(/\$\{benchmark_refs_block\}/g, o.benchmarkRefs || ''),
|
|
604
|
+
o.projectRoot
|
|
605
|
+
);
|
|
490
606
|
|
|
491
607
|
const names = isRetry
|
|
492
608
|
? ['overview', 'file:' + batch.type, 'consistency', 'errors', 'forbidden']
|
|
@@ -497,6 +613,11 @@ export const storyBuilder = {
|
|
|
497
613
|
}
|
|
498
614
|
|
|
499
615
|
const parts = [picked.text, '', '---', ''];
|
|
616
|
+
if (o.benchmarkDigest) {
|
|
617
|
+
// 摘要正文必须每轮都在:重试轮不抽 refs 小节,但参考不能跟着消失。
|
|
618
|
+
parts.push(String(o.benchmarkDigest).trim());
|
|
619
|
+
parts.push('', '---', '');
|
|
620
|
+
}
|
|
500
621
|
parts.push(this.buildRhythmBatchTask({
|
|
501
622
|
batch: batch,
|
|
502
623
|
attempt: attempt,
|
|
@@ -591,6 +712,21 @@ export const storyBuilder = {
|
|
|
591
712
|
lines.push('');
|
|
592
713
|
lines.push('**只写这些章,章号原样抄。**');
|
|
593
714
|
lines.push('');
|
|
715
|
+
} else if (batch.rowsMode === 'optional') {
|
|
716
|
+
lines.push('## 本批负责的关系阶段(可 0 行)');
|
|
717
|
+
lines.push('');
|
|
718
|
+
lines.push('本批写 `' + batch.groupKey + '` 里 `' + (batch.ownerField || 'from') + '` 属于下表这些角色的行:');
|
|
719
|
+
lines.push('');
|
|
720
|
+
lines.push('| id | name |');
|
|
721
|
+
lines.push('|---|---|');
|
|
722
|
+
for (const item of batch.items || []) {
|
|
723
|
+
lines.push('| ' + (item.id || '-') + ' | ' + (item.name || '-') + ' |');
|
|
724
|
+
}
|
|
725
|
+
lines.push('');
|
|
726
|
+
lines.push('**没有关系变化就写空数组 `[]`**;有关系变化才写行,不要为了凑数硬编。');
|
|
727
|
+
lines.push('每行只写一个阶段:`from` / `to` / `stage` / `state` / `start_chapter`,可选 `end_chapter` / `trigger_event` / `description`。');
|
|
728
|
+
lines.push('同一对角色按 `start_chapter` 严格递增;`from` 必须是上表里的角色 id。');
|
|
729
|
+
lines.push('');
|
|
594
730
|
} else if (!batch.wholeObject) {
|
|
595
731
|
lines.push('## 本批要写的条目(' + batch.count + ' 条)');
|
|
596
732
|
lines.push('');
|
|
@@ -627,6 +763,7 @@ export const storyBuilder = {
|
|
|
627
763
|
lines.push('');
|
|
628
764
|
}
|
|
629
765
|
|
|
766
|
+
lines.push(referenceLogPromptBlock('节奏', batch));
|
|
630
767
|
lines.push('## 交付');
|
|
631
768
|
lines.push('');
|
|
632
769
|
lines.push('写完 `' + shardPath + '` 就结束本轮。');
|
|
@@ -731,6 +868,7 @@ export const storyBuilder = {
|
|
|
731
868
|
lines.push('');
|
|
732
869
|
}
|
|
733
870
|
|
|
871
|
+
lines.push(referenceLogPromptBlock('血肉', batch));
|
|
734
872
|
lines.push('## 交付');
|
|
735
873
|
lines.push('');
|
|
736
874
|
lines.push('写完 `' + shardPath + '` 就结束本轮。');
|
|
@@ -782,10 +920,13 @@ export const storyBuilder = {
|
|
|
782
920
|
|
|
783
921
|
const contract = readBuiltInReference('01-skeleton-prompt.md');
|
|
784
922
|
const snapshot = o.inputSnapshot || snapshotFromTopicData(o.topicData || {});
|
|
785
|
-
const withSnapshot =
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
923
|
+
const withSnapshot = absolutizeInlinePaths(
|
|
924
|
+
contract
|
|
925
|
+
.replace(/\$\{input_snapshot_block\}/g, renderInputSnapshot(snapshot))
|
|
926
|
+
// 参考分层说明(插件指导 vs 对标样本)+ 本阶段可读的对标文件清单
|
|
927
|
+
.replace(/\$\{benchmark_refs_block\}/g, o.benchmarkRefs || ''),
|
|
928
|
+
o.projectRoot
|
|
929
|
+
);
|
|
789
930
|
|
|
790
931
|
const names = isRetry
|
|
791
932
|
? ['overview', 'file:' + batch.type, 'consistency', 'errors', 'forbidden']
|
|
@@ -796,12 +937,18 @@ export const storyBuilder = {
|
|
|
796
937
|
}
|
|
797
938
|
|
|
798
939
|
const parts = [picked.text, '', '---', ''];
|
|
940
|
+
if (o.benchmarkDigest) {
|
|
941
|
+
// 摘要正文必须每轮都在:重试轮不抽 refs 小节,但参考不能跟着消失。
|
|
942
|
+
parts.push(String(o.benchmarkDigest).trim());
|
|
943
|
+
parts.push('', '---', '');
|
|
944
|
+
}
|
|
799
945
|
parts.push(this.buildSkeletonBatchTask({
|
|
800
946
|
batch: batch,
|
|
801
947
|
attempt: attempt,
|
|
802
948
|
failures: o.failures,
|
|
803
949
|
idTables: o.idTables,
|
|
804
950
|
extraContext: o.extraContext,
|
|
951
|
+
existingEntities: o.existingEntities,
|
|
805
952
|
stageDir: o.stageDir,
|
|
806
953
|
buildDir: o.buildDir
|
|
807
954
|
}));
|
|
@@ -1026,6 +1173,21 @@ export const storyBuilder = {
|
|
|
1026
1173
|
lines.push('');
|
|
1027
1174
|
lines.push('**id 和 name 原样抄,不要改一个字符。**');
|
|
1028
1175
|
lines.push('');
|
|
1176
|
+
} else if (batch.rowsMode === 'optional') {
|
|
1177
|
+
lines.push('## 本批负责的关系阶段(可 0 行)');
|
|
1178
|
+
lines.push('');
|
|
1179
|
+
lines.push('本批写 `' + batch.groupKey + '` 里 `' + (batch.ownerField || 'from') + '` 属于下表这些角色的行:');
|
|
1180
|
+
lines.push('');
|
|
1181
|
+
lines.push('| id | name | 类型 |');
|
|
1182
|
+
lines.push('|---|---|---|');
|
|
1183
|
+
for (const item of batch.items || []) {
|
|
1184
|
+
lines.push('| ' + (item.id || '-') + ' | ' + (item.name || '-') + ' | ' + (item.type || item.category || '-') + ' |');
|
|
1185
|
+
}
|
|
1186
|
+
lines.push('');
|
|
1187
|
+
lines.push('**没有关系变化就写空数组 `[]`**;有关系变化才写行,不要为了凑数硬编。');
|
|
1188
|
+
lines.push('每行只写一个阶段:`from` / `to` / `stage` / `state` / `start_chapter`,可选 `end_chapter` / `trigger_event` / `description`。');
|
|
1189
|
+
lines.push('同一对角色按 `start_chapter` 严格递增;`from` 必须是上表里的角色 id。');
|
|
1190
|
+
lines.push('');
|
|
1029
1191
|
} else if (batch.rowsMode === 'semantic') {
|
|
1030
1192
|
// 行数由关系本身决定:**不能说"写 N 条"**。
|
|
1031
1193
|
// 以前统一写"本批要写的条目(N 条)…每行一条",模型把 N 当成真实行数,
|
|
@@ -1068,6 +1230,20 @@ export const storyBuilder = {
|
|
|
1068
1230
|
lines.push('');
|
|
1069
1231
|
}
|
|
1070
1232
|
|
|
1233
|
+
if (o.existingEntities && o.existingEntities.length) {
|
|
1234
|
+
lines.push('## 已经存在的实体(同一实体必须复用,不要换名新建)');
|
|
1235
|
+
lines.push('');
|
|
1236
|
+
lines.push('这些是前面批次已经写进实体清单的 id + name。写本批之前先对照:');
|
|
1237
|
+
lines.push('');
|
|
1238
|
+
lines.push('- 要写的实体已经在表里 → **复用原 id 和 name**,不要改一个字,也不要再建一条。');
|
|
1239
|
+
lines.push('- 同一个人的本名、称呼、绰号、关系称谓(哥哥/父亲/老师/老板等)→ 只保留**本名/正式名**那一条,不要为称呼另建实体。');
|
|
1240
|
+
lines.push('- 同一势力的全称、简称、集团/公司后缀 → 只保留已经存在的正式名,不要重复建。');
|
|
1241
|
+
lines.push('- 表里没有、且确实是新实体 → 才按本批配额新建。');
|
|
1242
|
+
lines.push('');
|
|
1243
|
+
for (const line of o.existingEntities) lines.push(line);
|
|
1244
|
+
lines.push('');
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1071
1247
|
if (o.idTables && o.idTables.length) {
|
|
1072
1248
|
lines.push('## 可引用的 id(只能从下表里选)');
|
|
1073
1249
|
lines.push('');
|
|
@@ -1084,6 +1260,20 @@ export const storyBuilder = {
|
|
|
1084
1260
|
lines.push('');
|
|
1085
1261
|
}
|
|
1086
1262
|
|
|
1263
|
+
if (Array.isArray(batch.optionalArrays) && batch.optionalArrays.indexOf('other_entities') >= 0) {
|
|
1264
|
+
lines.push('## 题材扩展实体(可选,0-8 条)');
|
|
1265
|
+
lines.push('');
|
|
1266
|
+
lines.push('同一个分片里还要写 `other_entities` 数组。它**不计入上面的条目数**:');
|
|
1267
|
+
lines.push('如果题材里有灵魂、神魂、器灵、妖灵、鬼魂、概念体等**不适合当人物**的存在,就在这里登记;没有就写空数组 `[]`。');
|
|
1268
|
+
lines.push('');
|
|
1269
|
+
lines.push('- id 从 `O001` 起连续编号,三位数字;不要占用人物/势力/道具的 id。');
|
|
1270
|
+
lines.push('- `kind` 写具体类别(灵魂/神魂/器灵/妖灵/鬼魂/概念体/其他),不要只写“其他”。');
|
|
1271
|
+
lines.push('- 只登记会在故事里实际出场、且需要独立关系的存在;不要把普通人物换个名字塞进来。');
|
|
1272
|
+
lines.push('- 它们与人物/其他实体的关系由 `关系.json` 的 `other_relations` 表达(如 `附身`、`寄宿`、`共生`、`镇压`)。');
|
|
1273
|
+
lines.push('');
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
lines.push(referenceLogPromptBlock('骨架', batch));
|
|
1087
1277
|
lines.push('## 交付');
|
|
1088
1278
|
lines.push('');
|
|
1089
1279
|
lines.push('写完 `' + shardPath + '` 就结束本轮。');
|
|
@@ -1134,7 +1324,7 @@ export const storyBuilder = {
|
|
|
1134
1324
|
* 写哪些文件、写成什么格式、什么会被校验拦下、收到错误后怎么改。
|
|
1135
1325
|
* 流程(读什么、校验什么、推进到哪一章)由 writing-driver.js 负责。
|
|
1136
1326
|
*/
|
|
1137
|
-
generateWritingContract({ topicData, benchmarkRefs }) {
|
|
1327
|
+
generateWritingContract({ topicData, benchmarkRefs, benchmarkDigest, projectRoot }) {
|
|
1138
1328
|
const { book_name, target_chapters } = topicData || {};
|
|
1139
1329
|
|
|
1140
1330
|
// 读取写作阶段提示词模板(prompts/04-writing-prompt.md)
|
|
@@ -1146,6 +1336,12 @@ export const storyBuilder = {
|
|
|
1146
1336
|
// 参考分层说明(插件指导 vs 对标样本)+ 本阶段可读的对标文件清单
|
|
1147
1337
|
prompt = prompt.replace(/\$\{benchmark_refs_block\}/g, benchmarkRefs || '');
|
|
1148
1338
|
prompt = prompt.replace(/\$\{[a-z_]+\}/g, '');
|
|
1339
|
+
if (benchmarkDigest) {
|
|
1340
|
+
// 摘要正文直接注入,避免模型只收藏路径、写作时完全不看。
|
|
1341
|
+
prompt = prompt + '\n\n' + String(benchmarkDigest).trim();
|
|
1342
|
+
}
|
|
1343
|
+
// 参考文件路径绝对化(基准钉死,见 absolutizeInlinePaths 的说明)
|
|
1344
|
+
prompt = absolutizeInlinePaths(prompt, projectRoot);
|
|
1149
1345
|
|
|
1150
1346
|
// 追加"合法取值清单" —— 从 reading-power.js(唯一真源)动态生成。
|
|
1151
1347
|
//
|
|
@@ -1167,7 +1363,7 @@ export const storyBuilder = {
|
|
|
1167
1363
|
* 写死一份必然漂移:旧 skill 就坏在这儿(deslop 说 24 类、review 说 19 类、
|
|
1168
1364
|
* 代码实现 21 类,三份对不上,模型根本不知道以哪套为准)。
|
|
1169
1365
|
*/
|
|
1170
|
-
generateDeslopContract({ topicData } = {}) {
|
|
1366
|
+
generateDeslopContract({ topicData, projectRoot } = {}) {
|
|
1171
1367
|
const { book_name } = topicData || {};
|
|
1172
1368
|
|
|
1173
1369
|
// 读取去味阶段提示词模板(prompts/05-deslop-prompt.md)
|
|
@@ -1175,6 +1371,8 @@ export const storyBuilder = {
|
|
|
1175
1371
|
|
|
1176
1372
|
prompt = prompt.replace(/\$\{book_name\}/g, book_name || '');
|
|
1177
1373
|
prompt = prompt.replace(/\$\{[a-z_]+\}/g, '');
|
|
1374
|
+
// 参考文件路径绝对化(基准钉死,见 absolutizeInlinePaths 的说明)
|
|
1375
|
+
prompt = absolutizeInlinePaths(prompt, projectRoot);
|
|
1178
1376
|
|
|
1179
1377
|
// 追加"AI 模式清单" —— 每一类都带改法,模型照着改
|
|
1180
1378
|
prompt = prompt + '\n\n' + deslopLegend();
|