Просмотр исходного кода

fix: 修复脚本健壮性和兼容性问题

- context_pack_builder: 章节匹配正则兼容空格/中英文冒号/标题级别
- state_manager: 修复 entities_v3 和 current 字段类型边界情况
- state_manager: Windows 平台 UTF-8 编码支持
- extract_chapter_context: 复用 find_chapter_file,修复 foreshadowing 路径

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
lingfengQAQ 4 месяцев назад
Родитель
Сommit
eabebd2a40

+ 17 - 4
.claude/scripts/context_pack_builder.py

@@ -190,9 +190,22 @@ class ContextPackBuilder:
             with open(vf, 'r', encoding='utf-8') as f:
                 content = f.read()
 
-            # 查找章节标记
-            pattern = rf'第{chapter_num}章[^\n]*\n(.*?)(?=第\d+章|$)'
-            match = re.search(pattern, content, re.DOTALL)
+            # 查找章节标记(兼容空格/中英文冒号/不同标题级别)
+            # 常见格式:### 第 1 章:标题 或 ### 第1章: 标题
+            heading_pattern = (
+                rf"(?m)^#+\s*第\s*{chapter_num}\s*章[::][^\n]*\n"
+                rf".*?(?=^#+\s*第\s*\d+\s*章|^##\s|\Z)"
+            )
+            match = re.search(heading_pattern, content, re.DOTALL)
+            if match:
+                return match.group(0).strip()
+
+            # 兼容无标题级别的格式:第 1 章 标题
+            plain_pattern = (
+                rf"(?m)^第\s*{chapter_num}\s*章[^\n]*\n"
+                rf".*?(?=^第\s*\d+\s*章|\Z)"
+            )
+            match = re.search(plain_pattern, content, re.DOTALL)
             if match:
                 return match.group(0).strip()
 
@@ -546,7 +559,7 @@ class ContextPackBuilder:
 
 
 def main():
-    parser = argparse.ArgumentParser(description="Context Pack Builder v5.1")
+    parser = argparse.ArgumentParser(description="Context Pack Builder v5.2")
     parser.add_argument("--chapter", type=int, required=True, help="章节编号")
     parser.add_argument("--project-root", metavar="PATH", help="项目根目录")
     parser.add_argument("--output", metavar="FILE", help="输出文件路径(默认输出到 stdout)")

+ 22 - 2
.claude/scripts/data_modules/state_manager.py

@@ -14,6 +14,7 @@ v5.1 变更:
 """
 
 import json
+import sys
 from pathlib import Path
 from typing import Dict, List, Optional, Any
 from dataclasses import dataclass, field, asdict
@@ -763,7 +764,13 @@ class StateManager:
         if not entity_type:
             return
 
-        entity = self._state["entities_v3"][entity_type].get(entity_id)
+        entities_v3 = self._state.get("entities_v3")
+        if not isinstance(entities_v3, dict):
+            entities_v3 = {t: {} for t in self.ENTITY_TYPES}
+            self._state["entities_v3"] = entities_v3
+        entities_v3.setdefault(entity_type, {})
+
+        entity = entities_v3[entity_type].get(entity_id)
         if entity:
             if entity.get("first_appearance", 0) == 0:
                 entity["first_appearance"] = chapter
@@ -1093,7 +1100,16 @@ class StateManager:
         if not entity:
             return
 
-        current = entity.get("current", {})
+        current = entity.get("current")
+        if not isinstance(current, dict):
+            current = entity.get("current_json", {})
+        if isinstance(current, str):
+            try:
+                current = json.loads(current) if current else {}
+            except (json.JSONDecodeError, TypeError):
+                current = {}
+        if not isinstance(current, dict):
+            current = {}
         protag = self._state.setdefault("protagonist_state", {})
 
         # 同步境界
@@ -1214,4 +1230,8 @@ def main():
 
 
 if __name__ == "__main__":
+    if sys.platform == "win32":
+        import io
+        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
+        sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
     main()

+ 10 - 19
.claude/scripts/extract_chapter_context.py

@@ -18,6 +18,11 @@ import re
 import sys
 from pathlib import Path
 
+try:
+    from chapter_paths import find_chapter_file
+except ImportError:  # pragma: no cover
+    from scripts.chapter_paths import find_chapter_file
+
 
 def find_project_root(start_path: Path = None) -> Path:
     """查找包含 .webnovel 目录的项目根目录"""
@@ -91,22 +96,7 @@ def extract_chapter_summary(project_root: Path, chapter_num: int) -> str:
     if summary:
         return summary
 
-    volume_num = (chapter_num - 1) // 50 + 1
-    chapter_dir = project_root / "正文" / f"第{volume_num}卷"
-
-    # 尝试匹配章节文件
-    patterns = [
-        f"第{chapter_num:03d}章*.md",
-        f"第{chapter_num:04d}章*.md",
-    ]
-
-    chapter_file = None
-    for pattern in patterns:
-        matches = list(chapter_dir.glob(pattern))
-        if matches:
-            chapter_file = matches[0]
-            break
-
+    chapter_file = find_chapter_file(project_root, chapter_num)
     if not chapter_file or not chapter_file.exists():
         return f"⚠️ 第 {chapter_num} 章文件不存在"
 
@@ -165,9 +155,10 @@ def extract_state_summary(project_root: Path) -> str:
             summary_parts.append(f"**近5章Strand**: {strand_str}")
 
     # 活跃伏笔(只显示紧急的)
-    if "foreshadowing" in state:
-        fs = state["foreshadowing"]
-        active = [f for f in fs if f.get("status") == "active"]
+    plot_threads = state.get("plot_threads", {}) if isinstance(state.get("plot_threads"), dict) else {}
+    foreshadowing = plot_threads.get("foreshadowing", [])
+    if isinstance(foreshadowing, list) and foreshadowing:
+        active = [f for f in foreshadowing if f.get("status") in {"active", "未回收"}]
         urgent = [f for f in active if f.get("urgency", 0) > 50]
         if urgent:
             urgent_list = [f"{f.get('content', '?')[:30]}... (紧急度:{f.get('urgency')})" for f in urgent[:3]]