personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: fix ruff formatting in Granola importer and home app

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+75 -63
+35 -29
apps/home/routes.py
··· 78 78 continue 79 79 80 80 is_principal = meta.get("is_principal", False) 81 - nodes.append({ 82 - "id": entity_id or entity_name, 83 - "name": meta.get("name") or entity_name, 84 - "type": entity_type, 85 - "score": r["score"], 86 - "co_occurrence": r["co_occurrence"], 87 - "appearance": r["appearance"], 88 - "recency": r["recency"], 89 - "facet_breadth": r["facet_breadth"], 90 - "observation_depth": r["observation_depth"], 91 - "is_principal": is_principal, 92 - }) 81 + nodes.append( 82 + { 83 + "id": entity_id or entity_name, 84 + "name": meta.get("name") or entity_name, 85 + "type": entity_type, 86 + "score": r["score"], 87 + "co_occurrence": r["co_occurrence"], 88 + "appearance": r["appearance"], 89 + "recency": r["recency"], 90 + "facet_breadth": r["facet_breadth"], 91 + "observation_depth": r["observation_depth"], 92 + "is_principal": is_principal, 93 + } 94 + ) 93 95 node_names.add(entity_name) 94 96 if entity_id: 95 97 node_names.add(entity_id) ··· 120 122 total_entities = conn.execute( 121 123 "SELECT COUNT(DISTINCT entity_id) FROM entities WHERE source='identity'" 122 124 ).fetchone()[0] 123 - total_signals = conn.execute( 124 - "SELECT COUNT(*) FROM entity_signals" 125 - ).fetchone()[0] 125 + total_signals = conn.execute("SELECT COUNT(*) FROM entity_signals").fetchone()[ 126 + 0 127 + ] 126 128 127 129 finally: 128 130 conn.close() 129 131 130 - return jsonify({ 131 - "nodes": nodes, 132 - "edges": edges, 133 - "stats": { 134 - "total_entities": total_entities, 135 - "total_signals": total_signals, 136 - }, 137 - }) 132 + return jsonify( 133 + { 134 + "nodes": nodes, 135 + "edges": edges, 136 + "stats": { 137 + "total_entities": total_entities, 138 + "total_signals": total_signals, 139 + }, 140 + } 141 + ) 138 142 139 143 140 144 @home_bp.route("/api/entity/<path:name>") ··· 257 261 for r in rows: 258 262 if (r[0], r[1]) in explicit_pairs: 259 263 continue 260 - edges.append({ 261 - "from_name": r[0], 262 - "to_name": r[1], 263 - "frequency": r[2], 264 - "edge_type": "co_occurrence", 265 - }) 264 + edges.append( 265 + { 266 + "from_name": r[0], 267 + "to_name": r[1], 268 + "frequency": r[2], 269 + "edge_type": "co_occurrence", 270 + } 271 + ) 266 272 267 273 return edges 268 274
+1 -3
apps/settings/routes.py
··· 1556 1556 }, 1557 1557 "granola": { 1558 1558 "enabled": ( 1559 - granola_entry.get("enabled", True) 1560 - if granola_entry 1561 - else False 1559 + granola_entry.get("enabled", True) if granola_entry else False 1562 1560 ), 1563 1561 "configured": bool(granola_entry), 1564 1562 },
+23 -13
tests/test_importer_granola.py
··· 9 9 10 10 import pytest 11 11 12 - 13 12 # --------------------------------------------------------------------------- 14 13 # Helpers 15 14 # --------------------------------------------------------------------------- ··· 225 224 from think.importers.granola import _parse_transcript_entries 226 225 227 226 base_dt = dt.datetime(2025, 10, 28, 0, 0, 0, tzinfo=dt.timezone.utc) 228 - messages = _parse_transcript_entries("No transcript here.", base_dt, dt.timezone.utc) 227 + messages = _parse_transcript_entries( 228 + "No transcript here.", base_dt, dt.timezone.utc 229 + ) 229 230 assert messages == [] 230 231 231 232 ··· 236 237 237 238 def test_date_from_filename(): 238 239 """Extract date from muesli filename.""" 239 - from think.importers.granola import _date_from_filename 240 - 241 240 import datetime as dt 241 + 242 + from think.importers.granola import _date_from_filename 242 243 243 244 assert _date_from_filename("2025-10-28_q1-planning.md") == dt.date(2025, 10, 28) 244 245 assert _date_from_filename("random-file.md") is None ··· 374 375 def test_granola_sync_incremental(tmp_path): 375 376 """Second sync skips already-imported transcripts.""" 376 377 from think.importers.granola import GranolaBackend 377 - from think.importers.sync import load_sync_state, save_sync_state 378 + from think.importers.sync import save_sync_state 378 379 379 380 muesli_dir = tmp_path / "muesli" 380 381 _write_transcript(muesli_dir, "2025-10-28_q1.md", SAMPLE_TRANSCRIPT) ··· 502 503 muesli_dir = tmp_path / "muesli" 503 504 _write_transcript(muesli_dir, "2025-10-28_q1.md", SAMPLE_TRANSCRIPT) 504 505 505 - result = GranolaBackend().sync( 506 - tmp_path, source_path=muesli_dir, dry_run=False 507 - ) 506 + result = GranolaBackend().sync(tmp_path, source_path=muesli_dir, dry_run=False) 508 507 509 508 assert result["downloaded"] == 1 510 509 assert result["imported"] == 1 ··· 523 522 # so we check for import.granola directories 524 523 import glob 525 524 526 - segments = glob.glob(str(tmp_path / "*/import.granola/*/conversation_transcript.jsonl")) 525 + segments = glob.glob( 526 + str(tmp_path / "*/import.granola/*/conversation_transcript.jsonl") 527 + ) 527 528 assert len(segments) >= 1 528 529 529 530 # Check JSONL content of first segment ··· 639 640 # Alice: title + company 640 641 alice_obs = load_observations("import.granola", "Alice Smith") 641 642 alice_contents = [o["content"] for o in alice_obs] 642 - assert "Engineering Manager at Acme Corp (via Granola, 2025-10-28)" in alice_contents 643 + assert ( 644 + "Engineering Manager at Acme Corp (via Granola, 2025-10-28)" in alice_contents 645 + ) 643 646 644 647 # Bob: no title, no company, no linkedin — no observations 645 648 bob_obs = load_observations("import.granola", "Bob Jones") ··· 649 652 jane_obs = load_observations("import.granola", "Jane Doe") 650 653 jane_contents = [o["content"] for o in jane_obs] 651 654 assert "CTO at StartupCo (via Granola, 2025-10-28)" in jane_contents 652 - assert "LinkedIn: linkedin.com/in/janedoe (via Granola, 2025-10-28)" in jane_contents 655 + assert ( 656 + "LinkedIn: linkedin.com/in/janedoe (via Granola, 2025-10-28)" in jane_contents 657 + ) 653 658 654 659 # Carlos: title only (Designer, no company) 655 660 carlos_obs = load_observations("import.granola", "Carlos Garcia") ··· 748 753 { 749 754 "name": "Person D", 750 755 "type": "Person", 751 - "observations": ["LinkedIn: linkedin.com/in/persond (via Granola, 2025-10-28)"], 756 + "observations": [ 757 + "LinkedIn: linkedin.com/in/persond (via Granola, 2025-10-28)" 758 + ], 752 759 }, 753 760 ] 754 761 seed_entities("test.facet", "20251028", entities) ··· 768 775 769 776 d_obs = load_observations("test.facet", "Person D") 770 777 assert len(d_obs) == 1 771 - assert d_obs[0]["content"] == "LinkedIn: linkedin.com/in/persond (via Granola, 2025-10-28)" 778 + assert ( 779 + d_obs[0]["content"] 780 + == "LinkedIn: linkedin.com/in/persond (via Granola, 2025-10-28)" 781 + ) 772 782 773 783 774 784 def test_seed_entities_observation_dedup(tmp_path, monkeypatch):
+9 -3
think/importers/cli.py
··· 128 128 if result.returncode == 0: 129 129 print(" muesli sync complete.") 130 130 else: 131 - logger.warning("muesli sync exited with code %d: %s", result.returncode, result.stderr.strip()) 132 - print(f" muesli sync failed (exit {result.returncode}), continuing with existing files.") 131 + logger.warning( 132 + "muesli sync exited with code %d: %s", 133 + result.returncode, 134 + result.stderr.strip(), 135 + ) 136 + print( 137 + f" muesli sync failed (exit {result.returncode}), continuing with existing files." 138 + ) 133 139 return result.returncode == 0 134 140 except subprocess.TimeoutExpired: 135 141 logger.warning("muesli sync timed out after 60s") ··· 232 238 else: 233 239 print(f" - {name}") 234 240 print() 235 - print(f"Run with --save to import:") 241 + print("Run with --save to import:") 236 242 print(f" sol import --sync {backend_name} --save") 237 243 238 244 if not dry_run and available == 0 and downloaded == 0:
+7 -15
think/importers/granola.py
··· 277 277 company = p.get("company", "") 278 278 linkedin = p.get("linkedin", "") 279 279 if title and company: 280 - observations.append( 281 - f"{title} at {company} (via Granola, {obs_date})" 282 - ) 280 + observations.append(f"{title} at {company} (via Granola, {obs_date})") 283 281 elif title: 284 282 observations.append(f"{title} (via Granola, {obs_date})") 285 283 elif company: 286 - observations.append( 287 - f"Works at {company} (via Granola, {obs_date})" 288 - ) 284 + observations.append(f"Works at {company} (via Granola, {obs_date})") 289 285 if linkedin: 290 286 observations.append( 291 287 f"LinkedIn: linkedin.com/in/{linkedin} (via Granola, {obs_date})" ··· 298 294 seeded = seed_entities("import.granola", first_day, entity_dicts) 299 295 entities_seeded = len(seeded) 300 296 except Exception as exc: 301 - logger.warning( 302 - "Entity seeding failed for %s: %s", md_file.name, exc 303 - ) 297 + logger.warning("Entity seeding failed for %s: %s", md_file.name, exc) 304 298 305 299 return created_files, segments, entities_seeded 306 300 ··· 435 429 436 430 # Compute summary 437 431 total = len(known_files) 438 - imported = sum( 439 - 1 for f in known_files.values() if f.get("status") == "imported" 440 - ) 432 + imported = sum(1 for f in known_files.values() if f.get("status") == "imported") 441 433 available = len(to_import) 442 434 skipped_total = sum( 443 435 1 for f in known_files.values() if f.get("status") == "skipped" ··· 466 458 467 459 if files: 468 460 known_files[doc_id]["status"] = "imported" 469 - known_files[doc_id][ 470 - "imported_at" 471 - ] = dt.datetime.now().isoformat() 461 + known_files[doc_id]["imported_at"] = ( 462 + dt.datetime.now().isoformat() 463 + ) 472 464 known_files[doc_id]["segments"] = len(segs) 473 465 known_files[doc_id]["entities_seeded"] = entities 474 466 downloaded += 1