codebee 0.1.65 → 0.1.67

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 (116) hide show
  1. package/CHANGELOG.md +532 -513
  2. package/LICENSE +21 -21
  3. package/README.md +494 -487
  4. package/app/core/acceptance.py +100 -0
  5. package/app/core/aiflavor.py +197 -197
  6. package/app/core/artifacts.py +148 -148
  7. package/app/core/assets.py +110 -0
  8. package/app/core/attachments.py +623 -532
  9. package/app/core/automation.py +758 -758
  10. package/app/core/backup.py +470 -470
  11. package/app/core/beekeeper.py +183 -183
  12. package/app/core/board.py +209 -209
  13. package/app/core/bookmeta.py +930 -759
  14. package/app/core/bookmeta_catalog.py +180 -180
  15. package/app/core/branching.py +137 -137
  16. package/app/core/builtin_agent.py +1234 -1191
  17. package/app/core/capability.py +144 -144
  18. package/app/core/catalog.py +23 -24
  19. package/app/core/chaptersafe.py +32 -32
  20. package/app/core/cleanup.py +303 -303
  21. package/app/core/compaction.py +201 -186
  22. package/app/core/covergen.py +356 -356
  23. package/app/core/defectretro.py +43 -0
  24. package/app/core/diagnostics.py +114 -114
  25. package/app/core/dispatch.py +173 -173
  26. package/app/core/dispatch_log.py +113 -113
  27. package/app/core/env_scrub.py +85 -85
  28. package/app/core/error_codes.py +220 -65
  29. package/app/core/errorlog.py +137 -137
  30. package/app/core/evaluation.py +174 -0
  31. package/app/core/failure_notify.py +290 -0
  32. package/app/core/flows.py +401 -355
  33. package/app/core/goal_service.py +167 -167
  34. package/app/core/health.py +374 -342
  35. package/app/core/history.py +38 -38
  36. package/app/core/hooks.py +162 -156
  37. package/app/core/jobs.py +1050 -1022
  38. package/app/core/knowledge.py +56 -5
  39. package/app/core/manager.py +2122 -1996
  40. package/app/core/market.py +362 -362
  41. package/app/core/market_remote.py +190 -92
  42. package/app/core/mocks.py +64 -64
  43. package/app/core/modelhub.py +3546 -3332
  44. package/app/core/notify.py +106 -106
  45. package/app/core/operations.py +228 -0
  46. package/app/core/paihang.py +138 -109
  47. package/app/core/paths.py +2 -2
  48. package/app/core/pipeline.py +4808 -4494
  49. package/app/core/planner.py +37 -32
  50. package/app/core/portguard.py +111 -111
  51. package/app/core/preview.py +293 -293
  52. package/app/core/publish/__init__.py +7 -7
  53. package/app/core/publish/auto.py +367 -367
  54. package/app/core/publish/browser.py +539 -533
  55. package/app/core/publish/fanqie.py +159 -128
  56. package/app/core/publish/flow.py +440 -406
  57. package/app/core/publish/flows-fanqie-calibrated.json +17 -9
  58. package/app/core/publish/flows-qimao-calibrated.json +6 -2
  59. package/app/core/publish/ledger.py +203 -198
  60. package/app/core/publish/manager.py +692 -590
  61. package/app/core/publish/qimao.py +141 -141
  62. package/app/core/publish/ws.py +139 -139
  63. package/app/core/redact.py +38 -38
  64. package/app/core/repeat_guard.py +129 -124
  65. package/app/core/revisions.py +51 -0
  66. package/app/core/router.py +119 -81
  67. package/app/core/runner.py +2065 -2042
  68. package/app/core/selfupdate.py +384 -384
  69. package/app/core/session_log.py +161 -161
  70. package/app/core/sessions.py +312 -312
  71. package/app/core/settings.py +149 -149
  72. package/app/core/settings_schema.py +262 -262
  73. package/app/core/share_page.py +114 -114
  74. package/app/core/skill_scan.py +114 -106
  75. package/app/core/skillpacks/fanqie-novel.md +80 -80
  76. package/app/core/skillpacks/market/character-bible.md +66 -66
  77. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  78. package/app/core/skillpacks/market/git-workflow.md +57 -57
  79. package/app/core/skillpacks/market/release-notes.md +72 -72
  80. package/app/core/skillpacks/market/weekly-report.md +71 -71
  81. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  82. package/app/core/skills.py +315 -73
  83. package/app/core/step_runner.py +132 -132
  84. package/app/core/store.py +230 -11
  85. package/app/core/task_compile.py +199 -199
  86. package/app/core/telemetry.py +292 -292
  87. package/app/core/tlsctx.py +64 -64
  88. package/app/core/token_meter.py +147 -147
  89. package/app/core/upstream.py +41 -0
  90. package/app/core/usage.py +114 -3
  91. package/app/core/volumes.py +389 -355
  92. package/app/core/wxdigest.py +858 -858
  93. package/app/core/zentao.py +2465 -2447
  94. package/app/main.py +2957 -2832
  95. package/app/pick_dialog.py +110 -110
  96. package/app/ui/app.js +14384 -13977
  97. package/app/ui/artdiff.js +74 -0
  98. package/app/ui/board.css +20 -41
  99. package/app/ui/board.html +162 -162
  100. package/app/ui/board.js +305 -305
  101. package/app/ui/i18n.js +2766 -2711
  102. package/app/ui/icons/bee.svg +79 -79
  103. package/app/ui/index.html +7 -1
  104. package/app/ui/manifest.json +16 -16
  105. package/app/ui/qrcode.js +2297 -2297
  106. package/app/ui/style.css +6179 -6125
  107. package/bin/tutti.js +147 -147
  108. package/package.json +46 -41
  109. package/tools/wx_reader.py +261 -261
  110. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
  111. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
  112. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  113. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
  114. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  115. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  116. package/app/.playwright-cli/run-detail-after.png +0 -0
@@ -29,7 +29,7 @@ import threading
29
29
  import time
30
30
  from pathlib import Path
31
31
 
32
- from . import paths, skills
32
+ from . import paths, revisions, skills
33
33
 
34
34
  _LOCK = threading.RLock()
35
35
  _FILE = paths.DATA_DIR / "knowledge.json"
@@ -82,6 +82,29 @@ def list_entries(scope=None, status=None, tag=None, only_enabled=False):
82
82
  return items
83
83
 
84
84
 
85
+ def revision_for_task(task) -> dict:
86
+ """Return approved knowledge references selected by task scope."""
87
+ scope = str((task or {}).get("type") or "*")
88
+ entries = list_entries(scope=scope, status="approved", only_enabled=True)
89
+ refs = []
90
+ for entry in entries:
91
+ body = entry.get("body") or ""
92
+ digest = revisions.content_sha256(body)
93
+ stored_digest = str(entry.get("content_sha256") or "")
94
+ drift = bool(stored_digest and stored_digest != digest)
95
+ refs.append({"id": entry.get("id") or "",
96
+ "revision_id": (entry.get("revision_id")
97
+ if stored_digest == digest else
98
+ revisions.revision_id("kbrev", digest)) or
99
+ revisions.revision_id("kbrev", digest),
100
+ "content_sha256": digest,
101
+ "revision_drift": drift})
102
+ payload = {"scope": scope, "entries": refs}
103
+ digest = revisions.stable_digest(payload)
104
+ return {"revision_id": revisions.revision_id("knowledge", digest),
105
+ "content_sha256": digest, "scope": scope, "entries": refs}
106
+
107
+
85
108
  def _title_sim(a, b):
86
109
  """标题近似度:字符 bigram 的包含度(交集 / 较短者,中文友好零分词)。
87
110
  用包含度而非 Jaccard:「X」vs「X 指南」的较短者完全被包含 →1.0,
@@ -131,13 +154,25 @@ def upsert_entry(scope, title, body, tags=None, source="", source_file="",
131
154
  if hit is not None:
132
155
  it = hit
133
156
  it["seen"] = int(it.get("seen") or 1) + 1
157
+ old_body = str(it.get("body") or "")
158
+ old_rev = it.get("revision_id") or ""
159
+ new_rev = revisions.make_revision("kbrev", body, source=source)
134
160
  if status == "approved" or it.get("status") != "approved":
161
+ if old_body and old_body != body:
162
+ it.setdefault("revisions", []).append(
163
+ revisions.revision_record(
164
+ "kbrev", old_body, source=it.get("source") or "",
165
+ extra={"body": old_body, "revision_id": old_rev or
166
+ revisions.revision_id("kbrev", revisions.content_sha256(old_body))}))
167
+ it["revisions"] = it["revisions"][-5:]
135
168
  it["body"] = body
169
+ it.update({k: new_rev[k] for k in ("revision_id", "content_sha256")})
136
170
  if status == "approved":
137
171
  it["status"] = "approved"
138
172
  else:
139
173
  it.setdefault("revisions", []).append(
140
- {"at": _now(), "body": body, "source": source})
174
+ revisions.revision_record("kbrev", body, source=source,
175
+ extra={"body": body}))
141
176
  it["revisions"] = it["revisions"][-5:] # 最多留 5 条修订候选
142
177
  for tg in tags:
143
178
  if tg not in (it.get("tags") or []):
@@ -154,6 +189,7 @@ def upsert_entry(scope, title, body, tags=None, source="", source_file="",
154
189
  it["updated_at"] = _now()
155
190
  _save(data)
156
191
  return it
192
+ rev = revisions.make_revision("kbrev", body, source=source)
157
193
  it = {"id": kid, "scope": scope, "title": title, "body": body,
158
194
  "tags": tags, "status": status, "enabled": True,
159
195
  "source": source, "source_file": source_file,
@@ -161,6 +197,7 @@ def upsert_entry(scope, title, body, tags=None, source="", source_file="",
161
197
  "confidence": confidence or "medium",
162
198
  "revisions": [], "hits": 0, "seen": 1,
163
199
  "created_at": _now(), "updated_at": _now(), "kind": "knowledge"}
200
+ it.update({k: rev[k] for k in ("revision_id", "content_sha256")})
164
201
  items.append(it)
165
202
  _save(data)
166
203
  return it
@@ -197,7 +234,17 @@ def entry_op(entry_id, op, fields=None):
197
234
  if str(f.get("title") or "").strip():
198
235
  hit["title"] = str(f["title"]).strip()[:80]
199
236
  if str(f.get("body") or "").strip():
200
- hit["body"] = str(f["body"]).strip()[:1500]
237
+ new_body = str(f["body"]).strip()[:1500]
238
+ old_body = str(hit.get("body") or "")
239
+ if old_body != new_body:
240
+ hit.setdefault("revisions", []).append(
241
+ revisions.revision_record(
242
+ "kbrev", old_body, source=hit.get("source") or "",
243
+ extra={"body": old_body}))
244
+ hit["revisions"] = hit["revisions"][-5:]
245
+ hit["body"] = new_body
246
+ hit.update({k: revisions.make_revision("kbrev", new_body)[k]
247
+ for k in ("revision_id", "content_sha256")})
201
248
  if "tags" in f:
202
249
  hit["tags"] = [str(t).strip()[:20]
203
250
  for t in (f.get("tags") or []) if str(t).strip()][:8]
@@ -238,8 +285,12 @@ def block_for(task):
238
285
  | skills._text_bigrams(x.get("body"))
239
286
  | set(x.get("tags") or []))
240
287
  # 过期降权(inkos 检索保留来源/位置的启发):同等相关性下,
241
- # 可能过期的事实排在新鲜事实之后——旧知识不该压过新知识
242
- return (-len(probe & grams), 1 if _is_stale(x.get("as_of")) else 0,
288
+ # 可能过期的事实排在新鲜事实之后——旧知识不该压过新知识。
289
+ # 命中热度平级决胜(教训库 karma 的轻量同构):_bump_hits 记的
290
+ # 注入命中数此前只进不出;相关性同档时高频命中条目优先——
291
+ # 被反复召回的知识已被任务面验证过可用性,压过从未被选中的
292
+ return (-len(probe & grams), -(int(x.get("hits") or 0)),
293
+ 1 if _is_stale(x.get("as_of")) else 0,
243
294
  x.get("id") or "")
244
295
  entries = sorted(entries, key=rank)
245
296
  else: