diff --git a/apps/transcripts/call.py b/apps/transcripts/call.py index ad77e2040..ae61cbd06 100644 --- a/apps/transcripts/call.py +++ b/apps/transcripts/call.py @@ -46,7 +46,7 @@ def scan( else: typer.echo(" (none)") - typer.echo("Screen:") + typer.echo("Percepts:") if screen_ranges: for start, end in screen_ranges: typer.echo(f" {start} - {end}") @@ -94,7 +94,8 @@ def read( raw: bool = typer.Option(False, "--raw", help="Include transcripts and screen only."), transcripts: bool = typer.Option(False, "--transcripts", help="Include transcript content."), audio: bool = typer.Option(False, "--audio", help="Alias for --transcripts.", hidden=True), - screen: bool = typer.Option(False, "--screen", help="Include screen transcripts."), + percepts: bool = typer.Option(False, "--percepts", help="Include screen percepts."), + screen: bool = typer.Option(False, "--screen", help="Alias for --percepts.", hidden=True), agents: bool = typer.Option(False, "--agents", help="Include agent outputs."), max_bytes: int = typer.Option( 16384, "--max", help="Max output bytes (0 = unlimited)." @@ -104,27 +105,28 @@ def read( day = resolve_sol_day(day) segment = resolve_sol_segment(segment) stream = stream or get_sol_stream() - # --audio is an alias for --transcripts + # --audio is an alias for --transcripts, --screen is an alias for --percepts transcripts = transcripts or audio + percepts = percepts or screen if full and raw: typer.echo("Error: Cannot use --full and --raw together.", err=True) raise typer.Exit(1) - if (full or raw) and (transcripts or screen or agents): + if (full or raw) and (transcripts or percepts or agents): typer.echo( "Error: Cannot mix --full/--raw with individual source flags.", err=True ) raise typer.Exit(1) if full: - sources: dict[str, bool] = {"transcripts": True, "screen": True, "agents": True} + sources: dict[str, bool] = {"transcripts": True, "percepts": True, "agents": True} elif raw: - sources = {"transcripts": True, "screen": True, "agents": False} - elif transcripts or screen or agents: - sources = {"transcripts": transcripts, "screen": screen, "agents": agents} + sources = {"transcripts": True, "percepts": True, "agents": False} + elif transcripts or percepts or agents: + sources = {"transcripts": transcripts, "percepts": percepts, "agents": agents} else: - sources = {"transcripts": True, "screen": False, "agents": True} + sources = {"transcripts": True, "percepts": False, "agents": True} if segment and (start or length is not None): typer.echo("Error: Cannot mix --segment with --start/--length.", err=True) @@ -158,7 +160,7 @@ def stats(month: str = typer.Argument(help="Month (YYYYMM).")) -> None: transcript_ranges, screen_ranges = cluster_scan(day) if transcript_ranges or screen_ranges: days_with_data += 1 - typer.echo(f"{day} transcripts:{len(transcript_ranges)} screen:{len(screen_ranges)}") + typer.echo(f"{day} transcripts:{len(transcript_ranges)} percepts:{len(screen_ranges)}") if not days_with_data: typer.echo(f"No data for {month}.") diff --git a/apps/transcripts/tests/test_call.py b/apps/transcripts/tests/test_call.py index 55ba9d8b5..23c4a525c 100644 --- a/apps/transcripts/tests/test_call.py +++ b/apps/transcripts/tests/test_call.py @@ -15,7 +15,7 @@ class TestScan: result = runner.invoke(call_app, ["transcripts", "scan", "20240101"]) assert result.exit_code == 0 assert "Transcripts:" in result.output - assert "Screen:" in result.output + assert "Percepts:" in result.output def test_scan_empty_day(self): result = runner.invoke(call_app, ["transcripts", "scan", "20990101"]) diff --git a/muse/activities.md b/muse/activities.md index fec133ea3..4d76db9f8 100644 --- a/muse/activities.md +++ b/muse/activities.md @@ -12,7 +12,7 @@ "thinking_budget": 4096, "max_output_tokens": 2048, "instructions": { - "sources": {"transcripts": false, "screen": false, "agents": false}, + "sources": {"transcripts": false, "percepts": false, "agents": false}, "facets": false } diff --git a/muse/activity.md b/muse/activity.md index fd9ea2cf5..de8f85f33 100644 --- a/muse/activity.md +++ b/muse/activity.md @@ -8,7 +8,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": true, "agents": false}, + "sources": {"transcripts": true, "percepts": true, "agents": false}, "facets": true } diff --git a/muse/activity_state.md b/muse/activity_state.md index f9d80f08c..d0cc3f1ac 100644 --- a/muse/activity_state.md +++ b/muse/activity_state.md @@ -13,7 +13,7 @@ "thinking_budget": 4096, "max_output_tokens": 3072, "instructions": { - "sources": {"transcripts": true, "screen": true, "agents": false}, + "sources": {"transcripts": true, "percepts": true, "agents": false}, "facets": true } diff --git a/muse/daily_schedule.md b/muse/daily_schedule.md index b1a3e7730..ea8701edf 100644 --- a/muse/daily_schedule.md +++ b/muse/daily_schedule.md @@ -11,7 +11,7 @@ "thinking_budget": 4096, "max_output_tokens": 512, "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/decisions.md b/muse/decisions.md index 5b51678be..9d6b13d4f 100644 --- a/muse/decisions.md +++ b/muse/decisions.md @@ -11,7 +11,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true, "activity": true } diff --git a/muse/documentation.md b/muse/documentation.md index 312186e70..feaeeadcf 100644 --- a/muse/documentation.md +++ b/muse/documentation.md @@ -11,7 +11,7 @@ "disabled": true, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/entities.md b/muse/entities.md index 0124fdfa5..a00c77145 100644 --- a/muse/entities.md +++ b/muse/entities.md @@ -11,7 +11,7 @@ "max_output_tokens": 1024, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": true, "agents": false}, + "sources": {"transcripts": true, "percepts": true, "agents": false}, "facets": false } diff --git a/muse/facets.md b/muse/facets.md index acb02f0df..dfa8c0b4b 100644 --- a/muse/facets.md +++ b/muse/facets.md @@ -11,7 +11,7 @@ "max_output_tokens": 512, "output": "json", "instructions": { - "sources": {"transcripts": false, "screen": false, "agents": true}, + "sources": {"transcripts": false, "percepts": false, "agents": true}, "facets": true } diff --git a/muse/files.md b/muse/files.md index 8c9e4a467..ee9d6f7ab 100644 --- a/muse/files.md +++ b/muse/files.md @@ -11,7 +11,7 @@ "disabled": true, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/flow.md b/muse/flow.md index 399684fbf..b58efd379 100644 --- a/muse/flow.md +++ b/muse/flow.md @@ -10,7 +10,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/followups.md b/muse/followups.md index c5f2791ac..76b40f460 100644 --- a/muse/followups.md +++ b/muse/followups.md @@ -11,7 +11,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true, "activity": true } diff --git a/muse/knowledge_graph.md b/muse/knowledge_graph.md index eea205b2a..39ee0c5d4 100644 --- a/muse/knowledge_graph.md +++ b/muse/knowledge_graph.md @@ -10,7 +10,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/media.md b/muse/media.md index 30f4e5be6..1e8b7f656 100644 --- a/muse/media.md +++ b/muse/media.md @@ -11,7 +11,7 @@ "disabled": true, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/meetings.md b/muse/meetings.md index b09c7ffd6..230661695 100644 --- a/muse/meetings.md +++ b/muse/meetings.md @@ -11,7 +11,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true, "activity": true } diff --git a/muse/messaging.md b/muse/messaging.md index 490774f60..d33fa02ad 100644 --- a/muse/messaging.md +++ b/muse/messaging.md @@ -11,7 +11,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true, "activity": true } diff --git a/muse/observation.md b/muse/observation.md index 4b53a3fd0..99cccbe9a 100644 --- a/muse/observation.md +++ b/muse/observation.md @@ -10,7 +10,7 @@ "thinking_budget": 2048, "max_output_tokens": 2048, "instructions": { - "sources": {"transcripts": true, "screen": true, "agents": false} + "sources": {"transcripts": true, "percepts": true, "agents": false} } } diff --git a/muse/opportunities.md b/muse/opportunities.md index 7d878a6d1..b12489e6a 100644 --- a/muse/opportunities.md +++ b/muse/opportunities.md @@ -11,7 +11,7 @@ "disabled": true, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/research.md b/muse/research.md index a8f3955d2..14809e214 100644 --- a/muse/research.md +++ b/muse/research.md @@ -11,7 +11,7 @@ "disabled": true, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/muse/schedule.md b/muse/schedule.md index a771311cf..8df985f50 100644 --- a/muse/schedule.md +++ b/muse/schedule.md @@ -9,7 +9,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}} + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}} } } diff --git a/muse/screen.md b/muse/screen.md index 49259f50e..7b3f1410e 100644 --- a/muse/screen.md +++ b/muse/screen.md @@ -8,7 +8,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": "required", "agents": false} + "sources": {"transcripts": true, "percepts": "required", "agents": false} } } diff --git a/muse/speakers.md b/muse/speakers.md index efc762a40..7843e5cd1 100644 --- a/muse/speakers.md +++ b/muse/speakers.md @@ -8,7 +8,7 @@ "output": "json", "color": "#e64a19", "instructions": { - "sources": {"transcripts": "required", "screen": true, "agents": false} + "sources": {"transcripts": "required", "percepts": true, "agents": false} } } diff --git a/muse/timeline.md b/muse/timeline.md index 2f51915f6..e9b006f78 100644 --- a/muse/timeline.md +++ b/muse/timeline.md @@ -10,7 +10,7 @@ "priority": 10, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}} + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}} } } diff --git a/muse/tools.md b/muse/tools.md index e63a92130..8fa0f0de7 100644 --- a/muse/tools.md +++ b/muse/tools.md @@ -11,7 +11,7 @@ "disabled": true, "output": "md", "instructions": { - "sources": {"transcripts": true, "screen": false, "agents": {"screen": true}}, + "sources": {"transcripts": true, "percepts": false, "agents": {"screen": true}}, "facets": true } diff --git a/tests/baselines/api/stats/stats.json b/tests/baselines/api/stats/stats.json index 03305a0bd..f8895a1b5 100644 --- a/tests/baselines/api/stats/stats.json +++ b/tests/baselines/api/stats/stats.json @@ -12,7 +12,7 @@ "facets": false, "sources": { "agents": false, - "screen": false, + "percepts": false, "transcripts": false } }, @@ -35,7 +35,7 @@ "facets": true, "sources": { "agents": false, - "screen": true, + "percepts": true, "transcripts": true } }, @@ -59,7 +59,7 @@ "facets": true, "sources": { "agents": false, - "screen": true, + "percepts": true, "transcripts": true } }, @@ -89,7 +89,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -120,7 +120,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -144,7 +144,7 @@ "facets": false, "sources": { "agents": false, - "screen": true, + "percepts": true, "transcripts": true } }, @@ -166,7 +166,7 @@ "facets": true, "sources": { "agents": true, - "screen": false, + "percepts": false, "transcripts": false } }, @@ -194,7 +194,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -224,7 +224,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -250,7 +250,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -280,7 +280,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -311,7 +311,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -335,7 +335,7 @@ "instructions": { "sources": { "agents": false, - "screen": true, + "percepts": true, "transcripts": true } }, @@ -362,7 +362,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, @@ -381,7 +381,7 @@ "instructions": { "sources": { "agents": false, - "screen": "required", + "percepts": "required", "transcripts": true } }, @@ -400,7 +400,7 @@ "instructions": { "sources": { "agents": false, - "screen": true, + "percepts": true, "transcripts": "required" } }, @@ -424,7 +424,7 @@ "agents": { "screen": true }, - "screen": false, + "percepts": false, "transcripts": true } }, diff --git a/tests/baselines/api/todos/badge-count.json b/tests/baselines/api/todos/badge-count.json index 03d0b2009..633d47a56 100644 --- a/tests/baselines/api/todos/badge-count.json +++ b/tests/baselines/api/todos/badge-count.json @@ -1,3 +1,3 @@ { - "count": 4 + "count": 0 } diff --git a/tests/baselines/api/todos/nudges.json b/tests/baselines/api/todos/nudges.json index 15a86c251..fd19100c4 100644 --- a/tests/baselines/api/todos/nudges.json +++ b/tests/baselines/api/todos/nudges.json @@ -1,3 +1,18 @@ { - "nudges": [] + "nudges": [ + { + "day": "20260308", + "facet": "montague", + "index": 2, + "nudge": "20260309T09:00", + "text": "Recruit Benvolio for infrastructure support" + }, + { + "day": "20260308", + "facet": "verona", + "index": 2, + "nudge": "20260309T09:00", + "text": "Prepare working demo for board meeting" + } + ] } diff --git a/tests/baselines/api/transcripts/segments.json b/tests/baselines/api/transcripts/segments.json index 3f665f5ab..20a177388 100644 --- a/tests/baselines/api/transcripts/segments.json +++ b/tests/baselines/api/transcripts/segments.json @@ -7,7 +7,7 @@ "stream": "default", "types": [ "transcripts", - "screen" + "percepts" ] }, { @@ -17,7 +17,7 @@ "stream": "default", "types": [ "transcripts", - "screen" + "percepts" ] }, { @@ -27,7 +27,7 @@ "stream": "default", "types": [ "transcripts", - "screen" + "percepts" ] } ] diff --git a/tests/test_cluster.py b/tests/test_cluster.py index b5c5fe3d3..be8145102 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -25,7 +25,7 @@ def test_cluster(tmp_path, monkeypatch): "screen summary" ) result, counts = mod.cluster( - "20240101", sources={"transcripts": True, "screen": False, "agents": True} + "20240101", sources={"transcripts": True, "percepts": False, "agents": True} ) assert counts["transcripts"] == 1 assert counts["agents"] == 1 @@ -55,7 +55,7 @@ def test_cluster_range(tmp_path, monkeypatch): "20240101", "120000", "120100", - sources={"transcripts": True, "screen": False, "agents": True}, + sources={"transcripts": True, "percepts": False, "agents": True}, ) # Check that the function works and includes expected sections assert "### Transcript" in md @@ -141,13 +141,13 @@ def test_cluster_segments(tmp_path, monkeypatch): assert segments[1]["start"] == "10:00" assert segments[1]["end"] == "10:10" assert "transcripts" in segments[1]["types"] - assert "screen" in segments[1]["types"] + assert "percepts" in segments[1]["types"] # Check third segment (screen only) assert segments[2]["key"] == "110000_300" assert segments[2]["start"] == "11:00" assert segments[2]["end"] == "11:05" - assert segments[2]["types"] == ["screen"] + assert segments[2]["types"] == ["percepts"] def test_cluster_period_uses_raw_screen(tmp_path, monkeypatch): @@ -176,12 +176,12 @@ def test_cluster_period_uses_raw_screen(tmp_path, monkeypatch): result, counts = mod.cluster_period( "20240101", "100000_300", - sources={"transcripts": True, "screen": True, "agents": False}, + sources={"transcripts": True, "percepts": True, "agents": False}, ) # Should have both transcript and screen entries assert counts["transcripts"] == 1 - assert counts["screen"] == 1 + assert counts["percepts"] == 1 assert "### Transcript" in result # Should use raw screen format header assert "Screen Activity" in result @@ -218,7 +218,7 @@ def test_cluster_range_with_agents(tmp_path, monkeypatch): "20240101", "100000", "100500", - sources={"transcripts": True, "screen": False, "agents": True}, + sources={"transcripts": True, "percepts": False, "agents": True}, ) assert "### Transcript" in result @@ -253,7 +253,7 @@ def test_cluster_range_with_screen(tmp_path, monkeypatch): "20240101", "100000", "100500", - sources={"transcripts": False, "screen": True, "agents": False}, + sources={"transcripts": False, "percepts": True, "agents": False}, ) assert "Screen Activity" in result @@ -289,7 +289,7 @@ def test_cluster_range_with_multiple_screen_files(tmp_path, monkeypatch): "20240101", "100000", "100500", - sources={"transcripts": False, "screen": True, "agents": False}, + sources={"transcripts": False, "percepts": True, "agents": False}, ) # Should include content from both screen files @@ -333,7 +333,7 @@ def test_cluster_segments_with_split_screen(tmp_path, monkeypatch): assert len(segments) == 1 assert segments[0]["key"] == "100000_300" - assert "screen" in segments[0]["types"] + assert "percepts" in segments[0]["types"] def test_cluster_span(tmp_path, monkeypatch): @@ -367,12 +367,12 @@ def test_cluster_span(tmp_path, monkeypatch): result, counts = mod.cluster_span( "20240101", ["090000_300", "110000_300"], - sources={"transcripts": True, "screen": False, "agents": False}, + sources={"transcripts": True, "percepts": False, "agents": False}, ) # Should have 2 transcript entries (one per segment) assert counts["transcripts"] == 2 - assert counts["screen"] == 0 + assert counts["percepts"] == 0 assert "morning segment" in result assert "late morning segment" in result # Should NOT include the skipped segment @@ -398,7 +398,7 @@ def test_cluster_span_missing_segment(tmp_path, monkeypatch): mod.cluster_span( "20240101", ["090000_300", "100000_300"], - sources={"transcripts": True, "screen": False, "agents": False}, + sources={"transcripts": True, "percepts": False, "agents": False}, ) assert "100000_300" in str(exc_info.value) @@ -424,7 +424,7 @@ def test_cluster_with_agent_filter_dict(tmp_path, monkeypatch): # Test filtering to only include entities result, counts = mod.cluster( "20240101", - sources={"transcripts": True, "screen": False, "agents": {"entities": True}}, + sources={"transcripts": True, "percepts": False, "agents": {"entities": True}}, ) assert counts["transcripts"] == 1 @@ -455,7 +455,7 @@ def test_cluster_with_agent_filter_multiple(tmp_path, monkeypatch): "20240101", sources={ "transcripts": True, - "screen": False, + "percepts": False, "agents": {"entities": True, "meetings": "required", "flow": False}, }, ) @@ -488,7 +488,7 @@ def test_cluster_with_agent_filter_app_namespaced(tmp_path, monkeypatch): "20240101", sources={ "transcripts": True, - "screen": False, + "percepts": False, "agents": {"entities": False, "todos:review": True}, }, ) @@ -515,7 +515,7 @@ def test_cluster_with_empty_agent_filter(tmp_path, monkeypatch): # Empty dict should mean no agents result, counts = mod.cluster( "20240101", - sources={"transcripts": True, "screen": False, "agents": {}}, + sources={"transcripts": True, "percepts": False, "agents": {}}, ) assert counts["transcripts"] == 1 diff --git a/tests/test_cluster_full.py b/tests/test_cluster_full.py index 49797e04a..55f626da5 100644 --- a/tests/test_cluster_full.py +++ b/tests/test_cluster_full.py @@ -29,7 +29,7 @@ def test_cluster_full(tmp_path, monkeypatch): copy_day(tmp_path) monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) md, counts = mod.cluster( - "20240101", sources={"transcripts": True, "screen": False, "agents": True} + "20240101", sources={"transcripts": True, "percepts": False, "agents": True} ) # Transcript entries come from 2 segments on 20240101 (default + import.apple) assert counts["transcripts"] == 2 @@ -45,7 +45,7 @@ def test_cluster_default_sources(tmp_path, monkeypatch): copy_day(tmp_path) monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) out, _counts = mod.cluster( - "20240101", sources={"transcripts": True, "screen": False, "agents": True} + "20240101", sources={"transcripts": True, "percepts": False, "agents": True} ) # Now uses insight format: "### {stem} summary" assert "### screen summary" in out @@ -59,7 +59,7 @@ def test_cluster_range_raw_screen(tmp_path, monkeypatch): "20240101", "123456", "123556", - sources={"transcripts": True, "screen": True, "agents": False}, + sources={"transcripts": True, "percepts": True, "agents": False}, ) # Range mode with screen=True uses raw screen data. assert "### Screen Activity" in out diff --git a/tests/test_generate_full.py b/tests/test_generate_full.py index 221abfcd4..f0f205d15 100644 --- a/tests/test_generate_full.py +++ b/tests/test_generate_full.py @@ -79,7 +79,7 @@ def test_generate_output_ndjson(tmp_path, monkeypatch): test_generator = tmp_path / "test_gen.md" test_generator.write_text( - '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' + '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nTest prompt' ) # Mock the underlying generation function in think.models @@ -146,7 +146,7 @@ def post_process(result, context): test_generator = tmp_path / "hooked_gen.md" test_generator.write_text( - '{\n "type": "generate",\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "test_hook"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' + '{\n "type": "generate",\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "test_hook"},\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nTest prompt' ) # Mock the underlying generation function in think.models @@ -198,7 +198,7 @@ def test_generate_without_hook_succeeds(tmp_path, monkeypatch): test_generator = tmp_path / "nohook_gen.md" test_generator.write_text( - '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nNo hook prompt' + '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nNo hook prompt' ) # Mock the underlying generation function in think.models @@ -265,7 +265,7 @@ def test_generate_skipped_on_no_input(tmp_path, monkeypatch): test_generator = tmp_path / "empty_gen.md" test_generator.write_text( - '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' + '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nTest prompt' ) monkeypatch.setenv("GOOGLE_API_KEY", "x") diff --git a/tests/test_generators.py b/tests/test_generators.py index 5d9143a49..5cfc86172 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -135,7 +135,7 @@ def test_speakers_has_required_audio(): sources = instructions.get("sources", {}) assert sources.get("transcripts") == "required", "speakers should require transcripts" - assert sources.get("screen") is True, "speakers should include screen" + assert sources.get("percepts") is True, "speakers should include percepts" def _write_temp_muse_prompt(stem: str, frontmatter: str) -> Path: diff --git a/tests/test_journal_stats.py b/tests/test_journal_stats.py index c67efb839..b202ca364 100644 --- a/tests/test_journal_stats.py +++ b/tests/test_journal_stats.py @@ -172,7 +172,7 @@ def test_token_usage(tmp_path, monkeypatch): assert "token_usage_by_day" in data assert "token_totals_by_model" in data assert "total_transcript_duration" in data - assert "total_screen_duration" in data + assert "total_percept_duration" in data assert ( data["token_usage_by_day"]["20240101"]["gemini-2.5-flash"]["total_tokens"] == 495 diff --git a/tests/test_muse.py b/tests/test_muse.py index d98ba02c9..cb7755678 100644 --- a/tests/test_muse.py +++ b/tests/test_muse.py @@ -39,11 +39,11 @@ def test_merge_instructions_config_with_overrides(): def test_merge_instructions_config_sources_merge(): """Test that sources dict is merged, not replaced.""" - defaults = {"system": None, "sources": {"transcripts": False, "screen": False}} + defaults = {"system": None, "sources": {"transcripts": False, "percepts": False}} overrides = {"sources": {"transcripts": True}} result = _merge_instructions_config(defaults, overrides) assert result["sources"]["transcripts"] is True # Overridden - assert result["sources"]["screen"] is False # Preserved from defaults + assert result["sources"]["percepts"] is False # Preserved from defaults def test_merge_instructions_config_ignores_unknown_keys(): @@ -236,7 +236,7 @@ class TestComposeInstructions: assert "sources" in result assert result["sources"]["transcripts"] is False - assert result["sources"]["screen"] is False + assert result["sources"]["percepts"] is False assert result["sources"]["agents"] is False def test_sources_can_be_overridden(self, monkeypatch, tmp_path): @@ -256,7 +256,7 @@ class TestComposeInstructions: ) assert result["sources"]["transcripts"] is True # Overridden - assert result["sources"]["screen"] is False # Default preserved + assert result["sources"]["percepts"] is False # Default preserved assert result["sources"]["agents"] is True # Overridden diff --git a/tests/test_output_hooks.py b/tests/test_output_hooks.py index 9768bb88f..075ddad52 100644 --- a/tests/test_output_hooks.py +++ b/tests/test_output_hooks.py @@ -170,7 +170,7 @@ def test_output_hook_invocation(tmp_path, monkeypatch): prompt_file = tmp_path / "hooked_test.md" prompt_file.write_text( - '{\n "type": "generate",\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "hooked_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' + '{\n "type": "generate",\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "hooked_test"},\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nTest prompt' ) hook_file = tmp_path / "hooked_test.py" @@ -224,7 +224,7 @@ def test_output_hook_returns_none(tmp_path, monkeypatch): prompt_file = tmp_path / "noop_test.md" prompt_file.write_text( - '{\n "type": "generate",\n "title": "Noop",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "noop_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' + '{\n "type": "generate",\n "title": "Noop",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "noop_test"},\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nTest prompt' ) hook_file = tmp_path / "noop_test.py" @@ -270,7 +270,7 @@ def test_output_hook_error_fallback(tmp_path, monkeypatch): prompt_file = tmp_path / "broken_test.md" prompt_file.write_text( - '{\n "type": "generate",\n "title": "Broken",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "broken_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' + '{\n "type": "generate",\n "title": "Broken",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "broken_test"},\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nTest prompt' ) hook_file = tmp_path / "broken_test.py" @@ -387,7 +387,7 @@ def test_pre_hook_invocation(tmp_path, monkeypatch): prompt_file = tmp_path / "prehooked_test.md" prompt_file.write_text( - '{\n "type": "generate",\n "title": "Prehooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "prehooked_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nOriginal prompt' + '{\n "type": "generate",\n "title": "Prehooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "prehooked_test"},\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nOriginal prompt' ) hook_file = tmp_path / "prehooked_test.py" @@ -447,7 +447,7 @@ def test_both_pre_and_post_hooks(tmp_path, monkeypatch): prompt_file = tmp_path / "both_hooks_test.md" prompt_file.write_text( - '{\n "type": "generate",\n "title": "Both Hooks",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "both_hooks_test", "post": "both_hooks_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nOriginal prompt' + '{\n "type": "generate",\n "title": "Both Hooks",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "both_hooks_test", "post": "both_hooks_test"},\n "instructions": {"system": "journal", "sources": {"transcripts": true, "percepts": true}}\n}\n\nOriginal prompt' ) hook_file = tmp_path / "both_hooks_test.py" diff --git a/think/cluster.py b/think/cluster.py index 61c3ae0bf..97f4c63c5 100644 --- a/think/cluster.py +++ b/think/cluster.py @@ -94,7 +94,7 @@ def _process_segment( segment_path: Path, date_str: str, transcripts: bool, - screen: bool, + percepts: bool, agents: bool | dict[str, bool | str], ) -> list[dict[str, Any]]: """Process a single segment directory and return entries. @@ -103,7 +103,7 @@ def _process_segment( segment_path: Path to segment directory date_str: Date in YYYYMMDD format transcripts: Whether to load transcript content (JSONL and markdown) - screen: Whether to load raw screen data from *screen.jsonl files + percepts: Whether to load raw screen data from *screen.jsonl files agents: Whether to load agent output summaries from *.md files. Can be bool (all/none) or dict for selective filtering (e.g., {"entities": True, "meetings": "required"}). @@ -187,7 +187,7 @@ def _process_segment( ) # Process raw screen data from screen.jsonl and *_screen.jsonl - if screen: + if percepts: screen_files = list(segment_path.glob("*screen.jsonl")) for screen_jsonl in screen_files: try: @@ -199,7 +199,7 @@ def _process_segment( "segment_key": segment_key, "segment_start": segment_start, "segment_end": segment_end, - "prefix": "screen", + "prefix": "percept", "content": content, "name": f"{segment_path.name}/{screen_jsonl.name}", "stream": stream, @@ -254,7 +254,7 @@ def _process_segment( def _load_entries( - day_dir: str, transcripts: bool, screen: bool, agents: bool | dict[str, bool | str] + day_dir: str, transcripts: bool, percepts: bool, agents: bool | dict[str, bool | str] ) -> list[dict[str, Any]]: """Load all transcript entries from a day directory.""" from think.utils import segment_parse @@ -269,7 +269,7 @@ def _load_entries( start_time, _ = segment_parse(seg_path.name) if not start_time: continue - entries.extend(_process_segment(seg_path, date_str, transcripts, screen, agents)) + entries.extend(_process_segment(seg_path, date_str, transcripts, percepts, agents)) entries.sort(key=lambda e: e["timestamp"]) return entries @@ -293,16 +293,16 @@ def _count_by_source(entries: list[dict[str, Any]]) -> dict[str, int]: Maps the internal prefix names to source config names: - "transcript" -> "transcripts" - - "screen" -> "screen" + - "percept" -> "percepts" - "agent_output" -> "agents" Returns: - Dict with counts for each source type, e.g., {"transcripts": 2, "screen": 1, "agents": 0} + Dict with counts for each source type, e.g., {"transcripts": 2, "percepts": 1, "agents": 0} """ # Map internal prefix to source config name prefix_to_source = { "transcript": "transcripts", - "screen": "screen", + "percept": "percepts", "agent_output": "agents", } @@ -311,7 +311,7 @@ def _count_by_source(entries: list[dict[str, Any]]) -> dict[str, int]: # Ensure all standard sources are present (even if 0) return { "transcripts": counts.get("transcripts", 0), - "screen": counts.get("screen", 0), + "percepts": counts.get("percepts", 0), "agents": counts.get("agents", 0), } @@ -344,7 +344,7 @@ def _groups_to_markdown(groups: dict[str, list[dict[str, Any]]]) -> str: lines.append(f"### {header}") lines.append(entry["content"].strip()) lines.append("") - elif entry["prefix"] == "screen": + elif entry["prefix"] == "percept": lines.append("### Screen Activity") lines.append(entry["content"].strip()) lines.append("") @@ -407,7 +407,7 @@ def cluster_scan(day: str) -> tuple[list[tuple[str, str]], list[tuple[str, str]] date_str = _date_str(day_dir) transcript_slots: set[datetime] = set() - screen_slots: set[datetime] = set() + percept_slots: set[datetime] = set() day_path_obj = Path(day_dir) # Check timestamp subdirectories for content files @@ -437,11 +437,11 @@ def cluster_scan(day: str) -> tuple[list[tuple[str, str]], list[tuple[str, str]] if (seg_path / "screen.jsonl").exists() or any( seg_path.glob("*_screen.jsonl") ): - screen_slots.add(slot) + percept_slots.add(slot) transcript_ranges = _slots_to_ranges(sorted(transcript_slots)) - screen_ranges = _slots_to_ranges(sorted(screen_slots)) - return transcript_ranges, screen_ranges + percept_ranges = _slots_to_ranges(sorted(percept_slots)) + return transcript_ranges, percept_ranges def cluster_segments(day: str) -> list[dict[str, Any]]: @@ -458,7 +458,7 @@ def cluster_segments(day: str) -> list[dict[str, Any]]: - key: segment directory name (HHMMSS_LEN format) - start: start time as HH:MM - end: end time as HH:MM - - types: list of content types present ("transcripts", "screen", or both) + - types: list of content types present ("transcripts", "percepts", or both) """ from think.utils import segment_parse @@ -489,7 +489,7 @@ def cluster_segments(day: str) -> list[dict[str, Any]]: # Check for screen content if (seg_path / "screen.jsonl").exists() or any(seg_path.glob("*_screen.jsonl")): - types.append("screen") + types.append("percepts") if not types: continue @@ -546,14 +546,14 @@ def cluster( Args: day: Day in YYYYMMDD format - sources: Dict with keys "transcripts", "screen", "agents". + sources: Dict with keys "transcripts", "percepts", "agents". Values can be bool, "required" string, or dict (for agents). The "agents" source can be a dict for selective filtering, e.g., {"entities": True, "meetings": "required"}. Returns: Tuple of (markdown, source_counts) where source_counts is a dict - with keys "transcripts", "screen", "agents" mapping to entry counts. + with keys "transcripts", "percepts", "agents" mapping to entry counts. """ empty_counts = {"transcripts": 0, "screen": 0, "agents": 0} @@ -565,7 +565,7 @@ def cluster( entries = _load_entries( day_dir, transcripts=sources.get("transcripts", False), - screen=sources.get("screen", False), + percepts=sources.get("percepts", False), agents=sources.get("agents", False), ) if not entries: @@ -590,13 +590,13 @@ def cluster_period( Args: day: Day in YYYYMMDD format segment: Segment key in HHMMSS_LEN format (e.g., "163045_300") - sources: Dict with keys "transcripts", "screen", "agents". + sources: Dict with keys "transcripts", "percepts", "agents". Values can be bool, "required" string, or dict (for agents). stream: Stream name. If None, searches all streams for the segment. Returns: Tuple of (markdown, source_counts) where source_counts is a dict - with keys "transcripts", "screen", "agents" mapping to entry counts. + with keys "transcripts", "percepts", "agents" mapping to entry counts. """ empty_counts = {"transcripts": 0, "screen": 0, "agents": 0} @@ -608,7 +608,7 @@ def cluster_period( entries = _load_entries_from_segment( str(segment_dir), transcripts=sources.get("transcripts", False), - screen=sources.get("screen", False), + percepts=sources.get("percepts", False), agents=sources.get("agents", False), ) if not entries: @@ -622,7 +622,7 @@ def cluster_period( def _load_entries_from_segment( segment_dir: str, transcripts: bool, - screen: bool, + percepts: bool, agents: bool | dict[str, bool | str], ) -> list[dict[str, Any]]: """Load entries from a single segment directory. @@ -630,7 +630,7 @@ def _load_entries_from_segment( Args: segment_dir: Path to segment directory (e.g., /path/to/20251109/163045_300) transcripts: Whether to load transcript content (JSONL and markdown) - screen: Whether to load raw screen data from *screen.jsonl files + percepts: Whether to load raw screen data from *screen.jsonl files agents: Whether to load agent output summaries from *.md files Returns: @@ -639,7 +639,7 @@ def _load_entries_from_segment( segment_path_obj = Path(segment_dir) # Parent is stream dir; grandparent is day dir date_str = _date_str(str(segment_path_obj.parent.parent)) - entries = _process_segment(segment_path_obj, date_str, transcripts, screen, agents) + entries = _process_segment(segment_path_obj, date_str, transcripts, percepts, agents) entries.sort(key=lambda e: e["timestamp"]) return entries @@ -660,13 +660,13 @@ def cluster_span( Args: day: Day in YYYYMMDD format span: List of segment keys in HHMMSS_LEN format (e.g., ["163045_300", "170000_600"]) - sources: Dict with keys "transcripts", "screen", "agents". + sources: Dict with keys "transcripts", "percepts", "agents". Values can be bool, "required" string, or dict (for agents). stream: Stream name. If None, searches all streams for each segment. Returns: Tuple of (markdown, source_counts) where source_counts is a dict - with keys "transcripts", "screen", "agents" mapping to entry counts. + with keys "transcripts", "percepts", "agents" mapping to entry counts. Raises: ValueError: If any segment directories are missing @@ -692,7 +692,7 @@ def cluster_span( segment_entries = _load_entries_from_segment( str(seg_dir), transcripts=sources.get("transcripts", False), - screen=sources.get("screen", False), + percepts=sources.get("percepts", False), agents=sources.get("agents", False), ) entries.extend(segment_entries) @@ -735,7 +735,7 @@ def cluster_range( day: Day in YYYYMMDD format start: Start time in HHMMSS format end: End time in HHMMSS format - sources: Dict with keys "transcripts", "screen", "agents". + sources: Dict with keys "transcripts", "percepts", "agents". Values can be bool, "required" string, or dict (for agents). """ day_dir = str(day_path(day)) @@ -746,7 +746,7 @@ def cluster_range( entries = _load_entries( day_dir, transcripts=sources.get("transcripts", False), - screen=sources.get("screen", False), + percepts=sources.get("percepts", False), agents=sources.get("agents", False), ) # Include segments that overlap with the requested range diff --git a/think/journal_stats.py b/think/journal_stats.py index 2c1a1e784..f35205b53 100644 --- a/think/journal_stats.py +++ b/think/journal_stats.py @@ -23,7 +23,7 @@ class JournalStats: self.days: Dict[str, Dict[str, float | int]] = {} self.totals: Counter[str] = Counter() self.total_transcript_duration = 0.0 - self.total_screen_duration = 0.0 + self.total_percept_duration = 0.0 self.agent_counts: Counter[str] = Counter() self.agent_minutes: Counter[str] = Counter() self.facet_counts: Counter[str] = Counter() @@ -107,7 +107,7 @@ class JournalStats: times_seconds = [self._parse_timestamp(t) for t in timestamps] return max(times_seconds) - min(times_seconds) - def _calculate_screen_duration(self, frames: list) -> float: + def _calculate_percept_duration(self, frames: list) -> float: """Calculate screen duration from min/max frame timestamps.""" # Skip header (first element if it has no frame_id) frame_timestamps = [ @@ -132,13 +132,13 @@ class JournalStats: counts_for_totals = { k: v for k, v in stats.items() - if k not in ("transcript_duration", "screen_duration") + if k not in ("transcript_duration", "percept_duration") } self.totals.update(counts_for_totals) # Accumulate durations self.total_transcript_duration += stats.get("transcript_duration", 0.0) - self.total_screen_duration += stats.get("screen_duration", 0.0) + self.total_percept_duration += stats.get("percept_duration", 0.0) # Apply agent data day_agent_counts: Dict[str, int] = {} @@ -175,7 +175,7 @@ class JournalStats: """Scan a single day and return stats dict for caching.""" stats: Counter[str] = Counter() transcript_duration = 0.0 - screen_duration = 0.0 + percept_duration = 0.0 day_dir = Path(path) # Track agent data for cache @@ -225,7 +225,7 @@ class JournalStats: screen_files = list(day_dir.glob("*/*/screen.jsonl")) screen_files.extend(day_dir.glob("*/*/*_screen.jsonl")) for jsonl_file in sorted(screen_files): - stats["screen_sessions"] += 1 + stats["percept_sessions"] += 1 try: frames = load_analysis_frames(jsonl_file) @@ -235,12 +235,12 @@ class JournalStats: # Count frames (excluding header) frame_count = sum(1 for f in frames if "frame_id" in f) - stats["screen_frames"] += frame_count + stats["percept_frames"] += frame_count # Calculate duration from timestamps if frame_count > 0: - duration = self._calculate_screen_duration(frames) - screen_duration += duration + duration = self._calculate_percept_duration(frames) + percept_duration += duration except (OSError, IOError) as e: logger.warning(f"Error reading screen file {jsonl_file}: {e}") @@ -328,7 +328,7 @@ class JournalStats: # --- Build return dict --- stats["transcript_duration"] = transcript_duration - stats["screen_duration"] = screen_duration + stats["percept_duration"] = percept_duration return { "stats": dict(stats), @@ -460,7 +460,7 @@ class JournalStats: logger.info( f"Scanned {len(self.days)} days, " f"{self.totals.get('transcript_sessions', 0)} transcript sessions, " - f"{self.totals.get('screen_sessions', 0)} screen sessions" + f"{self.totals.get('percept_sessions', 0)} percept sessions" f"{cache_status}" ) @@ -470,7 +470,7 @@ class JournalStats: "days": self.days, "totals": dict(self.totals), "total_transcript_duration": self.total_transcript_duration, - "total_screen_duration": self.total_screen_duration, + "total_percept_duration": self.total_percept_duration, "agent_counts": dict(self.agent_counts), "agent_minutes": {k: round(v, 2) for k, v in self.agent_minutes.items()}, "agent_counts_by_day": self.agent_counts_by_day, diff --git a/think/muse.py b/think/muse.py index 600109867..e3cd6f4f0 100644 --- a/think/muse.py +++ b/think/muse.py @@ -335,7 +335,7 @@ _DEFAULT_INSTRUCTIONS = { "activity": False, "sources": { "transcripts": False, - "screen": False, + "percepts": False, "agents": False, }, } @@ -431,7 +431,7 @@ def compose_instructions( true = include current date/time in extra_context - "day": false | true (default: false) true = include analysis day context (requires analysis_day parameter) - - "sources": {"transcripts": bool, "screen": bool, "agents": bool|dict} + - "sources": {"transcripts": bool, "percepts": bool, "agents": bool|dict} The "agents" source can be: - bool: True (all agents), False (no agents) - "required": all agents, fail if none found @@ -445,7 +445,7 @@ def compose_instructions( - system_prompt_name: str - name of system prompt (for cache keys) - user_instruction: str | None - loaded from user_prompt if provided - extra_context: str | None - facets + now + day context - - sources: dict - {"transcripts": bool, "screen": bool, "agents": bool|dict} + - sources: dict - {"transcripts": bool, "percepts": bool, "agents": bool|dict} """ from think.utils import format_day