@cloud411716/fancy-webnovel 1.0.46 → 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 +369 -0
- package/cordis.patch.yml +4 -13
- 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 +26 -38
- package/lib/atomic-write.js +109 -0
- package/lib/chapter-order.js +100 -0
- package/lib/chinese-num.js +111 -0
- package/lib/confirmation.js +80 -0
- package/lib/db-init.js +123 -0
- package/lib/deployed-state.js +141 -0
- package/lib/direction-labels.js +265 -0
- package/lib/error-codes.js +104 -0
- package/lib/infra.js +594 -0
- package/lib/llm-wiki.js +109 -0
- 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 +305 -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 +62 -5
- package/plugins/fancy-analyze/analyze-definitions.md +519 -0
- package/plugins/fancy-analyze/command-input.js +65 -0
- package/plugins/fancy-analyze/index.js +2024 -0
- package/plugins/fancy-analyze/ingest-coverage.js +107 -0
- package/plugins/fancy-analyze/schema-analyze.md +627 -0
- package/plugins/fancy-analyze/schema-write.md +178 -0
- package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
- package/plugins/fancy-analyze/scripts/split_source.js +292 -0
- package/plugins/fancy-analyze/summary-progress.js +495 -0
- package/plugins/fancy-analyze/templates.js +1179 -0
- package/plugins/fancy-bootstrap/index.js +313 -0
- package/plugins/fancy-bootstrap/scripts/init.js +177 -0
- package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
- package/plugins/fancy-bootstrap/templates.js +71 -0
- 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-core.js +1118 -0
- package/plugins/fancy-import/import-driver.js +604 -0
- package/plugins/fancy-import/index.js +738 -0
- package/plugins/fancy-import/prompts/01-import-prompt.md +131 -0
- package/plugins/fancy-import/templates.js +58 -0
- 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 +708 -0
- 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 +667 -0
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +923 -0
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +469 -0
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +629 -0
- 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 +694 -0
- 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/deploy-manifest.js +239 -0
- package/plugins/fancy-story-builder/deslop-driver.js +821 -0
- package/plugins/fancy-story-builder/flesh-output.js +168 -0
- package/plugins/fancy-story-builder/index.js +6861 -0
- package/plugins/fancy-story-builder/patch-driver.js +390 -0
- package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +563 -0
- package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +602 -0
- package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +539 -0
- package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +225 -0
- package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
- package/plugins/fancy-story-builder/purpose-template.md +83 -0
- package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
- package/plugins/fancy-story-builder/references/formulas-skeleton.json +402 -0
- package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
- package/plugins/fancy-story-builder/references/glossary.md +594 -0
- package/plugins/fancy-story-builder/references/index.md +581 -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 +181 -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 +243 -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 +185 -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 +187 -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 +209 -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 +160 -0
- package/plugins/fancy-story-builder/rhythm-output.js +140 -0
- package/plugins/fancy-story-builder/schema-setting.json +1100 -0
- package/plugins/fancy-story-builder/schema-setting.md +232 -0
- package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
- package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
- package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
- package/plugins/fancy-story-builder/scripts/content-quality.js +416 -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 +1312 -0
- package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
- package/plugins/fancy-story-builder/scripts/graph-utils.js +1484 -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/reference-log.js +110 -0
- package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1312 -0
- package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
- package/plugins/fancy-story-builder/scripts/semantic-lint.js +381 -0
- package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
- package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1973 -0
- package/plugins/fancy-story-builder/scripts/stage-batches.js +1405 -0
- package/plugins/fancy-story-builder/scripts/stage-contract.js +429 -0
- package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
- package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
- 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/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/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 +427 -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 +168 -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 +184 -0
- 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 +131 -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 +85 -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 +103 -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 +68 -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 +142 -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 +292 -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 +266 -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 +336 -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 +308 -0
- package/plugins/fancy-story-builder/templates.js +1446 -0
- package/plugins/fancy-story-builder/wiki-references.js +540 -0
- package/plugins/fancy-story-builder/writing-driver.js +1388 -0
- package/plugins/fancy-topic/index.js +1057 -0
- 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 +317 -0
- package/plugins/fancy-topic/templates.js +476 -0
- package/plugins/fancy-topic/tool-deploy.js +200 -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,794 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2/export-markdown.js —— 把阶段 JSON 导出成供另一个 LLM 阅读的 Markdown。
|
|
3
|
+
*
|
|
4
|
+
* 导出不是复制目录:读者应该看到章节、人物、关系、冲突等内容的
|
|
5
|
+
* Markdown 结构,而不是一堆和源目录完全相同的 JSON 文件。
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { asArray, readJson, stageDir, stageFile } from './core.js';
|
|
10
|
+
import { contractFor } from './contracts.js';
|
|
11
|
+
|
|
12
|
+
const STAGE_FILE_ORDER = {
|
|
13
|
+
'10-scale': ['scale.json'],
|
|
14
|
+
'20-macro': ['world.json', 'story.json', 'volumes.json', 'lines.json', 'endings.json', 'emotion.json', 'roles.json'],
|
|
15
|
+
'30-cast': ['characters.json', 'factions.json', 'locations.json', 'items.json', 'conflicts.json', 'foreshadows.json', 'climaxes.json', 'payoffs.json', 'relations.json', 'information.json'],
|
|
16
|
+
'40-rhythm': ['chapter_map.json', 'index.json'],
|
|
17
|
+
'50-writing': ['ledger.json'],
|
|
18
|
+
'60-deslop': [],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const KEY_LABELS = {
|
|
22
|
+
// 通用
|
|
23
|
+
schema_version: '格式版本', stage: '阶段', status: '状态', checked_at: '检查时间',
|
|
24
|
+
files: '文件', errors: '错误', warnings: '警告', id: '编号', name: '名称',
|
|
25
|
+
title: '标题', type: '类型', chapter_id: '章节编号', display_no: '章节序号',
|
|
26
|
+
chapter: '章节', chapters: '章节列表', content: '内容', description: '描述',
|
|
27
|
+
note: '备注', reason: '原因', value: '值', values: '值列表', label: '标签',
|
|
28
|
+
order: '顺序', path: '路径', method: '方法', function: '功能', question: '问题',
|
|
29
|
+
answer: '答案', goal: '目标', stakes: '赌注', theme: '主题', tone: '基调',
|
|
30
|
+
premise: '前提', promise: '承诺', recognition: '认知', measure: '度量',
|
|
31
|
+
limits: '限制', limit: '限制', cost: '代价', impact: '影响', scope: '范围',
|
|
32
|
+
rules: '规则', exceptions: '例外', layers: '层级', axes: '维度',
|
|
33
|
+
// 数量与公式
|
|
34
|
+
formula_version: '公式版本', genre_profile: '题材配置', capacity: '规模参数',
|
|
35
|
+
target_chapters: '目标章数', target_words: '目标总字数', avg_words_per_chapter: '平均每章字数',
|
|
36
|
+
base_words_per_chapter: '基准每章字数', chapter_scale: '章节字数换算系数',
|
|
37
|
+
structure: '结构', volume_count: '卷数', branch_count: '支线数', total_acts: '总幕数',
|
|
38
|
+
volumes: '卷列表', acts: '幕列表', act: '幕', act_id: '幕编号', act_index: '幕序号',
|
|
39
|
+
act_count: '幕数', act_chapter_sum: '幕章数合计', chapter_start: '起始章',
|
|
40
|
+
chapter_end: '结束章', chapter_sum: '章节数合计', ratio: '占比', quotas: '配额',
|
|
41
|
+
distribution: '分布', rhythm_budget: '节奏预算', hooks: '钩子', reveals: '揭示',
|
|
42
|
+
reversals: '反转', emotional_peaks: '情绪峰值', golden_three: '黄金三章',
|
|
43
|
+
hook_strength: '钩子强度', must_establish: '必须建立', min: '最低值', target: '目标值',
|
|
44
|
+
base_range: '基础区间', base_range_low: '基础区间下界', base_range_high: '基础区间上界',
|
|
45
|
+
adjusted_range: '调整后区间', range_low: '区间下界', range_high: '区间上界',
|
|
46
|
+
formula: '公式', field: '字段', scale_field: '数量字段', genre_multiplier: '题材倍率',
|
|
47
|
+
deviation_reason: '偏离理由', derivation_reasons: '推导理由', derivations: '推导结果',
|
|
48
|
+
chosen: '已选值', constraints: '约束', no_content_fields: '禁止内容字段',
|
|
49
|
+
climax_chapters: '高潮章', payoff_chapters: '爽点章', main_lines: '主线',
|
|
50
|
+
main_line: '主线', branches: '支线', branch: '支线', endings: '结局',
|
|
51
|
+
information: '信息差', information_gaps: '信息差',
|
|
52
|
+
// 宏观
|
|
53
|
+
world: '世界', world_rules: '世界规则', world_setting: '世界设定',
|
|
54
|
+
power_system: '力量体系', social_order: '社会秩序', taboos: '禁忌',
|
|
55
|
+
core_imagery: '核心意象', reader_promise: '读者承诺', anti_tropes: '反套路',
|
|
56
|
+
core_question: '核心问题', core_conflict: '核心冲突', turning_points: '转折点',
|
|
57
|
+
end_state: '结束状态', internal_conflict: '内部冲突', romance_constraint: '情感线约束',
|
|
58
|
+
roles: '角色功能位', role_id: '功能位编号', role_slot: '功能位', macro_goal: '宏观目标',
|
|
59
|
+
arc_question: '弧光问题', expected_strength: '预期强度', participants: '参与者',
|
|
60
|
+
emotional_arc: '情绪弧', emotion: '情绪', phase: '阶段', intensity: '强度',
|
|
61
|
+
peaks: '峰值', arc: '弧光', growth: '成长', direction: '方向', level: '层级',
|
|
62
|
+
required_foreshadow_recovery: '必须回收的伏笔', volume: '卷', volume_id: '卷编号',
|
|
63
|
+
core_goal: '核心目标', escalation_path: '升级路径', resolution_plan: '解决计划',
|
|
64
|
+
residual_impact: '残余影响', node_type: '节点类型', resolution: '收束',
|
|
65
|
+
// 实体
|
|
66
|
+
characters: '人物', character: '人物', protagonist: '主角', core_supporting: '核心配角',
|
|
67
|
+
volume_supporting: '卷级配角', core_antagonist: '核心反派', volume_antagonist: '卷级反派',
|
|
68
|
+
role_slot: '功能位', faction: '势力', description: '描述', flaw: '缺陷',
|
|
69
|
+
golden_finger: '金手指', information_state: '信息状态', known: '已知', unknown: '未知',
|
|
70
|
+
false_belief: '误信', catchphrase: '口头禅', rhythm: '节奏', traits: '特征',
|
|
71
|
+
exit: '退场', short_term: '短期目标', medium_term: '中期目标', long_term: '长期目标',
|
|
72
|
+
arc: '弧光', stages: '阶段', planned_change: '计划变化', question: '问题',
|
|
73
|
+
factions: '势力', faction_relations: '势力关系', faction_membership: '成员关系',
|
|
74
|
+
relations: '关系', character_relations: '人物关系', memberships: '成员关系',
|
|
75
|
+
from: '从', to: '到', kind: '关系类型', initial_state: '初始状态', trigger: '触发',
|
|
76
|
+
changes: '变化', position: '位置', resources: '资源', territory: '地盘',
|
|
77
|
+
internal_conflict: '内部冲突', narrative_function: '叙事功能', locations: '地点',
|
|
78
|
+
items: '道具', owner: '拥有者', category: '分类', conflicts: '冲突',
|
|
79
|
+
foreshadows: '伏笔', climaxes: '高潮', payoffs: '爽点', pleasures: '爽点',
|
|
80
|
+
clues: '线索', setup: '埋设', payoff_target: '回收目标', recovery_requirement: '回收要求',
|
|
81
|
+
red_herrings: '误导线索', payoff: '兑现', required_setup: '所需铺垫',
|
|
82
|
+
emotional_effect: '情绪效果', scale: '规模', info_name: '信息名称',
|
|
83
|
+
who_knows: '知道的人', who_does_not_know: '不知道的人', resolution_method: '揭示方式',
|
|
84
|
+
writing_ban: '写作禁令', setup: '铺垫', required_setup: '所需铺垫',
|
|
85
|
+
// 节奏 / 章节
|
|
86
|
+
chapter_map: '章节地图', chapters: '章节', why: '为什么', how: '怎么写',
|
|
87
|
+
main_line_nodes: '主线节点', branch_nodes: '支线节点', conflict: '冲突',
|
|
88
|
+
function_main: '主功能', function_aux: '辅助功能', turn: '转折', ending_hook: '章尾钩子',
|
|
89
|
+
foreshadow_actions: '伏笔动作', payoff_actions: '爽点动作', information_gaps: '信息差动作',
|
|
90
|
+
ending_actions: '结局动作', action: '动作', detail: '细节', state: '状态',
|
|
91
|
+
pov: '视角人物', cast: '出场人物', mentioned: '提及人物', time: '时间',
|
|
92
|
+
story_day: '故事日', clock: '时钟', duration_minutes: '持续分钟数',
|
|
93
|
+
word_target: '字数目标', hook: '钩子', strength: '强度',
|
|
94
|
+
// 细纲
|
|
95
|
+
chapter: '章节', number: '章号', entry_state: '进入状态', exit_state: '离开状态',
|
|
96
|
+
locations: '地点', sensory: '感官', role_in_chapter: '本章角色', entry: '进入',
|
|
97
|
+
exit: '离开', knowledge: '知识状态', scenes: '场景节拍', no: '序号',
|
|
98
|
+
goal: '目标', obstacle: '阻碍', outcome: '结果', emotion: '情绪',
|
|
99
|
+
information_gap: '信息差', foreshadow: '伏笔', payoff: '爽点',
|
|
100
|
+
must_write: '必须写', must_not_write: '绝对不能写', ending: '收束',
|
|
101
|
+
continuity: '连续性', from_prev: '承接上一章', to_next: '衔接下一章',
|
|
102
|
+
open_threads: '未闭合线索', coverage: '覆盖矩阵', hard_constraints: '硬约束',
|
|
103
|
+
soft_suggestions: '软建议', scene_split: '场景拆分', emotion_trend: '情绪趋势',
|
|
104
|
+
description_focus: '描写重点', pace: '节奏', dialogue_ratio: '对话比例',
|
|
105
|
+
information_state: '信息状态', knowledge_state: '知识状态',
|
|
106
|
+
// 台账 / 导出 / 知识时间线
|
|
107
|
+
ch: '章', knowledge_changes: '知识变化', gained: '获得', lost: '失去',
|
|
108
|
+
foreshadow_status: '伏笔状态', information_status: '信息差状态',
|
|
109
|
+
ending_hook: '章尾钩子', word_count: '字数',
|
|
110
|
+
subject_type: '对象类型', subject_id: '对象编号', change: '变化',
|
|
111
|
+
knowledge: '知道什么', coverage: '掌握程度', source_character: '来源人物',
|
|
112
|
+
source_event: '来源事件', scene: '场景', evidence: '依据',
|
|
113
|
+
initial_knowledge: '初始知识', knowledge_events: '知识事件',
|
|
114
|
+
from_state: '原状态', to_state: '新状态',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const TOKEN_LABELS = {
|
|
118
|
+
structure: '结构', branch: '支线', volume: '卷', count: '数量', sum: '合计',
|
|
119
|
+
character: '人物', characters: '人物', elements: '要素', supporting: '配角',
|
|
120
|
+
antagonist: '反派', protagonist: '主角', fixed: '固定规则', custom: '自定义公式',
|
|
121
|
+
conflict: '冲突', conflicts: '冲突', phase: '阶段', item: '道具', items: '道具',
|
|
122
|
+
location: '地点', locations: '地点', faction: '势力', factions: '势力',
|
|
123
|
+
foreshadow: '伏笔', foreshadows: '伏笔', climax: '高潮', climaxes: '高潮',
|
|
124
|
+
pleasure: '爽点', pleasures: '爽点', world: '世界', rule: '规则', rules: '规则',
|
|
125
|
+
counts: '数量', info: '信息', information: '信息', gaps: '差',
|
|
126
|
+
relations: '关系', relation: '关系', character_relations: '人物关系',
|
|
127
|
+
faction_relations: '势力关系', memberships: '成员关系', membership: '成员关系',
|
|
128
|
+
main: '主线', line: '线', lines: '线', nodes: '节点', node: '节点',
|
|
129
|
+
start: '开始', end: '结束', high: '上界', low: '下界', range: '区间',
|
|
130
|
+
base: '基础', adjusted: '调整后', target: '目标', value: '值', values: '值列表',
|
|
131
|
+
chapter: '章节', chapters: '章节', id: '编号', name: '名称', type: '类型',
|
|
132
|
+
role: '功能', slot: '位', goal: '目标', stakes: '赌注', theme: '主题',
|
|
133
|
+
tone: '基调', premise: '前提', promise: '承诺', recognition: '认知',
|
|
134
|
+
measure: '度量', limits: '限制', limit: '限制', cost: '代价', impact: '影响',
|
|
135
|
+
scope: '范围', layers: '层级', axes: '维度', description: '描述', content: '内容',
|
|
136
|
+
note: '备注', reason: '原因', value: '值', label: '标签', order: '顺序',
|
|
137
|
+
path: '路径', method: '方法', function: '功能', question: '问题',
|
|
138
|
+
answer: '答案', setup: '铺垫', clues: '线索', payoff: '兑现', target: '目标',
|
|
139
|
+
red: '误导', herrings: '线索', required: '所需', emotional: '情绪',
|
|
140
|
+
effect: '效果', info_name: '信息名称', who: '谁', knows: '知道',
|
|
141
|
+
does: '不', not: '不', know: '知道', resolution: '揭示', writing: '写作',
|
|
142
|
+
ban: '禁令', chapter_map: '章节地图', main_line: '主线', branch_nodes: '支线节点',
|
|
143
|
+
main_line_nodes: '主线节点', function_main: '主功能', function_aux: '辅助功能',
|
|
144
|
+
turn: '转折', ending_hook: '章尾钩子', foreshadow_actions: '伏笔动作',
|
|
145
|
+
payoff_actions: '爽点动作', information_gaps: '信息差', ending_actions: '结局动作',
|
|
146
|
+
action: '动作', detail: '细节', state: '状态', pov: '视角人物', cast: '出场人物',
|
|
147
|
+
mentioned: '提及人物', time: '时间', story_day: '故事日', clock: '时钟',
|
|
148
|
+
duration_minutes: '持续分钟数', word_target: '字数目标', hook: '钩子',
|
|
149
|
+
strength: '强度', entry_state: '进入状态', exit_state: '离开状态',
|
|
150
|
+
role_in_chapter: '本章角色', entry: '进入', exit: '离开', knowledge: '知识状态',
|
|
151
|
+
scenes: '场景节拍', no: '序号', goal: '目标', obstacle: '阻碍', outcome: '结果',
|
|
152
|
+
emotion: '情绪', information_gap: '信息差', foreshadow: '伏笔',
|
|
153
|
+
must_write: '必须写', must_not_write: '绝对不能写', ending: '收束',
|
|
154
|
+
continuity: '连续性', from_prev: '承接上一章', to_next: '衔接下一章',
|
|
155
|
+
open_threads: '未闭合线索', coverage: '覆盖矩阵', hard_constraints: '硬约束',
|
|
156
|
+
soft_suggestions: '软建议', scene_split: '场景拆分', emotion_trend: '情绪趋势',
|
|
157
|
+
description_focus: '描写重点', pace: '节奏', dialogue_ratio: '对话比例',
|
|
158
|
+
information_state: '信息状态', knowledge_state: '知识状态', ch: '章',
|
|
159
|
+
knowledge_changes: '知识变化', gained: '获得', lost: '失去',
|
|
160
|
+
foreshadow_status: '伏笔状态', information_status: '信息差状态', word_count: '字数',
|
|
161
|
+
subject_type: '对象类型', subject_id: '对象编号', change: '变化',
|
|
162
|
+
knowledge_events: '知识事件', initial_knowledge: '初始知识',
|
|
163
|
+
source_character: '来源人物', source_event: '来源事件', scene: '场景',
|
|
164
|
+
evidence: '依据', from_state: '原状态', to_state: '新状态',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const VALUE_LABELS = {
|
|
168
|
+
created: '已创建', progressed: '推进中', resolved: '已解决',
|
|
169
|
+
planted: '已埋设', recovered: '已回收', reinforced: '已强化',
|
|
170
|
+
partial: '部分知道', full: '完全知道', unknown: '不知道', false: '误信',
|
|
171
|
+
witnessed: '亲眼看到', told: '他人告知', inferred: '推断', document: '文书',
|
|
172
|
+
rumor: '传闻', experience: '亲身经历', none: '无', other: '其他',
|
|
173
|
+
book: '全书', volume: '卷', serial: '连载', core: '核心',
|
|
174
|
+
phase: '阶段', branch: '支线', small: '小', medium: '中', large: '大',
|
|
175
|
+
draft: '草稿', passed: '已通过', stale: '过期', archived: '已归档',
|
|
176
|
+
learns: '获知', partially_learns: '部分获知', confirms: '确认',
|
|
177
|
+
corrects: '纠正', forgets: '遗忘', misinforms: '误信',
|
|
178
|
+
information: '信息差', foreshadow: '伏笔',
|
|
179
|
+
'ROLE-PROTAGONIST': '主角', 'ROLE-CORE-SUPPORT-01': '核心配角',
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// 少量容易漏掉的业务键与枚举键,单独覆盖,避免被通用拆分规则拼成半英文。
|
|
183
|
+
Object.assign(KEY_LABELS, {
|
|
184
|
+
voice: '语言风格', exit: '退场', interweaves: '交织节点', turning: '转折点',
|
|
185
|
+
hard_limits: '硬性限制', core: '核心', large: '大', medium: '中', small: '小', character_arc: '人物弧光',
|
|
186
|
+
social: '社会', symbolic: '象征', aliases: '别名', benchmark: '对标',
|
|
187
|
+
book_name: '书名', created_at: '创建时间', channel_model: '频道模型',
|
|
188
|
+
charTotal: '人物总数', core_supporting_appearance_per_10: '核心配角每 10 章出场次数',
|
|
189
|
+
protagonist_appearance_per_10: '主角每 10 章出场次数', default_words_per_chapter: '默认每章字数',
|
|
190
|
+
distribution_weights: '分布权重', applies_to_field: '适用字段', apply: '应用',
|
|
191
|
+
default: '默认', profile: '题材配置', profiles: '题材配置列表',
|
|
192
|
+
character_elements: '人物要素', climax_elements: '高潮要素', conflict_elements: '冲突要素',
|
|
193
|
+
ending_elements: '结局要素', faction_elements: '势力要素', foreshadow_elements: '伏笔要素',
|
|
194
|
+
item_elements: '道具要素', location_elements: '地点要素', pleasure_elements: '爽点要素',
|
|
195
|
+
world_rule_counts: '世界规则数量', volume_ratio_sum: '卷占比合计',
|
|
196
|
+
volume_antagonist_count: '卷级反派数量', volume_supporting_count: '卷级配角数量',
|
|
197
|
+
large_climax_count: '大高潮数量', medium_climax_count: '中高潮数量', small_climax_count: '小高潮数量',
|
|
198
|
+
branch_conflict_count: '交织冲突数量', phase_conflict_count: '阶段冲突数量',
|
|
199
|
+
ending_count: '结局数量', faction_count: '势力数量', faction_relation_count: '势力关系数量',
|
|
200
|
+
large_foreshadow_count: '大伏笔数量', medium_foreshadow_count: '中伏笔数量', small_foreshadow_count: '小伏笔数量',
|
|
201
|
+
branch_item_count: '支线道具数量', core_item_count: '核心道具数量', symbolic_item_count: '象征道具数量',
|
|
202
|
+
key_location_count: '关键地点数量', large_pleasure_count: '大爽点数量', medium_pleasure_count: '中爽点数量',
|
|
203
|
+
small_pleasure_count: '小爽点数量', main_line_count: '主线数量',
|
|
204
|
+
large_climax: '大高潮', medium_climax: '中高潮', small_climax: '小高潮',
|
|
205
|
+
large_foreshadow: '大伏笔', medium_foreshadow: '中伏笔', small_foreshadow: '小伏笔',
|
|
206
|
+
core_setting: '核心设定', base_formula_words_per_chapter: '公式基准每章字数',
|
|
207
|
+
custom_formula_rule: '自定义公式规则', default_profile: '默认题材配置',
|
|
208
|
+
emotion_jump_warning: '情绪跳变警告', climax_gap_chapters: '高潮间隔章数',
|
|
209
|
+
flatline_chapters: '平流章数', expr_high: '表达式上界', expr_low: '表达式下界',
|
|
210
|
+
BC: '基础章数', CA: '当前幕章数', CC: '冲突数', CS: '章节规模', T: '总章数',
|
|
211
|
+
V: '卷数', P: '平台系数', n: '数量', Math: '数学函数',
|
|
212
|
+
channel: '频道', fallback: '回退配置', farming: '种田', first: '第一段',
|
|
213
|
+
genre: '题材', gt25: '大于 25 章', lt10: '小于 10 章', last: '最后一段',
|
|
214
|
+
middle: '中间段', missing: '缺失项', new_entities: '新增实体', notes: '备注',
|
|
215
|
+
one_line_pitch: '一句话卖点', opening_chapters: '开篇章数',
|
|
216
|
+
opening_hook_multiplier: '开篇钩子倍率', overrides: '覆盖规则',
|
|
217
|
+
payoff_gap_chapters: '爽点间隔章数', pleasure_density_multiplier: '爽点密度倍率',
|
|
218
|
+
positioning: '定位', preferred_chapters_per_volume: '每卷推荐章数',
|
|
219
|
+
profile_file: '题材配置文件', profile_path: '题材配置路径', profile_rule: '题材匹配规则',
|
|
220
|
+
quality: '质量', reader_value: '读者价值', references: '参考资料',
|
|
221
|
+
relationships: '关系', rounding: '取整规则', scale_field_rule: '数量字段规则',
|
|
222
|
+
selling_points: '卖点', shuangwen: '爽文', special_rules: '特殊规则',
|
|
223
|
+
structure_feature: '结构特征', suspense: '悬疑', version: '版本',
|
|
224
|
+
volume_length_multiplier: '卷长倍率', volume_supporting_retention: '卷级配角留存率',
|
|
225
|
+
hook_density_per_chapter: '每章钩子密度', formula_with_multiplier: '带倍率公式',
|
|
226
|
+
multiplier_category: '倍率分类', multipliers: '倍率',
|
|
227
|
+
small_foreshadow_recovery: '小伏笔回收', medium_foreshadow_recovery: '中伏笔回收',
|
|
228
|
+
large_foreshadow_recovery: '大伏笔回收',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
function translateValue(value) {
|
|
232
|
+
if (typeof value !== 'string') return value;
|
|
233
|
+
return VALUE_LABELS[value] || value;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function translateKey(key) {
|
|
237
|
+
const raw = String(key);
|
|
238
|
+
if (KEY_LABELS[raw]) return KEY_LABELS[raw];
|
|
239
|
+
if (/^V\d{3}$/.test(raw)) return '第 ' + Number(raw.slice(1)) + ' 卷';
|
|
240
|
+
if (raw.includes('.')) {
|
|
241
|
+
return raw.split('.').map((part) => KEY_LABELS[part] || TOKEN_LABELS[part] || part).join(' · ');
|
|
242
|
+
}
|
|
243
|
+
const parts = raw.split('_').filter(Boolean);
|
|
244
|
+
if (parts.length > 1) {
|
|
245
|
+
const translated = parts.map((part) => TOKEN_LABELS[part]);
|
|
246
|
+
if (translated.every(Boolean)) return translated.join('');
|
|
247
|
+
return raw;
|
|
248
|
+
}
|
|
249
|
+
return TOKEN_LABELS[raw] || raw;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const STAGE_LABEL = {
|
|
253
|
+
'10-scale': '数量表',
|
|
254
|
+
'20-macro': '宏观设定',
|
|
255
|
+
'30-cast': '人物与实体',
|
|
256
|
+
'40-rhythm': '章节地图与细纲',
|
|
257
|
+
'50-writing': '正文与台账',
|
|
258
|
+
'60-deslop': '去味报告',
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
function heading(level, text) {
|
|
262
|
+
return '#'.repeat(Math.max(1, Math.min(6, level))) + ' ' + text;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function isObject(value) {
|
|
266
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isScalar(value) {
|
|
270
|
+
return value === null || value === undefined || ['string', 'number', 'boolean'].includes(typeof value);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isScalarArray(value) {
|
|
274
|
+
return Array.isArray(value) && value.every(isScalar);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function cleanObject(value) {
|
|
278
|
+
if (!isObject(value)) return value;
|
|
279
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== '_meta'));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function oneLine(value) {
|
|
283
|
+
if (value === null || value === undefined || value === '') return '—';
|
|
284
|
+
if (typeof value === 'boolean') return value ? '是' : '否';
|
|
285
|
+
if (Array.isArray(value)) return value.map(oneLine).join('、') || '—';
|
|
286
|
+
if (isObject(value)) return JSON.stringify(value);
|
|
287
|
+
const text = String(value).replace(/\r?\n/g, ' ').trim();
|
|
288
|
+
return text ? translateValue(text) : '—';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function labelOf(item, index = 0) {
|
|
292
|
+
if (!isObject(item)) return '第 ' + (index + 1) + ' 项';
|
|
293
|
+
const id = item.id || item.chapter_id;
|
|
294
|
+
const name = item.name || item.title || item.label || item.info_name;
|
|
295
|
+
if (id && name) return id + ' ' + name;
|
|
296
|
+
if (id) return String(id);
|
|
297
|
+
if (name) return String(name);
|
|
298
|
+
const chap = item.chapter_id || (item.display_no !== undefined ? '第 ' + item.display_no + ' 章' : '');
|
|
299
|
+
return chap || ('第 ' + (index + 1) + ' 项');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function fieldLine(key, value, depth = 0) {
|
|
303
|
+
const pad = ' '.repeat(depth);
|
|
304
|
+
if (isScalar(value) || isScalarArray(value)) return [pad + '- **' + translateKey(key) + '**:' + oneLine(value)];
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** 实体内部字段:标量用列表,数组/对象继续缩进。 */
|
|
309
|
+
function renderFields(obj, depth, lines) {
|
|
310
|
+
const data = cleanObject(obj) || {};
|
|
311
|
+
for (const [key, value] of Object.entries(data)) {
|
|
312
|
+
if (value === undefined) continue;
|
|
313
|
+
if (isScalar(value) || isScalarArray(value)) {
|
|
314
|
+
lines.push(...fieldLine(key, value, depth));
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const pad = ' '.repeat(depth);
|
|
318
|
+
if (Array.isArray(value)) {
|
|
319
|
+
lines.push(pad + '- **' + translateKey(key) + '**:');
|
|
320
|
+
if (!value.length) {
|
|
321
|
+
lines.push(' '.repeat(depth + 1) + '- (空)');
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
value.forEach((item, index) => {
|
|
325
|
+
if (isObject(item)) {
|
|
326
|
+
lines.push(' '.repeat(depth + 1) + '- **' + labelOf(item, index) + '**');
|
|
327
|
+
renderFields(item, depth + 2, lines);
|
|
328
|
+
} else {
|
|
329
|
+
lines.push(' '.repeat(depth + 1) + '- ' + oneLine(item));
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (isObject(value)) {
|
|
335
|
+
lines.push(pad + '- **' + translateKey(key) + '**:');
|
|
336
|
+
renderFields(value, depth + 1, lines);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renderSection(key, value, level, lines) {
|
|
342
|
+
lines.push(heading(level, translateKey(key)));
|
|
343
|
+
lines.push('');
|
|
344
|
+
if (Array.isArray(value)) {
|
|
345
|
+
if (!value.length) {
|
|
346
|
+
lines.push('(空)');
|
|
347
|
+
lines.push('');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
value.forEach((item, index) => {
|
|
351
|
+
if (isObject(item)) {
|
|
352
|
+
lines.push(heading(level + 1, labelOf(item, index)));
|
|
353
|
+
lines.push('');
|
|
354
|
+
renderFields(item, 0, lines);
|
|
355
|
+
lines.push('');
|
|
356
|
+
} else {
|
|
357
|
+
lines.push('- ' + oneLine(item));
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (isObject(value)) {
|
|
363
|
+
const entries = Object.entries(cleanObject(value) || {});
|
|
364
|
+
const objectMap = entries.length > 0 && entries.every(([, child]) => isObject(child));
|
|
365
|
+
if (objectMap) {
|
|
366
|
+
for (const [childKey, child] of entries) {
|
|
367
|
+
const childLabel = child.label || translateKey(childKey);
|
|
368
|
+
lines.push(heading(level + 1, childLabel));
|
|
369
|
+
lines.push('');
|
|
370
|
+
lines.push('- **键名**:`' + childKey + '`');
|
|
371
|
+
renderFields(child, 0, lines);
|
|
372
|
+
lines.push('');
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
renderFields(value, 0, lines);
|
|
376
|
+
lines.push('');
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
lines.push(oneLine(value));
|
|
381
|
+
lines.push('');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function renderTopLevel(data, level, lines) {
|
|
385
|
+
if (Array.isArray(data)) {
|
|
386
|
+
renderSection('条目', data, level, lines);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const obj = cleanObject(data) || {};
|
|
390
|
+
const scalars = [];
|
|
391
|
+
const composites = [];
|
|
392
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
393
|
+
if (value === undefined) continue;
|
|
394
|
+
if (isScalar(value) || isScalarArray(value)) scalars.push([key, value]);
|
|
395
|
+
else composites.push([key, value]);
|
|
396
|
+
}
|
|
397
|
+
if (scalars.length) {
|
|
398
|
+
for (const [key, value] of scalars) lines.push(...fieldLine(key, value));
|
|
399
|
+
lines.push('');
|
|
400
|
+
}
|
|
401
|
+
for (const [key, value] of composites) renderSection(key, value, level, lines);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function indented(lines, prefix = ' ') {
|
|
405
|
+
return lines.map((line) => prefix + line);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function actionLines(actions) {
|
|
409
|
+
const out = [];
|
|
410
|
+
for (const action of asArray(actions)) {
|
|
411
|
+
if (!isObject(action)) {
|
|
412
|
+
out.push('- ' + oneLine(action));
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
out.push('- **' + (action.id || '条目') + '**');
|
|
416
|
+
if (action.action !== undefined) out.push(' - ' + translateKey('action') + ':' + translateValue(action.action));
|
|
417
|
+
if (action.state !== undefined) out.push(' - ' + translateKey('state') + ':' + translateValue(action.state));
|
|
418
|
+
if (action.detail !== undefined) out.push(' - ' + translateKey('detail') + ':' + oneLine(action.detail));
|
|
419
|
+
const rest = { ...action };
|
|
420
|
+
delete rest.id;
|
|
421
|
+
delete rest.action;
|
|
422
|
+
delete rest.state;
|
|
423
|
+
delete rest.detail;
|
|
424
|
+
if (Object.keys(rest).length) renderFields(rest, 1, out);
|
|
425
|
+
}
|
|
426
|
+
return out;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function knowledgeEventLines(event) {
|
|
430
|
+
const out = [];
|
|
431
|
+
if (!isObject(event)) return ['- ' + oneLine(event)];
|
|
432
|
+
out.push('- **' + [event.character, event.subject_id].filter(Boolean).join(' / ') + '**');
|
|
433
|
+
if (event.subject_type) out.push(' - 对象类型:' + oneLine(event.subject_type));
|
|
434
|
+
if (event.change) out.push(' - 变化:' + oneLine(event.change));
|
|
435
|
+
if (event.coverage) out.push(' - 掌握程度:' + oneLine(event.coverage));
|
|
436
|
+
if (event.knowledge) out.push(' - 知道什么:' + oneLine(event.knowledge));
|
|
437
|
+
if (event.how) out.push(' - 获知方式:' + oneLine(event.how));
|
|
438
|
+
if (event.source_character) out.push(' - 来源人物:' + oneLine(event.source_character));
|
|
439
|
+
if (event.scene) out.push(' - 场景:' + oneLine(event.scene));
|
|
440
|
+
if (event.evidence) out.push(' - 依据:' + oneLine(event.evidence));
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function renderChapter(chapter, lines, level = 3) {
|
|
445
|
+
const c = chapter || {};
|
|
446
|
+
lines.push(heading(level, '第' + (c.display_no || '?') + '章 ' + (c.title || '')));
|
|
447
|
+
lines.push('');
|
|
448
|
+
lines.push('- **章节编号**:' + (c.chapter_id || '—'));
|
|
449
|
+
lines.push('- **章节序号**:' + (c.display_no ?? '—'));
|
|
450
|
+
lines.push('- **卷**:' + (c.volume || '—'));
|
|
451
|
+
lines.push('- **幕**:' + (c.act || '—'));
|
|
452
|
+
lines.push('- **视角人物**:' + (c.pov || '—'));
|
|
453
|
+
lines.push('- **出场人物**:' + oneLine(c.cast));
|
|
454
|
+
if (asArray(c.mentioned).length) lines.push('- **提及人物**:' + oneLine(c.mentioned));
|
|
455
|
+
lines.push('- **地点**:' + oneLine(c.locations));
|
|
456
|
+
if (c.time) {
|
|
457
|
+
if (c.time.story_day !== undefined) lines.push('- **故事日**:' + c.time.story_day);
|
|
458
|
+
if (c.time.clock) lines.push('- **时钟**:' + c.time.clock);
|
|
459
|
+
if (c.time.duration_minutes !== undefined) lines.push('- **持续分钟数**:' + c.time.duration_minutes);
|
|
460
|
+
}
|
|
461
|
+
const why = c.why || {};
|
|
462
|
+
lines.push('- **为什么**:');
|
|
463
|
+
lines.push(' - 主线节点:' + oneLine(why.main_line_nodes));
|
|
464
|
+
lines.push(' - 支线节点:' + oneLine(why.branch_nodes));
|
|
465
|
+
lines.push(' - 冲突:' + oneLine(why.conflict));
|
|
466
|
+
lines.push(' - 主题:' + oneLine(why.theme));
|
|
467
|
+
lines.push(' - 赌注:' + oneLine(why.stakes));
|
|
468
|
+
const how = c.how || {};
|
|
469
|
+
lines.push('- **怎么写**:');
|
|
470
|
+
lines.push(' - 主功能:' + oneLine(how.function_main));
|
|
471
|
+
if (how.function_aux) lines.push(' - 辅功能:' + oneLine(how.function_aux));
|
|
472
|
+
lines.push(' - 转折:' + oneLine(how.turn));
|
|
473
|
+
lines.push(' - 章尾钩子:' + oneLine(how.ending_hook));
|
|
474
|
+
const actionBlocks = [
|
|
475
|
+
['伏笔动作', c.foreshadow_actions],
|
|
476
|
+
['爽点动作', c.payoff_actions],
|
|
477
|
+
['信息差', c.information_gaps],
|
|
478
|
+
['结局落点', c.ending_actions],
|
|
479
|
+
];
|
|
480
|
+
for (const [label, actions] of actionBlocks) {
|
|
481
|
+
if (!asArray(actions).length) continue;
|
|
482
|
+
lines.push('- **' + label + '**:');
|
|
483
|
+
lines.push(...indented(actionLines(actions)));
|
|
484
|
+
}
|
|
485
|
+
if (asArray(c.knowledge_events).length) {
|
|
486
|
+
lines.push('- **知识事件**:');
|
|
487
|
+
lines.push(...indented(asArray(c.knowledge_events).flatMap(knowledgeEventLines)));
|
|
488
|
+
}
|
|
489
|
+
if (c.emotion) {
|
|
490
|
+
if (c.emotion.phase !== undefined) lines.push('- **情绪阶段**:' + oneLine(c.emotion.phase));
|
|
491
|
+
if (c.emotion.value !== undefined) lines.push('- **情绪强度**:' + c.emotion.value);
|
|
492
|
+
}
|
|
493
|
+
if (c.hook) {
|
|
494
|
+
if (c.hook.type !== undefined) lines.push('- **钩子类型**:' + oneLine(c.hook.type));
|
|
495
|
+
if (c.hook.strength !== undefined) lines.push('- **钩子强度**:' + translateValue(c.hook.strength));
|
|
496
|
+
}
|
|
497
|
+
if (c.word_target !== undefined) lines.push('- **字数目标**:' + c.word_target);
|
|
498
|
+
lines.push('');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function renderChapterMap(data, lines, level = 3) {
|
|
502
|
+
const chapters = asArray(data && data.chapters);
|
|
503
|
+
lines.push(heading(level, '章节地图'));
|
|
504
|
+
lines.push('');
|
|
505
|
+
lines.push('> 共 ' + chapters.length + ' 章。每章按 5W 展开,供后续细纲/正文读取。');
|
|
506
|
+
lines.push('');
|
|
507
|
+
for (const chapter of chapters) renderChapter(chapter, lines, level + 1);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function renderChapterIndex(data, lines, level = 3) {
|
|
511
|
+
const chapters = asArray(data && data.chapters);
|
|
512
|
+
lines.push(heading(level, '章节索引'));
|
|
513
|
+
lines.push('');
|
|
514
|
+
lines.push('| 章 | 章节编号 | 标题 | 卷 | 幕 | 细纲路径 | 状态 |');
|
|
515
|
+
lines.push('|---|---|---|---|---|---|---|');
|
|
516
|
+
for (const c of chapters) {
|
|
517
|
+
lines.push('| ' + [c.display_no, c.chapter_id, c.title, c.volume, c.act, c.path, c.status]
|
|
518
|
+
.map((v) => oneLine(v).replace(/\|/g, '\\|')).join(' | ') + ' |');
|
|
519
|
+
}
|
|
520
|
+
lines.push('');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function renderScene(scene, index, lines) {
|
|
524
|
+
lines.push(heading(4, '场景 ' + (index + 1)));
|
|
525
|
+
lines.push('');
|
|
526
|
+
renderFields(scene, 0, lines);
|
|
527
|
+
lines.push('');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function renderOutline(data, lines, level = 3) {
|
|
531
|
+
const chapter = (data && data.chapter) || data || {};
|
|
532
|
+
lines.push(heading(level, '第' + (chapter.number || chapter.display_no || '?') + '章 ' + (chapter.title || '')));
|
|
533
|
+
lines.push('');
|
|
534
|
+
lines.push('- **章节编号**:' + (chapter.chapter_id || '—'));
|
|
535
|
+
lines.push('- **卷**:' + (chapter.volume || '—'));
|
|
536
|
+
lines.push('- **幕**:' + (chapter.act || '—'));
|
|
537
|
+
lines.push('- **字数目标**:' + (chapter.word_target ?? '—'));
|
|
538
|
+
if (chapter.pov) {
|
|
539
|
+
const pov = chapter.pov;
|
|
540
|
+
lines.push('- **视角人物**:' + (pov.id || pov.name || '—'));
|
|
541
|
+
if (pov.entry_state) lines.push('- **视角进入状态**:' + oneLine(pov.entry_state));
|
|
542
|
+
if (pov.exit_state) lines.push('- **视角离开状态**:' + oneLine(pov.exit_state));
|
|
543
|
+
}
|
|
544
|
+
if (chapter.time) {
|
|
545
|
+
if (isObject(chapter.time)) {
|
|
546
|
+
if (chapter.time.story_day !== undefined) lines.push('- **故事日**:' + chapter.time.story_day);
|
|
547
|
+
if (chapter.time.clock) lines.push('- **时钟**:' + chapter.time.clock);
|
|
548
|
+
if (chapter.time.duration_minutes !== undefined) lines.push('- **持续分钟数**:' + chapter.time.duration_minutes);
|
|
549
|
+
if (chapter.time.chronology) lines.push('- **叙事顺序**:' + oneLine(chapter.time.chronology));
|
|
550
|
+
} else {
|
|
551
|
+
lines.push('- **时间**:' + oneLine(chapter.time));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (chapter.locations) {
|
|
555
|
+
const locs = Array.isArray(chapter.locations) ? chapter.locations : [chapter.locations];
|
|
556
|
+
lines.push('- **地点**:');
|
|
557
|
+
for (const loc of locs) {
|
|
558
|
+
if (isObject(loc)) lines.push(' - ' + [loc.id, loc.name, loc.state ? '(' + oneLine(loc.state) + ')' : ''].filter(Boolean).join(' '));
|
|
559
|
+
else lines.push(' - ' + oneLine(loc));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (asArray(chapter.cast).length) {
|
|
563
|
+
lines.push('- **出场人物**:');
|
|
564
|
+
for (const person of chapter.cast) {
|
|
565
|
+
if (isObject(person)) {
|
|
566
|
+
lines.push(' - **' + labelOf(person) + '**');
|
|
567
|
+
if (person.role_in_chapter) lines.push(' - 本章角色:' + oneLine(person.role_in_chapter));
|
|
568
|
+
if (person.entry) lines.push(' - 进入:' + oneLine(person.entry));
|
|
569
|
+
if (person.exit) lines.push(' - 离开:' + oneLine(person.exit));
|
|
570
|
+
if (person.knowledge) {
|
|
571
|
+
lines.push(' - 知识状态:');
|
|
572
|
+
for (const item of asArray(person.knowledge)) {
|
|
573
|
+
if (isObject(item)) {
|
|
574
|
+
lines.push(' - **' + [item.subject_id, item.character].filter(Boolean).join(' / ') + '**');
|
|
575
|
+
for (const [k, v] of Object.entries(item)) {
|
|
576
|
+
if (['subject_id', 'character'].includes(k)) continue;
|
|
577
|
+
lines.push(' - ' + translateKey(k) + ':' + oneLine(v));
|
|
578
|
+
}
|
|
579
|
+
} else {
|
|
580
|
+
lines.push(' - ' + oneLine(item));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
lines.push(' - ' + oneLine(person));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (chapter.why) {
|
|
590
|
+
lines.push('- **为什么**:');
|
|
591
|
+
renderFields(chapter.why, 1, lines);
|
|
592
|
+
}
|
|
593
|
+
if (asArray(chapter.knowledge_events).length) {
|
|
594
|
+
lines.push('- **知识事件**:');
|
|
595
|
+
lines.push(...indented(asArray(chapter.knowledge_events).flatMap(knowledgeEventLines)));
|
|
596
|
+
}
|
|
597
|
+
const how = chapter.how || {};
|
|
598
|
+
const scenes = asArray(how.scenes);
|
|
599
|
+
if (scenes.length) {
|
|
600
|
+
lines.push('- **场景节拍**:');
|
|
601
|
+
lines.push('');
|
|
602
|
+
for (let i = 0; i < scenes.length; i += 1) renderScene(scenes[i], i, lines);
|
|
603
|
+
}
|
|
604
|
+
if (how.ending) {
|
|
605
|
+
lines.push('- **本章收束**:');
|
|
606
|
+
if (isObject(how.ending)) {
|
|
607
|
+
if (how.ending.type) lines.push(' - 类型:' + oneLine(how.ending.type));
|
|
608
|
+
if (how.ending.text) lines.push(' - 文本:' + oneLine(how.ending.text));
|
|
609
|
+
if (how.ending.hook) lines.push(' - 钩子:' + oneLine(how.ending.hook));
|
|
610
|
+
} else {
|
|
611
|
+
lines.push(' - ' + oneLine(how.ending));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (chapter.continuity) {
|
|
615
|
+
lines.push('- **连续性**:');
|
|
616
|
+
renderFields(chapter.continuity, 1, lines);
|
|
617
|
+
}
|
|
618
|
+
if (chapter.coverage) {
|
|
619
|
+
lines.push('- **覆盖矩阵**:');
|
|
620
|
+
renderFields(chapter.coverage, 1, lines);
|
|
621
|
+
}
|
|
622
|
+
if (chapter.hard_constraints) {
|
|
623
|
+
lines.push('- **硬约束**:');
|
|
624
|
+
renderFields(chapter.hard_constraints, 1, lines);
|
|
625
|
+
}
|
|
626
|
+
if (chapter.soft_suggestions) {
|
|
627
|
+
lines.push('- **软建议**:');
|
|
628
|
+
renderFields(chapter.soft_suggestions, 1, lines);
|
|
629
|
+
}
|
|
630
|
+
lines.push('');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function bookNameOf(projectRoot) {
|
|
634
|
+
const input = readJson(path.join(projectRoot, '设定', '00-baseline', 'input.json'), null)
|
|
635
|
+
|| readJson(stageFile(projectRoot, '00-baseline', 'input.json'), null);
|
|
636
|
+
return (input && input.book_name) || '';
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function stageFiles(projectRoot, stageId) {
|
|
640
|
+
const dir = stageDir(projectRoot, stageId);
|
|
641
|
+
const root = fs.readdirSync(dir, { withFileTypes: true })
|
|
642
|
+
.filter((e) => e.isFile() && e.name.endsWith('.json') && e.name !== 'bundle.json' && !e.name.startsWith('.'))
|
|
643
|
+
.map((e) => e.name);
|
|
644
|
+
const preferred = STAGE_FILE_ORDER[stageId] || [];
|
|
645
|
+
const ordered = preferred.filter((name) => root.includes(name));
|
|
646
|
+
const rest = root.filter((name) => !ordered.includes(name)).sort();
|
|
647
|
+
const files = [...ordered, ...rest];
|
|
648
|
+
const extra = [];
|
|
649
|
+
if (stageId === '40-rhythm') {
|
|
650
|
+
const outlineDir = path.join(dir, 'outlines');
|
|
651
|
+
let names = [];
|
|
652
|
+
try { names = fs.readdirSync(outlineDir).filter((n) => n.endsWith('.json')).sort(); } catch { names = []; }
|
|
653
|
+
for (const name of names) extra.push({ rel: 'outlines/' + name, json: true });
|
|
654
|
+
}
|
|
655
|
+
if (stageId === '50-writing') {
|
|
656
|
+
const chapterDir = path.join(dir, 'chapters');
|
|
657
|
+
let names = [];
|
|
658
|
+
try { names = fs.readdirSync(chapterDir).filter((n) => n.endsWith('.md')).sort(); } catch { names = []; }
|
|
659
|
+
for (const name of names) extra.push({ rel: 'chapters/' + name, markdown: true });
|
|
660
|
+
}
|
|
661
|
+
if (stageId === '60-deslop') {
|
|
662
|
+
for (const sub of ['reports', 'revisions']) {
|
|
663
|
+
const base = path.join(dir, sub);
|
|
664
|
+
let names = [];
|
|
665
|
+
try { names = fs.readdirSync(base).sort(); } catch { names = []; }
|
|
666
|
+
for (const name of names) {
|
|
667
|
+
if (name.endsWith('.md')) extra.push({ rel: sub + '/' + name, markdown: true });
|
|
668
|
+
else if (name.endsWith('.json')) extra.push({ rel: sub + '/' + name, json: true });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
dir,
|
|
674
|
+
files: [...files.map((name) => ({ rel: name, json: true })), ...extra],
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* 生成阶段汇总 Markdown。
|
|
680
|
+
* @returns {{markdown:string, files:string[], stageId:string, title:string}}
|
|
681
|
+
*/
|
|
682
|
+
function sourceFileLabel(files) {
|
|
683
|
+
const grouped = [];
|
|
684
|
+
const top = [];
|
|
685
|
+
let outlines = 0;
|
|
686
|
+
let chapters = 0;
|
|
687
|
+
for (const file of files) {
|
|
688
|
+
if (file.rel.startsWith('outlines/')) outlines += 1;
|
|
689
|
+
else if (file.rel.startsWith('chapters/')) chapters += 1;
|
|
690
|
+
else top.push(file.rel);
|
|
691
|
+
}
|
|
692
|
+
for (const name of top) grouped.push('`' + name + '`');
|
|
693
|
+
if (outlines) grouped.push('`outlines/`(' + outlines + ' 个细纲)');
|
|
694
|
+
if (chapters) grouped.push('`chapters/`(' + chapters + ' 篇正文)');
|
|
695
|
+
return grouped.join('、');
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function renderStageMarkdown(projectRoot, stageId) {
|
|
699
|
+
const contract = contractFor(stageId);
|
|
700
|
+
const label = STAGE_LABEL[stageId] || (contract && contract.title) || stageId;
|
|
701
|
+
const { files } = stageFiles(projectRoot, stageId);
|
|
702
|
+
const lines = [];
|
|
703
|
+
lines.push('# ' + stageId + ' 阶段导出:' + label);
|
|
704
|
+
lines.push('');
|
|
705
|
+
lines.push('> 本文件由 fancy-story-builder 2.0 从阶段 JSON 生成,供其它 LLM 直接阅读;不是源目录 JSON 文件的复制。');
|
|
706
|
+
lines.push('');
|
|
707
|
+
const book = bookNameOf(projectRoot);
|
|
708
|
+
if (book) lines.push('- **项目**:' + book);
|
|
709
|
+
lines.push('- **阶段**:`' + stageId + '`(' + label + ')');
|
|
710
|
+
lines.push('- **导出时间**:' + new Date().toISOString());
|
|
711
|
+
lines.push('- **源文件**:' + sourceFileLabel(files));
|
|
712
|
+
if (contract && contract.purpose) lines.push('- **阶段目的**:' + contract.purpose);
|
|
713
|
+
if (contract && asArray(contract.readOrder).length) lines.push('- **建议读法**:' + contract.readOrder.join(' → '));
|
|
714
|
+
lines.push('');
|
|
715
|
+
lines.push('---');
|
|
716
|
+
lines.push('');
|
|
717
|
+
|
|
718
|
+
let rendered = 0;
|
|
719
|
+
for (const file of files) {
|
|
720
|
+
const full = path.join(stageDir(projectRoot, stageId), file.rel);
|
|
721
|
+
if (file.json) {
|
|
722
|
+
const data = readJson(full, null);
|
|
723
|
+
if (data === null) continue;
|
|
724
|
+
lines.push('## `' + file.rel + '`');
|
|
725
|
+
lines.push('');
|
|
726
|
+
if (file.rel === 'chapter_map.json') renderChapterMap(data, lines, 3);
|
|
727
|
+
else if (file.rel === 'index.json') renderChapterIndex(data, lines, 3);
|
|
728
|
+
else if (file.rel.startsWith('outlines/')) renderOutline(data, lines, 3);
|
|
729
|
+
else renderTopLevel(data, 3, lines);
|
|
730
|
+
lines.push('---');
|
|
731
|
+
lines.push('');
|
|
732
|
+
rendered += 1;
|
|
733
|
+
} else if (file.markdown) {
|
|
734
|
+
const text = fs.readFileSync(full, 'utf-8');
|
|
735
|
+
lines.push('## `' + file.rel + '`');
|
|
736
|
+
lines.push('');
|
|
737
|
+
lines.push(text.trim());
|
|
738
|
+
lines.push('');
|
|
739
|
+
lines.push('---');
|
|
740
|
+
lines.push('');
|
|
741
|
+
rendered += 1;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (!rendered) {
|
|
745
|
+
lines.push('(当前阶段没有可导出的 JSON 或正文文件。)');
|
|
746
|
+
lines.push('');
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
markdown: lines.join('\n'),
|
|
750
|
+
files: files.map((f) => f.rel),
|
|
751
|
+
stageId,
|
|
752
|
+
title: label,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/** 只导出单章细纲,附带章节地图中该章的条目。 */
|
|
757
|
+
export function renderOutlineMarkdown(projectRoot, chapterId) {
|
|
758
|
+
const dir = stageDir(projectRoot, '40-rhythm');
|
|
759
|
+
const outlineFile = path.join(dir, 'outlines', chapterId + '.json');
|
|
760
|
+
const outline = readJson(outlineFile, null);
|
|
761
|
+
if (!outline) return null;
|
|
762
|
+
const map = readJson(path.join(dir, 'chapter_map.json'), null);
|
|
763
|
+
const chapter = asArray(map && map.chapters).find((c) => String(c.chapter_id) === String(chapterId));
|
|
764
|
+
const lines = [];
|
|
765
|
+
lines.push('# 40-rhythm 单章细纲导出:' + chapterId);
|
|
766
|
+
lines.push('');
|
|
767
|
+
lines.push('> 本文件由 fancy-story-builder 2.0 从章节地图和单章细纲 JSON 生成,供写作模型直接阅读。');
|
|
768
|
+
lines.push('');
|
|
769
|
+
const book = bookNameOf(projectRoot);
|
|
770
|
+
if (book) lines.push('- **项目**:' + book);
|
|
771
|
+
lines.push('- **章节**:' + chapterId);
|
|
772
|
+
lines.push('- **导出时间**:' + new Date().toISOString());
|
|
773
|
+
lines.push('');
|
|
774
|
+
lines.push('---');
|
|
775
|
+
lines.push('');
|
|
776
|
+
if (chapter) {
|
|
777
|
+
lines.push('## 章节地图条目');
|
|
778
|
+
lines.push('');
|
|
779
|
+
renderChapter(chapter, lines, 3);
|
|
780
|
+
lines.push('---');
|
|
781
|
+
lines.push('');
|
|
782
|
+
}
|
|
783
|
+
lines.push('## 细纲');
|
|
784
|
+
lines.push('');
|
|
785
|
+
renderOutline(outline, lines, 3);
|
|
786
|
+
return {
|
|
787
|
+
markdown: lines.join('\n'),
|
|
788
|
+
chapterId,
|
|
789
|
+
chapter,
|
|
790
|
+
outline,
|
|
791
|
+
files: ['chapter_map.json', 'outlines/' + chapterId + '.json'],
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|