@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.
Files changed (58) hide show
  1. package/README.md +7 -7
  2. package/lib/client.js +932 -631
  3. package/lib/client.js.map +4 -4
  4. package/lib/index.js +1 -0
  5. package/lib/index.js.map +2 -2
  6. package/lib/oh-story/manifest.json +72 -72
  7. package/lib/oh-story/skills/story/SKILL.md +1 -1
  8. package/lib/oh-story/skills/story/VERSION +1 -1
  9. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +2 -2
  10. package/lib/oh-story/skills/story/references/author-memory.md +4 -4
  11. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +61 -58
  12. package/lib/oh-story/skills/story-deslop/SKILL.md +1 -1
  13. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +1 -1
  14. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +2 -2
  15. package/lib/oh-story/skills/story-deslop/references/author-memory.md +4 -4
  16. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +61 -58
  17. package/lib/oh-story/skills/story-long-scan/SKILL.md +2 -7
  18. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +11 -1
  19. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +12 -2
  20. package/lib/oh-story/skills/story-long-write/SKILL.md +1 -1
  21. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +2 -2
  22. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +2 -2
  23. package/lib/oh-story/skills/story-long-write/references/author-memory.md +4 -4
  24. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +1 -0
  25. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +2 -1
  26. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +1 -1
  27. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +61 -58
  28. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +87 -20
  29. package/lib/oh-story/skills/story-review/SKILL.md +1 -1
  30. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +2 -2
  31. package/lib/oh-story/skills/story-review/references/author-memory.md +4 -4
  32. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +61 -58
  33. package/lib/oh-story/skills/story-short-write/SKILL.md +1 -1
  34. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +2 -2
  35. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +2 -2
  36. package/lib/oh-story/skills/story-short-write/references/author-memory.md +4 -4
  37. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +61 -58
  38. package/lib/types/client/canvas-viewport.d.ts +31 -0
  39. package/lib/types/client/canvas-viewport.d.ts.map +1 -0
  40. package/lib/types/client/drama-production-view.d.ts +3 -2
  41. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  42. package/lib/types/client/index.d.ts.map +1 -1
  43. package/lib/types/client/production-canvas.d.ts +24 -0
  44. package/lib/types/client/production-canvas.d.ts.map +1 -0
  45. package/lib/types/skill-provider.d.ts.map +1 -1
  46. package/lib/video-recap/CHANGELOG.md +16 -1
  47. package/lib/video-recap/README.en.md +2 -2
  48. package/lib/video-recap/README.md +2 -2
  49. package/lib/video-recap/manifest.json +25 -25
  50. package/lib/video-recap/skills/video-recap/SKILL.md +4 -2
  51. package/lib/video-recap/skills/video-recap/references/config-playbook.md +2 -2
  52. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +6 -0
  53. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +10 -1
  54. package/lib/video-recap/skills/video-script/SKILL.md +45 -18
  55. package/lib/video-recap/skills/video-script/scripts/review_runner.py +11 -2
  56. package/lib/video-recap/skills/video-voiceover/SKILL.md +34 -8
  57. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +21 -0
  58. package/package.json +1 -1
@@ -15,7 +15,8 @@
15
15
  职责边界:
16
16
  - 脚本做确定性部分:固定首行、定位、标题行字面量、细纲指针、文风全文路径与裁决、
17
17
  上一章结尾、降档判定与情绪/节奏槽、固定块指针。
18
- - 脚本代查作者记忆(prose_style + story_design)并填好 author_preferences。
18
+ - 脚本代查作者记忆(prose_style + story_design)并填好 author_preferences;装不下的条目在核对报告里
19
+ 点名,并拼好章末对作者说的那句原话。
19
20
  - 脚本注入 `设定/题材定位.md`「作者已定」(本书的偏好、红线与否掉的方案),超过 AUTHOR_DECIDED_CHARS
20
21
  可见字截断并注明去原文看;它是书级设定,优先于作者记忆。
21
22
  - 主会话填九个固定槽:执行安排 / 本章意图 / 作者本轮要求 / 本章技法 / 本节速记 / 涉及角色 /
@@ -147,6 +148,8 @@ def previous_chapter_tail(project: Path, chapter: int):
147
148
 
148
149
 
149
150
  MEMORY_KINDS = ("prose_style", "story_design")
151
+ IMPORTANCE_RANK = {"low": 1, "medium": 2, "high": 3}
152
+ MISSED_SHOWN = 8 # 给作者的那句话最多点名几条,其余用「等」带过;完整清单走「整理作者记忆」
150
153
  # 限定「流程」的作者记忆里,这些取值指的就是长篇写正文。
151
154
  LONG_WRITE_WORKFLOWS = {"长篇", "长篇写作", "写长篇", "长篇连载", "长篇网文", "长篇正文", "story-long-write"}
152
155
 
@@ -170,6 +173,51 @@ def scoped_memory_values(workspace: Path):
170
173
  return values
171
174
 
172
175
 
176
+ WORKSPACE_MARKERS = (".active-book", "长篇", "短篇", "拆文库")
177
+
178
+
179
+ def memory_state_kind(directory: Path):
180
+ """目录下 `.story/作者记忆/` 那份 state 是哪一级:不带 book 字段为 "project",带书名为 "book",
181
+ 没有或读不出为 None(与 author_memory_commit.py 的 peek_book_name 同一判据)。"""
182
+ text = read_text(directory / ".story" / "作者记忆" / "_author-memory-state.json")
183
+ try:
184
+ document = json.loads(text) if text else None
185
+ except ValueError:
186
+ return None
187
+ if not isinstance(document, dict):
188
+ return None
189
+ book = document.get("book")
190
+ return "book" if isinstance(book, str) and book.strip() else "project"
191
+
192
+
193
+ def find_workspace(project: Path):
194
+ """写章代查作者记忆用的创作工作区,按 author-memory.md「存放与路由」认,返回 (目录, 是否认出)。
195
+
196
+ 依次认:书目录或祖先里的 `.story-deployed`(已部署的宿主);书目录上一层叫 长篇/短篇 时取再上一层;
197
+ 书目录自己就有项目级作者记忆或 `书级/`(单书布局);最近一个带 `.active-book`、`长篇/`、`短篇/`、`拆文库/`
198
+ 或项目级作者记忆的祖先(到用户主目录为止,主目录本身不算)。都没有时把书目录当工作区,但这是猜的:
199
+ DSH 等宿主不写 `.story-deployed`,多书工作区里的书若被当成工作区,查询会报错或把书级记忆挪位。
200
+ """
201
+ project = project.resolve()
202
+ deployed = next((d for d in (project, *project.parents) if (d / ".story-deployed").is_file()), None)
203
+ if deployed is not None:
204
+ return deployed, True
205
+ if project.parent.name in {"长篇", "短篇"}:
206
+ return project.parent.parent, True
207
+ if memory_state_kind(project) == "project" or (project / ".story" / "作者记忆" / "书级").is_dir():
208
+ return project, True
209
+ try:
210
+ home = Path.home().resolve()
211
+ except (RuntimeError, KeyError): # 拿不到主目录时一路找到根
212
+ home = None
213
+ for ancestor in project.parents:
214
+ if ancestor == home:
215
+ break
216
+ if any((ancestor / marker).exists() for marker in WORKSPACE_MARKERS) or memory_state_kind(ancestor) == "project":
217
+ return ancestor, True
218
+ return project, False
219
+
220
+
173
221
  def genre_line(project: Path):
174
222
  """「题材类型」行的原文;未填的模板占位({…})当没有。"""
175
223
  text = read_text(project / "设定" / "题材定位.md") or ""
@@ -232,24 +280,30 @@ def author_decided(project: Path):
232
280
 
233
281
 
234
282
  def query_author_memory(project: Path):
235
- """代主会话做写正文那一次作者记忆查询(prose_style + story_design,每次 ≤2KB)。
283
+ """代主会话做写正文那一次作者记忆查询(prose_style + story_design)。
236
284
 
237
- 工作区取书目录及其祖先里第一个带 `.story-deployed` 的目录;找不到就把书目录当工作区。
285
+ 注入块原样用 query 给的 lines 拼——每次查询的 lines ≤2KB,预算量的就是写手读到的这几行,
286
+ 这里不另行排版。工作区按 find_workspace 认;认不出、而书目录里又是一份带书名的记忆时不代查——
287
+ 那既可能是单书布局的旧版记忆,也可能是没有任何迹象的多书工作区里的一本书,拿书目录当工作区去查,
288
+ 后一种会被当成前一种「归位」,此后正确的查询全部报错。
238
289
  限定题材的条目只在取值出现在本书「题材类型」里时代查,限定流程的只认长篇写作的取值;
239
290
  其余限定条目不猜,报给主会话自己判断。
240
- 返回 (条目列表, 超编 ID 列表, 未代查的限定取值, 错误);错误时由主会话按 author-memory.md 手动查询。
291
+ 返回 (注入行, 没带上的 [(ID, 断言首句)], 未代查的限定取值, 错误);错误时由主会话按 author-memory.md 手动查询。
241
292
  """
242
- workspace = next((d for d in (project, *project.parents) if (d / ".story-deployed").is_file()), project)
293
+ workspace, recognised = find_workspace(project)
243
294
  script = Path(__file__).with_name("author_memory_commit.py")
244
295
  if not script.is_file():
245
296
  return None, [], {}, "author_memory_commit.py 缺失"
297
+ if not recognised and memory_state_kind(project) == "book":
298
+ return None, [], {}, ("认不出创作工作区(书目录及祖先都没有 .active-book、长篇/、短篇/、拆文库/ 或项目级作者记忆),"
299
+ "书目录里却是本书的记忆;先按 author-memory.md「存放与路由」定好 --workspace")
246
300
  scoped = scoped_memory_values(workspace)
247
301
  book = book_genres(project)
248
302
  genres = sorted(v for v in scoped["genre"] if v.casefold() in book) or [None]
249
303
  workflows = sorted(v for v in scoped["workflow"] if v.casefold() in {w.casefold() for w in LONG_WRITE_WORKFLOWS})
250
304
  skipped = {"genre": sorted(v for v in scoped["genre"] if v not in genres),
251
305
  "workflow": sorted(v for v in scoped["workflow"] if v not in workflows)}
252
- items, omitted, seen = [], [], set()
306
+ lines, omitted, summaries, importance, seen = [], [], {}, {}, set()
253
307
  for genre in genres:
254
308
  for workflow in workflows or [None]:
255
309
  command = [sys.executable, str(script), "query", "--workspace", str(workspace), "--book-root", str(project)]
@@ -266,13 +320,22 @@ def query_author_memory(project: Path):
266
320
  result = {}
267
321
  if not result.get("ok"):
268
322
  return None, [], {}, result.get("error") or completed.stderr.strip() or "query 失败"
269
- for item in result.get("items") or []:
323
+ items, rendered = result.get("items") or [], result.get("lines") or []
324
+ if len(items) != len(rendered):
325
+ return None, [], {}, "query 输出缺 lines(作者记忆脚本与组装脚本版本不一致)"
326
+ for item, line in zip(items, rendered):
270
327
  if item.get("id") not in seen:
271
328
  seen.add(item.get("id"))
272
- items.append(item)
273
- omitted += [i for i in result.get("omitted_ids") or [] if i not in omitted and i not in seen]
274
- omitted = [i for i in omitted if i not in seen]
275
- return items, omitted, {k: v for k, v in skipped.items() if v}, None
329
+ lines.append(line)
330
+ omitted += [i for i in result.get("omitted_ids") or [] if i not in omitted]
331
+ summaries.update(result.get("omitted_summaries") or {})
332
+ importance.update(result.get("omitted_importance") or {})
333
+ # 每次查询的漏项各自按优先级排好,合并后按重要度重排(同重要度保持先后),
334
+ # 给作者点名的前几条才是最该知道的;某次漏、另一次带上了的不算没带上。
335
+ ranked = sorted((i for i in omitted if i not in seen),
336
+ key=lambda i: (-IMPORTANCE_RANK.get(importance.get(i), 0), omitted.index(i)))
337
+ missed = [(i, summaries.get(i, "")) for i in ranked]
338
+ return lines, missed, {k: v for k, v in skipped.items() if v}, None
276
339
 
277
340
 
278
341
  def learn_heading_form(project: Path, chapter: int, title: str):
@@ -503,23 +566,27 @@ def build(project: Path, chapter: int, report: list):
503
566
  report.append("作者已定:已注入" + ("(超长截断,写手需回原文读完)" if cut else ""))
504
567
  else:
505
568
  report.append("作者已定:题材定位里没有或为空,未注入")
506
- items, omitted, skipped, memory_error = query_author_memory(project)
569
+ lines, missed, skipped, memory_error = query_author_memory(project)
507
570
  if memory_error:
508
571
  memory_block = (f"{SLOT_MARK} 组装脚本查询作者记忆失败({memory_error}),"
509
572
  "按 author-memory.md 手动 query prose_style + story_design 后填入;无则写「无」")
510
- report.append(f"作者记忆:脚本查询失败——{memory_error},归主会话手动查询")
511
- elif items:
512
- memory_block = "\n".join(f"- {item.get('assertion', '').strip()}({item.get('id', '')})" for item in items)
513
- report.append(f"作者记忆:已注入 {len(items)} 条" + (
514
- f";超编未装下 {len(omitted)} 条({'、'.join(omitted)}),转告作者建议「整理作者记忆」" if omitted else ""))
573
+ report.append(f"作者记忆:脚本查询失败——{memory_error},归主会话手动查询;手查有没带上的(任一次查询带上了的不算),章末照模板那一行用原话说")
515
574
  else:
516
- memory_block = "无"
517
- report.append("作者记忆:无相关 active 条目")
575
+ memory_block = "\n".join(lines) if lines else "无"
576
+ report.append(f"作者记忆:已注入 {len(lines)} 条" if lines or missed else "作者记忆:无相关 active 条目")
577
+ if missed:
578
+ # 装不下的不能悄悄略过:拼好给作者的原话,章末汇报照模板那一行原样说。
579
+ shown = "".join(f"「{summary or item_id}」" for item_id, summary in missed[:MISSED_SHOWN])
580
+ more = "等" if len(missed) > MISSED_SHOWN else ""
581
+ report.append(
582
+ f"作者记忆:装不下、这章没带上 {len(missed)} 条({'、'.join(i for i, _ in missed)}),章末汇报原样说这句——"
583
+ f"你记下的写作习惯这章有 {len(missed)} 条没带上:{shown}{more};"
584
+ "说「整理作者记忆」可以合并相近的、调低不常用的。")
518
585
  if skipped and not memory_error:
519
586
  named = ";".join(f"{'题材' if k == 'genre' else '流程'}:{'、'.join(v)}" for k, v in skipped.items())
520
587
  memory_block += (f"\n{SLOT_MARK} 另有限定范围的作者记忆未代查({named}):本书适用的,"
521
588
  "带 --genre/--workflow 跑 SKILL.md「核心方法」的 query 命令(--kind prose_style --kind story_design)后补进本块")
522
- report.append(f"作者记忆:限定范围未代查({named}),归主会话判断是否适用")
589
+ report.append(f"作者记忆:限定范围未代查({named}),归主会话判断是否适用;补查有没带上的(已在本块里的不算),章末照模板那一行用原话说")
523
590
  parts.append("——— author_preferences(作者记忆,低优先级倾向,不逐条追求命中)———\n" + memory_block)
524
591
  parts.append("——— style_resolution ———\n"
525
592
  f"{SLOT_MARK} 本轮请求里作者对表达的明确要求及其覆盖的默认条款(没有写「无」);"
@@ -16,7 +16,7 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
16
16
 
17
17
  ## 作者习惯边界
18
18
 
19
- 若作者记忆 state 已存在,审查前用 `scripts/author_memory_commit.py query --workspace {工作区} --book-root {书目录} --kind delivery --kind interaction --kind prose_style [--genre {题材}] [--workflow 审稿]` 获取本次相关 active 条目(`--workspace`、`--kind` 必传;不传 `--book-root` 就拿不到本书级偏好;`--genre` 填本书题材类型;总输出 ≤2KB)。它们只能帮助解释意图和组织报告,不能降低 rubric 严重度、把事实冲突判为无问题或跳过平台门禁;当前请求仍优先。完整规则见 [references/author-memory.md](references/author-memory.md)。
19
+ 若作者记忆 state 已存在,审查前用 `scripts/author_memory_commit.py query --workspace {工作区} --book-root {书目录} --kind delivery --kind interaction --kind prose_style [--genre {题材}] [--workflow 审稿]` 获取本次相关 active 条目(`--workspace`、`--kind` 必传;不传 `--book-root` 就拿不到本书级偏好;`--genre` 填本书题材类型)。它们只能帮助解释意图和组织报告,不能降低 rubric 严重度、把事实冲突判为无问题或跳过平台门禁;当前请求仍优先。完整规则见 [references/author-memory.md](references/author-memory.md)。
20
20
 
21
21
  用户对报告格式或协作方式作出稳定声明时,在本轮审查完成后用 `record` 记录,并按 author-memory.md「回执怎么告诉作者」转告;只记作者明确说的,一次性要求不记录,不从反复修改推断。审查发现、工具告警和助手建议本身绝不自动学习。
22
22
 
@@ -32,9 +32,9 @@
32
32
 
33
33
  ## 注入预算与容量
34
34
 
35
- - **写入不因注入预算失败**:`record` / `commit` 照常成功、给回执;工具按上表四类任务组合估算最坏查询情形(全局条目+各 scope 维度最重的单一切片,切片按大小写无关归并、轻重按写作时真正读到的字段算,与真实查询同一把尺),装不进 2048 字节的组合在返回的 `warnings` 里点名将被略过的条目及其断言首句。
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 按同一优先级报进 `omitted_ids`(最多列 20 条,`omitted` 是真实总数)。
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/作者记忆/`,不能覆盖当前请求,不能降低审稿 rubric,也不能让去 AI 味改动剧情意图。小说事实继续由各书的 `追踪/` 和 `设定/` 管理。
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` 必传,输出不超过 2048 字节。
31
+ 各 skill 入口已写好本任务的 `query` 命令(长篇正文由组装脚本代查);没写命令的长篇设定、大纲等任务查 `story_design` + `workflow` + `interaction`,结果只给主会话、不传正文 agent。四类任务的映射表见维护文件。state 存在才查(两级都不存在时返回空结果、零写入);结果合并项目级与 `--book-root` 所指书级(不传就拿不到本书条目),`--kind` 必传;交给执行者的只有 `lines`(每条一行原话+编号,单次查询 ≤2048 字节)。
32
32
 
33
- 普通创作只做一次本地 `query`,完整画像、证据、候选和 journal 不进 prompt。查询项是低优先级倾向,不是逐条打卡清单:自然吸收,不复述画像、不刻意提高词面命中率,不为命中牺牲连贯、节奏、字数或本书既定笔调。**`omitted_ids` 非空=记忆超编**,不是「没有更多了」:转告作者并建议「整理作者记忆」,不得改读完整画像规避预算。待确认项不进 prompt,也不为确认它们中断任务;只在作者主动查看、候选积累到适合回顾的节点,或新偏好与 active 条目冲突时集中呈现。
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` 不原样贴:说「你的习惯攒得有点多,写正文时这几条可能顾不上:「……」」,并建议说「整理作者记忆」。不提字节、prompt、kind、scope;编号只能跟着原话出现。
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 的输出是要原样贴进执行 agent prompt 的注入载荷,QUERY_MAX_BYTES
41
- # 是它在 prompt 里的注意力预算,不该放大。防「作者以为载入了、实际被静默
42
- # 截断挤掉」靠三层:①新建条目的断言限 ASSERTION_MAX_BYTES,从源头短(强化
43
- # 已有条目不受限,否则存量长断言再也无法被确认,只会派生重复条目);②写入
44
- # 端按下列任务组合(与 references/author-memory-maintenance.md 的映射表同包跟版)估算
45
- # 最坏查询情形——全局条目+各 scope 维度上最重的单一切片(一次查询只带一
46
- # 个 book/genre/workflow,不同书的条目不会同现;切片按 casefold 归并,与
47
- # same_scope_value 同一口径,轻重按 compact 字节+列表分隔符算,与真实载荷
48
- # 同一把尺),装不下时在返回的 warnings 里点名将被略过的条目、指向「整理作
49
- # 者记忆」,写入本身永不因注入预算失败;③查询按 重要度→本书例外→最近更新
50
- # 排序装填,被略过的恒是重要度较低的条目,漏下的 ID 按同一优先级顺序报进
51
- # omitted_ids。
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 compact_bytes(item: dict[str, Any]) -> int:
1094
- return len(json.dumps(compact_item(item), ensure_ascii=False).encode("utf-8"))
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
- """按 query 输出信封把条目装进 QUERY_MAX_BYTES:装不下的跳过而不中断
1125
- (一条长的不挡后面的短条),漏下的 ID 报进 omitted_ids(封顶
1126
- OMITTED_IDS_MAX 条,omitted 保留真实总数)。返回 (结果文档, 全部漏下 ID)。
1132
+ """按渲染后的行把条目装进 QUERY_MAX_BYTES:装不下的跳过而不中断(一条长的
1133
+ 不挡后面的短条)。返回 (结果文档, 全部漏下 ID)。
1127
1134
 
1128
- 漏项恒按候选优先级排序,不按被丢弃的先后:收尾回吐的条目优先级高于循环
1129
- 里跳过的,若按追加顺序排,omitted_ids 的封顶正好会把最该报的那条切掉。
1130
- `extra` 是同样计入信封的附加字段(book_revision 等)。
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
- "omitted": 0,
1139
- "omitted_ids": [],
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
- order = {item["id"]: index for index, item in enumerate(sorted_items)}
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
- """切片在真实载荷里的占位:compact 字节+每条在 JSON 数组里的分隔符。
1209
+ """切片在真实载荷里的占位:按渲染后的行(含换行)计。
1212
1210
 
1213
- 只比 compact 字节会挑错切片——条目多、单条短的切片字节和更小,实际占位
1214
- 却更大,于是估算判「装得下」而真实查询溢出(warnings 假阴性)。
1211
+ 必须与 fit_items 同一把尺。按 JSON 字节挑会挑错切片——scope 取值长、断言
1212
+ 短的切片 JSON 更重,渲染出的行却更轻,于是估算判「装得下」而真实查询溢出
1213
+ (warnings 假阴性)。
1215
1214
  """
1216
- return sum(compact_bytes(item) + 2 for item in items)
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": [], "omitted": 0, "omitted_ids": []}
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(len((json.dumps(result, ensure_ascii=False, sort_keys=True) + "\n").encode("utf-8")) <= QUERY_MAX_BYTES, "query result exceeds its fixed byte budget")
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
 
@@ -48,7 +48,7 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
48
48
  除了上面的执行规则,构思和写作时遵循:
49
49
 
50
50
  - **定方向就换风格**:题材一旦确定,腔调、开篇、钩子、情绪烈度、金句、招式、收尾全部切到该题材包(追妻含时代变体与小三/死人文学分支);冷门题材用公式结构骨架兜底,腔调按 `short-craft.md`
51
- - **复用作者习惯**:有作者记忆时,正文前用 `scripts/author_memory_commit.py query --workspace {工作区} --book-root {项目目录} --kind prose_style --kind story_design [--genre {题材}] [--workflow 短篇]` 获取 active 条目(≤2KB),传给正文/改写 agent 作为自然倾向,不逐条展示或最大化命中,不牺牲连贯、节奏和字数;硬门禁、当前请求和本篇设定优先。长期声明在收尾用 `record` 写入并回传回执,细则见 [references/author-memory.md](references/author-memory.md)。
51
+ - **复用作者习惯**:有作者记忆时,正文前用 `scripts/author_memory_commit.py query --workspace {工作区} --book-root {项目目录} --kind prose_style --kind story_design [--genre {题材}] [--workflow 短篇]` 把输出的 `lines` 原样交正文/改写 agent 作自然倾向,不逐条追求命中,不牺牲连贯、节奏和字数;硬门禁、当前请求和本篇设定优先;没装下的交稿时用原话告诉作者。长期声明在收尾用 `record` 写入并回传回执,细则见 [references/author-memory.md](references/author-memory.md)。
52
52
 
53
53
  ---
54
54
 
@@ -4,10 +4,10 @@
4
4
 
5
5
  ## 写正文
6
6
 
7
- spawn `Agent(subagent_type: "narrative-writer", prompt: ...)`,只传项目目录、输出文件、目标情绪、题材风格包、小节大纲、角色、主/副对标召回摘要、本书文风全文路径与 style_resolution、作者偏好 query 中的文风/故事设计项、格式硬约束和写作硬约束,并传入检查分工:本批只做内容覆盖与格式自检,完整语义去味由 Phase 4 负责,最终文件扫描由主会话负责。
7
+ spawn `Agent(subagent_type: "narrative-writer", prompt: ...)`,只传项目目录、输出文件、目标情绪、题材风格包、小节大纲、角色、主/副对标召回摘要、本书文风全文路径与 style_resolution、作者偏好 query 输出的 lines、格式硬约束和写作硬约束,并传入检查分工:本批只做内容覆盖与格式自检,完整语义去味由 Phase 4 负责,最终文件扫描由主会话负责。
8
8
 
9
9
  不把本 skill 整段规则塞进 prompt;细节以 `short-format.md`、题材包、`short-craft.md` 与 `scene-craft.md` 为准。收回后按 workflow-draft.md 的格式规范重排再写入 `正文.md`。
10
10
 
11
11
  ## 精修去味
12
12
 
13
- spawn `Agent(subagent_type: "narrative-writer", prompt: "项目目录:{dir}\n任务描述:去AI味+格式检查\n检查分工:你负责本次语义去味及原定自检;最终文件扫描由主会话执行,不在子代理内重复\n检查范围:{正文文件}\nstyle_resolution:{与写作一致的本次文风裁决,含全文路径}\n作者偏好:{query 命中的 prose_style/story_design 项}\n篇幅:短篇\n卖点保留:第一人称审判句、火葬场预告、心死式章尾留,只删中立作者讲解与空洞升华\nAI味等级:{轻度/中度/重度;未分级按轻度}\n删除优先:每条 AI 味项先判能否删除——删后不丢伏笔/钩子/角色/情节/必要信息的直接删,会丢才润色(删除受比例上限与本次字数范围下沿约束,跌破改降AI重写)\n必须检查:检查是否连续使用头皮发紧/眼皮一跳/心口一沉/胃里翻涌等精致戏剧反应,能写普通动作/普通感觉就写普通动作/普通感觉;已有手机/聊天记录/公告/账单/病历/证据截图等信息,保留为角色看到或处理的场内载体,不改成叙述者解释;任务卡点只在角色本来有要办的事且能加重情绪/证据/关系/反转时使用,不为自然感补流程")`
13
+ spawn `Agent(subagent_type: "narrative-writer", prompt: "项目目录:{dir}\n任务描述:去AI味+格式检查\n检查分工:你负责本次语义去味及原定自检;最终文件扫描由主会话执行,不在子代理内重复\n检查范围:{正文文件}\nstyle_resolution:{与写作一致的本次文风裁决,含全文路径}\n作者偏好:{query 输出的 lines}\n篇幅:短篇\n卖点保留:第一人称审判句、火葬场预告、心死式章尾留,只删中立作者讲解与空洞升华\nAI味等级:{轻度/中度/重度;未分级按轻度}\n删除优先:每条 AI 味项先判能否删除——删后不丢伏笔/钩子/角色/情节/必要信息的直接删,会丢才润色(删除受比例上限与本次字数范围下沿约束,跌破改降AI重写)\n必须检查:检查是否连续使用头皮发紧/眼皮一跳/心口一沉/胃里翻涌等精致戏剧反应,能写普通动作/普通感觉就写普通动作/普通感觉;已有手机/聊天记录/公告/账单/病历/证据截图等信息,保留为角色看到或处理的场内载体,不改成叙述者解释;任务卡点只在角色本来有要办的事且能加重情绪/证据/关系/反转时使用,不为自然感补流程")`
@@ -32,9 +32,9 @@
32
32
 
33
33
  ## 注入预算与容量
34
34
 
35
- - **写入不因注入预算失败**:`record` / `commit` 照常成功、给回执;工具按上表四类任务组合估算最坏查询情形(全局条目+各 scope 维度最重的单一切片,切片按大小写无关归并、轻重按写作时真正读到的字段算,与真实查询同一把尺),装不进 2048 字节的组合在返回的 `warnings` 里点名将被略过的条目及其断言首句。
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 按同一优先级报进 `omitted_ids`(最多列 20 条,`omitted` 是真实总数)。
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/作者记忆/`,不能覆盖当前请求,不能降低审稿 rubric,也不能让去 AI 味改动剧情意图。小说事实继续由各书的 `追踪/` 和 `设定/` 管理。
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` 必传,输出不超过 2048 字节。
31
+ 各 skill 入口已写好本任务的 `query` 命令(长篇正文由组装脚本代查);没写命令的长篇设定、大纲等任务查 `story_design` + `workflow` + `interaction`,结果只给主会话、不传正文 agent。四类任务的映射表见维护文件。state 存在才查(两级都不存在时返回空结果、零写入);结果合并项目级与 `--book-root` 所指书级(不传就拿不到本书条目),`--kind` 必传;交给执行者的只有 `lines`(每条一行原话+编号,单次查询 ≤2048 字节)。
32
32
 
33
- 普通创作只做一次本地 `query`,完整画像、证据、候选和 journal 不进 prompt。查询项是低优先级倾向,不是逐条打卡清单:自然吸收,不复述画像、不刻意提高词面命中率,不为命中牺牲连贯、节奏、字数或本书既定笔调。**`omitted_ids` 非空=记忆超编**,不是「没有更多了」:转告作者并建议「整理作者记忆」,不得改读完整画像规避预算。待确认项不进 prompt,也不为确认它们中断任务;只在作者主动查看、候选积累到适合回顾的节点,或新偏好与 active 条目冲突时集中呈现。
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` 不原样贴:说「你的习惯攒得有点多,写正文时这几条可能顾不上:「……」」,并建议说「整理作者记忆」。不提字节、prompt、kind、scope;编号只能跟着原话出现。
69
+ - `warnings` / `omitted_ids` 不原样贴:说「你的习惯攒得有点多,写正文时这几条会带不上:「……」」(`warnings` 是最坏估算,改说「可能带不上」),并建议说「整理作者记忆」。不提字节、prompt、kind、scope;编号只能跟着原话出现。
70
70
 
71
71
  ## 运行工具
72
72