feat: recover evolving threads from freeform notes
This commit is contained in:
@@ -64,3 +64,71 @@ def test_tool_loop_uses_no_tool_convergence_repair(
|
||||
assert "convergence_repair_started" in [
|
||||
event["event_type"] for event in events
|
||||
]
|
||||
|
||||
|
||||
def test_curator_recovers_visible_prior_fragment(
|
||||
tmp_path: Path, monkeypatch
|
||||
):
|
||||
admin = UserSeed(
|
||||
id="admin",
|
||||
label="管理员",
|
||||
role="admin",
|
||||
access_key_hash="unused",
|
||||
)
|
||||
database = Database(tmp_path / "recovery.sqlite3")
|
||||
database.initialize([admin])
|
||||
earlier = database.create_fragment(
|
||||
"admin", "有个新工作可能要做小爱架构。"
|
||||
)
|
||||
database.apply_assessments("admin", earlier["id"], [])
|
||||
current = database.create_fragment(
|
||||
"admin", "确定要做,先熟悉 harness 5.0。"
|
||||
)
|
||||
settings = Settings(
|
||||
data_dir=tmp_path,
|
||||
database_path=tmp_path / "recovery.sqlite3",
|
||||
users=(admin,),
|
||||
session_secret="unused",
|
||||
deepseek_api_key="test-key",
|
||||
deepseek_base_url="https://api.deepseek.com",
|
||||
deepseek_model="deepseek-v4-pro",
|
||||
cookie_secure=False,
|
||||
auth_disabled=True,
|
||||
)
|
||||
|
||||
async def fake_run(*args, **kwargs):
|
||||
return SimpleNamespace(
|
||||
final_output=(
|
||||
"{"
|
||||
'"standalone":false,'
|
||||
'"reasoning_note":"明确的新工作,应形成可跟踪脉络",'
|
||||
'"assessments":[{'
|
||||
'"idea_id":null,'
|
||||
'"title":"小爱新架构开发",'
|
||||
'"summary":"熟悉现有方案并推进新架构开发。",'
|
||||
'"maturity":10,'
|
||||
'"confidence":0.9,'
|
||||
'"motion":"启动中",'
|
||||
'"position":"先熟悉 harness 5.0 核心方案。",'
|
||||
'"tension":"任务已明确,但范围与现有方案尚未理解。",'
|
||||
'"trajectory":"从可能插入的工作变成明确承诺。",'
|
||||
'"possible_moves":["阅读核心方案"],'
|
||||
'"relevance":1,'
|
||||
f'"supporting_fragment_ids":["{earlier["id"]}"]'
|
||||
"}]}"
|
||||
)
|
||||
)
|
||||
|
||||
monkeypatch.setattr(Runner, "run", fake_run)
|
||||
curator = Curator(database, settings)
|
||||
|
||||
asyncio.run(curator.analyze(current["id"]))
|
||||
|
||||
idea = database.get_idea(
|
||||
"admin", database.list_ideas("admin")[0]["id"]
|
||||
)
|
||||
assert idea["title"] == "小爱新架构开发"
|
||||
assert [item["id"] for item in idea["fragments"]] == [
|
||||
earlier["id"],
|
||||
current["id"],
|
||||
]
|
||||
|
||||
+60
-1
@@ -25,7 +25,11 @@ def database(tmp_path: Path) -> Database:
|
||||
return db
|
||||
|
||||
|
||||
def assessment(idea_id: str | None = None, maturity: float = 38):
|
||||
def assessment(
|
||||
idea_id: str | None = None,
|
||||
maturity: float = 38,
|
||||
supporting_fragment_ids: list[str] | None = None,
|
||||
):
|
||||
return {
|
||||
"idea_id": idea_id,
|
||||
"title": "无分类的笔记入口",
|
||||
@@ -38,6 +42,7 @@ def assessment(idea_id: str | None = None, maturity: float = 38):
|
||||
"trajectory": "从输入摩擦的感受,收束成了产品边界。",
|
||||
"possible_moves": ["做一个只保留原文的输入原型"],
|
||||
"relevance": 1,
|
||||
"supporting_fragment_ids": supporting_fragment_ids or [],
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +124,60 @@ def test_curator_prompt_contains_recent_continuity_and_idea_catalog(
|
||||
assert "我识别出了两个新的认知" in prompt
|
||||
assert "无分类的笔记入口" in prompt
|
||||
assert "不要要求用户显式建立会话" in prompt
|
||||
assert "supporting_fragment_ids" in prompt
|
||||
assert "是否形成新的未完成方向" in prompt
|
||||
|
||||
|
||||
def test_later_fragment_can_recover_earlier_standalone_evidence(
|
||||
tmp_path: Path,
|
||||
):
|
||||
db = database(tmp_path)
|
||||
earlier = db.create_fragment(
|
||||
ADMIN.id, "也许该重新设计小爱的架构,但现在还说不清。"
|
||||
)
|
||||
db.apply_assessments(ADMIN.id, earlier["id"], [])
|
||||
current = db.create_fragment(
|
||||
ADMIN.id, "这个新工作确定要做,我先熟悉 harness 5.0。"
|
||||
)
|
||||
|
||||
db.apply_assessments(
|
||||
ADMIN.id,
|
||||
current["id"],
|
||||
[assessment(supporting_fragment_ids=[earlier["id"]])],
|
||||
)
|
||||
|
||||
idea = db.get_idea(ADMIN.id, db.list_ideas(ADMIN.id)[0]["id"])
|
||||
assert [item["content"] for item in idea["fragments"]] == [
|
||||
earlier["content"],
|
||||
current["content"],
|
||||
]
|
||||
assert idea["snapshots"][-1]["fragment_ids"] == [
|
||||
earlier["id"],
|
||||
current["id"],
|
||||
]
|
||||
|
||||
|
||||
def test_recovered_evidence_cannot_cross_user_boundary(tmp_path: Path):
|
||||
db = database(tmp_path)
|
||||
member_fragment = db.create_fragment(MEMBER.id, "另一个人的记录")
|
||||
current = db.create_fragment(ADMIN.id, "管理员的新方向")
|
||||
|
||||
try:
|
||||
db.apply_assessments(
|
||||
ADMIN.id,
|
||||
current["id"],
|
||||
[
|
||||
assessment(
|
||||
supporting_fragment_ids=[member_fragment["id"]]
|
||||
)
|
||||
],
|
||||
)
|
||||
except ValueError as exc:
|
||||
assert str(exc) == "supporting fragment not found for user"
|
||||
else:
|
||||
raise AssertionError("cross-user supporting fragment was accepted")
|
||||
|
||||
assert db.list_ideas(ADMIN.id) == []
|
||||
|
||||
|
||||
def test_delayed_reanalysis_keeps_later_input_out_of_prior_context(
|
||||
|
||||
Reference in New Issue
Block a user