@oh-story/dsh 0.1.13 → 0.1.15
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/README.md +7 -7
- package/lib/client.js +932 -631
- package/lib/client.js.map +4 -4
- package/lib/index.js +1 -0
- package/lib/index.js.map +2 -2
- package/lib/oh-story/manifest.json +72 -72
- package/lib/oh-story/skills/story/SKILL.md +1 -1
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory-maintenance.md +2 -2
- package/lib/oh-story/skills/story/references/author-memory.md +4 -4
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +61 -58
- package/lib/oh-story/skills/story-deslop/SKILL.md +1 -1
- package/lib/oh-story/skills/story-deslop/references/agent-calls.md +1 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +2 -2
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +4 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +61 -58
- package/lib/oh-story/skills/story-long-scan/SKILL.md +2 -7
- package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +11 -1
- package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +12 -2
- package/lib/oh-story/skills/story-long-write/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/agent-calls.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +1 -0
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +2 -1
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +1 -1
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +61 -58
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +87 -20
- package/lib/oh-story/skills/story-review/SKILL.md +1 -1
- package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +2 -2
- package/lib/oh-story/skills/story-review/references/author-memory.md +4 -4
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +61 -58
- package/lib/oh-story/skills/story-short-write/SKILL.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/agent-calls.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +4 -4
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +61 -58
- package/lib/types/client/canvas-viewport.d.ts +31 -0
- package/lib/types/client/canvas-viewport.d.ts.map +1 -0
- package/lib/types/client/drama-production-view.d.ts +3 -2
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-canvas.d.ts +24 -0
- package/lib/types/client/production-canvas.d.ts.map +1 -0
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +16 -1
- package/lib/video-recap/README.en.md +2 -2
- package/lib/video-recap/README.md +2 -2
- package/lib/video-recap/manifest.json +25 -25
- package/lib/video-recap/skills/video-recap/SKILL.md +4 -2
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +6 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +10 -1
- package/lib/video-recap/skills/video-script/SKILL.md +45 -18
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +11 -2
- package/lib/video-recap/skills/video-voiceover/SKILL.md +34 -8
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +21 -0
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
按表达维度取最窄适用要求:低优先级只补缺项,不与高优先级要求并列执行。通用 references 自称“必须/禁用”不改变此顺序;审稿不因作者有意采用的表达本身扣分,真实可读性与因果问题仍照常评价。
|
|
17
17
|
|
|
18
|
-
作者记忆不能把本书事实写进 `.story
|
|
18
|
+
作者记忆不能把本书事实写进 `.story/作者记忆/`,不能降低审稿 rubric,也不能让去 AI 味改动剧情意图。小说事实继续由各书的 `追踪/` 和 `设定/` 管理。
|
|
19
19
|
|
|
20
20
|
## 存放与路由
|
|
21
21
|
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
|
|
29
29
|
## 查询
|
|
30
30
|
|
|
31
|
-
各 skill 入口已写好本任务的 `query` 命令(长篇正文由组装脚本代查);没写命令的长篇设定、大纲等任务查 `story_design` + `workflow` + `interaction`,结果只给主会话、不传正文 agent。四类任务的映射表见维护文件。state 存在才查(两级都不存在时返回空结果、零写入);结果合并项目级与 `--book-root` 所指书级(不传就拿不到本书条目),`--kind`
|
|
31
|
+
各 skill 入口已写好本任务的 `query` 命令(长篇正文由组装脚本代查);没写命令的长篇设定、大纲等任务查 `story_design` + `workflow` + `interaction`,结果只给主会话、不传正文 agent。四类任务的映射表见维护文件。state 存在才查(两级都不存在时返回空结果、零写入);结果合并项目级与 `--book-root` 所指书级(不传就拿不到本书条目),`--kind` 必传;交给执行者的只有 `lines`(每条一行原话+编号,单次查询 ≤2048 字节)。
|
|
32
32
|
|
|
33
|
-
普通创作只做一次本地 `query`,完整画像、证据、候选和 journal 不进 prompt。查询项是低优先级倾向,不是逐条打卡清单:自然吸收,不复述画像、不刻意提高词面命中率,不为命中牺牲连贯、节奏、字数或本书既定笔调。**`omitted_ids`
|
|
33
|
+
普通创作只做一次本地 `query`,完整画像、证据、候选和 journal 不进 prompt。查询项是低优先级倾向,不是逐条打卡清单:自然吸收,不复述画像、不刻意提高词面命中率,不为命中牺牲连贯、节奏、字数或本书既定笔调。**`omitted_ids` 非空=记忆超编**,不是「没有更多了」:照 `omitted_summaries` 转告作者并建议「整理作者记忆」,不得改读完整画像规避预算。待确认项不进 prompt,也不为确认它们中断任务;只在作者主动查看、候选积累到适合回顾的节点,或新偏好与 active 条目冲突时集中呈现。
|
|
34
34
|
|
|
35
35
|
## 记不记、记成什么
|
|
36
36
|
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
回复就两行纯文本,不加代码块或引用格式:第一行用一句人话说记住了什么、管哪本书或哪类场合,如「记住了:《{书名}》的对话一律用「」,以后写这本书都照这个来;想改随时说。」;第二行是机器回执作凭证,如「技术备注:Author Memory Receipt: r1 · BP001」。
|
|
67
67
|
|
|
68
68
|
- 确认、替换、忘掉同理:「好,这条生效了:……」「换成了:……,原来的「……」不再用」「忘掉了:……」。只进待确认时说「这条先记在待确认里,你说"确认"才生效」;有冲突时用原话说明跟哪条旧习惯冲突。
|
|
69
|
-
- `warnings` / `omitted_ids`
|
|
69
|
+
- `warnings` / `omitted_ids` 不原样贴:说「你的习惯攒得有点多,写正文时这几条会带不上:「……」」(`warnings` 是最坏估算,改说「可能带不上」),并建议说「整理作者记忆」。不提字节、prompt、kind、scope;编号只能跟着原话出现。
|
|
70
70
|
|
|
71
71
|
## 运行工具
|
|
72
72
|
|
|
@@ -34,21 +34,22 @@ PENDING_MAX_BYTES = 12288
|
|
|
34
34
|
JOURNAL_MAX_BYTES = 24576
|
|
35
35
|
QUERY_MAX_BYTES = 2048
|
|
36
36
|
ASSERTION_MAX_BYTES = 120 # 新建条目的断言限一句话;解释进 reason(不进 query 载荷)。
|
|
37
|
-
OMITTED_IDS_MAX = 20 # omitted_ids 封顶,omitted 保留真实总数——漏项列表不许把载荷本身挤炸。
|
|
38
37
|
LEGACY_ASSERTION_MAX_BYTES = 768 # 存量条目的读取上限;强化老条目不受新上限约束。
|
|
39
38
|
|
|
40
|
-
# query
|
|
41
|
-
# 是它在 prompt
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
39
|
+
# query 输出里的 lines(每条一行「- 断言(编号)」)是交给执行 agent 的注入
|
|
40
|
+
# 载荷,QUERY_MAX_BYTES 是它在 prompt 里的注意力预算,不该放大;量的是执行者
|
|
41
|
+
# 真正读到的这几行,不是 JSON 外壳(编号、kind、scope 只给主会话和脚本看)。
|
|
42
|
+
# 防「作者以为载入了、实际被静默挤掉」靠三层:①新建条目的断言限
|
|
43
|
+
# ASSERTION_MAX_BYTES,从源头短(强化已有条目不受限,否则存量长断言再也无法
|
|
44
|
+
# 被确认,只会派生重复条目);②写入端按下列任务组合(与
|
|
45
|
+
# references/author-memory-maintenance.md 的映射表同包跟版)估算最坏查询情形——
|
|
46
|
+
# 全局条目+各 scope 维度上最重的单一切片(一次查询只带一个 book/genre/workflow,
|
|
47
|
+
# 不同书的条目不会同现;切片按 casefold 归并,与 same_scope_value 同一口径,
|
|
48
|
+
# 轻重按渲染后的行字节算,与真实载荷同一把尺),装不下时在返回的 warnings 里
|
|
49
|
+
# 点名将被略过的条目、指向「整理作者记忆」,写入本身永不因注入预算失败;
|
|
50
|
+
# ③查询按 重要度→本书例外→最近更新 排序装填,被略过的恒是重要度较低的条目,
|
|
51
|
+
# 漏下的 ID 按同一优先级全数报进 omitted_ids,首句报进 omitted_summaries,
|
|
52
|
+
# 供主会话用原话告诉作者哪几条没带上。
|
|
52
53
|
QUERY_COMBOS: dict[str, tuple[str, ...]] = {
|
|
53
54
|
"正文初稿/续写": ("prose_style", "story_design"),
|
|
54
55
|
"去AI味/改写": ("prose_style",),
|
|
@@ -1086,12 +1087,19 @@ def summarize_assertion(assertion: str, *, limit: int = 14) -> str:
|
|
|
1086
1087
|
|
|
1087
1088
|
|
|
1088
1089
|
def compact_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
1089
|
-
"""query
|
|
1090
|
+
"""query 的 items 只带这四个字段,给主会话和脚本按 kind/scope 判断用;
|
|
1091
|
+
reason、evidence 等一律不出 store。"""
|
|
1090
1092
|
return {"id": item["id"], "kind": item["kind"], "scope": item["scope"], "assertion": item["assertion"]}
|
|
1091
1093
|
|
|
1092
1094
|
|
|
1093
|
-
def
|
|
1094
|
-
|
|
1095
|
+
def render_line(item: dict[str, Any]) -> str:
|
|
1096
|
+
"""执行者读到的一行:断言原文+编号。注入预算量的就是这几行。"""
|
|
1097
|
+
return f"- {item['assertion']}({item['id']})"
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def line_bytes(item: dict[str, Any]) -> int:
|
|
1101
|
+
"""一行在注入载荷里的占位,含行尾换行——估算与真实装填只用这一把尺。"""
|
|
1102
|
+
return len((render_line(item) + "\n").encode("utf-8"))
|
|
1095
1103
|
|
|
1096
1104
|
|
|
1097
1105
|
SCOPE_RANK = {"book": 0, "genre": 1, "workflow": 2, "global": 3}
|
|
@@ -1121,48 +1129,38 @@ def fit_items(
|
|
|
1121
1129
|
*,
|
|
1122
1130
|
extra: dict[str, Any] | None = None,
|
|
1123
1131
|
) -> tuple[dict[str, Any], list[str]]:
|
|
1124
|
-
"""
|
|
1125
|
-
|
|
1126
|
-
OMITTED_IDS_MAX 条,omitted 保留真实总数)。返回 (结果文档, 全部漏下 ID)。
|
|
1132
|
+
"""按渲染后的行把条目装进 QUERY_MAX_BYTES:装不下的跳过而不中断(一条长的
|
|
1133
|
+
不挡后面的短条)。返回 (结果文档, 全部漏下 ID)。
|
|
1127
1134
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1135
|
+
只有 lines 计入预算;items、漏项清单和 `extra`(book_revision 等)是给主会话
|
|
1136
|
+
和脚本看的,不进执行者 prompt,所以漏项全数列出、不封顶,并按候选优先级
|
|
1137
|
+
排序——排在前面的就是作者最该知道没带上的那几条。
|
|
1131
1138
|
"""
|
|
1139
|
+
kept: list[dict[str, Any]] = []
|
|
1140
|
+
dropped: list[dict[str, Any]] = []
|
|
1141
|
+
used = 0
|
|
1142
|
+
for item in sorted_items:
|
|
1143
|
+
size = line_bytes(item)
|
|
1144
|
+
if used + size > QUERY_MAX_BYTES:
|
|
1145
|
+
dropped.append(item)
|
|
1146
|
+
continue
|
|
1147
|
+
kept.append(item)
|
|
1148
|
+
used += size
|
|
1132
1149
|
result: dict[str, Any] = {
|
|
1133
1150
|
"ok": True,
|
|
1134
1151
|
"command": "query",
|
|
1135
1152
|
"initialized": True,
|
|
1136
1153
|
"revision": revision,
|
|
1137
|
-
"items": [],
|
|
1138
|
-
"
|
|
1139
|
-
"
|
|
1154
|
+
"items": [compact_item(item) for item in kept],
|
|
1155
|
+
"lines": [render_line(item) for item in kept],
|
|
1156
|
+
"omitted": len(dropped),
|
|
1157
|
+
"omitted_ids": [item["id"] for item in dropped],
|
|
1158
|
+
"omitted_summaries": {item["id"]: summarize_assertion(item["assertion"]) for item in dropped},
|
|
1159
|
+
# 组装脚本按题材×流程查好几次再合并,合并后只能按重要度重排漏项,所以把它带出来。
|
|
1160
|
+
"omitted_importance": {item["id"]: item["importance"] for item in dropped},
|
|
1140
1161
|
}
|
|
1141
1162
|
result.update(extra or {})
|
|
1142
|
-
|
|
1143
|
-
kept: list[dict[str, Any]] = []
|
|
1144
|
-
dropped: set[str] = set()
|
|
1145
|
-
|
|
1146
|
-
def ordered_omitted() -> list[str]:
|
|
1147
|
-
return sorted(dropped, key=order.__getitem__)
|
|
1148
|
-
|
|
1149
|
-
def envelope_bytes() -> int:
|
|
1150
|
-
omitted = ordered_omitted()
|
|
1151
|
-
result["items"] = [compact_item(item) for item in kept]
|
|
1152
|
-
result["omitted"] = len(omitted)
|
|
1153
|
-
result["omitted_ids"] = omitted[:OMITTED_IDS_MAX]
|
|
1154
|
-
return len((json.dumps(result, ensure_ascii=False, sort_keys=True) + "\n").encode("utf-8"))
|
|
1155
|
-
|
|
1156
|
-
for item in sorted_items:
|
|
1157
|
-
kept.append(item)
|
|
1158
|
-
if envelope_bytes() > QUERY_MAX_BYTES:
|
|
1159
|
-
kept.pop()
|
|
1160
|
-
dropped.add(item["id"])
|
|
1161
|
-
# omitted 计数落定后包可能恰好贴边超出一两个字节,回吐条目直到装下。
|
|
1162
|
-
while kept and envelope_bytes() > QUERY_MAX_BYTES:
|
|
1163
|
-
dropped.add(kept.pop()["id"])
|
|
1164
|
-
envelope_bytes()
|
|
1165
|
-
return result, ordered_omitted()
|
|
1163
|
+
return result, result["omitted_ids"]
|
|
1166
1164
|
|
|
1167
1165
|
|
|
1168
1166
|
def build_query_result(
|
|
@@ -1170,8 +1168,8 @@ def build_query_result(
|
|
|
1170
1168
|
project_state: dict[str, Any] | None,
|
|
1171
1169
|
book_state: dict[str, Any] | None,
|
|
1172
1170
|
) -> tuple[dict[str, Any], list[str]]:
|
|
1173
|
-
"""
|
|
1174
|
-
book_revision
|
|
1171
|
+
"""真实查询与写入端估算共用的唯一装填入口:候选已按优先级排好,这里补上
|
|
1172
|
+
book_revision 等附加字段再装填。两处装填口径一旦分叉就是假阴性——回执
|
|
1175
1173
|
说没事、query 照样丢条。"""
|
|
1176
1174
|
extra: dict[str, Any] = {}
|
|
1177
1175
|
if book_state is not None:
|
|
@@ -1208,12 +1206,13 @@ def merged_query(
|
|
|
1208
1206
|
|
|
1209
1207
|
|
|
1210
1208
|
def slice_weight(items: list[dict[str, Any]]) -> int:
|
|
1211
|
-
"""
|
|
1209
|
+
"""切片在真实载荷里的占位:按渲染后的行(含换行)计。
|
|
1212
1210
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1211
|
+
必须与 fit_items 同一把尺。按 JSON 字节挑会挑错切片——scope 取值长、断言
|
|
1212
|
+
短的切片 JSON 更重,渲染出的行却更轻,于是估算判「装得下」而真实查询溢出
|
|
1213
|
+
(warnings 假阴性)。
|
|
1215
1214
|
"""
|
|
1216
|
-
return sum(
|
|
1215
|
+
return sum(line_bytes(item) for item in items)
|
|
1217
1216
|
|
|
1218
1217
|
|
|
1219
1218
|
def active_of_kinds(state: dict[str, Any] | None, kinds: tuple[str, ...]) -> list[dict[str, Any]]:
|
|
@@ -1419,16 +1418,20 @@ def command_query(
|
|
|
1419
1418
|
project_state = load_state(project_store(workspace))
|
|
1420
1419
|
book_state = load_state(book_store(workspace, book_root, book)) if book_root is not None else None
|
|
1421
1420
|
if project_state is None and book_state is None:
|
|
1422
|
-
return {"ok": True, "command": "query", "initialized": False, "revision": 0, "items": [], "
|
|
1421
|
+
return {"ok": True, "command": "query", "initialized": False, "revision": 0, "items": [], "lines": [],
|
|
1422
|
+
"omitted": 0, "omitted_ids": [], "omitted_summaries": {}, "omitted_importance": {}}
|
|
1423
1423
|
requested_scopes = {
|
|
1424
1424
|
"genre": optional_text(genre, "query.genre", max_bytes=180),
|
|
1425
1425
|
"workflow": optional_text(workflow, "query.workflow", max_bytes=180),
|
|
1426
1426
|
}
|
|
1427
1427
|
# 装不下的**跳过而不中断**(一条长的不挡后面的短条),漏下的 ID 报进
|
|
1428
1428
|
# omitted_ids:非空=记忆超编该整理了,不是「没有更多了」。写入端的
|
|
1429
|
-
# warnings 与这里共用 build_query_result
|
|
1429
|
+
# warnings 与这里共用 build_query_result 同一把尺,估算即实况。
|
|
1430
1430
|
result, _ = merged_query(project_state, book_state, set(kinds), requested_scopes)
|
|
1431
|
-
require(
|
|
1431
|
+
require(
|
|
1432
|
+
sum(len((line + "\n").encode("utf-8")) for line in result["lines"]) <= QUERY_MAX_BYTES,
|
|
1433
|
+
"query lines exceed their fixed byte budget",
|
|
1434
|
+
)
|
|
1432
1435
|
return result
|
|
1433
1436
|
|
|
1434
1437
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: story-long-scan
|
|
3
3
|
version: 1.0.0
|
|
4
|
-
description: "
|
|
4
|
+
description: "长篇网文扫榜。分析起点、番茄、晋江、七猫等平台排行榜与新书数据,提炼市场趋势与热门题材。触发方式:/story-long-scan、/长篇扫榜、「长篇什么火」「起点排行」「扫七猫新书」。"
|
|
5
5
|
metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudecode"}}
|
|
6
6
|
---
|
|
7
7
|
# story-long-scan:长篇网文扫榜
|
|
@@ -178,12 +178,7 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
|
|
|
178
178
|
| [references/publishing-guide.md](references/publishing-guide.md) | 平台适配+推荐机制校验+数据指标+简介设计 |
|
|
179
179
|
| 五份平台参考(链接见「脚本采集模式」第 1 步) | 「确定数据来源」:只读所选平台那份——榜单网址、命令、字段、故障排查、平台分析维度 |
|
|
180
180
|
| [scripts/aggregate-rank.js](scripts/aggregate-rank.js) | 把输出目录里的原始榜单聚合成短表(`--out` 落盘,`--sample` 抽原始条目,`--json` 机读),主会话只读它 |
|
|
181
|
-
| [scripts/cdp-utils.js](scripts/cdp-utils.js) | CDP
|
|
182
|
-
| [scripts/fanqie-rank-scraper.js](scripts/fanqie-rank-scraper.js) | 番茄榜单采集,用法见番茄参考 |
|
|
183
|
-
| [scripts/qidian-rank-scraper.js](scripts/qidian-rank-scraper.js) | 起点榜单采集,用法见起点参考 |
|
|
184
|
-
| [scripts/qimao-rank-scraper.js](scripts/qimao-rank-scraper.js) | 七猫榜单采集,用法见七猫参考 |
|
|
185
|
-
| [scripts/jjwxc-rank-scraper.js](scripts/jjwxc-rank-scraper.js) | 晋江榜单采集,用法见晋江参考 |
|
|
186
|
-
| [scripts/ciweimao-rank-scraper.js](scripts/ciweimao-rank-scraper.js) | 刺猬猫榜单采集,用法见刺猬猫参考 |
|
|
181
|
+
| [scripts/cdp-utils.js](scripts/cdp-utils.js) 与各平台采集脚本 | CDP 公共工具与各平台采集脚本(`{平台}-rank-scraper.js`),命令见所选平台参考 |
|
|
187
182
|
|
|
188
183
|
---
|
|
189
184
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 起点:采集与分析
|
|
2
2
|
|
|
3
|
-
作者选了起点才读本文。默认不需要 Chrome:脚本先读 `https://m.qidian.com` 移动端 SSR pageContext,移动端不可用时才回退 CDP/PC 页面,文件头 `抓取方式` 标 `mobile-ssr` 或 `cdp-pc
|
|
3
|
+
作者选了起点才读本文。默认不需要 Chrome:脚本先读 `https://m.qidian.com` 移动端 SSR pageContext,移动端不可用时才回退 CDP/PC 页面,文件头 `抓取方式` 标 `mobile-ssr` 或 `cdp-pc`。书库新书除外:只能走 Chrome,先 `/browser-cdp`,开之前告诉作者。
|
|
4
4
|
|
|
5
5
|
## 榜单与命令
|
|
6
6
|
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
|
|
22
22
|
默认组合:新人签约新书榜 + 签约作者新书榜 + 月票榜 + 畅销榜。文件名 `起点{榜单}_{YYYYMMDD}.md`。
|
|
23
23
|
|
|
24
|
+
### 书库新书
|
|
25
|
+
|
|
26
|
+
作者说「新书」「最近冒头」「什么在起量」时再加,不进默认组合(要开 Chrome)。全部作品筛选页 qidian.com/all/action0-size1-update1/:男生、连载、30 万字以下、三日内更新,按人气排,每页 20 本。能补上已下新书榜、还在更新的新书。
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node scripts/qidian-rank-scraper.js --type library --pages 3 --outdir {输出目录} # 默认 3 页,最多 10,不进 --type all
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
所有页写进一份 `起点男频书库人气新书_{YYYYMMDD}.md`,名次接着排,就是人气排名;只收 30 万字以下,会拉低字数中位。字数从加密字体解出;签约、收费从移动端作品页补,补不到写 `[待补]`;总推荐单列为「新书总推荐」,不进热度口径。跟作者说「起点最近三天还在更新的 30 万字以内新书,按人气取前 3 页,要开浏览器」。
|
|
33
|
+
|
|
24
34
|
## 字段
|
|
25
35
|
|
|
26
36
|
排名、书名、作者、题材(主类·子类;聚合时主类作题材、子类进标签)、状态、签约、收费模式、字数、总推荐(聚合的热度口径)、标签/最新更新/简介(详情页)、作品页链接。额外必填:题材、字数、总推荐,缺的写 `[待补]`。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 七猫:采集与分析
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
作者选了七猫才读本文。排行榜需先用 `/browser-cdp` 启动 Chrome;页面无明显反爬,脚本滚动加载,男生榜/女生榜 tab 切换(失败重试)。下面的书库新书不用 Chrome。
|
|
4
4
|
|
|
5
5
|
## 榜单与命令
|
|
6
6
|
|
|
@@ -13,9 +13,19 @@ node scripts/qimao-rank-scraper.js --channel all --type all --outdir {输出目
|
|
|
13
13
|
|
|
14
14
|
大热榜用 `--period day|month|all` 选日榜、月榜或两者(默认 day),周期进文件头与文件名;其他榜不分周期。默认组合:男频大热榜日榜 + 女频大热榜日榜。
|
|
15
15
|
|
|
16
|
+
### 书库新书
|
|
17
|
+
|
|
18
|
+
作者说「新书」「最近冒头」「什么在起量」时再加,不进默认组合。书库筛选页 qimao.com/shuku/a-a-a-1-1-a-0-click-{页码}/:全部频道、30 万字以下、3 天内更新、连载中,按点击量排,每页 15 本。
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
node scripts/qimao-rank-scraper.js --source library --pages 3 --outdir {输出目录} # 前 3 页(默认 3,最多 10)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
直接读网页,不用 Chrome。所有页写进一份 `七猫全站书库点击新书_{YYYYMMDD}.md`,名次接着排,就是点击排名,只收 30 万字以下,会拉低字数中位,篇幅看排行榜;书库页只给子分类,脚本按分类页补出主类,和排行榜的题材对得上。页面没有热度数字,七猫的热度只来自排行榜,这份没有热度属正常,不用重采。Chrome 起不来时可以只采这份,结论只看题材分布、字数和标签,没有热度和多榜重合。跟作者说「七猫最近三天还在更新的 30 万字以内新书,按点击取前 3 页」。
|
|
25
|
+
|
|
16
26
|
## 字段
|
|
17
27
|
|
|
18
|
-
|
|
28
|
+
排名、书名、作者、题材、分类标签(聚合时进标签)、状态、字数(万字)、热度(核心指标,聚合的热度口径;书库新书没有)、最新更新、作品页链接、简介。文件头带 `数据质量`、`有效条目`、`问题摘要`、链接与热度命中数;热度缺失写 `[待补]`。
|
|
19
29
|
|
|
20
30
|
## 分析维度
|
|
21
31
|
|
|
@@ -31,7 +31,7 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
|
|
|
31
31
|
|
|
32
32
|
## 核心方法
|
|
33
33
|
|
|
34
|
-
先抓情绪,再用验证过的模式可靠交付,灵感只做素材:每个场景服务一个说得清的目标情绪;从对标里找验证过的剧情模式,把对标角色当功能位,用本书的角色和素材填;写每章只读「不知道就会写错」的状态、伏笔与设定。契约与推进决策先过 `references/reader-contract-and-progression.md`「契约四问」。作者记忆:写正文时组装脚本已代查注入;其他任务直接跑 `{PYTHON} {skill 根}/scripts/author_memory_commit.py query --workspace {放 .active-book 的那层} --book-root {书目录} --kind …`(规划查 story_design、workflow、interaction,改稿查 prose_style、story_design
|
|
34
|
+
先抓情绪,再用验证过的模式可靠交付,灵感只做素材:每个场景服务一个说得清的目标情绪;从对标里找验证过的剧情模式,把对标角色当功能位,用本书的角色和素材填;写每章只读「不知道就会写错」的状态、伏笔与设定。契约与推进决策先过 `references/reader-contract-and-progression.md`「契约四问」。作者记忆:写正文时组装脚本已代查注入;其他任务直接跑 `{PYTHON} {skill 根}/scripts/author_memory_commit.py query --workspace {放 .active-book 的那层} --book-root {书目录} --kind …`(规划查 story_design、workflow、interaction,改稿查 prose_style、story_design;无记忆返回空),执行者只拿输出的 `lines`,当前请求与本书文风优先;作者说出要长期记住的偏好时才读 [references/author-memory.md](references/author-memory.md) 按它 `record` 写入、回传回执。
|
|
35
35
|
|
|
36
36
|
**本章技法(每章至多读一份)**:按细纲主功能选——打脸/逆袭(爽感释放)`style-combat-face.md`;身份/认知/立场反转(震撼+痛快)`long-reversal.md`;感情拉扯(意难平)`emotional-methods.md`,自己写时加 `emotion-on-page.md`;悬疑/惊悚/异常线索(紧张+好奇)`long-suspense.md`;日常装逼(期待感)`long-chapter-hooks.md`。都不对应就不读,按 writing-craft 写。交给写手时只在 prompt「本章技法」填类别,由写手整份读;主会话自己写正文时只读该文件的「决策路由」和它指向的那一节(先看标题定位,不整读),日更一批只读一份,按本批最需要的那章选,其余章按 writing-craft 写。作者先说情绪没说题材时按括号反查,再从 `long-genre-catalog.md` 找细分方向。
|
|
37
37
|
|
|
@@ -28,7 +28,7 @@ Prompt:`项目目录:{dir}\n检查范围:{本次写作的章节}\n检查
|
|
|
28
28
|
|
|
29
29
|
## narrative-writer:审查+去AI味
|
|
30
30
|
|
|
31
|
-
Prompt:`项目目录:{dir}\n任务描述:审查+去AI味\n检查分工:你负责语义去味及原定自检;最终文件扫描由主会话执行\n检查范围:{本次写作的章节}\n文风路径:{设定/文风.md 全文路径}\nstyle_resolution:{与写作一致的裁决}\n作者偏好:{本章 query
|
|
31
|
+
Prompt:`项目目录:{dir}\n任务描述:审查+去AI味\n检查分工:你负责语义去味及原定自检;最终文件扫描由主会话执行\n检查范围:{本次写作的章节}\n文风路径:{设定/文风.md 全文路径}\nstyle_resolution:{与写作一致的裁决}\n作者偏好:{本章 query 输出的 lines,无写「无」}\n触发本次审查的检测结果:{chapter check 里 blocking 与语义类 advisory 的行号和类别,原样粘贴}\n所选 Gate:{按检测类别选,如 B 句式、G 解释腔;类别不明写 A-G}\n删除优先:每条 AI 味项先判能否删除,删后不丢伏笔/钩子/角色/情节/必要信息的直接删,会丢才润色\n按你的 7 Gate 与对话自检执行,台词里的工整否定不因脚本豁免而跳过\n删除测试:按 deslop-gates.md「写法抽查」执行,报告列候选数/删改数/保留理由`
|
|
32
32
|
|
|
33
33
|
## narrative-writer:超字一次净删(compress-once)
|
|
34
34
|
|
|
@@ -36,7 +36,7 @@ Prompt:`项目目录:{dir}\n任务描述:净删压缩\n正文:{正文路
|
|
|
36
36
|
|
|
37
37
|
## narrative-writer:改写一章(大修)
|
|
38
38
|
|
|
39
|
-
Prompt:`项目目录:{dir}\n任务描述:改写已写章节\n正文:{正文路径}(原稿已备份)\n细纲:{细纲路径}\n改动范围:{局部:哪几段或哪个场面,要改成什么/整章:按细纲重写}\n作者本轮要求:{作者原话要点}\n不动的部分:{局部修改时其余段落一字不改}\n衔接:{前一章结尾与后一章开头各一两句}\n文风路径:{设定/文风.md 全文路径}\nstyle_resolution:{与写作一致的裁决}\n作者偏好:{prose_style
|
|
39
|
+
Prompt:`项目目录:{dir}\n任务描述:改写已写章节\n正文:{正文路径}(原稿已备份)\n细纲:{细纲路径}\n改动范围:{局部:哪几段或哪个场面,要改成什么/整章:按细纲重写}\n作者本轮要求:{作者原话要点}\n不动的部分:{局部修改时其余段落一字不改}\n衔接:{前一章结尾与后一章开头各一两句}\n文风路径:{设定/文风.md 全文路径}\nstyle_resolution:{与写作一致的裁决}\n作者偏好:{prose_style 查询的 lines,无写「无」}\n不借改稿新增细纲外的剧情;交付后回:改了哪些段、新增申报(无写 0)`
|
|
40
40
|
|
|
41
41
|
## story-explorer:写前对标召回
|
|
42
42
|
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
|
|
33
33
|
## 注入预算与容量
|
|
34
34
|
|
|
35
|
-
- **写入不因注入预算失败**:`record` / `commit` 照常成功、给回执;工具按上表四类任务组合估算最坏查询情形(全局条目+各 scope
|
|
35
|
+
- **写入不因注入预算失败**:`record` / `commit` 照常成功、给回执;工具按上表四类任务组合估算最坏查询情形(全局条目+各 scope 维度最重的单一切片,切片按大小写无关归并、轻重按执行者真正读到的行算,与真实查询同一把尺),装不进 2048 字节的组合在返回的 `warnings` 里点名将被略过的条目及其断言首句。2048 字节量的是交给执行者的 `lines`(每条一行「- 断言(编号)」,含换行),不是 JSON 外壳:`items` 里的编号、类型、范围只给主会话和脚本判断用,不进执行者 prompt。
|
|
36
36
|
- 写入落盘后另一级 store 读不出来(书目录不存在、`--book` 与书级记录不符等)也照常给回执,`warnings` 注明本次提醒没算上它。写书级条目时「本书+全局」按实际条目精确计算;写项目级条目时只看得到项目级 store,顺手传 `--book-root` 就把当前这本书也算进提醒。
|
|
37
|
-
- 查询按 **重要度 → 本书例外 → 最近更新** 排序装填(同一范围的条目必在同一 store,「最近」按该 store 的修订号比,不跨 store 比较),先丢的恒是重要度较低的条目——`importance` 决定超编时谁留在 prompt 里。装不下的条目跳过而不中断(一条长的不挡后面的短条),漏下的 ID
|
|
37
|
+
- 查询按 **重要度 → 本书例外 → 最近更新** 排序装填(同一范围的条目必在同一 store,「最近」按该 store 的修订号比,不跨 store 比较),先丢的恒是重要度较低的条目——`importance` 决定超编时谁留在 prompt 里。装不下的条目跳过而不中断(一条长的不挡后面的短条),漏下的 ID 按同一优先级全数报进 `omitted_ids`,断言首句报进 `omitted_summaries`、重要度报进 `omitted_importance`,供主会话用原话告诉作者哪几条没带上;长篇写章由组装脚本把这句话拼好写进核对报告,章末汇报照模板原样说。
|
|
38
38
|
- 注入预算之外还有一道硬上限:`作者画像.md` 超过 12288 字节时写入会直接失败并要求先整理。active 条目攒到几十上百条才会碰到(远在注入预算之后),碰到就走「整理作者记忆」;`forget` 这类减量操作在满编时照常可用。
|
|
39
39
|
|
|
40
40
|
## 整理作者记忆
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
按表达维度取最窄适用要求:低优先级只补缺项,不与高优先级要求并列执行。通用 references 自称“必须/禁用”不改变此顺序;审稿不因作者有意采用的表达本身扣分,真实可读性与因果问题仍照常评价。
|
|
17
17
|
|
|
18
|
-
作者记忆不能把本书事实写进 `.story
|
|
18
|
+
作者记忆不能把本书事实写进 `.story/作者记忆/`,不能降低审稿 rubric,也不能让去 AI 味改动剧情意图。小说事实继续由各书的 `追踪/` 和 `设定/` 管理。
|
|
19
19
|
|
|
20
20
|
## 存放与路由
|
|
21
21
|
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
|
|
29
29
|
## 查询
|
|
30
30
|
|
|
31
|
-
各 skill 入口已写好本任务的 `query` 命令(长篇正文由组装脚本代查);没写命令的长篇设定、大纲等任务查 `story_design` + `workflow` + `interaction`,结果只给主会话、不传正文 agent。四类任务的映射表见维护文件。state 存在才查(两级都不存在时返回空结果、零写入);结果合并项目级与 `--book-root` 所指书级(不传就拿不到本书条目),`--kind`
|
|
31
|
+
各 skill 入口已写好本任务的 `query` 命令(长篇正文由组装脚本代查);没写命令的长篇设定、大纲等任务查 `story_design` + `workflow` + `interaction`,结果只给主会话、不传正文 agent。四类任务的映射表见维护文件。state 存在才查(两级都不存在时返回空结果、零写入);结果合并项目级与 `--book-root` 所指书级(不传就拿不到本书条目),`--kind` 必传;交给执行者的只有 `lines`(每条一行原话+编号,单次查询 ≤2048 字节)。
|
|
32
32
|
|
|
33
|
-
普通创作只做一次本地 `query`,完整画像、证据、候选和 journal 不进 prompt。查询项是低优先级倾向,不是逐条打卡清单:自然吸收,不复述画像、不刻意提高词面命中率,不为命中牺牲连贯、节奏、字数或本书既定笔调。**`omitted_ids`
|
|
33
|
+
普通创作只做一次本地 `query`,完整画像、证据、候选和 journal 不进 prompt。查询项是低优先级倾向,不是逐条打卡清单:自然吸收,不复述画像、不刻意提高词面命中率,不为命中牺牲连贯、节奏、字数或本书既定笔调。**`omitted_ids` 非空=记忆超编**,不是「没有更多了」:照 `omitted_summaries` 转告作者并建议「整理作者记忆」,不得改读完整画像规避预算。待确认项不进 prompt,也不为确认它们中断任务;只在作者主动查看、候选积累到适合回顾的节点,或新偏好与 active 条目冲突时集中呈现。
|
|
34
34
|
|
|
35
35
|
## 记不记、记成什么
|
|
36
36
|
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
回复就两行纯文本,不加代码块或引用格式:第一行用一句人话说记住了什么、管哪本书或哪类场合,如「记住了:《{书名}》的对话一律用「」,以后写这本书都照这个来;想改随时说。」;第二行是机器回执作凭证,如「技术备注:Author Memory Receipt: r1 · BP001」。
|
|
67
67
|
|
|
68
68
|
- 确认、替换、忘掉同理:「好,这条生效了:……」「换成了:……,原来的「……」不再用」「忘掉了:……」。只进待确认时说「这条先记在待确认里,你说"确认"才生效」;有冲突时用原话说明跟哪条旧习惯冲突。
|
|
69
|
-
- `warnings` / `omitted_ids`
|
|
69
|
+
- `warnings` / `omitted_ids` 不原样贴:说「你的习惯攒得有点多,写正文时这几条会带不上:「……」」(`warnings` 是最坏估算,改说「可能带不上」),并建议说「整理作者记忆」。不提字节、prompt、kind、scope;编号只能跟着原话出现。
|
|
70
70
|
|
|
71
71
|
## 运行工具
|
|
72
72
|
|
|
@@ -12,7 +12,7 @@ SKILL.md 路由到「日更续写」后按本文件控制批次;每章正文
|
|
|
12
12
|
|
|
13
13
|
**缺 state 或视图不一致**:停下,按 [stop-and-fix.md](stop-and-fix.md) 修好再开始。
|
|
14
14
|
|
|
15
|
-
确定范围后直接进 Step 2,不问"是否继续";K 默认 2-3
|
|
15
|
+
确定范围后直接进 Step 2,不问"是否继续";K 默认 2-3 章,作者另给按作者。只在章号冲突、细纲缺失、范围超出已有细纲、要改大纲/追踪时暂停确认。
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
@@ -71,5 +71,6 @@ SKILL.md 路由到「日更续写」后按本文件控制批次;每章正文
|
|
|
71
71
|
- 第{N+1}章《{章名}》:{一句发生了什么}
|
|
72
72
|
{偏离原计划的就一句话说偏在哪、打算怎么接,如「阿福比计划早露面一章,后面照常」;主角功劳被人拿走、提前用了后面的底牌也在这里说;没有就删掉此行}
|
|
73
73
|
{要作者定的事:一句问题+白话选项+推荐,如「沈栀要不要在下一章就知道信的下落?1. 先瞒着(推荐)2. 这章就告诉她」;没有就删掉此行}
|
|
74
|
+
{本批有习惯没带上就用原话合并说一次;没有就删掉此行}
|
|
74
75
|
下一批:第{M}到第{M+2}章,{细纲已备好 / 需要先补细纲}。接着写吗?{写到卷末时改问:「这一卷写完了,要开新卷吗?」}
|
|
75
76
|
```
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- `genre_prose_card`(题材正文提示卡摘要,只含本章相关条目)。
|
|
14
14
|
- 文风全文路径、按 `style-resolution.md` 形成的 `style_resolution`、原文锚点;同一裁决原样交后续去味与审稿。
|
|
15
15
|
- `设定/题材定位.md`「作者已定」原样摘入(约 600 字内,超了注明去原文读完)。
|
|
16
|
-
- `author_preferences`:作者记忆 `query`
|
|
16
|
+
- `author_preferences`:作者记忆 `query --kind prose_style --kind story_design` 输出的 `lines` 原样填(本书题材、长篇写作流程有限定条目时,每个题材×流程组合带 `--genre`/`--workflow` 各查一次,合并去重),无则不传;`omitted_ids` 里没被任一次查询带上的,章末照模板那一行用 `omitted_summaries` 的原话告诉作者;禁止把完整画像或待确认项塞进 prompt;作为低优先级倾向自然吸收,不逐条展示或最大化命中,不牺牲连贯、节奏和字数。
|
|
17
17
|
- 本章禁止提前释放信息与各点「放」;细纲写了阶段位置、结构公式时一并传。
|
|
18
18
|
- 字数目标、`visible_chars_v1` 口径、格式硬约束;默认两组交付并由主会话测一次 checkpoint;用户明确要求一次成文时才写全章。字数目标按整章分量刻度使用,疏密自行分配,不填写逐情节点配额。
|
|
19
19
|
- **agent 定义里已有的不重述**,只留一行指针:细纲优先边界=铁律 1、正文形状=铁律 2、新增物三级=铁律 3、阅读体验字段(本章标价/闭环状态/信息差触发点/镜头准入/情节点分辨率列/执行边界「放」半边)=铁律 4、交付三附件(情节点落点/新增申报表含末行「本章没写成的」/参考文件读取清单)=被调用协议。申报表与「没写成的」行的去向见 workflow-chapter 步骤 12 与「新增物处置(主会话)」。
|
|
@@ -34,21 +34,22 @@ PENDING_MAX_BYTES = 12288
|
|
|
34
34
|
JOURNAL_MAX_BYTES = 24576
|
|
35
35
|
QUERY_MAX_BYTES = 2048
|
|
36
36
|
ASSERTION_MAX_BYTES = 120 # 新建条目的断言限一句话;解释进 reason(不进 query 载荷)。
|
|
37
|
-
OMITTED_IDS_MAX = 20 # omitted_ids 封顶,omitted 保留真实总数——漏项列表不许把载荷本身挤炸。
|
|
38
37
|
LEGACY_ASSERTION_MAX_BYTES = 768 # 存量条目的读取上限;强化老条目不受新上限约束。
|
|
39
38
|
|
|
40
|
-
# query
|
|
41
|
-
# 是它在 prompt
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
39
|
+
# query 输出里的 lines(每条一行「- 断言(编号)」)是交给执行 agent 的注入
|
|
40
|
+
# 载荷,QUERY_MAX_BYTES 是它在 prompt 里的注意力预算,不该放大;量的是执行者
|
|
41
|
+
# 真正读到的这几行,不是 JSON 外壳(编号、kind、scope 只给主会话和脚本看)。
|
|
42
|
+
# 防「作者以为载入了、实际被静默挤掉」靠三层:①新建条目的断言限
|
|
43
|
+
# ASSERTION_MAX_BYTES,从源头短(强化已有条目不受限,否则存量长断言再也无法
|
|
44
|
+
# 被确认,只会派生重复条目);②写入端按下列任务组合(与
|
|
45
|
+
# references/author-memory-maintenance.md 的映射表同包跟版)估算最坏查询情形——
|
|
46
|
+
# 全局条目+各 scope 维度上最重的单一切片(一次查询只带一个 book/genre/workflow,
|
|
47
|
+
# 不同书的条目不会同现;切片按 casefold 归并,与 same_scope_value 同一口径,
|
|
48
|
+
# 轻重按渲染后的行字节算,与真实载荷同一把尺),装不下时在返回的 warnings 里
|
|
49
|
+
# 点名将被略过的条目、指向「整理作者记忆」,写入本身永不因注入预算失败;
|
|
50
|
+
# ③查询按 重要度→本书例外→最近更新 排序装填,被略过的恒是重要度较低的条目,
|
|
51
|
+
# 漏下的 ID 按同一优先级全数报进 omitted_ids,首句报进 omitted_summaries,
|
|
52
|
+
# 供主会话用原话告诉作者哪几条没带上。
|
|
52
53
|
QUERY_COMBOS: dict[str, tuple[str, ...]] = {
|
|
53
54
|
"正文初稿/续写": ("prose_style", "story_design"),
|
|
54
55
|
"去AI味/改写": ("prose_style",),
|
|
@@ -1086,12 +1087,19 @@ def summarize_assertion(assertion: str, *, limit: int = 14) -> str:
|
|
|
1086
1087
|
|
|
1087
1088
|
|
|
1088
1089
|
def compact_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
1089
|
-
"""query
|
|
1090
|
+
"""query 的 items 只带这四个字段,给主会话和脚本按 kind/scope 判断用;
|
|
1091
|
+
reason、evidence 等一律不出 store。"""
|
|
1090
1092
|
return {"id": item["id"], "kind": item["kind"], "scope": item["scope"], "assertion": item["assertion"]}
|
|
1091
1093
|
|
|
1092
1094
|
|
|
1093
|
-
def
|
|
1094
|
-
|
|
1095
|
+
def render_line(item: dict[str, Any]) -> str:
|
|
1096
|
+
"""执行者读到的一行:断言原文+编号。注入预算量的就是这几行。"""
|
|
1097
|
+
return f"- {item['assertion']}({item['id']})"
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def line_bytes(item: dict[str, Any]) -> int:
|
|
1101
|
+
"""一行在注入载荷里的占位,含行尾换行——估算与真实装填只用这一把尺。"""
|
|
1102
|
+
return len((render_line(item) + "\n").encode("utf-8"))
|
|
1095
1103
|
|
|
1096
1104
|
|
|
1097
1105
|
SCOPE_RANK = {"book": 0, "genre": 1, "workflow": 2, "global": 3}
|
|
@@ -1121,48 +1129,38 @@ def fit_items(
|
|
|
1121
1129
|
*,
|
|
1122
1130
|
extra: dict[str, Any] | None = None,
|
|
1123
1131
|
) -> tuple[dict[str, Any], list[str]]:
|
|
1124
|
-
"""
|
|
1125
|
-
|
|
1126
|
-
OMITTED_IDS_MAX 条,omitted 保留真实总数)。返回 (结果文档, 全部漏下 ID)。
|
|
1132
|
+
"""按渲染后的行把条目装进 QUERY_MAX_BYTES:装不下的跳过而不中断(一条长的
|
|
1133
|
+
不挡后面的短条)。返回 (结果文档, 全部漏下 ID)。
|
|
1127
1134
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1135
|
+
只有 lines 计入预算;items、漏项清单和 `extra`(book_revision 等)是给主会话
|
|
1136
|
+
和脚本看的,不进执行者 prompt,所以漏项全数列出、不封顶,并按候选优先级
|
|
1137
|
+
排序——排在前面的就是作者最该知道没带上的那几条。
|
|
1131
1138
|
"""
|
|
1139
|
+
kept: list[dict[str, Any]] = []
|
|
1140
|
+
dropped: list[dict[str, Any]] = []
|
|
1141
|
+
used = 0
|
|
1142
|
+
for item in sorted_items:
|
|
1143
|
+
size = line_bytes(item)
|
|
1144
|
+
if used + size > QUERY_MAX_BYTES:
|
|
1145
|
+
dropped.append(item)
|
|
1146
|
+
continue
|
|
1147
|
+
kept.append(item)
|
|
1148
|
+
used += size
|
|
1132
1149
|
result: dict[str, Any] = {
|
|
1133
1150
|
"ok": True,
|
|
1134
1151
|
"command": "query",
|
|
1135
1152
|
"initialized": True,
|
|
1136
1153
|
"revision": revision,
|
|
1137
|
-
"items": [],
|
|
1138
|
-
"
|
|
1139
|
-
"
|
|
1154
|
+
"items": [compact_item(item) for item in kept],
|
|
1155
|
+
"lines": [render_line(item) for item in kept],
|
|
1156
|
+
"omitted": len(dropped),
|
|
1157
|
+
"omitted_ids": [item["id"] for item in dropped],
|
|
1158
|
+
"omitted_summaries": {item["id"]: summarize_assertion(item["assertion"]) for item in dropped},
|
|
1159
|
+
# 组装脚本按题材×流程查好几次再合并,合并后只能按重要度重排漏项,所以把它带出来。
|
|
1160
|
+
"omitted_importance": {item["id"]: item["importance"] for item in dropped},
|
|
1140
1161
|
}
|
|
1141
1162
|
result.update(extra or {})
|
|
1142
|
-
|
|
1143
|
-
kept: list[dict[str, Any]] = []
|
|
1144
|
-
dropped: set[str] = set()
|
|
1145
|
-
|
|
1146
|
-
def ordered_omitted() -> list[str]:
|
|
1147
|
-
return sorted(dropped, key=order.__getitem__)
|
|
1148
|
-
|
|
1149
|
-
def envelope_bytes() -> int:
|
|
1150
|
-
omitted = ordered_omitted()
|
|
1151
|
-
result["items"] = [compact_item(item) for item in kept]
|
|
1152
|
-
result["omitted"] = len(omitted)
|
|
1153
|
-
result["omitted_ids"] = omitted[:OMITTED_IDS_MAX]
|
|
1154
|
-
return len((json.dumps(result, ensure_ascii=False, sort_keys=True) + "\n").encode("utf-8"))
|
|
1155
|
-
|
|
1156
|
-
for item in sorted_items:
|
|
1157
|
-
kept.append(item)
|
|
1158
|
-
if envelope_bytes() > QUERY_MAX_BYTES:
|
|
1159
|
-
kept.pop()
|
|
1160
|
-
dropped.add(item["id"])
|
|
1161
|
-
# omitted 计数落定后包可能恰好贴边超出一两个字节,回吐条目直到装下。
|
|
1162
|
-
while kept and envelope_bytes() > QUERY_MAX_BYTES:
|
|
1163
|
-
dropped.add(kept.pop()["id"])
|
|
1164
|
-
envelope_bytes()
|
|
1165
|
-
return result, ordered_omitted()
|
|
1163
|
+
return result, result["omitted_ids"]
|
|
1166
1164
|
|
|
1167
1165
|
|
|
1168
1166
|
def build_query_result(
|
|
@@ -1170,8 +1168,8 @@ def build_query_result(
|
|
|
1170
1168
|
project_state: dict[str, Any] | None,
|
|
1171
1169
|
book_state: dict[str, Any] | None,
|
|
1172
1170
|
) -> tuple[dict[str, Any], list[str]]:
|
|
1173
|
-
"""
|
|
1174
|
-
book_revision
|
|
1171
|
+
"""真实查询与写入端估算共用的唯一装填入口:候选已按优先级排好,这里补上
|
|
1172
|
+
book_revision 等附加字段再装填。两处装填口径一旦分叉就是假阴性——回执
|
|
1175
1173
|
说没事、query 照样丢条。"""
|
|
1176
1174
|
extra: dict[str, Any] = {}
|
|
1177
1175
|
if book_state is not None:
|
|
@@ -1208,12 +1206,13 @@ def merged_query(
|
|
|
1208
1206
|
|
|
1209
1207
|
|
|
1210
1208
|
def slice_weight(items: list[dict[str, Any]]) -> int:
|
|
1211
|
-
"""
|
|
1209
|
+
"""切片在真实载荷里的占位:按渲染后的行(含换行)计。
|
|
1212
1210
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1211
|
+
必须与 fit_items 同一把尺。按 JSON 字节挑会挑错切片——scope 取值长、断言
|
|
1212
|
+
短的切片 JSON 更重,渲染出的行却更轻,于是估算判「装得下」而真实查询溢出
|
|
1213
|
+
(warnings 假阴性)。
|
|
1215
1214
|
"""
|
|
1216
|
-
return sum(
|
|
1215
|
+
return sum(line_bytes(item) for item in items)
|
|
1217
1216
|
|
|
1218
1217
|
|
|
1219
1218
|
def active_of_kinds(state: dict[str, Any] | None, kinds: tuple[str, ...]) -> list[dict[str, Any]]:
|
|
@@ -1419,16 +1418,20 @@ def command_query(
|
|
|
1419
1418
|
project_state = load_state(project_store(workspace))
|
|
1420
1419
|
book_state = load_state(book_store(workspace, book_root, book)) if book_root is not None else None
|
|
1421
1420
|
if project_state is None and book_state is None:
|
|
1422
|
-
return {"ok": True, "command": "query", "initialized": False, "revision": 0, "items": [], "
|
|
1421
|
+
return {"ok": True, "command": "query", "initialized": False, "revision": 0, "items": [], "lines": [],
|
|
1422
|
+
"omitted": 0, "omitted_ids": [], "omitted_summaries": {}, "omitted_importance": {}}
|
|
1423
1423
|
requested_scopes = {
|
|
1424
1424
|
"genre": optional_text(genre, "query.genre", max_bytes=180),
|
|
1425
1425
|
"workflow": optional_text(workflow, "query.workflow", max_bytes=180),
|
|
1426
1426
|
}
|
|
1427
1427
|
# 装不下的**跳过而不中断**(一条长的不挡后面的短条),漏下的 ID 报进
|
|
1428
1428
|
# omitted_ids:非空=记忆超编该整理了,不是「没有更多了」。写入端的
|
|
1429
|
-
# warnings 与这里共用 build_query_result
|
|
1429
|
+
# warnings 与这里共用 build_query_result 同一把尺,估算即实况。
|
|
1430
1430
|
result, _ = merged_query(project_state, book_state, set(kinds), requested_scopes)
|
|
1431
|
-
require(
|
|
1431
|
+
require(
|
|
1432
|
+
sum(len((line + "\n").encode("utf-8")) for line in result["lines"]) <= QUERY_MAX_BYTES,
|
|
1433
|
+
"query lines exceed their fixed byte budget",
|
|
1434
|
+
)
|
|
1432
1435
|
return result
|
|
1433
1436
|
|
|
1434
1437
|
|