ソースを参照

fix: weight_adjust floor check + normalization + git push coverage

- quality_tuner: add 0.05 floor per dimension, normalize weights to 1.0 after
  each adjustment, round to prevent floating point drift
- quality_tuner: align payoff_interval defaults (300/500) with writing templates
- evolve.sh: expand git_push_events from evolution/genres/ to evolution/ so
  skill-level files (thresholds.json, population.json) are auto-committed too

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nonvioce 5 ヶ月 前
親
コミット
e8d563c2ca

+ 108 - 0
evolution/evolution_log.md

@@ -69,3 +69,111 @@
 - **问题数**: 0
 - **问题数**: 0
 
 
 ---
 ---
+
+## 2026-05-15 20:14 | 章节反馈收集
+
+- **章节**: 37
+- **综合评分**: 82
+- **各维评分**: P1:86, P2:79, P3:72, P4:83, P5:89
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 20
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 21
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 22
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 23
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 24
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 25
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 26
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 27
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 28
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 29
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---
+
+## 2026-05-15 20:42 | 章节反馈收集
+
+- **章节**: 30
+- **综合评分**: 83
+- **各维评分**: P1:85, P2:82, P3:78, P4:80, P5:88
+- **问题数**: 0
+
+---

+ 148 - 5
evolution/prompts/population.json

@@ -1,6 +1,6 @@
 {
 {
   "writing": {
   "writing": {
-    "generation": 7,
+    "generation": 11,
     "variants": [
     "variants": [
       {
       {
         "id": "b52e674ced18",
         "id": "b52e674ced18",
@@ -179,11 +179,44 @@
         "lineage": [
         "lineage": [
           "049b9214dd85"
           "049b9214dd85"
         ]
         ]
+      },
+      {
+        "id": "17a1e8f41d8a",
+        "template": "注意:禁止跳过步骤直接写行为;六步走人物心理分析:1.当前处境(面临什么局面?手上什么牌?)2.核心动机(最想要什么?最怕什么?)3.信息边界(知道什么?不知道什么?误判什么?)4.性格过滤(同样局面这角色怎么反应?)5.行为选择(基于以上四点做什么选择?)6.情绪外化(伴随什么情绪?什么身体语言?);人设防崩三问:他为什么这么做?符合之前人设吗?只看过前文的读者会觉得突兀吗?",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "049b9214dd85"
+        ]
+      },
+      {
+        "id": "50b39c97e608",
+        "template": "核心:关系改变事件驱动;价值观盐溶于汤——靠行为证明不靠口号;任何行动立于三条腿:过往经历+当前利益+性格底色;配角有自己账本和利益诉求;每章结尾必有小悬念或情绪缺口;全员智商在线;日常场景七成成后续伏笔;核心规则(写作时内化不列出):Show, don't tell——用细节堆出真实;每一行字要么推动剧情要么塑造人物;",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "b52e674ced18"
+        ]
+      },
+      {
+        "id": "797ced85b248",
+        "template": "禁止跳过步骤直接写行为;人设防崩三问:他为什么这么做?符合之前人设吗?只看过前文的读者会觉得突兀吗?",
+        "source": "simplify",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "a0871e726d75"
+        ]
       }
       }
     ]
     ]
   },
   },
   "revision": {
   "revision": {
-    "generation": 7,
+    "generation": 11,
     "variants": [
     "variants": [
       {
       {
         "id": "0c3b109cf589",
         "id": "0c3b109cf589",
@@ -252,11 +285,55 @@
         "lineage": [
         "lineage": [
           "dbeb68b44c62"
           "dbeb68b44c62"
         ]
         ]
+      },
+      {
+        "id": "54cd057f5856",
+        "template": "修稿分流规则:PATCHES处理局部文字问题(措辞/对话/AI痕迹/小连续性错误)——每个PATCH引用原文段落给出替换文本,未涉及内容保持原样;伏笔状态须与伏笔池同步;修稿原则:修根因不做表面润色;REVISED_CONTENT处理全章级问题(字数压缩/结构重组/节奏重排/重大剧情偏离)——Critical包含字数或结构性问题时确保用REVISED_CONTENT;不改变剧情走向和核心矛盾;保留原文语言风格节奏呼吸;情绪用动作外化;坏事叠坏事每层比上一层过分;",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "72f787227d13"
+        ]
+      },
+      {
+        "id": "281753d919f9",
+        "template": "字数治理:压缩时优先合并重复信息/删除无变化场景/精简过渡段落——如需删75%以上原文→拒绝标准化标记为需人工拆分;",
+        "source": "simplify",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "445fe5328e69"
+        ]
+      },
+      {
+        "id": "da0026a6252b",
+        "template": "关键:字数治理:压缩时优先合并重复信息/删除无变化场景/精简过渡段落——如需删75%以上原文→拒绝标准化标记为需人工拆分。扩写时优先增加五感描写/角色内心反应/场景氛围——如扩展后信息密度显著下降→拒绝标准化标记为需合并。只修正一次不递归。保留关键标记/人物名/地点名/已有事实。不凭空新增子情节。",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "dbeb68b44c62"
+        ]
+      },
+      {
+        "id": "385154d96cd7",
+        "template": "修稿分流规则:PATCHES处理局部文字问题(措辞/对话/AI痕迹/小连续性错误)——每个PATCH引用原文段落给出替换文本,未涉及内容保持原样;",
+        "source": "simplify",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "72f787227d13"
+        ]
       }
       }
     ]
     ]
   },
   },
   "quality_check": {
   "quality_check": {
-    "generation": 7,
+    "generation": 11,
     "variants": [
     "variants": [
       {
       {
         "id": "e64cb47a10dc",
         "id": "e64cb47a10dc",
@@ -353,7 +430,7 @@
     ]
     ]
   },
   },
   "outline": {
   "outline": {
-    "generation": 7,
+    "generation": 11,
     "variants": [
     "variants": [
       {
       {
         "id": "1c411b7f3b48",
         "id": "1c411b7f3b48",
@@ -474,7 +551,7 @@
     ]
     ]
   },
   },
   "hook_management": {
   "hook_management": {
-    "generation": 7,
+    "generation": 11,
     "variants": [
     "variants": [
       {
       {
         "id": "48e23e01be06",
         "id": "48e23e01be06",
@@ -576,6 +653,72 @@
         "lineage": [
         "lineage": [
           "f8f652e81111"
           "f8f652e81111"
         ]
         ]
+      },
+      {
+        "id": "f24592e9df3d",
+        "template": "伏笔全生命周期:plant(种植)——新伏笔分配唯一ID/标记类型(SUSPENSE/PROMISE/MYSTERY/CONFLICT/CHEKHOV)和优先级/标明计划兑现章。advance(进展)——虽未兑现但给出新线索新事实新风险升级或范围收缩,更新最近推进章号。payoff(兑现)——明确揭示或解决,状态改为已回收。defer(推迟)——主动搁置转入后台或被剧情压后。stale(过期)——超10章无推进决定推进/废弃/合并。每章前读钩子账本→列出高优活跃钩子→确认推进/兑现机会。每章后更新:兑现移走→推进追加→新种写入→验证stale。",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "48e23e01be06"
+        ]
+      },
+      {
+        "id": "c315676f2aad",
+        "template": "悬念健康分析:stale(>10章无推进)→立即标记决定推进/废弃/合并;",
+        "source": "simplify",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "1721fac5c5ba"
+        ]
+      },
+      {
+        "id": "80de2177e2d2",
+        "template": "核心:抓手健康分析:stale(>10章无推进)→立即标记决定推进/废弃/合并。burst(单章>5个新钩子)→警告削减低优先级。no-advance(high钩子>5章无推进)→下章安排推进。payoff逾期(计划兑现章已过未标记)→确认断头钩子。核心钩子(core_hook=true)stale超10章→warning升级critical——全书只有3-7条核心钩子,任其漂移是叙事腐烂先行指标。卷末核心钩子仍open/stale无显式转入下卷规划→critical。",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "f8f652e81111"
+        ]
+      },
+      {
+        "id": "30316fb1e8a9",
+        "template": "伏笔全生命周期:plant(种植)——新伏笔分配唯一ID/标记类型(SUSPENSE/PROMISE/MYSTERY/CONFLICT/CHEKHOV)和优先级/标明计划兑现章。advance(推进)——虽未兑现但给出新线索新事实新风险升级或范围收缩,更新最近推进章号。payoff(兑现)——明确揭示或解决,状态改为已回收。defer(推迟)——主动搁置转入后台或被剧情压后。stale(过期)——超10章无推进决定推进/废弃/合并。每章前读抓手账本→列出高优活跃钩子→确认推进/兑现机会。每章后更新:兑现移走→推进追加→新种写入→检查stale。",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "48e23e01be06"
+        ]
+      },
+      {
+        "id": "8d8827ea1322",
+        "template": "核心钩子(core_hook=true)stale超10章→warning升级critical——全书只有3-7条核心钩子,任其漂移是叙事腐烂先行指标;burst(单章>5个新钩子)→警告削减低优先级;no-advance(high钩子>5章无推进)→下章安排推进;payoff逾期(计划兑现章已过未标记)→确认断头钩子;悬念健康分析:stale(>10章无推进)→立即标记决定推进/废弃/合并;卷末核心钩子仍open/stale无显式转入下卷规划→critical;",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "1721fac5c5ba"
+        ]
+      },
+      {
+        "id": "4c94716aab18",
+        "template": "伏笔全生命周期:plant(种植)——新伏笔分配唯一ID/标记类型(SUSPENSE/PROMISE/MYSTERY/CONFLICT/CHEKHOV)和优先级/标明计划兑现章。advance(推动)——虽未兑现但给出新线索新事实新风险升级或范围收缩,更新最近推进章号。payoff(兑现)——明确揭示或解决,状态改为已回收。defer(推迟)——主动搁置转入后台或被剧情压后。stale(过期)——超10章无推进决定推进/废弃/合并。每章前读钩子账本→列出高优活跃钩子→确认推进/兑现机会。每章后更新:兑现移走→推进追加→新种写入→检查stale。",
+        "source": "mutate",
+        "scores": {},
+        "trials": 0,
+        "trials_per_dim": {},
+        "lineage": [
+          "48e23e01be06"
+        ]
       }
       }
     ]
     ]
   }
   }

+ 5 - 5
evolution/thresholds.json

@@ -4,11 +4,11 @@
   "rewrite_low": 40,
   "rewrite_low": 40,
   "max_revision_rounds": 3,
   "max_revision_rounds": 3,
   "weights": {
   "weights": {
-    "P1": 0.24999999999999994,
-    "P2": 0.25,
-    "P3": 0.35,
-    "P4": 0.15,
-    "P5": 2.7755575615628914e-17
+    "P1": 0.2273,
+    "P2": 0.2273,
+    "P3": 0.3636,
+    "P4": 0.1364,
+    "P5": 0.0454
   },
   },
   "stale_hook_limit": 10,
   "stale_hook_limit": 10,
   "burst_hook_limit": 5,
   "burst_hook_limit": 5,

+ 4 - 4
scripts/evolve.sh

@@ -69,9 +69,9 @@ git_push_events() {
     if ! is_git_repo; then return 0; fi
     if ! is_git_repo; then return 0; fi
     if ! git -C "$SKILL_DIR" remote get-url origin &>/dev/null; then return 0; fi
     if ! git -C "$SKILL_DIR" remote get-url origin &>/dev/null; then return 0; fi
 
 
-    # 检查 evolution/genres/ 下是否有变更(新事件、编译产物、阈值等)
+    # 检查 evolution/ 下所有变更(题材级 + skill 级)
     local has_changes
     local has_changes
-    has_changes=$(git -C "$SKILL_DIR" status --porcelain -- evolution/genres/ 2>/dev/null || echo "")
+    has_changes=$(git -C "$SKILL_DIR" status --porcelain -- evolution/ 2>/dev/null || echo "")
 
 
     if [[ -z "$has_changes" ]]; then
     if [[ -z "$has_changes" ]]; then
         log "git: 无变更,跳过推送"
         log "git: 无变更,跳过推送"
@@ -82,9 +82,9 @@ git_push_events() {
 
 
     # 汇总变更的题材
     # 汇总变更的题材
     local summary
     local summary
-    summary=$(echo "$has_changes" | sed 's|.*evolution/genres/||' | cut -d/ -f1 | sort -u | tr '\n' ',' | sed 's/,$//')
+    summary=$(echo "$has_changes" | sed 's|.*evolution/||' | cut -d/ -f1 | sort -u | tr '\n' ',' | sed 's/,$//')
 
 
-    git -C "$SKILL_DIR" add evolution/genres/ 2>/dev/null || true
+    git -C "$SKILL_DIR" add evolution/ 2>/dev/null || true
     git -C "$SKILL_DIR" commit -m "evolve(${summary:-auto}): auto-commit evolution data" 2>/dev/null || true
     git -C "$SKILL_DIR" commit -m "evolve(${summary:-auto}): auto-commit evolution data" 2>/dev/null || true
 
 
     if git -C "$SKILL_DIR" push origin master 2>/dev/null; then
     if git -C "$SKILL_DIR" push origin master 2>/dev/null; then

+ 28 - 5
scripts/quality_tuner.py

@@ -21,7 +21,7 @@ DEFAULT_THRESHOLDS = {
     "weights": {"P1": 0.35, "P2": 0.25, "P3": 0.15, "P4": 0.15, "P5": 0.10},
     "weights": {"P1": 0.35, "P2": 0.25, "P3": 0.15, "P4": 0.15, "P5": 0.10},
     "stale_hook_limit": 10, "burst_hook_limit": 5,
     "stale_hook_limit": 10, "burst_hook_limit": 5,
     "no_advance_limit": 5,
     "no_advance_limit": 5,
-    "payoff_interval_min": 2000, "payoff_interval_max": 5000,
+    "payoff_interval_min": 300, "payoff_interval_max": 500,
     "weak_hook_consecutive_max": 3,
     "weak_hook_consecutive_max": 3,
 }
 }
 
 
@@ -125,12 +125,35 @@ class QualityTuner:
                 applied.append({**s, "applied": True})
                 applied.append({**s, "applied": True})
             elif s["type"] == "weight_adjust":
             elif s["type"] == "weight_adjust":
                 target = s["target_pass"]
                 target = s["target_pass"]
-                redistribute = s.get("redistribute_from")
                 old_w = self.thresholds["weights"][target]
                 old_w = self.thresholds["weights"][target]
+                delta = s["suggested_weight"] - old_w
                 self.thresholds["weights"][target] = s["suggested_weight"]
                 self.thresholds["weights"][target] = s["suggested_weight"]
-                if redistribute:
-                    self.thresholds["weights"][redistribute] -= (s["suggested_weight"] - old_w)
-                self.adjustments.append({"what": f"weight_{target}", "from": old_w, "to": s["suggested_weight"]})
+
+                # 从其他维度扣减 delta,每维度不低于 floor=0.05
+                floor = 0.05
+                donors = sorted(
+                    [(dim, w) for dim, w in self.thresholds["weights"].items()
+                     if dim != target and w > floor],
+                    key=lambda x: x[1], reverse=True
+                )
+                remaining = delta
+                for dim, w in donors:
+                    if remaining <= 0:
+                        break
+                    available = w - floor
+                    take = min(available, remaining)
+                    self.thresholds["weights"][dim] -= take
+                    remaining -= take
+
+                # 归一化到 1.0,避免浮点累积
+                total = sum(self.thresholds["weights"].values())
+                for dim in self.thresholds["weights"]:
+                    self.thresholds["weights"][dim] = round(
+                        self.thresholds["weights"][dim] / total, 10
+                    )
+
+                self.adjustments.append({"what": f"weight_{target}", "from": old_w,
+                                         "to": self.thresholds["weights"][target]})
                 applied.append({**s, "applied": True})
                 applied.append({**s, "applied": True})
         return applied
         return applied