from pathlib import Path from zotero_kb.cards import CardBuilder from zotero_kb.config import AppConfig from zotero_kb.projects import ProjectService from zotero_kb.workspace import Workspace from zotero_kb.writing import WritingService from zotero_kb.zotero_reader import ZoteroItemRecord class FakeLlmClient: def generate_card(self, source_bundle: dict[str, object]) -> dict[str, object]: title = str(source_bundle["title"]) if "Scoped" in title: summary = "Project-scoped retrieval improves drafting support." claims = ["Project scoping improves citation precision."] hints = ["Use for scoped retrieval arguments."] else: summary = "Broad retrieval baseline for comparison." claims = ["Baseline retrieval may drift."] hints = ["Use as a contrast only."] return { "summary": summary, "core_claims": claims, "methods": ["Method details."], "evidence": ["Evidence details."], "quotable_passages": claims, "writing_hints": hints, "keywords": ["retrieval", "writing"], } def _build_item(item_key: str, title: str) -> ZoteroItemRecord: return ZoteroItemRecord( item_key=item_key, title=title, creators=["Alice Smith"], year="2024", item_type="journalArticle", abstract=title, tags=["retrieval"], collection_paths=[["Theory", "Drafting"]], notes=[title], attachment_texts=[title], ) def test_recommend_citations_only_reads_project_items(tmp_path: Path) -> None: config = AppConfig(workspace_dir=tmp_path / "workspace", zotero_data_dir=tmp_path / "zotero") workspace = Workspace(config) workspace.create_project("thesis-ch2", "Thesis Chapter 2", "openai", "gpt-5-mini") builder = CardBuilder(config.workspace_dir, FakeLlmClient()) builder.build_or_update(_build_item("PAPER0001", "Scoped Retrieval for Drafting")) builder.build_or_update(_build_item("PAPER0002", "Broad Retrieval Baseline")) project_service = ProjectService(config.workspace_dir) project_service.add_items("thesis-ch2", ["PAPER0001"]) service = WritingService(config.workspace_dir) result = service.recommend_citations("thesis-ch2", "support scoped retrieval during drafting") assert [item["item_key"] for item in result["results"]] == ["PAPER0001"] def test_generate_plan_returns_structured_sections(tmp_path: Path) -> None: config = AppConfig(workspace_dir=tmp_path / "workspace", zotero_data_dir=tmp_path / "zotero") workspace = Workspace(config) workspace.create_project("thesis-ch2", "Thesis Chapter 2", "openai", "gpt-5-mini") builder = CardBuilder(config.workspace_dir, FakeLlmClient()) builder.build_or_update(_build_item("PAPER0001", "Scoped Retrieval for Drafting")) project_service = ProjectService(config.workspace_dir) project_service.add_items("thesis-ch2", ["PAPER0001"]) service = WritingService(config.workspace_dir) plan = service.generate_plan("thesis-ch2", "argue that project scoping improves drafting") assert "sections" in plan assert plan["sections"][0]["citations"][0]["item_key"] == "PAPER0001"