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

fix(v5.4): 数据闭环审查修复与流程优化

修复内容:
- SQLite 同步失败时输出 warning 而非静默降级
- rag_adapter 向量写入增强错误处理和日志
- 债务利息计算集成到 Data Agent 流程
- 所有 CLI 入口添加 Windows UTF-8 编码修复

流程优化:
- 明确 Step 2B/Step 4 职责边界,避免重复工作
- 创建 checker-output-schema.md 统一审查输出格式
- 各 Checker Agent 引用统一 JSON Schema

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

+ 2 - 0
.claude/agents/consistency-checker.md

@@ -8,6 +8,8 @@ tools: Read, Grep
 
 > **Role**: Continuity guardian enforcing the second anti-hallucination law (设定即物理 - Settings are Physics).
 
+> **输出格式**: 遵循 `.claude/references/checker-output-schema.md` 统一 JSON Schema
+
 ## Scope
 
 **Input**: Chapter range (e.g., "1-2", "45-46")

+ 2 - 0
.claude/agents/continuity-checker.md

@@ -8,6 +8,8 @@ tools: Read, Grep
 
 > **Role**: Narrative flow guardian ensuring smooth transitions and logical plot progression.
 
+> **输出格式**: 遵循 `.claude/references/checker-output-schema.md` 统一 JSON Schema
+
 ## Scope
 
 **Input**: Chapter range (e.g., "1-2", "45-46")

+ 13 - 1
.claude/agents/data-agent.md

@@ -177,7 +177,19 @@ if review_score >= 80:
 python -m data_modules.style_sampler extract --chapter 100 --score 85 --scenes '[...]' --project-root "."
 ```
 
-### Step I: 生成处理报告
+### Step I: 债务利息计算(v5.4 新增)
+
+**每章完成后自动触发**:
+```bash
+python -m data_modules.index_manager accrue-interest --chapter {chapter} --project-root "."
+```
+
+此步骤会:
+- 对所有 `status='active'` 的债务计算利息(每章 10%)
+- 将逾期债务标记为 `status='overdue'`
+- 记录利息事件到 `debt_events` 表
+
+### Step J: 生成处理报告
 
 ```json
 {

+ 2 - 0
.claude/agents/high-point-checker.md

@@ -8,6 +8,8 @@ tools: Read, Grep, Bash
 
 > **Role**: Quality assurance specialist focused on reader satisfaction mechanics (爽点设计).
 
+> **输出格式**: 遵循 `.claude/references/checker-output-schema.md` 统一 JSON Schema
+
 ## 核心参考
 
 - **Taxonomy**: `.claude/references/reading-power-taxonomy.md`

+ 2 - 0
.claude/agents/ooc-checker.md

@@ -8,6 +8,8 @@ tools: Read, Grep
 
 > **Role**: Character integrity guardian preventing OOC (Out-Of-Character) violations.
 
+> **输出格式**: 遵循 `.claude/references/checker-output-schema.md` 统一 JSON Schema
+
 ## Scope
 
 **Input**: Chapter range (e.g., "1-2", "45-46")

+ 2 - 0
.claude/agents/pacing-checker.md

@@ -8,6 +8,8 @@ tools: Read, Grep
 
 > **Role**: Pacing analyst enforcing Strand Weave balance to prevent reader fatigue.
 
+> **输出格式**: 遵循 `.claude/references/checker-output-schema.md` 统一 JSON Schema
+
 ## Scope
 
 **Input**: Chapter range (e.g., "1-2", "45-46")

+ 159 - 0
.claude/references/checker-output-schema.md

@@ -0,0 +1,159 @@
+# Checker 统一输出 Schema (v5.4)
+
+所有审查 Agent 应遵循此统一输出格式,便于自动化汇总和趋势分析。
+
+## 标准 JSON Schema
+
+```json
+{
+  "agent": "checker-name",
+  "chapter": 100,
+  "overall_score": 85,
+  "pass": true,
+  "issues": [
+    {
+      "id": "ISSUE_001",
+      "type": "问题类型",
+      "severity": "critical|high|medium|low",
+      "location": "位置描述",
+      "description": "问题描述",
+      "suggestion": "修复建议",
+      "can_override": false
+    }
+  ],
+  "metrics": {},
+  "summary": "简短总结"
+}
+```
+
+## 字段说明
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `agent` | string | ✅ | Agent 名称 |
+| `chapter` | int | ✅ | 章节号 |
+| `overall_score` | int | ✅ | 总分 (0-100) |
+| `pass` | bool | ✅ | 是否通过 |
+| `issues` | array | ✅ | 问题列表 |
+| `metrics` | object | ✅ | Agent 特定指标 |
+| `summary` | string | ✅ | 简短总结 |
+
+## 问题严重度定义
+
+| severity | 含义 | 处理方式 |
+|----------|------|----------|
+| `critical` | 严重问题,必须修复 | 润色步骤必须修复 |
+| `high` | 高优先级问题 | 优先修复 |
+| `medium` | 中等问题 | 建议修复 |
+| `low` | 轻微问题 | 可选修复 |
+
+## 各 Checker 特定 metrics
+
+### reader-pull-checker
+```json
+{
+  "metrics": {
+    "hook_present": true,
+    "hook_type": "危机钩",
+    "hook_strength": "strong",
+    "prev_hook_fulfilled": true,
+    "micropayoff_count": 2,
+    "micropayoffs": ["能力兑现", "认可兑现"],
+    "is_transition": false,
+    "debt_balance": 0.0
+  }
+}
+```
+
+### high-point-checker
+```json
+{
+  "metrics": {
+    "cool_point_count": 2,
+    "cool_point_types": ["装逼打脸", "越级反杀"],
+    "density_score": 8,
+    "type_diversity": 0.8,
+    "milestone_present": false
+  }
+}
+```
+
+### consistency-checker
+```json
+{
+  "metrics": {
+    "power_violations": 0,
+    "location_errors": 1,
+    "timeline_issues": 0,
+    "entity_conflicts": 0
+  }
+}
+```
+
+### ooc-checker
+```json
+{
+  "metrics": {
+    "severe_ooc": 0,
+    "moderate_ooc": 1,
+    "minor_ooc": 2,
+    "speech_violations": 0,
+    "character_development_valid": true
+  }
+}
+```
+
+### continuity-checker
+```json
+{
+  "metrics": {
+    "transition_grade": "B",
+    "active_threads": 3,
+    "dormant_threads": 1,
+    "forgotten_foreshadowing": 0,
+    "logic_holes": 0,
+    "outline_deviations": 0
+  }
+}
+```
+
+### pacing-checker
+```json
+{
+  "metrics": {
+    "dominant_strand": "quest",
+    "quest_ratio": 0.6,
+    "fire_ratio": 0.25,
+    "constellation_ratio": 0.15,
+    "consecutive_quest": 3,
+    "fire_gap": 4,
+    "constellation_gap": 8,
+    "fatigue_risk": "low"
+  }
+}
+```
+
+## 汇总格式
+
+Step 3 完成后,输出汇总 JSON:
+
+```json
+{
+  "chapter": 100,
+  "checkers": {
+    "reader-pull-checker": {"score": 85, "pass": true, "critical": 0, "high": 1},
+    "high-point-checker": {"score": 80, "pass": true, "critical": 0, "high": 0},
+    "consistency-checker": {"score": 90, "pass": true, "critical": 0, "high": 0},
+    "ooc-checker": {"score": 75, "pass": true, "critical": 0, "high": 1},
+    "continuity-checker": {"score": 85, "pass": true, "critical": 0, "high": 0},
+    "pacing-checker": {"score": 80, "pass": true, "critical": 0, "high": 0}
+  },
+  "overall": {
+    "score": 82.5,
+    "pass": true,
+    "critical_total": 0,
+    "high_total": 2,
+    "can_proceed": true
+  }
+}
+```

+ 5 - 0
.claude/scripts/data_modules/context_manager.py

@@ -320,4 +320,9 @@ def main():
 
 
 if __name__ == "__main__":
+    import sys
+    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()

+ 5 - 0
.claude/scripts/data_modules/index_manager.py

@@ -2745,4 +2745,9 @@ def main():
 
 
 if __name__ == "__main__":
+    import sys
+    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()

+ 35 - 4
.claude/scripts/data_modules/rag_adapter.py

@@ -255,6 +255,7 @@ class RAGAdapter:
         # 存储到数据库(跳过嵌入失败的 chunk)
         stored = 0
         skipped = 0
+        errors = []
         with self._get_conn() as conn:
             cursor = conn.cursor()
 
@@ -268,7 +269,10 @@ class RAGAdapter:
                             chunk_id = f"ch{int(chunk['chapter']):04d}_summary"
                         else:
                             chunk_id = f"ch{int(chunk['chapter']):04d}_s{int(chunk['scene_index'])}"
-                    self._update_bm25_index(cursor, chunk_id, chunk.get("content", ""))
+                    try:
+                        self._update_bm25_index(cursor, chunk_id, chunk.get("content", ""))
+                    except Exception as e:
+                        errors.append(f"BM25 index failed for {chunk_id}: {e}")
                     continue
 
                 chunk_type = chunk.get("chunk_type") or "scene"
@@ -298,11 +302,28 @@ class RAGAdapter:
                 ))
 
                 # 同时更新 BM25 索引
-                self._update_bm25_index(cursor, chunk_id, chunk.get("content", ""))
+                try:
+                    self._update_bm25_index(cursor, chunk_id, chunk.get("content", ""))
+                except Exception as e:
+                    errors.append(f"BM25 index failed for {chunk_id}: {e}")
 
                 stored += 1
 
-            conn.commit()
+            try:
+                conn.commit()
+            except Exception as e:
+                import sys
+                print(f"[ERROR] SQLite commit failed: {e}", file=sys.stderr)
+                errors.append(f"SQLite commit failed: {e}")
+
+        # 输出警告日志
+        if skipped > 0:
+            import sys
+            print(f"[WARN] Vector embedding: {stored} stored, {skipped} skipped (embedding failed)", file=sys.stderr)
+        if errors:
+            import sys
+            for err in errors[:5]:  # 最多显示5条
+                print(f"[WARN] {err}", file=sys.stderr)
 
         return stored
 
@@ -879,7 +900,12 @@ def main():
             )
 
         stored = asyncio.run(adapter.store_chunks(chunks))
-        emit_success({"stored": stored, "chunks": len(chunks)}, message="indexed")
+        skipped = len(chunks) - stored
+        result = {"stored": stored, "skipped": skipped, "total": len(chunks)}
+        if skipped > 0:
+            emit_success(result, message="indexed_with_warnings")
+        else:
+            emit_success(result, message="indexed")
 
     elif args.command == "search":
         if args.mode == "vector":
@@ -899,4 +925,9 @@ def main():
 
 
 if __name__ == "__main__":
+    import sys
+    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()

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

@@ -534,8 +534,10 @@ class StateManager:
                     chapter=rel.get("chapter", 0)
                 )
 
-        except Exception:
-            pass  # SQLite 同步失败时静默降级
+        except Exception as e:
+            # SQLite 同步失败时记录警告(不中断主流程)
+            import sys
+            print(f"[WARNING] SQLite sync failed: {e}", file=sys.stderr)
 
     def _clear_pending_sqlite_data(self):
         """清空待同步的 SQLite 数据"""

+ 6 - 0
.claude/skills/webnovel-write/SKILL.md

@@ -412,6 +412,12 @@ cat "${CLAUDE_PLUGIN_ROOT}/skills/webnovel-write/references/writing/typesetting.
 5. **向量嵌入**(rag_adapter)
 6. **风格样本评估**(review_score > 80)
 7. **摘要分离存储**:写入 `.webnovel/summaries/ch{NNNN}.md`
+8. **债务利息计算**(自动触发)
+
+**债务利息计算**(Data Agent 完成后自动执行):
+```bash
+python -m data_modules.index_manager accrue-interest --chapter {chapter_num} --project-root "."
+```
 
 **输出**:
 ```json

+ 7 - 1
.claude/skills/webnovel-write/references/polish-guide.md

@@ -5,11 +5,17 @@ version: "5.4"
 ---
 
 <context>
-此文件用于内容润色,v5.2 引入“网文口感 + 追读力”重点,v5.4 沿用。
+此文件用于内容润色,v5.2 引入"网文口感 + 追读力"重点,v5.4 沿用。
 
 润色步骤接收两个输入:
 1. 章节正文
 2. 审查报告(来自 6 个 checker agents)
+
+**与 Step 2B (style-adapter) 的职责边界**:
+- **Step 2B**: 纯风格转换 —— 句式改写、抽象→具体、长句拆分
+- **Step 4 (本步骤)**: 问题修复 —— 基于审查报告修复问题 + AI痕迹检测 + 规则校验
+
+> 注意:如果已执行 Step 2B,本步骤不需要重复句式转换,专注于修复审查问题。
 </context>
 
 <instructions>

+ 6 - 0
.claude/skills/webnovel-write/references/style-adapter.md

@@ -1,6 +1,12 @@
 ## 定位
 Step 2B 专用提示词,将粗稿改写为网文风格。
 
+**与 Step 4 (polish-guide) 的职责边界**:
+- **Step 2B (本步骤)**: 纯风格转换 —— 句式改写、抽象→具体、网文口感
+- **Step 4**: 问题修复 —— 基于审查报告修复 critical/high 问题 + AI痕迹检测
+
+> 注意:Step 4 不会重复执行 Step 2B 的句式转换,两步各有侧重。
+
 ## 输入
 - 粗稿正文(Step 2A 输出)
 - 本章类型(战斗/对话/过渡/突破)

+ 1 - 0
CLAUDE.md

@@ -40,6 +40,7 @@
 ├── references/             # 写作指南
 │   ├── reading-power-taxonomy.md  # 追读力分类标准 (v5.3)
 │   ├── genre-profiles.md          # 题材配置档案 (v5.3)
+│   ├── checker-output-schema.md   # Checker 统一输出格式 (v5.4)
 │   └── ...
 └── templates/              # 题材模板
 ```