codebee 0.1.72 → 0.1.74
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 +12 -0
- package/README.md +4 -4
- package/app/core/analytics.py +38 -0
- package/app/core/bookmeta.py +0 -14
- package/app/core/contracts.py +255 -0
- package/app/core/error_codes.py +9 -0
- package/app/core/flows.py +1 -1
- package/app/core/health.py +14 -1
- package/app/core/jobs.py +54 -1
- package/app/core/manager.py +106 -17
- package/app/core/market.py +2 -2
- package/app/core/modelhub.py +104 -9
- package/app/core/openapi.py +154 -0
- package/app/core/paihang.py +8 -1
- package/app/core/pipeline.py +41 -0
- package/app/core/planner.py +2 -0
- package/app/core/plugins.py +34 -0
- package/app/core/presence.py +77 -0
- package/app/core/publish/auto.py +11 -0
- package/app/core/publish/flow.py +54 -3
- package/app/core/publish/manager.py +215 -28
- package/app/core/publish/qimao.py +30 -0
- package/app/core/retrieval.py +137 -0
- package/app/core/runner.py +85 -2
- package/app/core/skillpacks/fanqie-novel.md +66 -48
- package/app/core/skillpacks/qimao-signing.md +70 -59
- package/app/core/store.py +72 -0
- package/app/core/story_tracking.py +241 -0
- package/app/main.py +205 -48
- package/app/mcp_server.py +75 -6
- package/app/ui/app.js +390 -101
- package/app/ui/i18n.js +36 -7
- package/app/ui/index.html +8 -3
- package/app/ui/style.css +7059 -7040
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Dependency-free retrieval index with traceable keyword scoring.
|
|
2
|
+
|
|
3
|
+
This is intentionally a RAG-lite layer: SQLite/vector backends can be added
|
|
4
|
+
later without changing the API contract. Every hit carries its source and the
|
|
5
|
+
terms that caused the match.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import threading
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from . import paths
|
|
16
|
+
|
|
17
|
+
_LOCK = threading.RLock()
|
|
18
|
+
_FILE = paths.DATA_DIR / "retrieval_index.json"
|
|
19
|
+
_WORD_RE = re.compile(r"[A-Za-z0-9_]+|[\u4e00-\u9fff]")
|
|
20
|
+
_MAX_FILES = 500
|
|
21
|
+
_MAX_BYTES = 20 * 1024 * 1024
|
|
22
|
+
_SKIP_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv", ".idea", ".vscode"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _terms(text):
|
|
26
|
+
return [x.lower() for x in _WORD_RE.findall(str(text or ""))]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load():
|
|
30
|
+
try:
|
|
31
|
+
value = json.loads(_FILE.read_text(encoding="utf-8"))
|
|
32
|
+
return value if isinstance(value, dict) else {"documents": {}}
|
|
33
|
+
except Exception:
|
|
34
|
+
return {"version": 1, "documents": {}}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _save(value):
|
|
38
|
+
_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
tmp = _FILE.with_suffix(".tmp")
|
|
40
|
+
tmp.write_text(json.dumps(value, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
41
|
+
tmp.replace(_FILE)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def upsert(source, text, metadata=None, doc_id=""):
|
|
45
|
+
source = str(source or "").strip()[:500]
|
|
46
|
+
text = str(text or "").strip()
|
|
47
|
+
if not source or not text:
|
|
48
|
+
raise ValueError("source 和 text 不能为空")
|
|
49
|
+
root_hint = ""
|
|
50
|
+
if isinstance(metadata, dict):
|
|
51
|
+
root_hint = str(metadata.get("root") or "")[:500]
|
|
52
|
+
did = str(doc_id or hashlib.sha256((root_hint + "\0" + source).encode("utf-8")).hexdigest()[:20])[:100]
|
|
53
|
+
meta = metadata if isinstance(metadata, dict) else {}
|
|
54
|
+
meta = {str(k)[:80]: str(v)[:1000] for k, v in list(meta.items())[:30]}
|
|
55
|
+
row = {"id": did, "source": source, "text": text[:200000],
|
|
56
|
+
"terms": sorted(set(_terms(text))),
|
|
57
|
+
"metadata": meta}
|
|
58
|
+
with _LOCK:
|
|
59
|
+
data = _load()
|
|
60
|
+
data.setdefault("version", 1)
|
|
61
|
+
documents = data.setdefault("documents", {})
|
|
62
|
+
# Explicit IDs are client supplied; avoid silently replacing an
|
|
63
|
+
# unrelated document when two sources reuse the same short ID.
|
|
64
|
+
if did in documents and documents[did].get("source") != source:
|
|
65
|
+
suffix = hashlib.sha256((source + "\0" + text).encode("utf-8")).hexdigest()[:12]
|
|
66
|
+
did = (did[:87] + "-" + suffix)[:100]
|
|
67
|
+
row["id"] = did
|
|
68
|
+
documents[did] = row
|
|
69
|
+
_save(data)
|
|
70
|
+
return row
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def index_directory(root, extensions=(".md", ".txt", ".json"), limit=500,
|
|
74
|
+
allowed_roots=None, max_bytes=_MAX_BYTES):
|
|
75
|
+
root = Path(root).expanduser().resolve()
|
|
76
|
+
if not root.is_dir():
|
|
77
|
+
raise ValueError("目录不存在")
|
|
78
|
+
if allowed_roots is not None:
|
|
79
|
+
roots = [Path(p).expanduser().resolve() for p in allowed_roots if p]
|
|
80
|
+
if not any(root == p or p in root.parents for p in roots):
|
|
81
|
+
raise ValueError("只能索引任务工作目录范围内的文件")
|
|
82
|
+
try:
|
|
83
|
+
file_limit = max(1, min(_MAX_FILES, int(limit)))
|
|
84
|
+
except (TypeError, ValueError):
|
|
85
|
+
raise ValueError("limit 必须是整数")
|
|
86
|
+
try:
|
|
87
|
+
byte_limit = max(1, min(_MAX_BYTES, int(max_bytes)))
|
|
88
|
+
except (TypeError, ValueError):
|
|
89
|
+
raise ValueError("max_bytes 必须是整数")
|
|
90
|
+
count = 0
|
|
91
|
+
total_bytes = 0
|
|
92
|
+
for path in root.rglob("*"):
|
|
93
|
+
if count >= file_limit or total_bytes >= byte_limit:
|
|
94
|
+
break
|
|
95
|
+
# Never follow a symlink while indexing user-controlled trees.
|
|
96
|
+
if path.is_symlink() or any(part in _SKIP_DIRS for part in path.relative_to(root).parts):
|
|
97
|
+
continue
|
|
98
|
+
if path.is_file() and path.suffix.lower() in extensions:
|
|
99
|
+
try:
|
|
100
|
+
resolved = path.resolve()
|
|
101
|
+
if resolved != root and root not in resolved.parents:
|
|
102
|
+
continue
|
|
103
|
+
size = path.stat().st_size
|
|
104
|
+
if size > byte_limit - total_bytes:
|
|
105
|
+
continue
|
|
106
|
+
upsert(str(path.relative_to(root)), path.read_text(encoding="utf-8", errors="replace"),
|
|
107
|
+
{"root": str(root)})
|
|
108
|
+
count += 1
|
|
109
|
+
total_bytes += size
|
|
110
|
+
except OSError:
|
|
111
|
+
pass
|
|
112
|
+
return {"indexed": count, "bytes": total_bytes,
|
|
113
|
+
"total": len(_load().get("documents") or {})}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def search(query, limit=10):
|
|
117
|
+
try:
|
|
118
|
+
n = max(1, min(50, int(limit or 10)))
|
|
119
|
+
except (TypeError, ValueError):
|
|
120
|
+
raise ValueError("limit 必须是整数")
|
|
121
|
+
terms = set(_terms(str(query or "")[:500]))
|
|
122
|
+
if not terms:
|
|
123
|
+
return []
|
|
124
|
+
with _LOCK:
|
|
125
|
+
docs = list((_load().get("documents") or {}).values())
|
|
126
|
+
rows = []
|
|
127
|
+
for doc in docs:
|
|
128
|
+
matched = sorted(terms.intersection(doc.get("terms") or []))
|
|
129
|
+
if not matched:
|
|
130
|
+
continue
|
|
131
|
+
score = len(matched) / max(1, len(terms))
|
|
132
|
+
rows.append({"id": doc.get("id"), "source": doc.get("source"),
|
|
133
|
+
"score": round(score, 4), "matched_terms": matched,
|
|
134
|
+
"snippet": str(doc.get("text") or "")[:600],
|
|
135
|
+
"metadata": doc.get("metadata") or {}})
|
|
136
|
+
rows.sort(key=lambda x: (-x["score"], x["source"] or ""))
|
|
137
|
+
return rows[:n]
|
package/app/core/runner.py
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
import glob
|
|
15
16
|
import json
|
|
16
17
|
import os
|
|
17
18
|
import re
|
|
@@ -68,10 +69,50 @@ def find_git_bash():
|
|
|
68
69
|
return None
|
|
69
70
|
|
|
70
71
|
|
|
72
|
+
_CLI_EXTS = (".exe", ".cmd", ".bat")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _fallback_probe_cli(name):
|
|
76
|
+
"""which() 落空时按常见安装位兜底找 CLI(Windows only),返回全路径或 None。
|
|
77
|
+
|
|
78
|
+
服务进程的 PATH 是启动那一刻的快照:CLI 在服务起跑之后才安装/迁移(如
|
|
79
|
+
2026-09-30 claude 转 winget 原生安装)时,快照里没有新目录,which 解析
|
|
80
|
+
不到、裸名直传 Popen 直接 FileNotFoundError,直到重启服务才自愈。这里
|
|
81
|
+
只探测磁盘已知落点,不改 PATH、不缓存(装没装是低频事件,探几次不亏)。
|
|
82
|
+
"""
|
|
83
|
+
if os.name != "nt":
|
|
84
|
+
return None
|
|
85
|
+
name = str(name)
|
|
86
|
+
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", name):
|
|
87
|
+
return None # 带斜杠/参数形态的不是裸命令名,交给原路径逻辑
|
|
88
|
+
local = os.environ.get("LOCALAPPDATA") or ""
|
|
89
|
+
home = os.path.expanduser("~")
|
|
90
|
+
appdata = os.environ.get("APPDATA") or os.path.join(home, "AppData", "Roaming")
|
|
91
|
+
flat_dirs = (
|
|
92
|
+
os.path.join(local, "Microsoft", "WinGet", "Links"),
|
|
93
|
+
os.path.join(home, ".local", "bin"),
|
|
94
|
+
os.path.join(appdata, "npm"),
|
|
95
|
+
)
|
|
96
|
+
for d in flat_dirs:
|
|
97
|
+
for ext in _CLI_EXTS:
|
|
98
|
+
p = os.path.join(d, name + ext)
|
|
99
|
+
if os.path.isfile(p):
|
|
100
|
+
return p
|
|
101
|
+
# winget 便携包:Packages\<厂商.Id_*>\name.EXE(目录名带来源哈希,glob 一层)
|
|
102
|
+
pkgs = os.path.join(local, "Microsoft", "WinGet", "Packages")
|
|
103
|
+
try:
|
|
104
|
+
for p in glob.glob(os.path.join(pkgs, "*", name + ".*")):
|
|
105
|
+
if p.lower().endswith(_CLI_EXTS) and os.path.isfile(p):
|
|
106
|
+
return p
|
|
107
|
+
except OSError:
|
|
108
|
+
pass
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
|
|
71
112
|
def resolve_command(command):
|
|
72
113
|
"""命令名 → 可直接 spawn 的 argv 前缀(.cmd/.bat 垫片必须经 cmd /c)。"""
|
|
73
|
-
path = shutil.which(command) or command
|
|
74
|
-
low = path.lower()
|
|
114
|
+
path = shutil.which(command) or _fallback_probe_cli(command) or command
|
|
115
|
+
low = str(path).lower()
|
|
75
116
|
if low.endswith(".cmd") or low.endswith(".bat"):
|
|
76
117
|
return ["cmd", "/c", path]
|
|
77
118
|
return [path]
|
|
@@ -719,6 +760,16 @@ def run_process(argv=None, shell_cmd=None, stdin_text=None, cwd=None, env=None,
|
|
|
719
760
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
720
761
|
creationflags=CREATE_NO_WINDOW,
|
|
721
762
|
start_new_session=(os.name != "nt")) # POSIX 需独立进程组供 killpg 杀树;Windows 忽略该参数
|
|
763
|
+
except FileNotFoundError as e:
|
|
764
|
+
# filename=None 时 %r 只有一句「系统找不到指定的文件」,无从排查
|
|
765
|
+
# (2026-09-30 claude winget 迁移实案:服务 PATH 快照过旧)。
|
|
766
|
+
tgt = str(argv[0]) if argv else ""
|
|
767
|
+
return {"ok": False, "exit_code": None, "stdout": "",
|
|
768
|
+
"stderr": ("启动失败:找不到可执行文件「%s」。CLI 未安装或已卸载;"
|
|
769
|
+
"若刚安装,重启 CodeBee 服务让 PATH 生效即可。"
|
|
770
|
+
% tgt),
|
|
771
|
+
"duration": 0.0, "cancelled": False, "timed_out": False,
|
|
772
|
+
"stalled": False, "deadline_exceeded": False, "abort_marker": None}
|
|
722
773
|
except Exception as e:
|
|
723
774
|
return {"ok": False, "exit_code": None, "stdout": "",
|
|
724
775
|
"stderr": "启动失败: %r" % e, "duration": 0.0,
|
|
@@ -1578,6 +1629,30 @@ def _invalid_model(model):
|
|
|
1578
1629
|
return str(model or "").strip().lower() in _INVALID_MODEL_NAMES
|
|
1579
1630
|
|
|
1580
1631
|
|
|
1632
|
+
# opencode 启动横幅形如「> build · glm-5.3」(ANSI 清洗后一行、圆点分隔);
|
|
1633
|
+
# 真错误(配额/配置/网络)CLI 都会另打印文本,不会是纯横幅形态
|
|
1634
|
+
_OC_BANNER_LINE_RE = re.compile(r"^\s*>?\s*[^\s·•]+\s*[·•]\s*\S+\s*$")
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
def _opencode_fast_banner_death(res):
|
|
1638
|
+
"""opencode 启动期秒退签名:退出码 1、总时长很短、输出清洗 ANSI 后只剩
|
|
1639
|
+
启动横幅、没有任何错误文本。真实失败不走这个形态;该形态只值得原地
|
|
1640
|
+
立即重试一次(配置写读竞态/端口抢占类瞬时抖动,2026-09-30 实案:
|
|
1641
|
+
并行评审爆发期连续三发「退出码 1;stderr/stdout: > build · glm-5.3」,
|
|
1642
|
+
数分钟后同进程自愈)。"""
|
|
1643
|
+
if res.get("ok") or res.get("exit_code") != 1:
|
|
1644
|
+
return False
|
|
1645
|
+
try:
|
|
1646
|
+
if float(res.get("duration") or 0) > 15.0:
|
|
1647
|
+
return False
|
|
1648
|
+
except (TypeError, ValueError):
|
|
1649
|
+
return False
|
|
1650
|
+
blob = clean_cli_text((res.get("stderr") or "") + "\n"
|
|
1651
|
+
+ (res.get("stdout") or ""))
|
|
1652
|
+
lines = [ln.strip() for ln in blob.splitlines() if ln.strip()]
|
|
1653
|
+
return bool(lines) and all(_OC_BANNER_LINE_RE.match(ln) for ln in lines)
|
|
1654
|
+
|
|
1655
|
+
|
|
1581
1656
|
def run_agent(agent, prompt, workdir=None, readonly=True,
|
|
1582
1657
|
timeout=DEFAULT_TIMEOUT, cancel_event=None, log_path=None, resume=None,
|
|
1583
1658
|
images=None, require_tools=False, deadline=None):
|
|
@@ -1801,6 +1876,14 @@ def run_agent(agent, prompt, workdir=None, readonly=True,
|
|
|
1801
1876
|
"provider_id": att.get("provider_id") or "",
|
|
1802
1877
|
"provider": att.get("provider") or {}}
|
|
1803
1878
|
if not res["ok"]:
|
|
1879
|
+
if (kind == "opencode" and attempt == 0
|
|
1880
|
+
and _opencode_fast_banner_death(res)
|
|
1881
|
+
and not (cancel_event is not None
|
|
1882
|
+
and cancel_event.is_set())):
|
|
1883
|
+
_log_note(log_path,
|
|
1884
|
+
"opencode 启动即退(仅横幅无错误文本)——按瞬时抖动 2s 后原地重试一次")
|
|
1885
|
+
time.sleep(2.0)
|
|
1886
|
+
continue
|
|
1804
1887
|
# stderr 与 stdout 都要进错误串:codex 把 "Reading prompt from
|
|
1805
1888
|
# stdin..." 打在 stderr,真正的配额/限流错误全在 stdout 的 JSONL
|
|
1806
1889
|
# 里——只取其一会让 _quota_error/_transient_error 判空。
|
|
@@ -1,63 +1,69 @@
|
|
|
1
1
|
# 番茄小说写作与流量守则(内置经验包)
|
|
2
2
|
|
|
3
|
-
来源:番茄作家社区与作者群体的通行经验汇编 + 平台公开运营机制(2025
|
|
3
|
+
来源:番茄作家社区与作者群体的通行经验汇编 + 平台公开运营机制(2025,B/C 级经验材料);
|
|
4
|
+
可复查入口:[番茄作家后台](https://fanqienovel.com/main/writer/)(当前政策以登录后页面为准)。
|
|
4
5
|
签约字数、保底金额、全勤门槛、审核时长等**政策数字随年份调整,一律以「番茄作家后台」当前规则为准**,
|
|
5
6
|
本包不固化具体数值。这是写作参考,不是官方规则,也不是爆款预测;不以格式检查推断过签/起量概率。
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
8
|
+
## 证据等级与使用边界(2026-09-30 更新)
|
|
9
|
+
|
|
10
|
+
- **A级:当前后台/合同/官方公告**:用于确认投稿、签约、更新、版权和内容红线;公开经验不能覆盖当前页面。
|
|
11
|
+
- **B级:多作者经验、编辑反馈或后台数据**:只作为可检验的诊断假设,必须写明样本、章节范围和下一步实验。
|
|
12
|
+
- **C级:单个案例、固定字数或传闻算法权重**:不作硬门槛,不宣称推荐、签约或收入因果;没有样本就保留为待验证。
|
|
13
|
+
- 质量报告按“文本质量 → 平台适配 → 当前政策”分层。每个结论都给出证据位置、未知项和可逆改动;完读率、追读率等数字是定位线索,不是文学评分。
|
|
14
|
+
|
|
15
|
+
### 番茄与通用质量底线
|
|
16
|
+
|
|
17
|
+
先检查具体事件是否触发主角主动选择,并产生可见后果、阶段性兑现和新的问题;再检查人物动机、因果连续性、文风一致、移动端可读性与内容安全。算法指标不能替代逐章阅读,也不能把“数据暂时好”写成质量已证实。
|
|
18
|
+
|
|
19
|
+
## 〇、与传统编辑平台的差异(工作假设,非权重说明)
|
|
20
|
+
- 番茄可按“数据参与分发”的工作假设来设计实验;这不等于知道算法权重,也不等于签约后完全无人审查。
|
|
21
|
+
“签约只是门票、完读率/追读率影响后续分发”是作者社区的工作假设,不能据此断言推荐权重或作品成败。
|
|
22
|
+
- 作者社区常用“免费阅读、碎片时间、切换成本低”描述部分读者场景;这是受众假设,不代表所有番茄读者。
|
|
23
|
+
可按“书名 → 简介/标签 → 第一章 → 前三章”检查作品承诺是否连贯,但不预设各环节转化率。
|
|
24
|
+
- 作者材料通常将番茄描述为免费阅读与广告变现模式,合同也可能包含保底/全勤;具体收入来源、结算和资格以当前平台说明及合同为准。
|
|
25
|
+
追读、留存和阅读时长可以帮助定位问题,但不能直接换算成收益、推荐因果,也不能推出“追读率 > 文笔”或任何平台保证。
|
|
26
|
+
|
|
27
|
+
## 一、新书观察:流量池与数据指标
|
|
28
|
+
- “先小范围试读、再按数据扩大分发”是作者对新书的常用工作假设;推荐路径和权重不可见,不能写成算法事实。
|
|
29
|
+
- “前 30 天”只能作为一个可调整的早期观察窗口;具体窗口随频道、发书节奏和样本量变化,不是定生死的硬线。
|
|
30
|
+
- 后台常见观察指标(名称与定义以作家后台实际展示为准):完读、追读、留存、平均阅读时长;
|
|
31
|
+
报告使用平台给出的定义,不根据经验帖自行推导指标口径。它们是诊断线索,不能代替因果证据。
|
|
32
|
+
- 数据异常可先按段落提出假设:第一章完读偏低就复查开篇,中段下滑就复查节奏/兑现,
|
|
33
|
+
章末流失就复查钩子与承诺回收;题材、流量、样本量和发布节奏也可能影响结果,需逐项复验。
|
|
34
|
+
- 反恐慌纪律:头几天样本小、噪声大,不要用单日波动证明改稿有效;达到可解释的样本后,按段落和读者反馈做小步实验。是否重写、重开或继续,需结合作者成本、合同和目标,不给“48 小时/两周切书”固定建议。
|
|
35
|
+
|
|
36
|
+
## 二、开篇(前三章观察框架,不是平台硬门槛)
|
|
37
|
+
- 作者经验常用**前三章**做完读与追读对照:第一章交代钩子、第二章兑现核心设定、
|
|
38
|
+
第三章完成一个故事单元;缺一章不等于必然失败,须结合样本与正文证据。
|
|
39
|
+
- 前 100/600 字可作为阅读负担取样点:记录主角、冲突和困境何时出现,不把位置数字当硬门槛。
|
|
40
|
+
- 世界观说明文、慢热日常、多视角群像和倒叙解谜可列为待验证风险;不能直接把某种写法归因成数据崩溃。
|
|
31
41
|
|
|
32
42
|
## 三、节奏与爽点
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
追读率就会肉眼可见地掉。
|
|
39
|
-
- 跨平台通用的铁律仍然有效:主角主观能动性红线、打压为爆发服务、反派不能无脑。
|
|
43
|
+
- **每章应有可感知的变化、情绪波动或兑现,并留下可追问的下一步**;1800-2500 字可作为一次节奏实验的起点,按题材、章节目标和后台数据调整,**不作硬门槛**。
|
|
44
|
+
- 建议先用一句话说明主线目标和下一步;多线叙事、慢热铺垫可列为待验证风险,
|
|
45
|
+
先用章节目标、读者反馈和后台分段数据复核,不预设结果。
|
|
46
|
+
- 早期长线应安排一次完整的大兑现(打脸/升级/身份跃迁等),具体章节不固定;连续多个章节无兑现时,先回到事件、选择、后果链查找原因,不把数据变化直接归因于某个数字。
|
|
47
|
+
- 跨平台复查维度:主角是否有选择,阻碍是否有因果,对手是否有动机;具体呈现不强制一种人物模板。
|
|
40
48
|
|
|
41
49
|
## 四、题材、书名、标签与简介
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
书名、简介、标签三者对题材的表达必须一致(算法按这些做人群匹配)。
|
|
50
|
+
- 经验材料提及过的题材仅作历史参照,不当当前热度结论;开书前按当季榜单和作者后台实际类目复核,
|
|
51
|
+
再判断题材与目标读者是否匹配。
|
|
52
|
+
- **分类/标签是作者常用的匹配入口假设**:挂错分类可能造成承诺与读者预期错位,但实际推荐机制不可见。
|
|
53
|
+
书名、简介、标签三者应与正文题材一致;不要把一致性检查写成已知算法规则。
|
|
47
54
|
- 书名策略:长尾、带情绪、带题材关键词;简介三要素:身份处境 + 核心冲突 + 悬念钩子。
|
|
48
55
|
|
|
49
|
-
##
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
- 存稿 ≥10 万字再发书;新书期若存稿允许,日双更冲数据——推荐系统吃"活跃 + 追读"信号。
|
|
56
|
+
## 五、更新纪律(可持续性优先)
|
|
57
|
+
- **稳定更新是可检验的生产策略,不是推荐保证**:断更会影响读者预期,但全勤和推荐条件必须以当前合同/后台为准。
|
|
58
|
+
- 存稿量、日更频率和是否双更按作者的可持续产能、章节质量和当前规则设定;“10 万字存稿”等经验数字不作硬门槛。
|
|
53
59
|
- 卡文宁可降纲不降质:拆未来 3 章的单元(目标→行动→阻碍→反转),别水字数糊弄——
|
|
54
|
-
|
|
60
|
+
水章可能在完读或读者反馈中暴露,但必须记录样本和改动范围再下结论。
|
|
55
61
|
|
|
56
62
|
## 六、签约与合同
|
|
57
63
|
- 申请门槛(字数)、审核时长以作家后台当前规则为准;常见审核材料 = 开篇正文 + 大纲,
|
|
58
64
|
大纲别只写一句"后面上架爆火",要有明确的阶段目标与兑现节点。
|
|
59
|
-
-
|
|
60
|
-
|
|
65
|
+
- 分成、保底及其他合作模式以当前后台可选项和合同为准;签前逐条核对影视/动漫/版权衍生授权范围、
|
|
66
|
+
独家义务、违约与解约条款,不沿用经验帖中的金额或比例。
|
|
61
67
|
- 拒签自救:先归因(题材带匹配?开篇数据思维?大纲兑现链缺失?),改完再申;
|
|
62
68
|
同一作品反复无修改重投没有意义。
|
|
63
69
|
|
|
@@ -69,12 +75,24 @@
|
|
|
69
75
|
- **细节刻画力度**:每个爽点/反转标记"特写镜头",五感 + 微动作 + 环境反馈把爆发那几秒写厚,
|
|
70
76
|
铺垫在前、放大在中、余波在后;过渡章才可一笔带过。**"每章一小爽"不配特写等于没写**。
|
|
71
77
|
|
|
72
|
-
##
|
|
78
|
+
## 八、后台数据诊断卡(仅在样本足够时使用)
|
|
79
|
+
|
|
80
|
+
| 观察到的信号 | 先检查的文本假设 | 可逆动作 | 复验方式 |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| 第一章完读偏低 | 冲突/主角/承诺是否晚于读者耐心 | 只改开篇一个变量 | 对照后续同类章节或下一批样本 |
|
|
83
|
+
| 中段明显下滑 | 目标、阻碍和兑现是否断档 | 收紧一个故事单元 | 看后续章节完读与反馈是否同步改善 |
|
|
84
|
+
| 章末到下一章流失 | 章尾问题是否可追问、前文承诺是否兑现 | 重写章尾或补回收 | 观察下一章首段和追读变化 |
|
|
85
|
+
| 标签/简介点击与阅读不匹配 | 作品承诺与正文是否错位 | 先修承诺或分类,不同时改正文 | 记录改动范围后再看数据 |
|
|
86
|
+
|
|
87
|
+
没有样本、后台指标含义不明或同时改了多个变量时,结论标为“证据不足”,不宣称掌握推荐权重。
|
|
88
|
+
|
|
89
|
+
## 九、开书/投稿前自检清单
|
|
73
90
|
- [ ] 书名/标签/简介一致表达题材,分类挂对
|
|
74
91
|
- [ ] 第 1 章前 100 字冲突进场;前三章 = 钩子 + 设定兑现 + 完整爽点闭环
|
|
75
|
-
- [ ]
|
|
92
|
+
- [ ] 每章有可感知的变化/情绪或兑现,章尾留下可追问的问题;字数仅作节奏实验,不作硬门槛
|
|
76
93
|
- [ ] 主线一句话讲得清,单线推进,无慢热铺垫章节
|
|
77
|
-
- [ ]
|
|
78
|
-
- [ ]
|
|
94
|
+
- [ ] 早期长线有一次大兑现闭环的规划(章节位置可调整)
|
|
95
|
+
- [ ] 存稿和日更承诺可持续,并已核对当前合同/后台(不套用固定数字)
|
|
79
96
|
- [ ] 文笔三词(风格统一/流畅/细节力度)逐章自查过
|
|
80
97
|
- [ ] 合同衍生授权与独家条款已看清
|
|
98
|
+
- [ ] 后台数据只用于分段诊断,未把单次波动写成算法权重或签约结论
|
|
@@ -1,61 +1,69 @@
|
|
|
1
1
|
# 七猫签约标准与写作规范(内置经验包)
|
|
2
2
|
|
|
3
|
-
来源:七猫作家交流区专栏《拒稿到签约:过稿经验分享》(26
|
|
4
|
-
|
|
3
|
+
来源:七猫作家交流区专栏《拒稿到签约:过稿经验分享》(26 篇,含女频编辑满星官方拒稿模板拆解及作者方法论);
|
|
4
|
+
可复查入口:[bbs.qimao.com/column/6859f2539995c](https://bbs.qimao.com/column/6859f2539995c)(经验帖,不等同当前政策)。
|
|
5
|
+
签约作者(玄弈、竹简听雪、安澜瀧王、苝薇等)方法论。完整原文见
|
|
5
6
|
`C:\Users\HP\.zcode\skills\qimao-signing-guide\SKILL.md`(含《不周山守村人》两次拒稿复盘)。
|
|
6
7
|
|
|
7
8
|
> 这是写作参考与自检清单,不是平台官方规则或过签预测;按当前频道与后台要求核实。
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
10
|
+
## 证据等级与使用边界(2026-09-30 更新)
|
|
11
|
+
|
|
12
|
+
- **A级:当前平台事实**:只接受作家助手/合同/官方公告中当前可见的规则;投稿、独家、更新和内容红线必须回到原页面核对,历史材料不能代替政策。
|
|
13
|
+
- **B级:多篇经验或明确拒稿原文**:可作为质量诊断假设,必须同时指出正文位置、读者/编辑反馈和可验证的下一步实验;不能写成“满足即过签”。
|
|
14
|
+
- **C级:单个作者口诀或未给证据的数字**:只保留为待验证问题,不注入硬门禁,不据此推断签约率、推荐权重或收入。
|
|
15
|
+
- 报告按“稿件质量 → 平台适配 → 当前政策”分层。每条结论都写清证据范围、未知项和风险;确定性字数/格式检查只能提示复查,不能替代文学判断。
|
|
16
|
+
|
|
17
|
+
### 跨平台质量底线
|
|
18
|
+
|
|
19
|
+
先检查首个故事单元是否有**具体事件、主角主动选择、可见后果、阶段性兑现和新的可追问问题**,再看爽点密度。人物动机、因果连续性、前后设定、文风锚、手机阅读段落和内容安全是独立维度;任何一项未读、未测或仅凭摘要得出,都标为“未证实”。
|
|
20
|
+
|
|
21
|
+
## 〇、平台定位(经验假设,先核对当前频道)
|
|
22
|
+
- 七猫的免费阅读、广告/保底和即时反馈,是选题与节奏的**定位假设**;不同频道、时期和合同可能不同,不能从定位直接推出收入或签约结果。
|
|
23
|
+
- “无线风”、热门题材带和“开头很快给到情绪价值”来自历史经验帖,适合拿来设计对照实验;榜单与后台数据优先于口诀。
|
|
24
|
+
- 核心问题不是套公式,而是:读者是否迅速知道主角、处境、目标和下一步;本章的承诺是否在正文兑现。
|
|
25
|
+
- 工作观可保留为复盘提示:“方向、结构、情绪”分别写成可验证的事件、选择、后果,不把口号当质量结论。
|
|
26
|
+
|
|
27
|
+
## 一、黄金一章(历史经验观察点,不是“一章定生死”)
|
|
28
|
+
资料建议把第一章作为高信息密度取样点;以下位置是阅读负担观察参考,不代表编辑统一标准。逐项记录证据,未满足不等于质量不合格。
|
|
29
|
+
- **前 100 字**:观察主角何时登场,读者是否知道故事围绕谁展开。
|
|
30
|
+
- **前 200 字**:观察是否已有值得关心的异常、欲望或冲突。
|
|
31
|
+
- **前 600 字**:观察眼前困境是否具体(退婚/背叛/陷害/侮辱/破产/误会/灾难/生死危机),而非只铺垫。
|
|
32
|
+
- **前 2000 字**:观察核心吸引点是否实际展示(字数位置本身不证明吸引力)。
|
|
33
|
+
- **第三章前**:观察主线是否明确(主角要干什么,像“鸣人要当火影”一样清楚)。
|
|
34
|
+
- 第一章具名人物尽量少(五人只是阅读负担参考,不是硬门槛);前三章要有实质局面变化;
|
|
25
35
|
反转必须由前文条件引发,不为口诀机械加死亡/背叛。
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
- 用对话开场最有效(悬念式/劲爆式/仇恨式);行文"短平快",写小说不是说明文。
|
|
36
|
+
- 可复用的一种开篇节奏:**第一章建立冲突并留后续问题 → 第二章让核心能力/设定影响行动 → 第三章完成第一次阶段性兑现**;具体次序服务于本稿题材与因果。
|
|
37
|
+
- 三幕式可以作为结构草图:开端(危机/目标)→发展(行动/代价)→转折(新信息/选择),不要求每稿套“金手指/背叛”等桥段。
|
|
38
|
+
- 历史经验帖常提醒检查设定、世界观、环境和人物关系是否挤占事件;倒叙、群像或配角戏份本身不是禁写项,重点是读者能否跟住主线。
|
|
39
|
+
- 部分经验帖推荐用对话开场(悬念式/劲爆式/仇恨式);可检查开场是否尽快进入人物行动,不能据此规定所有题材都用对话。
|
|
31
40
|
|
|
32
41
|
## 二、爽点、期待感与钩子
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
- **期待感三源**(掌握一种就够用):① 信息差(读者知道、主角/对手不知道);
|
|
42
|
+
- 建议复查每章是否有可感知的变化、情绪或兑现;“三章一小爆,十章一大爆”是作者口诀,不能替代逐章判断。
|
|
43
|
+
- 部分作者强调及时兑现:可检查目标是否持续推进、阶段结果是否可见,不以等级或固定章节位置判断有效性;"定个目标 → 完成它"可作构思起点。
|
|
44
|
+
- 欲扬先抑是可选结构:打压应有情境动机,反击应有铺垫;反派心理变化可作为效果观察点,不要求每个故事都用打脸。
|
|
45
|
+
- **三类期待构造方式**(构思工具,不是充分条件):① 信息差(读者知道、主角/对手不知道);
|
|
38
46
|
② 目标预告("三年之约"式,读者等着看那一幕);③ 悬念伏笔(小伏笔=断章钩子;大伏笔有伏必应)。
|
|
39
|
-
-
|
|
40
|
-
-
|
|
47
|
+
- 建议复查章末是否留下可兑现的下一步问题:断章、说话说一半、挖坑或反转预告均可;“章尾小爽点更管用”只是部分作者经验,需结合读者反馈验证。
|
|
48
|
+
- 伏笔不以数量取胜:记录前文铺设、当前承诺和后续回收;神秘人、陷阱或关系反转只有在符合因果并服务本稿时才加入。
|
|
41
49
|
|
|
42
50
|
## 三、主线与节奏
|
|
43
51
|
- 一句话主线 + 主角目标细化;目标不细化 = 过程流水账。
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
52
|
+
- 复查段落是否推动目标、关系、信息或情绪;若没有可辨认的作用,再考虑删改。休整章节也应有清楚的阅读功能。
|
|
53
|
+
- 一种连载经验强调持续制造新局面;可用"起因→行动→阻碍→转折→结果"检查因果,但不要求主角永远处于危机。
|
|
54
|
+
- 小剧情单元可用目标→行动→阻碍→结果检查;大剧情链可用起因→发展→转折→高潮→结局规划。
|
|
47
55
|
- 同一件事不重复叙述;背景设定拆散夹在剧情里带出。
|
|
48
56
|
- 分清剧情流/感情流,笔墨按类型分配。
|
|
49
57
|
|
|
50
|
-
##
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
58
|
+
## 四、人物(经验复查维度)
|
|
59
|
+
- 多篇拒稿复盘关注主角是否作出选择并推动事件;检查“目标 → 行动 → 后果”,但不把单一人物类型写成通用过稿红线。
|
|
60
|
+
- 部分男频经验偏好果断与强反馈;是否适用取决于题材/人设,避免把复杂人物统一改成“杀伐果断”模板。
|
|
61
|
+
- 对手和配角应有可理解的动机与行动;他们不必都强势或聪明,关键是冲突因果成立。
|
|
54
62
|
- 人设四要素:基础设定、个性化标签(口头禅/小习惯)、反差萌、人物弧光。
|
|
55
63
|
- **人设不崩是底线**;人设变化需要铺垫。
|
|
56
64
|
|
|
57
65
|
## 五、视角与文笔
|
|
58
|
-
-
|
|
66
|
+
- 开篇视角应让读者容易定位人物、信息来源和情绪距离;第三人称全知等视角不是禁项,需看执行是否清楚。
|
|
59
67
|
例:×"昏暗的房间里,一个男人把南笙按在沙发上" → √"好痒……南笙迷迷糊糊苏醒,感觉腿上一阵瘙痒,
|
|
60
68
|
随后一只粗糙的大手便摸上了她的大腿"。
|
|
61
69
|
- 开篇即入主角视角,不用旁白自我介绍;读者未对主角产生好感前,不铺大篇幅受伤/惨状。
|
|
@@ -64,7 +72,7 @@
|
|
|
64
72
|
- 段落短,适配手机阅读;少错别字病句。
|
|
65
73
|
|
|
66
74
|
### 编辑高频驳回话术拆解(实测复盘,动笔/改稿时逐条对照)
|
|
67
|
-
|
|
75
|
+
以下三条是所汇编材料中反复出现的"文笔类"反馈,可转成自查问题;不代表所有频道或拒稿的完整原因:
|
|
68
76
|
|
|
69
77
|
- **"风格不够统一"**:叙述语域/句式长短/人称距离在一章内忽高忽低(半文半白 + 网络梗 + 书面长句混用),
|
|
70
78
|
或切场景/切视角时文风跟着变。**修法**:全篇定一个文风锚(如"短句 + 白描 + 口语但不过火"),
|
|
@@ -75,7 +83,7 @@
|
|
|
75
83
|
- **"细节刻画力度稍弱"**:核心冲突/情绪爆点被一句话带过(该"放慢给特写"的地方按了快进),
|
|
76
84
|
打压—反击缺生理与环境反应,爽点"打不满"。**修法**:每个爽点/反转标记为"特写镜头",
|
|
77
85
|
用五感 + 微动作 + 环境反馈把这几秒写"厚"(心跳、指节、周围人的呼吸与目光),
|
|
78
|
-
|
|
86
|
+
铺垫在前、放大在中、余波在后;场景篇幅按叙事重要性调整,不把"特写"固定套给每个爽点。
|
|
79
87
|
|
|
80
88
|
## 六、题材、书名、简介
|
|
81
89
|
- 题材 = 个人兴趣 × 市场需求的最大公约数;风格要匹配题材。
|
|
@@ -83,23 +91,26 @@
|
|
|
83
91
|
- 书名带情绪、有看点、有关键词;忌文艺含蓄无信息量。
|
|
84
92
|
- 简介三要素:背景(身份+处境)+ 冲突(核心矛盾+情绪爆发)+ 悬念(埋钩子)。
|
|
85
93
|
|
|
86
|
-
##
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- [ ]
|
|
96
|
-
- [ ]
|
|
97
|
-
- [ ]
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- [ ]
|
|
94
|
+
## 七、投稿与签约(历史流程,提交前必须回后台核实)
|
|
95
|
+
- 经验材料常见“2 万字/3 万字/3-5 个工作日”等节点,只能作为项目排期提示;当前频道的字数、审核时长和可投稿状态以作家助手页面为准。
|
|
96
|
+
- 一稿一投、跟进节奏和签约模式是作者经验,不是过签条件;拒稿后保留原文、版本、编辑原话和修改范围,再决定重修、重立项或换稿。
|
|
97
|
+
- 红线需按当前公告、合同和频道规则核对:涉政涉军涉敏低俗、抄袭洗稿、版权/独家义务等不能靠内部自检替代平台判断。
|
|
98
|
+
|
|
99
|
+
## 八、投稿前质量与证据清单(逐项过;非签约保证)
|
|
100
|
+
先给每个结论标注“当前规则 / 多源经验 / 待验证”,并记录实际读过的章节范围;没有证据的项目不标“通过”。
|
|
101
|
+
|
|
102
|
+
**开篇(第一章;字数位置仅作观察点)**
|
|
103
|
+
- [ ] 观察主角是否尽早登场、读者能否辨认主角 [ ] 观察异常/欲望/冲突何时出现
|
|
104
|
+
- [ ] 观察困境是否具体、核心吸引点是否兑现 [ ] 复查具名人物是否过密(“五人”仅为阅读负担参考)
|
|
105
|
+
- [ ] 复查设定/环境/背景是否挤占事件 [ ] 主角视角是否清楚、旁白距离是否符合本稿选择
|
|
106
|
+
- [ ] 开场与题材承诺是否匹配 [ ] 章末是否有可兑现的后续问题
|
|
107
|
+
**前三章(随题材调整,不强制套章纲)**
|
|
108
|
+
- [ ] 核心设定或能力是否改变人物行动 [ ] 是否完成一个有因果的阶段性兑现
|
|
109
|
+
- [ ] 是否有局面变化 [ ] 主线目标/障碍/下一步是否可复述
|
|
101
110
|
**全书通用**
|
|
102
|
-
- [ ]
|
|
103
|
-
- [ ]
|
|
104
|
-
- [ ]
|
|
105
|
-
- [ ]
|
|
111
|
+
- [ ] 主角是否作出改变局面的选择 [ ] 本章事件是否推进目标或关系
|
|
112
|
+
- [ ] 打压/阻碍有动机、反击/解决有铺垫 [ ] 对手与配角有各自目标
|
|
113
|
+
- [ ] 人物变化有铺垫 [ ] 重复信息和与主线无关内容是否影响阅读
|
|
114
|
+
- [ ] 情绪表达是否具体且适合本稿文风 [ ] 段落长度是否适配目标阅读场景
|
|
115
|
+
- [ ] 每个故事单元都能指出事件、选择、后果、兑现和后续问题
|
|
116
|
+
- [ ] 文风锚、因果链、连续性和内容安全分别复核,未把字数/格式当文学评分
|