personal memory agent
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 "" 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 "" 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