codebee 0.1.88 → 0.1.89

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 CHANGED
@@ -6,6 +6,11 @@ README 元数据带回,供老版本在「发现新版本」时展示新版更
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## v0.1.89(2026-10-07)
10
+
11
+ - **连载起草「相关历史章节推荐」**:起草前情注入新增历史章回看指针——用连载追踪里逐章持久化的伏笔/人物/事实记录,与本章大纲要点做纯本地重叠计分(零模型调用、零额外存储),按命中行数推荐最相关的至多 3 个历史章摘录供起草时回看;近 2 章窗口外的伏笔回收、人物回场不再只靠账本督促兜底;零匹配零输出(老书无感)。
12
+ - **经验库存量去重**:新增教训并类管理通道(历史标题与正文留痕、召回证据并入主条不缩水),存量近重复教训 8→3 条并类、1 条归位分类,经验库 73→68 条——重复教训不再稀释召回排序。
13
+
9
14
  ## v0.1.88(2026-10-06)
10
15
 
11
16
  - **封面生成前提示词预览确认**:「生成封面」先只读展示将使用的图像提示词(新增 GET /api/tasks/{id}/cover/prompt,不触发任何模型/图像调用),确认后才走付费生成链——防误触花钱、封面元素所见即所得;取消零请求。
package/README.md CHANGED
@@ -33,10 +33,10 @@ Trae Agent、OpenClaw……),提供
33
33
  ---
34
34
 
35
35
  <!-- relnotes:start -->
36
- ### 最新版更新内容(v0.1.88)
36
+ ### 最新版更新内容(v0.1.89)
37
37
 
38
- - 封面生成前提示词预览确认:点「生成封面」先只读展示图像提示词,确认后才调用图像接口——防误触付费、所见即所得
39
- - 调研沉淀与封面预览 12 项回归测试入库
38
+ - 连载起草「相关历史章节推荐」:起草前情自动带历史章回看指针(伏笔回收/人物回场),零模型调用零额外开销
39
+ - 经验库存量去重:近重复教训并类留痕,召回排序证据不缩水
40
40
  <!-- relnotes:end -->
41
41
 
42
42
 
@@ -2587,6 +2587,55 @@ def _serial_shrunk_block(lessons, bible, kb_block, budget=12000):
2587
2587
  return "\n\n".join(p for p in (sk2, bible2, kb_block) if p), note
2588
2588
 
2589
2589
 
2590
+ _RELATED_ROW_CHARS = 80 # 单行摘录截断(记录行本体可达 1000 字,注入面只留指针)
2591
+ _RELATED_ROWS_PER_CH = 3 # 每章最多摘录的行数
2592
+ _RELATED_MIN_GRAMS = 2 # 单行命中门槛:bigram 重叠数(1 个重叠噪音过高)
2593
+
2594
+
2595
+ def _related_chapters_note(tracking_state, ch, current=0, max_n=3):
2596
+ """连载起草的「相关历史章节推荐」(纯本地重叠计分,零 LLM 调用零存储写入)。
2597
+
2598
+ 前情注入只看近 2 章结尾+findings 摘要,中期记忆(伏笔回收/人物回场)此前
2599
+ 只有账本督促兜底;story_tracking 已逐章持久化 facts/characters/foreshadowing,
2600
+ 这里拿本章大纲要点(beats/hook/highlight)与各历史章记录行做字符 bigram
2601
+ 重叠计分(复用 skills._text_bigrams,口径与经验库召回一致),按命中行数挑
2602
+ 最相关的 max_n 章输出「第 N 章:相关行摘录」指针。零匹配/无状态返回空串
2603
+ (老书零噪音);current 传当前全书章号时只看更早的章(重跑已提交章不跟
2604
+ 自己的记录自证)。"""
2605
+ ch = ch or {}
2606
+ probe = skills._text_bigrams(" ".join(
2607
+ str(ch.get(k) or "") for k in ("beats", "hook", "highlight")))
2608
+ if not probe:
2609
+ return ""
2610
+ scored = []
2611
+ for rec in (tracking_state or {}).get("chapters") or []:
2612
+ try:
2613
+ no = int(rec.get("chapter") or 0)
2614
+ except (TypeError, ValueError):
2615
+ continue
2616
+ if no < 1 or (current and no >= current):
2617
+ continue
2618
+ rows = [str((x or {}).get("text") or "") for x in rec.get("foreshadowing") or []]
2619
+ rows += [str(x) for x in rec.get("characters") or []]
2620
+ rows += [str(x) for x in rec.get("facts") or []]
2621
+ hits = []
2622
+ for row in rows:
2623
+ row = row.strip()
2624
+ if row and len(probe & skills._text_bigrams(row)) >= _RELATED_MIN_GRAMS:
2625
+ hits.append(row[:_RELATED_ROW_CHARS]
2626
+ + ("…" if len(row) > _RELATED_ROW_CHARS else ""))
2627
+ if hits:
2628
+ scored.append((len(hits), no, hits))
2629
+ scored.sort(key=lambda x: (-x[0], x[1]))
2630
+ take = scored[:max(0, int(max_n or 0))]
2631
+ if not take:
2632
+ return ""
2633
+ lines = ["### 相关历史章节(按本章大纲要点重叠检出,起草前可回看)"]
2634
+ for _, no, hits in take:
2635
+ lines.append("- 第 %d 章:%s" % (no, ";".join(hits[:_RELATED_ROWS_PER_CH])))
2636
+ return "\n".join(lines)
2637
+
2638
+
2590
2639
  def _critic_lens(critics, agent):
2591
2640
  """该评审的专属视角;单评审/手动指定时不播种(无从轮换,也别稀释注意力)。"""
2592
2641
  try:
@@ -3411,6 +3460,16 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
3411
3460
  prev += "\n\n## 此前章节发现摘要\n" + fd_txt[:3000]
3412
3461
  except Exception:
3413
3462
  pass
3463
+ # 相关历史章节推荐(story_tracking 逐章 facts/characters/foreshadowing
3464
+ # 与本章大纲要点的纯本地重叠计分):近 2 章窗口外的伏笔回收/人物回场
3465
+ # 给显式指针,经 prev 注入路径自然随降级块走(不新增预算口子);
3466
+ # 零匹配零输出(老书零噪音),失败静默。
3467
+ try:
3468
+ rel = _related_chapters_note(tracking_state, ch, current=i)
3469
+ if rel:
3470
+ prev += "\n\n" + rel
3471
+ except Exception:
3472
+ pass
3414
3473
 
3415
3474
  # 评审-修订(每章至多 1 轮修订)
3416
3475
  rounds_used = 1
@@ -513,6 +513,63 @@ def lesson_op(lesson_id, op):
513
513
  return None
514
514
 
515
515
 
516
+ def merge_lessons(primary_id, duplicate_ids):
517
+ """把近重复教训并为主条(存量聚簇的管理通道;upsert_lesson 合并路径的管理版)。
518
+
519
+ 自动去重只拦沉淀时的近似题(bigram 包含度 ≥0.8),语义近但措辞散的存量
520
+ 聚簇没有并类通道。语义=upsert 合并语义的延伸:被并条标题进主条
521
+ merged_titles(去重封顶 8,溯源+变体检索可见)、正文进 revisions 留痕
522
+ (封顶 5,带 merged_from 指明来源条),注入/结局证据计数(hits/lost/
523
+ useless/won/seen)并入主条——召回排序的 karma 证据不因合并缩水;正文与
524
+ 分类以主条为准(不批量重写内容)。返回错误说明或 None。
525
+ """
526
+ dup_ids = []
527
+ for x in (duplicate_ids or []):
528
+ did = str(x or "").strip()
529
+ if did and did not in dup_ids:
530
+ dup_ids.append(did)
531
+ if not dup_ids:
532
+ return "缺少被并条目"
533
+ with _LOCK:
534
+ data = _load()
535
+ items = data.get("lessons") or []
536
+ by_id = {}
537
+ for x in items:
538
+ by_id[x.get("id")] = x
539
+ primary = by_id.get(primary_id)
540
+ if not primary:
541
+ return "主条不存在"
542
+ if primary_id in dup_ids:
543
+ return "主条不能同时是被并条目"
544
+ dups = []
545
+ for did in dup_ids:
546
+ d = by_id.get(did)
547
+ if not d:
548
+ return "被并条目不存在 " + did
549
+ dups.append(d)
550
+ revs = primary.setdefault("revisions", [])
551
+ titles = primary.setdefault("merged_titles", [])
552
+ primary_title = str(primary.get("title") or "")
553
+ for d in dups:
554
+ content = str(d.get("content") or "")
555
+ if content:
556
+ revs.append(revisions.revision_record(
557
+ "skrev", content, source=d.get("source") or "",
558
+ extra={"content": content, "merged_from": d.get("id")}))
559
+ title = str(d.get("title") or "")
560
+ if title and title != primary_title and title not in titles:
561
+ titles.append(title)
562
+ for k in ("hits", "lost", "useless", "won", "seen"):
563
+ primary[k] = int(primary.get(k) or 0) + int(d.get(k) or 0)
564
+ primary["revisions"] = revs[-5:]
565
+ primary["merged_titles"] = titles[-8:]
566
+ primary["updated_at"] = _now()
567
+ drop = {d.get("id") for d in dups}
568
+ data["lessons"] = [x for x in items if x.get("id") not in drop]
569
+ _save(data)
570
+ return None
571
+
572
+
516
573
  def pack_op(pack_id, op):
517
574
  """启用/停用包(内置与用户自建均可停用;用户包不可通过此接口删除,删文件即可)。"""
518
575
  if op not in ("enable", "disable"):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebee",
3
- "version": "0.1.88",
3
+ "version": "0.1.89",
4
4
  "description": "CodeBee · 多智能体编排台 — 统一编排本机 AI 编码 CLI(Codex / Claude Code / QwenCode / OpenCode / Aider…):目标→拆解→路由→执行→验证→跨厂商评审→修复→报告。纯 Python 标准库实现,本地 Web UI。",
5
5
  "license": "MIT",
6
6
  "bin": {