@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,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage completion checks for the story graph.
|
|
3
|
+
*
|
|
4
|
+
* These are the "is this stage done enough to move on?" queries. The LLM can
|
|
5
|
+
* call them via `storydb stage-check <stage>` after writing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { validate } from './store.js';
|
|
9
|
+
|
|
10
|
+
const STORY = 'story:main';
|
|
11
|
+
|
|
12
|
+
const CHAR_TYPE_TO_QUOTA = {
|
|
13
|
+
主角: 'protagonist',
|
|
14
|
+
核心配角: 'core_supporting',
|
|
15
|
+
贯穿级核心配角: 'core_supporting',
|
|
16
|
+
卷级配角: 'volume_supporting',
|
|
17
|
+
卷级重要配角: 'volume_supporting',
|
|
18
|
+
核心反派: 'core_antagonist',
|
|
19
|
+
贯穿级核心反派: 'core_antagonist',
|
|
20
|
+
卷级反派: 'volume_antagonist',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const SIZE_ALIASES = {
|
|
24
|
+
small: 'small', 小: 'small', 小型: 'small',
|
|
25
|
+
medium: 'medium', 中: 'medium', 中型: 'medium',
|
|
26
|
+
large: 'large', 大: 'large', 大型: 'large',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function num(value) {
|
|
30
|
+
return Number(value) || 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function addMissing(errors, label, actual, expected) {
|
|
34
|
+
if (num(actual) < num(expected)) {
|
|
35
|
+
errors.push({
|
|
36
|
+
code: 'STAGE_INCOMPLETE',
|
|
37
|
+
message: label + '数量不足:' + actual + ' / ' + expected,
|
|
38
|
+
expected: num(expected),
|
|
39
|
+
actual: num(actual),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function count(sdb, table, where = '') {
|
|
45
|
+
const rows = await sdb.rows(
|
|
46
|
+
`SELECT count() FROM ${table} WHERE story = ${STORY}${where ? ' AND ' + where : ''} GROUP ALL;`,
|
|
47
|
+
);
|
|
48
|
+
return num(rows[0] && rows[0].count);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function checkScale(sdb) {
|
|
52
|
+
const errors = [];
|
|
53
|
+
const scale = await sdb.one('SELECT * FROM scale:main;');
|
|
54
|
+
if (!scale) {
|
|
55
|
+
errors.push({ code: 'SCALE_MISSING', message: '尚无 scale 记录' });
|
|
56
|
+
return { errors, warnings: [], pending: [], counts: {} };
|
|
57
|
+
}
|
|
58
|
+
const story = await sdb.one('SELECT * FROM story:main;');
|
|
59
|
+
const target = num(story.target_chapters);
|
|
60
|
+
const volumes = await sdb.rows(`SELECT * FROM volume WHERE story = ${STORY} ORDER BY chapter_start;`);
|
|
61
|
+
const acts = await sdb.rows(`SELECT * FROM act WHERE story = ${STORY} ORDER BY chapter_start;`);
|
|
62
|
+
if (!volumes.length) errors.push({ code: 'SCALE_VOLUMES', message: '没有卷' });
|
|
63
|
+
if (!acts.length) errors.push({ code: 'SCALE_ACTS', message: '没有幕' });
|
|
64
|
+
const covered = volumes.reduce((sum, v) => sum + Math.max(0, num(v.chapter_end) - num(v.chapter_start) + 1), 0);
|
|
65
|
+
if (target && covered < target) {
|
|
66
|
+
errors.push({ code: 'SCALE_RANGE', message: '卷覆盖章节不足:' + covered + ' / ' + target });
|
|
67
|
+
}
|
|
68
|
+
for (const v of volumes) {
|
|
69
|
+
if (num(v.chapter_start) > num(v.chapter_end)) {
|
|
70
|
+
errors.push({ code: 'SCALE_RANGE', message: '卷 ' + v.volume_id + ' 起止倒置' });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
errors,
|
|
75
|
+
warnings: [],
|
|
76
|
+
pending: [],
|
|
77
|
+
counts: { volumes: volumes.length, acts: acts.length, covered, target },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function checkMacro(sdb) {
|
|
82
|
+
const errors = [];
|
|
83
|
+
const world = await count(sdb, 'world_doc');
|
|
84
|
+
const storyDoc = await count(sdb, 'story_doc');
|
|
85
|
+
const lines = await count(sdb, 'line');
|
|
86
|
+
const nodes = await count(sdb, 'line_node');
|
|
87
|
+
const endings = await count(sdb, 'ending');
|
|
88
|
+
const roles = await count(sdb, 'role_slot');
|
|
89
|
+
if (!world) errors.push({ code: 'MACRO_WORLD', message: '缺少世界观' });
|
|
90
|
+
if (!storyDoc) errors.push({ code: 'MACRO_STORY', message: '缺少故事文档' });
|
|
91
|
+
if (!lines) errors.push({ code: 'MACRO_LINE', message: '缺少主线/支线' });
|
|
92
|
+
if (!nodes) errors.push({ code: 'MACRO_NODES', message: '缺少主线节点' });
|
|
93
|
+
if (!endings) errors.push({ code: 'MACRO_ENDING', message: '缺少结局' });
|
|
94
|
+
if (!roles) errors.push({ code: 'MACRO_ROLES', message: '缺少角色功能位' });
|
|
95
|
+
return {
|
|
96
|
+
errors,
|
|
97
|
+
warnings: [],
|
|
98
|
+
pending: [],
|
|
99
|
+
counts: { world, story: storyDoc, lines, nodes, endings, roles },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function checkCast(sdb) {
|
|
104
|
+
const errors = [];
|
|
105
|
+
const scale = await sdb.one('SELECT * FROM scale:main;');
|
|
106
|
+
const quotas = (scale && scale.quotas) || {};
|
|
107
|
+
const characters = await sdb.rows(
|
|
108
|
+
`SELECT type, count() AS n FROM character WHERE story = ${STORY} GROUP BY type;`,
|
|
109
|
+
);
|
|
110
|
+
const charCounts = {};
|
|
111
|
+
for (const row of characters) {
|
|
112
|
+
const key = CHAR_TYPE_TO_QUOTA[row.type] || row.type;
|
|
113
|
+
charCounts[key] = (charCounts[key] || 0) + num(row.n);
|
|
114
|
+
}
|
|
115
|
+
const charQuotas = quotas.characters || {};
|
|
116
|
+
for (const [key, expected] of Object.entries(charQuotas)) {
|
|
117
|
+
addMissing(errors, '人物(' + key + ')', charCounts[key] || 0, expected);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
addMissing(errors, '势力', await count(sdb, 'faction'), quotas.factions);
|
|
121
|
+
addMissing(errors, '势力关系', await count(sdb, 'faction_relation'), quotas.faction_relations);
|
|
122
|
+
addMissing(errors, '地点', await count(sdb, 'location'), quotas.locations);
|
|
123
|
+
addMissing(errors, '结局', await count(sdb, 'ending'), quotas.endings);
|
|
124
|
+
|
|
125
|
+
const items = await sdb.rows(`SELECT category, count() AS n FROM item WHERE story = ${STORY} GROUP BY category;`);
|
|
126
|
+
const itemQuotas = quotas.items || {};
|
|
127
|
+
const itemCounts = {};
|
|
128
|
+
for (const row of items) {
|
|
129
|
+
const key = { 核心: 'core', 支线: 'branch', 象征: 'symbolic' }[row.category] || row.category;
|
|
130
|
+
itemCounts[key] = (itemCounts[key] || 0) + num(row.n);
|
|
131
|
+
}
|
|
132
|
+
for (const [key, expected] of Object.entries(itemQuotas)) addMissing(errors, '道具(' + key + ')', itemCounts[key] || 0, expected);
|
|
133
|
+
|
|
134
|
+
const conflicts = await sdb.rows(`SELECT type, count() AS n FROM conflict WHERE story = ${STORY} GROUP BY type;`);
|
|
135
|
+
const conflictQuotas = quotas.conflicts || {};
|
|
136
|
+
const conflictCounts = {};
|
|
137
|
+
for (const row of conflicts) {
|
|
138
|
+
const key = { 核心: 'core', 阶段: 'phase', 交织: 'branch' }[row.type] || row.type;
|
|
139
|
+
conflictCounts[key] = (conflictCounts[key] || 0) + num(row.n);
|
|
140
|
+
}
|
|
141
|
+
for (const [key, expected] of Object.entries(conflictQuotas)) addMissing(errors, '冲突(' + key + ')', conflictCounts[key] || 0, expected);
|
|
142
|
+
|
|
143
|
+
const foreshadows = await sdb.rows(`SELECT type, count() AS n FROM foreshadow WHERE story = ${STORY} GROUP BY type;`);
|
|
144
|
+
const fsQuotas = quotas.foreshadows || {};
|
|
145
|
+
const fsCounts = {};
|
|
146
|
+
for (const row of foreshadows) {
|
|
147
|
+
const key = SIZE_ALIASES[row.type] || row.type;
|
|
148
|
+
fsCounts[key] = (fsCounts[key] || 0) + num(row.n);
|
|
149
|
+
}
|
|
150
|
+
for (const [key, expected] of Object.entries(fsQuotas)) addMissing(errors, '伏笔(' + key + ')', fsCounts[key] || 0, expected);
|
|
151
|
+
|
|
152
|
+
const climaxes = await sdb.rows(`SELECT type, count() AS n FROM climax WHERE story = ${STORY} GROUP BY type;`);
|
|
153
|
+
const clQuotas = quotas.climaxes || {};
|
|
154
|
+
const clCounts = {};
|
|
155
|
+
for (const row of climaxes) {
|
|
156
|
+
const key = SIZE_ALIASES[row.type] || row.type;
|
|
157
|
+
clCounts[key] = (clCounts[key] || 0) + num(row.n);
|
|
158
|
+
}
|
|
159
|
+
for (const [key, expected] of Object.entries(clQuotas)) addMissing(errors, '高潮(' + key + ')', clCounts[key] || 0, expected);
|
|
160
|
+
|
|
161
|
+
const payoffs = await sdb.rows(`SELECT scale, count() AS n FROM payoff WHERE story = ${STORY} GROUP BY scale;`);
|
|
162
|
+
const payQuotas = quotas.pleasures || {};
|
|
163
|
+
const payCounts = {};
|
|
164
|
+
for (const row of payoffs) {
|
|
165
|
+
const key = SIZE_ALIASES[row.scale] || row.scale;
|
|
166
|
+
payCounts[key] = (payCounts[key] || 0) + num(row.n);
|
|
167
|
+
}
|
|
168
|
+
for (const [key, expected] of Object.entries(payQuotas)) addMissing(errors, '爽点(' + key + ')', payCounts[key] || 0, expected);
|
|
169
|
+
|
|
170
|
+
const roles = await count(sdb, 'role_slot');
|
|
171
|
+
if (!roles) errors.push({ code: 'CAST_ROLES', message: '缺少角色功能位' });
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
errors,
|
|
175
|
+
warnings: [],
|
|
176
|
+
pending: [],
|
|
177
|
+
counts: {
|
|
178
|
+
characters: charCounts,
|
|
179
|
+
factions: await count(sdb, 'faction'),
|
|
180
|
+
faction_relations: await count(sdb, 'faction_relation'),
|
|
181
|
+
locations: await count(sdb, 'location'),
|
|
182
|
+
items: itemCounts,
|
|
183
|
+
conflicts: conflictCounts,
|
|
184
|
+
foreshadows: fsCounts,
|
|
185
|
+
climaxes: clCounts,
|
|
186
|
+
payoffs: payCounts,
|
|
187
|
+
information: await count(sdb, 'information'),
|
|
188
|
+
endings: await count(sdb, 'ending'),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function checkRhythm(sdb) {
|
|
194
|
+
const errors = [];
|
|
195
|
+
const story = await sdb.one('SELECT * FROM story:main;');
|
|
196
|
+
const target = num(story.target_chapters);
|
|
197
|
+
const chapters = await sdb.rows(
|
|
198
|
+
`SELECT chapter_id, display_no FROM chapter WHERE story = ${STORY} ORDER BY display_no;`,
|
|
199
|
+
);
|
|
200
|
+
if (target && chapters.length < target) {
|
|
201
|
+
errors.push({ code: 'RHYTHM_CHAPTERS', message: '章节数不足:' + chapters.length + ' / ' + target });
|
|
202
|
+
}
|
|
203
|
+
const seen = new Set(chapters.map((c) => num(c.display_no)));
|
|
204
|
+
for (let i = 1; i <= Math.max(target, chapters.length); i += 1) {
|
|
205
|
+
if (!seen.has(i)) {
|
|
206
|
+
errors.push({ code: 'RHYTHM_GAP', message: '缺少第 ' + i + ' 章' });
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const noCast = await sdb.rows(
|
|
211
|
+
`SELECT chapter_id, display_no FROM chapter
|
|
212
|
+
WHERE story = ${STORY} AND array::len(->chapter_cast) = 0 ORDER BY display_no LIMIT 10;`,
|
|
213
|
+
);
|
|
214
|
+
for (const c of noCast) {
|
|
215
|
+
errors.push({ code: 'RHYTHM_CAST', message: '第 ' + c.display_no + ' 章没有出场人物' });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const report = await validate(sdb);
|
|
219
|
+
for (const error of report.errors) {
|
|
220
|
+
if (error.code === 'REQUIREMENT_PENDING') continue;
|
|
221
|
+
errors.push(error);
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
errors,
|
|
225
|
+
warnings: report.warnings,
|
|
226
|
+
pending: [],
|
|
227
|
+
counts: {
|
|
228
|
+
chapters: chapters.length,
|
|
229
|
+
target,
|
|
230
|
+
missing_chapters: Math.max(0, target - chapters.length),
|
|
231
|
+
casts: await count(sdb, 'chapter_cast'),
|
|
232
|
+
chapter_locations: await count(sdb, 'chapter_location'),
|
|
233
|
+
chapter_line_nodes: await count(sdb, 'chapter_line_node'),
|
|
234
|
+
chapter_branches: await count(sdb, 'chapter_branch'),
|
|
235
|
+
chapter_conflicts: await count(sdb, 'chapter_conflict'),
|
|
236
|
+
chapter_climaxes: await count(sdb, 'chapter_climax'),
|
|
237
|
+
chapter_items: await count(sdb, 'chapter_item'),
|
|
238
|
+
ending_actions: await count(sdb, 'ending_action'),
|
|
239
|
+
foreshadow_actions: await count(sdb, 'foreshadow_action'),
|
|
240
|
+
payoff_actions: await count(sdb, 'payoff_action'),
|
|
241
|
+
info_actions: await count(sdb, 'info_action'),
|
|
242
|
+
knowledge_events: await count(sdb, 'knowledge_event'),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function checkOutline(sdb, opts = {}) {
|
|
248
|
+
const errors = [];
|
|
249
|
+
const allChapters = await sdb.rows(
|
|
250
|
+
`SELECT chapter_id, display_no FROM chapter WHERE story = ${STORY} ORDER BY display_no;`,
|
|
251
|
+
);
|
|
252
|
+
const chapters = opts.chapterIds
|
|
253
|
+
? allChapters.filter((row) => opts.chapterIds.has(row.chapter_id))
|
|
254
|
+
: allChapters;
|
|
255
|
+
const allOutlines = await sdb.rows(
|
|
256
|
+
`SELECT chapter_id FROM outline WHERE story = ${STORY};`,
|
|
257
|
+
);
|
|
258
|
+
const outlines = opts.chapterIds
|
|
259
|
+
? allOutlines.filter((row) => opts.chapterIds.has(row.chapter_id))
|
|
260
|
+
: allOutlines;
|
|
261
|
+
const outlineSet = new Set(outlines.map((o) => o.chapter_id));
|
|
262
|
+
const missing = chapters.filter((c) => !outlineSet.has(c.chapter_id));
|
|
263
|
+
for (const c of missing.slice(0, 10)) {
|
|
264
|
+
errors.push({ code: 'OUTLINE_MISSING', message: '第 ' + c.display_no + ' 章缺少细纲' });
|
|
265
|
+
}
|
|
266
|
+
if (missing.length > 10) errors.push({ code: 'OUTLINE_MISSING', message: '还有 ' + (missing.length - 10) + ' 章缺少细纲' });
|
|
267
|
+
|
|
268
|
+
const allEmpty = await sdb.rows(
|
|
269
|
+
`SELECT chapter_id FROM outline WHERE story = ${STORY} AND array::len(->outline_scene) = 0 LIMIT 10;`,
|
|
270
|
+
);
|
|
271
|
+
const empty = opts.chapterIds
|
|
272
|
+
? allEmpty.filter((row) => opts.chapterIds.has(row.chapter_id))
|
|
273
|
+
: allEmpty;
|
|
274
|
+
for (const o of empty.slice(0, 10)) {
|
|
275
|
+
errors.push({ code: 'OUTLINE_SCENES', message: o.chapter_id + ' 没有场景' });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const report = await validate(sdb);
|
|
279
|
+
const allOutlineRows = await sdb.rows(
|
|
280
|
+
`SELECT chapter_id, must_write FROM outline WHERE story = ${STORY};`,
|
|
281
|
+
);
|
|
282
|
+
const outlineRows = opts.chapterIds
|
|
283
|
+
? allOutlineRows.filter((row) => opts.chapterIds.has(row.chapter_id))
|
|
284
|
+
: allOutlineRows;
|
|
285
|
+
const sceneCounts = new Map(
|
|
286
|
+
(await sdb.rows(
|
|
287
|
+
`SELECT in, count() AS n FROM outline_scene WHERE story = ${STORY} GROUP BY in;`,
|
|
288
|
+
)).map((row) => [String(row.in).split(':').pop().replace(/[⟨⟩]/g, ''), Number(row.n) || 0]),
|
|
289
|
+
);
|
|
290
|
+
const warnings = reportWarnings(report).filter((item) => !opts.chapterIds
|
|
291
|
+
|| !item.scope || !String(item.scope).startsWith('chapter:')
|
|
292
|
+
|| opts.chapterIds.has(String(item.scope).split(':').pop()));
|
|
293
|
+
for (const row of outlineRows) {
|
|
294
|
+
const sceneCount = sceneCounts.get(String(row.chapter_id)) || 0;
|
|
295
|
+
if (sceneCount < 2) {
|
|
296
|
+
warnings.push({
|
|
297
|
+
code: 'OUTLINE_THIN_SCENES',
|
|
298
|
+
severity: 'warning',
|
|
299
|
+
scope: 'outline:' + row.chapter_id,
|
|
300
|
+
message: row.chapter_id + ' 只有 ' + sceneCount + ' 个场景;一般应有 2-4 个',
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (!Array.isArray(row.must_write) || row.must_write.length < 3) {
|
|
304
|
+
warnings.push({
|
|
305
|
+
code: 'OUTLINE_THIN_BEATS',
|
|
306
|
+
severity: 'warning',
|
|
307
|
+
scope: 'outline:' + row.chapter_id,
|
|
308
|
+
message: row.chapter_id + ' 的 must_write 少于 3 条',
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for (const error of report.errors) {
|
|
314
|
+
if (error.code === 'REQUIREMENT_PENDING') continue;
|
|
315
|
+
if (opts.chapterIds && error.scope && String(error.scope).startsWith('chapter:')
|
|
316
|
+
&& !opts.chapterIds.has(String(error.scope).split(':').pop())) continue;
|
|
317
|
+
errors.push(error);
|
|
318
|
+
}
|
|
319
|
+
const selectedOutlineIds = new Set(outlines.map((row) => row.chapter_id));
|
|
320
|
+
const countForOutlines = async (table) => {
|
|
321
|
+
if (!opts.chapterIds) return count(sdb, table);
|
|
322
|
+
const rows = await sdb.rows(`SELECT in FROM ${table} WHERE story = ${STORY};`);
|
|
323
|
+
return rows.filter((row) => selectedOutlineIds.has(String(row.in).split(':').pop().replace(/[⟨⟩]/g, ''))).length;
|
|
324
|
+
};
|
|
325
|
+
const scenesForSelection = opts.chapterIds
|
|
326
|
+
? [...selectedOutlineIds].reduce((sum, id) => sum + (sceneCounts.get(String(id)) || 0), 0)
|
|
327
|
+
: await count(sdb, 'scene');
|
|
328
|
+
return {
|
|
329
|
+
errors,
|
|
330
|
+
warnings,
|
|
331
|
+
pending: [],
|
|
332
|
+
counts: {
|
|
333
|
+
chapters: chapters.length,
|
|
334
|
+
outlines: outlines.length,
|
|
335
|
+
missing_outlines: missing.length,
|
|
336
|
+
scenes: scenesForSelection,
|
|
337
|
+
outline_casts: await countForOutlines('outline_cast'),
|
|
338
|
+
outline_locations: await countForOutlines('outline_location'),
|
|
339
|
+
outline_foreshadows: await countForOutlines('outline_foreshadow'),
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function reportWarnings(report) {
|
|
345
|
+
return report && Array.isArray(report.warnings) ? report.warnings : [];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function checkWriting(sdb, opts = {}) {
|
|
349
|
+
const errors = [];
|
|
350
|
+
const allChapters = await sdb.rows(
|
|
351
|
+
`SELECT chapter_id, display_no, word_target FROM chapter WHERE story = ${STORY} ORDER BY display_no;`,
|
|
352
|
+
);
|
|
353
|
+
const chapters = opts.chapterIds
|
|
354
|
+
? allChapters.filter((row) => opts.chapterIds.has(row.chapter_id))
|
|
355
|
+
: allChapters;
|
|
356
|
+
const allTexts = await sdb.rows(
|
|
357
|
+
`SELECT chapter_id, display_no, word_count FROM chapter_text WHERE story = ${STORY};`,
|
|
358
|
+
);
|
|
359
|
+
const texts = opts.chapterIds
|
|
360
|
+
? allTexts.filter((row) => opts.chapterIds.has(row.chapter_id))
|
|
361
|
+
: allTexts;
|
|
362
|
+
const textSet = new Set(texts.map((t) => t.chapter_id));
|
|
363
|
+
const missing = chapters.filter((c) => !textSet.has(c.chapter_id));
|
|
364
|
+
for (const c of missing.slice(0, 10)) {
|
|
365
|
+
errors.push({ code: 'WRITING_MISSING', message: '第 ' + c.display_no + ' 章缺少正文' });
|
|
366
|
+
}
|
|
367
|
+
if (missing.length > 10) errors.push({ code: 'WRITING_MISSING', message: '还有 ' + (missing.length - 10) + ' 章缺少正文' });
|
|
368
|
+
const chapterById = new Map(chapters.map((row) => [row.chapter_id, row]));
|
|
369
|
+
const warnings = [];
|
|
370
|
+
for (const text of texts) {
|
|
371
|
+
if (num(text.word_count) <= 0) {
|
|
372
|
+
errors.push({ code: 'WRITING_EMPTY', message: '第 ' + text.display_no + ' 章正文为空' });
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const target = num(chapterById.get(text.chapter_id)?.word_target);
|
|
376
|
+
if (target && num(text.word_count) < target * 0.8) {
|
|
377
|
+
warnings.push({
|
|
378
|
+
code: 'WRITING_SHORT',
|
|
379
|
+
severity: 'warning',
|
|
380
|
+
scope: 'chapter:' + text.chapter_id,
|
|
381
|
+
message: '第 ' + text.display_no + ' 章正文 ' + text.word_count + ' 字,低于目标 ' + target + ' 的 80%',
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const report = await validate(sdb);
|
|
386
|
+
for (const error of report.errors) {
|
|
387
|
+
if (error.code === 'REQUIREMENT_PENDING') continue;
|
|
388
|
+
if (opts.chapterIds && error.scope && String(error.scope).startsWith('chapter:')
|
|
389
|
+
&& !opts.chapterIds.has(String(error.scope).split(':').pop())) continue;
|
|
390
|
+
errors.push(error);
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
errors,
|
|
394
|
+
warnings: [
|
|
395
|
+
...report.warnings.filter((item) => !opts.chapterIds
|
|
396
|
+
|| !item.scope || !String(item.scope).startsWith('chapter:')
|
|
397
|
+
|| opts.chapterIds.has(String(item.scope).split(':').pop())),
|
|
398
|
+
...warnings,
|
|
399
|
+
],
|
|
400
|
+
pending: [],
|
|
401
|
+
counts: {
|
|
402
|
+
chapters: chapters.length,
|
|
403
|
+
texts: texts.length,
|
|
404
|
+
missing_texts: missing.length,
|
|
405
|
+
words: texts.reduce((sum, text) => sum + (Number(text.word_count) || 0), 0),
|
|
406
|
+
handoffs: await count(sdb, 'handoff'),
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const CHECKS = {
|
|
412
|
+
scale: checkScale,
|
|
413
|
+
macro: checkMacro,
|
|
414
|
+
cast: checkCast,
|
|
415
|
+
rhythm: checkRhythm,
|
|
416
|
+
outline: checkOutline,
|
|
417
|
+
writing: checkWriting,
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export async function stageCheck(sdb, stage, opts = {}) {
|
|
421
|
+
const check = CHECKS[stage];
|
|
422
|
+
if (!check) throw new Error('未知阶段:' + stage);
|
|
423
|
+
const result = await check(sdb, opts);
|
|
424
|
+
const ok = result.errors.length === 0;
|
|
425
|
+
return { ok, stage, ...result };
|
|
426
|
+
}
|