diff --git a/apps/entities/call.py b/apps/entities/call.py index 460edfa18..5001e2f27 100644 --- a/apps/entities/call.py +++ b/apps/entities/call.py @@ -13,6 +13,12 @@ from pathlib import Path import typer from think.entities.core import entity_slug, is_valid_entity_type +from think.entities.journal import ( + clear_journal_entity_cache, + get_or_create_journal_entity, + load_journal_entity, + save_journal_entity, +) from think.entities.loading import clear_entity_loading_cache, load_entities from think.entities.matching import resolve_entity, validate_aka_uniqueness from think.entities.observations import ( @@ -26,12 +32,6 @@ from think.entities.relationships import ( load_facet_relationship, save_facet_relationship, ) -from think.entities.journal import ( - clear_journal_entity_cache, - get_or_create_journal_entity, - load_journal_entity, - save_journal_entity, -) from think.entities.saving import ( save_detected_entity, update_detected_entity, diff --git a/apps/sol/maint/002_migrate_chronicle.py b/apps/sol/maint/002_migrate_chronicle.py new file mode 100644 index 000000000..bd1d5f999 --- /dev/null +++ b/apps/sol/maint/002_migrate_chronicle.py @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Migrate root day directories into chronicle/.""" + +from __future__ import annotations + +import argparse +import shutil +from dataclasses import dataclass +from pathlib import Path + +from think.utils import CHRONICLE_DIR, DATE_RE, get_journal, setup_cli + + +@dataclass +class MigrationSummary: + """Mutable counters for migration operations.""" + + moved: int = 0 + skipped: int = 0 + sqlite_deleted: int = 0 + errors: int = 0 + + +def _root_day_dirs(journal_path: Path) -> list[Path]: + return [ + path + for path in sorted(journal_path.iterdir()) + if path.is_dir() and DATE_RE.fullmatch(path.name) + ] + + +def _sqlite_paths(journal_path: Path) -> list[Path]: + db_path = journal_path / "indexer" / "journal.sqlite" + return [ + db_path, + db_path.with_name(f"{db_path.name}-wal"), + db_path.with_name(f"{db_path.name}-shm"), + ] + + +def _validate_end_state(journal_path: Path) -> None: + if _root_day_dirs(journal_path): + raise RuntimeError("root day directories remain after chronicle migration") + if not (journal_path / CHRONICLE_DIR).is_dir(): + raise RuntimeError("chronicle/ missing after chronicle migration") + remaining_sqlite = [path for path in _sqlite_paths(journal_path) if path.exists()] + if remaining_sqlite: + joined = ", ".join(str(path) for path in remaining_sqlite) + raise RuntimeError(f"sqlite files remain after chronicle migration: {joined}") + + +def migrate(journal_path: Path, dry_run: bool = False) -> MigrationSummary: + """Migrate root day directories into chronicle/.""" + summary = MigrationSummary() + day_dirs = _root_day_dirs(journal_path) + if not day_dirs: + print("Nothing to migrate.") + return summary + + chronicle_dir = journal_path / CHRONICLE_DIR + if dry_run: + print("[DRY-RUN] No files will be modified.") + else: + chronicle_dir.mkdir(parents=True, exist_ok=True) + + print(f"Migrating chronicle dirs in: {journal_path}") + for source_day in day_dirs: + target_day = chronicle_dir / source_day.name + if target_day.exists(): + print(f" Skip (already exists): {source_day.name}") + summary.skipped += 1 + continue + + print(f" Move: {source_day.name} -> {CHRONICLE_DIR}/{source_day.name}") + if not dry_run: + shutil.move(str(source_day), str(target_day)) + summary.moved += 1 + + if summary.moved and not dry_run: + for sqlite_path in _sqlite_paths(journal_path): + if not sqlite_path.exists(): + continue + print(f" Delete: {sqlite_path.relative_to(journal_path)}") + sqlite_path.unlink() + summary.sqlite_deleted += 1 + _validate_end_state(journal_path) + + return summary + + +def main() -> None: + """CLI entrypoint.""" + parser = argparse.ArgumentParser( + description="Migrate root day directories into chronicle/" + ) + parser.add_argument( + "--dry-run", action="store_true", help="Preview changes without writing" + ) + args = setup_cli(parser) + + summary = migrate(Path(get_journal()), dry_run=args.dry_run) + if summary.moved == 0 and summary.skipped == 0 and summary.sqlite_deleted == 0: + return + + print("Migration complete") + print(f" moved: {summary.moved}") + print(f" skipped: {summary.skipped}") + print(f" sqlite_deleted:{summary.sqlite_deleted}") + print(f" errors: {summary.errors}") + + +if __name__ == "__main__": + main() diff --git a/apps/sol/routes.py b/apps/sol/routes.py index a3ff94c54..8ab8b7922 100644 --- a/apps/sol/routes.py +++ b/apps/sol/routes.py @@ -19,7 +19,7 @@ from convey.utils import DATE_RE, format_date from think.facets import get_facets from think.models import calc_agent_cost from think.talent import get_output_path, get_talent_configs -from think.utils import day_path, updated_days +from think.utils import resolve_journal_path, updated_days sol_bp = Blueprint( "app:sol", @@ -510,7 +510,12 @@ def api_output_file(day: str, filename: str) -> Any: if filename.startswith("facets/"): file_path = (journal_root / filename).resolve() else: - file_path = (day_path(day, create=False) / filename).resolve() + try: + file_path = resolve_journal_path( + journal_root, f"{day}/{filename}" + ).resolve() + except ValueError: + return jsonify(error="Invalid path"), 403 # Security: ensure path is within the journal directory try: diff --git a/apps/speakers/voiceprint_io.py b/apps/speakers/voiceprint_io.py index 9027542f3..ec98d3740 100644 --- a/apps/speakers/voiceprint_io.py +++ b/apps/speakers/voiceprint_io.py @@ -11,8 +11,6 @@ and adds an integrity check after writing. import fcntl import logging -import os -import re from pathlib import Path import numpy as np diff --git a/apps/support/portal.py b/apps/support/portal.py index 44c773dc5..5726f0d32 100644 --- a/apps/support/portal.py +++ b/apps/support/portal.py @@ -403,8 +403,8 @@ class PortalClient: raise RuntimeError( "could not register — all handle variants were taken after 3 attempts" ) - import re import random + import re import string base = re.sub(r"-[a-z0-9]{4}$", "", self._handle) diff --git a/convey/__init__.py b/convey/__init__.py index bd9304725..04afa2ee2 100644 --- a/convey/__init__.py +++ b/convey/__init__.py @@ -17,8 +17,7 @@ from jinja2 import ChoiceLoader, FileSystemLoader from apps import AppRegistry -from . import state -from . import system +from . import state, system from .apps import register_app_context from .bridge import emit, register_websocket from .config import bp as config_bp diff --git a/observe/describe.py b/observe/describe.py index ece1b5999..e1983d098 100644 --- a/observe/describe.py +++ b/observe/describe.py @@ -36,7 +36,13 @@ from observe.extract import DEFAULT_MAX_EXTRACTIONS, select_frames_for_extractio from observe.utils import get_segment_key from think.callosum import callosum_send from think.prompts import load_prompt -from think.utils import day_from_path, get_config, get_journal, setup_cli +from think.utils import ( + day_from_path, + get_config, + get_journal, + journal_relative_path, + setup_cli, +) logger = logging.getLogger(__name__) @@ -946,8 +952,8 @@ async def async_main(): journal_path = Path(get_journal()) try: - rel_input = video_path.relative_to(journal_path) - rel_output = output_path.relative_to(journal_path) + rel_input = journal_relative_path(journal_path, video_path) + rel_output = journal_relative_path(journal_path, output_path) except ValueError: rel_input = video_path rel_output = output_path diff --git a/observe/sense.py b/observe/sense.py index e4069f5d2..5bc64caad 100644 --- a/observe/sense.py +++ b/observe/sense.py @@ -24,7 +24,18 @@ from typing import Any, Dict, List, Optional from observe.utils import AUDIO_EXTENSIONS, VIDEO_EXTENSIONS from think.callosum import CallosumConnection from think.runner import ManagedProcess as RunnerManagedProcess -from think.utils import day_path, get_journal, get_rev, iter_segments, now_ms, setup_cli +from think.utils import ( + CHRONICLE_DIR, + DATE_RE, + day_path, + get_journal, + get_rev, + iter_segments, + journal_relative_path, + now_ms, + resolve_journal_path, + setup_cli, +) logger = logging.getLogger(__name__) @@ -173,20 +184,28 @@ class FileSensor: self.handlers[pattern] = (handler_name, command) logger.info(f"Registered handler '{handler_name}' for pattern '{pattern}'") + def _segment_relative_path(self, file_path: Path) -> Optional[Path]: + """Return day/stream/segment/file relative path for journal media files.""" + roots = [self.journal_dir / CHRONICLE_DIR, self.journal_dir] + for root in roots: + try: + rel_path = file_path.relative_to(root) + except ValueError: + continue + if len(rel_path.parts) == 4 and DATE_RE.fullmatch(rel_path.parts[0]): + return rel_path + return None + def _match_pattern(self, file_path: Path) -> Optional[tuple[str, List[str]]]: """Check if file matches any registered pattern.""" # Ignore hidden files (temp recordings with dot prefix) if file_path.name.startswith("."): return None - # Files should be in segment directories: journal_dir/YYYYMMDD/stream/HHMMSS_LEN/file.ext - # Expected structure: 4 parts from journal_dir - try: - rel_path = file_path.relative_to(self.journal_dir) - if len(rel_path.parts) != 4: - return None - except ValueError: - # File not under journal directory + # Files should be in segment directories: + # journal_dir/chronicle/YYYYMMDD/stream/HHMMSS_LEN/file.ext + # Expected structure after stripping journal_dir[/chronicle]: 4 parts + if self._segment_relative_path(file_path) is None: return None for pattern, handler_info in self.handlers.items(): @@ -208,7 +227,8 @@ class FileSensor: ): """Spawn a handler process for the file. - Files are expected to be in segment directories: YYYYMMDD/stream/HHMMSS_LEN/file.ext + Files are expected to be in segment directories: + YYYYMMDD/stream/HHMMSS_LEN/file.ext relative to journal_dir[/chronicle] Args: file_path: Path to the file to process (in segment directory) @@ -223,16 +243,13 @@ class FileSensor: cpu_fallback: If True, this is a retry after GPU failure (adds --cpu, skips tracking/events since already done on first attempt) """ - # Extract day and segment from path: journal_dir/YYYYMMDD/stream/HHMMSS_LEN/file.ext - try: - rel_path = file_path.relative_to(self.journal_dir) - if len(rel_path.parts) >= 4: - if day is None: - day = rel_path.parts[0] - if segment is None: - segment = rel_path.parts[2] - except ValueError: - pass + # Extract day and segment from path relative to journal_dir[/chronicle]. + rel_path = self._segment_relative_path(file_path) + if rel_path is not None: + if day is None: + day = rel_path.parts[0] + if segment is None: + segment = rel_path.parts[2] # Skip tracking/queueing for CPU fallback (already done on first attempt) if not cpu_fallback: @@ -617,10 +634,9 @@ class FileSensor: meta = {} meta["stream"] = stream - # Build full paths for all files in this segment - # Files are in segment directories: YYYYMMDD/stream/HHMMSS_LEN/filename - dp = day_path(day, create=False) - segment_dir = dp / stream / segment if stream else dp / segment + # Build full paths for all files in this segment. + rel_segment = f"{day}/{stream}/{segment}" if stream else f"{day}/{segment}" + segment_dir = resolve_journal_path(self.journal_dir, rel_segment) file_paths = [segment_dir / filename for filename in files] # Pre-register segment tracking with complete file list @@ -663,7 +679,7 @@ class FileSensor: status = {} # Get journal path for relative paths - journal_path = get_journal() + journal_path = Path(get_journal()) now = time.time() # Build status for each serialized handler queue @@ -674,7 +690,9 @@ class FileSensor: if handler_queue.current_process is not None: handler_proc = handler_queue.current_process try: - rel_file = str(handler_proc.file_path.relative_to(journal_path)) + rel_file = journal_relative_path( + journal_path, handler_proc.file_path + ) except ValueError: rel_file = str(handler_proc.file_path) @@ -691,7 +709,9 @@ class FileSensor: queued_list = [] for item in handler_queue.queue: try: - rel_file = str(item.file_path.relative_to(journal_path)) + rel_file = journal_relative_path( + journal_path, item.file_path + ) except ValueError: rel_file = str(item.file_path) @@ -1094,7 +1114,7 @@ def main(): if deleted: logger.info(f"Would delete {len(deleted)} output file(s):") for path in deleted: - logger.info(f" {path.relative_to(journal)}") + logger.info(f" {journal_relative_path(Path(journal), path)}") else: logger.info("No files to delete") return @@ -1116,7 +1136,7 @@ def main(): ) logger.info(f"Would process {len(to_process)} file(s) ({breakdown}):") for file_path, handler_name, command in to_process: - logger.info(f" {file_path.relative_to(journal)}") + logger.info(f" {journal_relative_path(Path(journal), file_path)}") else: logger.info("No unprocessed files found") return diff --git a/observe/transcribe/main.py b/observe/transcribe/main.py index f6830a2ca..9365effd1 100644 --- a/observe/transcribe/main.py +++ b/observe/transcribe/main.py @@ -77,6 +77,8 @@ from think.utils import ( get_config, get_journal, iter_segments, + journal_relative_path, + resolve_journal_path, setup_cli, ) @@ -187,7 +189,7 @@ def _build_base_event( day = day_from_path(audio_path) try: - rel_input = audio_path.relative_to(journal_path) + rel_input = journal_relative_path(journal_path, audio_path) except ValueError: rel_input = audio_path @@ -616,10 +618,10 @@ def process_audio( event["outcome"] = "transcribed" event["duration_ms"] = int((time.time() - start_time) * 1000) try: - rel_output = jsonl_path.relative_to(journal_path) + rel_output = journal_relative_path(journal_path, jsonl_path) except ValueError: rel_output = jsonl_path - event["output"] = str(rel_output) + event["output"] = rel_output callosum_send("observe", "transcribed", **event) @@ -852,7 +854,10 @@ def main(): audio_path = Path(args.audio_path) if not audio_path.exists(): - journal_relative = Path(get_journal()) / args.audio_path + if audio_path.is_absolute(): + journal_relative = Path(get_journal()) / audio_path.as_posix().lstrip("/") + else: + journal_relative = resolve_journal_path(get_journal(), args.audio_path) if journal_relative.exists(): audio_path = journal_relative else: diff --git a/observe/transfer.py b/observe/transfer.py index e65af3f82..9099579ea 100644 --- a/observe/transfer.py +++ b/observe/transfer.py @@ -29,7 +29,14 @@ from typing import Any import requests from think.callosum import callosum_send -from think.utils import day_path, get_journal, iter_segments, now_ms, setup_cli +from think.utils import ( + CHRONICLE_DIR, + day_path, + get_journal, + iter_segments, + now_ms, + setup_cli, +) from .utils import compute_file_sha256, find_available_segment @@ -375,10 +382,15 @@ def _normalize_url(to: str) -> str: def _parse_day_spec(day_spec: str | None, journal_root: Path) -> list[str]: """Parse a single day, day range, or default to all journal days.""" if day_spec is None: + day_root = ( + journal_root / CHRONICLE_DIR + if (journal_root / CHRONICLE_DIR).is_dir() + else journal_root + ) return sorted( [ day_dir.name - for day_dir in journal_root.iterdir() + for day_dir in day_root.iterdir() if day_dir.is_dir() and re.match(r"^\d{8}$", day_dir.name) ] ) diff --git a/pyproject.toml b/pyproject.toml index 6c46c828f..09e099e1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,7 +134,7 @@ constraint-dependencies = ["numba>=0.60"] override-dependencies = ["webrtcvad>=99; python_version < '0'"] [tool.pytest.ini_options] -addopts = "--import-mode=importlib" +addopts = "--import-mode=importlib --basetemp=/var/tmp/pytest-solstone" testpaths = ["tests", "apps"] python_files = ["test_*.py"] python_classes = ["Test*"] @@ -144,7 +144,6 @@ markers = [ ] timeout = 5 tmp_path_retention_policy = "none" -basetemp = "/var/tmp/pytest-solstone" filterwarnings = [ # Third-party deprecation warnings (Python 3.14+) "ignore:codecs.open.* is deprecated:DeprecationWarning", diff --git a/tests/fixtures/journal/20240101/agents/flow.md b/tests/fixtures/journal/chronicle/20240101/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20240101/agents/flow.md rename to tests/fixtures/journal/chronicle/20240101/agents/flow.md diff --git a/tests/fixtures/journal/20240101/agents/knowledge_graph.md b/tests/fixtures/journal/chronicle/20240101/agents/knowledge_graph.md similarity index 100% rename from tests/fixtures/journal/20240101/agents/knowledge_graph.md rename to tests/fixtures/journal/chronicle/20240101/agents/knowledge_graph.md diff --git a/tests/fixtures/journal/20240101/agents/meetings.md b/tests/fixtures/journal/chronicle/20240101/agents/meetings.md similarity index 100% rename from tests/fixtures/journal/20240101/agents/meetings.md rename to tests/fixtures/journal/chronicle/20240101/agents/meetings.md diff --git a/tests/fixtures/journal/20240101/default/123456_300/agents/audio.md b/tests/fixtures/journal/chronicle/20240101/default/123456_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/agents/audio.md diff --git a/tests/fixtures/journal/20240101/default/123456_300/agents/screen.md b/tests/fixtures/journal/chronicle/20240101/default/123456_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/agents/screen.md diff --git a/tests/fixtures/journal/20240101/default/123456_300/audio.json b/tests/fixtures/journal/chronicle/20240101/default/123456_300/audio.json similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/audio.json rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/audio.json diff --git a/tests/fixtures/journal/20240101/default/123456_300/audio.jsonl b/tests/fixtures/journal/chronicle/20240101/default/123456_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/audio.jsonl diff --git a/tests/fixtures/journal/20240101/default/123456_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20240101/default/123456_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20240101/default/123456_300/monitor_1_diff.png b/tests/fixtures/journal/chronicle/20240101/default/123456_300/monitor_1_diff.png similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/monitor_1_diff.png rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/monitor_1_diff.png diff --git a/tests/fixtures/journal/20240101/default/123456_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20240101/default/123456_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20240101/default/123456_300/screen.jsonl b/tests/fixtures/journal/chronicle/20240101/default/123456_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/screen.jsonl diff --git a/tests/fixtures/journal/20240101/default/123456_300/stream.json b/tests/fixtures/journal/chronicle/20240101/default/123456_300/stream.json similarity index 100% rename from tests/fixtures/journal/20240101/default/123456_300/stream.json rename to tests/fixtures/journal/chronicle/20240101/default/123456_300/stream.json diff --git a/tests/fixtures/journal/20240101/import.apple/140000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20240101/import.apple/140000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20240101/import.apple/140000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20240101/import.apple/140000_300/audio.jsonl diff --git a/tests/fixtures/journal/20240101/import.apple/140000_300/stream.json b/tests/fixtures/journal/chronicle/20240101/import.apple/140000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20240101/import.apple/140000_300/stream.json rename to tests/fixtures/journal/chronicle/20240101/import.apple/140000_300/stream.json diff --git a/tests/fixtures/journal/20240101/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20240101/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20240101/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20240101/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20240102/agents/flow.md b/tests/fixtures/journal/chronicle/20240102/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20240102/agents/flow.md rename to tests/fixtures/journal/chronicle/20240102/agents/flow.md diff --git a/tests/fixtures/journal/20240102/default/234567_300/agents/audio.md b/tests/fixtures/journal/chronicle/20240102/default/234567_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20240102/default/234567_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20240102/default/234567_300/agents/audio.md diff --git a/tests/fixtures/journal/20240102/default/234567_300/agents/screen.md b/tests/fixtures/journal/chronicle/20240102/default/234567_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20240102/default/234567_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20240102/default/234567_300/agents/screen.md diff --git a/tests/fixtures/journal/20240102/default/234567_300/audio.json b/tests/fixtures/journal/chronicle/20240102/default/234567_300/audio.json similarity index 100% rename from tests/fixtures/journal/20240102/default/234567_300/audio.json rename to tests/fixtures/journal/chronicle/20240102/default/234567_300/audio.json diff --git a/tests/fixtures/journal/20240102/default/234567_300/audio.jsonl b/tests/fixtures/journal/chronicle/20240102/default/234567_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20240102/default/234567_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20240102/default/234567_300/audio.jsonl diff --git a/tests/fixtures/journal/20240102/default/234567_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20240102/default/234567_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20240102/default/234567_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20240102/default/234567_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20240102/default/234567_300/screen.jsonl b/tests/fixtures/journal/chronicle/20240102/default/234567_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20240102/default/234567_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20240102/default/234567_300/screen.jsonl diff --git a/tests/fixtures/journal/20240102/default/234567_300/stream.json b/tests/fixtures/journal/chronicle/20240102/default/234567_300/stream.json similarity index 100% rename from tests/fixtures/journal/20240102/default/234567_300/stream.json rename to tests/fixtures/journal/chronicle/20240102/default/234567_300/stream.json diff --git a/tests/fixtures/journal/20240102/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20240102/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20240102/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20240102/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20250101/health/stream.updated b/tests/fixtures/journal/chronicle/20250101/health/stream.updated similarity index 100% rename from tests/fixtures/journal/20250101/health/stream.updated rename to tests/fixtures/journal/chronicle/20250101/health/stream.updated diff --git a/tests/fixtures/journal/20250124/TODO.md b/tests/fixtures/journal/chronicle/20250124/TODO.md similarity index 100% rename from tests/fixtures/journal/20250124/TODO.md rename to tests/fixtures/journal/chronicle/20250124/TODO.md diff --git a/tests/fixtures/journal/20260101/import.chatgpt/100000_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.chatgpt/100000_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.chatgpt/100000_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.chatgpt/100000_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.chatgpt/100000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.chatgpt/100000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.chatgpt/100000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.chatgpt/100000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.chatgpt/100500_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.chatgpt/100500_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.chatgpt/100500_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.chatgpt/100500_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.chatgpt/100500_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.chatgpt/100500_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.chatgpt/100500_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.chatgpt/100500_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.chatgpt/101000_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.chatgpt/101000_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.chatgpt/101000_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.chatgpt/101000_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.chatgpt/101000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.chatgpt/101000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.chatgpt/101000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.chatgpt/101000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.claude/140000_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.claude/140000_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.claude/140000_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.claude/140000_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.claude/140000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.claude/140000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.claude/140000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.claude/140000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.claude/140500_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.claude/140500_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.claude/140500_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.claude/140500_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.claude/140500_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.claude/140500_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.claude/140500_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.claude/140500_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.claude/141000_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.claude/141000_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.claude/141000_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.claude/141000_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.claude/141000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.claude/141000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.claude/141000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.claude/141000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.gemini/110000_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.gemini/110000_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.gemini/110000_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.gemini/110000_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.gemini/110000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.gemini/110000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.gemini/110000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.gemini/110000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.gemini/110500_300/conversation_transcript.jsonl b/tests/fixtures/journal/chronicle/20260101/import.gemini/110500_300/conversation_transcript.jsonl similarity index 100% rename from tests/fixtures/journal/20260101/import.gemini/110500_300/conversation_transcript.jsonl rename to tests/fixtures/journal/chronicle/20260101/import.gemini/110500_300/conversation_transcript.jsonl diff --git a/tests/fixtures/journal/20260101/import.gemini/110500_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.gemini/110500_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.gemini/110500_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.gemini/110500_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.ics/090000_300/event_transcript.md b/tests/fixtures/journal/chronicle/20260101/import.ics/090000_300/event_transcript.md similarity index 100% rename from tests/fixtures/journal/20260101/import.ics/090000_300/event_transcript.md rename to tests/fixtures/journal/chronicle/20260101/import.ics/090000_300/event_transcript.md diff --git a/tests/fixtures/journal/20260101/import.ics/090000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.ics/090000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.ics/090000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.ics/090000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.ics/093000_300/event_transcript.md b/tests/fixtures/journal/chronicle/20260101/import.ics/093000_300/event_transcript.md similarity index 100% rename from tests/fixtures/journal/20260101/import.ics/093000_300/event_transcript.md rename to tests/fixtures/journal/chronicle/20260101/import.ics/093000_300/event_transcript.md diff --git a/tests/fixtures/journal/20260101/import.ics/093000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.ics/093000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.ics/093000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.ics/093000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.kindle/200000_300/highlights_transcript.md b/tests/fixtures/journal/chronicle/20260101/import.kindle/200000_300/highlights_transcript.md similarity index 100% rename from tests/fixtures/journal/20260101/import.kindle/200000_300/highlights_transcript.md rename to tests/fixtures/journal/chronicle/20260101/import.kindle/200000_300/highlights_transcript.md diff --git a/tests/fixtures/journal/20260101/import.kindle/200000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.kindle/200000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.kindle/200000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.kindle/200000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.kindle/200500_300/highlights_transcript.md b/tests/fixtures/journal/chronicle/20260101/import.kindle/200500_300/highlights_transcript.md similarity index 100% rename from tests/fixtures/journal/20260101/import.kindle/200500_300/highlights_transcript.md rename to tests/fixtures/journal/chronicle/20260101/import.kindle/200500_300/highlights_transcript.md diff --git a/tests/fixtures/journal/20260101/import.kindle/200500_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.kindle/200500_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.kindle/200500_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.kindle/200500_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.obsidian/160000_300/note_transcript.md b/tests/fixtures/journal/chronicle/20260101/import.obsidian/160000_300/note_transcript.md similarity index 100% rename from tests/fixtures/journal/20260101/import.obsidian/160000_300/note_transcript.md rename to tests/fixtures/journal/chronicle/20260101/import.obsidian/160000_300/note_transcript.md diff --git a/tests/fixtures/journal/20260101/import.obsidian/160000_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.obsidian/160000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.obsidian/160000_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.obsidian/160000_300/stream.json diff --git a/tests/fixtures/journal/20260101/import.obsidian/160500_300/note_transcript.md b/tests/fixtures/journal/chronicle/20260101/import.obsidian/160500_300/note_transcript.md similarity index 100% rename from tests/fixtures/journal/20260101/import.obsidian/160500_300/note_transcript.md rename to tests/fixtures/journal/chronicle/20260101/import.obsidian/160500_300/note_transcript.md diff --git a/tests/fixtures/journal/20260101/import.obsidian/160500_300/stream.json b/tests/fixtures/journal/chronicle/20260101/import.obsidian/160500_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260101/import.obsidian/160500_300/stream.json rename to tests/fixtures/journal/chronicle/20260101/import.obsidian/160500_300/stream.json diff --git a/tests/fixtures/journal/20260304/agents/flow.md b/tests/fixtures/journal/chronicle/20260304/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20260304/agents/flow.md rename to tests/fixtures/journal/chronicle/20260304/agents/flow.md diff --git a/tests/fixtures/journal/20260304/agents/knowledge_graph.md b/tests/fixtures/journal/chronicle/20260304/agents/knowledge_graph.md similarity index 100% rename from tests/fixtures/journal/20260304/agents/knowledge_graph.md rename to tests/fixtures/journal/chronicle/20260304/agents/knowledge_graph.md diff --git a/tests/fixtures/journal/20260304/agents/meetings.md b/tests/fixtures/journal/chronicle/20260304/agents/meetings.md similarity index 100% rename from tests/fixtures/journal/20260304/agents/meetings.md rename to tests/fixtures/journal/chronicle/20260304/agents/meetings.md diff --git a/tests/fixtures/journal/20260304/default/090000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260304/default/090000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260304/default/090000_300/agents/speaker_labels.json b/tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/speaker_labels.json similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/agents/speaker_labels.json rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/speaker_labels.json diff --git a/tests/fixtures/journal/20260304/default/090000_300/agents/speakers.json b/tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/speakers.json similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/agents/speakers.json rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/agents/speakers.json diff --git a/tests/fixtures/journal/20260304/default/090000_300/audio.json b/tests/fixtures/journal/chronicle/20260304/default/090000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/audio.json rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/audio.json diff --git a/tests/fixtures/journal/20260304/default/090000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260304/default/090000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260304/default/090000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260304/default/090000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260304/default/090000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260304/default/090000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260304/default/090000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260304/default/090000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260304/default/090000_300/stream.json b/tests/fixtures/journal/chronicle/20260304/default/090000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260304/default/090000_300/stream.json rename to tests/fixtures/journal/chronicle/20260304/default/090000_300/stream.json diff --git a/tests/fixtures/journal/20260304/default/140000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260304/default/140000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260304/default/140000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260304/default/140000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260304/default/140000_300/audio.json b/tests/fixtures/journal/chronicle/20260304/default/140000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/audio.json rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/audio.json diff --git a/tests/fixtures/journal/20260304/default/140000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260304/default/140000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260304/default/140000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260304/default/140000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260304/default/140000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260304/default/140000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260304/default/140000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260304/default/140000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260304/default/140000_300/stream.json b/tests/fixtures/journal/chronicle/20260304/default/140000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260304/default/140000_300/stream.json rename to tests/fixtures/journal/chronicle/20260304/default/140000_300/stream.json diff --git a/tests/fixtures/journal/20260304/default/180000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260304/default/180000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260304/default/180000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260304/default/180000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260304/default/180000_300/audio.json b/tests/fixtures/journal/chronicle/20260304/default/180000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/audio.json rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/audio.json diff --git a/tests/fixtures/journal/20260304/default/180000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260304/default/180000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260304/default/180000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260304/default/180000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260304/default/180000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260304/default/180000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260304/default/180000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260304/default/180000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260304/default/180000_300/stream.json b/tests/fixtures/journal/chronicle/20260304/default/180000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260304/default/180000_300/stream.json rename to tests/fixtures/journal/chronicle/20260304/default/180000_300/stream.json diff --git a/tests/fixtures/journal/20260304/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20260304/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20260304/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20260304/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20260305/agents/flow.md b/tests/fixtures/journal/chronicle/20260305/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20260305/agents/flow.md rename to tests/fixtures/journal/chronicle/20260305/agents/flow.md diff --git a/tests/fixtures/journal/20260305/agents/meetings.md b/tests/fixtures/journal/chronicle/20260305/agents/meetings.md similarity index 100% rename from tests/fixtures/journal/20260305/agents/meetings.md rename to tests/fixtures/journal/chronicle/20260305/agents/meetings.md diff --git a/tests/fixtures/journal/20260305/default/090000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260305/default/090000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260305/default/090000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260305/default/090000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260305/default/090000_300/audio.json b/tests/fixtures/journal/chronicle/20260305/default/090000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/audio.json rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/audio.json diff --git a/tests/fixtures/journal/20260305/default/090000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260305/default/090000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260305/default/090000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260305/default/090000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260305/default/090000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260305/default/090000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260305/default/090000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260305/default/090000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260305/default/090000_300/stream.json b/tests/fixtures/journal/chronicle/20260305/default/090000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260305/default/090000_300/stream.json rename to tests/fixtures/journal/chronicle/20260305/default/090000_300/stream.json diff --git a/tests/fixtures/journal/20260305/default/133000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260305/default/133000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260305/default/133000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260305/default/133000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260305/default/133000_300/audio.json b/tests/fixtures/journal/chronicle/20260305/default/133000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/audio.json rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/audio.json diff --git a/tests/fixtures/journal/20260305/default/133000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260305/default/133000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260305/default/133000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260305/default/133000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260305/default/133000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260305/default/133000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260305/default/133000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260305/default/133000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260305/default/133000_300/stream.json b/tests/fixtures/journal/chronicle/20260305/default/133000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260305/default/133000_300/stream.json rename to tests/fixtures/journal/chronicle/20260305/default/133000_300/stream.json diff --git a/tests/fixtures/journal/20260305/default/220000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260305/default/220000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260305/default/220000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260305/default/220000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260305/default/220000_300/audio.json b/tests/fixtures/journal/chronicle/20260305/default/220000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/audio.json rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/audio.json diff --git a/tests/fixtures/journal/20260305/default/220000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260305/default/220000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260305/default/220000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260305/default/220000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260305/default/220000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260305/default/220000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260305/default/220000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260305/default/220000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260305/default/220000_300/stream.json b/tests/fixtures/journal/chronicle/20260305/default/220000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260305/default/220000_300/stream.json rename to tests/fixtures/journal/chronicle/20260305/default/220000_300/stream.json diff --git a/tests/fixtures/journal/20260305/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20260305/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20260305/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20260305/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20260306/agents/flow.md b/tests/fixtures/journal/chronicle/20260306/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20260306/agents/flow.md rename to tests/fixtures/journal/chronicle/20260306/agents/flow.md diff --git a/tests/fixtures/journal/20260306/agents/knowledge_graph.md b/tests/fixtures/journal/chronicle/20260306/agents/knowledge_graph.md similarity index 100% rename from tests/fixtures/journal/20260306/agents/knowledge_graph.md rename to tests/fixtures/journal/chronicle/20260306/agents/knowledge_graph.md diff --git a/tests/fixtures/journal/20260306/default/093000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260306/default/093000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260306/default/093000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260306/default/093000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260306/default/093000_300/audio.json b/tests/fixtures/journal/chronicle/20260306/default/093000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/audio.json rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/audio.json diff --git a/tests/fixtures/journal/20260306/default/093000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260306/default/093000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260306/default/093000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260306/default/093000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260306/default/093000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260306/default/093000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260306/default/093000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260306/default/093000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260306/default/093000_300/stream.json b/tests/fixtures/journal/chronicle/20260306/default/093000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260306/default/093000_300/stream.json rename to tests/fixtures/journal/chronicle/20260306/default/093000_300/stream.json diff --git a/tests/fixtures/journal/20260306/default/110000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260306/default/110000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260306/default/110000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260306/default/110000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260306/default/110000_300/audio.json b/tests/fixtures/journal/chronicle/20260306/default/110000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/audio.json rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/audio.json diff --git a/tests/fixtures/journal/20260306/default/110000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260306/default/110000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260306/default/110000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260306/default/110000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260306/default/110000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260306/default/110000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260306/default/110000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260306/default/110000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260306/default/110000_300/stream.json b/tests/fixtures/journal/chronicle/20260306/default/110000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260306/default/110000_300/stream.json rename to tests/fixtures/journal/chronicle/20260306/default/110000_300/stream.json diff --git a/tests/fixtures/journal/20260306/default/143000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260306/default/143000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260306/default/143000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260306/default/143000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260306/default/143000_300/audio.json b/tests/fixtures/journal/chronicle/20260306/default/143000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/audio.json rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/audio.json diff --git a/tests/fixtures/journal/20260306/default/143000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260306/default/143000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260306/default/143000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260306/default/143000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260306/default/143000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260306/default/143000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260306/default/143000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260306/default/143000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260306/default/143000_300/stream.json b/tests/fixtures/journal/chronicle/20260306/default/143000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260306/default/143000_300/stream.json rename to tests/fixtures/journal/chronicle/20260306/default/143000_300/stream.json diff --git a/tests/fixtures/journal/20260306/default/170000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260306/default/170000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260306/default/170000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260306/default/170000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260306/default/170000_300/audio.json b/tests/fixtures/journal/chronicle/20260306/default/170000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/audio.json rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/audio.json diff --git a/tests/fixtures/journal/20260306/default/170000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260306/default/170000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260306/default/170000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260306/default/170000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260306/default/170000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260306/default/170000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260306/default/170000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260306/default/170000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260306/default/170000_300/stream.json b/tests/fixtures/journal/chronicle/20260306/default/170000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260306/default/170000_300/stream.json rename to tests/fixtures/journal/chronicle/20260306/default/170000_300/stream.json diff --git a/tests/fixtures/journal/20260306/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20260306/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20260306/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20260306/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20260307/agents/flow.md b/tests/fixtures/journal/chronicle/20260307/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20260307/agents/flow.md rename to tests/fixtures/journal/chronicle/20260307/agents/flow.md diff --git a/tests/fixtures/journal/20260307/agents/meetings.md b/tests/fixtures/journal/chronicle/20260307/agents/meetings.md similarity index 100% rename from tests/fixtures/journal/20260307/agents/meetings.md rename to tests/fixtures/journal/chronicle/20260307/agents/meetings.md diff --git a/tests/fixtures/journal/20260307/default/100000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260307/default/100000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260307/default/100000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260307/default/100000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260307/default/100000_300/audio.json b/tests/fixtures/journal/chronicle/20260307/default/100000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260307/default/100000_300/audio.json rename to tests/fixtures/journal/chronicle/20260307/default/100000_300/audio.json diff --git a/tests/fixtures/journal/20260307/default/100000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260307/default/100000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260307/default/100000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260307/default/100000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260307/default/100000_300/stream.json b/tests/fixtures/journal/chronicle/20260307/default/100000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260307/default/100000_300/stream.json rename to tests/fixtures/journal/chronicle/20260307/default/100000_300/stream.json diff --git a/tests/fixtures/journal/20260307/default/150000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260307/default/150000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260307/default/150000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260307/default/150000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260307/default/150000_300/audio.json b/tests/fixtures/journal/chronicle/20260307/default/150000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260307/default/150000_300/audio.json rename to tests/fixtures/journal/chronicle/20260307/default/150000_300/audio.json diff --git a/tests/fixtures/journal/20260307/default/150000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260307/default/150000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260307/default/150000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260307/default/150000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260307/default/150000_300/stream.json b/tests/fixtures/journal/chronicle/20260307/default/150000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260307/default/150000_300/stream.json rename to tests/fixtures/journal/chronicle/20260307/default/150000_300/stream.json diff --git a/tests/fixtures/journal/20260307/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20260307/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20260307/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20260307/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20260308/agents/flow.md b/tests/fixtures/journal/chronicle/20260308/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20260308/agents/flow.md rename to tests/fixtures/journal/chronicle/20260308/agents/flow.md diff --git a/tests/fixtures/journal/20260308/agents/knowledge_graph.md b/tests/fixtures/journal/chronicle/20260308/agents/knowledge_graph.md similarity index 100% rename from tests/fixtures/journal/20260308/agents/knowledge_graph.md rename to tests/fixtures/journal/chronicle/20260308/agents/knowledge_graph.md diff --git a/tests/fixtures/journal/20260308/agents/meetings.md b/tests/fixtures/journal/chronicle/20260308/agents/meetings.md similarity index 100% rename from tests/fixtures/journal/20260308/agents/meetings.md rename to tests/fixtures/journal/chronicle/20260308/agents/meetings.md diff --git a/tests/fixtures/journal/20260308/default/100000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260308/default/100000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260308/default/100000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260308/default/100000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260308/default/100000_300/audio.json b/tests/fixtures/journal/chronicle/20260308/default/100000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260308/default/100000_300/audio.json rename to tests/fixtures/journal/chronicle/20260308/default/100000_300/audio.json diff --git a/tests/fixtures/journal/20260308/default/100000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260308/default/100000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260308/default/100000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260308/default/100000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260308/default/100000_300/stream.json b/tests/fixtures/journal/chronicle/20260308/default/100000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260308/default/100000_300/stream.json rename to tests/fixtures/journal/chronicle/20260308/default/100000_300/stream.json diff --git a/tests/fixtures/journal/20260308/default/153000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260308/default/153000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260308/default/153000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260308/default/153000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260308/default/153000_300/audio.json b/tests/fixtures/journal/chronicle/20260308/default/153000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260308/default/153000_300/audio.json rename to tests/fixtures/journal/chronicle/20260308/default/153000_300/audio.json diff --git a/tests/fixtures/journal/20260308/default/153000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260308/default/153000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260308/default/153000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260308/default/153000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260308/default/153000_300/stream.json b/tests/fixtures/journal/chronicle/20260308/default/153000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260308/default/153000_300/stream.json rename to tests/fixtures/journal/chronicle/20260308/default/153000_300/stream.json diff --git a/tests/fixtures/journal/20260308/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20260308/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20260308/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20260308/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20260309/agents/flow.md b/tests/fixtures/journal/chronicle/20260309/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20260309/agents/flow.md rename to tests/fixtures/journal/chronicle/20260309/agents/flow.md diff --git a/tests/fixtures/journal/20260309/default/090000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260309/default/090000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260309/default/090000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260309/default/090000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260309/default/090000_300/audio.json b/tests/fixtures/journal/chronicle/20260309/default/090000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/audio.json rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/audio.json diff --git a/tests/fixtures/journal/20260309/default/090000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260309/default/090000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260309/default/090000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260309/default/090000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260309/default/090000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260309/default/090000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260309/default/090000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260309/default/090000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260309/default/090000_300/stream.json b/tests/fixtures/journal/chronicle/20260309/default/090000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260309/default/090000_300/stream.json rename to tests/fixtures/journal/chronicle/20260309/default/090000_300/stream.json diff --git a/tests/fixtures/journal/20260309/default/133000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260309/default/133000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260309/default/133000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260309/default/133000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260309/default/133000_300/audio.json b/tests/fixtures/journal/chronicle/20260309/default/133000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/audio.json rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/audio.json diff --git a/tests/fixtures/journal/20260309/default/133000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260309/default/133000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260309/default/133000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260309/default/133000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260309/default/133000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260309/default/133000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260309/default/133000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260309/default/133000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260309/default/133000_300/stream.json b/tests/fixtures/journal/chronicle/20260309/default/133000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260309/default/133000_300/stream.json rename to tests/fixtures/journal/chronicle/20260309/default/133000_300/stream.json diff --git a/tests/fixtures/journal/20260309/default/193000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260309/default/193000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260309/default/193000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260309/default/193000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260309/default/193000_300/audio.json b/tests/fixtures/journal/chronicle/20260309/default/193000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/audio.json rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/audio.json diff --git a/tests/fixtures/journal/20260309/default/193000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260309/default/193000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260309/default/193000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260309/default/193000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260309/default/193000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260309/default/193000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260309/default/193000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260309/default/193000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260309/default/193000_300/stream.json b/tests/fixtures/journal/chronicle/20260309/default/193000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260309/default/193000_300/stream.json rename to tests/fixtures/journal/chronicle/20260309/default/193000_300/stream.json diff --git a/tests/fixtures/journal/20260309/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20260309/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20260309/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20260309/indexer/transcripts.sqlite diff --git a/tests/fixtures/journal/20260310/agents/flow.md b/tests/fixtures/journal/chronicle/20260310/agents/flow.md similarity index 100% rename from tests/fixtures/journal/20260310/agents/flow.md rename to tests/fixtures/journal/chronicle/20260310/agents/flow.md diff --git a/tests/fixtures/journal/20260310/agents/knowledge_graph.md b/tests/fixtures/journal/chronicle/20260310/agents/knowledge_graph.md similarity index 100% rename from tests/fixtures/journal/20260310/agents/knowledge_graph.md rename to tests/fixtures/journal/chronicle/20260310/agents/knowledge_graph.md diff --git a/tests/fixtures/journal/20260310/agents/meetings.md b/tests/fixtures/journal/chronicle/20260310/agents/meetings.md similarity index 100% rename from tests/fixtures/journal/20260310/agents/meetings.md rename to tests/fixtures/journal/chronicle/20260310/agents/meetings.md diff --git a/tests/fixtures/journal/20260310/default/083000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260310/default/083000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260310/default/083000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260310/default/083000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260310/default/083000_300/audio.json b/tests/fixtures/journal/chronicle/20260310/default/083000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/audio.json rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/audio.json diff --git a/tests/fixtures/journal/20260310/default/083000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260310/default/083000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260310/default/083000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260310/default/083000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260310/default/083000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260310/default/083000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260310/default/083000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260310/default/083000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260310/default/083000_300/stream.json b/tests/fixtures/journal/chronicle/20260310/default/083000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260310/default/083000_300/stream.json rename to tests/fixtures/journal/chronicle/20260310/default/083000_300/stream.json diff --git a/tests/fixtures/journal/20260310/default/100000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260310/default/100000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260310/default/100000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260310/default/100000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260310/default/100000_300/audio.json b/tests/fixtures/journal/chronicle/20260310/default/100000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260310/default/100000_300/audio.json rename to tests/fixtures/journal/chronicle/20260310/default/100000_300/audio.json diff --git a/tests/fixtures/journal/20260310/default/100000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260310/default/100000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260310/default/100000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260310/default/100000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260310/default/100000_300/stream.json b/tests/fixtures/journal/chronicle/20260310/default/100000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260310/default/100000_300/stream.json rename to tests/fixtures/journal/chronicle/20260310/default/100000_300/stream.json diff --git a/tests/fixtures/journal/20260310/default/170000_300/agents/audio.md b/tests/fixtures/journal/chronicle/20260310/default/170000_300/agents/audio.md similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/agents/audio.md rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/agents/audio.md diff --git a/tests/fixtures/journal/20260310/default/170000_300/agents/screen.md b/tests/fixtures/journal/chronicle/20260310/default/170000_300/agents/screen.md similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/agents/screen.md rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/agents/screen.md diff --git a/tests/fixtures/journal/20260310/default/170000_300/audio.json b/tests/fixtures/journal/chronicle/20260310/default/170000_300/audio.json similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/audio.json rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/audio.json diff --git a/tests/fixtures/journal/20260310/default/170000_300/audio.jsonl b/tests/fixtures/journal/chronicle/20260310/default/170000_300/audio.jsonl similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/audio.jsonl rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/audio.jsonl diff --git a/tests/fixtures/journal/20260310/default/170000_300/monitor_1_diff.json b/tests/fixtures/journal/chronicle/20260310/default/170000_300/monitor_1_diff.json similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/monitor_1_diff.json rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/monitor_1_diff.json diff --git a/tests/fixtures/journal/20260310/default/170000_300/monitor_1_diff_box.json b/tests/fixtures/journal/chronicle/20260310/default/170000_300/monitor_1_diff_box.json similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/monitor_1_diff_box.json rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/monitor_1_diff_box.json diff --git a/tests/fixtures/journal/20260310/default/170000_300/screen.jsonl b/tests/fixtures/journal/chronicle/20260310/default/170000_300/screen.jsonl similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/screen.jsonl rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/screen.jsonl diff --git a/tests/fixtures/journal/20260310/default/170000_300/stream.json b/tests/fixtures/journal/chronicle/20260310/default/170000_300/stream.json similarity index 100% rename from tests/fixtures/journal/20260310/default/170000_300/stream.json rename to tests/fixtures/journal/chronicle/20260310/default/170000_300/stream.json diff --git a/tests/fixtures/journal/20260310/indexer/transcripts.sqlite b/tests/fixtures/journal/chronicle/20260310/indexer/transcripts.sqlite similarity index 100% rename from tests/fixtures/journal/20260310/indexer/transcripts.sqlite rename to tests/fixtures/journal/chronicle/20260310/indexer/transcripts.sqlite diff --git a/tests/test_activities.py b/tests/test_activities.py index 1d08b960e..601dae860 100644 --- a/tests/test_activities.py +++ b/tests/test_activities.py @@ -638,7 +638,9 @@ class TestActivityRecordIO: def _setup_segment(tmpdir, day, segment, facet, state): """Helper to create an activity_state.json file in a segment.""" - agents_dir = Path(tmpdir) / day / "default" / segment / "agents" / facet + agents_dir = ( + Path(tmpdir) / "chronicle" / day / "default" / segment / "agents" / facet + ) agents_dir.mkdir(parents=True, exist_ok=True) state_file = agents_dir / "activity_state.json" state_file.write_text(json.dumps(state)) @@ -861,7 +863,7 @@ class TestWalkActivitySegments: with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) - (Path(tmpdir) / "20260209").mkdir() + (Path(tmpdir) / "chronicle" / "20260209").mkdir(parents=True) result = _walk_activity_segments( "20260209", "work", "coding", "100000_300", "100500_300" @@ -878,8 +880,8 @@ class TestPreProcess: with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) - day_dir = Path(tmpdir) / "20260209" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260209" + day_dir.mkdir(parents=True) (day_dir / "default" / "100000_300").mkdir(parents=True) result = pre_process( @@ -1638,7 +1640,7 @@ class TestPreProcessFlush: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) # Create segment dir but no activity_state files - seg_dir = Path(tmpdir) / "20260209" / "default" / "100000_300" + seg_dir = Path(tmpdir) / "chronicle" / "20260209" / "default" / "100000_300" seg_dir.mkdir(parents=True) result = pre_process( diff --git a/tests/test_activity_state.py b/tests/test_activity_state.py index 038b4cadc..1eb56ff3d 100644 --- a/tests/test_activity_state.py +++ b/tests/test_activity_state.py @@ -54,8 +54,8 @@ class TestFindPreviousSegment: try: # Create day directory with segments - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) (day_dir / "default" / "100000_300").mkdir(parents=True) (day_dir / "default" / "110000_300").mkdir(parents=True) (day_dir / "default" / "120000_300").mkdir(parents=True) @@ -90,8 +90,8 @@ class TestFindPreviousSegment: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) (day_dir / "default" / "100000_300_audio").mkdir(parents=True) (day_dir / "default" / "110000_300").mkdir(parents=True) @@ -142,7 +142,9 @@ class TestLoadPreviousState: try: # Create state file (new flat format) - segment_dir = Path(tmpdir) / "20260130" / "default" / "100000_300" + segment_dir = ( + Path(tmpdir) / "chronicle" / "20260130" / "default" / "100000_300" + ) segment_dir.mkdir(parents=True) (segment_dir / "agents" / "work").mkdir(parents=True) @@ -178,7 +180,9 @@ class TestLoadPreviousState: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - segment_dir = Path(tmpdir) / "20260130" / "default" / "100000_300" + segment_dir = ( + Path(tmpdir) / "chronicle" / "20260130" / "default" / "100000_300" + ) segment_dir.mkdir(parents=True) (segment_dir / "agents" / "work").mkdir(parents=True) @@ -200,7 +204,9 @@ class TestLoadPreviousState: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - segment_dir = Path(tmpdir) / "20260130" / "default" / "100000_300" + segment_dir = ( + Path(tmpdir) / "chronicle" / "20260130" / "default" / "100000_300" + ) segment_dir.mkdir(parents=True) (segment_dir / "agents" / "work").mkdir(parents=True) @@ -355,8 +361,8 @@ class TestPreProcess: try: # Create day and segments - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) (day_dir / "default" / "100000_300").mkdir(parents=True) (day_dir / "default" / "100000_300" / "agents" / "work").mkdir( parents=True @@ -475,8 +481,8 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) # Previous segment with active meeting prev_dir = day_dir / "default" / "100000_300" @@ -533,8 +539,8 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) # Previous segment with active meeting prev_dir = day_dir / "default" / "100000_300" @@ -657,8 +663,8 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) # Previous segment — email already ended prev_dir = day_dir / "default" / "100000_300" @@ -713,8 +719,8 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) # Previous segment — email ended with different description prev_dir = day_dir / "default" / "100000_300" @@ -795,8 +801,8 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) prev_dir = day_dir / "default" / "100000_300" prev_dir.mkdir(parents=True) @@ -916,8 +922,8 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) prev_dir = day_dir / "default" / "100000_300" prev_dir.mkdir(parents=True) @@ -973,8 +979,8 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) prev_dir = day_dir / "default" / "100000_300" prev_dir.mkdir(parents=True) @@ -1058,8 +1064,8 @@ class TestActivityId: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) prev_dir = day_dir / "default" / "100000_300" prev_dir.mkdir(parents=True) @@ -1113,8 +1119,8 @@ class TestActivityId: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) prev_dir = day_dir / "default" / "100000_300" prev_dir.mkdir(parents=True) @@ -1233,8 +1239,8 @@ class TestActivityLiveEvents: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) prev_dir = day_dir / "default" / "100000_300" prev_dir.mkdir(parents=True) @@ -1295,8 +1301,8 @@ class TestActivityLiveEvents: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir try: - day_dir = Path(tmpdir) / "20260130" - day_dir.mkdir() + day_dir = Path(tmpdir) / "chronicle" / "20260130" + day_dir.mkdir(parents=True) prev_dir = day_dir / "default" / "100000_300" prev_dir.mkdir(parents=True) diff --git a/tests/test_app_sol.py b/tests/test_app_sol.py index 4d40c3b62..c4f991ef0 100644 --- a/tests/test_app_sol.py +++ b/tests/test_app_sol.py @@ -256,8 +256,8 @@ def agents_client(tmp_path): state.journal_root = str(tmp_path) # Create test files - day_dir = tmp_path / "20260214" - day_dir.mkdir() + day_dir = tmp_path / "chronicle" / "20260214" + day_dir.mkdir(parents=True) (day_dir / "agents" / "flow.md").parent.mkdir(parents=True) (day_dir / "agents" / "flow.md").write_text("# Day agent output") diff --git a/tests/test_call.py b/tests/test_call.py index 2930a638f..82c949e50 100644 --- a/tests/test_call.py +++ b/tests/test_call.py @@ -10,9 +10,9 @@ import pytest import typer from typer.testing import CliRunner +from tests.conftest import copytree_tracked from think.call import call_app from think.utils import resolve_sol_day, resolve_sol_facet, resolve_sol_segment -from tests.conftest import copytree_tracked runner = CliRunner() diff --git a/tests/test_cluster_full.py b/tests/test_cluster_full.py index 717b31e54..65907219f 100644 --- a/tests/test_cluster_full.py +++ b/tests/test_cluster_full.py @@ -5,8 +5,8 @@ import importlib import os from pathlib import Path -from think.utils import day_path from tests.conftest import copytree_tracked +from think.utils import day_path FIXTURES = Path("tests/fixtures") @@ -14,7 +14,7 @@ FIXTURES = Path("tests/fixtures") def copy_day(tmp_path: Path) -> Path: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) dest = day_path("20240101") - src = FIXTURES / "journal" / "20240101" + src = FIXTURES / "journal" / "chronicle" / "20240101" copytree_tracked(src, dest) return dest diff --git a/tests/test_content_api.py b/tests/test_content_api.py index 127582242..a6d17841d 100644 --- a/tests/test_content_api.py +++ b/tests/test_content_api.py @@ -92,7 +92,7 @@ def test_content_detail_404_for_missing_item(content_client): def test_content_lazy_backfill(tmp_path): journal_root = tmp_path import_dir = journal_root / "imports" / "20260101_120000" - seg_dir = journal_root / "20260101" / "import.chatgpt" / "120000_300" + seg_dir = journal_root / "chronicle" / "20260101" / "import.chatgpt" / "120000_300" import_dir.mkdir(parents=True) seg_dir.mkdir(parents=True) diff --git a/tests/test_content_manifest.py b/tests/test_content_manifest.py index 527969166..a92f9cb16 100644 --- a/tests/test_content_manifest.py +++ b/tests/test_content_manifest.py @@ -51,7 +51,7 @@ def test_map_items_to_segments(): def test_generate_content_manifest_from_segments(tmp_path): journal_root = tmp_path import_dir = journal_root / "imports" / "20260101_090000" - segment_dir = journal_root / "20260101" / "import.ics" / "090000_300" + segment_dir = journal_root / "chronicle" / "20260101" / "import.ics" / "090000_300" segment_dir.mkdir(parents=True) import_dir.mkdir(parents=True) diff --git a/tests/test_conversation.py b/tests/test_conversation.py index 3195f5ed1..104c3a76a 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -11,10 +11,11 @@ import pytest @pytest.fixture -def journal_dir(tmp_path): +def journal_dir(tmp_path, monkeypatch): """Create a temporary journal directory.""" journal = tmp_path / "journal" journal.mkdir() + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) with mock.patch("think.conversation.get_journal", return_value=str(journal)): yield journal @@ -79,6 +80,7 @@ def test_record_exchange_writes_journal_segment(journal_dir): time_key = datetime.fromtimestamp(ts / 1000).strftime("%H%M%S") md_path = ( journal_dir + / "chronicle" / day / "conversation" / f"{time_key}_1" diff --git a/tests/test_dream_activity.py b/tests/test_dream_activity.py index 439440ee5..9ff8e0839 100644 --- a/tests/test_dream_activity.py +++ b/tests/test_dream_activity.py @@ -554,11 +554,8 @@ class TestActivityPersistenceRoundTrip: sm = ActivityStateMachine() sm.update(self._sense(content_type="coding"), "090000_300", "20260304") - changes = sm.update( - self._sense(content_type="meeting"), "090500_300", "20260304" - ) + sm.update(self._sense(content_type="meeting"), "090500_300", "20260304") - ended = [c for c in changes if c.get("state") == "ended"] rec = sm.get_completed_activities()[0] # Write once @@ -732,7 +729,6 @@ class TestCreatedAtRoutesCompat: def test_created_at_passes_cutoff_filter(self, monkeypatch): """Simulate routes.py filtering — recent records pass, old records don't.""" - import time from datetime import datetime, timedelta from think.activities import append_activity_record, load_activity_records diff --git a/tests/test_dream_full.py b/tests/test_dream_full.py index 1244a3555..ffbc131a5 100644 --- a/tests/test_dream_full.py +++ b/tests/test_dream_full.py @@ -53,7 +53,7 @@ def test_segment_mode_skips_pre_post_phases(journal_copy, monkeypatch): mod = importlib.import_module("think.dream") # Create segment directory - segment_dir = journal_copy / "20240101" / "default" / "120000_300" + segment_dir = journal_copy / "chronicle" / "20240101" / "default" / "120000_300" segment_dir.mkdir(parents=True, exist_ok=True) commands_run = [] diff --git a/tests/test_dream_segment.py b/tests/test_dream_segment.py index 1c7ac502a..0e0a3f3d8 100644 --- a/tests/test_dream_segment.py +++ b/tests/test_dream_segment.py @@ -14,7 +14,7 @@ import pytest def segment_dir(tmp_path, monkeypatch): """Create a temporary journal with a segment directory.""" journal = tmp_path / "journal" - day_dir = journal / "20240115" + day_dir = journal / "chronicle" / "20240115" segment_path = day_dir / "default" / "120000_300" segment_path.mkdir(parents=True) (segment_path / "agents").mkdir(parents=True) @@ -226,7 +226,7 @@ class TestRunSegmentSense: # Verify activity state persisted even on idle path activity_state_path = ( - segment_dir.parent.parent.parent / "awareness" / "activity_state.json" + segment_dir.parents[3] / "awareness" / "activity_state.json" ) assert activity_state_path.exists() state_data = json.loads(activity_state_path.read_text()) @@ -566,7 +566,7 @@ class TestRunSegmentSense: } ] activity_state_path = ( - segment_dir.parent.parent.parent / "awareness" / "activity_state.json" + segment_dir.parents[3] / "awareness" / "activity_state.json" ) assert activity_state_path.exists() state_data = json.loads(activity_state_path.read_text()) diff --git a/tests/test_entities.py b/tests/test_entities.py index c8541f964..a70915477 100644 --- a/tests/test_entities.py +++ b/tests/test_entities.py @@ -1742,7 +1742,7 @@ def test_parse_knowledge_graph_entities(tmp_path): os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) # Create a knowledge graph file - day_dir = tmp_path / "20260108" / "agents" + day_dir = tmp_path / "chronicle" / "20260108" / "agents" day_dir.mkdir(parents=True) kg_content = """# Knowledge Graph Report @@ -1790,7 +1790,7 @@ def test_parse_knowledge_graph_entities_empty_file(tmp_path): """Test parsing returns empty list for empty KG.""" os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) - day_dir = tmp_path / "20260108" / "agents" + day_dir = tmp_path / "chronicle" / "20260108" / "agents" day_dir.mkdir(parents=True) (day_dir / "knowledge_graph.md").write_text("") diff --git a/tests/test_export.py b/tests/test_export.py index 7c007ab9a..bee534bb5 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -16,8 +16,8 @@ from observe.utils import compute_bytes_sha256 def _setup_journal(tmp_path, *, include_stream_json: bool = False): - first_dir = tmp_path / "20260413" / "laptop" / "143022_300" - second_dir = tmp_path / "20260413" / "laptop" / "150000_600" + first_dir = tmp_path / "chronicle" / "20260413" / "laptop" / "143022_300" + second_dir = tmp_path / "chronicle" / "20260413" / "laptop" / "150000_600" first_dir.mkdir(parents=True) second_dir.mkdir(parents=True) @@ -324,7 +324,7 @@ class TestExportSegments: journal = _setup_journal(tmp_path) _set_journal_override(monkeypatch, journal) - second_dir = journal / "20260413" / "laptop" / "150000_600" + second_dir = journal / "chronicle" / "20260413" / "laptop" / "150000_600" for file_path in second_dir.iterdir(): file_path.unlink() second_dir.rmdir() diff --git a/tests/test_export_integration.py b/tests/test_export_integration.py index 04042866f..d453d42e9 100644 --- a/tests/test_export_integration.py +++ b/tests/test_export_integration.py @@ -208,11 +208,12 @@ def _load_json(path: Path) -> dict: def _setup_segments( journal_root: Path, *, day: str = "20260413", include_default: bool = False ) -> list[str]: - segment_dir = journal_root / day / "laptop" / "143022_300" + chronicle_day = journal_root / "chronicle" / day + segment_dir = chronicle_day / "laptop" / "143022_300" _write_bytes(segment_dir / "audio.flac", b"audio-data") _write_bytes(segment_dir / "transcript.jsonl", b'{"text":"hello"}\n') if include_default: - default_segment_dir = journal_root / day / "180000_300" + default_segment_dir = chronicle_day / "180000_300" _write_bytes(default_segment_dir / "audio.flac", b"default-audio") return [day] @@ -317,7 +318,12 @@ def test_full_export_cycle(export_integration_env): assert config_result == ExportResult(area="config", staged=1) assert ( - env["target"] / "20260413" / "laptop" / "143022_300" / "audio.flac" + env["target"] + / "chronicle" + / "20260413" + / "laptop" + / "143022_300" + / "audio.flac" ).exists() assert (env["target"] / "entities" / "source_entity" / "entity.json").exists() assert (env["target"] / "imports" / "20260101_090000" / "import.json").exists() @@ -379,7 +385,12 @@ def test_idempotent_reexport_default_stream(export_integration_env): assert first.sent == 2 assert ( - env["target"] / "20260413" / "_default" / "180000_300" / "audio.flac" + env["target"] + / "chronicle" + / "20260413" + / "_default" + / "180000_300" + / "audio.flac" ).exists() assert second.sent == 0 assert second.skipped == 2 @@ -395,7 +406,12 @@ def test_partial_only_segments(export_integration_env): assert result.sent == 1 assert ( - env["target"] / "20260413" / "laptop" / "143022_300" / "transcript.jsonl" + env["target"] + / "chronicle" + / "20260413" + / "laptop" + / "143022_300" + / "transcript.jsonl" ).exists() @@ -612,5 +628,5 @@ def test_dry_run_full(export_integration_env): assert entity_result.sent == 1 assert facet_result.sent == 1 assert config_result.staged == 1 - assert not (env["target"] / "20260413").exists() + assert not (env["target"] / "chronicle" / "20260413").exists() assert not (env["target"] / "entities" / "source_entity" / "entity.json").exists() diff --git a/tests/test_facets.py b/tests/test_facets.py index 067bf0a9e..4ca40da2c 100644 --- a/tests/test_facets.py +++ b/tests/test_facets.py @@ -343,7 +343,7 @@ def test_facet_summaries_mixed_entities(monkeypatch): def test_get_active_facets_from_segment_facets(monkeypatch, tmp_path): """Test get_active_facets() returns facets from segment facets.json files.""" journal = tmp_path / "journal" - day_dir = journal / "20240115" + day_dir = journal / "chronicle" / "20240115" # Create segment with facets.json containing two facets (stream layout) seg1 = day_dir / "archon" / "100000_300" / "agents" @@ -379,7 +379,7 @@ def test_get_active_facets_from_segment_facets(monkeypatch, tmp_path): def test_get_active_facets_empty_segments(monkeypatch, tmp_path): """Test get_active_facets() with segments that have empty facets.json.""" journal = tmp_path / "journal" - day_dir = journal / "20240115" + day_dir = journal / "chronicle" / "20240115" # Segment with empty facets array (stream layout) seg1 = day_dir / "archon" / "100000_300" / "agents" @@ -401,7 +401,7 @@ def test_get_active_facets_empty_segments(monkeypatch, tmp_path): def test_get_active_facets_no_segments(monkeypatch, tmp_path): """Test get_active_facets() when day directory has no segments.""" journal = tmp_path / "journal" - (journal / "20240115").mkdir(parents=True) + (journal / "chronicle" / "20240115").mkdir(parents=True) monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) @@ -425,7 +425,7 @@ def test_get_active_facets_no_day_dir(monkeypatch, tmp_path): def test_get_active_facets_malformed_json(monkeypatch, tmp_path): """Test get_active_facets() skips malformed facets.json gracefully.""" journal = tmp_path / "journal" - day_dir = journal / "20240115" + day_dir = journal / "chronicle" / "20240115" # Malformed JSON segment (stream layout) seg1 = day_dir / "archon" / "100000_300" / "agents" diff --git a/tests/test_formatters.py b/tests/test_formatters.py index 84a4e39d4..79b75c847 100644 --- a/tests/test_formatters.py +++ b/tests/test_formatters.py @@ -89,7 +89,7 @@ class TestLoadJsonl: path = ( Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) - / "20240101/default/123456_300/audio.jsonl" + / "chronicle/20240101/default/123456_300/audio.jsonl" ) entries = load_jsonl(path) @@ -144,7 +144,7 @@ class TestFormatFile: path = ( Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) - / "20240102/default/234567_300/screen.jsonl" + / "chronicle/20240102/default/234567_300/screen.jsonl" ) chunks, meta = format_file(path) @@ -161,7 +161,7 @@ class TestFormatFile: path = ( Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) - / "20240101/default/123456_300/audio.jsonl" + / "chronicle/20240101/default/123456_300/audio.jsonl" ) chunks, meta = format_file(path) @@ -471,7 +471,7 @@ class TestLoadTranscriptBackwardCompat: path = ( Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) - / "20240101/default/123456_300/audio.jsonl" + / "chronicle/20240101/default/123456_300/audio.jsonl" ) metadata, entries, formatted_text = load_transcript(path) @@ -1281,7 +1281,8 @@ class TestFormatMarkdown: from think.formatters import format_file path = ( - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) / "20240101/agents/flow.md" + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) + / "chronicle/20240101/agents/flow.md" ) chunks, meta = format_file(path) @@ -1294,7 +1295,8 @@ class TestFormatMarkdown: from think.formatters import load_markdown path = ( - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) / "20240101/agents/flow.md" + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) + / "chronicle/20240101/agents/flow.md" ) text = load_markdown(path) diff --git a/tests/test_generate_full.py b/tests/test_generate_full.py index a68c909c0..814e6cd22 100644 --- a/tests/test_generate_full.py +++ b/tests/test_generate_full.py @@ -15,8 +15,8 @@ import json import os from pathlib import Path -from think.utils import day_path from tests.conftest import copytree_tracked +from think.utils import day_path FIXTURES = Path("tests/fixtures") @@ -24,7 +24,7 @@ FIXTURES = Path("tests/fixtures") def copy_day(tmp_path: Path) -> Path: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) dest = day_path("20240101") - src = FIXTURES / "journal" / "20240101" + src = FIXTURES / "journal" / "chronicle" / "20240101" copytree_tracked(src, dest) return dest @@ -170,7 +170,9 @@ def post_process(result, context): assert len(finish_events) == 1 # Read captured context - captured_path = tmp_path / "20240101" / "agents" / "context_captured.json" + captured_path = ( + tmp_path / "chronicle" / "20240101" / "agents" / "context_captured.json" + ) captured = json.loads(captured_path.read_text()) assert captured["day"] == "20240101" diff --git a/tests/test_generate_scan_day.py b/tests/test_generate_scan_day.py index 48cbfc454..5aea3d137 100644 --- a/tests/test_generate_scan_day.py +++ b/tests/test_generate_scan_day.py @@ -5,8 +5,8 @@ import importlib import os from pathlib import Path -from think.utils import day_path from tests.conftest import copytree_tracked +from think.utils import day_path FIXTURES = Path("tests/fixtures") @@ -14,7 +14,7 @@ FIXTURES = Path("tests/fixtures") def copy_day(tmp_path: Path) -> Path: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) dest = day_path("20240101") - src = FIXTURES / "journal" / "20240101" + src = FIXTURES / "journal" / "chronicle" / "20240101" copytree_tracked(src, dest) agents_dir = dest / "agents" agents_dir.mkdir(exist_ok=True) # Allow existing directory diff --git a/tests/test_importer.py b/tests/test_importer.py index 3fc8db6d3..464576950 100644 --- a/tests/test_importer.py +++ b/tests/test_importer.py @@ -269,7 +269,7 @@ def test_write_segment(tmp_path): mod = importlib.import_module("think.importers.shared") json_path = mod.write_segment( - str(tmp_path / "20240101"), + str(tmp_path / "chronicle" / "20240101"), "import.text", "120000_300", [{"start": "00:00:00", "speaker": "Alice", "text": "Hello"}], @@ -284,6 +284,7 @@ def test_write_segment(tmp_path): assert ( written == tmp_path + / "chronicle" / "20240101" / "import.text" / "120000_300" @@ -720,8 +721,8 @@ def test_prepare_audio_segments(tmp_path, monkeypatch): audio_file = tmp_path / "test.mp3" audio_file.write_bytes(b"fake audio content") - day_dir = tmp_path / "20240101" - day_dir.mkdir() + day_dir = tmp_path / "chronicle" / "20240101" + day_dir.mkdir(parents=True) base_dt = dt.datetime(2024, 1, 1, 12, 0, 0) @@ -773,8 +774,8 @@ def test_prepare_audio_segments_with_collision(tmp_path, monkeypatch): audio_file = tmp_path / "test.mp3" audio_file.write_bytes(b"fake audio content") - day_dir = tmp_path / "20240101" - day_dir.mkdir() + day_dir = tmp_path / "chronicle" / "20240101" + day_dir.mkdir(parents=True) base_dt = dt.datetime(2024, 1, 1, 12, 0, 0) @@ -840,7 +841,7 @@ def test_importer_dry_run_text(tmp_path, monkeypatch, capsys): assert "2 lines" in captured.out assert not (tmp_path / "imports").exists() - assert not (tmp_path / "20240101").exists() + assert not (tmp_path / "chronicle" / "20240101").exists() def test_importer_dry_run_audio(tmp_path, monkeypatch, capsys): @@ -879,7 +880,7 @@ def test_importer_dry_run_audio(tmp_path, monkeypatch, capsys): assert "120500_300" in captured.out assert not (tmp_path / "imports").exists() - assert not (tmp_path / "20240101").exists() + assert not (tmp_path / "chronicle" / "20240101").exists() assert callosum_cls.call_count == 0 @@ -909,7 +910,7 @@ def test_importer_dry_run_auto(tmp_path, monkeypatch, capsys): assert "Content:" in captured.out assert not (tmp_path / "imports").exists() - assert not (tmp_path / "20240315").exists() + assert not (tmp_path / "chronicle" / "20240315").exists() def test_file_importer_without_timestamp(tmp_path, monkeypatch, capsys): diff --git a/tests/test_importer_documents.py b/tests/test_importer_documents.py index df4149571..33b22c588 100644 --- a/tests/test_importer_documents.py +++ b/tests/test_importer_documents.py @@ -75,7 +75,7 @@ def test_process_text_pdf(tmp_path, monkeypatch): pdf.write_bytes(b"%PDF-1.4") monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) monkeypatch.setattr(mod, "PdfReader", MockPdfReader) - monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) + monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) monkeypatch.setattr( mod, "write_content_manifest", @@ -87,7 +87,7 @@ def test_process_text_pdf(tmp_path, monkeypatch): pdf, tmp_path, facet="work", import_id="20260115_120000" ) - seg_dir = tmp_path / "20260115" / "import.document" / "120000_0" + seg_dir = tmp_path / "chronicle" / "20260115" / "import.document" / "120000_0" md_path = seg_dir / "document_transcript.md" assert result.entries_written == 1 @@ -107,7 +107,7 @@ def test_process_creates_original_pdf(tmp_path, monkeypatch): pdf.write_bytes(b"%PDF-1.4 fake") monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) monkeypatch.setattr(mod, "PdfReader", MockPdfReader) - monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) + monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) monkeypatch.setattr( mod, "write_content_manifest", @@ -117,7 +117,14 @@ def test_process_creates_original_pdf(tmp_path, monkeypatch): result = mod.importer.process(pdf, tmp_path, import_id="20260115_120000") - copied = tmp_path / "20260115" / "import.document" / "120000_0" / "original.pdf" + copied = ( + tmp_path + / "chronicle" + / "20260115" + / "import.document" + / "120000_0" + / "original.pdf" + ) assert copied.exists() assert copied.read_bytes() == b"%PDF-1.4 fake" assert str(copied) not in result.files_created @@ -136,7 +143,7 @@ def test_process_scanned_detection(tmp_path, monkeypatch): pdf.write_bytes(b"%PDF-1.4") monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) monkeypatch.setattr(mod, "PdfReader", ScannedReader) - monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) + monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) monkeypatch.setattr( mod, "write_content_manifest", @@ -157,6 +164,7 @@ def test_process_scanned_detection(tmp_path, monkeypatch): assert result.errors == [] md_path = ( tmp_path + / "chronicle" / "20260115" / "import.document" / "120000_0" @@ -178,7 +186,7 @@ def test_process_scanned_ocr_fallback(tmp_path, monkeypatch): pdf.write_bytes(b"%PDF-1.4") monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) monkeypatch.setattr(mod, "PdfReader", ScannedReader) - monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) + monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) monkeypatch.setattr( mod, "write_content_manifest", @@ -198,6 +206,7 @@ def test_process_scanned_ocr_fallback(tmp_path, monkeypatch): md_path = ( tmp_path + / "chronicle" / "20260115" / "import.document" / "120000_0" @@ -219,7 +228,7 @@ def test_process_scanned_all_fallback(tmp_path, monkeypatch): pdf.write_bytes(b"%PDF-1.4") monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) monkeypatch.setattr(mod, "PdfReader", ScannedReader) - monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) + monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) monkeypatch.setattr( mod, "write_content_manifest", @@ -246,6 +255,7 @@ def test_process_scanned_all_fallback(tmp_path, monkeypatch): ] md_path = ( tmp_path + / "chronicle" / "20260115" / "import.document" / "120000_0" @@ -262,7 +272,7 @@ def test_process_multi_file(tmp_path, monkeypatch): pdf_b.write_bytes(b"%PDF-1.4") monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) monkeypatch.setattr(mod, "PdfReader", MockPdfReader) - monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) + monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) monkeypatch.setattr( mod, "write_content_manifest", @@ -293,7 +303,7 @@ def test_process_entity_seeding(tmp_path, monkeypatch): pdf.write_bytes(b"%PDF-1.4") monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) monkeypatch.setattr(mod, "PdfReader", EntityReader) - monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) + monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) monkeypatch.setattr( mod, "write_content_manifest", diff --git a/tests/test_importer_granola.py b/tests/test_importer_granola.py index 932731bf5..8c924338b 100644 --- a/tests/test_importer_granola.py +++ b/tests/test_importer_granola.py @@ -523,7 +523,14 @@ def test_granola_sync_import(tmp_path, monkeypatch): import glob segments = glob.glob( - str(tmp_path / "*/import.granola/*/conversation_transcript.jsonl") + str( + tmp_path + / "chronicle" + / "*" + / "import.granola" + / "*" + / "conversation_transcript.jsonl" + ) ) assert len(segments) >= 1 @@ -545,7 +552,9 @@ def test_granola_sync_import(tmp_path, monkeypatch): assert entry["source"] == "import" # Check source.md was copied - source_files = glob.glob(str(tmp_path / "*/import.granola/*/source.md")) + source_files = glob.glob( + str(tmp_path / "chronicle" / "*" / "import.granola" / "*" / "source.md") + ) assert len(source_files) == 1 # only in first segment diff --git a/tests/test_importer_obsidian_sync.py b/tests/test_importer_obsidian_sync.py index 708bdbd22..383e7679d 100644 --- a/tests/test_importer_obsidian_sync.py +++ b/tests/test_importer_obsidian_sync.py @@ -105,7 +105,16 @@ def test_obsidian_sync_import(tmp_path, monkeypatch): assert result["downloaded"] >= 1 assert result["imported"] >= 1 - segments = glob.glob(str(tmp_path / "*/import.obsidian/*/note_transcript.md")) + segments = glob.glob( + str( + tmp_path + / "chronicle" + / "*" + / "import.obsidian" + / "*" + / "note_transcript.md" + ) + ) assert len(segments) >= 1 state = load_sync_state(tmp_path, "obsidian") @@ -126,7 +135,16 @@ def test_obsidian_sync_edit_creates_new_segments(tmp_path, monkeypatch): first = backend.sync(tmp_path, source_path=vault, dry_run=False) assert first["downloaded"] == 1 first_segments = sorted( - glob.glob(str(tmp_path / "*/import.obsidian/*/note_transcript.md")) + glob.glob( + str( + tmp_path + / "chronicle" + / "*" + / "import.obsidian" + / "*" + / "note_transcript.md" + ) + ) ) assert len(first_segments) == 1 @@ -135,7 +153,16 @@ def test_obsidian_sync_edit_creates_new_segments(tmp_path, monkeypatch): assert second["downloaded"] == 1 all_segments = sorted( - glob.glob(str(tmp_path / "*/import.obsidian/*/note_transcript.md")) + glob.glob( + str( + tmp_path + / "chronicle" + / "*" + / "import.obsidian" + / "*" + / "note_transcript.md" + ) + ) ) assert len(all_segments) == 2 assert first_segments[0] in all_segments diff --git a/tests/test_journal_index.py b/tests/test_journal_index.py index fda824983..7096cd453 100644 --- a/tests/test_journal_index.py +++ b/tests/test_journal_index.py @@ -10,13 +10,13 @@ from pathlib import Path import pytest +from tests.conftest import copytree_tracked from think.indexer import sanitize_fts_query from think.indexer.journal import ( extract_temporal_references, get_journal_index, search_journal, ) -from tests.conftest import copytree_tracked class TestSanitizeFtsQuery: @@ -279,8 +279,8 @@ def journal_fixture(tmp_path): os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) # Create daily insight - day = journal / "20240101" - day.mkdir() + day = journal / "chronicle" / "20240101" + day.mkdir(parents=True) agents_dir = day / "agents" agents_dir.mkdir() (agents_dir / "flow.md").write_text("# Flow Summary\n\nWorked on project alpha.\n") @@ -908,7 +908,7 @@ def test_light_scan_removes_deleted_today_segment(tmp_path): # Create content for today (which is in light scan scope) today = datetime.now().strftime("%Y%m%d") day_dir = journal / today - day_dir.mkdir() + day_dir.mkdir(parents=True) agents_dir = day_dir / "agents" agents_dir.mkdir() output_file = agents_dir / "flow.md" @@ -941,8 +941,8 @@ def test_light_scan_preserves_historical_content(tmp_path): os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) # Create historical day content - day_dir = journal / "20200101" - day_dir.mkdir() + day_dir = journal / "chronicle" / "20200101" + day_dir.mkdir(parents=True) agents_dir = day_dir / "agents" agents_dir.mkdir() output_file = agents_dir / "flow.md" @@ -976,8 +976,8 @@ def test_full_scan_removes_historical_content(tmp_path): os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) # Create historical day content - day_dir = journal / "20200101" - day_dir.mkdir() + day_dir = journal / "chronicle" / "20200101" + day_dir.mkdir(parents=True) agents_dir = day_dir / "agents" agents_dir.mkdir() output_file = agents_dir / "flow.md" @@ -1019,7 +1019,7 @@ def test_index_file_absolute_path(journal_fixture): """Test indexing with absolute path.""" from think.indexer.journal import index_file, search_journal - abs_path = str(journal_fixture / "20240101" / "agents" / "flow.md") + abs_path = str(journal_fixture / "chronicle" / "20240101" / "agents" / "flow.md") result = index_file(str(journal_fixture), abs_path, verbose=True) assert result is True @@ -1028,6 +1028,30 @@ def test_index_file_absolute_path(journal_fixture): assert total >= 1 +def test_scan_journal_never_stores_chronicle_prefix(journal_fixture): + """Chronicle is an on-disk prefix only, never a stored relative path.""" + from think.indexer.journal import scan_journal + + scan_journal(str(journal_fixture), full=True) + + conn, _ = get_journal_index(str(journal_fixture)) + try: + assert ( + conn.execute( + "SELECT count(*) FROM files WHERE path LIKE 'chronicle/%'" + ).fetchone()[0] + == 0 + ) + assert ( + conn.execute( + "SELECT count(*) FROM chunks WHERE path LIKE 'chronicle/%'" + ).fetchone()[0] + == 0 + ) + finally: + conn.close() + + def test_index_file_updates_existing(journal_fixture): """Test that re-indexing a file replaces existing chunks.""" from think.indexer.journal import index_file, search_journal @@ -1072,7 +1096,7 @@ def test_index_file_no_formatter(journal_fixture): from think.indexer.journal import index_file # Create a file with no formatter (e.g., .txt) - txt_file = journal_fixture / "20240101" / "notes.txt" + txt_file = journal_fixture / "chronicle" / "20240101" / "notes.txt" txt_file.write_text("Just some text notes.\n") with pytest.raises(ValueError, match="No formatter found"): @@ -1088,7 +1112,7 @@ def test_extract_stream_segment_path(tmp_path): from think.streams import write_segment_stream # Create a segment with stream marker - seg_dir = tmp_path / "20240101" / "default" / "123456_300" + seg_dir = tmp_path / "chronicle" / "20240101" / "default" / "123456_300" seg_dir.mkdir(parents=True) write_segment_stream(seg_dir, "archon", None, None, 1) @@ -1113,7 +1137,7 @@ def test_extract_stream_missing_marker(tmp_path): """_extract_stream returns None when stream.json doesn't exist.""" from think.indexer.journal import _extract_stream - seg_dir = tmp_path / "20240101" / "default" / "123456_300" + seg_dir = tmp_path / "chronicle" / "20240101" / "default" / "123456_300" seg_dir.mkdir(parents=True) result = _extract_stream( @@ -1443,7 +1467,7 @@ def test_scan_signals_deletion(tmp_path): conn.close() assert initial == 45 - kg_file = dst / "20240101" / "agents" / "knowledge_graph.md" + kg_file = dst / "chronicle" / "20240101" / "agents" / "knowledge_graph.md" kg_file.unlink() scan_journal(j, full=True) diff --git a/tests/test_journal_merge.py b/tests/test_journal_merge.py index f3016f00e..567cb3b29 100644 --- a/tests/test_journal_merge.py +++ b/tests/test_journal_merge.py @@ -76,8 +76,8 @@ def merge_journals_fixture(tmp_path, monkeypatch): encoding="utf-8", ) - (target / "20260101" / "120000_60").mkdir(parents=True) - (target / "20260101" / "120000_60" / "audio.jsonl").write_text( + (target / "chronicle" / "20260101" / "120000_60").mkdir(parents=True) + (target / "chronicle" / "20260101" / "120000_60" / "audio.jsonl").write_text( '{"audio": "target-existing-segment"}\n', encoding="utf-8", ) @@ -138,7 +138,9 @@ def test_segment_copy(merge_journals_fixture, monkeypatch): result = runner.invoke(call_app, ["journal", "merge", str(paths["source"])]) assert result.exit_code == 0 - assert (paths["target"] / "20260101" / "143022_300" / "audio.jsonl").exists() + assert ( + paths["target"] / "chronicle" / "20260101" / "143022_300" / "audio.jsonl" + ).exists() def test_segment_skip(merge_journals_fixture, monkeypatch): @@ -148,9 +150,9 @@ def test_segment_skip(merge_journals_fixture, monkeypatch): result = runner.invoke(call_app, ["journal", "merge", str(paths["source"])]) assert result.exit_code == 0 - assert (paths["target"] / "20260101" / "120000_60" / "audio.jsonl").read_text( - encoding="utf-8" - ) == '{"audio": "target-existing-segment"}\n' + assert ( + paths["target"] / "chronicle" / "20260101" / "120000_60" / "audio.jsonl" + ).read_text(encoding="utf-8") == '{"audio": "target-existing-segment"}\n' def test_entity_create(merge_journals_fixture, monkeypatch): @@ -576,7 +578,7 @@ def test_dry_run(merge_journals_fixture): assert result.exit_code == 0 assert "Would merge:" in result.output - assert not (paths["target"] / "20260101" / "143022_300").exists() + assert not (paths["target"] / "chronicle" / "20260101" / "143022_300").exists() assert not (paths["target"] / "entities" / "bob_smith").exists() assert not (paths["target"] / "facets" / "work").exists() assert not (paths["target"] / "imports" / "20260101_120000").exists() @@ -632,7 +634,9 @@ def test_error_resilience(merge_journals_fixture, monkeypatch): result = runner.invoke(call_app, ["journal", "merge", str(paths["source"])]) assert result.exit_code == 0 - assert (paths["target"] / "20260101" / "143022_300" / "audio.jsonl").exists() + assert ( + paths["target"] / "chronicle" / "20260101" / "143022_300" / "audio.jsonl" + ).exists() assert (paths["target"] / "entities" / "bob_smith" / "entity.json").exists() assert "1 errors:" in result.output diff --git a/tests/test_journal_stats.py b/tests/test_journal_stats.py index f583dc85f..ba5d1c129 100644 --- a/tests/test_journal_stats.py +++ b/tests/test_journal_stats.py @@ -8,8 +8,8 @@ import json def test_scan_day(tmp_path, monkeypatch): stats_mod = importlib.import_module("think.journal_stats") journal = tmp_path - day = journal / "20240101" - day.mkdir() + day = journal / "chronicle" / "20240101" + day.mkdir(parents=True) # Create an audio jsonl file in segment directory (already processed) ts_dir = day / "default" / "123456_300" @@ -68,10 +68,10 @@ def test_scan_day(tmp_path, monkeypatch): def test_token_usage(tmp_path, monkeypatch): stats_mod = importlib.import_module("think.journal_stats") journal = tmp_path - day1 = journal / "20240101" - day1.mkdir() - day2 = journal / "20240102" - day2.mkdir() + day1 = journal / "chronicle" / "20240101" + day1.mkdir(parents=True) + day2 = journal / "chronicle" / "20240102" + day2.mkdir(parents=True) # Create tokens directory with test token files tokens_dir = journal / "tokens" @@ -185,8 +185,8 @@ def test_caching(tmp_path, monkeypatch): """Test that per-day caching works correctly.""" stats_mod = importlib.import_module("think.journal_stats") journal = tmp_path - day = journal / "20240101" - day.mkdir() + day = journal / "chronicle" / "20240101" + day.mkdir(parents=True) # Create an audio jsonl file in segment directory ts_dir = day / "default" / "123456_300" @@ -227,8 +227,8 @@ def test_facet_event_mtime_invalidates_cache(tmp_path, monkeypatch): """Modifying a facet event file invalidates that day's cache.""" stats_mod = importlib.import_module("think.journal_stats") journal = tmp_path - day = journal / "20240101" - day.mkdir() + day = journal / "chronicle" / "20240101" + day.mkdir(parents=True) # Create minimal day content ts_dir = day / "default" / "123456_300" @@ -287,8 +287,8 @@ def test_token_usage_new_format(tmp_path, monkeypatch): """Test that the new unified token format is properly handled.""" stats_mod = importlib.import_module("think.journal_stats") journal = tmp_path - day1 = journal / "20240101" - day1.mkdir() + day1 = journal / "chronicle" / "20240101" + day1.mkdir(parents=True) # Create tokens directory with new format token files tokens_dir = journal / "tokens" @@ -336,8 +336,8 @@ def test_process_token_entry_counts_all_int_usage_fields(tmp_path, monkeypatch): """Int-valued fields in usage are all counted; top-level metadata is ignored.""" stats_mod = importlib.import_module("think.journal_stats") journal = tmp_path - day1 = journal / "20240101" - day1.mkdir() + day1 = journal / "chronicle" / "20240101" + day1.mkdir(parents=True) tokens_dir = journal / "tokens" tokens_dir.mkdir() diff --git a/tests/test_matching.py b/tests/test_matching.py index 9d344e84e..6ae2aca8c 100644 --- a/tests/test_matching.py +++ b/tests/test_matching.py @@ -4,7 +4,6 @@ """Tests for entity matching and name variant resolution.""" from think.entities.matching import ( - MatchResult, MatchTier, build_name_resolution_map, find_matching_entity, diff --git a/tests/test_output_hooks.py b/tests/test_output_hooks.py index e768144aa..20778b7ee 100644 --- a/tests/test_output_hooks.py +++ b/tests/test_output_hooks.py @@ -15,10 +15,10 @@ import json import os from pathlib import Path +from tests.conftest import copytree_tracked from think.agents import _apply_template_vars from think.talent import load_post_hook, load_pre_hook from think.utils import day_path -from tests.conftest import copytree_tracked FIXTURES = Path("tests/fixtures") @@ -26,7 +26,7 @@ FIXTURES = Path("tests/fixtures") def copy_day(tmp_path: Path) -> Path: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) dest = day_path("20240101") - src = FIXTURES / "journal" / "20240101" + src = FIXTURES / "journal" / "chronicle" / "20240101" copytree_tracked(src, dest) return dest diff --git a/tests/test_pipeline_health.py b/tests/test_pipeline_health.py index 21cf1b430..b6d0a6fd6 100644 --- a/tests/test_pipeline_health.py +++ b/tests/test_pipeline_health.py @@ -53,7 +53,7 @@ def test_empty_day_is_healthy(pipeline_journal): def test_missing_health_dir(pipeline_journal): - (pipeline_journal / "20260101").mkdir() + (pipeline_journal / "chronicle" / "20260101").mkdir(parents=True) summary = summarize_pipeline_day("20260101") @@ -64,7 +64,7 @@ def test_missing_health_dir(pipeline_journal): def test_healthy_day_with_all_modes(pipeline_journal): day = "20990101" - base = pipeline_journal / day / "health" + base = pipeline_journal / "chronicle" / day / "health" _write_jsonl( base / "1_segment_dream.jsonl", [ @@ -107,7 +107,7 @@ def test_healthy_day_with_all_modes(pipeline_journal): def test_agent_failure_promotes_warning(pipeline_journal): day = "20990102" _write_jsonl( - pipeline_journal / day / "health" / "1_segment_dream.jsonl", + pipeline_journal / "chronicle" / day / "health" / "1_segment_dream.jsonl", [ { "event": "agent.fail", @@ -149,7 +149,9 @@ def test_failed_list_truncates_at_20(pipeline_journal): } for idx in range(25) ] - _write_jsonl(pipeline_journal / day / "health" / "1_daily_dream.jsonl", events) + _write_jsonl( + pipeline_journal / "chronicle" / day / "health" / "1_daily_dream.jsonl", events + ) summary = summarize_pipeline_day(day) @@ -162,7 +164,7 @@ def test_failed_list_truncates_at_20(pipeline_journal): def test_activity_detected_without_run_is_stale(pipeline_journal): day = "20990104" _write_jsonl( - pipeline_journal / day / "health" / "1_segment_dream.jsonl", + pipeline_journal / "chronicle" / day / "health" / "1_segment_dream.jsonl", [{"event": "activity.detected", "mode": "segment"}], ) @@ -175,7 +177,7 @@ def test_activity_detected_without_run_is_stale(pipeline_journal): def test_past_day_without_daily_run_is_stale(pipeline_journal, monkeypatch): day = "20200101" _write_jsonl( - pipeline_journal / day / "health" / "1_segment_dream.jsonl", + pipeline_journal / "chronicle" / day / "health" / "1_segment_dream.jsonl", [{"event": "run.start", "mode": "segment"}], ) monkeypatch.setattr( @@ -191,7 +193,9 @@ def test_past_day_without_daily_run_is_stale(pipeline_journal, monkeypatch): def test_today_before_23h_no_daily_run_is_healthy(pipeline_journal, monkeypatch): current = datetime(2026, 4, 16, 12, 0, 0) monkeypatch.setattr("think.pipeline_health._now", lambda: current) - (pipeline_journal / current.strftime("%Y%m%d") / "health").mkdir(parents=True) + (pipeline_journal / "chronicle" / current.strftime("%Y%m%d") / "health").mkdir( + parents=True + ) summary = summarize_pipeline_day(current.strftime("%Y%m%d")) @@ -202,7 +206,9 @@ def test_today_before_23h_no_daily_run_is_healthy(pipeline_journal, monkeypatch) def test_today_after_23h_no_daily_run_is_stale(pipeline_journal, monkeypatch): current = datetime(2026, 4, 16, 23, 30, 0) monkeypatch.setattr("think.pipeline_health._now", lambda: current) - (pipeline_journal / current.strftime("%Y%m%d") / "health").mkdir(parents=True) + (pipeline_journal / "chronicle" / current.strftime("%Y%m%d") / "health").mkdir( + parents=True + ) summary = summarize_pipeline_day(current.strftime("%Y%m%d")) @@ -212,7 +218,7 @@ def test_today_after_23h_no_daily_run_is_stale(pipeline_journal, monkeypatch): def test_segment_runs_missing_is_soft(pipeline_journal, monkeypatch): day = "20990105" - (pipeline_journal / day / "health").mkdir(parents=True) + (pipeline_journal / "chronicle" / day / "health").mkdir(parents=True) monkeypatch.setattr( "think.pipeline_health.iter_segments", lambda value: [("default", "120000_300", Path("/tmp/fake"))], @@ -241,7 +247,7 @@ def test_invalid_day_returns_healthy_empty(pipeline_journal): def test_malformed_json_lines_skipped(pipeline_journal): day = "20990106" - path = pipeline_journal / day / "health" / "1_segment_dream.jsonl" + path = pipeline_journal / "chronicle" / day / "health" / "1_segment_dream.jsonl" path.parent.mkdir(parents=True, exist_ok=True) path.write_text( json.dumps({"event": "run.start", "mode": "segment"}) diff --git a/tests/test_pipeline_smoke.py b/tests/test_pipeline_smoke.py index 61d6ce7e4..177f88e8c 100644 --- a/tests/test_pipeline_smoke.py +++ b/tests/test_pipeline_smoke.py @@ -141,7 +141,7 @@ class TestPipelineSmokeTest: ) for segment_key, sense_dict in SEGMENTS: - seg_dir = journal / DAY / STREAM / segment_key + seg_dir = journal / "chronicle" / DAY / STREAM / segment_key (seg_dir / "agents").mkdir(parents=True, exist_ok=True) (seg_dir / "agents" / "sense.json").write_text(json.dumps(sense_dict)) @@ -161,7 +161,7 @@ class TestPipelineSmokeTest: "091500_300", "100000_300", ]: - seg_agents = journal / DAY / STREAM / seg_key / "agents" + seg_agents = journal / "chronicle" / DAY / STREAM / seg_key / "agents" assert (seg_agents / "sense.json").exists() assert (seg_agents / "activity.md").exists() assert (seg_agents / "density.json").exists() @@ -174,19 +174,37 @@ class TestPipelineSmokeTest: for seg_key in ["091000_300", "091500_300"]: speakers = json.loads( ( - journal / DAY / STREAM / seg_key / "agents" / "speakers.json" + journal + / "chronicle" + / DAY + / STREAM + / seg_key + / "agents" + / "speakers.json" ).read_text() ) assert speakers == ["Alice", "Bob"] for seg_key in ["090000_300", "090500_300", "100000_300"]: assert not ( - journal / DAY / STREAM / seg_key / "agents" / "speakers.json" + journal + / "chronicle" + / DAY + / STREAM + / seg_key + / "agents" + / "speakers.json" ).exists() idle_density = json.loads( ( - journal / DAY / STREAM / "092000_300" / "agents" / "density.json" + journal + / "chronicle" + / DAY + / STREAM + / "092000_300" + / "agents" + / "density.json" ).read_text() ) assert idle_density["classification"] == "idle" diff --git a/tests/test_retention.py b/tests/test_retention.py index 2f0ea3565..2fb7b39ba 100644 --- a/tests/test_retention.py +++ b/tests/test_retention.py @@ -281,14 +281,14 @@ class TestPurge: journal = tmp_path / "journal" # Day 1: 60 days old — two complete segments - day1 = journal / "20260115" / "default" / "100000_300" + day1 = journal / "chronicle" / "20260115" / "default" / "100000_300" day1.mkdir(parents=True) (day1 / "audio.flac").write_bytes(b"x" * 1000) (day1 / "audio.jsonl").write_text('{"raw":"audio.flac"}\n') (day1 / "stream.json").write_text('{"stream":"default"}') (day1 / "agents").mkdir() - day1b = journal / "20260115" / "plaud" / "103000_300" + day1b = journal / "chronicle" / "20260115" / "plaud" / "103000_300" day1b.mkdir(parents=True) (day1b / "audio.m4a").write_bytes(b"x" * 500) (day1b / "audio.jsonl").write_text('{"raw":"audio.m4a"}\n') @@ -296,7 +296,7 @@ class TestPurge: (day1b / "agents").mkdir() # Day 2: recent — one complete segment (must stay within 30d window) - day2 = journal / "20260401" / "default" / "120000_300" + day2 = journal / "chronicle" / "20260401" / "default" / "120000_300" day2.mkdir(parents=True) (day2 / "audio.flac").write_bytes(b"x" * 800) (day2 / "audio.jsonl").write_text('{"raw":"audio.flac"}\n') @@ -304,7 +304,7 @@ class TestPurge: (day2 / "agents").mkdir() # Day 3: incomplete segment (no audio.jsonl) - day3 = journal / "20260101" / "default" / "140000_300" + day3 = journal / "chronicle" / "20260101" / "default" / "140000_300" day3.mkdir(parents=True) (day3 / "audio.flac").write_bytes(b"x" * 600) (day3 / "stream.json").write_text('{"stream":"default"}') @@ -325,8 +325,12 @@ class TestPurge: # Should report but not delete assert result.files_deleted == 2 # day1 default + plaud assert result.bytes_freed == 1500 - assert (journal / "20260115" / "default" / "100000_300" / "audio.flac").exists() - assert (journal / "20260115" / "plaud" / "103000_300" / "audio.m4a").exists() + assert ( + journal / "chronicle" / "20260115" / "default" / "100000_300" / "audio.flac" + ).exists() + assert ( + journal / "chronicle" / "20260115" / "plaud" / "103000_300" / "audio.m4a" + ).exists() # No retention log for dry run assert not (journal / "health" / "retention.log").exists() @@ -338,14 +342,19 @@ class TestPurge: assert result.files_deleted == 2 # Files should be gone assert not ( - journal / "20260115" / "default" / "100000_300" / "audio.flac" + journal / "chronicle" / "20260115" / "default" / "100000_300" / "audio.flac" ).exists() assert not ( - journal / "20260115" / "plaud" / "103000_300" / "audio.m4a" + journal / "chronicle" / "20260115" / "plaud" / "103000_300" / "audio.m4a" ).exists() # Derived content preserved assert ( - journal / "20260115" / "default" / "100000_300" / "audio.jsonl" + journal + / "chronicle" + / "20260115" + / "default" + / "100000_300" + / "audio.jsonl" ).exists() # Retention log written assert (journal / "health" / "retention.log").exists() @@ -437,7 +446,7 @@ class TestPurgeProvenance: def test_processed_at_reflects_latest_mtime(self, tmp_path, monkeypatch): journal = self._setup_journal(tmp_path, monkeypatch) - segment = journal / "20260115" / "default" / "100000_300" + segment = journal / "chronicle" / "20260115" / "default" / "100000_300" audio_jsonl = segment / "audio.jsonl" alternate_audio_jsonl = segment / "meeting_audio.jsonl" speaker_labels = segment / "agents" / "speaker_labels.json" diff --git a/tests/test_runner.py b/tests/test_runner.py index 64b17119a..ed473a84a 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -266,14 +266,14 @@ def test_process_creates_health_log(journal_path, mock_callosum): from datetime import datetime day = datetime.now().strftime("%Y%m%d") - log_path = journal_path / day / "health" / f"{ref}_echo.log" + log_path = journal_path / "chronicle" / day / "health" / f"{ref}_echo.log" assert log_path.exists() content = log_path.read_text() assert "logged output" in content # Verify day-level symlink exists - day_symlink = journal_path / day / "health" / "echo.log" + day_symlink = journal_path / "chronicle" / day / "health" / "echo.log" assert day_symlink.is_symlink() assert day_symlink.resolve() == log_path.resolve() @@ -292,7 +292,7 @@ def test_process_day_override(journal_path, mock_callosum): managed.cleanup() # Log should be in target day, not today - log_path = journal_path / target_day / "health" / f"{ref}_echo.log" + log_path = journal_path / "chronicle" / target_day / "health" / f"{ref}_echo.log" assert log_path.exists() content = log_path.read_text() assert "day test" in content @@ -306,7 +306,7 @@ def test_process_day_override(journal_path, mock_callosum): assert not today_log.exists() # Day-level symlink in target day - day_symlink = journal_path / target_day / "health" / "echo.log" + day_symlink = journal_path / "chronicle" / target_day / "health" / "echo.log" assert day_symlink.is_symlink() assert day_symlink.resolve() == log_path.resolve() diff --git a/tests/test_segment.py b/tests/test_segment.py index 173d829a7..577bd94ec 100644 --- a/tests/test_segment.py +++ b/tests/test_segment.py @@ -23,7 +23,7 @@ def _make_segment( agents=None, ): """Create a minimal segment fixture directory.""" - seg_dir = base / day / stream / segment + seg_dir = base / "chronicle" / day / stream / segment seg_dir.mkdir(parents=True) if stream_json is not None: (seg_dir / "stream.json").write_text(json.dumps(stream_json)) @@ -560,12 +560,12 @@ def test_move_basic(tmp_path, monkeypatch, capsys): ) cmd_move(args) - assert not (tmp_path / "20240101" / "default" / "090000_300").exists() - assert (tmp_path / "20240115" / "default" / "090000_300").is_dir() + assert not (tmp_path / "chronicle" / "20240101" / "default" / "090000_300").exists() + assert (tmp_path / "chronicle" / "20240115" / "default" / "090000_300").is_dir() import think.streams marker = think.streams.read_segment_stream( - tmp_path / "20240115" / "default" / "090000_300" + tmp_path / "chronicle" / "20240115" / "default" / "090000_300" ) assert marker["stream"] == "default" assert marker["seq"] == 1 @@ -601,8 +601,8 @@ def test_move_with_to_time(tmp_path, monkeypatch, capsys): ) cmd_move(args) - assert not (tmp_path / "20240101" / "default" / "090000_300").exists() - assert (tmp_path / "20240101" / "default" / "140000_300").is_dir() + assert not (tmp_path / "chronicle" / "20240101" / "default" / "090000_300").exists() + assert (tmp_path / "chronicle" / "20240101" / "default" / "140000_300").is_dir() def test_move_dry_run(tmp_path, monkeypatch, capsys): @@ -638,7 +638,7 @@ def test_move_dry_run(tmp_path, monkeypatch, capsys): assert "[dry run]" in out assert "090000_300" in out - assert (tmp_path / "20240101" / "default" / "090000_300").is_dir() + assert (tmp_path / "chronicle" / "20240101" / "default" / "090000_300").is_dir() def test_move_collision_no_to_time(tmp_path, monkeypatch, capsys): @@ -722,7 +722,7 @@ def test_move_no_events_jsonl(tmp_path, monkeypatch, capsys): cmd_move(args) out = capsys.readouterr().out - assert (tmp_path / "20240115" / "default" / "090000_300").is_dir() + assert (tmp_path / "chronicle" / "20240115" / "default" / "090000_300").is_dir() assert "events.jsonl lines: 0" in out @@ -771,7 +771,7 @@ def test_move_patches_successor(tmp_path, monkeypatch, capsys): import think.streams succ_marker = think.streams.read_segment_stream( - tmp_path / "20240101" / "default" / "140000_300" + tmp_path / "chronicle" / "20240101" / "default" / "140000_300" ) assert succ_marker["prev_day"] == "20240115" assert succ_marker["prev_segment"] == "090000_300" @@ -821,7 +821,7 @@ def test_move_stream_tail(tmp_path, monkeypatch, capsys): out = capsys.readouterr().out assert "successor to patch: (none - stream tail)" in out - assert (tmp_path / "20240115" / "default" / "140000_300").is_dir() + assert (tmp_path / "chronicle" / "20240115" / "default" / "140000_300").is_dir() def test_move_rewrites_events_jsonl(tmp_path, monkeypatch, capsys): @@ -868,7 +868,9 @@ def test_move_rewrites_events_jsonl(tmp_path, monkeypatch, capsys): ) cmd_move(args) - new_events_path = tmp_path / "20240115" / "default" / "140000_300" / "events.jsonl" + new_events_path = ( + tmp_path / "chronicle" / "20240115" / "default" / "140000_300" / "events.jsonl" + ) assert new_events_path.exists() lines = [ json.loads(line) @@ -909,8 +911,8 @@ def test_move_touches_health_markers(tmp_path, monkeypatch, capsys): ) cmd_move(args) - assert (tmp_path / "20240101" / "health" / "stream.updated").exists() - assert (tmp_path / "20240115" / "health" / "stream.updated").exists() + assert (tmp_path / "chronicle" / "20240101" / "health" / "stream.updated").exists() + assert (tmp_path / "chronicle" / "20240115" / "health" / "stream.updated").exists() def test_move_same_location_refused(tmp_path, monkeypatch, capsys): diff --git a/tests/test_segment_ingest.py b/tests/test_segment_ingest.py index d0371d6dd..ea632e09b 100644 --- a/tests/test_segment_ingest.py +++ b/tests/test_segment_ingest.py @@ -28,6 +28,7 @@ register_ingest_routes = ingest.register_ingest_routes def journal_env(tmp_path, monkeypatch): """Set up journal root and source storage.""" monkeypatch.setattr(convey.state, "journal_root", str(tmp_path), raising=False) + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) (tmp_path / "apps" / "import" / "journal_sources").mkdir( parents=True, exist_ok=True ) @@ -156,8 +157,8 @@ def test_ingest_new_segments(ingest_env): "errors": [], } - first_dir = env["root"] / "20260413" / "laptop" / "143022_300" - second_dir = env["root"] / "20260413" / "laptop" / "143500_300" + first_dir = env["root"] / "chronicle" / "20260413" / "laptop" / "143022_300" + second_dir = env["root"] / "chronicle" / "20260413" / "laptop" / "143500_300" assert (first_dir / "audio.flac").read_bytes() == b"audio one" assert (first_dir / "transcript.jsonl").read_bytes() == b'{"text":"one"}\n' assert (second_dir / "audio.flac").read_bytes() == b"audio two" @@ -224,7 +225,7 @@ def test_ingest_duplicate_detection(ingest_env): def test_ingest_deconfliction(ingest_env, monkeypatch): env = ingest_env - target_dir = env["root"] / "20260413" / "laptop" / "143022_300" + target_dir = env["root"] / "chronicle" / "20260413" / "laptop" / "143022_300" target_dir.mkdir(parents=True, exist_ok=True) (target_dir / "audio.flac").write_bytes(b"existing audio") @@ -251,7 +252,7 @@ def test_ingest_deconfliction(ingest_env, monkeypatch): "errors": [], } assert ( - env["root"] / "20260413" / "laptop" / "143023_300" / "audio.flac" + env["root"] / "chronicle" / "20260413" / "laptop" / "143023_300" / "audio.flac" ).read_bytes() == b"new audio" state_data = _read_state(env["key_prefix"]) @@ -296,7 +297,7 @@ def test_ingest_batch_error_isolation(ingest_env): } ] assert ( - env["root"] / "20260413" / "laptop" / "143500_300" / "audio.flac" + env["root"] / "chronicle" / "20260413" / "laptop" / "143500_300" / "audio.flac" ).read_bytes() == b"good" @@ -435,7 +436,7 @@ def test_ingest_callosum_failure_isolated(ingest_env, monkeypatch): def test_ingest_skip_ignores_extra_existing_files(ingest_env): env = ingest_env - segment_dir = env["root"] / "20260413" / "laptop" / "143022_300" + segment_dir = env["root"] / "chronicle" / "20260413" / "laptop" / "143022_300" segment_dir.mkdir(parents=True, exist_ok=True) (segment_dir / "audio.flac").write_bytes(b"audio one") (segment_dir / "extra.txt").write_bytes(b"keep me") @@ -550,7 +551,12 @@ def test_ingest_default_stream_segment(ingest_env): state_data = _read_state(env["key_prefix"]) assert "_default/143022_300" in state_data["20260413"] assert ( - env["root"] / "20260413" / "_default" / "143022_300" / "transcript.jsonl" + env["root"] + / "chronicle" + / "20260413" + / "_default" + / "143022_300" + / "transcript.jsonl" ).read_bytes() == b'{"text":"default"}\n' diff --git a/tests/test_sense.py b/tests/test_sense.py index 59d1d4e10..baddd346e 100644 --- a/tests/test_sense.py +++ b/tests/test_sense.py @@ -164,14 +164,14 @@ def test_process_log_writer(tmp_path, monkeypatch): writer.close() # Log file uses {ref}_{name}.log format - log_path = tmp_path / "20241101" / "health" / f"{ref}_test.log" + log_path = tmp_path / "chronicle" / "20241101" / "health" / f"{ref}_test.log" assert log_path.exists() content = log_path.read_text() assert "line 1\n" in content assert "line 2\n" in content # Verify symlinks exist - day_symlink = tmp_path / "20241101" / "health" / "test.log" + day_symlink = tmp_path / "chronicle" / "20241101" / "health" / "test.log" assert day_symlink.is_symlink() journal_symlink = tmp_path / "health" / "test.log" assert journal_symlink.is_symlink() @@ -204,7 +204,7 @@ def test_process_log_writer_thread_safe(tmp_path, monkeypatch): writer.close() # Log file uses {ref}_{name}.log format - log_path = tmp_path / "20241101" / "health" / f"{ref}_test.log" + log_path = tmp_path / "chronicle" / "20241101" / "health" / f"{ref}_test.log" lines = log_path.read_text().split("\n") # Should have 50 lines (5 threads * 10 lines each) assert len([line for line in lines if line]) == 50 @@ -245,7 +245,7 @@ def test_file_sensor_match_pattern(): with tempfile.TemporaryDirectory() as tmpdir: # Create journal/day/stream/segment structure journal_dir = Path(tmpdir) - day_dir = journal_dir / "20250101" + day_dir = journal_dir / "chronicle" / "20250101" segment_dir = day_dir / "default" / "123456_300" segment_dir.mkdir(parents=True) @@ -286,7 +286,7 @@ def test_standalone_dry_run(tmp_path, monkeypatch): monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -316,7 +316,7 @@ def test_standalone_dry_run_with_segment_filter(tmp_path, monkeypatch): monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_1 = day_dir / "default" / "143022_300" segment_2 = day_dir / "default" / "150022_300" segment_1.mkdir(parents=True) @@ -368,7 +368,7 @@ def test_file_sensor_spawn_handler(mock_popen, mock_day, mock_journal, tmp_path) assert args == ["echo", str(test_file)] # Verify log file was created with {ref}_echo.log format - health_dir = tmp_path / "20241101" / "health" + health_dir = tmp_path / "chronicle" / "20241101" / "health" log_files = list(health_dir.glob("*_echo.log")) assert len(log_files) == 1, f"Expected 1 echo log file, found {len(log_files)}" @@ -376,8 +376,8 @@ def test_file_sensor_spawn_handler(mock_popen, mock_day, mock_journal, tmp_path) def test_file_sensor_spawn_handler_duplicate(tmp_path, mock_callosum): """Test that duplicate file processing is prevented.""" # Create journal/day structure - day_dir = tmp_path / "20250101" - day_dir.mkdir() + day_dir = tmp_path / "chronicle" / "20250101" + day_dir.mkdir(parents=True) sensor = FileSensor(tmp_path) sensor.register("*.txt", "test", ["echo", "hello"]) @@ -424,7 +424,7 @@ def test_file_sensor_spawn_handler_real_process( assert test_file not in sensor.running # Check log file contains output with {ref}_echo.log format - health_dir = tmp_path / "20241101" / "health" + health_dir = tmp_path / "chronicle" / "20241101" / "health" log_files = list(health_dir.glob("*_echo.log")) assert len(log_files) == 1, f"Expected 1 echo log file, found {len(log_files)}" @@ -486,7 +486,7 @@ def test_file_sensor_handle_file(tmp_path): """Test file handling dispatches to correct handler.""" with patch.object(FileSensor, "_spawn_handler") as mock_spawn: # Create journal/day/stream/segment structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -536,7 +536,7 @@ def test_file_sensor_handle_callosum_message(tmp_path): """Test handling of observe.observing Callosum events.""" with patch.object(FileSensor, "_handle_file") as mock_handle: # Create journal/day/stream/segment structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -618,7 +618,7 @@ def test_file_sensor_segment_observed_includes_day(tmp_path, mock_callosum): from think.callosum import CallosumConnection # Create journal/day/stream/segment structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -671,7 +671,7 @@ def test_file_sensor_segment_observed_no_handlers(tmp_path, mock_callosum): from think.callosum import CallosumConnection # Create journal/day/stream/segment structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -720,7 +720,7 @@ def test_delete_outputs_screen(tmp_path): from observe.sense import delete_outputs # Create journal/day/stream/segment structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -744,7 +744,7 @@ def test_delete_outputs_audio(tmp_path): from observe.sense import delete_outputs # Create journal/day/stream/segment structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -768,7 +768,7 @@ def test_delete_outputs_dry_run(tmp_path): from observe.sense import delete_outputs # Create journal/day/stream/segment structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "143022_300" segment_dir.mkdir(parents=True) @@ -788,7 +788,7 @@ def test_delete_outputs_segment_filter(tmp_path): from observe.sense import delete_outputs # Create journal/day/stream/segments structure - day_dir = tmp_path / "20250101" + day_dir = tmp_path / "chronicle" / "20250101" segment1 = day_dir / "default" / "143022_300" segment2 = day_dir / "default" / "150022_300" segment1.mkdir(parents=True) diff --git a/tests/test_sol.py b/tests/test_sol.py index 4296bfe32..26fae0429 100644 --- a/tests/test_sol.py +++ b/tests/test_sol.py @@ -228,7 +228,11 @@ class TestMain: # root should NOT end with /journal — that's --path assert not path.endswith("/journal") # should be a parent of the journal path - assert path.endswith("/solstone") or "/solstone" in path + assert ( + path.endswith("/solstone") + or "/solstone" in path + or path.endswith("/worktree") + ) def test_main_unknown_command_exits(self, monkeypatch): """Test that unknown command exits with code 1.""" diff --git a/tests/test_stats_contract.py b/tests/test_stats_contract.py index da27774ed..f7cebeac1 100644 --- a/tests/test_stats_contract.py +++ b/tests/test_stats_contract.py @@ -5,7 +5,6 @@ import importlib import json from pathlib import Path - CONTRACT_FIELDS = [ ("days", "stats.days"), ("totals", "stats.totals"), @@ -57,7 +56,7 @@ def _resolve_path(data, path): def _build_journal(base_path): journal = base_path - day = journal / "20240101" + day = journal / "chronicle" / "20240101" seg1 = day / "default" / "123456_300" seg2 = day / "default" / "134500_300" seg1.mkdir(parents=True) diff --git a/tests/test_stats_schema.py b/tests/test_stats_schema.py index cbbbe9353..541218213 100644 --- a/tests/test_stats_schema.py +++ b/tests/test_stats_schema.py @@ -11,8 +11,8 @@ def test_validate_passes_on_valid_output(tmp_path, monkeypatch): stats_mod = importlib.import_module("think.journal_stats") schema_mod = importlib.import_module("think.stats_schema") journal = tmp_path - day = journal / "20240101" - day.mkdir() + day = journal / "chronicle" / "20240101" + day.mkdir(parents=True) # Create minimal transcript fixture ts_dir = day / "default" / "123456_300" @@ -79,8 +79,8 @@ def test_day_fields_present_in_scan_day(tmp_path, monkeypatch): stats_mod = importlib.import_module("think.journal_stats") schema_mod = importlib.import_module("think.stats_schema") journal = tmp_path - day = journal / "20240101" - day.mkdir() + day = journal / "chronicle" / "20240101" + day.mkdir(parents=True) # Create transcript and percept fixtures ts_dir = day / "default" / "123456_300" diff --git a/tests/test_streams.py b/tests/test_streams.py index 35f632eb8..8400e698c 100644 --- a/tests/test_streams.py +++ b/tests/test_streams.py @@ -20,7 +20,7 @@ from think.streams import ( # --- stream_name tests --- -def test_stream_name_observer(): +def test_stream_name_host(): """Host only -> hostname.""" assert stream_name(host="archon") == "archon" @@ -141,7 +141,7 @@ def test_update_stream_cross_day(tmp_path, monkeypatch): def test_write_read_segment_stream(tmp_path): """Round-trip write/read stream.json.""" - seg_dir = tmp_path / "20250119" / "default" / "142500_300" + seg_dir = tmp_path / "chronicle" / "20250119" / "default" / "142500_300" seg_dir.mkdir(parents=True) write_segment_stream(seg_dir, "archon", "20250119", "142000_300", 5) @@ -156,7 +156,7 @@ def test_write_read_segment_stream(tmp_path): def test_write_segment_stream_first(tmp_path): """First segment has None prev values.""" - seg_dir = tmp_path / "20250119" / "default" / "142500_300" + seg_dir = tmp_path / "chronicle" / "20250119" / "default" / "142500_300" seg_dir.mkdir(parents=True) write_segment_stream(seg_dir, "archon", None, None, 1) @@ -169,7 +169,7 @@ def test_write_segment_stream_first(tmp_path): def test_read_segment_stream_missing(tmp_path): """Returns None for pre-stream segments.""" - seg_dir = tmp_path / "20250119" / "default" / "142500_300" + seg_dir = tmp_path / "chronicle" / "20250119" / "default" / "142500_300" seg_dir.mkdir(parents=True) assert read_segment_stream(seg_dir) is None @@ -202,7 +202,7 @@ def test_rebuild_stream_state(tmp_path, monkeypatch): monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) # Create segment dirs with stream markers under default stream - day_dir = tmp_path / "20250119" + day_dir = tmp_path / "chronicle" / "20250119" seg1 = day_dir / "default" / "142500_300" seg2 = day_dir / "default" / "143000_300" seg1.mkdir(parents=True) diff --git a/tests/test_supervisor.py b/tests/test_supervisor.py index 706d0176c..666db6288 100644 --- a/tests/test_supervisor.py +++ b/tests/test_supervisor.py @@ -132,8 +132,6 @@ def test_parse_args_remote_flag_optional(): def test_shutdown_stops_in_reverse_order(monkeypatch): """Shutdown stops services in reverse order.""" - mod = importlib.import_module("think.supervisor") - operations = [] class MockProc: diff --git a/tests/test_template_substitution.py b/tests/test_template_substitution.py index 7f65d0279..11b71f250 100644 --- a/tests/test_template_substitution.py +++ b/tests/test_template_substitution.py @@ -251,3 +251,40 @@ def test_load_prompt_empty_context(mock_journal_with_config, mock_prompt_dir): result_empty = load_prompt("plain", base_dir=mock_prompt_dir, context={}) assert result_none.text == result_empty.text + + +def test_load_prompt_sol_vars_follow_journal_override(monkeypatch, tmp_path): + """Journal sol/ content should not leak across journal overrides.""" + + def write_journal(journal_dir, awareness_text): + config_dir = journal_dir / "config" + config_dir.mkdir(parents=True) + (config_dir / "journal.json").write_text( + json.dumps( + { + "identity": {"name": "Test User"}, + "agent": {"name": "sol", "name_status": "default"}, + } + ) + ) + sol_dir = journal_dir / "sol" + sol_dir.mkdir() + (sol_dir / "awareness.md").write_text(awareness_text) + + prompt_dir = tmp_path / "prompts" + prompt_dir.mkdir() + (prompt_dir / "sol_vars.md").write_text("Awareness:\n$sol_awareness\n") + + journal_one = tmp_path / "journal-one" + journal_two = tmp_path / "journal-two" + write_journal(journal_one, "first awareness") + write_journal(journal_two, "second awareness") + + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_one)) + first = load_prompt("sol_vars", base_dir=prompt_dir) + assert "first awareness" in first.text + + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_two)) + second = load_prompt("sol_vars", base_dir=prompt_dir) + assert "second awareness" in second.text + assert "first awareness" not in second.text diff --git a/tests/test_think_utils.py b/tests/test_think_utils.py index d38213f28..7e4b563d4 100644 --- a/tests/test_think_utils.py +++ b/tests/test_think_utils.py @@ -18,8 +18,8 @@ from think.utils import ( DEFAULT_STREAM, day_from_path, iter_segments, - segment_parse, segment_key, + segment_parse, setup_cli, ) @@ -830,8 +830,8 @@ class TestPortDiscovery: class TestIterSegments: def test_skips_health_directory(self, tmp_path): """iter_segments does not return segments from health/ dirs.""" - day_dir = tmp_path / "20240101" - day_dir.mkdir() + day_dir = tmp_path / "chronicle" / "20240101" + day_dir.mkdir(parents=True) health_seg = day_dir / "health" / "120000_300" health_seg.mkdir(parents=True) normal_seg = day_dir / "default" / "130000_300" @@ -844,8 +844,8 @@ class TestIterSegments: def test_toplevel_segments_as_default_stream(self, tmp_path): """Top-level segment dirs are returned with _default stream name.""" - day_dir = tmp_path / "20240101" - day_dir.mkdir() + day_dir = tmp_path / "chronicle" / "20240101" + day_dir.mkdir(parents=True) toplevel_seg = day_dir / "143022_300" toplevel_seg.mkdir() normal_seg = day_dir / "default" / "150000_300" @@ -861,8 +861,8 @@ class TestIterSegments: def test_normal_stream_discovery_unchanged(self, tmp_path): """Normal stream/segment discovery still works correctly.""" - day_dir = tmp_path / "20240101" - day_dir.mkdir() + day_dir = tmp_path / "chronicle" / "20240101" + day_dir.mkdir(parents=True) (day_dir / "default" / "100000_300").mkdir(parents=True) (day_dir / "default" / "110000_300").mkdir(parents=True) (day_dir / "import.apple" / "120000_600").mkdir(parents=True) diff --git a/tests/test_transcribe_cli.py b/tests/test_transcribe_cli.py index da367f4a2..ca425585e 100644 --- a/tests/test_transcribe_cli.py +++ b/tests/test_transcribe_cli.py @@ -12,7 +12,7 @@ import pytest def test_main_accepts_journal_relative_path(tmp_path, monkeypatch): """main() resolves audio_path relative to journal when absolute path fails.""" - seg_dir = tmp_path / "20260201" / "default" / "090000_300" + seg_dir = tmp_path / "chronicle" / "20260201" / "default" / "090000_300" seg_dir.mkdir(parents=True) audio_file = seg_dir / "audio.wav" audio_file.touch() @@ -78,16 +78,16 @@ def test_setup_cli_no_message_on_project_journal(tmp_path, monkeypatch, capsys): def _make_batch_journal(tmp_path: Path) -> Path: """Create a minimal temp journal with three segments for batch testing.""" - seg1 = tmp_path / "20260101" / "default" / "090000_300" + seg1 = tmp_path / "chronicle" / "20260101" / "default" / "090000_300" seg1.mkdir(parents=True) (seg1 / "audio.flac").touch() - seg2 = tmp_path / "20260101" / "default" / "140000_300" + seg2 = tmp_path / "chronicle" / "20260101" / "default" / "140000_300" seg2.mkdir(parents=True) (seg2 / "audio.flac").touch() (seg2 / "audio.jsonl").touch() - seg3 = tmp_path / "20260101" / "default" / "180000_300" + seg3 = tmp_path / "chronicle" / "20260101" / "default" / "180000_300" seg3.mkdir(parents=True) (seg3 / "screen.png").touch() diff --git a/tests/test_transfer.py b/tests/test_transfer.py index c257e53c3..6c6983c5e 100644 --- a/tests/test_transfer.py +++ b/tests/test_transfer.py @@ -90,7 +90,7 @@ class TestTransferExport: # Set up mock journal with day/stream/segment structure journal_path = tmp_path / "journal" - day_dir = journal_path / "20250101" + day_dir = journal_path / "chronicle" / "20250101" segment_dir = day_dir / "default" / "120000_300" segment_dir.mkdir(parents=True) @@ -130,7 +130,7 @@ class TestTransferExport: from observe.transfer import create_archive journal_path = tmp_path / "journal" - day_dir = journal_path / "20250101" + day_dir = journal_path / "chronicle" / "20250101" day_dir.mkdir(parents=True) monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) @@ -249,7 +249,7 @@ class TestTransferImport: # Set up journal with matching segment journal_path = tmp_path / "journal" - segment_dir = journal_path / "20250101" / "120000_300" + segment_dir = journal_path / "chronicle" / "20250101" / "120000_300" segment_dir.mkdir(parents=True) (segment_dir / "audio.flac").write_bytes(content) @@ -276,7 +276,7 @@ class TestTransferImport: # Set up journal with different content in same segment journal_path = tmp_path / "journal" - segment_dir = journal_path / "20250101" / "120000_300" + segment_dir = journal_path / "chronicle" / "20250101" / "120000_300" segment_dir.mkdir(parents=True) (segment_dir / "audio.flac").write_bytes(b"existing different data") @@ -327,7 +327,7 @@ class TestTransferImport: assert "120000_300" in result["imported"] # Verify files were extracted - segment_dir = journal_path / "20250101" / "120000_300" + segment_dir = journal_path / "chronicle" / "20250101" / "120000_300" assert segment_dir.exists() assert (segment_dir / "audio.flac").read_bytes() == audio_content assert (segment_dir / "audio.jsonl").read_bytes() == jsonl_content @@ -354,7 +354,7 @@ class TestTransferImport: assert result["status"] == "dry_run" # Directory should not be created - assert not (journal_path / "20250101").exists() + assert not (journal_path / "chronicle" / "20250101").exists() def test_import_archive_nothing_to_import(self, tmp_path, monkeypatch): """Test import_archive when all segments already synced.""" @@ -368,7 +368,7 @@ class TestTransferImport: # Set up journal with matching content journal_path = tmp_path / "journal" - segment_dir = journal_path / "20250101" / "120000_300" + segment_dir = journal_path / "chronicle" / "20250101" / "120000_300" segment_dir.mkdir(parents=True) (segment_dir / "audio.flac").write_bytes(content) @@ -440,7 +440,7 @@ class TestTransferSend: def _setup_journal(self, tmp_path, *, include_stream_json: bool = False) -> Path: journal = tmp_path / "journal" - day_dir = journal / "20250103" / "default" / "120000_300" + day_dir = journal / "chronicle" / "20250103" / "default" / "120000_300" day_dir.mkdir(parents=True) (day_dir / "audio.flac").write_bytes(b"audio data") (day_dir / "transcript.jsonl").write_text('{"text": "hello"}\n') @@ -507,8 +507,8 @@ class TestTransferSend: journal_root = tmp_path / "journal" journal_root.mkdir() - (journal_root / "20250101").mkdir() - (journal_root / "20250103").mkdir() + (journal_root / "chronicle" / "20250101").mkdir(parents=True) + (journal_root / "chronicle" / "20250103").mkdir(parents=True) (journal_root / "config").mkdir() (journal_root / "streams").mkdir() @@ -556,7 +556,7 @@ class TestTransferSend: journal = self._setup_journal(tmp_path) self._set_journal_override(monkeypatch, journal) - segment_dir = journal / "20250103" / "default" / "120000_300" + segment_dir = journal / "chronicle" / "20250103" / "default" / "120000_300" remote_files = [ { "name": "audio.flac", @@ -655,7 +655,7 @@ class TestTransferSend: journal = self._setup_journal(tmp_path) self._set_journal_override(monkeypatch, journal) - segment_dir = journal / "20250103" / "default" / "120000_300" + segment_dir = journal / "chronicle" / "20250103" / "default" / "120000_300" remote_files = [ { "name": "audio.flac", diff --git a/tests/test_updated_days.py b/tests/test_updated_days.py index 87566b47e..0a8c05dd9 100644 --- a/tests/test_updated_days.py +++ b/tests/test_updated_days.py @@ -5,14 +5,12 @@ import time -import think.utils from think.utils import updated_days def test_updated_days_fixture(monkeypatch): """20250101 has stream.updated but no daily.updated — should be updated.""" monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") - monkeypatch.setattr(think.utils, "_journal_path_cache", None) days = updated_days() assert "20250101" in days @@ -20,7 +18,6 @@ def test_updated_days_fixture(monkeypatch): def test_updated_days_exclude(monkeypatch): """Excluded days should not appear in results.""" monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") - monkeypatch.setattr(think.utils, "_journal_path_cache", None) days = updated_days(exclude={"20250101"}) assert "20250101" not in days @@ -28,7 +25,7 @@ def test_updated_days_exclude(monkeypatch): def test_updated_days_clean(tmp_path, monkeypatch): """Day with daily.updated newer than stream.updated is not updated.""" monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) - day_dir = tmp_path / "20260101" / "health" + day_dir = tmp_path / "chronicle" / "20260101" / "health" day_dir.mkdir(parents=True) (day_dir / "stream.updated").touch() time.sleep(0.05) @@ -39,5 +36,5 @@ def test_updated_days_clean(tmp_path, monkeypatch): def test_updated_days_no_stream(tmp_path, monkeypatch): """Day without stream.updated is not updated (no stream data).""" monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) - (tmp_path / "20260101").mkdir() + (tmp_path / "chronicle" / "20260101").mkdir(parents=True) assert updated_days() == [] diff --git a/tests/test_validate_key.py b/tests/test_validate_key.py index 0f39bde83..9986292a9 100644 --- a/tests/test_validate_key.py +++ b/tests/test_validate_key.py @@ -4,7 +4,6 @@ from __future__ import annotations import json -from pathlib import Path from unittest.mock import Mock, patch import pytest diff --git a/think/awareness.py b/think/awareness.py index a2c757f1a..b70fdf250 100644 --- a/think/awareness.py +++ b/think/awareness.py @@ -15,12 +15,12 @@ learned preferences, and cross-session agent memory. from __future__ import annotations +import fcntl import json import logging import os import tempfile import time -import fcntl from datetime import datetime from pathlib import Path from typing import Any @@ -268,8 +268,8 @@ def update_identity_section(filename: str, heading: str, content: str) -> bool: bool True if the section was found and updated, False otherwise. """ - from think.utils import get_journal from think.entities.core import atomic_write + from think.utils import get_journal file_path = Path(get_journal()) / "sol" / filename lock_path = file_path.parent / f".{filename}.lock" diff --git a/think/formatters.py b/think/formatters.py index 119b1843f..8d96c2cbe 100644 --- a/think/formatters.py +++ b/think/formatters.py @@ -40,7 +40,7 @@ from importlib import import_module from pathlib import Path from typing import Any, Callable -from think.utils import DATE_RE, get_journal +from think.utils import CHRONICLE_DIR, DATE_RE, get_journal, journal_relative_path def extract_path_metadata(rel_path: str) -> dict[str, str]: @@ -202,6 +202,9 @@ FORMATTERS: dict[str, tuple[str, str, bool]] = { "apps/*/agents/*.md": ("think.markdown", "format_markdown", True), } +_DAY_ROOTED_PATTERNS = [p for p in FORMATTERS if p.startswith("*/")] +_STRUCTURAL_PATTERNS = [p for p in FORMATTERS if not p.startswith("*/")] + def get_formatter(file_path: str) -> Callable | None: """Return formatter function for a journal-relative file path. @@ -271,13 +274,28 @@ def find_formattable_files(journal: str) -> dict[str, str]: """ files: dict[str, str] = {} journal_path = Path(journal) - - for pattern, (_mod, _func, indexed) in FORMATTERS.items(): + day_root = ( + journal_path / CHRONICLE_DIR + if (journal_path / CHRONICLE_DIR).is_dir() + else journal_path + ) + + for pattern in _STRUCTURAL_PATTERNS: + _mod, _func, indexed = FORMATTERS[pattern] if not indexed: continue for match in journal_path.glob(pattern): if match.is_file(): - rel = str(match.relative_to(journal_path)) + rel = match.relative_to(journal_path).as_posix() + files[rel] = str(match) + + for pattern in _DAY_ROOTED_PATTERNS: + _mod, _func, indexed = FORMATTERS[pattern] + if not indexed: + continue + for match in day_root.glob(pattern): + if match.is_file(): + rel = match.relative_to(day_root).as_posix() files[rel] = str(match) return files @@ -314,7 +332,7 @@ def format_file( if not file_path.is_relative_to(journal_path): raise ValueError(f"File is outside journal directory: {file_path}") - rel_path = str(file_path.relative_to(journal_path)) + rel_path = journal_relative_path(journal_path, file_path) formatter = get_formatter(rel_path) if formatter is None: diff --git a/think/hooks.py b/think/hooks.py index a90e29c13..77782ebe0 100644 --- a/think/hooks.py +++ b/think/hooks.py @@ -183,7 +183,7 @@ def compute_output_source(context: dict) -> str: Relative path like "20240101/agents/meetings.md". """ from think.talent import get_output_name - from think.utils import get_journal + from think.utils import CHRONICLE_DIR, get_journal day = context.get("day", "") output_path = context.get("output_path", "") @@ -191,7 +191,8 @@ def compute_output_source(context: dict) -> str: journal = get_journal() try: - return os.path.relpath(output_path, journal) + rel = os.path.relpath(output_path, journal).replace("\\", "/") + return rel.removeprefix(f"{CHRONICLE_DIR}/") except ValueError: segment = context.get("segment") output_name = get_output_name(name) diff --git a/think/importers/utils.py b/think/importers/utils.py index ab3d33853..d74b329b5 100644 --- a/think/importers/utils.py +++ b/think/importers/utils.py @@ -13,6 +13,8 @@ import json import re from pathlib import Path +from think.utils import resolve_journal_path + # ============================================================================ # File Operations # ============================================================================ @@ -450,7 +452,7 @@ def generate_content_manifest(journal_root: Path, timestamp: str) -> Path | None for file_path_str in all_files: file_path = Path(file_path_str) if not file_path.exists(): - file_path = journal_root / file_path_str + file_path = resolve_journal_path(journal_root, file_path_str) if not file_path.exists(): continue diff --git a/think/indexer/journal.py b/think/indexer/journal.py index ade52d17f..bcc595494 100644 --- a/think/indexer/journal.py +++ b/think/indexer/journal.py @@ -36,7 +36,16 @@ from think.formatters import ( load_jsonl, ) from think.markdown import format_markdown -from think.utils import DATE_RE, get_journal, now_ms, segment_key, segment_parse +from think.utils import ( + CHRONICLE_DIR, + DATE_RE, + get_journal, + journal_relative_path, + now_ms, + resolve_journal_path, + segment_key, + segment_parse, +) logger = logging.getLogger(__name__) @@ -243,10 +252,15 @@ def _find_signal_files(journal: str) -> dict[str, tuple[str, str]]: """Find all signal source files (KG markdown + event JSONL).""" journal_path = Path(journal) files: dict[str, tuple[str, str]] = {} + day_root = ( + journal_path / CHRONICLE_DIR + if (journal_path / CHRONICLE_DIR).is_dir() + else journal_path + ) - for path in journal_path.glob("*/agents/knowledge_graph.md"): + for path in day_root.glob("*/agents/knowledge_graph.md"): if path.is_file(): - rel = path.relative_to(journal_path).as_posix() + rel = path.relative_to(day_root).as_posix() files[rel] = (str(path), "kg") for path in journal_path.glob("facets/*/events/*.jsonl"): @@ -502,7 +516,7 @@ def _load_facet_entities_for_day(journal: str, day: str) -> dict[str, set[str]]: def _extract_signal_kg(journal: str, rel_path: str) -> list[dict[str, Any]]: """Extract KG appearance and edge signals from a knowledge graph markdown file.""" - abs_path = os.path.join(journal, rel_path) + abs_path = resolve_journal_path(journal, rel_path) day = rel_path.split("/")[0] try: @@ -904,7 +918,7 @@ def index_file(journal: str, file_path: str, verbose: bool = False) -> bool: if os.path.isabs(file_path): abs_path = Path(file_path).resolve() else: - abs_path = (journal_path / file_path).resolve() + abs_path = resolve_journal_path(journal_path, file_path).resolve() # Validate file exists if not abs_path.is_file(): @@ -912,7 +926,7 @@ def index_file(journal: str, file_path: str, verbose: bool = False) -> bool: # Validate file is under journal try: - rel_path = str(abs_path.relative_to(journal_path)) + rel_path = journal_relative_path(journal_path, abs_path) except ValueError: raise ValueError(f"File is outside journal directory: {abs_path}") from None @@ -942,7 +956,7 @@ def index_file(journal: str, file_path: str, verbose: bool = False) -> bool: parts = rel_path.replace("\\", "/").split("/") if len(parts) >= 4 and segment_key(parts[2]): rel_segment = "/".join(parts[:3]) - seg_dir = os.path.join(journal, rel_segment) + seg_dir = str(resolve_journal_path(journal, rel_segment)) conn.execute("DELETE FROM chunks WHERE path=?", (rel_segment,)) if os.path.isdir(seg_dir): seg_stream = _extract_stream(journal, rel_segment + "/dummy") @@ -967,7 +981,7 @@ def _extract_stream(journal: str, rel: str) -> str | None: parts = rel.replace("\\", "/").split("/") # Segment paths: parts[0]=day, parts[1]=stream, parts[2]=segment, parts[3+]=file if len(parts) >= 3 and segment_key(parts[2]): - seg_dir = os.path.join(journal, parts[0], parts[1], parts[2]) + seg_dir = str(resolve_journal_path(journal, "/".join(parts[:3]))) marker = read_segment_stream(seg_dir) if marker: return marker.get("stream") @@ -1483,15 +1497,20 @@ def consolidate_segment_entities(journal: str, full: bool = False) -> int: from datetime import datetime journal_path = Path(journal) + day_root = ( + journal_path / CHRONICLE_DIR + if (journal_path / CHRONICLE_DIR).is_dir() + else journal_path + ) today = datetime.now().strftime("%Y%m%d") # Collect all matching segment entity files across day/stream/segment dirs segment_files = [] - for path in journal_path.glob("**/agents/entities.jsonl"): + for path in day_root.glob("**/agents/entities.jsonl"): if not path.is_file(): continue try: - day = path.relative_to(journal_path).parts[0] + day = path.relative_to(day_root).parts[0] except (ValueError, IndexError): continue if not DATE_RE.fullmatch(day): @@ -1723,7 +1742,7 @@ def scan_journal(journal: str, verbose: bool = False, full: bool = False) -> boo seg_count = 0 for rel_segment in sorted(affected_segments): - segment_dir = os.path.join(journal, rel_segment) + segment_dir = str(resolve_journal_path(journal, rel_segment)) conn.execute("DELETE FROM chunks WHERE path=?", (rel_segment,)) if os.path.isdir(segment_dir): stream = _extract_stream(journal, rel_segment + "/dummy") diff --git a/think/merge.py b/think/merge.py index 7bea90013..d0d1f9df7 100644 --- a/think/merge.py +++ b/think/merge.py @@ -4,6 +4,7 @@ """Journal merge engine - one-shot merge of a source journal into the target.""" import json +import logging import re import shutil from dataclasses import dataclass, field @@ -19,9 +20,10 @@ from think.entities.journal import ( from think.entities.matching import find_matching_entity from think.entities.observations import save_observations from think.entities.relationships import save_facet_relationship -from think.utils import iter_segments +from think.utils import CHRONICLE_DIR, iter_segments DATE_RE = re.compile(r"^\d{8}$") +logger = logging.getLogger(__name__) @dataclass @@ -76,11 +78,27 @@ def _log_decision(log_path: Path | None, entry: dict[str, Any]) -> None: def _source_day_dirs(source: Path) -> dict[str, Path]: - days: dict[str, Path] = {} - for entry in sorted(source.iterdir()): - if entry.is_dir() and DATE_RE.match(entry.name): - days[entry.name] = entry - return days + chronicle_dir = source / CHRONICLE_DIR + chronicle_days = {} + if chronicle_dir.is_dir(): + chronicle_days = { + entry.name: entry + for entry in chronicle_dir.iterdir() + if entry.is_dir() and DATE_RE.match(entry.name) + } + flat_days = { + entry.name: entry + for entry in source.iterdir() + if entry.is_dir() and DATE_RE.match(entry.name) + } + if chronicle_days and flat_days: + logger.warning( + "Merge source has both flat and %s/ day dirs; preferring %s/.", + CHRONICLE_DIR, + CHRONICLE_DIR, + ) + return chronicle_days + return chronicle_days or flat_days def _merge_segments( @@ -90,9 +108,12 @@ def _merge_segments( dry_run: bool, log_path: Path | None = None, ) -> None: + target_chronicle = target / CHRONICLE_DIR + if not dry_run: + target_chronicle.mkdir(parents=True, exist_ok=True) for day_name, source_day in sorted(_source_day_dirs(source).items()): - target_day = target / day_name + target_day = target_chronicle / day_name for stream, seg_key, seg_path in iter_segments(source_day): if stream == "_default": target_path = target_day / seg_key diff --git a/think/prompts.py b/think/prompts.py index 4bb464741..bb61e7ced 100644 --- a/think/prompts.py +++ b/think/prompts.py @@ -32,7 +32,7 @@ TEMPLATES_DIR = Path(__file__).parent / "templates" # Cached raw template content loaded from think/templates/*.md _templates_cache: dict[str, str] | None = None -# Cached sol/ template vars loaded from repo and journal sol/ dirs +# Cached repo sol/ template vars loaded from sol/*.md _sol_vars_cache: dict[str, str] | None = None SOL_DIR = Path(__file__).parent.parent / "sol" @@ -116,22 +116,22 @@ def _load_sol_vars() -> dict[str, str]: Journal sol/ files override repo sol/ files on collision. """ global _sol_vars_cache - if _sol_vars_cache is not None: - return _sol_vars_cache - from think.utils import get_journal - _sol_vars_cache = {} + if _sol_vars_cache is None: + _sol_vars_cache = {} - # Repo sol/ first - if SOL_DIR.is_dir(): - for md_path in sorted(SOL_DIR.glob("*.md")): - var_name = f"sol_{md_path.stem}" - try: - post = frontmatter.load(md_path) - _sol_vars_cache[var_name] = post.content.strip() - except Exception: - pass + # Repo sol/ first + if SOL_DIR.is_dir(): + for md_path in sorted(SOL_DIR.glob("*.md")): + var_name = f"sol_{md_path.stem}" + try: + post = frontmatter.load(md_path) + _sol_vars_cache[var_name] = post.content.strip() + except Exception: + pass + + sol_vars = dict(_sol_vars_cache) # Journal sol/ second (wins on collision) try: @@ -141,13 +141,13 @@ def _load_sol_vars() -> dict[str, str]: var_name = f"sol_{md_path.stem}" try: post = frontmatter.load(md_path) - _sol_vars_cache[var_name] = post.content.strip() + sol_vars[var_name] = post.content.strip() except Exception: pass except Exception: pass - return _sol_vars_cache + return sol_vars def reset_sol_vars_cache() -> None: diff --git a/think/runner.py b/think/runner.py index 90ba195ef..7c484180d 100644 --- a/think/runner.py +++ b/think/runner.py @@ -5,12 +5,12 @@ """Unified process spawning and lifecycle management utilities. All subprocess output is automatically logged to: - journal/{YYYYMMDD}/health/{ref}_{process_name}.log + journal/chronicle/{YYYYMMDD}/health/{ref}_{process_name}.log Where process_name is derived from cmd[0] basename, and ref is a unique correlation ID. Symlinks provide stable access paths: - journal/{YYYYMMDD}/health/{process_name}.log (day-level symlink) + journal/chronicle/{YYYYMMDD}/health/{process_name}.log (day-level symlink) journal/health/{process_name}.log (journal-level symlink) Logs automatically roll over at midnight for long-running processes. @@ -28,7 +28,7 @@ from datetime import datetime from pathlib import Path from think.callosum import CallosumConnection -from think.utils import day_path, get_journal, now_ms +from think.utils import CHRONICLE_DIR, get_journal, now_ms logger = logging.getLogger(__name__) @@ -46,9 +46,9 @@ def _current_day() -> str: def _day_health_log_path(day: str, ref: str, name: str) -> Path: """Build path to day health log. - Returns: journal/{day}/health/{ref}_{name}.log + Returns: journal/chronicle/{day}/health/{ref}_{name}.log """ - return day_path(day) / "health" / f"{ref}_{name}.log" + return _get_journal_path() / CHRONICLE_DIR / day / "health" / f"{ref}_{name}.log" def _atomic_symlink(link_path: Path, target: str) -> None: @@ -91,11 +91,11 @@ class DailyLogWriter: When ``day`` is provided, the writer is pinned to that day directory and midnight rollover is disabled (batch processing of historical days). - Writes to: journal/{YYYYMMDD}/health/{ref}_{name}.log + Writes to: journal/chronicle/{YYYYMMDD}/health/{ref}_{name}.log Creates and maintains symlinks: - - journal/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) - - journal/health/{name}.log -> {YYYYMMDD}/health/{ref}_{name}.log (journal-level) + - journal/chronicle/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) + - journal/health/{name}.log -> chronicle/{YYYYMMDD}/health/{ref}_{name}.log (journal-level) When the day changes, automatically closes old file, opens new file, and updates symlinks. """ @@ -118,17 +118,19 @@ class DailyLogWriter: def _update_symlinks(self) -> None: """Update day-level and journal-level symlinks to point to current log.""" journal = _get_journal_path() - day_health = journal / self._current_day / "health" + day_health = journal / CHRONICLE_DIR / self._current_day / "health" log_filename = f"{self._ref}_{self._name}.log" - # Day-level symlink: {YYYYMMDD}/health/{name}.log -> {ref}_{name}.log + # Day-level symlink: chronicle/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log day_symlink = day_health / f"{self._name}.log" _atomic_symlink(day_symlink, log_filename) - # Journal-level symlink: health/{name}.log -> ../{YYYYMMDD}/health/{ref}_{name}.log - # Relative from journal/health/ to journal/{YYYYMMDD}/health/ + # Journal-level symlink: health/{name}.log -> ../chronicle/{YYYYMMDD}/health/{ref}_{name}.log + # Relative from journal/health/ to journal/chronicle/{YYYYMMDD}/health/ journal_symlink = journal / "health" / f"{self._name}.log" - relative_target = f"../{self._current_day}/health/{log_filename}" + relative_target = ( + f"../{CHRONICLE_DIR}/{self._current_day}/health/{log_filename}" + ) _atomic_symlink(journal_symlink, relative_target) def write(self, message: str) -> None: @@ -172,14 +174,14 @@ class DailyLogWriter: class ManagedProcess: """Subprocess wrapper with automatic output logging and lifecycle management. - All output is automatically logged to: - journal/{YYYYMMDD}/health/{ref}_{name}.log + All output is automatically logged to: + journal/chronicle/{YYYYMMDD}/health/{ref}_{name}.log Where name is derived from cmd[0] basename, and ref is a unique correlation ID. - Symlinks are automatically created and maintained: - journal/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) - journal/health/{name}.log -> {YYYYMMDD}/health/{ref}_{name}.log (journal-level) + Symlinks are automatically created and maintained: + journal/chronicle/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) + journal/health/{name}.log -> chronicle/{YYYYMMDD}/health/{ref}_{name}.log (journal-level) Logs roll over automatically at midnight for long-running processes. diff --git a/think/segment.py b/think/segment.py index 617a830cf..73572374f 100644 --- a/think/segment.py +++ b/think/segment.py @@ -569,7 +569,6 @@ def cmd_move(args: argparse.Namespace) -> None: if succ_path: succ_marker = read_segment_stream(succ_path) if succ_marker: - old_prev = f"{succ_marker.get('prev_day')}/{stream}/{succ_marker.get('prev_segment')}" write_segment_stream( succ_path, succ_marker["stream"], @@ -609,7 +608,7 @@ def cmd_move(args: argparse.Namespace) -> None: _touch_health_marker(to_day) print(f" touched health markers: {src_day}, {to_day}") if verbose: - print(f" dream will re-run daily agents on both days") + print(" dream will re-run daily agents on both days") # Post-move verify is informational — the move already completed. print() diff --git a/think/utils.py b/think/utils.py index 205eb2347..ed81ee96f 100644 --- a/think/utils.py +++ b/think/utils.py @@ -27,6 +27,7 @@ from timefhuman import timefhuman from media import MIME_TYPES DATE_RE = re.compile(r"\d{8}") +CHRONICLE_DIR = "chronicle" DEFAULT_STREAM = "_default" @@ -122,8 +123,35 @@ def get_journal() -> str: return journal +def resolve_journal_path(journal: str | Path, rel: str) -> Path: + """Resolve a chronicle-free journal-relative path to its on-disk location.""" + if not rel: + raise ValueError("rel must be non-empty") + if os.path.isabs(rel): + raise ValueError("rel must be journal-relative") + if "\\" in rel: + raise ValueError("rel must use POSIX separators") + parts = Path(rel).parts + if not parts or any(p in ("", ".", "..") for p in parts): + raise ValueError("rel must not contain empty, '.', or '..' components") + journal_path = Path(journal) + if DATE_RE.fullmatch(parts[0]): + return journal_path / CHRONICLE_DIR / rel + return journal_path / rel + + +def journal_relative_path(journal: str | Path, abs_path: str | Path) -> str: + """Return a chronicle-free journal-relative POSIX path for an absolute path under the journal.""" + journal_path = Path(journal) + file_path = Path(abs_path) + chronicle_root = journal_path / CHRONICLE_DIR + if file_path.is_relative_to(chronicle_root): + return file_path.relative_to(chronicle_root).as_posix() + return file_path.relative_to(journal_path).as_posix() + + def day_path(day: Optional[str] = None, *, create: bool = True) -> Path: - """Return absolute path for a day directory within the journal. + """Return absolute path for a day directory within the journal chronicle. Parameters ---------- @@ -135,7 +163,8 @@ def day_path(day: Optional[str] = None, *, create: bool = True) -> Path: Returns ------- Path - Absolute path to the day directory. Directory is created if it doesn't exist. + Absolute path to the day directory in chronicle/. Directory is created if + it doesn't exist. Raises ------ @@ -150,7 +179,7 @@ def day_path(day: Optional[str] = None, *, create: bool = True) -> Path: elif not DATE_RE.fullmatch(day): raise ValueError("day must be in YYYYMMDD format") - path = Path(journal) / day + path = Path(journal) / CHRONICLE_DIR / day if create: path.mkdir(parents=True, exist_ok=True) return path @@ -163,14 +192,16 @@ def day_dirs() -> dict[str, str]: ------- dict[str, str] Mapping of day folder names to their full paths. - Example: {"20250101": "/path/to/journal/20250101", ...} + Example: {"20250101": "/path/to/journal/chronicle/20250101", ...} """ - journal = get_journal() + chronicle_dir = Path(get_journal()) / CHRONICLE_DIR + if not chronicle_dir.is_dir(): + return {} days: dict[str, str] = {} - for name in os.listdir(journal): + for name in os.listdir(chronicle_dir): if DATE_RE.fullmatch(name): - path = os.path.join(journal, name) + path = os.path.join(chronicle_dir, name) if os.path.isdir(path): days[name] = path return days