novel-maker 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/bin/novel-maker.js +229 -0
- package/package.json +33 -0
- package/skill/CHANGELOG.md +82 -0
- package/skill/QUICK-REF.md +168 -0
- package/skill/README.md +75 -0
- package/skill/SKILL.md +715 -0
- package/skill/agents/README.md +59 -0
- package/skill/agents/auditor.md +234 -0
- package/skill/agents/coordinator.md +150 -0
- package/skill/agents/planner.md +220 -0
- package/skill/agents/reviewer.md +249 -0
- package/skill/agents/reviser.md +144 -0
- package/skill/agents/writer.md +213 -0
- package/skill/arc-templates/README.md +43 -0
- package/skill/arc-templates/apocalypse/ability.md +81 -0
- package/skill/arc-templates/apocalypse/faction.md +81 -0
- package/skill/arc-templates/apocalypse/humanity.md +81 -0
- package/skill/arc-templates/apocalypse/survival.md +81 -0
- package/skill/arc-templates/game/competition.md +81 -0
- package/skill/arc-templates/game/dungeon.md +81 -0
- package/skill/arc-templates/game/guild-war.md +81 -0
- package/skill/arc-templates/game/leveling.md +80 -0
- package/skill/arc-templates/general/challenge.md +44 -0
- package/skill/arc-templates/general/conflict.md +71 -0
- package/skill/arc-templates/general/explore.md +71 -0
- package/skill/arc-templates/general/growth.md +71 -0
- package/skill/arc-templates/general/mystery.md +71 -0
- package/skill/arc-templates/general/relation.md +71 -0
- package/skill/arc-templates/history/battle.md +71 -0
- package/skill/arc-templates/history/politics.md +71 -0
- package/skill/arc-templates/history/reform.md +71 -0
- package/skill/arc-templates/infinite-flow/boss.md +71 -0
- package/skill/arc-templates/infinite-flow/dungeon.md +71 -0
- package/skill/arc-templates/infinite-flow/enhance.md +71 -0
- package/skill/arc-templates/infinite-flow/team.md +71 -0
- package/skill/arc-templates/mystery/case.md +71 -0
- package/skill/arc-templates/mystery/deduction.md +71 -0
- package/skill/arc-templates/mystery/twist.md +71 -0
- package/skill/arc-templates/romance/angst.md +80 -0
- package/skill/arc-templates/romance/chase.md +71 -0
- package/skill/arc-templates/romance/slow-burn.md +71 -0
- package/skill/arc-templates/romance/sweet.md +80 -0
- package/skill/arc-templates/sci-fi/awakening.md +81 -0
- package/skill/arc-templates/sci-fi/breakthrough.md +81 -0
- package/skill/arc-templates/sci-fi/contact.md +81 -0
- package/skill/arc-templates/sci-fi/exploration.md +81 -0
- package/skill/arc-templates/urban/business.md +71 -0
- package/skill/arc-templates/urban/revenge.md +71 -0
- package/skill/arc-templates/urban/rise.md +71 -0
- package/skill/arc-templates/urban/romance.md +71 -0
- package/skill/arc-templates/western-fantasy/adventure.md +80 -0
- package/skill/arc-templates/western-fantasy/kingdom.md +71 -0
- package/skill/arc-templates/western-fantasy/magic-awakening.md +71 -0
- package/skill/arc-templates/western-fantasy/racial-conflict.md +71 -0
- package/skill/arc-templates/wuxia/breakthrough.md +71 -0
- package/skill/arc-templates/wuxia/grudge.md +71 -0
- package/skill/arc-templates/wuxia/hero-path.md +71 -0
- package/skill/arc-templates/wuxia/sect-war.md +71 -0
- package/skill/arc-templates/xianxia/breakthrough.md +71 -0
- package/skill/arc-templates/xianxia/dungeon.md +71 -0
- package/skill/arc-templates/xianxia/tournament.md +71 -0
- package/skill/arc-templates/xianxia/tribulation.md +71 -0
- package/skill/docs/examples.md +61 -0
- package/skill/docs/faq.md +81 -0
- package/skill/docs/installation.md +87 -0
- package/skill/docs/quickstart.md +83 -0
- package/skill/genre-packs/README.md +47 -0
- package/skill/genre-packs/_default/arc-types.md +153 -0
- package/skill/genre-packs/_default/rules.md +56 -0
- package/skill/genre-packs/_default/templates.md +135 -0
- package/skill/genre-packs/apocalypse/arc-types.md +109 -0
- package/skill/genre-packs/apocalypse/rules.md +113 -0
- package/skill/genre-packs/apocalypse/settings.md +106 -0
- package/skill/genre-packs/apocalypse/templates.md +192 -0
- package/skill/genre-packs/game/arc-types.md +109 -0
- package/skill/genre-packs/game/rules.md +113 -0
- package/skill/genre-packs/game/settings.md +103 -0
- package/skill/genre-packs/game/templates.md +173 -0
- package/skill/genre-packs/history/arc-types.md +109 -0
- package/skill/genre-packs/history/rules.md +107 -0
- package/skill/genre-packs/history/settings.md +126 -0
- package/skill/genre-packs/history/templates.md +179 -0
- package/skill/genre-packs/infinite-flow/arc-types.md +101 -0
- package/skill/genre-packs/infinite-flow/rules.md +75 -0
- package/skill/genre-packs/infinite-flow/settings.md +102 -0
- package/skill/genre-packs/infinite-flow/templates.md +226 -0
- package/skill/genre-packs/mystery/arc-types.md +109 -0
- package/skill/genre-packs/mystery/rules.md +107 -0
- package/skill/genre-packs/mystery/settings.md +103 -0
- package/skill/genre-packs/mystery/templates.md +178 -0
- package/skill/genre-packs/romance/arc-types.md +130 -0
- package/skill/genre-packs/romance/rules.md +88 -0
- package/skill/genre-packs/romance/settings.md +146 -0
- package/skill/genre-packs/romance/templates.md +245 -0
- package/skill/genre-packs/sci-fi/arc-types.md +109 -0
- package/skill/genre-packs/sci-fi/rules.md +113 -0
- package/skill/genre-packs/sci-fi/settings.md +99 -0
- package/skill/genre-packs/sci-fi/templates.md +170 -0
- package/skill/genre-packs/urban/arc-types.md +101 -0
- package/skill/genre-packs/urban/rules.md +75 -0
- package/skill/genre-packs/urban/settings.md +82 -0
- package/skill/genre-packs/urban/templates.md +212 -0
- package/skill/genre-packs/western-fantasy/arc-types.md +128 -0
- package/skill/genre-packs/western-fantasy/rules.md +88 -0
- package/skill/genre-packs/western-fantasy/settings.md +160 -0
- package/skill/genre-packs/western-fantasy/templates.md +225 -0
- package/skill/genre-packs/wuxia/arc-types.md +126 -0
- package/skill/genre-packs/wuxia/rules.md +86 -0
- package/skill/genre-packs/wuxia/settings.md +150 -0
- package/skill/genre-packs/wuxia/templates.md +195 -0
- package/skill/genre-packs/xianxia/arc-types.md +101 -0
- package/skill/genre-packs/xianxia/rules.md +74 -0
- package/skill/genre-packs/xianxia/settings.md +107 -0
- package/skill/genre-packs/xianxia/templates.md +202 -0
- package/skill/hooks/README.md +102 -0
- package/skill/hooks/chapter-complete.md +176 -0
- package/skill/hooks/context-injection.md +152 -0
- package/skill/hooks/intent-detection.md +183 -0
- package/skill/hooks/review-trigger.md +219 -0
- package/skill/hooks/summary-trigger.md +185 -0
- package/skill/references/act-guidance.md +228 -0
- package/skill/references/audit-core.md +130 -0
- package/skill/references/audit-dimensions.md +202 -0
- package/skill/references/character-voice-card.md +196 -0
- package/skill/references/consistency-checker.md +209 -0
- package/skill/references/content-expansion.md +68 -0
- package/skill/references/creative-constraints.md +200 -0
- package/skill/references/data-agent.md +286 -0
- package/skill/references/dialogue-writing.md +104 -0
- package/skill/references/editorial-perspective.md +166 -0
- package/skill/references/emotion-curve.md +127 -0
- package/skill/references/genre-rules.md +389 -0
- package/skill/references/golden-opening.md +81 -0
- package/skill/references/memory-system.md +288 -0
- package/skill/references/pacing-analysis.md +201 -0
- package/skill/references/platform-rules.md +244 -0
- package/skill/references/plot-structures.md +108 -0
- package/skill/references/reader-feedback.md +119 -0
- package/skill/references/rhythm-system.md +204 -0
- package/skill/references/style-imitation.md +193 -0
- package/skill/references/sweet-spot-tracking.md +182 -0
- package/skill/references/usage-guide.md +174 -0
- package/skill/references/writing-methods.md +169 -0
- package/skill/rules/anti-ai-expressions.md +206 -0
- package/skill/rules/character-voice.md +184 -0
- package/skill/rules/consistency-check.md +232 -0
- package/skill/rules/smart-query.md +263 -0
- package/skill/scripts/README.md +380 -0
- package/skill/scripts/auditor/chapter_transition.py +217 -0
- package/skill/scripts/auditor/consistency_scan.py +194 -0
- package/skill/scripts/auditor/dialogue_checker.py +194 -0
- package/skill/scripts/auditor/hook_report.py +115 -0
- package/skill/scripts/auditor/pacing_optimizer.py +303 -0
- package/skill/scripts/auditor/pacing_report.py +275 -0
- package/skill/scripts/auditor/pre_audit.py +203 -0
- package/skill/scripts/auditor/style_check.py +158 -0
- package/skill/scripts/auditor/worldbuilding_checker.py +637 -0
- package/skill/scripts/common/analyze.py +129 -0
- package/skill/scripts/common/init_guide.py +796 -0
- package/skill/scripts/common/install.py +169 -0
- package/skill/scripts/common/nm_utils.py +296 -0
- package/skill/scripts/common/validate.py +215 -0
- package/skill/scripts/coordinator/stats_report.py +165 -0
- package/skill/scripts/coordinator/volume_batch.py +121 -0
- package/skill/scripts/planner/outline_extractor.py +89 -0
- package/skill/scripts/planner/planner_context.py +220 -0
- package/skill/scripts/planner/query_engine.py +289 -0
- package/skill/scripts/reviewer/chapter_diff.py +143 -0
- package/skill/scripts/reviewer/character_arc_tracker.py +191 -0
- package/skill/scripts/reviewer/emotion_curve.py +340 -0
- package/skill/scripts/reviewer/foreshadowing_tracker.py +286 -0
- package/skill/scripts/reviewer/subplot_tracker.py +207 -0
- package/skill/scripts/reviewer/summary_generator.py +130 -0
- package/skill/scripts/reviewer/truth_diff.py +227 -0
- package/skill/scripts/reviewer/truth_manager.py +120 -0
- package/skill/scripts/test_scripts.py +366 -0
- package/skill/scripts/writer/build_write_context.py +255 -0
- package/skill/scripts/writer/chapter_info.py +67 -0
- package/skill/scripts/writer/check_wordcount.py +115 -0
- package/skill/scripts/writer/scene_builder.py +227 -0
- package/skill/scripts/writer/style_anchor.py +135 -0
- package/skill/styles/author-styles.md +53 -0
- package/skill/styles/authors//344/270/245/350/260/250/350/256/276/345/256/232/346/265/201//345/277/230/350/257/255.md +110 -0
- package/skill/styles/authors//344/270/245/350/260/250/350/256/276/345/256/232/346/265/201//347/210/261/346/275/234/346/260/264/347/232/204/344/271/214/350/264/274.md +110 -0
- package/skill/styles/authors//344/270/245/350/260/250/350/256/276/345/256/232/346/265/201//350/250/200/345/275/222/346/255/243/344/274/240.md +110 -0
- package/skill/styles/authors//345/244/232/347/245/236/350/257/235/347/203/255/350/241/200/346/265/201//344/270/211/344/271/235/351/237/263/345/237/237.md +108 -0
- package/skill/styles/authors//346/202/254/347/226/221/346/216/250/347/220/206/346/265/201//346/235/200/350/231/253/351/230/237/351/230/237/345/221/230.md +108 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//344/270/211/345/244/251/344/270/244/350/247/211.md +110 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//344/274/232/350/257/264/350/257/235/347/232/204/350/202/230/345/255/220.md +110 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//345/215/226/346/212/245/345/260/217/351/203/216/345/220/233.md +108 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//345/274/210/351/235/222/345/263/260.md +123 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//345/224/220/345/256/266/344/270/211/345/260/221.md +109 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//345/244/251/350/232/225/345/234/237/350/261/206.md +110 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//346/210/221/345/220/203/350/245/277/347/272/242/346/237/277.md +108 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//346/273/232/345/274/200.md +109 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//350/276/260/344/270/234.md +109 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//345/244/251/344/270/213/351/234/270/345/224/261.md +110 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//346/234/210/345/205/263.md +108 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//350/220/247/351/274/216.md +109 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//350/235/264/350/235/266/350/223/235.md +112 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//346/204/244/346/200/222/347/232/204/351/246/231/350/225/211.md +109 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//347/203/275/347/201/253/346/210/217/350/257/270/344/276/257.md +109 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//347/214/253/350/205/273.md +110 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//350/200/263/346/240/271.md +109 -0
- package/skill/templates/INDEX.md +48 -0
- package/skill/templates/act-plan.md +72 -0
- package/skill/templates/chapter.md +53 -0
- package/skill/templates/character-profile.md +107 -0
- package/skill/templates/character-voice.md +106 -0
- package/skill/templates/constitution.md +90 -0
- package/skill/templates/emotional-arcs.md +37 -0
- package/skill/templates/hook-template.md +68 -0
- package/skill/templates/outline.md +155 -0
- package/skill/templates/plot-card.md +432 -0
- package/skill/templates/power-system.md +124 -0
- package/skill/templates/presets.json +69 -0
- package/skill/templates/review-report.md +135 -0
- package/skill/templates/scene-plan.md +221 -0
- package/skill/templates/scene-template.md +78 -0
- package/skill/templates/subplot-board.md +48 -0
- package/skill/templates/summary-10chapters.md +79 -0
- package/skill/templates/summary-50chapters.md +131 -0
- package/skill/templates/summary-volume.md +148 -0
- package/skill/templates/timeline.md +37 -0
- package/skill/templates/volume-plan.md +44 -0
- package/skill/templates/world-setting.md +151 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
世界观一致性检查脚本
|
|
5
|
+
检测世界观设定的前后矛盾,包括力量体系、地理、历史、社会制度等。
|
|
6
|
+
供审计师在审查时使用,或复盘师在卷末复盘时参考。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
python scripts/auditor/worldbuilding_checker.py 章节目录 --truth 真相文件目录 --json
|
|
10
|
+
python scripts/auditor/worldbuilding_checker.py 章节目录 --truth 真相文件目录 --recent 10
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
|
|
21
|
+
from nm_utils import (
|
|
22
|
+
list_chapters, read_chapter, count_chinese, clean_markdown,
|
|
23
|
+
read_truth_section
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# ─── 世界观检查维度 ──────────────────────────────
|
|
27
|
+
WORLDBUILDING_DIMENSIONS = {
|
|
28
|
+
'power_system': {
|
|
29
|
+
'name': '力量体系',
|
|
30
|
+
'keywords': ['等级', '境界', '修为', '实力', '突破', '晋升', '阶位', '段位'],
|
|
31
|
+
'check_patterns': [
|
|
32
|
+
r'(\w+)\s*(?:从|由)\s*(\w+)\s*(?:突破|晋升|进阶)\s*(?:到|为)\s*(\w+)',
|
|
33
|
+
r'(\w+)\s*(?:是|为)\s*(\w+)\s*(?:级别|等级|境界)'
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
'geography': {
|
|
37
|
+
'name': '地理设定',
|
|
38
|
+
'keywords': ['城市', '国家', '大陆', '山脉', '河流', '森林', '沙漠', '海洋'],
|
|
39
|
+
'check_patterns': [
|
|
40
|
+
r'(?:位于|在)\s*(\w+)\s*(?:的|方向|方位)',
|
|
41
|
+
r'(\w+)\s*(?:距离|相距)\s*(\w+)\s*(?:有|约)\s*(\d+)\s*(?:里|公里)'
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
'history': {
|
|
45
|
+
'name': '历史设定',
|
|
46
|
+
'keywords': ['年前', '年前', '历史', '传说', '古老', '过去', '曾经', '当年'],
|
|
47
|
+
'check_patterns': [
|
|
48
|
+
r'(\d+)\s*(?:年前|百年前|千年前)',
|
|
49
|
+
r'(?:在|于)\s*(\w+)\s*(?:时期|时代|年代)'
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
'social_system': {
|
|
53
|
+
'name': '社会制度',
|
|
54
|
+
'keywords': ['宗门', '门派', '家族', '朝廷', '帝国', '王国', '组织', '势力'],
|
|
55
|
+
'check_patterns': [
|
|
56
|
+
r'(\w+)\s*(?:是|属于)\s*(\w+)\s*(?:的|宗门|门派|家族)',
|
|
57
|
+
r'(\w+)\s*(?:担任|是)\s*(\w+)\s*(?:职位|职务|身份)'
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
'items': {
|
|
61
|
+
'name': '物品设定',
|
|
62
|
+
'keywords': ['宝物', '神器', '丹药', '武器', '装备', '秘籍', '功法'],
|
|
63
|
+
'check_patterns': [
|
|
64
|
+
r'(\w+)\s*(?:具有|拥有)\s*(\w+)\s*(?:效果|功能|作用)',
|
|
65
|
+
r'(\w+)\s*(?:需要|要求)\s*(\w+)\s*(?:才能|方可)\s*(?:使用|激活)'
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def extract_worldbuilding_facts(text, dimension_config):
|
|
72
|
+
"""Extract worldbuilding facts from text based on dimension config."""
|
|
73
|
+
facts = []
|
|
74
|
+
|
|
75
|
+
for pattern in dimension_config['check_patterns']:
|
|
76
|
+
matches = re.finditer(pattern, text)
|
|
77
|
+
for match in matches:
|
|
78
|
+
facts.append({
|
|
79
|
+
'pattern': pattern,
|
|
80
|
+
'match': match.group(0),
|
|
81
|
+
'groups': match.groups(),
|
|
82
|
+
'position': match.start()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return facts
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def check_worldbuilding_consistency(chapters_dir, truth_dir=None, recent_n=None):
|
|
89
|
+
"""Check worldbuilding consistency across chapters."""
|
|
90
|
+
chapter_files = list_chapters(chapters_dir, recent_n)
|
|
91
|
+
if not chapter_files:
|
|
92
|
+
return {'error': '未找到章节文件'}
|
|
93
|
+
|
|
94
|
+
# Load truth files if provided
|
|
95
|
+
truth_facts = {}
|
|
96
|
+
if truth_dir and os.path.isdir(truth_dir):
|
|
97
|
+
for dimension in WORLDBUILDING_DIMENSIONS.keys():
|
|
98
|
+
truth_file = os.path.join(truth_dir, f'{dimension}.md')
|
|
99
|
+
if os.path.exists(truth_file):
|
|
100
|
+
with open(truth_file, 'r', encoding='utf-8') as f:
|
|
101
|
+
truth_facts[dimension] = f.read()
|
|
102
|
+
|
|
103
|
+
# Analyze each chapter
|
|
104
|
+
chapter_facts = defaultdict(lambda: defaultdict(list))
|
|
105
|
+
issues = []
|
|
106
|
+
|
|
107
|
+
for idx, cf in enumerate(chapter_files):
|
|
108
|
+
raw, title, clean, wc = read_chapter(cf)
|
|
109
|
+
ch_num = idx + 1
|
|
110
|
+
|
|
111
|
+
for dimension, config in WORLDBUILDING_DIMENSIONS.items():
|
|
112
|
+
facts = extract_worldbuilding_facts(clean, config)
|
|
113
|
+
chapter_facts[ch_num][dimension] = facts
|
|
114
|
+
|
|
115
|
+
# Check against truth files
|
|
116
|
+
if dimension in truth_facts:
|
|
117
|
+
truth_content = truth_facts[dimension]
|
|
118
|
+
for fact in facts:
|
|
119
|
+
# Simple consistency check: verify key terms appear in truth
|
|
120
|
+
for group in fact['groups']:
|
|
121
|
+
if group and group not in truth_content:
|
|
122
|
+
issues.append({
|
|
123
|
+
'type': '设定冲突',
|
|
124
|
+
'dimension': config['name'],
|
|
125
|
+
'chapter': ch_num,
|
|
126
|
+
'chapter_title': title,
|
|
127
|
+
'fact': fact['match'],
|
|
128
|
+
'message': f"「{group}」未在真相文件「{dimension}.md」中找到",
|
|
129
|
+
'severity': 'warning'
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
# Detect internal contradictions (same entity with different descriptions)
|
|
133
|
+
entity_descriptions = defaultdict(list)
|
|
134
|
+
for ch_num, dimensions in chapter_facts.items():
|
|
135
|
+
for dimension, facts in dimensions.items():
|
|
136
|
+
for fact in facts:
|
|
137
|
+
if fact['groups'] and len(fact['groups']) >= 2:
|
|
138
|
+
entity = fact['groups'][0]
|
|
139
|
+
description = fact['groups'][1]
|
|
140
|
+
entity_descriptions[entity].append({
|
|
141
|
+
'chapter': ch_num,
|
|
142
|
+
'dimension': dimension,
|
|
143
|
+
'description': description
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
# Find contradictions
|
|
147
|
+
for entity, descriptions in entity_descriptions.items():
|
|
148
|
+
unique_descriptions = set(d['description'] for d in descriptions)
|
|
149
|
+
if len(unique_descriptions) > 1:
|
|
150
|
+
issues.append({
|
|
151
|
+
'type': '内部矛盾',
|
|
152
|
+
'dimension': WORLDBUILDING_DIMENSIONS[descriptions[0]['dimension']]['name'],
|
|
153
|
+
'entity': entity,
|
|
154
|
+
'descriptions': descriptions,
|
|
155
|
+
'message': f"「{entity}」在不同章节有不同描述",
|
|
156
|
+
'severity': 'error'
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
# Summary statistics
|
|
160
|
+
dimension_stats = {}
|
|
161
|
+
for dimension, config in WORLDBUILDING_DIMENSIONS.items():
|
|
162
|
+
total_facts = sum(len(chapter_facts[ch][dimension]) for ch in chapter_facts)
|
|
163
|
+
dimension_stats[dimension] = {
|
|
164
|
+
'name': config['name'],
|
|
165
|
+
'total_facts': total_facts,
|
|
166
|
+
'chapters_with_facts': sum(1 for ch in chapter_facts if chapter_facts[ch][dimension])
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
'total_chapters': len(chapter_files),
|
|
171
|
+
'dimensions_checked': len(WORLDBUILDING_DIMENSIONS),
|
|
172
|
+
'dimension_stats': dimension_stats,
|
|
173
|
+
'issues': issues,
|
|
174
|
+
'issue_count': len(issues),
|
|
175
|
+
'error_count': sum(1 for i in issues if i['severity'] == 'error'),
|
|
176
|
+
'warning_count': sum(1 for i in issues if i['severity'] == 'warning')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def generate_fix_suggestions(issues, truth_facts=None):
|
|
181
|
+
"""Generate automatic fix suggestions for worldbuilding issues."""
|
|
182
|
+
suggestions = []
|
|
183
|
+
|
|
184
|
+
# Fix templates for each issue type
|
|
185
|
+
fix_templates = {
|
|
186
|
+
'设定冲突': {
|
|
187
|
+
'action': '更新真相文件或修正章节内容',
|
|
188
|
+
'steps': [
|
|
189
|
+
'确认该设定是否为有意的新增设定',
|
|
190
|
+
'如果是新设定,将其添加到对应的真相文件中',
|
|
191
|
+
'如果是笔误,修正章节中的描述使其与真相文件一致'
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
'内部矛盾': {
|
|
195
|
+
'action': '统一实体描述,消除前后矛盾',
|
|
196
|
+
'steps': [
|
|
197
|
+
'确定以哪个章节的描述为准(通常以首次出现为准)',
|
|
198
|
+
'修正后续章节中不一致的描述',
|
|
199
|
+
'将统一后的描述更新到真相文件中'
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for issue in issues:
|
|
205
|
+
issue_type = issue['type']
|
|
206
|
+
if issue_type not in fix_templates:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
template = fix_templates[issue_type]
|
|
210
|
+
suggestion = {
|
|
211
|
+
'type': issue_type,
|
|
212
|
+
'dimension': issue.get('dimension', '未知'),
|
|
213
|
+
'message': issue['message'],
|
|
214
|
+
'action': template['action'],
|
|
215
|
+
'steps': template['steps'],
|
|
216
|
+
'severity': issue['severity']
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if issue_type == '设定冲突':
|
|
220
|
+
suggestion['missing_term'] = issue.get('fact', '')
|
|
221
|
+
suggestion['chapter'] = issue.get('chapter', '未知')
|
|
222
|
+
suggestion['fix_options'] = [
|
|
223
|
+
f"将「{issue.get('fact', '')}」添加到真相文件对应维度中",
|
|
224
|
+
f"修正第{issue.get('chapter', '?')}章中的描述使其与真相文件一致"
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
elif issue_type == '内部矛盾':
|
|
228
|
+
entity = issue.get('entity', '')
|
|
229
|
+
descriptions = issue.get('descriptions', [])
|
|
230
|
+
suggestion['entity'] = entity
|
|
231
|
+
suggestion['conflict_count'] = len(descriptions)
|
|
232
|
+
|
|
233
|
+
# Build specific fix based on descriptions
|
|
234
|
+
if descriptions:
|
|
235
|
+
first_desc = descriptions[0]
|
|
236
|
+
suggestion['recommended_fix'] = (
|
|
237
|
+
f"以第{first_desc['chapter']}章的描述「{first_desc['description']}」为准,"
|
|
238
|
+
f"统一其他{len(descriptions)-1}处描述"
|
|
239
|
+
)
|
|
240
|
+
suggestion['conflict_details'] = [
|
|
241
|
+
{
|
|
242
|
+
'chapter': d['chapter'],
|
|
243
|
+
'description': d['description']
|
|
244
|
+
}
|
|
245
|
+
for d in descriptions
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
suggestions.append(suggestion)
|
|
249
|
+
|
|
250
|
+
return suggestions
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def auto_fix_truth_files(issues, truth_dir, dry_run=True):
|
|
254
|
+
"""Auto-fix truth files by adding missing terms from chapters.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
issues: List of detected issues
|
|
258
|
+
truth_dir: Path to truth files directory
|
|
259
|
+
dry_run: If True, only report what would be fixed; if False, actually modify files
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
List of fix actions performed
|
|
263
|
+
"""
|
|
264
|
+
fix_actions = []
|
|
265
|
+
|
|
266
|
+
if not truth_dir or not os.path.isdir(truth_dir):
|
|
267
|
+
return fix_actions
|
|
268
|
+
|
|
269
|
+
# Group issues by dimension
|
|
270
|
+
missing_by_dimension = defaultdict(set)
|
|
271
|
+
for issue in issues:
|
|
272
|
+
if issue['type'] == '设定冲突' and 'dimension' in issue:
|
|
273
|
+
# Extract dimension key from display name
|
|
274
|
+
dim_key = None
|
|
275
|
+
for k, v in WORLDBUILDING_DIMENSIONS.items():
|
|
276
|
+
if v['name'] == issue['dimension']:
|
|
277
|
+
dim_key = k
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
if dim_key and 'fact' in issue:
|
|
281
|
+
# Extract the missing term from the fact
|
|
282
|
+
fact_text = issue['fact']
|
|
283
|
+
# Try to extract key terms from the fact
|
|
284
|
+
for group in re.findall(r'[\u4e00-\u9fa5]+', fact_text):
|
|
285
|
+
if len(group) >= 2: # Only terms with 2+ chars
|
|
286
|
+
missing_by_dimension[dim_key].add(group)
|
|
287
|
+
|
|
288
|
+
# Add missing terms to truth files
|
|
289
|
+
for dim_key, terms in missing_by_dimension.items():
|
|
290
|
+
truth_file = os.path.join(truth_dir, f'{dim_key}.md')
|
|
291
|
+
|
|
292
|
+
if os.path.exists(truth_file):
|
|
293
|
+
with open(truth_file, 'r', encoding='utf-8') as f:
|
|
294
|
+
content = f.read()
|
|
295
|
+
|
|
296
|
+
# Check which terms are actually missing
|
|
297
|
+
new_terms = [t for t in terms if t not in content]
|
|
298
|
+
|
|
299
|
+
if new_terms:
|
|
300
|
+
if not dry_run:
|
|
301
|
+
# Check if auto-fix section already exists
|
|
302
|
+
auto_fix_header = '## 自动补充设定'
|
|
303
|
+
if auto_fix_header in content:
|
|
304
|
+
# Append to existing section
|
|
305
|
+
with open(truth_file, 'a', encoding='utf-8') as f:
|
|
306
|
+
for term in sorted(new_terms):
|
|
307
|
+
f.write(f'- {term}\n')
|
|
308
|
+
else:
|
|
309
|
+
# Create new section
|
|
310
|
+
with open(truth_file, 'a', encoding='utf-8') as f:
|
|
311
|
+
f.write('\n\n## 自动补充设定\n')
|
|
312
|
+
for term in sorted(new_terms):
|
|
313
|
+
f.write(f'- {term}\n')
|
|
314
|
+
|
|
315
|
+
fix_actions.append({
|
|
316
|
+
'action': '补全真相文件',
|
|
317
|
+
'dimension': WORLDBUILDING_DIMENSIONS[dim_key]['name'],
|
|
318
|
+
'file': truth_file,
|
|
319
|
+
'added_terms': sorted(new_terms),
|
|
320
|
+
'count': len(new_terms),
|
|
321
|
+
'status': '待修复(dry-run)' if dry_run else '已修复'
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
return fix_actions
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def auto_fix_chapter_contradictions(issues, chapters_dir, dry_run=True):
|
|
328
|
+
"""Auto-fix contradictions in chapter files by standardizing entity descriptions.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
issues: List of detected issues
|
|
332
|
+
chapters_dir: Path to chapters directory
|
|
333
|
+
dry_run: If True, only report what would be fixed; if False, actually modify files
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
List of fix actions performed or proposed
|
|
337
|
+
"""
|
|
338
|
+
fix_actions = []
|
|
339
|
+
|
|
340
|
+
# Get chapter files
|
|
341
|
+
chapter_files = list_chapters(chapters_dir)
|
|
342
|
+
if not chapter_files:
|
|
343
|
+
return fix_actions
|
|
344
|
+
|
|
345
|
+
# Process internal contradictions
|
|
346
|
+
for issue in issues:
|
|
347
|
+
if issue['type'] != '内部矛盾':
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
entity = issue.get('entity', '')
|
|
351
|
+
descriptions = issue.get('descriptions', [])
|
|
352
|
+
|
|
353
|
+
if not entity or len(descriptions) < 2:
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
# Use first description as the canonical one
|
|
357
|
+
canonical = descriptions[0]
|
|
358
|
+
canonical_desc = canonical['description']
|
|
359
|
+
|
|
360
|
+
# Skip if description is too short (risk of false positives)
|
|
361
|
+
if len(canonical_desc) < 4:
|
|
362
|
+
continue
|
|
363
|
+
|
|
364
|
+
# Find chapters that need fixing
|
|
365
|
+
chapters_to_fix = []
|
|
366
|
+
for desc in descriptions[1:]:
|
|
367
|
+
if desc['description'] != canonical_desc:
|
|
368
|
+
chapters_to_fix.append({
|
|
369
|
+
'chapter': desc['chapter'],
|
|
370
|
+
'wrong_desc': desc['description'],
|
|
371
|
+
'right_desc': canonical_desc
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
if chapters_to_fix:
|
|
375
|
+
fix_action = {
|
|
376
|
+
'action': '修正章节矛盾',
|
|
377
|
+
'entity': entity,
|
|
378
|
+
'canonical_description': canonical_desc,
|
|
379
|
+
'canonical_chapter': canonical['chapter'],
|
|
380
|
+
'fixes': []
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
for fix_info in chapters_to_fix:
|
|
384
|
+
ch_idx = fix_info['chapter'] - 1
|
|
385
|
+
if 0 <= ch_idx < len(chapter_files):
|
|
386
|
+
ch_file = chapter_files[ch_idx]
|
|
387
|
+
|
|
388
|
+
if not dry_run:
|
|
389
|
+
# Read and fix the chapter
|
|
390
|
+
with open(ch_file, 'r', encoding='utf-8') as f:
|
|
391
|
+
content = f.read()
|
|
392
|
+
|
|
393
|
+
# Use word boundary matching to avoid false replacements
|
|
394
|
+
# Only replace if the wrong description appears as a distinct phrase
|
|
395
|
+
wrong = fix_info['wrong_desc']
|
|
396
|
+
right = fix_info['right_desc']
|
|
397
|
+
|
|
398
|
+
# Count occurrences to detect scope
|
|
399
|
+
count = content.count(wrong)
|
|
400
|
+
if count > 0 and count <= 3: # Only fix if 1-3 occurrences
|
|
401
|
+
fixed_content = content.replace(wrong, right)
|
|
402
|
+
|
|
403
|
+
if fixed_content != content:
|
|
404
|
+
with open(ch_file, 'w', encoding='utf-8') as f:
|
|
405
|
+
f.write(fixed_content)
|
|
406
|
+
|
|
407
|
+
fix_action['fixes'].append({
|
|
408
|
+
'chapter': fix_info['chapter'],
|
|
409
|
+
'file': ch_file,
|
|
410
|
+
'replaced': wrong,
|
|
411
|
+
'with': right,
|
|
412
|
+
'occurrences': count,
|
|
413
|
+
'status': '已修复'
|
|
414
|
+
})
|
|
415
|
+
else:
|
|
416
|
+
fix_action['fixes'].append({
|
|
417
|
+
'chapter': fix_info['chapter'],
|
|
418
|
+
'file': ch_file,
|
|
419
|
+
'replaced': wrong,
|
|
420
|
+
'with': right,
|
|
421
|
+
'occurrences': count,
|
|
422
|
+
'status': '跳过(出现次数过多,需人工确认)'
|
|
423
|
+
})
|
|
424
|
+
else:
|
|
425
|
+
fix_action['fixes'].append({
|
|
426
|
+
'chapter': fix_info['chapter'],
|
|
427
|
+
'file': ch_file,
|
|
428
|
+
'replaced': fix_info['wrong_desc'],
|
|
429
|
+
'with': fix_info['right_desc'],
|
|
430
|
+
'status': '待修复(dry-run)'
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
if fix_action['fixes']:
|
|
434
|
+
fix_actions.append(fix_action)
|
|
435
|
+
|
|
436
|
+
return fix_actions
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def generate_fix_report(truth_fixes, chapter_fixes, issues):
|
|
440
|
+
"""Generate a structured fix report for reviewer.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
truth_fixes: List of truth file fix actions
|
|
444
|
+
chapter_fixes: List of chapter fix actions
|
|
445
|
+
issues: Original list of issues
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Dict containing structured fix report
|
|
449
|
+
"""
|
|
450
|
+
report = {
|
|
451
|
+
'summary': {
|
|
452
|
+
'total_issues': len(issues),
|
|
453
|
+
'truth_files_fixed': len(truth_fixes),
|
|
454
|
+
'chapters_fixed': len(chapter_fixes),
|
|
455
|
+
'remaining_issues': 0
|
|
456
|
+
},
|
|
457
|
+
'truth_file_fixes': truth_fixes,
|
|
458
|
+
'chapter_fixes': chapter_fixes,
|
|
459
|
+
'unfixed_issues': []
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
# Track which issues were actually fixed
|
|
463
|
+
fixed_issue_indices = set()
|
|
464
|
+
|
|
465
|
+
# Check truth file fixes
|
|
466
|
+
for fix in truth_fixes:
|
|
467
|
+
dim_name = fix.get('dimension')
|
|
468
|
+
added_terms = fix.get('added_terms', [])
|
|
469
|
+
for idx, issue in enumerate(issues):
|
|
470
|
+
if issue['type'] == '设定冲突' and issue.get('dimension') == dim_name:
|
|
471
|
+
fact_text = issue.get('fact', '')
|
|
472
|
+
for term in re.findall(r'[\u4e00-\u9fa5]+', fact_text):
|
|
473
|
+
if term in added_terms:
|
|
474
|
+
fixed_issue_indices.add(idx)
|
|
475
|
+
break
|
|
476
|
+
|
|
477
|
+
# Check chapter fixes
|
|
478
|
+
fixed_entities = set()
|
|
479
|
+
for fix in chapter_fixes:
|
|
480
|
+
if 'entity' in fix:
|
|
481
|
+
fixed_entities.add(fix['entity'])
|
|
482
|
+
|
|
483
|
+
for idx, issue in enumerate(issues):
|
|
484
|
+
if issue['type'] == '内部矛盾' and issue.get('entity') in fixed_entities:
|
|
485
|
+
fixed_issue_indices.add(idx)
|
|
486
|
+
|
|
487
|
+
# Calculate remaining issues
|
|
488
|
+
report['summary']['remaining_issues'] = len(issues) - len(fixed_issue_indices)
|
|
489
|
+
|
|
490
|
+
# Collect unfixed issues
|
|
491
|
+
for idx, issue in enumerate(issues):
|
|
492
|
+
if idx not in fixed_issue_indices:
|
|
493
|
+
report['unfixed_issues'].append(issue)
|
|
494
|
+
|
|
495
|
+
return report
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def main():
|
|
499
|
+
parser = argparse.ArgumentParser(description='世界观一致性检查脚本')
|
|
500
|
+
parser.add_argument('chapters_dir', help='章节目录路径')
|
|
501
|
+
parser.add_argument('--truth', '-t', help='真相文件目录路径')
|
|
502
|
+
parser.add_argument('--recent', type=int, help='仅检查最近N章')
|
|
503
|
+
parser.add_argument('--json', action='store_true', help='JSON输出')
|
|
504
|
+
parser.add_argument('--fix', action='store_true', help='显示自动修复建议')
|
|
505
|
+
parser.add_argument('--apply', action='store_true', help='应用自动修复(需配合--truth)')
|
|
506
|
+
parser.add_argument('--dry-run', action='store_true', help='仅预览修复内容,不实际修改文件')
|
|
507
|
+
args = parser.parse_args()
|
|
508
|
+
|
|
509
|
+
if not os.path.isdir(args.chapters_dir):
|
|
510
|
+
print(f"错误: 目录不存在: {args.chapters_dir}", file=sys.stderr)
|
|
511
|
+
sys.exit(1)
|
|
512
|
+
|
|
513
|
+
result = check_worldbuilding_consistency(args.chapters_dir, args.truth, args.recent)
|
|
514
|
+
|
|
515
|
+
# Generate fix suggestions if requested
|
|
516
|
+
if args.fix and result.get('issues'):
|
|
517
|
+
result['fix_suggestions'] = generate_fix_suggestions(result['issues'])
|
|
518
|
+
|
|
519
|
+
# Apply auto-fix if requested
|
|
520
|
+
if args.apply and result.get('issues'):
|
|
521
|
+
if not args.truth:
|
|
522
|
+
print("错误: --apply 需要配合 --truth 参数使用", file=sys.stderr)
|
|
523
|
+
sys.exit(1)
|
|
524
|
+
|
|
525
|
+
# Load truth facts for reference
|
|
526
|
+
truth_facts = {}
|
|
527
|
+
if os.path.isdir(args.truth):
|
|
528
|
+
for dimension in WORLDBUILDING_DIMENSIONS.keys():
|
|
529
|
+
truth_file = os.path.join(args.truth, f'{dimension}.md')
|
|
530
|
+
if os.path.exists(truth_file):
|
|
531
|
+
with open(truth_file, 'r', encoding='utf-8') as f:
|
|
532
|
+
truth_facts[dimension] = f.read()
|
|
533
|
+
|
|
534
|
+
# Auto-fix truth files
|
|
535
|
+
dry_run = args.dry_run
|
|
536
|
+
truth_fixes = auto_fix_truth_files(result['issues'], args.truth, dry_run=dry_run)
|
|
537
|
+
|
|
538
|
+
# Auto-fix chapter contradictions
|
|
539
|
+
chapter_fixes = auto_fix_chapter_contradictions(result['issues'], args.chapters_dir, dry_run=dry_run)
|
|
540
|
+
|
|
541
|
+
# Generate fix report
|
|
542
|
+
fix_report = generate_fix_report(truth_fixes, chapter_fixes, result['issues'])
|
|
543
|
+
|
|
544
|
+
result['fix_report'] = fix_report
|
|
545
|
+
|
|
546
|
+
if args.json:
|
|
547
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
548
|
+
else:
|
|
549
|
+
if 'error' in result:
|
|
550
|
+
print(result['error'])
|
|
551
|
+
return
|
|
552
|
+
|
|
553
|
+
print(f"\n=== 世界观一致性检查 ===\n")
|
|
554
|
+
print(f"检查章节数: {result['total_chapters']}")
|
|
555
|
+
print(f"检查维度数: {result['dimensions_checked']}")
|
|
556
|
+
print(f"问题总数: {result['issue_count']}")
|
|
557
|
+
print(f" 错误: {result['error_count']}")
|
|
558
|
+
print(f" 警告: {result['warning_count']}")
|
|
559
|
+
|
|
560
|
+
print(f"\n各维度统计:")
|
|
561
|
+
for dimension, stats in result['dimension_stats'].items():
|
|
562
|
+
print(f" {stats['name']}: {stats['total_facts']}个设定, 涉及{stats['chapters_with_facts']}章")
|
|
563
|
+
|
|
564
|
+
if result['issues']:
|
|
565
|
+
print(f"\n问题详情:")
|
|
566
|
+
for issue in result['issues'][:20]: # Show first 20 issues
|
|
567
|
+
icon = '' if issue['severity'] == 'error' else '⚠'
|
|
568
|
+
print(f"\n {icon} [{issue['type']}] {issue['dimension']}")
|
|
569
|
+
print(f" {issue['message']}")
|
|
570
|
+
if 'chapter' in issue:
|
|
571
|
+
print(f" 章节: 第{issue['chapter']}章 {issue.get('chapter_title', '')}")
|
|
572
|
+
if 'fact' in issue:
|
|
573
|
+
print(f" 原文: {issue['fact'][:60]}...")
|
|
574
|
+
|
|
575
|
+
# Show fix suggestions if requested
|
|
576
|
+
if args.fix and result.get('fix_suggestions'):
|
|
577
|
+
print(f"\n{'='*60}")
|
|
578
|
+
print(f"自动修复建议:")
|
|
579
|
+
print(f"{'='*60}")
|
|
580
|
+
for i, suggestion in enumerate(result['fix_suggestions'], 1):
|
|
581
|
+
print(f"\n [{i}] {suggestion['type']} - {suggestion['dimension']}")
|
|
582
|
+
print(f" 问题: {suggestion['message']}")
|
|
583
|
+
print(f" 操作: {suggestion['action']}")
|
|
584
|
+
for j, step in enumerate(suggestion['steps'], 1):
|
|
585
|
+
print(f" {j}. {step}")
|
|
586
|
+
|
|
587
|
+
if 'fix_options' in suggestion:
|
|
588
|
+
print(f" 修复选项:")
|
|
589
|
+
for opt in suggestion['fix_options']:
|
|
590
|
+
print(f" - {opt}")
|
|
591
|
+
|
|
592
|
+
if 'recommended_fix' in suggestion:
|
|
593
|
+
print(f" 推荐修复: {suggestion['recommended_fix']}")
|
|
594
|
+
if 'conflict_details' in suggestion:
|
|
595
|
+
print(f" 冲突详情:")
|
|
596
|
+
for detail in suggestion['conflict_details']:
|
|
597
|
+
print(f" 第{detail['chapter']}章: {detail['description']}")
|
|
598
|
+
|
|
599
|
+
# Show fix report if applied
|
|
600
|
+
if args.apply and result.get('fix_report'):
|
|
601
|
+
report = result['fix_report']
|
|
602
|
+
print(f"\n{'='*60}")
|
|
603
|
+
print(f"自动修复报告:")
|
|
604
|
+
print(f"{'='*60}")
|
|
605
|
+
print(f"\n修复统计:")
|
|
606
|
+
print(f" 总问题数: {report['summary']['total_issues']}")
|
|
607
|
+
print(f" 真相文件修复: {report['summary']['truth_files_fixed']}")
|
|
608
|
+
print(f" 章节修复: {report['summary']['chapters_fixed']}")
|
|
609
|
+
print(f" 剩余问题: {report['summary']['remaining_issues']}")
|
|
610
|
+
|
|
611
|
+
if report['truth_file_fixes']:
|
|
612
|
+
print(f"\n真相文件修复详情:")
|
|
613
|
+
for fix in report['truth_file_fixes']:
|
|
614
|
+
status = fix.get('status', '已修复')
|
|
615
|
+
status_icon = '✓' if '已修复' in status else '○'
|
|
616
|
+
print(f" {status_icon} [{fix['dimension']}] 补全 {fix['count']} 个设定")
|
|
617
|
+
for term in fix['added_terms']:
|
|
618
|
+
print(f" - {term}")
|
|
619
|
+
|
|
620
|
+
if report['chapter_fixes']:
|
|
621
|
+
print(f"\n章节修复详情:")
|
|
622
|
+
for fix in report['chapter_fixes']:
|
|
623
|
+
print(f" [{fix['entity']}] 以第{fix['canonical_chapter']}章为准")
|
|
624
|
+
for ch_fix in fix['fixes']:
|
|
625
|
+
status_icon = '✓' if '已修复' in ch_fix['status'] else '○'
|
|
626
|
+
print(f" {status_icon} 第{ch_fix['chapter']}章: {ch_fix['replaced']} → {ch_fix['with']}")
|
|
627
|
+
|
|
628
|
+
if report['unfixed_issues']:
|
|
629
|
+
print(f"\n未修复问题(需人工处理):")
|
|
630
|
+
for issue in report['unfixed_issues'][:10]:
|
|
631
|
+
print(f" - [{issue['type']}] {issue['message']}")
|
|
632
|
+
else:
|
|
633
|
+
print(f"\n✓ 未检测到世界观一致性问题")
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
if __name__ == '__main__':
|
|
637
|
+
main()
|