diff --git a/solstone/think/briefing.py b/solstone/think/briefing.py index 8d4833eb1..2ac520cd8 100644 --- a/solstone/think/briefing.py +++ b/solstone/think/briefing.py @@ -13,7 +13,7 @@ from solstone.think.talent import morning_briefing_path logger = logging.getLogger(__name__) -ABSENT_TEXT = "Not specified in this document" +BRIEFING_ABSENT_TEXT = "Nothing to report." SECTION_KEYS = ( "your_day", "yesterday", @@ -113,14 +113,13 @@ def render_briefing_markdown(briefing: dict) -> str: lines: list[str] = [] if preamble: lines.extend(f"> {line}" if line else ">" for line in preamble.splitlines()) - else: - lines.append(f"> {ABSENT_TEXT}") for key in SECTION_KEYS: - lines.append("") + if lines: + lines.append("") lines.append(f"## {SECTION_HEADINGS[key]}") lines.append("") - lines.append(sections.get(key) or ABSENT_TEXT) + lines.append(sections.get(key) or BRIEFING_ABSENT_TEXT) return "\n".join(lines).strip() diff --git a/solstone/think/tools/sol.py b/solstone/think/tools/sol.py index 445cca759..810513d3f 100644 --- a/solstone/think/tools/sol.py +++ b/solstone/think/tools/sol.py @@ -36,7 +36,6 @@ from solstone.think.steward import ( latest_daily_run_complete_ts, release_steward_lock, ) -from solstone.think.talent import morning_briefing_path from solstone.think.utils import day_dirs, get_journal, require_solstone app = typer.Typer( @@ -283,12 +282,9 @@ def briefing_cmd( # No day specified — find most recent for day in sorted(day_dirs().keys(), reverse=True): - briefing = morning_briefing_path(day) - if briefing.exists() and briefing.stat().st_size > 0: - data = load_briefing(day) - if data is None: - continue - typer.echo(render_briefing_markdown(data)) + briefing = load_briefing(day) + if briefing is not None: + typer.echo(render_briefing_markdown(briefing)) return typer.echo("No briefing found.", err=True) diff --git a/tests/test_formatters.py b/tests/test_formatters.py index 4ce0017b9..0982931aa 100644 --- a/tests/test_formatters.py +++ b/tests/test_formatters.py @@ -473,6 +473,38 @@ def test_format_morning_briefing_renders_all_five_sections(): assert "- **work** — Newsletter summary." in rendered +def test_format_morning_briefing_omits_empty_preamble_and_marks_empty_sections(): + from solstone.think.talent_outputs import format_morning_briefing + + briefing = { + "metadata": { + "generated": "2026-03-27T06:45:00", + "model": "test-model", + "sources": { + "segments": 0, + "anticipated_activities": 0, + "facet_newsletters": 0, + "followups": 0, + "steward_health": "missing", + }, + "gaps": [], + "coverage_preamble": "", + }, + "your_day": [], + "yesterday": [], + "needs_attention": [], + "forward_look": [], + "reading": [], + } + + chunks, _meta = format_morning_briefing([briefing]) + + rendered = chunks[0]["markdown"] + assert not rendered.startswith(">") + assert rendered.count("## ") == 5 + assert rendered.count("Nothing to report.") == 5 + + def test_find_formattable_includes_day_level_morning_briefing_only(tmp_path: Path): from solstone.think.formatters import find_formattable_files diff --git a/tests/test_schema_prep.py b/tests/test_schema_prep.py index c46dcda3a..838a8caac 100644 --- a/tests/test_schema_prep.py +++ b/tests/test_schema_prep.py @@ -15,6 +15,8 @@ import pytest from solstone.apps.timeline.rollup import build_rollup_schema from solstone.think.models import SchemaValidationError, generate from solstone.think.schema_prep import prepare_provider_schema, unsupported_keyword_hits +from solstone.think.talent import RUNTIME_FACETS_SENTINEL +from tests.eval_schemas import DEFAULT_CASES, load_cases REPO_ROOT = Path(__file__).resolve().parents[1] @@ -110,6 +112,21 @@ def test_morning_briefing_schema_is_provider_portable(provider: str) -> None: ) +def test_schema_eval_cases_hydrate_runtime_facets() -> None: + cases = load_cases(DEFAULT_CASES) + morning_case = next( + case for case in cases if case["name"] == "morning_briefing_20260708_trimmed" + ) + + facet_schema = morning_case["schema"]["properties"]["reading"]["items"][ + "properties" + ]["facet"] + + assert facet_schema.get("enum") != [RUNTIME_FACETS_SENTINEL] + assert RUNTIME_FACETS_SENTINEL not in facet_schema.get("enum", []) + assert facet_schema["maxLength"] == 80 + + @pytest.mark.parametrize("provider", ["local", "openai", "google", "anthropic", "fake"]) def test_prepare_provider_schema_is_pure_and_idempotent( bounded_schema: dict[str, Any], provider: str diff --git a/tests/test_sol_call_identity_briefing.py b/tests/test_sol_call_identity_briefing.py index 5fec14731..1c39e03a4 100644 --- a/tests/test_sol_call_identity_briefing.py +++ b/tests/test_sol_call_identity_briefing.py @@ -59,6 +59,23 @@ def test_briefing_no_day_returns_most_recent_available(tmp_path, monkeypatch): assert "older-marker" in result.stdout +def test_briefing_no_day_skips_malformed_newest(tmp_path, monkeypatch): + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) + + _seed_briefing("20260101", "older-marker") + from solstone.think.talent import morning_briefing_path + + newest = morning_briefing_path("20260102") + newest.parent.mkdir(parents=True, exist_ok=True) + newest.write_text("{not json", encoding="utf-8") + + result = runner.invoke(app, ["briefing"]) + + assert result.exit_code == 0 + assert "## Yesterday" in result.stdout + assert "older-marker" in result.stdout + + def test_briefing_no_briefing_anywhere_exits_nonzero(tmp_path, monkeypatch): monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))