personal memory agent
at main 741 lines 25 kB view raw
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4"""Tests for think.facets module.""" 5 6import json 7from pathlib import Path 8 9import pytest 10from slugify import slugify 11 12from think.facets import ( 13 _format_principal_role, 14 _get_principal_display_name, 15 facet_summaries, 16 facet_summary, 17 get_active_facets, 18 get_facets, 19) 20 21# Use the permanent fixtures in tests/fixtures/journal/facets/ 22FIXTURES_PATH = Path(__file__).parent / "fixtures" / "journal" 23 24 25def setup_entities_new_structure( 26 journal_path: Path, 27 facet: str, 28 entities: list[dict], 29): 30 """Helper to set up entities using the new structure for tests. 31 32 Creates both journal-level entity files and facet relationship files. 33 34 Args: 35 journal_path: Path to journal root 36 facet: Facet name (e.g., "work") 37 entities: List of entity dicts with type, name, description, etc. 38 """ 39 for entity in entities: 40 etype = entity.get("type", "") 41 name = entity.get("name", "") 42 desc = entity.get("description", "") 43 is_principal = entity.get("is_principal", False) 44 45 entity_id = slugify(name, separator="_") 46 if not entity_id: 47 continue 48 49 # Create journal-level entity 50 journal_entity_dir = journal_path / "entities" / entity_id 51 journal_entity_dir.mkdir(parents=True, exist_ok=True) 52 journal_entity = {"id": entity_id, "name": name, "type": etype} 53 if is_principal: 54 journal_entity["is_principal"] = True 55 with open(journal_entity_dir / "entity.json", "w", encoding="utf-8") as f: 56 json.dump(journal_entity, f) 57 58 # Create facet relationship 59 facet_entity_dir = journal_path / "facets" / facet / "entities" / entity_id 60 facet_entity_dir.mkdir(parents=True, exist_ok=True) 61 relationship = {"entity_id": entity_id, "description": desc} 62 with open(facet_entity_dir / "entity.json", "w", encoding="utf-8") as f: 63 json.dump(relationship, f) 64 65 66def test_facet_summary_full(monkeypatch): 67 """Test facet_summary with full metadata.""" 68 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 69 70 summary = facet_summary("full-featured") 71 72 # Check title without emoji 73 assert "# Full Featured Facet" in summary 74 75 # Check description 76 assert "**Description:** A facet for testing all features" in summary 77 78 # Check color badge 79 assert "![Color](#28a745)" in summary 80 81 # Check entities section 82 assert "## Entities" in summary 83 assert "**Entity 1**: First test entity" in summary 84 assert "**Entity 2**: Second test entity" in summary 85 assert "**Entity 3**: Third test entity with description" in summary 86 87 # Check activities section 88 assert "## Activities" in summary 89 assert "**Meetings** (high)" in summary 90 assert "**Coding**" in summary 91 assert "**Custom Activity**:" in summary 92 assert "A custom test activity" in summary 93 94 95def test_facet_summary_short_mode(monkeypatch): 96 """Test facet_summary with detailed=False shows names only.""" 97 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 98 99 summary = facet_summary("full-featured", detailed=False) 100 101 # Check title and description still present 102 assert "# Full Featured Facet" in summary 103 assert "**Description:** A facet for testing all features" in summary 104 105 # Should NOT have detailed entities section 106 assert "## Entities" not in summary 107 # Should have inline entities list 108 assert "**Entities**:" in summary 109 110 # Should NOT have detailed activities section 111 assert "## Activities" not in summary 112 # Should have inline activities list 113 assert "**Activities**:" in summary 114 115 # Should NOT have activity descriptions 116 assert "A custom test activity" not in summary 117 118 119def test_facet_summary_minimal(monkeypatch): 120 """Test facet_summary with minimal metadata.""" 121 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 122 123 summary = facet_summary("minimal-facet") 124 125 # Check title without emoji 126 assert "# Minimal Facet" in summary 127 128 # Should not have description, color, or entities 129 assert "**Description:**" not in summary 130 assert "![Color]" not in summary 131 assert "## Entities" not in summary 132 133 134def test_facet_summary_test_facet(monkeypatch): 135 """Test facet_summary with the existing test-facet fixture.""" 136 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 137 138 summary = facet_summary("test-facet") 139 140 # Check title without emoji 141 assert "# Test Facet" in summary 142 143 # Check description 144 assert "**Description:** A test facet for validating functionality" in summary 145 146 # Check color badge 147 assert "![Color](#007bff)" in summary 148 149 150def test_facet_summary_nonexistent(monkeypatch): 151 """Test facet_summary with nonexistent facet.""" 152 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 153 154 with pytest.raises(FileNotFoundError, match="Facet 'nonexistent' not found"): 155 facet_summary("nonexistent") 156 157 158def test_facet_summary_empty_journal(tmp_path, monkeypatch): 159 """Test facet_summary raises FileNotFoundError with empty journal.""" 160 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 161 162 with pytest.raises(FileNotFoundError, match="not found"): 163 facet_summary("any-facet") 164 165 166def test_facet_summary_missing_facet_json(monkeypatch): 167 """Test facet_summary with missing facet.json.""" 168 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 169 170 with pytest.raises(FileNotFoundError, match="facet.json not found"): 171 facet_summary("broken-facet") 172 173 174def test_facet_summary_empty_entities(monkeypatch): 175 """Test facet_summary with empty entities file.""" 176 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 177 178 summary = facet_summary("empty-entities") 179 180 # Should not include entities section if file is empty 181 assert "## Entities" not in summary 182 183 184def test_get_facets_with_entities(monkeypatch): 185 """Test that get_facets() returns metadata and load_entity_names() works with facets.""" 186 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 187 188 facets = get_facets() 189 190 # Check test-facet exists 191 assert "test-facet" in facets 192 test_facet = facets["test-facet"] 193 194 # Check basic metadata 195 assert test_facet["title"] == "Test Facet" 196 assert test_facet["emoji"] == "🧪" 197 198 # Verify entities are NOT included in get_facets() anymore 199 assert "entities" not in test_facet 200 201 # Instead, verify entities can be loaded via load_entity_names() 202 from think.entities import load_entity_names 203 204 entity_names = load_entity_names(facet="test-facet") 205 assert entity_names is not None 206 207 # Check that specific entities are in the semicolon-delimited string 208 assert "John Smith" in entity_names 209 assert "Jane Doe" in entity_names 210 assert "Bob Wilson" in entity_names 211 assert "Acme Corp" in entity_names 212 assert "Tech Solutions Inc" in entity_names 213 assert "API Optimization" in entity_names 214 assert "Dashboard Redesign" in entity_names 215 assert "Visual Studio Code" in entity_names 216 assert "Docker" in entity_names 217 assert "PostgreSQL" in entity_names 218 219 220def test_get_facets_empty_entities(monkeypatch): 221 """Test get_facets() with facet that has no entities.""" 222 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 223 224 facets = get_facets() 225 226 # Check minimal-facet (should have no entities file) 227 if "minimal-facet" in facets: 228 minimal_facet = facets["minimal-facet"] 229 # Entities are no longer included in get_facets() 230 assert "entities" not in minimal_facet 231 232 # Verify load_entity_names returns None for facets without entities 233 from think.entities import load_entity_names 234 235 entity_names = load_entity_names(facet="minimal-facet") 236 assert entity_names is None 237 238 239def test_facet_summaries(monkeypatch): 240 """Test facet_summaries() generates correct agent prompt format.""" 241 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 242 243 summary = facet_summaries() 244 245 # Check header 246 assert "## Available Facets" in summary 247 248 # Check test-facet is included with backtick format 249 assert "**Test Facet** (`test-facet`)" in summary 250 assert "A test facet for validating functionality" in summary 251 252 # Check entities are included with title prefix 253 assert " - **Test Facet Entities**:" in summary 254 # Verify some specific entities are present 255 assert "John Smith" in summary 256 assert "Jane Doe" in summary 257 assert "Acme Corp" in summary 258 assert "API Optimization" in summary 259 260 # Check other facets are included 261 assert "(`full-featured`)" in summary 262 assert "(`minimal-facet`)" in summary 263 264 # Check activities are included (short mode - names only) 265 assert ( 266 "**Full Featured Facet Activities**: Meetings; Coding; Custom Activity" 267 in summary 268 ) 269 270 271def test_facet_summaries_excludes_muted(monkeypatch, tmp_path): 272 """Test facet_summaries() excludes muted facets.""" 273 facets_dir = tmp_path / "facets" 274 active_dir = facets_dir / "active" 275 muted_dir = facets_dir / "muted_one" 276 active_dir.mkdir(parents=True) 277 muted_dir.mkdir(parents=True) 278 279 (active_dir / "facet.json").write_text( 280 json.dumps({"name": "active", "title": "Active Facet"}), 281 encoding="utf-8", 282 ) 283 (muted_dir / "facet.json").write_text( 284 json.dumps({"name": "muted_one", "title": "Muted Facet", "muted": True}), 285 encoding="utf-8", 286 ) 287 288 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 289 290 summary = facet_summaries() 291 292 assert "(`active`)" in summary 293 assert "(`muted_one`)" not in summary 294 295 296def test_facet_summaries_no_facets(monkeypatch, tmp_path): 297 """Test facet_summaries() when no facets exist.""" 298 empty_journal = tmp_path / "empty_journal" 299 empty_journal.mkdir() 300 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(empty_journal)) 301 302 summary = facet_summaries() 303 assert summary == "No facets found." 304 305 306def test_facet_summaries_empty_journal(tmp_path, monkeypatch): 307 """Test facet_summaries() returns 'No facets found' with empty journal.""" 308 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 309 310 summary = facet_summaries() 311 assert summary == "No facets found." 312 313 314def test_facet_summaries_mixed_entities(monkeypatch): 315 """Test facet_summaries() with facets having different entity configurations.""" 316 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 317 318 summary = facet_summaries() 319 320 # Test facet should have entities (semicolon-delimited, not grouped by type) 321 assert "**Test Facet** (`test-facet`)" in summary 322 assert " - **Test Facet Entities**:" in summary 323 324 # Minimal facet should not have entity lists 325 assert "**Minimal Facet** (`minimal-facet`)" in summary 326 # Check that there's no entity list immediately after minimal-facet 327 lines = summary.split("\n") 328 for i, line in enumerate(lines): 329 if "**Minimal Facet** (`minimal-facet`)" in line: 330 # Next non-empty line should not be an entity list 331 j = i + 1 332 while j < len(lines) and lines[j].strip(): 333 # Should not have Entities line for minimal-facet 334 if lines[j].strip().startswith("- **"): 335 # This means we've reached the next facet 336 break 337 # If we're still in minimal-facet section, shouldn't have entities 338 assert not lines[j].strip().startswith("- **Entities**:") 339 j += 1 340 break 341 342 343def test_get_active_facets_from_segment_facets(monkeypatch, tmp_path): 344 """Test get_active_facets() returns facets from segment facets.json files.""" 345 journal = tmp_path / "journal" 346 day_dir = journal / "20240115" 347 348 # Create segment with facets.json containing two facets (stream layout) 349 seg1 = day_dir / "archon" / "100000_300" / "agents" 350 seg1.mkdir(parents=True) 351 (seg1 / "facets.json").write_text( 352 json.dumps( 353 [ 354 {"facet": "work", "activity": "Code review", "level": "high"}, 355 {"facet": "personal", "activity": "Email check", "level": "low"}, 356 ] 357 ) 358 ) 359 360 # Create another segment with overlapping + new facet 361 seg2 = day_dir / "archon" / "110000_300" / "agents" 362 seg2.mkdir(parents=True) 363 (seg2 / "facets.json").write_text( 364 json.dumps( 365 [ 366 {"facet": "work", "activity": "Meeting", "level": "high"}, 367 {"facet": "sunstone", "activity": "Dev work", "level": "medium"}, 368 ] 369 ) 370 ) 371 372 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 373 374 active = get_active_facets("20240115") 375 376 assert active == {"work", "personal", "sunstone"} 377 378 379def test_get_active_facets_empty_segments(monkeypatch, tmp_path): 380 """Test get_active_facets() with segments that have empty facets.json.""" 381 journal = tmp_path / "journal" 382 day_dir = journal / "20240115" 383 384 # Segment with empty facets array (stream layout) 385 seg1 = day_dir / "archon" / "100000_300" / "agents" 386 seg1.mkdir(parents=True) 387 (seg1 / "facets.json").write_text("[]") 388 389 # Segment with empty file 390 seg2 = day_dir / "archon" / "110000_300" / "agents" 391 seg2.mkdir(parents=True) 392 (seg2 / "facets.json").write_text("") 393 394 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 395 396 active = get_active_facets("20240115") 397 398 assert active == set() 399 400 401def test_get_active_facets_no_segments(monkeypatch, tmp_path): 402 """Test get_active_facets() when day directory has no segments.""" 403 journal = tmp_path / "journal" 404 (journal / "20240115").mkdir(parents=True) 405 406 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 407 408 active = get_active_facets("20240115") 409 410 assert active == set() 411 412 413def test_get_active_facets_no_day_dir(monkeypatch, tmp_path): 414 """Test get_active_facets() when day directory doesn't exist.""" 415 journal = tmp_path / "journal" 416 journal.mkdir() 417 418 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 419 420 active = get_active_facets("20240115") 421 422 assert active == set() 423 424 425def test_get_active_facets_malformed_json(monkeypatch, tmp_path): 426 """Test get_active_facets() skips malformed facets.json gracefully.""" 427 journal = tmp_path / "journal" 428 day_dir = journal / "20240115" 429 430 # Malformed JSON segment (stream layout) 431 seg1 = day_dir / "archon" / "100000_300" / "agents" 432 seg1.mkdir(parents=True) 433 (seg1 / "facets.json").write_text("{ invalid json") 434 435 # Valid segment 436 seg2 = day_dir / "archon" / "110000_300" / "agents" 437 seg2.mkdir(parents=True) 438 (seg2 / "facets.json").write_text( 439 json.dumps( 440 [ 441 {"facet": "work", "activity": "Coding", "level": "high"}, 442 ] 443 ) 444 ) 445 446 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 447 448 active = get_active_facets("20240115") 449 450 assert active == {"work"} 451 452 453# ============================================================================ 454# Principal role in facet summaries tests 455# ============================================================================ 456 457 458def test_get_principal_display_name_preferred(tmp_path, monkeypatch): 459 """Test _get_principal_display_name returns preferred name.""" 460 import json 461 462 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 463 464 config_dir = tmp_path / "config" 465 config_dir.mkdir() 466 config = {"identity": {"name": "Jeremy Miller", "preferred": "Jer"}} 467 (config_dir / "journal.json").write_text(json.dumps(config)) 468 469 assert _get_principal_display_name() == "Jer" 470 471 472def test_get_principal_display_name_fallback_to_name(tmp_path, monkeypatch): 473 """Test _get_principal_display_name falls back to name when no preferred.""" 474 import json 475 476 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 477 478 config_dir = tmp_path / "config" 479 config_dir.mkdir() 480 config = {"identity": {"name": "Jeremy Miller", "preferred": ""}} 481 (config_dir / "journal.json").write_text(json.dumps(config)) 482 483 assert _get_principal_display_name() == "Jeremy Miller" 484 485 486def test_get_principal_display_name_none_when_empty(tmp_path, monkeypatch): 487 """Test _get_principal_display_name returns None when identity empty.""" 488 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 489 # No config file 490 491 assert _get_principal_display_name() is None 492 493 494def test_format_principal_role_with_principal(tmp_path, monkeypatch): 495 """Test _format_principal_role extracts and formats principal.""" 496 import json 497 498 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 499 500 config_dir = tmp_path / "config" 501 config_dir.mkdir() 502 config = {"identity": {"name": "Jeremy", "preferred": "Jer"}} 503 (config_dir / "journal.json").write_text(json.dumps(config)) 504 505 entities = [ 506 {"name": "Jeremy", "description": "Software engineer", "is_principal": True}, 507 {"name": "Bob", "description": "Friend"}, 508 ] 509 510 role_line, filtered = _format_principal_role(entities) 511 512 assert role_line == "**Jer's Role**: Software engineer" 513 assert len(filtered) == 1 514 assert filtered[0]["name"] == "Bob" 515 516 517def test_format_principal_role_no_principal(tmp_path, monkeypatch): 518 """Test _format_principal_role returns None when no principal.""" 519 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 520 521 entities = [ 522 {"name": "Alice", "description": "Friend"}, 523 {"name": "Bob", "description": "Colleague"}, 524 ] 525 526 role_line, filtered = _format_principal_role(entities) 527 528 assert role_line is None 529 assert filtered == entities 530 531 532def test_format_principal_role_no_description(tmp_path, monkeypatch): 533 """Test _format_principal_role returns None when principal has no description.""" 534 import json 535 536 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 537 538 config_dir = tmp_path / "config" 539 config_dir.mkdir() 540 config = {"identity": {"name": "Jeremy", "preferred": "Jer"}} 541 (config_dir / "journal.json").write_text(json.dumps(config)) 542 543 entities = [ 544 {"name": "Jeremy", "description": "", "is_principal": True}, 545 {"name": "Bob", "description": "Friend"}, 546 ] 547 548 role_line, filtered = _format_principal_role(entities) 549 550 # No role line because description is empty 551 assert role_line is None 552 # But principal is still filtered out 553 assert filtered == entities 554 555 556def test_format_principal_role_no_identity(tmp_path, monkeypatch): 557 """Test _format_principal_role returns None when no identity configured.""" 558 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 559 # No config file 560 561 entities = [ 562 {"name": "Jeremy", "description": "Engineer", "is_principal": True}, 563 {"name": "Bob", "description": "Friend"}, 564 ] 565 566 role_line, filtered = _format_principal_role(entities) 567 568 # No role line because no identity config 569 assert role_line is None 570 # Entities unchanged 571 assert filtered == entities 572 573 574def test_facet_summary_with_principal(tmp_path, monkeypatch): 575 """Test facet_summary shows principal role and excludes from entities list.""" 576 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 577 578 # Create identity config 579 config_dir = tmp_path / "config" 580 config_dir.mkdir() 581 config = {"identity": {"name": "Test User", "preferred": "Tester"}} 582 (config_dir / "journal.json").write_text(json.dumps(config)) 583 584 # Create facet with principal entity using new structure 585 facet_dir = tmp_path / "facets" / "work" 586 facet_dir.mkdir(parents=True) 587 (facet_dir / "facet.json").write_text( 588 json.dumps({"title": "Work", "description": "Work stuff"}) 589 ) 590 setup_entities_new_structure( 591 tmp_path, 592 "work", 593 [ 594 { 595 "type": "Person", 596 "name": "Test User", 597 "description": "Lead developer", 598 "is_principal": True, 599 }, 600 {"type": "Person", "name": "Alice", "description": "Colleague"}, 601 ], 602 ) 603 604 summary = facet_summary("work") 605 606 # Should have principal role line 607 assert "**Tester's Role**: Lead developer" in summary 608 # Should have entities section with Alice but not Test User 609 assert "## Entities" in summary 610 assert "Alice" in summary 611 assert "Colleague" in summary 612 # Principal should not appear in entities list 613 assert "- **Person**: Test User" not in summary 614 615 616def test_facet_summary_principal_only_entity(tmp_path, monkeypatch): 617 """Test facet_summary when principal is the only entity.""" 618 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 619 620 # Create identity config 621 config_dir = tmp_path / "config" 622 config_dir.mkdir() 623 config = {"identity": {"name": "Test User", "preferred": "Tester"}} 624 (config_dir / "journal.json").write_text(json.dumps(config)) 625 626 # Create facet with only principal entity using new structure 627 facet_dir = tmp_path / "facets" / "solo" 628 facet_dir.mkdir(parents=True) 629 (facet_dir / "facet.json").write_text(json.dumps({"title": "Solo"})) 630 setup_entities_new_structure( 631 tmp_path, 632 "solo", 633 [ 634 { 635 "type": "Person", 636 "name": "Test User", 637 "description": "Just me", 638 "is_principal": True, 639 }, 640 ], 641 ) 642 643 summary = facet_summary("solo") 644 645 # Should have principal role line 646 assert "**Tester's Role**: Just me" in summary 647 # Should NOT have entities section (no other entities) 648 assert "## Entities" not in summary 649 650 651def test_facet_summaries_detailed_with_principal(tmp_path, monkeypatch): 652 """Test facet_summaries detailed mode shows principal role.""" 653 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 654 655 # Create identity config 656 config_dir = tmp_path / "config" 657 config_dir.mkdir() 658 config = {"identity": {"name": "Test User", "preferred": "Tester"}} 659 (config_dir / "journal.json").write_text(json.dumps(config)) 660 661 # Create facet with principal using new structure 662 facet_dir = tmp_path / "facets" / "project" 663 facet_dir.mkdir(parents=True) 664 (facet_dir / "facet.json").write_text( 665 json.dumps({"title": "Project X", "description": "Secret project"}) 666 ) 667 setup_entities_new_structure( 668 tmp_path, 669 "project", 670 [ 671 { 672 "type": "Person", 673 "name": "Test User", 674 "description": "Project lead", 675 "is_principal": True, 676 }, 677 {"type": "Person", "name": "Bob", "description": "Team member"}, 678 ], 679 ) 680 681 summary = facet_summaries(detailed=True) 682 683 # Should have principal role 684 assert "**Tester's Role**: Project lead" in summary 685 # Should have Bob in entities 686 assert "Bob: Team member" in summary 687 # Principal should not be in entities list 688 assert "Test User: Project lead" not in summary 689 690 691def test_facet_summaries_simple_mode_with_principal(tmp_path, monkeypatch): 692 """Test facet_summaries simple mode also filters principal consistently.""" 693 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 694 695 # Create identity config 696 config_dir = tmp_path / "config" 697 config_dir.mkdir() 698 config = {"identity": {"name": "Test User", "preferred": "Tester"}} 699 (config_dir / "journal.json").write_text(json.dumps(config)) 700 701 # Create facet with principal using new structure 702 facet_dir = tmp_path / "facets" / "simple" 703 facet_dir.mkdir(parents=True) 704 (facet_dir / "facet.json").write_text(json.dumps({"title": "Simple"})) 705 setup_entities_new_structure( 706 tmp_path, 707 "simple", 708 [ 709 { 710 "type": "Person", 711 "name": "Test User", 712 "description": "Me", 713 "is_principal": True, 714 }, 715 {"type": "Person", "name": "Bob", "description": "Friend"}, 716 ], 717 ) 718 719 summary = facet_summaries(detailed=False) 720 721 # Simple mode now shows principal role (consistent with detailed mode) 722 assert "**Tester's Role**: Me" in summary 723 # Principal should not appear in entity names 724 assert "Test User" not in summary 725 # Other entities should appear 726 assert "Bob" in summary 727 728 729def test_facet_summaries_detailed_with_activities(monkeypatch): 730 """Test facet_summaries detailed mode includes activity details.""" 731 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 732 733 summary = facet_summaries(detailed=True) 734 735 # Check activities are included with details 736 assert "**Full Featured Facet Activities**:" in summary 737 assert "Meetings (high):" in summary 738 assert "Video calls, in-person meetings, and conferences" in summary 739 assert "Coding:" in summary 740 assert "Custom Activity:" in summary 741 assert "A custom test activity" in summary