codebee 0.1.103 → 0.1.104
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/CHANGELOG.md +4 -0
- package/LICENSE +21 -21
- package/README.md +2 -3
- package/app/core/acceptance.py +100 -100
- package/app/core/acp_client.py +457 -457
- package/app/core/aiflavor.py +197 -197
- package/app/core/analytics.py +38 -38
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backend_profiles.py +188 -188
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +102 -102
- package/app/core/bookmeta.py +1036 -1036
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/command_auth.py +302 -302
- package/app/core/compaction.py +201 -201
- package/app/core/contracts.py +255 -255
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/eval_matrix.py +91 -91
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flow_graph.py +33 -33
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +389 -389
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/knowledge_pipeline.py +86 -86
- package/app/core/manager.py +2385 -2385
- package/app/core/mocks.py +64 -64
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +285 -181
- package/app/core/openapi.py +217 -217
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -145
- package/app/core/pipeline.py +55 -3
- package/app/core/planner.py +7 -0
- package/app/core/portguard.py +111 -111
- package/app/core/presence.py +77 -77
- package/app/core/preview.py +293 -293
- package/app/core/project_memory.py +206 -206
- package/app/core/prompt_guard.py +78 -78
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +659 -659
- package/app/core/publish/chaprows.py +138 -138
- package/app/core/publish/flows-fanqie-calibrated.json +442 -442
- package/app/core/publish/manager.py +1185 -1185
- package/app/core/publish/qimao.py +193 -193
- package/app/core/publish/volumes.py +108 -108
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +201 -201
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/retrieval.py +137 -137
- package/app/core/sandbox_runtime.py +95 -95
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings.py +215 -215
- package/app/core/settings_schema.py +282 -282
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -139
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/step_runner.py +132 -132
- package/app/core/story_tracking.py +275 -275
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tool_outputs.py +107 -107
- package/app/core/tracing.py +207 -207
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1038 -1038
- package/app/core/volumes.py +389 -389
- package/app/core/webhooks.py +89 -89
- package/app/core/wxdigest.py +865 -865
- package/app/core/zentao.py +2692 -2692
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/artdiff.js +74 -74
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/bin/tutti.js +147 -147
- package/package.json +1 -1
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app/output/_createbook_body.json +0 -1
- package/app/output/_probe_fq_click.py +0 -50
- package/app/output/_probe_fq_form.py +0 -42
- package/app/output/_probe_fq_modal.py +0 -55
- package/app/output/_probe_fq_points.py +0 -71
- package/app/output/_probe_fq_submit.py +0 -34
- package/app/output/_probe_fq_watch.py +0 -62
- package/app/output/workbench-clean-preview.jpg +0 -0
- package/app/output/workbench-reference-render.png +0 -0
- package/app/output/workbench-shell.png +0 -0
package/app/core/paihang.py
CHANGED
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""扫榜选材:抓取公开排行榜页,产出选题分析素材。
|
|
3
|
-
|
|
4
|
-
数据源(四源聚合,任一失败不影响其余源;2026-09-24 实测全 200):
|
|
5
|
-
- 七猫:www.qimao.com/paihang/(106KB)
|
|
6
|
-
- 番茄 Web 版:fanqienovel.com/rank(题材分类+书名在中文 a 标签里;
|
|
7
|
-
App 端接口有 SecuritySign/X-Argus 签名——那才是难路,Web 版榜单页
|
|
8
|
-
不需要签名,别走弯路)
|
|
9
|
-
- 起点移动版:m.qidian.com/rank(106KB,直接出书名;主站 www.qidian.com
|
|
10
|
-
被 WAF 拦 202/209B,移动页不需要签名)
|
|
11
|
-
- 纵横:www.zongheng.com/rank(65KB,书名+统计标签混排,靠噪音词过滤)
|
|
12
|
-
|
|
13
|
-
下载走 curl 子进程(与 covergen 同款),解析用宽松正则——页面结构变了
|
|
14
|
-
宁可返回空(调用方回落普通直连提示词),不做脆弱的强解析。
|
|
15
|
-
"""
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import logging
|
|
19
|
-
import re
|
|
20
|
-
|
|
21
|
-
log = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
QIMAO_RANK_URL = "https://www.qimao.com/paihang/"
|
|
24
|
-
FANQIE_RANK_URL = "https://fanqienovel.com/rank"
|
|
25
|
-
QIDIAN_M_RANK_URL = "https://m.qidian.com/rank/"
|
|
26
|
-
ZONGHENG_RANK_URL = "https://www.zongheng.com/rank/"
|
|
27
|
-
_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
28
|
-
"(KHTML, like Gecko) Chrome/126 Safari/537.36")
|
|
29
|
-
|
|
30
|
-
# 页面 UI 噪音词(非书目/分类):页面 chrome、导航、tab 名、登录/搜索、
|
|
31
|
-
# 榜单 tab 与统计标签(起点移动版 tab 名、纵横月票/人气/点击等)
|
|
32
|
-
_NOISE = {"加入书架", "立即阅读", "开始阅读", "免费阅读", "全部", "分类", "排行",
|
|
33
|
-
"排行榜", "完本", "连载中", "阅读", "书城", "男生", "女生",
|
|
34
|
-
"帮助中心", "作家助手", "客服", "登录", "注册", "立即登录",
|
|
35
|
-
"搜索", "热门作品排行", "大热榜", "新书榜", "完结榜", "收藏榜",
|
|
36
|
-
"更新榜", "日榜", "月榜", "蝉联", "榜首", "热度", "免费", "畅销",
|
|
37
|
-
"下载", "客户端", "首页", "我的", "设置", "更多", "返回", "关于",
|
|
38
|
-
"月票榜", "阅读榜", "书友榜", "推荐榜", "签约榜", "新人榜",
|
|
39
|
-
"畅销榜", "大家都在搜", "男生小说排行榜", "女生小说排行榜",
|
|
40
|
-
"男频阅读榜", "女频阅读榜", "男频新书榜", "番茄小说",
|
|
41
|
-
"登录后获得更多特色功能", "安装起点读书客户端", "看更多正版好书",
|
|
42
|
-
"月票", "人气", "点击", "万字", "推荐", "收藏", "鲜花", "打赏", "推荐票"}
|
|
43
|
-
|
|
44
|
-
_CJK_RE = re.compile(r">([\u4e00-\u9fa5]{2,12})<")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _curl_text(url):
|
|
48
|
-
"""curl 子进程抓页面文本;失败返回空串。"""
|
|
49
|
-
from . import runner
|
|
50
|
-
try:
|
|
51
|
-
r = runner.run_process(
|
|
52
|
-
argv=["curl", "-sS", "--max-time", "20", "-A", _UA, url],
|
|
53
|
-
timeout=30)
|
|
54
|
-
except Exception:
|
|
55
|
-
return ""
|
|
56
|
-
if not r.get("ok"):
|
|
57
|
-
return ""
|
|
58
|
-
return r.get("stdout") or ""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _extract_cjk(html, limit):
|
|
62
|
-
"""从 HTML 提取中文元素材(去 UI 噪音、保序去重)。"""
|
|
63
|
-
items, seen = [], set()
|
|
64
|
-
for t in _CJK_RE.findall(html or ""):
|
|
65
|
-
if t in _NOISE or t in seen:
|
|
66
|
-
continue
|
|
67
|
-
seen.add(t)
|
|
68
|
-
items.append(t)
|
|
69
|
-
if len(items) >= limit:
|
|
70
|
-
break
|
|
71
|
-
return items
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def fetch_qimao_rank(limit=30):
|
|
75
|
-
"""七猫排行榜:返回元素列表,抓取失败返回 []。"""
|
|
76
|
-
return _extract_cjk(_curl_text(QIMAO_RANK_URL), limit)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def fetch_fanqie_rank(limit=30):
|
|
80
|
-
"""番茄 Web 版榜单:返回元素列表,抓取失败返回 []。"""
|
|
81
|
-
return _extract_cjk(_curl_text(FANQIE_RANK_URL), limit)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def fetch_qidian_rank(limit=30):
|
|
85
|
-
"""起点移动版榜单:返回元素列表,抓取失败返回 []。"""
|
|
86
|
-
return _extract_cjk(_curl_text(QIDIAN_M_RANK_URL), limit)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def fetch_zongheng_rank(limit=30):
|
|
90
|
-
"""纵横排行榜:返回元素列表,抓取失败返回 []。"""
|
|
91
|
-
return _extract_cjk(_curl_text(ZONGHENG_RANK_URL), limit)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# 源清单:顺序即素材拼接顺序(七猫/番茄为元老源保持在前)。
|
|
95
|
-
# 存函数名、调用期 globals() 解析——mock.patch 换模块属性才能生效
|
|
96
|
-
# (存函数引用会把定义期原函数钉死,测试会穿透打真网络)。
|
|
97
|
-
_SOURCES = (
|
|
98
|
-
("七猫排行榜素材", "fetch_qimao_rank"),
|
|
99
|
-
("番茄排行榜素材", "fetch_fanqie_rank"),
|
|
100
|
-
("起点排行榜素材", "fetch_qidian_rank"),
|
|
101
|
-
("纵横排行榜素材", "fetch_zongheng_rank"),
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def fetch_rank_items(limit=40):
|
|
106
|
-
"""四源聚合:各抓一份(跨源去重合并,按源清单顺序)。
|
|
107
|
-
|
|
108
|
-
全部失败才返回 []——单源挂了其余源仍可用,扫榜永不因单点挡任务。"""
|
|
109
|
-
merged, seen = [], set()
|
|
110
|
-
for _label, fname in _SOURCES:
|
|
111
|
-
for t in globals()[fname](limit):
|
|
112
|
-
if t not in seen:
|
|
113
|
-
seen.add(t)
|
|
114
|
-
merged.append(t)
|
|
115
|
-
return merged
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def rank_scan_prompt(goal):
|
|
119
|
-
"""组装扫榜选材分析提示词。四源全空返回 None(调用方回落直连提示词)。
|
|
120
|
-
|
|
121
|
-
报告契约含逐条 A-E 跨平台证据分级(借鉴 career-ops 的分级报告交付形态):
|
|
122
|
-
分级只认素材实际出现情况——抑制单源孤证被模型放大成「大热题材」。"""
|
|
123
|
-
blocks, seen = [], set()
|
|
124
|
-
for label, fname in _SOURCES:
|
|
125
|
-
items = [t for t in globals()[fname](30) if t not in seen]
|
|
126
|
-
seen.update(items)
|
|
127
|
-
if items:
|
|
128
|
-
blocks.append("### %s\n%s" % (label, "、".join(items)))
|
|
129
|
-
if not blocks:
|
|
130
|
-
return None
|
|
131
|
-
material = "\n\n".join(blocks)
|
|
132
|
-
return (
|
|
133
|
-
"你是网文选题分析师。以下是刚刚抓取的各平台排行榜页面的书目与分类素材:\n\n"
|
|
134
|
-
"## 榜单元素材\n%s\n\n"
|
|
135
|
-
"## 用户想写的方向\n%s\n\n"
|
|
136
|
-
"## 你的产出(Markdown 报告)\n"
|
|
137
|
-
"报告第一行给图例(一行):「证据分级:A=三平台及以上重合,B=两平台重合,"
|
|
138
|
-
"C=单平台多次出现,D=单平台仅一次,E=素材无直接证据、系推断」。\n"
|
|
139
|
-
"1. **热门题材 Top3**:各自的共同特征与上榜代表书目(跨平台重合的题材单独点出——多平台都热说明是真风口)\n"
|
|
140
|
-
"2. **高频人设/套路总结**:3-5 条,点名反复出现的元素\n"
|
|
141
|
-
"3. **差异化切入建议**:2-3 个,结合用户方向给出「题材+人设」组合与一句话理由\n"
|
|
142
|
-
"以上每一项结论末尾标注【证据X】(X 为 A-E 之一)。分级只看素材里真实出现的"
|
|
143
|
-
"情况:组合条目按其组成元素的较低档取值,拿不准就降一档、不升级。\n"
|
|
144
|
-
"直接输出分析报告,不要复述素材清单,不要输出与报告无关的内容。"
|
|
145
|
-
% (material, goal or "(用户未指定方向,按大盘热门分析)"))
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""扫榜选材:抓取公开排行榜页,产出选题分析素材。
|
|
3
|
+
|
|
4
|
+
数据源(四源聚合,任一失败不影响其余源;2026-09-24 实测全 200):
|
|
5
|
+
- 七猫:www.qimao.com/paihang/(106KB)
|
|
6
|
+
- 番茄 Web 版:fanqienovel.com/rank(题材分类+书名在中文 a 标签里;
|
|
7
|
+
App 端接口有 SecuritySign/X-Argus 签名——那才是难路,Web 版榜单页
|
|
8
|
+
不需要签名,别走弯路)
|
|
9
|
+
- 起点移动版:m.qidian.com/rank(106KB,直接出书名;主站 www.qidian.com
|
|
10
|
+
被 WAF 拦 202/209B,移动页不需要签名)
|
|
11
|
+
- 纵横:www.zongheng.com/rank(65KB,书名+统计标签混排,靠噪音词过滤)
|
|
12
|
+
|
|
13
|
+
下载走 curl 子进程(与 covergen 同款),解析用宽松正则——页面结构变了
|
|
14
|
+
宁可返回空(调用方回落普通直连提示词),不做脆弱的强解析。
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
QIMAO_RANK_URL = "https://www.qimao.com/paihang/"
|
|
24
|
+
FANQIE_RANK_URL = "https://fanqienovel.com/rank"
|
|
25
|
+
QIDIAN_M_RANK_URL = "https://m.qidian.com/rank/"
|
|
26
|
+
ZONGHENG_RANK_URL = "https://www.zongheng.com/rank/"
|
|
27
|
+
_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
28
|
+
"(KHTML, like Gecko) Chrome/126 Safari/537.36")
|
|
29
|
+
|
|
30
|
+
# 页面 UI 噪音词(非书目/分类):页面 chrome、导航、tab 名、登录/搜索、
|
|
31
|
+
# 榜单 tab 与统计标签(起点移动版 tab 名、纵横月票/人气/点击等)
|
|
32
|
+
_NOISE = {"加入书架", "立即阅读", "开始阅读", "免费阅读", "全部", "分类", "排行",
|
|
33
|
+
"排行榜", "完本", "连载中", "阅读", "书城", "男生", "女生",
|
|
34
|
+
"帮助中心", "作家助手", "客服", "登录", "注册", "立即登录",
|
|
35
|
+
"搜索", "热门作品排行", "大热榜", "新书榜", "完结榜", "收藏榜",
|
|
36
|
+
"更新榜", "日榜", "月榜", "蝉联", "榜首", "热度", "免费", "畅销",
|
|
37
|
+
"下载", "客户端", "首页", "我的", "设置", "更多", "返回", "关于",
|
|
38
|
+
"月票榜", "阅读榜", "书友榜", "推荐榜", "签约榜", "新人榜",
|
|
39
|
+
"畅销榜", "大家都在搜", "男生小说排行榜", "女生小说排行榜",
|
|
40
|
+
"男频阅读榜", "女频阅读榜", "男频新书榜", "番茄小说",
|
|
41
|
+
"登录后获得更多特色功能", "安装起点读书客户端", "看更多正版好书",
|
|
42
|
+
"月票", "人气", "点击", "万字", "推荐", "收藏", "鲜花", "打赏", "推荐票"}
|
|
43
|
+
|
|
44
|
+
_CJK_RE = re.compile(r">([\u4e00-\u9fa5]{2,12})<")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _curl_text(url):
|
|
48
|
+
"""curl 子进程抓页面文本;失败返回空串。"""
|
|
49
|
+
from . import runner
|
|
50
|
+
try:
|
|
51
|
+
r = runner.run_process(
|
|
52
|
+
argv=["curl", "-sS", "--max-time", "20", "-A", _UA, url],
|
|
53
|
+
timeout=30)
|
|
54
|
+
except Exception:
|
|
55
|
+
return ""
|
|
56
|
+
if not r.get("ok"):
|
|
57
|
+
return ""
|
|
58
|
+
return r.get("stdout") or ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _extract_cjk(html, limit):
|
|
62
|
+
"""从 HTML 提取中文元素材(去 UI 噪音、保序去重)。"""
|
|
63
|
+
items, seen = [], set()
|
|
64
|
+
for t in _CJK_RE.findall(html or ""):
|
|
65
|
+
if t in _NOISE or t in seen:
|
|
66
|
+
continue
|
|
67
|
+
seen.add(t)
|
|
68
|
+
items.append(t)
|
|
69
|
+
if len(items) >= limit:
|
|
70
|
+
break
|
|
71
|
+
return items
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def fetch_qimao_rank(limit=30):
|
|
75
|
+
"""七猫排行榜:返回元素列表,抓取失败返回 []。"""
|
|
76
|
+
return _extract_cjk(_curl_text(QIMAO_RANK_URL), limit)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def fetch_fanqie_rank(limit=30):
|
|
80
|
+
"""番茄 Web 版榜单:返回元素列表,抓取失败返回 []。"""
|
|
81
|
+
return _extract_cjk(_curl_text(FANQIE_RANK_URL), limit)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def fetch_qidian_rank(limit=30):
|
|
85
|
+
"""起点移动版榜单:返回元素列表,抓取失败返回 []。"""
|
|
86
|
+
return _extract_cjk(_curl_text(QIDIAN_M_RANK_URL), limit)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def fetch_zongheng_rank(limit=30):
|
|
90
|
+
"""纵横排行榜:返回元素列表,抓取失败返回 []。"""
|
|
91
|
+
return _extract_cjk(_curl_text(ZONGHENG_RANK_URL), limit)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# 源清单:顺序即素材拼接顺序(七猫/番茄为元老源保持在前)。
|
|
95
|
+
# 存函数名、调用期 globals() 解析——mock.patch 换模块属性才能生效
|
|
96
|
+
# (存函数引用会把定义期原函数钉死,测试会穿透打真网络)。
|
|
97
|
+
_SOURCES = (
|
|
98
|
+
("七猫排行榜素材", "fetch_qimao_rank"),
|
|
99
|
+
("番茄排行榜素材", "fetch_fanqie_rank"),
|
|
100
|
+
("起点排行榜素材", "fetch_qidian_rank"),
|
|
101
|
+
("纵横排行榜素材", "fetch_zongheng_rank"),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def fetch_rank_items(limit=40):
|
|
106
|
+
"""四源聚合:各抓一份(跨源去重合并,按源清单顺序)。
|
|
107
|
+
|
|
108
|
+
全部失败才返回 []——单源挂了其余源仍可用,扫榜永不因单点挡任务。"""
|
|
109
|
+
merged, seen = [], set()
|
|
110
|
+
for _label, fname in _SOURCES:
|
|
111
|
+
for t in globals()[fname](limit):
|
|
112
|
+
if t not in seen:
|
|
113
|
+
seen.add(t)
|
|
114
|
+
merged.append(t)
|
|
115
|
+
return merged
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def rank_scan_prompt(goal):
|
|
119
|
+
"""组装扫榜选材分析提示词。四源全空返回 None(调用方回落直连提示词)。
|
|
120
|
+
|
|
121
|
+
报告契约含逐条 A-E 跨平台证据分级(借鉴 career-ops 的分级报告交付形态):
|
|
122
|
+
分级只认素材实际出现情况——抑制单源孤证被模型放大成「大热题材」。"""
|
|
123
|
+
blocks, seen = [], set()
|
|
124
|
+
for label, fname in _SOURCES:
|
|
125
|
+
items = [t for t in globals()[fname](30) if t not in seen]
|
|
126
|
+
seen.update(items)
|
|
127
|
+
if items:
|
|
128
|
+
blocks.append("### %s\n%s" % (label, "、".join(items)))
|
|
129
|
+
if not blocks:
|
|
130
|
+
return None
|
|
131
|
+
material = "\n\n".join(blocks)
|
|
132
|
+
return (
|
|
133
|
+
"你是网文选题分析师。以下是刚刚抓取的各平台排行榜页面的书目与分类素材:\n\n"
|
|
134
|
+
"## 榜单元素材\n%s\n\n"
|
|
135
|
+
"## 用户想写的方向\n%s\n\n"
|
|
136
|
+
"## 你的产出(Markdown 报告)\n"
|
|
137
|
+
"报告第一行给图例(一行):「证据分级:A=三平台及以上重合,B=两平台重合,"
|
|
138
|
+
"C=单平台多次出现,D=单平台仅一次,E=素材无直接证据、系推断」。\n"
|
|
139
|
+
"1. **热门题材 Top3**:各自的共同特征与上榜代表书目(跨平台重合的题材单独点出——多平台都热说明是真风口)\n"
|
|
140
|
+
"2. **高频人设/套路总结**:3-5 条,点名反复出现的元素\n"
|
|
141
|
+
"3. **差异化切入建议**:2-3 个,结合用户方向给出「题材+人设」组合与一句话理由\n"
|
|
142
|
+
"以上每一项结论末尾标注【证据X】(X 为 A-E 之一)。分级只看素材里真实出现的"
|
|
143
|
+
"情况:组合条目按其组成元素的较低档取值,拿不准就降一档、不升级。\n"
|
|
144
|
+
"直接输出分析报告,不要复述素材清单,不要输出与报告无关的内容。"
|
|
145
|
+
% (material, goal or "(用户未指定方向,按大盘热门分析)"))
|
package/app/core/pipeline.py
CHANGED
|
@@ -2919,6 +2919,7 @@ __VOLUME__
|
|
|
2919
2919
|
__LOCKED_CH__
|
|
2920
2920
|
__STALE_NOTE__
|
|
2921
2921
|
__REDO_ISSUES__
|
|
2922
|
+
__REUSE_GUARD__
|
|
2922
2923
|
- 正文约 __WORDS__ 字,中文,直接开写正文(可含本章标题行)。
|
|
2923
2924
|
|
|
2924
2925
|
## 前情提要(此前各章结尾摘录,衔接用)
|
|
@@ -3085,6 +3086,33 @@ def _wc(text):
|
|
|
3085
3086
|
return len(re.sub(r"\s", "", text or ""))
|
|
3086
3087
|
|
|
3087
3088
|
|
|
3089
|
+
def _reuse_guard_samples(workdir, upto, window=4, min_words=200):
|
|
3090
|
+
"""收集此前各章的开笔/收束摘录(起草负样例,防跨章句式同款——105-112 批
|
|
3091
|
+
「落笔入册」收束 8 章用 4 次被判 major 的根治手段之一:评审事后抓 → 起草
|
|
3092
|
+
事前给反面样例)。只看近 window 章(更早的句式影响衰减);过短/读不到的
|
|
3093
|
+
章静默跳过。"""
|
|
3094
|
+
samples = []
|
|
3095
|
+
for j in range(max(1, int(upto) - int(window)), int(upto)):
|
|
3096
|
+
t = _read_chapter(workdir, j)
|
|
3097
|
+
if not t or _wc(t) < int(min_words):
|
|
3098
|
+
continue
|
|
3099
|
+
body = re.sub(r"\s+", " ", t.strip())
|
|
3100
|
+
samples.append({"chapter": j, "head": body[:80], "tail": body[-100:]})
|
|
3101
|
+
return samples
|
|
3102
|
+
|
|
3103
|
+
|
|
3104
|
+
def _dup_reject_note(notes):
|
|
3105
|
+
"""查重打回后的重写指令块:把上一稿被拦截的重复证据逐条下发。"""
|
|
3106
|
+
lines = novel_quality.format_duplicate_notes(notes)
|
|
3107
|
+
if not lines:
|
|
3108
|
+
return ""
|
|
3109
|
+
return ("\n\n## 确定性查重拦截(必读)\n"
|
|
3110
|
+
"上一稿因整段/整句逐字重复未通过落盘验收,已被打回重写。重写时必须"
|
|
3111
|
+
"消除以下重复——删并重复内容、保留信息量最高的一处;若为有意的复诵"
|
|
3112
|
+
"或复沓,必须有动作/归属铺垫并改写措辞,不得逐字再现:\n- "
|
|
3113
|
+
+ "\n- ".join(lines) + "\n")
|
|
3114
|
+
|
|
3115
|
+
|
|
3088
3116
|
_ANCHOR_EDGE_PUNCT = "。!?,、;:…—·,.!?;:\"'“”‘’()()[]【】《》<>*#-"
|
|
3089
3117
|
|
|
3090
3118
|
|
|
@@ -3901,6 +3929,11 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
3901
3929
|
redo_lines += _major_lines(book_lvl)
|
|
3902
3930
|
redo_note = ("## 上一轮评审未达标原因(必读)\n" + "\n".join(redo_lines) + "\n"
|
|
3903
3931
|
) if redo_lines else ""
|
|
3932
|
+
# 跨章复用红线负样例:此前各章实际的开笔/收束摘录随起草下发,把
|
|
3933
|
+
# 「评审事后抓句式同款」前移成「起草事前看反面样例」(105-112 批
|
|
3934
|
+
# 章末收束句式 4 连用被判 major 的根治;首批第 1 章无样例为空块)
|
|
3935
|
+
reuse_guard = novel_quality.reuse_guard_block(
|
|
3936
|
+
_reuse_guard_samples(workdir, i))
|
|
3904
3937
|
# stable_order:同一任务 8 个章节的技能块必须字节级一致(§07 T1.2' 前缀缓存)
|
|
3905
3938
|
# 三块分开留底:起草重试的分层降级(_serial_shrunk_block)要按
|
|
3906
3939
|
# 「经验库/圣经」各自的身份收缩,折成一整块就只剩 4K 硬截一层了
|
|
@@ -3940,6 +3973,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
3940
3973
|
.replace("__LOCKED_CH__", serial_locked_chapter_block(workdir, i))
|
|
3941
3974
|
.replace("__STALE_NOTE__", stale_note)
|
|
3942
3975
|
.replace("__REDO_ISSUES__", redo_note)
|
|
3976
|
+
.replace("__REUSE_GUARD__", reuse_guard)
|
|
3943
3977
|
.replace("__REVIEW_RUBRIC__", rubric_block)
|
|
3944
3978
|
.replace("__HIGHLIGHT__", ch.get("highlight") or "按剧情要点自然铺设一处小冲突/小反转")
|
|
3945
3979
|
.replace("__STYLE__", style_tpl)
|
|
@@ -3956,9 +3990,14 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
3956
3990
|
# ——2026-09-17 七猫连载实测:多 run 并行推进时后位章节必撞限流,
|
|
3957
3991
|
# 一章失败即整 run 作废,太贵。
|
|
3958
3992
|
def _chapter_state():
|
|
3959
|
-
"""
|
|
3993
|
+
"""草稿验收:成品是章稿文件本身,不是退出码——文件够长且无
|
|
3994
|
+
逐字重复才算好。整段/整句/完整对白逐字重复是成稿事故(113/
|
|
3995
|
+
118 章实案两处整段连排、对白复现,评审 major 一票否决+白烧
|
|
3996
|
+
一轮评审),落盘时确定性拦截打回,省下评审再抓的往返。"""
|
|
3960
3997
|
txt = _read_chapter(workdir, i)
|
|
3961
|
-
|
|
3998
|
+
if not txt or _wc(txt) < int(wpc * 0.6):
|
|
3999
|
+
return False, (txt or "")
|
|
4000
|
+
return not novel_quality.duplicate_notes(txt), txt
|
|
3962
4001
|
|
|
3963
4002
|
res = None
|
|
3964
4003
|
good = False
|
|
@@ -3971,6 +4010,7 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
3971
4010
|
prev_prompt = None # 上一次实际下发的提示词
|
|
3972
4011
|
prev_timed_out = False # 上一次是否「超时/停滞」收场
|
|
3973
4012
|
shrink_note = "" # 本次重试的分层降级说明(进 step note 供对账)
|
|
4013
|
+
dup_fix_note = "" # 查重打回后的消重指令(重试/换将随提示词下发)
|
|
3974
4014
|
for draft_attempt in range(3):
|
|
3975
4015
|
if draft_attempt:
|
|
3976
4016
|
# 30s / 60s 退避;ev.wait 睡等可被取消即刻唤醒
|
|
@@ -3986,7 +4026,11 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
3986
4026
|
# 30KB 对讯飞必挂)
|
|
3987
4027
|
shrunk_block, shrink_note = _serial_shrunk_block(
|
|
3988
4028
|
sk_lessons, bible, kb_block)
|
|
3989
|
-
use_prompt = prompt.replace(sk_block, shrunk_block)
|
|
4029
|
+
use_prompt = prompt.replace(sk_block, shrunk_block) + dup_fix_note
|
|
4030
|
+
elif draft_attempt and dup_fix_note:
|
|
4031
|
+
# 同作者重试带上一稿的重复证据(消重指令)——同提示重跑
|
|
4032
|
+
# 只会长出同族事故,新变量才值得烧(同 2026-09-22 超时教训)
|
|
4033
|
+
use_prompt = prompt + dup_fix_note
|
|
3990
4034
|
# 超时重试必须有新变量才值得做(2026-09-22):提示词与上次逐字节
|
|
3991
4035
|
# 相同、上次又是超时/停滞收场时,重试只是把同一个超时再烧一遍
|
|
3992
4036
|
# (2400s × N)。此时直接跳出交替换将——换 CLI/模型才是新机会。
|
|
@@ -4014,6 +4058,10 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
4014
4058
|
good, txt = _chapter_state()
|
|
4015
4059
|
if good:
|
|
4016
4060
|
break
|
|
4061
|
+
if txt:
|
|
4062
|
+
_dups = novel_quality.duplicate_notes(txt)
|
|
4063
|
+
if _dups:
|
|
4064
|
+
dup_fix_note = _dup_reject_note(_dups)
|
|
4017
4065
|
if res["ok"] and _wc(res.get("text") or "") >= int(wpc * 0.6):
|
|
4018
4066
|
# 回复正文就是完整章稿(kimi 35B 实测:正文当消息回而不
|
|
4019
4067
|
# 落盘)→ 代为落盘救回成品
|
|
@@ -4024,6 +4072,10 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
4024
4072
|
pass
|
|
4025
4073
|
if good:
|
|
4026
4074
|
break
|
|
4075
|
+
if txt:
|
|
4076
|
+
_dups = novel_quality.duplicate_notes(txt)
|
|
4077
|
+
if _dups:
|
|
4078
|
+
dup_fix_note = _dup_reject_note(_dups)
|
|
4027
4079
|
if ev is not None and ev.is_set():
|
|
4028
4080
|
break
|
|
4029
4081
|
# 同作者重试穷尽 → 起草换将:按路由分序逐个试备选(最多 2 个,
|
package/app/core/planner.py
CHANGED
|
@@ -266,6 +266,10 @@ __SKILLS__
|
|
|
266
266
|
不强制每章都打脸、反转或以悬念断章;
|
|
267
267
|
- 章末可自然收束或留白;大纲中的 highlight 描述主要戏剧/情绪变化或回报,无显著爆点时写清叙事功能,不要强造爆点。
|
|
268
268
|
- 大纲中的情感转折必须给出触发事件和人物选择,不能只写“误会加深/感情升温”;
|
|
269
|
+
- 开笔方式与收束形态批内轮换:相邻章不得用同型句子开笔(如连续多章以「第X日/
|
|
270
|
+
清晨」时间戳起笔),收束形态(落笔定格、悬置问句、场景切换、对话收拍、动作
|
|
271
|
+
停顿等)同一形态全批至多两次,第二次必须换载体或升级信息量——章尾节奏趋同
|
|
272
|
+
是批量生成最易露馅的指纹;
|
|
269
273
|
- style_anchor 是全书唯一的文风锚,每章起草/修订/评审都会注入:必须写明视角人称与句长基调等可执行约束,不要空话;
|
|
270
274
|
- highlight 是本章主要情绪/戏剧变化或读者回报,可为爽、暖、痛、惊、趣味、亲密、发现、安定等;
|
|
271
275
|
平静或过渡章节可承担必要功能,不为填字段伪造高潮;
|
|
@@ -293,6 +297,9 @@ __SKILLS__
|
|
|
293
297
|
- 在关键章节写清人物选择、阻碍与后果;不要求每章都有完整冲突闭环或悬念钩子,避免无功能重复;
|
|
294
298
|
- 复杂情感必须落到触发事件、动作/生理反应和潜台词,不能用一句概括代替;
|
|
295
299
|
- 延续既定叙述视角和语言锚点(见下方既定文风锚),删掉重复复述,避免节奏变慢;
|
|
300
|
+
- 开笔方式与收束形态批内轮换:相邻章不得用同型句子开笔(如连续多章以「第X日/
|
|
301
|
+
清晨」时间戳起笔),收束形态同一形态全批至多两次,第二次必须换载体或升级
|
|
302
|
+
信息量——章尾节奏趋同是批量生成最易露馅的指纹;
|
|
296
303
|
- 题材健康,无违规内容,符合平台签约调性。
|
|
297
304
|
__VOLUMES__
|
|
298
305
|
|