personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Canonicalize journal path to ./journal/ from project root

Replace the 3-tier JOURNAL_PATH configuration system (env var → .env →
platformdirs default) with a single canonical path: <project_root>/journal/.
Internal override _SOLSTONE_JOURNAL_OVERRIDE preserved for tests.

- Rewrite get_journal() and get_journal_info() in think/utils.py
- Remove platformdirs import and dependency
- Update all 80+ test files to use _SOLSTONE_JOURNAL_OVERRIDE
- Update all documentation to reference journal/ instead of $JOURNAL_PATH
- Update Makefile, .env.example, cortex subprocess env propagation
- Simplify sol config env output (just prints the path)
- Remove setup_cli() "JOURNAL_PATH not set" warning (no longer applicable)

Implements: cpo/specs/in-flight/journal-path-canonicalization.md

+949 -1058
-3
.env.example
··· 2 2 # Copy this file to .env and fill in your values 3 3 # See docs/INSTALL.md for setup instructions 4 4 5 - # Required: Path to your journal directory 6 - JOURNAL_PATH=/path/to/your/journal 7 - 8 5 # AI API Keys (at least one required for chat and insights) 9 6 GOOGLE_API_KEY=your-google-api-key 10 7 OPENAI_API_KEY=your-openai-api-key
+7 -9
AGENTS.md
··· 18 18 19 19 Understanding these core concepts is essential for working with solstone: 20 20 21 - * **Journal**: Central data structure organized as `JOURNAL_PATH/YYYYMMDD/` directories. All captured data, transcripts, and analysis artifacts are stored here. See [docs/JOURNAL.md](docs/JOURNAL.md). 21 + * **Journal**: Central data structure organized as `journal/YYYYMMDD/` directories. All captured data, transcripts, and analysis artifacts are stored here. See [docs/JOURNAL.md](docs/JOURNAL.md). 22 22 23 23 * **Facets**: Project/context organization system (e.g., "work", "personal", "acme"). Facets group related content and provide scoped views of entities, tasks, and activities. 24 24 ··· 57 57 * **Modules**: Each top-level folder is a Python package with `__init__.py` unless it is data-only (e.g., `tests/fixtures/`) 58 58 * **Imports**: Prefer absolute imports (e.g., `from think.utils import setup_cli`) whenever feasible 59 59 * **Entry Points**: Commands are registered in `sol.py`'s `COMMANDS` dict (pyproject.toml just defines the `sol` entry point) 60 - * **Journal**: Data stored under `JOURNAL_PATH` (see Environment Management below) 60 + * **Journal**: Data stored under `journal/` at the project root 61 61 * **Calling**: When calling other modules as a separate process always use `sol <command>` and never call using `python -m ...` (e.g., use `sol indexer`, NOT `python -m think.indexer`) 62 62 63 63 --- ··· 67 67 **Core Pipeline**: `observe` (capture) → JSON transcripts → `think` (analyze) → SQLite index → `convey` (web UI) 68 68 69 69 **Data Organization**: 70 - * Everything organized under `JOURNAL_PATH/YYYYMMDD/` daily directories 70 + * Everything organized under `journal/YYYYMMDD/` daily directories 71 71 * Import segments are anchored to creation/modification time, not content "about" time. A calendar event is segmented at the moment it was created, not its scheduled time. See the creation-moment principle in the extro org's `cpo/strategy/journal-memory-structure.md`. 72 72 * Facets provide project-scoped organization and filtering 73 73 * Entities are extracted from transcripts and tracked across time ··· 87 87 88 88 ```python 89 89 # Use comprehensive mock journal data for testing 90 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 90 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 91 91 # Now all journal operations work with test data 92 92 ``` 93 93 ··· 153 153 ## Important Development Notes 154 154 155 155 ### Environment Management 156 - * **JOURNAL_PATH**: The live journal path is stored in `.env`. To access it: 157 - - **Shell/CLI**: Run `grep JOURNAL_PATH .env` to get the path, then use it directly 158 - - **Python**: Use `get_journal()` from `think.utils` - it handles `.env` loading and auto-creates a platform-specific default if unset 156 + * **Journal Path**: The journal lives at `journal/` in the project root. `get_journal()` from `think.utils` returns the path. For tests, set `_SOLSTONE_JOURNAL_OVERRIDE` to override. 159 157 * **API Keys**: Store in `.env` file, never commit to repository 160 158 161 159 ### Error Handling & Logging ··· 234 232 235 233 In a second terminal, take screenshots or hit endpoints: 236 234 ```bash 237 - export JOURNAL_PATH=tests/fixtures/journal 235 + export _SOLSTONE_JOURNAL_OVERRIDE=tests/fixtures/journal 238 236 export PATH=$(pwd)/.venv/bin:$PATH 239 237 sol screenshot / -o scratch/home.png 240 238 curl -s http://localhost:$(cat tests/fixtures/journal/health/convey.port)/ ··· 249 247 ### File Locations 250 248 * **Entry Points**: `sol.py` `COMMANDS` dict 251 249 * **Test Fixtures**: `tests/fixtures/journal/` - complete mock journal 252 - * **Live Logs**: `$JOURNAL_PATH/health/<service>.log` 250 + * **Live Logs**: `journal/health/<service>.log` 253 251 * **Agent Personas**: `muse/*.md` (apps can add their own in `muse/`, see [docs/APPS.md](docs/APPS.md)) 254 252 * **Generator Templates**: `muse/*.md` (apps can add their own in `muse/`, see [docs/APPS.md](docs/APPS.md)) 255 253 * **Agent Skills**: `muse/*/SKILL.md` - symlinked to `.agents/skills/` and `.claude/skills/` via `make skills`, read https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices to create the best skills
+6 -6
Makefile
··· 124 124 echo "$$SANDBOX_JOURNAL" > .sandbox.journal; \ 125 125 echo "Sandbox journal: $$SANDBOX_JOURNAL"; \ 126 126 # Boot supervisor in background \ 127 - JOURNAL_PATH="$$SANDBOX_JOURNAL" PATH=$(CURDIR)/$(VENV_BIN):$$PATH \ 127 + _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" PATH=$(CURDIR)/$(VENV_BIN):$$PATH \ 128 128 $(VENV_BIN)/sol supervisor 0 --no-observers --no-daily \ 129 129 > "$$SANDBOX_JOURNAL/health/supervisor.log" 2>&1 & \ 130 130 echo $$! > .sandbox.pid; \ ··· 133 133 echo "Waiting for services..."; \ 134 134 READY=false; \ 135 135 for i in $$(seq 1 20); do \ 136 - if JOURNAL_PATH="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol health > /dev/null 2>&1; then \ 136 + if _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol health > /dev/null 2>&1; then \ 137 137 READY=true; \ 138 138 break; \ 139 139 fi; \ ··· 182 182 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 183 183 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 184 184 RESULT=0; \ 185 - JOURNAL_PATH="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 185 + _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 186 186 $(MAKE) sandbox-stop; \ 187 187 exit $$RESULT 188 188 ··· 193 193 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 194 194 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 195 195 RESULT=0; \ 196 - JOURNAL_PATH="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py update --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 196 + _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py update --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 197 197 $(MAKE) sandbox-stop; \ 198 198 exit $$RESULT 199 199 ··· 245 245 RESULT_BROWSER=0; \ 246 246 echo ""; \ 247 247 echo "=== API baseline verification ==="; \ 248 - JOURNAL_PATH="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "$$BASE_URL" || RESULT_API=$$?; \ 248 + _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "$$BASE_URL" || RESULT_API=$$?; \ 249 249 echo ""; \ 250 250 echo "=== Browser scenario verification ==="; \ 251 251 $(VENV_BIN)/python tests/verify_browser.py verify --base-url "$$BASE_URL" || RESULT_BROWSER=$$?; \ ··· 273 273 fi 274 274 275 275 # Test environment - use fixtures journal for all tests 276 - TEST_ENV = JOURNAL_PATH=tests/fixtures/journal 276 + TEST_ENV = _SOLSTONE_JOURNAL_OVERRIDE=tests/fixtures/journal 277 277 278 278 # Venv tool shortcuts 279 279 PYTEST := $(VENV_BIN)/pytest
+1 -1
README.md
··· 64 64 - **cortex** — orchestrates agent execution. receives events, dispatches agents, writes results back to the journal. 65 65 - **callosum** — async message bus connecting all services. enables event-driven coordination between observe, think, cortex, and convey. 66 66 - **convey** — Flask-based web interface with 17 pluggable apps for navigating journal data. 67 - - **journal** — `JOURNAL_PATH/YYYYMMDD/` daily directories. the single source of truth — transcripts, media, entities, agent outputs, and the SQLite index all live here. 67 + - **journal** — `journal/YYYYMMDD/` daily directories. the single source of truth — transcripts, media, entities, agent outputs, and the SQLite index all live here. 68 68 69 69 ## quick start 70 70
+3 -3
apps/calendar/tests/conftest.py
··· 26 26 lines = [json.dumps(e, ensure_ascii=False) for e in entries] 27 27 calendar_path.write_text("\n".join(lines) + "\n", encoding="utf-8") 28 28 29 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 29 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 30 30 monkeypatch.setenv("SOL_DAY", day) 31 31 monkeypatch.setenv("SOL_FACET", facet) 32 32 return day, facet, calendar_path ··· 51 51 ) 52 52 53 53 (facet_path / "calendar").mkdir() 54 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 54 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 55 55 monkeypatch.setenv("SOL_FACET", facet) 56 56 return journal, facet 57 57 ··· 61 61 @pytest.fixture 62 62 def move_env(tmp_path, monkeypatch): 63 63 """Create a two-facet environment for move tests.""" 64 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 64 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 65 65 66 66 def _create( 67 67 entries: list[dict] | None = None,
+3 -3
apps/entities/tests/conftest.py
··· 22 22 entity_env(attached=[ 23 23 {"type": "Person", "name": "Alice", "description": "Friend"} 24 24 ]) 25 - # JOURNAL_PATH is set, entity files exist 25 + # _SOLSTONE_JOURNAL_OVERRIDE is set, entity files exist 26 26 """ 27 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 27 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 28 28 29 29 def _create( 30 30 attached: list[dict] | None = None, ··· 49 49 @pytest.fixture 50 50 def entity_move_env(tmp_path, monkeypatch): 51 51 """Create a two-facet environment for entity move tests.""" 52 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 52 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 53 53 54 54 def _create( 55 55 entity_name: str = "Alice Johnson",
+4 -4
apps/health/muse/health/SKILL.md
··· 17 17 18 18 Show current supervisor status: running services (names, PIDs, uptimes), crashed services, active tasks, queue depths, heartbeat health, and callosum client count. 19 19 20 - Connects to `$JOURNAL_PATH/health/callosum.sock` with a 10-second timeout. 20 + Connects to `journal/health/callosum.sock` with a 10-second timeout. 21 21 22 22 Example: 23 23 ··· 41 41 42 42 Behavior notes: 43 43 44 - - Reads symlinked logs from `$JOURNAL_PATH/YYYYMMDD/health/*.log`. 45 - - Includes `$JOURNAL_PATH/health/supervisor.log` when no filters are active. 44 + - Reads symlinked logs from `journal/YYYYMMDD/health/*.log`. 45 + - Includes `journal/health/supervisor.log` when no filters are active. 46 46 - Log line format: `ISO8601 [service:stream] LEVEL:logger:message`. 47 47 - `-f` mode handles symlink target rotation at midnight. 48 48 ··· 109 109 110 110 ## journal layout 111 111 112 - Reference map of key paths. `$JOURNAL_PATH` is the journal root. 112 + Reference map of key paths. `journal/` is the journal root. 113 113 114 114 ### journal level 115 115
+1 -1
apps/remote/tests/conftest.py
··· 25 25 journal.mkdir() 26 26 27 27 # Set environment 28 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 28 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 29 29 30 30 # Create Flask test client 31 31 from convey import create_app
+1 -1
apps/remote/tests/test_utils.py
··· 30 30 31 31 journal = tmp_path / "journal" 32 32 journal.mkdir() 33 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 33 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 34 34 monkeypatch.setattr(state, "journal_root", str(journal)) 35 35 36 36 # Create remotes directory
+1 -1
apps/search/tests/test_maint_migrate_index.py
··· 14 14 15 15 @pytest.fixture(autouse=True) 16 16 def _set_journal(tmp_path, monkeypatch): 17 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 17 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 18 18 19 19 20 20 def _create_old_schema(tmp_path):
+4 -10
apps/speakers/bootstrap.py
··· 124 124 ) 125 125 combined_meta = np.array(existing_meta + new_meta, dtype=str) 126 126 127 - tmp_path = npz_path.with_name(npz_path.stem + '.tmp.npz') 127 + tmp_path = npz_path.with_name(npz_path.stem + ".tmp.npz") 128 128 np.savez_compressed(tmp_path, embeddings=combined_emb, metadata=combined_meta) 129 129 tmp_path.rename(npz_path) 130 130 return len(new_items) ··· 481 481 482 482 # Save merged relationship (atomic write) 483 483 canonical_rel["entity_id"] = canonical_id 484 - content = ( 485 - json.dumps(canonical_rel, ensure_ascii=False, indent=2) + "\n" 486 - ) 484 + content = json.dumps(canonical_rel, ensure_ascii=False, indent=2) + "\n" 487 485 tmp = canonical_rel_path.with_suffix(".tmp") 488 486 with open(tmp, "w", encoding="utf-8") as f: 489 487 f.write(content) ··· 494 492 if alias_obs_path.exists(): 495 493 alias_obs = alias_obs_path.read_text(encoding="utf-8") 496 494 if alias_obs.strip(): 497 - canonical_obs_path = ( 498 - canonical_rel_dir / "observations.jsonl" 499 - ) 495 + canonical_obs_path = canonical_rel_dir / "observations.jsonl" 500 496 existing_obs = "" 501 497 if canonical_obs_path.exists(): 502 498 existing_obs = canonical_obs_path.read_text( 503 499 encoding="utf-8" 504 500 ) 505 - with open( 506 - canonical_obs_path, "a", encoding="utf-8" 507 - ) as f: 501 + with open(canonical_obs_path, "a", encoding="utf-8") as f: 508 502 if existing_obs and not existing_obs.endswith("\n"): 509 503 f.write("\n") 510 504 f.write(alias_obs)
+2 -2
apps/speakers/routes.py
··· 236 236 new_metadata = np.append(existing_metadata, metadata_json) 237 237 238 238 # Write back (atomic: temp file + rename) 239 - tmp_path = npz_path.with_name(npz_path.stem + '.tmp.npz') 239 + tmp_path = npz_path.with_name(npz_path.stem + ".tmp.npz") 240 240 np.savez_compressed(tmp_path, embeddings=new_embeddings, metadata=new_metadata) 241 241 tmp_path.rename(npz_path) 242 242 return npz_path ··· 296 296 297 297 new_embeddings = embeddings[keep] 298 298 new_metadata = metadata_arr[keep] 299 - tmp_path = npz_path.with_name(npz_path.stem + '.tmp.npz') 299 + tmp_path = npz_path.with_name(npz_path.stem + ".tmp.npz") 300 300 np.savez_compressed(tmp_path, embeddings=new_embeddings, metadata=new_metadata) 301 301 tmp_path.rename(npz_path) 302 302 return True
+4 -8
apps/speakers/tests/conftest.py
··· 30 30 env = speakers_env() 31 31 env.create_segment("20240101", "143022_300", ["mic_audio"]) 32 32 env.create_entity("Alice Test") 33 - # Now JOURNAL_PATH is set and data exists 33 + # Now _SOLSTONE_JOURNAL_OVERRIDE is set and data exists 34 34 """ 35 35 36 36 class SpeakersEnv: 37 37 def __init__(self, journal_path: Path): 38 38 self.journal = journal_path 39 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 39 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 40 40 41 41 def create_segment( 42 42 self, ··· 299 299 json.dump(relationship, f, indent=2) 300 300 301 301 if observations: 302 - with open( 303 - rel_dir / "observations.jsonl", "w", encoding="utf-8" 304 - ) as f: 302 + with open(rel_dir / "observations.jsonl", "w", encoding="utf-8") as f: 305 303 for obs in observations: 306 304 f.write( 307 - json.dumps( 308 - {"content": obs, "observed_at": 1700000000000} 309 - ) 305 + json.dumps({"content": obs, "observed_at": 1700000000000}) 310 306 + "\n" 311 307 ) 312 308
+9 -30
apps/speakers/tests/test_merge_names.py
··· 286 286 287 287 # Both have descriptions: canonical's wins 288 288 env.create_facet_relationship("work", "alias_d", description="From alias") 289 - env.create_facet_relationship( 290 - "work", "canonical_d", description="From canonical" 291 - ) 289 + env.create_facet_relationship("work", "canonical_d", description="From canonical") 292 290 293 291 merge_names("Alias D", "Canonical D") 294 292 ··· 327 325 env.create_facet_relationship( 328 326 "work", "alias_obs", observations=["Likes coffee", "Morning person"] 329 327 ) 330 - env.create_facet_relationship( 331 - "work", "canonical_obs", observations=["Senior role"] 332 - ) 328 + env.create_facet_relationship("work", "canonical_obs", observations=["Senior role"]) 333 329 334 330 merge_names("Alias Obs", "Canonical Obs") 335 331 ··· 353 349 env = speakers_env() 354 350 env.create_entity("Alias None") 355 351 env.create_entity("Canonical None") 356 - env.create_facet_relationship( 357 - "work", "canonical_none", description="Only me" 358 - ) 352 + env.create_facet_relationship("work", "canonical_none", description="Only me") 359 353 360 354 result = merge_names("Alias None", "Canonical None") 361 355 362 356 assert result["facets_merged"] == [] 363 357 assert result["facets_moved"] == [] 364 358 rel_path = ( 365 - env.journal 366 - / "facets" 367 - / "work" 368 - / "entities" 369 - / "canonical_none" 370 - / "entity.json" 359 + env.journal / "facets" / "work" / "entities" / "canonical_none" / "entity.json" 371 360 ) 372 361 with open(rel_path) as f: 373 362 rel = json.load(f) ··· 524 513 # Write corrupted file containing the alias_id string 525 514 agents_dir = env.journal / "20240101" / STREAM / "143022_300" / "agents" 526 515 agents_dir.mkdir(parents=True, exist_ok=True) 527 - (agents_dir / "speaker_labels.json").write_text( 528 - "corrupt_alias {not valid json" 529 - ) 516 + (agents_dir / "speaker_labels.json").write_text("corrupt_alias {not valid json") 530 517 531 518 result = merge_names("Corrupt Alias", "Corrupt Canon") 532 519 ··· 776 763 777 764 emb_a = np.random.default_rng(42).standard_normal((3, 256)).astype(np.float32) 778 765 emb_b = np.random.default_rng(99).standard_normal((3, 256)).astype(np.float32) 779 - meta_a = np.array( 780 - [json.dumps({"key": f"a_{i}"}) for i in range(3)], dtype=str 781 - ) 782 - meta_b = np.array( 783 - [json.dumps({"key": f"b_{i}"}) for i in range(3)], dtype=str 784 - ) 785 - np.savez_compressed( 786 - entity_a / "voiceprints.npz", embeddings=emb_a, metadata=meta_a 787 - ) 788 - np.savez_compressed( 789 - entity_b / "voiceprints.npz", embeddings=emb_b, metadata=meta_b 790 - ) 766 + meta_a = np.array([json.dumps({"key": f"a_{i}"}) for i in range(3)], dtype=str) 767 + meta_b = np.array([json.dumps({"key": f"b_{i}"}) for i in range(3)], dtype=str) 768 + np.savez_compressed(entity_a / "voiceprints.npz", embeddings=emb_a, metadata=meta_a) 769 + np.savez_compressed(entity_b / "voiceprints.npz", embeddings=emb_b, metadata=meta_b) 791 770 792 771 result = _runner.invoke( 793 772 speakers_app, ["merge-names", "Alice Alias", "Alice Canonical"]
+2 -2
apps/support/diagnostics.py
··· 78 78 def collect_services() -> dict[str, str]: 79 79 """Check which solstone services are running. 80 80 81 - Looks at PID files under ``$JOURNAL_PATH/health/``. 81 + Looks at PID files under ``journal/health/``. 82 82 """ 83 83 from think.utils import get_journal 84 84 ··· 106 106 def collect_recent_errors(limit: int = 10) -> list[dict[str, Any]]: 107 107 """Return the most recent callosum error events from service logs. 108 108 109 - Scans ``$JOURNAL_PATH/health/*.log`` for lines containing ``ERROR``. 109 + Scans ``journal/health/*.log`` for lines containing ``ERROR``. 110 110 """ 111 111 from think.utils import get_journal 112 112
+4 -4
apps/todos/tests/conftest.py
··· 28 28 {"text": "First item"}, 29 29 {"text": "Second item", "completed": True} 30 30 ]) 31 - # Now JOURNAL_PATH is set and todo file exists 31 + # Now _SOLSTONE_JOURNAL_OVERRIDE is set and todo file exists 32 32 """ 33 33 34 34 def _create( ··· 44 44 if entries is not None: 45 45 lines = [json.dumps(e, ensure_ascii=False) for e in entries] 46 46 todo_path.write_text("\n".join(lines) + "\n", encoding="utf-8") 47 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 47 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 48 48 return day, facet, todo_path 49 49 50 50 return _create ··· 74 74 # Create todos directory 75 75 (facet_path / "todos").mkdir() 76 76 77 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 77 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 78 78 return journal, facet 79 79 80 80 return _create ··· 83 83 @pytest.fixture 84 84 def move_env(tmp_path, monkeypatch): 85 85 """Create a two-facet environment for move tests.""" 86 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 86 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 87 87 88 88 def _create( 89 89 entries: list[dict] | None = None,
+23 -23
apps/todos/tests/test_todo.py
··· 37 37 38 38 39 39 def test_get_todos_returns_none_when_missing(monkeypatch, journal_root): 40 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 40 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 41 41 assert get_todos("20240101", "personal") is None 42 42 43 43 44 44 def test_get_todos_parses_basic_fields(monkeypatch, journal_root): 45 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 45 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 46 46 _write_todos( 47 47 journal_root, 48 48 "personal", ··· 75 75 76 76 77 77 def test_get_todos_handles_cancelled(monkeypatch, journal_root): 78 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 78 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 79 79 _write_todos( 80 80 journal_root, 81 81 "work", ··· 105 105 106 106 107 107 def test_get_todos_ignores_blank_lines(monkeypatch, journal_root): 108 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 108 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 109 109 # Write with blank lines mixed in 110 110 todos_dir = journal_root / "facets" / "personal" / "todos" 111 111 todos_dir.mkdir(parents=True, exist_ok=True) ··· 272 272 273 273 274 274 def test_upcoming_groups_future_days(monkeypatch, journal_root): 275 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 275 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 276 276 # Create facet structure 277 277 (journal_root / "facets" / "personal").mkdir(parents=True) 278 278 (journal_root / "facets" / "personal" / "facet.json").write_text( ··· 319 319 320 320 321 321 def test_upcoming_respects_limit(monkeypatch, journal_root): 322 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 322 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 323 323 # Create facet structure 324 324 (journal_root / "facets" / "work").mkdir(parents=True) 325 325 (journal_root / "facets" / "work" / "facet.json").write_text( ··· 346 346 347 347 def test_upcoming_excludes_cancelled(monkeypatch, journal_root): 348 348 """Cancelled todos should not appear in upcoming view.""" 349 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 349 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 350 350 (journal_root / "facets" / "personal").mkdir(parents=True) 351 351 (journal_root / "facets" / "personal" / "facet.json").write_text( 352 352 '{"title": "Personal"}', encoding="utf-8" ··· 371 371 372 372 373 373 def test_upcoming_when_no_future_todos(monkeypatch, journal_root): 374 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 374 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 375 375 (journal_root / "facets" / "personal").mkdir(parents=True) 376 376 377 377 _write_todos( ··· 389 389 390 390 391 391 def test_upcoming_filters_by_facet(monkeypatch, journal_root): 392 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 392 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 393 393 # Create multiple facets 394 394 for facet_name in ["personal", "work"]: 395 395 facet_dir = journal_root / "facets" / facet_name ··· 409 409 410 410 411 411 def test_upcoming_aggregates_all_facets(monkeypatch, journal_root): 412 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 412 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 413 413 # Create multiple facets 414 414 for facet_name in ["personal", "work"]: 415 415 facet_dir = journal_root / "facets" / facet_name ··· 431 431 432 432 def test_checklist_append_entry(monkeypatch, journal_root): 433 433 """Test TodoChecklist.append_entry() creates valid JSONL.""" 434 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 434 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 435 435 436 436 # Create facet directory 437 437 facets_dir = journal_root / "facets" / "work" ··· 454 454 455 455 def test_checklist_cancel_entry(monkeypatch, journal_root): 456 456 """Test TodoChecklist.cancel_entry() soft-deletes items.""" 457 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 457 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 458 458 459 459 _write_todos( 460 460 journal_root, ··· 479 479 monkeypatch, journal_root 480 480 ): 481 481 """Test TodoChecklist.display() always includes cancelled items with strikethrough.""" 482 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 482 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 483 483 484 484 _write_todos( 485 485 journal_root, ··· 502 502 503 503 504 504 def test_get_facets_with_todos(monkeypatch, journal_root): 505 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 505 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 506 506 507 507 # Create todos in multiple facets 508 508 _write_todos(journal_root, "personal", "20240105", [{"text": "Personal task"}]) ··· 527 527 """Test that timestamps are set when creating a new todo.""" 528 528 import time 529 529 530 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 530 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 531 531 532 532 # Create facet directory 533 533 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) ··· 548 548 """Test that updated_at changes when marking todo complete.""" 549 549 import time 550 550 551 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 551 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 552 552 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 553 553 554 554 checklist = TodoChecklist.load("20240110", "personal") ··· 568 568 """Test that updated_at changes when marking todo incomplete.""" 569 569 import time 570 570 571 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 571 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 572 572 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 573 573 574 574 checklist = TodoChecklist.load("20240110", "personal") ··· 593 593 """Test that updated_at changes when cancelling a todo.""" 594 594 import time 595 595 596 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 596 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 597 597 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 598 598 599 599 checklist = TodoChecklist.load("20240110", "personal") ··· 613 613 """Test that updated_at changes when updating todo text.""" 614 614 import time 615 615 616 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 616 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 617 617 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 618 618 619 619 checklist = TodoChecklist.load("20240110", "personal") ··· 631 631 632 632 def test_todo_item_timestamps_serialization(monkeypatch, journal_root): 633 633 """Test that timestamps are properly serialized to and from JSONL.""" 634 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 634 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 635 635 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 636 636 637 637 checklist = TodoChecklist.load("20240110", "personal") ··· 653 653 654 654 def test_todo_item_timestamps_in_as_dict(monkeypatch, journal_root): 655 655 """Test that timestamps are included in as_dict() output.""" 656 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 656 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 657 657 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 658 658 659 659 checklist = TodoChecklist.load("20240110", "personal") ··· 668 668 669 669 def test_todo_item_backward_compatibility_no_timestamps(monkeypatch, journal_root): 670 670 """Test loading files without timestamps (backward compatibility).""" 671 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 671 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 672 672 673 673 # Write old-format todos without timestamps 674 674 _write_todos( ··· 695 695 """Test that append_entry can preserve a provided created_at timestamp.""" 696 696 import time 697 697 698 - monkeypatch.setenv("JOURNAL_PATH", str(journal_root)) 698 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 699 699 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 700 700 701 701 checklist = TodoChecklist.load("20240110", "personal")
+3 -2
apps/transcripts/tests/conftest.py
··· 10 10 11 11 @pytest.fixture(autouse=True) 12 12 def _journal_env(monkeypatch): 13 - """Point JOURNAL_PATH at the test fixtures.""" 13 + """Point _SOLSTONE_JOURNAL_OVERRIDE at the test fixtures.""" 14 14 monkeypatch.setenv( 15 - "JOURNAL_PATH", os.path.join(os.getcwd(), "tests", "fixtures", "journal") 15 + "_SOLSTONE_JOURNAL_OVERRIDE", 16 + os.path.join(os.getcwd(), "tests", "fixtures", "journal"), 16 17 )
+3 -3
convey/bridge.py
··· 30 30 31 31 32 32 def _ensure_journal_env() -> None: 33 - if state.journal_root and not os.environ.get("JOURNAL_PATH"): 34 - os.environ["JOURNAL_PATH"] = state.journal_root 33 + if state.journal_root and not os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE"): 34 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = state.journal_root 35 35 36 36 37 37 def _broadcast_to_websockets(event: dict) -> None: ··· 71 71 if _CALLOSUM_CONNECTION: 72 72 return 73 73 74 - # Ensure JOURNAL_PATH is set 74 + # Ensure journal override is set for child processes 75 75 _ensure_journal_env() 76 76 77 77 # Create Callosum connection with callback
+3 -3
convey/restart.py
··· 7 7 from __future__ import annotations 8 8 9 9 import argparse 10 - import os 11 10 import sys 12 11 import threading 13 12 import time ··· 180 179 181 180 args = setup_cli(parser) 182 181 183 - journal_path = os.environ.get("JOURNAL_PATH", "(not set)") 184 - print(f"Journal: {journal_path}") 182 + from think.utils import get_journal 183 + 184 + print(f"Journal: {get_journal()}") 185 185 print(f"Timeout: {args.timeout}s") 186 186 187 187 success, logs = wait_for_convey_restart(timeout=args.timeout, verbose=args.verbose)
+1 -1
docs/CALLOSUM.md
··· 4 4 5 5 ## Protocol 6 6 7 - **Transport:** Unix domain socket at `$JOURNAL_PATH/health/callosum.sock` 7 + **Transport:** Unix domain socket at `journal/health/callosum.sock` 8 8 9 9 **Format:** Newline-delimited JSON. Broadcast to all connected clients. 10 10
+1 -1
docs/CORTEX.md
··· 299 299 - Keys are variable names, values are coerced to strings 300 300 - Request-level `env` overrides agent defaults 301 301 - Inherited by handoff agents unless explicitly overridden 302 - - Note: `JOURNAL_PATH` cannot be overridden (always set by Cortex) 302 + - Note: `_SOLSTONE_JOURNAL_OVERRIDE` is set by Cortex for child processes 303 303 304 304 ### Model Resolution 305 305
+25 -35
docs/DOCTOR.md
··· 2 2 3 3 Quick reference for debugging and diagnosing issues. For detailed specifications, see linked documentation. 4 4 5 - ## Prerequisites 6 - 7 - Always get the journal path first: 8 - 9 - ```bash 10 - export JOURNAL_PATH=$(grep JOURNAL_PATH .env | cut -d= -f2) 11 - ``` 12 - 13 - --- 14 - 15 5 ## Quick Health Check 16 6 17 7 ```bash ··· 19 9 pgrep -af "sol:observer|sol:sense|sol:supervisor" 20 10 21 11 # Check Callosum socket exists 22 - ls -la $JOURNAL_PATH/health/callosum.sock 12 + ls -la journal/health/callosum.sock 23 13 24 14 # Check for stuck agents (should be empty or short-lived) 25 - ls $JOURNAL_PATH/agents/*/*_active.jsonl 2>/dev/null 15 + ls journal/agents/*/*_active.jsonl 2>/dev/null 26 16 ``` 27 17 28 18 **Healthy state:** ··· 53 43 54 44 | What | Where | 55 45 |------|-------| 56 - | Current service logs | `$JOURNAL_PATH/health/{service}.log` (symlinks) | 57 - | Day's process logs | `$JOURNAL_PATH/{YYYYMMDD}/health/{ref}_{name}.log` | 58 - | Agent execution | `$JOURNAL_PATH/agents/<name>/*.jsonl` | 59 - | Journal task log | `$JOURNAL_PATH/task_log.txt` | 46 + | Current service logs | `journal/health/{service}.log` (symlinks) | 47 + | Day's process logs | `journal/{YYYYMMDD}/health/{ref}_{name}.log` | 48 + | Agent execution | `journal/agents/<name>/*.jsonl` | 49 + | Journal task log | `journal/task_log.txt` | 60 50 61 51 **Symlink structure:** Journal-level symlinks point to current day's logs. Day-level symlinks point to current process instance (by ref). 62 52 63 53 ```bash 64 54 # Tail current observer log 65 - tail -f $JOURNAL_PATH/health/observer.log 55 + tail -f journal/health/observer.log 66 56 67 57 # Find today's logs 68 - ls -la $JOURNAL_PATH/$(date +%Y%m%d)/health/ 58 + ls -la journal/$(date +%Y%m%d)/health/ 69 59 ``` 70 60 71 61 --- ··· 99 89 100 90 ## Reading Agent Files 101 91 102 - **Location:** `$JOURNAL_PATH/agents/` 92 + **Location:** `journal/agents/` 103 93 104 94 **File states:** 105 95 - `{name}/{timestamp}_active.jsonl` - Agent currently running ··· 115 105 116 106 ```bash 117 107 # View an agent's final result 118 - jq -r 'select(.event=="finish") | .result' $JOURNAL_PATH/agents/default/1234567890123.jsonl 108 + jq -r 'select(.event=="finish") | .result' journal/agents/default/1234567890123.jsonl 119 109 120 110 # List today's agents with their prompts 121 - for id in $(jq -r '.agent_id' $JOURNAL_PATH/agents/$(date +%Y%m%d).jsonl 2>/dev/null); do 122 - f=$(find $JOURNAL_PATH/agents -maxdepth 2 -path "*/${id}.jsonl" -print -quit) 111 + for id in $(jq -r '.agent_id' journal/agents/$(date +%Y%m%d).jsonl 2>/dev/null); do 112 + f=$(find journal/agents -maxdepth 2 -path "*/${id}.jsonl" -print -quit) 123 113 [ -n "$f" ] || continue 124 114 echo "=== $(basename "$f") ===" 125 115 head -1 "$f" | jq -r '.prompt[:80]' ··· 136 126 137 127 ```bash 138 128 # Check observer log for errors 139 - tail -50 $JOURNAL_PATH/health/observer.log | grep -i error 129 + tail -50 journal/health/observer.log | grep -i error 140 130 141 131 # Check if observer is emitting status (supervisor.status will show stale_heartbeats) 142 132 # Health is derived from observe.status Callosum events ··· 148 138 149 139 ```bash 150 140 # Find active agents 151 - ls -la $JOURNAL_PATH/agents/*/*_active.jsonl 141 + ls -la journal/agents/*/*_active.jsonl 152 142 153 143 # Check last event in active agent 154 - tail -1 $JOURNAL_PATH/agents/*/*_active.jsonl | jq . 144 + tail -1 journal/agents/*/*_active.jsonl | jq . 155 145 ``` 156 146 157 147 Causes: Backend timeout, tool hanging, network issues. ··· 160 150 161 151 ```bash 162 152 # Verify socket exists 163 - ls -la $JOURNAL_PATH/health/callosum.sock 153 + ls -la journal/health/callosum.sock 164 154 165 155 # Check supervisor is running 166 156 pgrep -af sol:supervisor ··· 172 162 173 163 ```bash 174 164 # Check sense log for queue status 175 - grep -i "queue" $JOURNAL_PATH/health/sense.log | tail -10 165 + grep -i "queue" journal/health/sense.log | tail -10 176 166 ``` 177 167 178 168 Causes: Slow transcription, describe API rate limits. ··· 183 173 184 174 ```bash 185 175 # Watch all service logs 186 - tail -f $JOURNAL_PATH/health/*.log 176 + tail -f journal/health/*.log 187 177 188 178 # Count today's agents by status 189 - echo "Completed: $([ -f $JOURNAL_PATH/agents/$(date +%Y%m%d).jsonl ] && wc -l < $JOURNAL_PATH/agents/$(date +%Y%m%d).jsonl || echo 0)" 190 - echo "Running: $(ls $JOURNAL_PATH/agents/*/*_active.jsonl 2>/dev/null | wc -l)" 179 + echo "Completed: $([ -f journal/agents/$(date +%Y%m%d).jsonl ] && wc -l < journal/agents/$(date +%Y%m%d).jsonl || echo 0)" 180 + echo "Running: $(ls journal/agents/*/*_active.jsonl 2>/dev/null | wc -l)" 191 181 192 182 # Find agents that errored today 193 - jq -r 'select(.status=="error") | .agent_id' $JOURNAL_PATH/agents/$(date +%Y%m%d).jsonl 2>/dev/null 183 + jq -r 'select(.status=="error") | .agent_id' journal/agents/$(date +%Y%m%d).jsonl 2>/dev/null 194 184 195 185 # Check token usage for today 196 - wc -l $JOURNAL_PATH/tokens/$(date +%Y%m%d).jsonl 186 + wc -l journal/tokens/$(date +%Y%m%d).jsonl 197 187 198 188 # Find errors in today's logs 199 - grep -i error $JOURNAL_PATH/$(date +%Y%m%d)/health/*.log 189 + grep -i error journal/$(date +%Y%m%d)/health/*.log 200 190 201 191 # Watch Callosum events in real-time 202 - socat - UNIX-CONNECT:$JOURNAL_PATH/health/callosum.sock 192 + socat - UNIX-CONNECT:journal/health/callosum.sock 203 193 ``` 204 194 205 195 --- ··· 216 206 217 207 ```bash 218 208 # Confirm the issue — should report "moov atom not found" 219 - ffprobe -v error $JOURNAL_PATH/YYYYMMDD/STREAM/SEGMENT/center_1_screen.mov 209 + ffprobe -v error journal/YYYYMMDD/STREAM/SEGMENT/center_1_screen.mov 220 210 221 211 # Inspect atom structure (moov should be present but isn't) 222 212 python3 -c "
+5 -22
docs/INSTALL.md
··· 72 72 cp .env.example .env 73 73 ``` 74 74 75 - 3. (Optional) Set a custom journal path: 76 - 77 - Set `JOURNAL_PATH` in your shell profile (e.g. `~/.bashrc`, `~/.zshrc`) or in `.env`: 78 - 79 - ``` 80 - JOURNAL_PATH=~/Documents/journal 81 - ``` 82 - 83 - **Important:** `JOURNAL_PATH` must be set in your shell environment or `.env` file — 84 - not in `journal.json` or any other config file. 85 - 86 - If not set, solstone automatically uses the platform-specific default: 87 - - Linux: `~/.local/share/solstone/journal` 88 - - macOS: `~/Library/Application Support/solstone/journal` 89 - 90 - When using the default, a reminder will be shown pointing back here. 91 - 92 - The journal directory is created automatically on first use. 75 + 3. Your journal lives at `journal/` inside the solstone directory. It's created automatically on first run. 93 76 94 77 --- 95 78 ··· 192 175 Create the config file: 193 176 194 177 ```bash 195 - mkdir -p $JOURNAL_PATH/config 196 - cat > $JOURNAL_PATH/config/journal.json << 'EOF' 178 + mkdir -p journal/config 179 + cat > journal/config/journal.json << 'EOF' 197 180 { 198 181 "convey": { 199 182 "password": "your-password-here" ··· 232 215 pgrep -af "sol:observer|sol:sense|sol:supervisor" 233 216 234 217 # Check Callosum socket exists 235 - ls -la $JOURNAL_PATH/health/callosum.sock 218 + ls -la journal/health/callosum.sock 236 219 237 220 # View service logs 238 - tail -f $JOURNAL_PATH/health/*.log 221 + tail -f journal/health/*.log 239 222 ``` 240 223 241 224 See [DOCTOR.md](DOCTOR.md) for troubleshooting.
+1 -1
docs/OBSERVE.md
··· 73 73 74 74 ## Configuration 75 75 76 - Requires `JOURNAL_PATH` environment variable. API keys for transcription/vision services configured in `.env`. 76 + Requires the journal directory at project root. API keys for transcription/vision services configured in `.env`.
+2 -2
docs/THINK.md
··· 34 34 Use `--refresh` to overwrite existing files, and `-v` for verbose logs. 35 35 36 36 Set `GOOGLE_API_KEY` before running any command that contacts Gemini. 37 - `JOURNAL_PATH` and `GOOGLE_API_KEY` can also be provided in a `.env` file which 37 + `GOOGLE_API_KEY` can also be provided in a `.env` file which 38 38 is loaded automatically by most commands. 39 39 40 40 ## Service Discovery ··· 161 161 ``` 162 162 163 163 The provider can be ``openai`` (default), ``google`` or ``anthropic``. Set the corresponding API key environment variable (`OPENAI_API_KEY`, 164 - `GOOGLE_API_KEY` or `ANTHROPIC_API_KEY`) along with `JOURNAL_PATH`. 164 + `GOOGLE_API_KEY` or `ANTHROPIC_API_KEY`). 165 165 166 166 ### Provider modules 167 167
-1
pyproject.toml
··· 67 67 "tzlocal", 68 68 "python-slugify", 69 69 "rapidfuzz", 70 - "platformdirs", 71 70 "typer", 72 71 73 72 # Audio processing
+1 -1
sol.py
··· 154 154 """Print current journal status.""" 155 155 status = get_status() 156 156 157 - print(f"JOURNAL_PATH={status['journal_path']} ({status['journal_source']})") 157 + print(f"Journal: {status['journal_path']}") 158 158 if status["journal_exists"]: 159 159 # Count day directories 160 160 journal = status["journal_path"]
+4 -4
tests/conftest.py
··· 14 14 15 15 @pytest.fixture(autouse=True) 16 16 def set_test_journal_path(request, monkeypatch): 17 - """Set JOURNAL_PATH to tests/fixtures/journal for all unit tests. 17 + """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for all unit tests. 18 18 19 - This ensures all tests have a valid JOURNAL_PATH without needing 19 + This ensures all tests have a valid _SOLSTONE_JOURNAL_OVERRIDE without needing 20 20 to explicitly set it in each test. Integration tests are excluded. 21 21 """ 22 22 # Skip for integration tests - they may have different requirements 23 23 if "integration" in request.node.keywords: 24 24 return 25 25 26 - # Set JOURNAL_PATH to tests/fixtures/journal for all unit tests 27 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 26 + # Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for all unit tests 27 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 28 28 29 29 30 30 @pytest.fixture(autouse=True)
+4 -4
tests/integration/conftest.py
··· 53 53 def integration_journal_path(tmp_path_factory): 54 54 """Create a temporary journal path for integration tests.""" 55 55 journal_dir = tmp_path_factory.mktemp("integration_journal") 56 - old_path = os.environ.get("JOURNAL_PATH") 57 - os.environ["JOURNAL_PATH"] = str(journal_dir) 56 + old_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 57 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal_dir) 58 58 yield journal_dir 59 59 if old_path: 60 - os.environ["JOURNAL_PATH"] = old_path 60 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = old_path 61 61 else: 62 - os.environ.pop("JOURNAL_PATH", None) 62 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 63 63 64 64 65 65 @pytest.fixture
+5 -5
tests/integration/test_anthropic_provider.py
··· 25 25 load_dotenv(fixtures_env, override=True) 26 26 27 27 api_key = os.getenv("ANTHROPIC_API_KEY") 28 - journal_path = os.getenv("JOURNAL_PATH") 28 + journal_path = os.getenv("_SOLSTONE_JOURNAL_OVERRIDE") 29 29 30 30 return fixtures_env, api_key, journal_path 31 31 ··· 44 44 pytest.skip("ANTHROPIC_API_KEY not found in tests/fixtures/.env file") 45 45 46 46 if not journal_path: 47 - pytest.skip("JOURNAL_PATH not found in tests/fixtures/.env file") 47 + pytest.skip("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 48 48 49 49 # Prepare environment 50 50 env = os.environ.copy() 51 - env["JOURNAL_PATH"] = journal_path 51 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 52 52 env["ANTHROPIC_API_KEY"] = api_key 53 53 54 54 # Create NDJSON input (no tool config) ··· 152 152 pytest.skip("ANTHROPIC_API_KEY not found in tests/fixtures/.env file") 153 153 154 154 if not journal_path: 155 - pytest.skip("JOURNAL_PATH not found in tests/fixtures/.env file") 155 + pytest.skip("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 156 156 157 157 # Prepare environment 158 158 env = os.environ.copy() 159 - env["JOURNAL_PATH"] = journal_path 159 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 160 160 env["ANTHROPIC_API_KEY"] = api_key 161 161 162 162 # Create NDJSON input with thinking config
+1 -1
tests/integration/test_batch.py
··· 226 226 async def test_batch_token_logging(): 227 227 """Test that token logging works with batch execution.""" 228 228 with tempfile.TemporaryDirectory() as tmpdir: 229 - os.environ["JOURNAL_PATH"] = tmpdir 229 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 230 230 231 231 batch = Batch(max_concurrent=2) 232 232
+3 -3
tests/integration/test_callosum.py
··· 20 20 """Set up a temporary journal path.""" 21 21 journal = tmp_path / "journal" 22 22 journal.mkdir() 23 - os.environ["JOURNAL_PATH"] = str(journal) 23 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 24 24 yield journal 25 25 # Cleanup 26 - if "JOURNAL_PATH" in os.environ: 27 - del os.environ["JOURNAL_PATH"] 26 + if "_SOLSTONE_JOURNAL_OVERRIDE" in os.environ: 27 + del os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 28 28 29 29 30 30 @pytest.fixture
+5 -5
tests/integration/test_cortex.py
··· 19 19 @pytest.fixture 20 20 def callosum_server(integration_journal_path): 21 21 """Start a Callosum server for integration testing.""" 22 - os.environ["JOURNAL_PATH"] = str(integration_journal_path) 22 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 23 23 24 24 server = CallosumServer() 25 25 server_thread = threading.Thread(target=server.start, daemon=True) ··· 60 60 @pytest.mark.integration 61 61 def test_cortex_request_creation(integration_journal_path, callosum_server): 62 62 """Test creating a Cortex agent request via Callosum.""" 63 - os.environ["JOURNAL_PATH"] = str(integration_journal_path) 63 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 64 64 65 65 # Listen for broadcasts 66 66 received_messages = [] ··· 93 93 @pytest.mark.integration 94 94 def test_cortex_end_to_end_with_echo_agent(integration_journal_path, callosum_server): 95 95 """Test end-to-end Cortex flow with a simple echo agent.""" 96 - os.environ["JOURNAL_PATH"] = str(integration_journal_path) 96 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 97 97 98 98 # Create a mock agent script that just echoes 99 99 agents_dir = integration_journal_path / "agents" ··· 143 143 @pytest.mark.integration 144 144 def test_cortex_agents_listing(integration_journal_path): 145 145 """Test listing agents from the cortex_agents function.""" 146 - os.environ["JOURNAL_PATH"] = str(integration_journal_path) 146 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 147 147 148 148 # Create some test agent files 149 149 agents_dir = integration_journal_path / "agents" ··· 189 189 @pytest.mark.integration 190 190 def test_cortex_error_handling(integration_journal_path, callosum_server): 191 191 """Test that Cortex handles errors gracefully.""" 192 - os.environ["JOURNAL_PATH"] = str(integration_journal_path) 192 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 193 193 194 194 # Listen for events 195 195 received_events = []
+5 -5
tests/integration/test_google_provider.py
··· 25 25 load_dotenv(fixtures_env, override=True) 26 26 27 27 api_key = os.getenv("GOOGLE_API_KEY") 28 - journal_path = os.getenv("JOURNAL_PATH") 28 + journal_path = os.getenv("_SOLSTONE_JOURNAL_OVERRIDE") 29 29 30 30 return fixtures_env, api_key, journal_path 31 31 ··· 44 44 pytest.skip("GOOGLE_API_KEY not found in tests/fixtures/.env file") 45 45 46 46 if not journal_path: 47 - pytest.skip("JOURNAL_PATH not found in tests/fixtures/.env file") 47 + pytest.skip("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 48 48 49 49 # Prepare environment 50 50 env = os.environ.copy() 51 - env["JOURNAL_PATH"] = journal_path 51 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 52 52 env["GOOGLE_API_KEY"] = api_key 53 53 54 54 # Create NDJSON input (no tool config) ··· 136 136 pytest.skip("GOOGLE_API_KEY not found in tests/fixtures/.env file") 137 137 138 138 if not journal_path: 139 - pytest.skip("JOURNAL_PATH not found in tests/fixtures/.env file") 139 + pytest.skip("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 140 140 141 141 # Prepare environment 142 142 env = os.environ.copy() 143 - env["JOURNAL_PATH"] = journal_path 143 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 144 144 env["GOOGLE_API_KEY"] = api_key 145 145 146 146 # Create NDJSON input with thinking model (if available)
+7 -7
tests/integration/test_openai_provider.py
··· 25 25 load_dotenv(fixtures_env, override=True) 26 26 27 27 api_key = os.getenv("OPENAI_API_KEY") 28 - journal_path = os.getenv("JOURNAL_PATH") 28 + journal_path = os.getenv("_SOLSTONE_JOURNAL_OVERRIDE") 29 29 30 30 return fixtures_env, api_key, journal_path 31 31 ··· 44 44 pytest.skip("OPENAI_API_KEY not found in tests/fixtures/.env file") 45 45 46 46 if not journal_path: 47 - pytest.skip("JOURNAL_PATH not found in tests/fixtures/.env file") 47 + pytest.skip("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 48 48 49 49 # Prepare environment 50 50 env = os.environ.copy() 51 - env["JOURNAL_PATH"] = journal_path 51 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 52 52 env["OPENAI_API_KEY"] = api_key 53 53 54 54 # Create NDJSON input (no tool config) ··· 144 144 pytest.skip("OPENAI_API_KEY not found in tests/fixtures/.env file") 145 145 146 146 if not journal_path: 147 - pytest.skip("JOURNAL_PATH not found in tests/fixtures/.env file") 147 + pytest.skip("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 148 148 149 149 # Prepare environment 150 150 env = os.environ.copy() 151 - env["JOURNAL_PATH"] = journal_path 151 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 152 152 env["OPENAI_API_KEY"] = api_key 153 153 154 154 # Use a prompt that encourages step-by-step reasoning ··· 229 229 pytest.skip("OPENAI_API_KEY not found in tests/fixtures/.env file") 230 230 231 231 if not journal_path: 232 - pytest.skip("JOURNAL_PATH not found in tests/fixtures/.env file") 232 + pytest.skip("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 233 233 234 234 # Prepare environment 235 235 env = os.environ.copy() 236 - env["JOURNAL_PATH"] = journal_path 236 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 237 237 env["OPENAI_API_KEY"] = api_key 238 238 239 239 # Include extra_context like get_agent() does in production
+2 -2
tests/test_action_logging.py
··· 15 15 16 16 @pytest.fixture 17 17 def test_facet(tmp_path, monkeypatch): 18 - """Set up a test facet with JOURNAL_PATH.""" 18 + """Set up a test facet with _SOLSTONE_JOURNAL_OVERRIDE.""" 19 19 journal = tmp_path / "journal" 20 20 journal.mkdir() 21 21 facet_path = journal / "facets" / "test_facet" ··· 27 27 json.dumps({"title": "Test Facet", "description": "Test"}), encoding="utf-8" 28 28 ) 29 29 30 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 30 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 31 31 return journal, "test_facet" 32 32 33 33
+53 -53
tests/test_activities.py
··· 66 66 assert "email" in always_on_ids 67 67 68 68 with tempfile.TemporaryDirectory() as tmpdir: 69 - original_path = os.environ.get("JOURNAL_PATH") 70 - os.environ["JOURNAL_PATH"] = tmpdir 69 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 70 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 71 71 72 72 facet_path = Path(tmpdir) / "facets" / "test_facet" 73 73 facet_path.mkdir(parents=True) ··· 88 88 89 89 finally: 90 90 if original_path: 91 - os.environ["JOURNAL_PATH"] = original_path 91 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 92 92 93 93 94 94 def test_generate_activity_id(): ··· 132 132 from think.activities import DEFAULT_ACTIVITIES, get_facet_activities 133 133 134 134 with tempfile.TemporaryDirectory() as tmpdir: 135 - original_path = os.environ.get("JOURNAL_PATH") 136 - os.environ["JOURNAL_PATH"] = tmpdir 135 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 136 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 137 137 138 138 facet_path = Path(tmpdir) / "facets" / "new_facet" 139 139 facet_path.mkdir(parents=True) ··· 152 152 153 153 finally: 154 154 if original_path: 155 - os.environ["JOURNAL_PATH"] = original_path 155 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 156 156 157 157 158 158 def test_configured_facet_includes_meeting_always_on(): ··· 160 160 from think.activities import get_facet_activities, save_facet_activities 161 161 162 162 with tempfile.TemporaryDirectory() as tmpdir: 163 - original_path = os.environ.get("JOURNAL_PATH") 164 - os.environ["JOURNAL_PATH"] = tmpdir 163 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 164 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 165 165 166 166 facet_path = Path(tmpdir) / "facets" / "work" 167 167 facet_path.mkdir(parents=True) ··· 180 180 181 181 finally: 182 182 if original_path: 183 - os.environ["JOURNAL_PATH"] = original_path 183 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 184 184 185 185 186 186 def test_facet_activities_roundtrip(): ··· 194 194 195 195 # Create a temp journal 196 196 with tempfile.TemporaryDirectory() as tmpdir: 197 - # Temporarily override JOURNAL_PATH 198 - original_path = os.environ.get("JOURNAL_PATH") 199 - os.environ["JOURNAL_PATH"] = tmpdir 197 + # Temporarily override _SOLSTONE_JOURNAL_OVERRIDE 198 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 199 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 200 200 201 201 # Create facet directory 202 202 facet_path = Path(tmpdir) / "facets" / "test_facet" ··· 262 262 263 263 finally: 264 264 if original_path: 265 - os.environ["JOURNAL_PATH"] = original_path 265 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 266 266 267 267 268 268 def test_add_activity_to_facet(): ··· 274 274 ) 275 275 276 276 with tempfile.TemporaryDirectory() as tmpdir: 277 - original_path = os.environ.get("JOURNAL_PATH") 278 - os.environ["JOURNAL_PATH"] = tmpdir 277 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 278 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 279 279 280 280 facet_path = Path(tmpdir) / "facets" / "test_facet" 281 281 facet_path.mkdir(parents=True) ··· 335 335 336 336 finally: 337 337 if original_path: 338 - os.environ["JOURNAL_PATH"] = original_path 338 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 339 339 340 340 341 341 def test_update_activity_in_facet(): ··· 347 347 ) 348 348 349 349 with tempfile.TemporaryDirectory() as tmpdir: 350 - original_path = os.environ.get("JOURNAL_PATH") 351 - os.environ["JOURNAL_PATH"] = tmpdir 350 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 351 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 352 352 353 353 facet_path = Path(tmpdir) / "facets" / "test_facet" 354 354 facet_path.mkdir(parents=True) ··· 420 420 421 421 finally: 422 422 if original_path: 423 - os.environ["JOURNAL_PATH"] = original_path 423 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 424 424 425 425 426 426 def test_format_activities_context_includes_instructions(): ··· 428 428 from think.activities import save_facet_activities 429 429 430 430 with tempfile.TemporaryDirectory() as tmpdir: 431 - original_path = os.environ.get("JOURNAL_PATH") 432 - os.environ["JOURNAL_PATH"] = tmpdir 431 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 432 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 433 433 434 434 facet_path = Path(tmpdir) / "facets" / "test_facet" 435 435 facet_path.mkdir(parents=True) ··· 463 463 464 464 finally: 465 465 if original_path: 466 - os.environ["JOURNAL_PATH"] = original_path 466 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 467 467 468 468 469 469 # --------------------------------------------------------------------------- ··· 512 512 from think.activities import append_activity_record, load_activity_records 513 513 514 514 with tempfile.TemporaryDirectory() as tmpdir: 515 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 515 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 516 516 517 517 record = { 518 518 "id": "coding_100000_300", ··· 534 534 from think.activities import append_activity_record, load_activity_records 535 535 536 536 with tempfile.TemporaryDirectory() as tmpdir: 537 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 537 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 538 538 539 539 record = { 540 540 "id": "coding_100000_300", ··· 553 553 from think.activities import load_activity_records 554 554 555 555 with tempfile.TemporaryDirectory() as tmpdir: 556 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 556 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 557 557 assert load_activity_records("work", "20260209") == [] 558 558 559 559 def test_update_description(self, monkeypatch): ··· 564 564 ) 565 565 566 566 with tempfile.TemporaryDirectory() as tmpdir: 567 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 567 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 568 568 569 569 record = { 570 570 "id": "coding_100000_300", ··· 587 587 from think.activities import update_record_description 588 588 589 589 with tempfile.TemporaryDirectory() as tmpdir: 590 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 590 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 591 591 assert ( 592 592 update_record_description("work", "20260209", "nonexistent", "desc") 593 593 is False ··· 601 601 ) 602 602 603 603 with tempfile.TemporaryDirectory() as tmpdir: 604 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 604 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 605 605 606 606 r1 = { 607 607 "id": "coding_100000_300", ··· 664 664 from muse.activities import _list_facets_with_activity_state 665 665 666 666 with tempfile.TemporaryDirectory() as tmpdir: 667 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 667 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 668 668 669 669 _setup_segment(tmpdir, "20260209", "100000_300", "personal", []) 670 670 _setup_segment(tmpdir, "20260209", "100000_300", "work", []) ··· 678 678 from muse.activities import _list_facets_with_activity_state 679 679 680 680 with tempfile.TemporaryDirectory() as tmpdir: 681 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 681 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 682 682 assert ( 683 683 _list_facets_with_activity_state( 684 684 "20260209", "100000_300", stream="default" ··· 769 769 from muse.activities import _walk_activity_segments 770 770 771 771 with tempfile.TemporaryDirectory() as tmpdir: 772 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 772 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 773 773 774 774 _setup_segment( 775 775 tmpdir, ··· 817 817 from muse.activities import _walk_activity_segments 818 818 819 819 with tempfile.TemporaryDirectory() as tmpdir: 820 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 820 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 821 821 822 822 _setup_segment( 823 823 tmpdir, ··· 860 860 from muse.activities import _walk_activity_segments 861 861 862 862 with tempfile.TemporaryDirectory() as tmpdir: 863 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 863 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 864 864 (Path(tmpdir) / "20260209").mkdir() 865 865 866 866 result = _walk_activity_segments( ··· 876 876 from muse.activities import pre_process 877 877 878 878 with tempfile.TemporaryDirectory() as tmpdir: 879 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 879 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 880 880 881 881 day_dir = Path(tmpdir) / "20260209" 882 882 day_dir.mkdir() ··· 892 892 from muse.activities import pre_process 893 893 894 894 with tempfile.TemporaryDirectory() as tmpdir: 895 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 895 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 896 896 897 897 _setup_segment( 898 898 tmpdir, ··· 934 934 from think.activities import load_activity_records 935 935 936 936 with tempfile.TemporaryDirectory() as tmpdir: 937 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 937 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 938 938 939 939 _setup_segment( 940 940 tmpdir, ··· 972 972 from think.activities import load_activity_records 973 973 974 974 with tempfile.TemporaryDirectory() as tmpdir: 975 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 975 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 976 976 977 977 _setup_segment( 978 978 tmpdir, ··· 1003 1003 from think.activities import load_activity_records 1004 1004 1005 1005 with tempfile.TemporaryDirectory() as tmpdir: 1006 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1006 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1007 1007 1008 1008 _setup_segment( 1009 1009 tmpdir, ··· 1058 1058 from think.activities import load_activity_records 1059 1059 1060 1060 with tempfile.TemporaryDirectory() as tmpdir: 1061 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1061 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1062 1062 1063 1063 _setup_segment( 1064 1064 tmpdir, ··· 1131 1131 from think.activities import append_activity_record, load_activity_records 1132 1132 1133 1133 with tempfile.TemporaryDirectory() as tmpdir: 1134 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1134 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1135 1135 1136 1136 record = { 1137 1137 "id": "coding_100000_300", ··· 1177 1177 from muse.activities import post_process 1178 1178 1179 1179 with tempfile.TemporaryDirectory() as tmpdir: 1180 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1180 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1181 1181 1182 1182 result = post_process("{}", {"day": "20260209"}) 1183 1183 assert result is None ··· 1207 1207 from muse.activities import pre_process 1208 1208 1209 1209 with tempfile.TemporaryDirectory() as tmpdir: 1210 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1210 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1211 1211 1212 1212 _setup_segment( 1213 1213 tmpdir, ··· 1251 1251 from muse.activities import pre_process 1252 1252 1253 1253 with tempfile.TemporaryDirectory() as tmpdir: 1254 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1254 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1255 1255 1256 1256 _setup_segment( 1257 1257 tmpdir, ··· 1303 1303 from think.activities import append_activity_record 1304 1304 1305 1305 with tempfile.TemporaryDirectory() as tmpdir: 1306 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1306 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1307 1307 1308 1308 record = { 1309 1309 "id": "coding_100000_300", ··· 1366 1366 from muse.activities import post_process 1367 1367 1368 1368 with tempfile.TemporaryDirectory() as tmpdir: 1369 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1369 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1370 1370 1371 1371 meta = { 1372 1372 "activity_records": { ··· 1404 1404 from muse.activities import post_process 1405 1405 1406 1406 with tempfile.TemporaryDirectory() as tmpdir: 1407 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1407 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1408 1408 1409 1409 with patch("muse.activities.callosum_send") as mock_send: 1410 1410 post_process("{}", {"day": "20260209", "segment": "100500_300"}) ··· 1416 1416 from muse.activities import post_process 1417 1417 1418 1418 with tempfile.TemporaryDirectory() as tmpdir: 1419 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1419 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1420 1420 1421 1421 meta = { 1422 1422 "activity_records": { ··· 1560 1560 from think.activities import load_activity_records 1561 1561 1562 1562 with tempfile.TemporaryDirectory() as tmpdir: 1563 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1563 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1564 1564 1565 1565 # Set up a segment with an active activity (no following segment) 1566 1566 _setup_segment( ··· 1604 1604 from muse.activities import pre_process 1605 1605 1606 1606 with tempfile.TemporaryDirectory() as tmpdir: 1607 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1607 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1608 1608 1609 1609 # Set up a segment with only ended activities 1610 1610 _setup_segment( ··· 1635 1635 from muse.activities import pre_process 1636 1636 1637 1637 with tempfile.TemporaryDirectory() as tmpdir: 1638 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1638 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1639 1639 1640 1640 # Create segment dir but no activity_state files 1641 1641 seg_dir = Path(tmpdir) / "20260209" / "default" / "100000_300" ··· 1656 1656 from think.activities import load_activity_records 1657 1657 1658 1658 with tempfile.TemporaryDirectory() as tmpdir: 1659 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1659 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1660 1660 1661 1661 _setup_segment( 1662 1662 tmpdir, ··· 1711 1711 from think.activities import load_activity_records 1712 1712 1713 1713 with tempfile.TemporaryDirectory() as tmpdir: 1714 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1714 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1715 1715 1716 1716 _setup_segment( 1717 1717 tmpdir, ··· 1745 1745 from muse.activities import pre_process 1746 1746 1747 1747 with tempfile.TemporaryDirectory() as tmpdir: 1748 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 1748 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1749 1749 1750 1750 _setup_segment( 1751 1751 tmpdir,
+72 -72
tests/test_activity_state.py
··· 49 49 from muse.activity_state import find_previous_segment 50 50 51 51 with tempfile.TemporaryDirectory() as tmpdir: 52 - original_path = os.environ.get("JOURNAL_PATH") 53 - os.environ["JOURNAL_PATH"] = tmpdir 52 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 53 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 54 54 55 55 try: 56 56 # Create day directory with segments ··· 67 67 68 68 finally: 69 69 if original_path: 70 - os.environ["JOURNAL_PATH"] = original_path 70 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 71 71 72 72 def test_returns_none_for_nonexistent_day(self): 73 73 from muse.activity_state import find_previous_segment 74 74 75 75 with tempfile.TemporaryDirectory() as tmpdir: 76 - original_path = os.environ.get("JOURNAL_PATH") 77 - os.environ["JOURNAL_PATH"] = tmpdir 76 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 77 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 78 78 79 79 try: 80 80 assert find_previous_segment("20260130", "100000_300") is None 81 81 finally: 82 82 if original_path: 83 - os.environ["JOURNAL_PATH"] = original_path 83 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 84 84 85 85 def test_handles_segments_with_suffix(self): 86 86 from muse.activity_state import find_previous_segment 87 87 88 88 with tempfile.TemporaryDirectory() as tmpdir: 89 - original_path = os.environ.get("JOURNAL_PATH") 90 - os.environ["JOURNAL_PATH"] = tmpdir 89 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 90 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 91 91 92 92 try: 93 93 day_dir = Path(tmpdir) / "20260130" ··· 103 103 104 104 finally: 105 105 if original_path: 106 - os.environ["JOURNAL_PATH"] = original_path 106 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 107 107 108 108 109 109 class TestCheckTimeout: ··· 137 137 from muse.activity_state import load_previous_state 138 138 139 139 with tempfile.TemporaryDirectory() as tmpdir: 140 - original_path = os.environ.get("JOURNAL_PATH") 141 - os.environ["JOURNAL_PATH"] = tmpdir 140 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 141 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 142 142 143 143 try: 144 144 # Create state file (new flat format) ··· 168 168 169 169 finally: 170 170 if original_path: 171 - os.environ["JOURNAL_PATH"] = original_path 171 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 172 172 173 173 def test_returns_none_for_missing_file(self): 174 174 from muse.activity_state import load_previous_state 175 175 176 176 with tempfile.TemporaryDirectory() as tmpdir: 177 - original_path = os.environ.get("JOURNAL_PATH") 178 - os.environ["JOURNAL_PATH"] = tmpdir 177 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 178 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 179 179 180 180 try: 181 181 segment_dir = Path(tmpdir) / "20260130" / "default" / "100000_300" ··· 190 190 191 191 finally: 192 192 if original_path: 193 - os.environ["JOURNAL_PATH"] = original_path 193 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 194 194 195 195 def test_rejects_non_array(self): 196 196 from muse.activity_state import load_previous_state 197 197 198 198 with tempfile.TemporaryDirectory() as tmpdir: 199 - original_path = os.environ.get("JOURNAL_PATH") 200 - os.environ["JOURNAL_PATH"] = tmpdir 199 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 200 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 201 201 202 202 try: 203 203 segment_dir = Path(tmpdir) / "20260130" / "default" / "100000_300" ··· 217 217 218 218 finally: 219 219 if original_path: 220 - os.environ["JOURNAL_PATH"] = original_path 220 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 221 221 222 222 223 223 class TestFormatActivitiesContext: ··· 227 227 from muse.activity_state import format_activities_context 228 228 229 229 with tempfile.TemporaryDirectory() as tmpdir: 230 - original_path = os.environ.get("JOURNAL_PATH") 231 - os.environ["JOURNAL_PATH"] = tmpdir 230 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 231 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 232 232 233 233 try: 234 234 # Create facet with activities ··· 251 251 252 252 finally: 253 253 if original_path: 254 - os.environ["JOURNAL_PATH"] = original_path 254 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 255 255 256 256 def test_handles_empty_activities(self): 257 257 """Facet with no activities.jsonl still gets always-on defaults.""" 258 258 from muse.activity_state import format_activities_context 259 259 260 260 with tempfile.TemporaryDirectory() as tmpdir: 261 - original_path = os.environ.get("JOURNAL_PATH") 262 - os.environ["JOURNAL_PATH"] = tmpdir 261 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 262 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 263 263 264 264 try: 265 265 # Create facet without activities ··· 274 274 275 275 finally: 276 276 if original_path: 277 - os.environ["JOURNAL_PATH"] = original_path 277 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 278 278 279 279 280 280 class TestFormatPreviousState: ··· 350 350 from muse.activity_state import pre_process 351 351 352 352 with tempfile.TemporaryDirectory() as tmpdir: 353 - original_path = os.environ.get("JOURNAL_PATH") 354 - os.environ["JOURNAL_PATH"] = tmpdir 353 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 354 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 355 355 356 356 try: 357 357 # Create day and segments ··· 411 411 412 412 finally: 413 413 if original_path: 414 - os.environ["JOURNAL_PATH"] = original_path 414 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 415 415 416 416 def test_returns_none_without_day(self): 417 417 from muse.activity_state import pre_process ··· 471 471 from muse.activity_state import post_process 472 472 473 473 with tempfile.TemporaryDirectory() as tmpdir: 474 - original_path = os.environ.get("JOURNAL_PATH") 475 - os.environ["JOURNAL_PATH"] = tmpdir 474 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 475 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 476 476 477 477 try: 478 478 day_dir = Path(tmpdir) / "20260130" ··· 523 523 524 524 finally: 525 525 if original_path: 526 - os.environ["JOURNAL_PATH"] = original_path 526 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 527 527 528 528 def test_ended_activity_copies_since(self): 529 529 from muse.activity_state import post_process 530 530 531 531 with tempfile.TemporaryDirectory() as tmpdir: 532 - original_path = os.environ.get("JOURNAL_PATH") 533 - os.environ["JOURNAL_PATH"] = tmpdir 532 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 533 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 534 534 535 535 try: 536 536 day_dir = Path(tmpdir) / "20260130" ··· 580 580 581 581 finally: 582 582 if original_path: 583 - os.environ["JOURNAL_PATH"] = original_path 583 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 584 584 585 585 def test_no_previous_state_continuing_becomes_new(self): 586 586 from muse.activity_state import post_process ··· 653 653 from muse.activity_state import post_process 654 654 655 655 with tempfile.TemporaryDirectory() as tmpdir: 656 - original_path = os.environ.get("JOURNAL_PATH") 657 - os.environ["JOURNAL_PATH"] = tmpdir 656 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 657 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 658 658 659 659 try: 660 660 day_dir = Path(tmpdir) / "20260130" ··· 701 701 702 702 finally: 703 703 if original_path: 704 - os.environ["JOURNAL_PATH"] = original_path 704 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 705 705 706 706 def test_unmatched_ended_novel_desc_with_prev_ended_becomes_active(self): 707 707 """Ended activity with novel description (different from prev ended) ··· 709 709 from muse.activity_state import post_process 710 710 711 711 with tempfile.TemporaryDirectory() as tmpdir: 712 - original_path = os.environ.get("JOURNAL_PATH") 713 - os.environ["JOURNAL_PATH"] = tmpdir 712 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 713 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 714 714 715 715 try: 716 716 day_dir = Path(tmpdir) / "20260130" ··· 759 759 760 760 finally: 761 761 if original_path: 762 - os.environ["JOURNAL_PATH"] = original_path 762 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 763 763 764 764 def test_empty_array_passthrough(self): 765 765 from muse.activity_state import post_process ··· 791 791 from muse.activity_state import post_process 792 792 793 793 with tempfile.TemporaryDirectory() as tmpdir: 794 - original_path = os.environ.get("JOURNAL_PATH") 795 - os.environ["JOURNAL_PATH"] = tmpdir 794 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 795 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 796 796 797 797 try: 798 798 day_dir = Path(tmpdir) / "20260130" ··· 853 853 854 854 finally: 855 855 if original_path: 856 - os.environ["JOURNAL_PATH"] = original_path 856 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 857 857 858 858 def test_default_level_for_new(self): 859 859 """New activity without level gets default 'medium'.""" ··· 912 912 from muse.activity_state import post_process 913 913 914 914 with tempfile.TemporaryDirectory() as tmpdir: 915 - original_path = os.environ.get("JOURNAL_PATH") 916 - os.environ["JOURNAL_PATH"] = tmpdir 915 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 916 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 917 917 918 918 try: 919 919 day_dir = Path(tmpdir) / "20260130" ··· 962 962 963 963 finally: 964 964 if original_path: 965 - os.environ["JOURNAL_PATH"] = original_path 965 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 966 966 967 967 def test_fuzzy_match_disambiguates_same_type(self): 968 968 """Multiple same-type previous activities matched by description.""" 969 969 from muse.activity_state import post_process 970 970 971 971 with tempfile.TemporaryDirectory() as tmpdir: 972 - original_path = os.environ.get("JOURNAL_PATH") 973 - os.environ["JOURNAL_PATH"] = tmpdir 972 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 973 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 974 974 975 975 try: 976 976 day_dir = Path(tmpdir) / "20260130" ··· 1026 1026 1027 1027 finally: 1028 1028 if original_path: 1029 - os.environ["JOURNAL_PATH"] = original_path 1029 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1030 1030 1031 1031 1032 1032 class TestActivityId: ··· 1054 1054 from muse.activity_state import post_process 1055 1055 1056 1056 with tempfile.TemporaryDirectory() as tmpdir: 1057 - original_path = os.environ.get("JOURNAL_PATH") 1058 - os.environ["JOURNAL_PATH"] = tmpdir 1057 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1058 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1059 1059 1060 1060 try: 1061 1061 day_dir = Path(tmpdir) / "20260130" ··· 1103 1103 1104 1104 finally: 1105 1105 if original_path: 1106 - os.environ["JOURNAL_PATH"] = original_path 1106 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1107 1107 1108 1108 def test_ended_activity_gets_id(self): 1109 1109 from muse.activity_state import post_process 1110 1110 1111 1111 with tempfile.TemporaryDirectory() as tmpdir: 1112 - original_path = os.environ.get("JOURNAL_PATH") 1113 - os.environ["JOURNAL_PATH"] = tmpdir 1112 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1113 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1114 1114 1115 1115 try: 1116 1116 day_dir = Path(tmpdir) / "20260130" ··· 1157 1157 1158 1158 finally: 1159 1159 if original_path: 1160 - os.environ["JOURNAL_PATH"] = original_path 1160 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1161 1161 1162 1162 def test_promoted_ended_gets_new_id(self): 1163 1163 """Ended activity promoted to active gets id with current segment.""" ··· 1229 1229 from muse.activity_state import post_process 1230 1230 1231 1231 with tempfile.TemporaryDirectory() as tmpdir: 1232 - original_path = os.environ.get("JOURNAL_PATH") 1233 - os.environ["JOURNAL_PATH"] = tmpdir 1232 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1233 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1234 1234 1235 1235 try: 1236 1236 day_dir = Path(tmpdir) / "20260130" ··· 1283 1283 1284 1284 finally: 1285 1285 if original_path: 1286 - os.environ["JOURNAL_PATH"] = original_path 1286 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1287 1287 1288 1288 def test_no_live_event_for_ended_activity(self): 1289 1289 from unittest.mock import patch ··· 1291 1291 from muse.activity_state import post_process 1292 1292 1293 1293 with tempfile.TemporaryDirectory() as tmpdir: 1294 - original_path = os.environ.get("JOURNAL_PATH") 1295 - os.environ["JOURNAL_PATH"] = tmpdir 1294 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1295 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1296 1296 1297 1297 try: 1298 1298 day_dir = Path(tmpdir) / "20260130" ··· 1339 1339 1340 1340 finally: 1341 1341 if original_path: 1342 - os.environ["JOURNAL_PATH"] = original_path 1342 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1343 1343 1344 1344 def test_no_live_events_without_day_or_facet(self): 1345 1345 from unittest.mock import patch ··· 1404 1404 from muse.activity_state import post_process 1405 1405 1406 1406 with tempfile.TemporaryDirectory() as tmpdir: 1407 - original_path = os.environ.get("JOURNAL_PATH") 1408 - os.environ["JOURNAL_PATH"] = tmpdir 1407 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1408 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1409 1409 1410 1410 try: 1411 1411 # Create facet with only coding and meeting configured ··· 1446 1446 1447 1447 finally: 1448 1448 if original_path: 1449 - os.environ["JOURNAL_PATH"] = original_path 1449 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1450 1450 1451 1451 def test_logs_warning_on_dropped_activities(self, caplog): 1452 1452 """Post-hook logs a warning when dropping unrecognized activity IDs.""" ··· 1456 1456 from muse.activity_state import post_process 1457 1457 1458 1458 with tempfile.TemporaryDirectory() as tmpdir: 1459 - original_path = os.environ.get("JOURNAL_PATH") 1460 - os.environ["JOURNAL_PATH"] = tmpdir 1459 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1460 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1461 1461 1462 1462 try: 1463 1463 facet_dir = Path(tmpdir) / "facets" / "work" / "activities" ··· 1489 1489 1490 1490 finally: 1491 1491 if original_path: 1492 - os.environ["JOURNAL_PATH"] = original_path 1492 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1493 1493 1494 1494 def test_valid_activity_ids_pass_through(self): 1495 1495 """Post-hook preserves entries with valid activity IDs.""" ··· 1498 1498 from muse.activity_state import post_process 1499 1499 1500 1500 with tempfile.TemporaryDirectory() as tmpdir: 1501 - original_path = os.environ.get("JOURNAL_PATH") 1502 - os.environ["JOURNAL_PATH"] = tmpdir 1501 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1502 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1503 1503 1504 1504 try: 1505 1505 facet_dir = Path(tmpdir) / "facets" / "work" / "activities" ··· 1547 1547 1548 1548 finally: 1549 1549 if original_path: 1550 - os.environ["JOURNAL_PATH"] = original_path 1550 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1551 1551 1552 1552 def test_unconfigured_facet_allows_all_defaults(self): 1553 1553 """Post-hook allows all default activity IDs for unconfigured facets.""" ··· 1556 1556 from muse.activity_state import post_process 1557 1557 1558 1558 with tempfile.TemporaryDirectory() as tmpdir: 1559 - original_path = os.environ.get("JOURNAL_PATH") 1560 - os.environ["JOURNAL_PATH"] = tmpdir 1559 + original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1560 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1561 1561 1562 1562 try: 1563 1563 # Create facet dir but no activities.jsonl ··· 1595 1595 1596 1596 finally: 1597 1597 if original_path: 1598 - os.environ["JOURNAL_PATH"] = original_path 1598 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path
+1 -1
tests/test_agents_check.py
··· 11 11 12 12 13 13 def test_run_check_writes_health_file(tmp_path, monkeypatch): 14 - """_run_check writes agents health results to JOURNAL_PATH/health/agents.json.""" 14 + """_run_check writes agents health results to _SOLSTONE_JOURNAL_OVERRIDE/health/agents.json.""" 15 15 import think.agents as agents 16 16 17 17 fake_registry = {"fake": object()}
+1 -1
tests/test_agents_ndjson.py
··· 22 22 agents_path = journal_path / "agents" 23 23 agents_path.mkdir() 24 24 25 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 25 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 26 26 return journal_path 27 27 28 28
+5 -5
tests/test_anthropic.py
··· 210 210 agents_dir = journal / "agents" 211 211 agents_dir.mkdir() 212 212 213 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 213 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 214 214 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 215 215 216 216 ndjson_input = json.dumps( ··· 253 253 agents_dir = journal / "agents" 254 254 agents_dir.mkdir() 255 255 256 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 256 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 257 257 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 258 258 259 259 ndjson_input = json.dumps( ··· 300 300 agents_dir = journal / "agents" 301 301 agents_dir.mkdir() 302 302 303 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 303 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 304 304 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 305 305 306 306 ndjson_input = json.dumps( ··· 342 342 agents_dir = journal / "agents" 343 343 agents_dir.mkdir() 344 344 345 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 345 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 346 346 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 347 347 348 348 ndjson_input = json.dumps( ··· 382 382 agents_dir = journal / "agents" 383 383 agents_dir.mkdir() 384 384 385 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 385 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 386 386 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 387 387 388 388 ndjson_input = json.dumps(
+2 -2
tests/test_app_agents.py
··· 15 15 16 16 @pytest.fixture 17 17 def fixture_journal(): 18 - """Set JOURNAL_PATH to tests/fixtures/journal for testing.""" 19 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 18 + """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 19 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 20 20 yield 21 21 22 22
+5 -5
tests/test_app_calendar.py
··· 12 12 13 13 @pytest.fixture 14 14 def fixture_journal(): 15 - """Set JOURNAL_PATH to tests/fixtures/journal for testing.""" 16 - old = os.environ.get("JOURNAL_PATH") 17 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 15 + """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 16 + old = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 17 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 18 18 yield 19 19 if old is None: 20 - os.environ.pop("JOURNAL_PATH", None) 20 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 21 21 else: 22 - os.environ["JOURNAL_PATH"] = old 22 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = old 23 23 24 24 25 25 @pytest.fixture
+1 -1
tests/test_awareness.py
··· 12 12 @pytest.fixture(autouse=True) 13 13 def _temp_journal(monkeypatch, tmp_path): 14 14 """Isolate all tests to a temporary journal.""" 15 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 15 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 16 17 17 18 18 class TestCurrentState:
+3 -3
tests/test_call.py
··· 40 40 json.dumps({"title": "Muted Facet", "muted": True}), 41 41 encoding="utf-8", 42 42 ) 43 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 43 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 44 44 import think.utils 45 45 46 46 think.utils._journal_path_cache = None ··· 134 134 encoding="utf-8", 135 135 ) 136 136 137 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 137 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 138 138 import think.utils 139 139 140 140 think.utils._journal_path_cache = None ··· 286 286 shutil.copytree( 287 287 "tests/fixtures/journal/facets/work", journal / "facets" / "work" 288 288 ) 289 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 289 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 290 290 # Clear cached journal path 291 291 import think.utils 292 292
+6 -6
tests/test_callosum.py
··· 20 20 """Set up a temporary journal path.""" 21 21 journal = tmp_path / "journal" 22 22 journal.mkdir() 23 - os.environ["JOURNAL_PATH"] = str(journal) 23 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 24 24 yield journal 25 25 # Cleanup 26 - if "JOURNAL_PATH" in os.environ: 27 - del os.environ["JOURNAL_PATH"] 26 + if "_SOLSTONE_JOURNAL_OVERRIDE" in os.environ: 27 + del os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 28 28 29 29 30 30 def test_server_broadcast_validates_tract_field(): ··· 217 217 218 218 219 219 def test_server_socket_path_from_env(journal_path): 220 - """Test that server uses JOURNAL_PATH env var for socket path.""" 220 + """Test that server uses _SOLSTONE_JOURNAL_OVERRIDE env var for socket path.""" 221 221 server = CallosumServer() 222 222 223 223 expected_path = journal_path / "health" / "callosum.sock" ··· 233 233 234 234 235 235 def test_client_socket_path_from_env(journal_path): 236 - """Test that client uses JOURNAL_PATH env var for socket path.""" 236 + """Test that client uses _SOLSTONE_JOURNAL_OVERRIDE env var for socket path.""" 237 237 client = CallosumConnection() 238 238 239 239 expected_path = journal_path / "health" / "callosum.sock" ··· 252 252 """Test that callosum_send() works with an empty journal directory.""" 253 253 from think.callosum import callosum_send 254 254 255 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 255 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 256 256 257 257 # No server listening at tmp_path, so send will fail gracefully 258 258 result = callosum_send("test", "event", data="value")
+16 -16
tests/test_cluster.py
··· 10 10 11 11 def test_cluster(tmp_path, monkeypatch): 12 12 """Test cluster() uses transcripts and agent output summaries (*.md files).""" 13 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 13 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 14 14 day_dir = day_path("20240101") 15 15 16 16 mod = importlib.import_module("think.cluster") ··· 36 36 37 37 def test_cluster_range(tmp_path, monkeypatch): 38 38 """Test cluster_range with transcripts and agents sources.""" 39 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 39 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 40 40 day_dir = day_path("20240101") 41 41 42 42 mod = importlib.import_module("think.cluster") ··· 65 65 66 66 67 67 def test_cluster_scan(tmp_path, monkeypatch): 68 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 68 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 69 69 day_dir = day_path("20240101") 70 70 71 71 mod = importlib.import_module("think.cluster") ··· 104 104 105 105 def test_cluster_segments(tmp_path, monkeypatch): 106 106 """Test cluster_segments returns individual segments with their types.""" 107 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 107 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 108 108 day_dir = day_path("20240101") 109 109 110 110 mod = importlib.import_module("think.cluster") ··· 152 152 153 153 def test_cluster_period_uses_raw_screen(tmp_path, monkeypatch): 154 154 """Test cluster_period uses raw screen.jsonl, not insight *.md files.""" 155 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 155 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 156 156 day_dir = day_path("20240101") 157 157 158 158 mod = importlib.import_module("think.cluster") ··· 193 193 194 194 def test_cluster_range_with_agents(tmp_path, monkeypatch): 195 195 """Test cluster_range with agents source loads all *.md files.""" 196 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 196 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 197 197 day_dir = day_path("20240101") 198 198 199 199 mod = importlib.import_module("think.cluster") ··· 233 233 234 234 def test_cluster_range_with_screen(tmp_path, monkeypatch): 235 235 """Test cluster_range with screen source loads raw screen.jsonl data.""" 236 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 236 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 237 237 day_dir = day_path("20240101") 238 238 239 239 mod = importlib.import_module("think.cluster") ··· 265 265 266 266 def test_cluster_range_with_multiple_screen_files(tmp_path, monkeypatch): 267 267 """Test cluster_range loads multiple *_screen.jsonl files per segment.""" 268 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 268 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 269 269 day_dir = day_path("20240101") 270 270 271 271 mod = importlib.import_module("think.cluster") ··· 299 299 300 300 def test_cluster_scan_with_split_screen(tmp_path, monkeypatch): 301 301 """Test cluster_scan detects *_screen.jsonl files.""" 302 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 302 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 303 303 day_dir = day_path("20240101") 304 304 305 305 mod = importlib.import_module("think.cluster") ··· 318 318 319 319 def test_cluster_segments_with_split_screen(tmp_path, monkeypatch): 320 320 """Test cluster_segments detects *_screen.jsonl files.""" 321 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 321 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 322 322 day_dir = day_path("20240101") 323 323 324 324 mod = importlib.import_module("think.cluster") ··· 338 338 339 339 def test_cluster_span(tmp_path, monkeypatch): 340 340 """Test cluster_span processes a span of segments.""" 341 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 341 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 342 342 day_dir = day_path("20240101") 343 343 344 344 mod = importlib.import_module("think.cluster") ··· 382 382 383 383 def test_cluster_span_missing_segment(tmp_path, monkeypatch): 384 384 """Test cluster_span fails fast when segment is missing.""" 385 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 385 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 386 386 day_dir = day_path("20240101") 387 387 388 388 mod = importlib.import_module("think.cluster") ··· 407 407 408 408 def test_cluster_with_agent_filter_dict(tmp_path, monkeypatch): 409 409 """Test cluster() with dict-valued agents source for selective filtering.""" 410 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 410 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 411 411 day_dir = day_path("20240101") 412 412 413 413 mod = importlib.import_module("think.cluster") ··· 436 436 437 437 def test_cluster_with_agent_filter_multiple(tmp_path, monkeypatch): 438 438 """Test cluster() with dict selecting multiple agents.""" 439 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 439 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 440 440 day_dir = day_path("20240101") 441 441 442 442 mod = importlib.import_module("think.cluster") ··· 469 469 470 470 def test_cluster_with_agent_filter_app_namespaced(tmp_path, monkeypatch): 471 471 """Test cluster() with dict filtering app-namespaced agent outputs.""" 472 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 472 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 473 473 day_dir = day_path("20240101") 474 474 475 475 mod = importlib.import_module("think.cluster") ··· 501 501 502 502 def test_cluster_with_empty_agent_filter(tmp_path, monkeypatch): 503 503 """Test cluster() with empty dict means no agents.""" 504 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 504 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 505 505 day_dir = day_path("20240101") 506 506 507 507 mod = importlib.import_module("think.cluster")
+4 -4
tests/test_cluster_full.py
··· 12 12 13 13 14 14 def copy_day(tmp_path: Path) -> Path: 15 - os.environ["JOURNAL_PATH"] = str(tmp_path) 15 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 16 16 dest = day_path("20240101") 17 17 src = FIXTURES / "journal" / "20240101" 18 18 # Copy contents from fixture to the day_path created directory ··· 27 27 def test_cluster_full(tmp_path, monkeypatch): 28 28 mod = importlib.import_module("think.cluster") 29 29 copy_day(tmp_path) 30 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 30 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 31 31 md, counts = mod.cluster( 32 32 "20240101", sources={"transcripts": True, "percepts": False, "agents": True} 33 33 ) ··· 43 43 def test_cluster_default_sources(tmp_path, monkeypatch): 44 44 mod = importlib.import_module("think.cluster") 45 45 copy_day(tmp_path) 46 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 46 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 47 47 out, _counts = mod.cluster( 48 48 "20240101", sources={"transcripts": True, "percepts": False, "agents": True} 49 49 ) ··· 54 54 def test_cluster_range_raw_screen(tmp_path, monkeypatch): 55 55 mod = importlib.import_module("think.cluster") 56 56 copy_day(tmp_path) 57 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 57 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 58 58 out = mod.cluster_range( 59 59 "20240101", 60 60 "123456",
+8 -8
tests/test_config.py
··· 44 44 45 45 def test_get_config_default_structure(tmp_path, monkeypatch): 46 46 """Test get_config returns default structure when file doesn't exist.""" 47 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 47 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 48 48 49 49 config = get_config() 50 50 ··· 70 70 71 71 def test_get_config_default_is_deep_copy(tmp_path, monkeypatch): 72 72 """Test that modifying returned defaults doesn't affect future calls.""" 73 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 73 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 74 74 75 75 config1 = get_config() 76 76 config1["identity"]["name"] = "Modified" ··· 83 83 84 84 def test_get_config_loads_existing(config_journal, monkeypatch): 85 85 """Test get_config loads existing configuration.""" 86 - monkeypatch.setenv("JOURNAL_PATH", str(config_journal)) 86 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(config_journal)) 87 87 88 88 config = get_config() 89 89 ··· 103 103 104 104 def test_get_config_existing_is_master(tmp_path, monkeypatch): 105 105 """Test that existing journal.json is returned as-is without merging defaults.""" 106 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 106 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 107 107 108 108 # Create config with only a name - no other identity fields, no describe 109 109 config_dir = tmp_path / "config" ··· 130 130 131 131 def test_get_config_empty_journal(tmp_path, monkeypatch): 132 132 """Test get_config returns defaults with an empty journal directory.""" 133 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 133 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 134 134 135 135 config = get_config() 136 136 assert "identity" in config ··· 139 139 140 140 def test_get_config_handles_invalid_json(tmp_path, monkeypatch): 141 141 """Test get_config returns defaults when JSON is invalid.""" 142 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 142 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 143 143 144 144 # Create config with invalid JSON 145 145 config_dir = tmp_path / "config" ··· 166 166 167 167 def test_get_config_with_fixtures(): 168 168 """Test get_config with tests/fixtures/journal path.""" 169 - # Set JOURNAL_PATH to fixtures 170 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 169 + # Set _SOLSTONE_JOURNAL_OVERRIDE to fixtures 170 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 171 171 172 172 config = get_config() 173 173
+4 -15
tests/test_config_cli.py
··· 10 10 11 11 def test_config_prints_json(monkeypatch, capsys): 12 12 """Default command prints full config JSON.""" 13 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 13 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 14 14 monkeypatch.setattr("sys.argv", ["sol config"]) 15 15 16 16 main() ··· 21 21 22 22 23 23 def test_config_env_prints_path(monkeypatch, capsys): 24 - """env subcommand prints resolved JOURNAL_PATH.""" 25 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 24 + """env subcommand prints the journal path.""" 25 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 26 26 monkeypatch.setattr("sys.argv", ["sol config", "env"]) 27 27 28 28 main() 29 29 30 30 output = capsys.readouterr().out.strip() 31 - assert output == "JOURNAL_PATH=tests/fixtures/journal (from shell)" 32 - 33 - 34 - def test_config_env_shows_source(monkeypatch, capsys): 35 - """env subcommand includes source for the resolved path.""" 36 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 37 - monkeypatch.setattr("sys.argv", ["sol config", "env"]) 38 - 39 - main() 40 - 41 - output = capsys.readouterr().out.strip() 42 - assert output.endswith("(from shell)") 31 + assert output == "tests/fixtures/journal"
+3 -3
tests/test_content_manifest.py
··· 12 12 13 13 14 14 def test_write_content_manifest(tmp_path, monkeypatch): 15 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 15 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 16 entries = [ 17 17 { 18 18 "id": "conv-0", ··· 88 88 89 89 90 90 def test_chatgpt_importer_writes_content_manifest(tmp_path, monkeypatch): 91 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 91 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 92 92 archive = tmp_path / "chatgpt.zip" 93 93 conversations = [ 94 94 { ··· 138 138 139 139 140 140 def test_ics_importer_writes_content_manifest(tmp_path, monkeypatch): 141 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 141 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 142 142 ics_path = tmp_path / "calendar.ics" 143 143 ics_path.write_bytes( 144 144 b"""BEGIN:VCALENDAR
+1 -1
tests/test_convey_utils.py
··· 51 51 52 52 53 53 def test_list_day_folders(tmp_path, monkeypatch): 54 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 54 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 55 55 from think.utils import day_dirs 56 56 57 57 day_path("20240101")
+1 -1
tests/test_cortex.py
··· 44 44 agents_path = journal_path / "agents" 45 45 agents_path.mkdir() 46 46 47 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 47 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 48 48 return journal_path 49 49 50 50
+19 -19
tests/test_cortex_client.py
··· 35 35 tmp_dir = tempfile.mkdtemp(dir="/tmp", prefix="callosum_") 36 36 tmp_path = Path(tmp_dir) 37 37 38 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 38 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 39 39 (tmp_path / "agents").mkdir(parents=True, exist_ok=True) 40 40 41 41 server = CallosumServer() ··· 162 162 def test_cortex_request_empty_journal(tmp_path, monkeypatch): 163 163 """Test cortex_request works with an empty journal directory.""" 164 164 monkeypatch.setattr("think.cortex_client.callosum_send", lambda *a, **kw: True) 165 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 165 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 166 166 167 167 agent_id = cortex_request("test", "default", "openai") 168 168 assert agent_id is not None ··· 174 174 175 175 def test_cortex_agents_empty(tmp_path, monkeypatch): 176 176 """Test cortex_agents with no agents.""" 177 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 177 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 178 178 179 179 result = cortex_agents() 180 180 ··· 187 187 188 188 def test_cortex_agents_with_active(tmp_path, monkeypatch): 189 189 """Test cortex_agents with active (running) agents.""" 190 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 190 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 191 191 agents_dir = tmp_path / "agents" 192 192 agents_dir.mkdir() 193 193 ··· 237 237 238 238 def test_cortex_agents_with_completed(tmp_path, monkeypatch): 239 239 """Test cortex_agents with completed (historical) agents.""" 240 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 240 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 241 241 agents_dir = tmp_path / "agents" 242 242 agents_dir.mkdir() 243 243 ··· 272 272 273 273 def test_cortex_agents_pagination(tmp_path, monkeypatch): 274 274 """Test cortex_agents pagination.""" 275 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 275 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 276 276 agents_dir = tmp_path / "agents" 277 277 agents_dir.mkdir() 278 278 ··· 305 305 306 306 def test_cortex_agents_empty_journal(tmp_path, monkeypatch): 307 307 """Test cortex_agents works with an empty journal directory.""" 308 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 308 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 309 309 310 310 result = cortex_agents() 311 311 assert "agents" in result ··· 315 315 316 316 def test_get_agent_log_status_completed(tmp_path, monkeypatch): 317 317 """Test get_agent_log_status returns 'completed' for finished agents.""" 318 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 318 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 319 319 agents_dir = tmp_path / "agents" 320 320 agents_dir.mkdir() 321 321 default_dir = agents_dir / "default" ··· 329 329 330 330 def test_get_agent_log_status_running(tmp_path, monkeypatch): 331 331 """Test get_agent_log_status returns 'running' for active agents.""" 332 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 332 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 333 333 agents_dir = tmp_path / "agents" 334 334 agents_dir.mkdir() 335 335 default_dir = agents_dir / "default" ··· 343 343 344 344 def test_get_agent_log_status_not_found(tmp_path, monkeypatch): 345 345 """Test get_agent_log_status returns 'not_found' for missing agents.""" 346 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 346 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 347 347 (tmp_path / "agents").mkdir() 348 348 349 349 assert get_agent_log_status("nonexistent") == "not_found" ··· 351 351 352 352 def test_get_agent_log_status_prefers_completed(tmp_path, monkeypatch): 353 353 """Test get_agent_log_status returns 'completed' when both files exist.""" 354 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 354 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 355 355 agents_dir = tmp_path / "agents" 356 356 agents_dir.mkdir() 357 357 default_dir = agents_dir / "default" ··· 367 367 368 368 def test_get_agent_end_state_finish(tmp_path, monkeypatch): 369 369 """Test get_agent_end_state returns 'finish' for successful agents.""" 370 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 370 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 371 371 agents_dir = tmp_path / "agents" 372 372 agents_dir.mkdir() 373 373 default_dir = agents_dir / "default" ··· 384 384 385 385 def test_get_agent_end_state_error(tmp_path, monkeypatch): 386 386 """Test get_agent_end_state returns 'error' for failed agents.""" 387 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 387 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 388 388 agents_dir = tmp_path / "agents" 389 389 agents_dir.mkdir() 390 390 default_dir = agents_dir / "default" ··· 401 401 402 402 def test_get_agent_end_state_running(tmp_path, monkeypatch): 403 403 """Test get_agent_end_state returns 'running' for active agents.""" 404 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 404 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 405 405 agents_dir = tmp_path / "agents" 406 406 agents_dir.mkdir() 407 407 default_dir = agents_dir / "default" ··· 417 417 418 418 def test_get_agent_end_state_unknown(tmp_path, monkeypatch): 419 419 """Test get_agent_end_state returns 'unknown' for missing agents.""" 420 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 420 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 421 421 (tmp_path / "agents").mkdir() 422 422 423 423 assert get_agent_end_state("nonexistent") == "unknown" ··· 428 428 429 429 def test_wait_for_agents_already_complete(tmp_path, monkeypatch): 430 430 """Test wait_for_agents returns immediately if agents already completed.""" 431 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 431 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 432 432 agents_dir = tmp_path / "agents" 433 433 agents_dir.mkdir() 434 434 default_dir = agents_dir / "default" ··· 525 525 526 526 def test_wait_for_agents_initial_file_check(tmp_path, monkeypatch): 527 527 """Test wait_for_agents finds already-completed agents via initial file check.""" 528 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 528 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 529 529 agents_dir = tmp_path / "agents" 530 530 agents_dir.mkdir() 531 531 default_dir = agents_dir / "default" ··· 546 546 547 547 def test_wait_for_agents_timeout_actual(tmp_path, monkeypatch): 548 548 """Test wait_for_agents times out for agents that never complete.""" 549 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 549 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 550 550 agents_dir = tmp_path / "agents" 551 551 agents_dir.mkdir() 552 552 default_dir = agents_dir / "default" ··· 607 607 """Test that missed events are recovered via final file check with INFO log.""" 608 608 import logging 609 609 610 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 610 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 611 611 agents_dir = tmp_path / "agents" 612 612 agents_dir.mkdir() 613 613 default_dir = agents_dir / "default"
+8 -8
tests/test_dream_activity.py
··· 28 28 from think.dream import run_activity_prompts 29 29 30 30 with tempfile.TemporaryDirectory() as tmpdir: 31 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 31 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 32 32 33 33 result = run_activity_prompts( 34 34 day="20260209", ··· 41 41 from think.dream import run_activity_prompts 42 42 43 43 with tempfile.TemporaryDirectory() as tmpdir: 44 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 44 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 45 45 46 46 self._write_record( 47 47 tmpdir, ··· 71 71 from think.dream import run_activity_prompts 72 72 73 73 with tempfile.TemporaryDirectory() as tmpdir: 74 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 74 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 75 75 76 76 self._write_record( 77 77 tmpdir, ··· 133 133 from think.dream import run_activity_prompts 134 134 135 135 with tempfile.TemporaryDirectory() as tmpdir: 136 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 136 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 137 137 138 138 self._write_record( 139 139 tmpdir, ··· 187 187 from think.dream import run_activity_prompts 188 188 189 189 with tempfile.TemporaryDirectory() as tmpdir: 190 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 190 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 191 191 192 192 record = { 193 193 "id": "coding_100000_300", ··· 250 250 from think.dream import run_activity_prompts 251 251 252 252 with tempfile.TemporaryDirectory() as tmpdir: 253 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 253 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 254 254 255 255 self._write_record( 256 256 tmpdir, ··· 298 298 from think.dream import run_activity_prompts 299 299 300 300 with tempfile.TemporaryDirectory() as tmpdir: 301 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 301 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 302 302 303 303 self._write_record( 304 304 tmpdir, ··· 326 326 from think.dream import run_activity_prompts 327 327 328 328 with tempfile.TemporaryDirectory() as tmpdir: 329 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 329 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 330 330 331 331 self._write_record( 332 332 tmpdir,
+6 -6
tests/test_dream_dry_run.py
··· 21 21 """Dry-run daily mode prints prompts without spawning agents.""" 22 22 mod = importlib.import_module("think.dream") 23 23 journal = copy_journal(tmp_path) 24 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 24 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 25 25 26 26 mod.dry_run("20240101") 27 27 ··· 37 37 """Dry-run segment mode skips pre/post phases.""" 38 38 mod = importlib.import_module("think.dream") 39 39 journal = copy_journal(tmp_path) 40 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 40 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 41 41 42 42 mod.dry_run("20240101", segment="120000_300") 43 43 ··· 51 51 """Dry-run --segments lists discovered segments.""" 52 52 mod = importlib.import_module("think.dream") 53 53 journal = copy_journal(tmp_path) 54 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 54 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 55 55 56 56 mod.dry_run("20240101", segments=True) 57 57 ··· 63 63 """Dry-run --flush shows flush-eligible agents.""" 64 64 mod = importlib.import_module("think.dream") 65 65 journal = copy_journal(tmp_path) 66 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 66 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 67 67 68 68 mod.dry_run("20240101", flush=True, segment="120000_300") 69 69 ··· 75 75 """Dry-run indicates refresh mode in header.""" 76 76 mod = importlib.import_module("think.dream") 77 77 journal = copy_journal(tmp_path) 78 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 78 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 79 79 80 80 mod.dry_run("20240101", refresh=True) 81 81 ··· 87 87 """Dry-run works without callosum connection.""" 88 88 mod = importlib.import_module("think.dream") 89 89 journal = copy_journal(tmp_path) 90 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 90 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 91 91 92 92 # Save and clear _callosum to verify dry_run doesn't create one 93 93 prev = mod._callosum
+2 -4
tests/test_dream_full.py
··· 21 21 """Test that main() runs pre/post phases and prompts by priority.""" 22 22 mod = importlib.import_module("think.dream") 23 23 journal = copy_journal(tmp_path) 24 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 24 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 25 25 26 26 commands_run = [] 27 27 prompts_run = False ··· 42 42 monkeypatch.setattr(mod, "run_command", mock_run_command) 43 43 monkeypatch.setattr(mod, "run_queued_command", mock_run_queued_command) 44 44 monkeypatch.setattr(mod, "run_prompts_by_priority", mock_run_prompts_by_priority) 45 - monkeypatch.setattr("think.utils.load_dotenv", lambda: True) 46 45 monkeypatch.setattr( 47 46 "sys.argv", 48 47 ["sol dream", "--day", "20240101", "--refresh", "--verbose"], ··· 71 70 segment_dir = journal / "20240101" / "default" / "120000_300" 72 71 segment_dir.mkdir(parents=True, exist_ok=True) 73 72 74 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 73 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 75 74 76 75 commands_run = [] 77 76 ··· 89 88 monkeypatch.setattr(mod, "run_command", mock_run_command) 90 89 monkeypatch.setattr(mod, "run_queued_command", mock_run_queued_command) 91 90 monkeypatch.setattr(mod, "run_prompts_by_priority", mock_run_prompts_by_priority) 92 - monkeypatch.setattr("think.utils.load_dotenv", lambda: True) 93 91 monkeypatch.setattr( 94 92 "sys.argv", 95 93 ["sol dream", "--day", "20240101", "--segment", "120000_300"],
+1 -4
tests/test_dream_segment.py
··· 19 19 segment_path.mkdir(parents=True) 20 20 (segment_path / "agents").mkdir(parents=True) 21 21 22 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 22 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 23 23 return segment_path 24 24 25 25 ··· 465 465 mod, "run_queued_command", lambda cmd, day, timeout=600: True 466 466 ) 467 467 monkeypatch.setattr(mod, "CallosumConnection", MockCallosumConnection) 468 - monkeypatch.setattr("think.utils.load_dotenv", lambda: True) 469 468 monkeypatch.setattr( 470 469 "sys.argv", 471 470 ["sol dream", "--day", "20240115", "--segment", "120000_300"], ··· 497 496 monkeypatch.setattr(mod, "check_callosum_available", lambda: True) 498 497 monkeypatch.setattr(mod, "run_command", lambda cmd, day: True) 499 498 monkeypatch.setattr(mod, "CallosumConnection", MockCallosumConnection) 500 - monkeypatch.setattr("think.utils.load_dotenv", lambda: True) 501 499 monkeypatch.setattr( 502 500 "sys.argv", 503 501 ["sol dream", "--day", "20240115", "--segment", "999999_300"], ··· 543 541 mod, "run_queued_command", lambda cmd, day, timeout=600: True 544 542 ) 545 543 monkeypatch.setattr(mod, "CallosumConnection", MockCallosumConnection) 546 - monkeypatch.setattr("think.utils.load_dotenv", lambda: True) 547 544 monkeypatch.setattr( 548 545 "sys.argv", 549 546 [
+87 -87
tests/test_entities.py
··· 42 42 43 43 @pytest.fixture 44 44 def fixture_journal(): 45 - """Set JOURNAL_PATH to tests/fixtures/journal for testing.""" 46 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 45 + """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 46 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 47 47 yield 48 48 # No cleanup needed - just testing reads 49 49 ··· 204 204 entities_dir = facet_path / "entities" 205 205 entities_dir.mkdir(parents=True) 206 206 207 - # Update JOURNAL_PATH to temp directory 208 - os.environ["JOURNAL_PATH"] = str(tmp_path) 207 + # Update _SOLSTONE_JOURNAL_OVERRIDE to temp directory 208 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 209 209 210 210 # Save some entities (dicts with extended fields) 211 211 test_entities = [ ··· 249 249 """Test that entities can be saved and loaded back correctly.""" 250 250 facet_path = tmp_path / "facets" / "test_facet" 251 251 facet_path.mkdir(parents=True) 252 - os.environ["JOURNAL_PATH"] = str(tmp_path) 252 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 253 253 254 254 # Save unsorted entities 255 255 unsorted = [ ··· 289 289 290 290 def test_save_detected_entity_basic(fixture_journal, tmp_path): 291 291 """Test save_detected_entity adds an entity with locking.""" 292 - os.environ["JOURNAL_PATH"] = str(tmp_path) 292 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 293 293 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 294 294 295 295 result = save_detected_entity("test_facet", "20250101", "Person", "Alice", "Friend") ··· 303 303 304 304 def test_save_detected_entity_duplicate(fixture_journal, tmp_path): 305 305 """Test save_detected_entity raises on duplicate.""" 306 - os.environ["JOURNAL_PATH"] = str(tmp_path) 306 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 307 307 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 308 308 309 309 save_detected_entity("test_facet", "20250101", "Person", "Alice", "Friend") ··· 318 318 """Test concurrent save_detected_entity calls don't lose data.""" 319 319 import threading 320 320 321 - os.environ["JOURNAL_PATH"] = str(tmp_path) 321 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 322 322 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 323 323 324 324 errors = [] ··· 351 351 """Test that save_detected_entity retries on transient OSError.""" 352 352 from unittest.mock import patch 353 353 354 - os.environ["JOURNAL_PATH"] = str(tmp_path) 354 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 355 355 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 356 356 357 357 call_count = 0 ··· 377 377 378 378 def test_update_detected_entity(fixture_journal, tmp_path): 379 379 """Test update_detected_entity with locking.""" 380 - os.environ["JOURNAL_PATH"] = str(tmp_path) 380 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 381 381 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 382 382 383 383 save_detected_entity("test_facet", "20250101", "Person", "Alice", "Friend") ··· 390 390 391 391 def test_update_detected_entity_not_found(fixture_journal, tmp_path): 392 392 """Test update_detected_entity raises when entity missing.""" 393 - os.environ["JOURNAL_PATH"] = str(tmp_path) 393 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 394 394 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 395 395 396 396 import pytest as _pytest ··· 421 421 facet1_path.mkdir(parents=True) 422 422 facet2_path.mkdir(parents=True) 423 423 424 - os.environ["JOURNAL_PATH"] = str(tmp_path) 424 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 425 425 426 426 # Save same entity name in both facets with different descriptions 427 427 entities1 = [ ··· 456 456 """Test sorting entities by last_seen.""" 457 457 facet_path = tmp_path / "facets" / "test_facet" 458 458 facet_path.mkdir(parents=True) 459 - os.environ["JOURNAL_PATH"] = str(tmp_path) 459 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 460 460 461 461 # Create entities with varying last_seen values 462 462 entities = [ ··· 489 489 """Test limiting number of entities returned.""" 490 490 facet_path = tmp_path / "facets" / "test_facet" 491 491 facet_path.mkdir(parents=True) 492 - os.environ["JOURNAL_PATH"] = str(tmp_path) 492 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 493 493 494 494 # Create 5 entities 495 495 entities = [ ··· 507 507 """Test sorting and limiting together.""" 508 508 facet_path = tmp_path / "facets" / "test_facet" 509 509 facet_path.mkdir(parents=True) 510 - os.environ["JOURNAL_PATH"] = str(tmp_path) 510 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 511 511 512 512 # Create entities with last_seen 513 513 entities = [ ··· 532 532 """Test basic functionality of load_recent_entity_names.""" 533 533 facet_path = tmp_path / "facets" / "test_facet" 534 534 facet_path.mkdir(parents=True) 535 - os.environ["JOURNAL_PATH"] = str(tmp_path) 535 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 536 536 537 537 # Create entities with last_seen 538 538 entities = [ ··· 554 554 """Test that result is a list of names.""" 555 555 facet_path = tmp_path / "facets" / "test_facet" 556 556 facet_path.mkdir(parents=True) 557 - os.environ["JOURNAL_PATH"] = str(tmp_path) 557 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 558 558 559 559 # Create 10 entities with speakable names (no digits) 560 560 names = [ ··· 586 586 """Test with no entities returns None.""" 587 587 facet_path = tmp_path / "facets" / "test_facet" 588 588 facet_path.mkdir(parents=True) 589 - os.environ["JOURNAL_PATH"] = str(tmp_path) 589 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 590 590 591 591 result = load_recent_entity_names() 592 592 assert result is None ··· 596 596 """Test that aka values are included in spoken names.""" 597 597 facet_path = tmp_path / "facets" / "test_facet" 598 598 facet_path.mkdir(parents=True) 599 - os.environ["JOURNAL_PATH"] = str(tmp_path) 599 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 600 600 601 601 entities = [ 602 602 { ··· 621 621 """Test that limit parameter is respected.""" 622 622 facet_path = tmp_path / "facets" / "test_facet" 623 623 facet_path.mkdir(parents=True) 624 - os.environ["JOURNAL_PATH"] = str(tmp_path) 624 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 625 625 626 626 # Create 30 entities with speakable names (no digits) 627 627 # Use unique first names that won't collide ··· 679 679 """Test that names with underscores or no letters are filtered out.""" 680 680 facet_path = tmp_path / "facets" / "test_facet" 681 681 facet_path.mkdir(parents=True) 682 - os.environ["JOURNAL_PATH"] = str(tmp_path) 682 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 683 683 684 684 entities = [ 685 685 # Speakable - should be included (letters required, digits OK) ··· 733 733 """Test that aka field is preserved during save/load operations.""" 734 734 facet_path = tmp_path / "facets" / "test_facet" 735 735 facet_path.mkdir(parents=True) 736 - os.environ["JOURNAL_PATH"] = str(tmp_path) 736 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 737 737 738 738 # Save entities with aka fields 739 739 test_entities = [ ··· 790 790 facet_path = tmp_path / "facets" / "test_facet" 791 791 entities_dir = facet_path / "entities" 792 792 entities_dir.mkdir(parents=True) 793 - os.environ["JOURNAL_PATH"] = str(tmp_path) 793 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 794 794 795 795 # Create attached entity with aka 796 796 attached = [ ··· 830 830 facet_path = tmp_path / "facets" / "test_facet" 831 831 entities_dir = facet_path / "entities" 832 832 entities_dir.mkdir(parents=True) 833 - os.environ["JOURNAL_PATH"] = str(tmp_path) 833 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 834 834 835 835 # Create same entity across multiple days 836 836 save_entities( ··· 862 862 facet_path = tmp_path / "facets" / "test_facet" 863 863 entities_dir = facet_path / "entities" 864 864 entities_dir.mkdir(parents=True) 865 - os.environ["JOURNAL_PATH"] = str(tmp_path) 865 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 866 866 867 867 # Create entity across multiple days with different descriptions 868 868 save_entities( ··· 900 900 facet_path = tmp_path / "facets" / "test_facet" 901 901 entities_dir = facet_path / "entities" 902 902 entities_dir.mkdir(parents=True) 903 - os.environ["JOURNAL_PATH"] = str(tmp_path) 903 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 904 904 905 905 from datetime import datetime, timedelta 906 906 ··· 934 934 """Test that empty or non-existent facet returns empty list.""" 935 935 facet_path = tmp_path / "facets" / "empty_facet" 936 936 facet_path.mkdir(parents=True) 937 - os.environ["JOURNAL_PATH"] = str(tmp_path) 937 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 938 938 939 939 # No entities directory 940 940 detected = load_detected_entities_recent("empty_facet") ··· 946 946 facet_path = tmp_path / "facets" / "test_facet" 947 947 entities_dir = facet_path / "entities" 948 948 entities_dir.mkdir(parents=True) 949 - os.environ["JOURNAL_PATH"] = str(tmp_path) 949 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 950 950 951 951 # Same name, different types - should be treated as separate entities 952 952 save_entities( ··· 970 970 """Test that attached_at and updated_at timestamps are preserved through save/load.""" 971 971 facet_path = tmp_path / "facets" / "test_facet" 972 972 facet_path.mkdir(parents=True) 973 - os.environ["JOURNAL_PATH"] = str(tmp_path) 973 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 974 974 975 975 # Save entities with timestamps 976 976 test_entities = [ ··· 1011 1011 """Test that load_entities excludes detached entities by default.""" 1012 1012 facet_path = tmp_path / "facets" / "test_facet" 1013 1013 facet_path.mkdir(parents=True) 1014 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1014 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1015 1015 1016 1016 # Save entities with one detached 1017 1017 test_entities = [ ··· 1039 1039 """Test that load_entities includes detached entities when include_detached=True.""" 1040 1040 facet_path = tmp_path / "facets" / "test_facet" 1041 1041 facet_path.mkdir(parents=True) 1042 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1042 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1043 1043 1044 1044 # Save entities with one detached 1045 1045 test_entities = [ ··· 1071 1071 facet2_path = tmp_path / "facets" / "facet2" 1072 1072 facet1_path.mkdir(parents=True) 1073 1073 facet2_path.mkdir(parents=True) 1074 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1074 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1075 1075 1076 1076 # Save entities - one active, one detached per facet 1077 1077 save_entities( ··· 1109 1109 facet_path = tmp_path / "facets" / "test_facet" 1110 1110 entities_dir = facet_path / "entities" 1111 1111 entities_dir.mkdir(parents=True) 1112 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1112 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1113 1113 1114 1114 # Create attached entity with detached=True 1115 1115 attached = [ ··· 1156 1156 """Test that detached entities preserve all fields including custom ones.""" 1157 1157 facet_path = tmp_path / "facets" / "test_facet" 1158 1158 facet_path.mkdir(parents=True) 1159 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1159 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1160 1160 1161 1161 # Save entity with custom fields and detached flag 1162 1162 test_entities = [ ··· 1194 1194 facet_path = tmp_path / "facets" / "test_facet" 1195 1195 entities_dir = facet_path / "entities" 1196 1196 entities_dir.mkdir(parents=True) 1197 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1197 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1198 1198 1199 1199 # Create detected entity for a specific day 1200 1200 detected_entities = [ ··· 1261 1261 1262 1262 def test_entity_memory_path(fixture_journal, tmp_path): 1263 1263 """Test entity memory path generation.""" 1264 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1264 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1265 1265 1266 1266 path = entity_memory_path("personal", "Alice Johnson") 1267 1267 expected = tmp_path / "facets" / "personal" / "entities" / "alice_johnson" ··· 1270 1270 1271 1271 def test_entity_memory_path_empty_name(fixture_journal, tmp_path): 1272 1272 """Test entity memory path with empty name raises ValueError.""" 1273 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1273 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1274 1274 1275 1275 with pytest.raises(ValueError, match="slugifies to empty string"): 1276 1276 entity_memory_path("personal", "") ··· 1278 1278 1279 1279 def test_ensure_entity_memory(fixture_journal, tmp_path): 1280 1280 """Test entity memory folder creation.""" 1281 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1281 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1282 1282 1283 1283 folder = ensure_entity_memory("personal", "Bob Smith") 1284 1284 assert folder.exists() ··· 1288 1288 1289 1289 def test_ensure_entity_memory_idempotent(fixture_journal, tmp_path): 1290 1290 """Test that ensure_entity_memory is idempotent.""" 1291 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1291 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1292 1292 1293 1293 folder1 = ensure_entity_memory("personal", "Charlie Brown") 1294 1294 folder2 = ensure_entity_memory("personal", "Charlie Brown") ··· 1298 1298 1299 1299 def test_rename_entity_memory(fixture_journal, tmp_path): 1300 1300 """Test renaming entity memory folder.""" 1301 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1301 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1302 1302 1303 1303 # Create original folder 1304 1304 old_folder = ensure_entity_memory("work", "Alice Johnson") ··· 1322 1322 1323 1323 def test_rename_entity_memory_not_exists(fixture_journal, tmp_path): 1324 1324 """Test renaming non-existent folder returns False.""" 1325 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1325 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1326 1326 1327 1327 result = rename_entity_memory("work", "NonExistent", "NewName") 1328 1328 assert result is False ··· 1330 1330 1331 1331 def test_rename_entity_memory_same_normalized(fixture_journal, tmp_path): 1332 1332 """Test renaming when normalized names are the same.""" 1333 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1333 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1334 1334 1335 1335 # Create folder 1336 1336 ensure_entity_memory("work", "Alice Johnson") ··· 1342 1342 1343 1343 def test_rename_entity_memory_target_exists(fixture_journal, tmp_path): 1344 1344 """Test renaming when target folder already exists raises OSError.""" 1345 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1345 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1346 1346 1347 1347 # Create both folders 1348 1348 ensure_entity_memory("work", "Alice") ··· 1565 1565 """Test touch_entity updates last_seen on attached entity.""" 1566 1566 facet_path = tmp_path / "facets" / "test_facet" 1567 1567 facet_path.mkdir(parents=True) 1568 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1568 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1569 1569 1570 1570 # Create attached entity without last_seen 1571 1571 entities = [ ··· 1587 1587 """Test touch_entity only updates if day is more recent.""" 1588 1588 facet_path = tmp_path / "facets" / "test_facet" 1589 1589 facet_path.mkdir(parents=True) 1590 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1590 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1591 1591 1592 1592 # Create attached entity with existing last_seen 1593 1593 entities = [ ··· 1623 1623 """Test touch_entity returns False when entity not found.""" 1624 1624 facet_path = tmp_path / "facets" / "test_facet" 1625 1625 facet_path.mkdir(parents=True) 1626 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1626 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1627 1627 1628 1628 # Create attached entity 1629 1629 entities = [ ··· 1640 1640 """Test touch_entity skips detached entities.""" 1641 1641 facet_path = tmp_path / "facets" / "test_facet" 1642 1642 facet_path.mkdir(parents=True) 1643 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1643 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1644 1644 1645 1645 # Create detached entity 1646 1646 entities = [ ··· 1666 1666 facet_path = tmp_path / "facets" / "test_facet" 1667 1667 entities_dir = facet_path / "entities" 1668 1668 entities_dir.mkdir(parents=True) 1669 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1669 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1670 1670 1671 1671 # Create attached entity 1672 1672 attached = [ ··· 1707 1707 facet_path = tmp_path / "facets" / "test_facet" 1708 1708 entities_dir = facet_path / "entities" 1709 1709 entities_dir.mkdir(parents=True) 1710 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1710 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1711 1711 1712 1712 # Create attached entity 1713 1713 attached = [ ··· 1739 1739 1740 1740 def test_parse_knowledge_graph_entities(tmp_path): 1741 1741 """Test parsing entity names from knowledge graph markdown.""" 1742 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1742 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1743 1743 1744 1744 # Create a knowledge graph file 1745 1745 day_dir = tmp_path / "20260108" / "agents" ··· 1780 1780 1781 1781 def test_parse_knowledge_graph_entities_missing_file(tmp_path): 1782 1782 """Test parsing returns empty list when KG doesn't exist.""" 1783 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1783 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1784 1784 1785 1785 entities = parse_knowledge_graph_entities("20260108") 1786 1786 assert entities == [] ··· 1788 1788 1789 1789 def test_parse_knowledge_graph_entities_empty_file(tmp_path): 1790 1790 """Test parsing returns empty list for empty KG.""" 1791 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1791 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1792 1792 1793 1793 day_dir = tmp_path / "20260108" / "agents" 1794 1794 day_dir.mkdir(parents=True) ··· 1805 1805 """Test updating last_seen from activity names.""" 1806 1806 facet_path = tmp_path / "facets" / "test_facet" 1807 1807 facet_path.mkdir(parents=True) 1808 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1808 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1809 1809 1810 1810 # Create attached entities 1811 1811 attached = [ ··· 1845 1845 """Test with empty names list.""" 1846 1846 facet_path = tmp_path / "facets" / "test_facet" 1847 1847 facet_path.mkdir(parents=True) 1848 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1848 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1849 1849 1850 1850 attached = [{"type": "Person", "name": "Alice", "description": "Test"}] 1851 1851 save_entities("test_facet", attached) ··· 1861 1861 """Test with no attached entities.""" 1862 1862 facet_path = tmp_path / "facets" / "test_facet" 1863 1863 facet_path.mkdir(parents=True) 1864 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1864 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1865 1865 1866 1866 result = touch_entities_from_activity("test_facet", ["Alice"], "20260108") 1867 1867 ··· 1874 1874 """Test that same entity matched multiple times is only updated once.""" 1875 1875 facet_path = tmp_path / "facets" / "test_facet" 1876 1876 facet_path.mkdir(parents=True) 1877 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1877 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1878 1878 1879 1879 attached = [ 1880 1880 { ··· 1902 1902 1903 1903 def test_observations_file_path(fixture_journal, tmp_path): 1904 1904 """Test observations file path generation.""" 1905 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1905 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1906 1906 1907 1907 path = observations_file_path("personal", "Alice Johnson") 1908 1908 expected = ( ··· 1918 1918 1919 1919 def test_load_observations_empty(fixture_journal, tmp_path): 1920 1920 """Test loading observations for entity with no observations.""" 1921 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1921 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1922 1922 1923 1923 # No file exists yet 1924 1924 observations = load_observations("personal", "Alice Johnson") ··· 1927 1927 1928 1928 def test_save_and_load_observations(fixture_journal, tmp_path): 1929 1929 """Test saving and loading observations.""" 1930 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1930 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1931 1931 1932 1932 # Save observations 1933 1933 test_observations = [ ··· 1951 1951 1952 1952 def test_add_observation_success(fixture_journal, tmp_path): 1953 1953 """Test adding observations sequentially.""" 1954 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1954 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1955 1955 1956 1956 result = add_observation( 1957 1957 "personal", "Alice", "Prefers async communication", "20250113" ··· 1973 1973 1974 1974 def test_add_observation_empty_content(fixture_journal, tmp_path): 1975 1975 """Test adding observation with empty content fails.""" 1976 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1976 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1977 1977 1978 1978 with pytest.raises(ValueError, match="cannot be empty"): 1979 1979 add_observation("personal", "Alice", "") ··· 1984 1984 1985 1985 def test_observations_with_entity_rename(fixture_journal, tmp_path): 1986 1986 """Test that observations are preserved when entity memory folder is renamed.""" 1987 - os.environ["JOURNAL_PATH"] = str(tmp_path) 1987 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1988 1988 1989 1989 # Create entity memory folder and add observations 1990 1990 ensure_entity_memory("work", "Alice Johnson") ··· 2010 2010 2011 2011 def test_observations_atomic_write(fixture_journal, tmp_path): 2012 2012 """Test that observations are written atomically.""" 2013 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2013 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2014 2014 2015 2015 # Save observations 2016 2016 test_observations = [ ··· 2041 2041 """Test extracting identity names from journal config.""" 2042 2042 import json 2043 2043 2044 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2044 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2045 2045 2046 2046 # Create config with identity 2047 2047 config_dir = tmp_path / "config" ··· 2062 2062 2063 2063 def test_get_identity_names_no_config(tmp_path): 2064 2064 """Test that missing config returns empty list.""" 2065 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2065 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2066 2066 # No config file 2067 2067 2068 2068 names = get_identity_names() ··· 2073 2073 """Test that empty identity config returns empty list.""" 2074 2074 import json 2075 2075 2076 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2076 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2077 2077 2078 2078 config_dir = tmp_path / "config" 2079 2079 config_dir.mkdir() ··· 2088 2088 """Test that save_entities flags an entity as principal when it matches identity name.""" 2089 2089 import json 2090 2090 2091 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2091 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2092 2092 2093 2093 # Create config with identity 2094 2094 config_dir = tmp_path / "config" ··· 2122 2122 """Test that save_entities flags principal when matching preferred name.""" 2123 2123 import json 2124 2124 2125 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2125 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2126 2126 2127 2127 # Create config with identity - preferred name differs from entity name 2128 2128 config_dir = tmp_path / "config" ··· 2149 2149 """Test that save_entities flags principal when matching an alias.""" 2150 2150 import json 2151 2151 2152 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2152 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2153 2153 2154 2154 # Create config with alias 2155 2155 config_dir = tmp_path / "config" ··· 2175 2175 """Test that save_entities flags principal when entity aka matches identity.""" 2176 2176 import json 2177 2177 2178 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2178 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2179 2179 2180 2180 # Create config 2181 2181 config_dir = tmp_path / "config" ··· 2206 2206 """Test that save_entities doesn't change principal if one already exists.""" 2207 2207 import json 2208 2208 2209 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2209 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2210 2210 2211 2211 # Create config 2212 2212 config_dir = tmp_path / "config" ··· 2242 2242 2243 2243 def test_save_entities_no_principal_without_identity(tmp_path): 2244 2244 """Test that save_entities doesn't flag principal when no identity configured.""" 2245 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2245 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2246 2246 # No config file 2247 2247 2248 2248 # Create facet directory ··· 2262 2262 """Test that detached entities are not flagged as principal.""" 2263 2263 import json 2264 2264 2265 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2265 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2266 2266 2267 2267 # Create config 2268 2268 config_dir = tmp_path / "config" ··· 2300 2300 """Test that principal matching is case-insensitive.""" 2301 2301 import json 2302 2302 2303 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2303 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2304 2304 2305 2305 # Create config with lowercase name 2306 2306 config_dir = tmp_path / "config" ··· 2326 2326 """Test that save_entities with day (detected) doesn't flag principal.""" 2327 2327 import json 2328 2328 2329 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2329 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2330 2330 2331 2331 # Create config 2332 2332 config_dir = tmp_path / "config" ··· 2358 2358 """Test blocking a journal entity sets blocked flag and detaches facets.""" 2359 2359 import json 2360 2360 2361 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2361 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2362 2362 2363 2363 # Create journal entity 2364 2364 entity_dir = tmp_path / "entities" / "alice" ··· 2391 2391 2392 2392 def test_block_journal_entity_not_found(tmp_path): 2393 2393 """Test blocking non-existent entity raises error.""" 2394 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2394 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2395 2395 2396 2396 with pytest.raises(ValueError, match="not found"): 2397 2397 block_journal_entity("nonexistent") ··· 2401 2401 """Test blocking principal entity is rejected.""" 2402 2402 import json 2403 2403 2404 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2404 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2405 2405 2406 2406 # Create principal entity 2407 2407 entity_dir = tmp_path / "entities" / "myself" ··· 2422 2422 """Test unblocking a blocked journal entity.""" 2423 2423 import json 2424 2424 2425 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2425 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2426 2426 2427 2427 # Create blocked entity 2428 2428 entity_dir = tmp_path / "entities" / "alice" ··· 2444 2444 """Test unblocking an entity that isn't blocked raises error.""" 2445 2445 import json 2446 2446 2447 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2447 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2448 2448 2449 2449 entity_dir = tmp_path / "entities" / "alice" 2450 2450 entity_dir.mkdir(parents=True) ··· 2464 2464 """Test deleting a journal entity removes all data.""" 2465 2465 import json 2466 2466 2467 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2467 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2468 2468 2469 2469 # Create journal entity with observations 2470 2470 entity_dir = tmp_path / "entities" / "alice" ··· 2494 2494 """Test deleting principal entity is rejected.""" 2495 2495 import json 2496 2496 2497 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2497 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2498 2498 2499 2499 # Create principal entity 2500 2500 entity_dir = tmp_path / "entities" / "myself" ··· 2515 2515 """Test that load_entities excludes blocked entities by default.""" 2516 2516 import json 2517 2517 2518 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2518 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2519 2519 2520 2520 # Create journal entities - one normal, one blocked 2521 2521 normal_dir = tmp_path / "entities" / "alice" ··· 2557 2557 """Test that load_entities includes blocked entities when include_blocked=True.""" 2558 2558 import json 2559 2559 2560 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2560 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2561 2561 2562 2562 # Create blocked journal entity 2563 2563 blocked_dir = tmp_path / "entities" / "bob" ··· 2588 2588 """Test that resolve_entity doesn't find blocked entities by default.""" 2589 2589 import json 2590 2590 2591 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2591 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2592 2592 2593 2593 # Create blocked journal entity 2594 2594 blocked_dir = tmp_path / "entities" / "bob" ··· 2617 2617 """Test that resolve_entity finds blocked entities when include_blocked=True.""" 2618 2618 import json 2619 2619 2620 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2620 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2621 2621 2622 2622 # Create blocked journal entity 2623 2623 blocked_dir = tmp_path / "entities" / "bob" ··· 2648 2648 """Test that load_all_attached_entities excludes blocked entities.""" 2649 2649 import json 2650 2650 2651 - os.environ["JOURNAL_PATH"] = str(tmp_path) 2651 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2652 2652 2653 2653 # Create journal entities - one normal, one blocked 2654 2654 normal_dir = tmp_path / "entities" / "alice"
+2 -2
tests/test_entity_agents.py
··· 12 12 13 13 @pytest.fixture 14 14 def fixture_journal(): 15 - """Set JOURNAL_PATH to tests/fixtures/journal for testing.""" 16 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 15 + """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 16 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 17 17 yield 18 18 # No cleanup needed - just testing reads 19 19
+1 -1
tests/test_entity_intelligence.py
··· 14 14 15 15 @pytest.fixture(autouse=True) 16 16 def fixture_journal(): 17 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 17 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 18 18 yield 19 19 20 20
+7 -7
tests/test_events.py
··· 32 32 json.dumps({"title": "Gym session", "start": "18:00:00"}) + "\n" 33 33 ) 34 34 35 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 35 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 36 36 37 37 result = get_month_event_counts("202401") 38 38 ··· 66 66 + "\n" 67 67 ) 68 68 69 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 69 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 70 70 71 71 result = get_month_event_counts("202512") 72 72 ··· 97 97 + "\n" 98 98 ) 99 99 100 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 100 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 101 101 102 102 result = get_month_event_counts("202401") 103 103 ··· 114 114 work_events = journal / "facets" / "work" / "events" 115 115 work_events.mkdir(parents=True) 116 116 117 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 117 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 118 118 119 119 result = get_month_event_counts("202402") 120 120 ··· 125 125 """Test that empty journal directory returns empty dict.""" 126 126 from think.events import get_month_event_counts 127 127 128 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 128 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 129 129 130 130 result = get_month_event_counts("202401") 131 131 ··· 153 153 + "\n" 154 154 ) 155 155 156 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 156 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 157 157 158 158 result = get_month_event_counts("202401") 159 159 ··· 172 172 json.dumps({"title": "Gym", "start": "18:00"}) + "\n" 173 173 ) 174 174 175 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 175 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 176 176 177 177 result = get_month_event_counts("202401") 178 178
+1 -1
tests/test_facet_news.py
··· 62 62 body="Older summary entry for the facet.", 63 63 ) 64 64 65 - with patch.dict("os.environ", {"JOURNAL_PATH": str(journal_path)}): 65 + with patch.dict("os.environ", {"_SOLSTONE_JOURNAL_OVERRIDE": str(journal_path)}): 66 66 from think.facets import get_facet_news 67 67 68 68 first_page = get_facet_news("test-facet")
+1 -1
tests/test_facet_rename.py
··· 13 13 @pytest.fixture 14 14 def journal(tmp_path, monkeypatch): 15 15 """Create a minimal journal with a facet for rename tests.""" 16 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 16 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 17 17 18 18 # Create facet directory with facet.json 19 19 facet_dir = tmp_path / "facets" / "old-name"
+32 -32
tests/test_facets.py
··· 65 65 66 66 def test_facet_summary_full(monkeypatch): 67 67 """Test facet_summary with full metadata.""" 68 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 68 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 69 69 70 70 summary = facet_summary("full-featured") 71 71 ··· 94 94 95 95 def test_facet_summary_short_mode(monkeypatch): 96 96 """Test facet_summary with detailed=False shows names only.""" 97 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 97 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 98 98 99 99 summary = facet_summary("full-featured", detailed=False) 100 100 ··· 118 118 119 119 def test_facet_summary_minimal(monkeypatch): 120 120 """Test facet_summary with minimal metadata.""" 121 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 121 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 122 122 123 123 summary = facet_summary("minimal-facet") 124 124 ··· 133 133 134 134 def test_facet_summary_test_facet(monkeypatch): 135 135 """Test facet_summary with the existing test-facet fixture.""" 136 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 136 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 137 137 138 138 summary = facet_summary("test-facet") 139 139 ··· 149 149 150 150 def test_facet_summary_nonexistent(monkeypatch): 151 151 """Test facet_summary with nonexistent facet.""" 152 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 152 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 153 153 154 154 with pytest.raises(FileNotFoundError, match="Facet 'nonexistent' not found"): 155 155 facet_summary("nonexistent") ··· 157 157 158 158 def test_facet_summary_empty_journal(tmp_path, monkeypatch): 159 159 """Test facet_summary raises FileNotFoundError with empty journal.""" 160 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 160 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 161 161 162 162 with pytest.raises(FileNotFoundError, match="not found"): 163 163 facet_summary("any-facet") ··· 165 165 166 166 def test_facet_summary_missing_facet_json(monkeypatch): 167 167 """Test facet_summary with missing facet.json.""" 168 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 168 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 169 169 170 170 with pytest.raises(FileNotFoundError, match="facet.json not found"): 171 171 facet_summary("broken-facet") ··· 173 173 174 174 def test_facet_summary_empty_entities(monkeypatch): 175 175 """Test facet_summary with empty entities file.""" 176 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 176 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 177 177 178 178 summary = facet_summary("empty-entities") 179 179 ··· 183 183 184 184 def test_get_facets_with_entities(monkeypatch): 185 185 """Test that get_facets() returns metadata and load_entity_names() works with facets.""" 186 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 186 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 187 187 188 188 facets = get_facets() 189 189 ··· 219 219 220 220 def test_get_facets_empty_entities(monkeypatch): 221 221 """Test get_facets() with facet that has no entities.""" 222 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 222 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 223 223 224 224 facets = get_facets() 225 225 ··· 238 238 239 239 def test_facet_summaries(monkeypatch): 240 240 """Test facet_summaries() generates correct agent prompt format.""" 241 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 241 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 242 242 243 243 summary = facet_summaries() 244 244 ··· 285 285 encoding="utf-8", 286 286 ) 287 287 288 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 288 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 289 289 290 290 summary = facet_summaries() 291 291 ··· 297 297 """Test facet_summaries() when no facets exist.""" 298 298 empty_journal = tmp_path / "empty_journal" 299 299 empty_journal.mkdir() 300 - monkeypatch.setenv("JOURNAL_PATH", str(empty_journal)) 300 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(empty_journal)) 301 301 302 302 summary = facet_summaries() 303 303 assert summary == "No facets found." ··· 305 305 306 306 def test_facet_summaries_empty_journal(tmp_path, monkeypatch): 307 307 """Test facet_summaries() returns 'No facets found' with empty journal.""" 308 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 308 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 309 309 310 310 summary = facet_summaries() 311 311 assert summary == "No facets found." ··· 313 313 314 314 def test_facet_summaries_mixed_entities(monkeypatch): 315 315 """Test facet_summaries() with facets having different entity configurations.""" 316 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 316 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 317 317 318 318 summary = facet_summaries() 319 319 ··· 369 369 ) 370 370 ) 371 371 372 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 372 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 373 373 374 374 active = get_active_facets("20240115") 375 375 ··· 391 391 seg2.mkdir(parents=True) 392 392 (seg2 / "facets.json").write_text("") 393 393 394 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 394 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 395 395 396 396 active = get_active_facets("20240115") 397 397 ··· 403 403 journal = tmp_path / "journal" 404 404 (journal / "20240115").mkdir(parents=True) 405 405 406 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 406 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 407 407 408 408 active = get_active_facets("20240115") 409 409 ··· 415 415 journal = tmp_path / "journal" 416 416 journal.mkdir() 417 417 418 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 418 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 419 419 420 420 active = get_active_facets("20240115") 421 421 ··· 443 443 ) 444 444 ) 445 445 446 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 446 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 447 447 448 448 active = get_active_facets("20240115") 449 449 ··· 459 459 """Test _get_principal_display_name returns preferred name.""" 460 460 import json 461 461 462 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 462 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 463 463 464 464 config_dir = tmp_path / "config" 465 465 config_dir.mkdir() ··· 473 473 """Test _get_principal_display_name falls back to name when no preferred.""" 474 474 import json 475 475 476 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 476 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 477 477 478 478 config_dir = tmp_path / "config" 479 479 config_dir.mkdir() ··· 485 485 486 486 def test_get_principal_display_name_none_when_empty(tmp_path, monkeypatch): 487 487 """Test _get_principal_display_name returns None when identity empty.""" 488 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 488 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 489 489 # No config file 490 490 491 491 assert _get_principal_display_name() is None ··· 495 495 """Test _format_principal_role extracts and formats principal.""" 496 496 import json 497 497 498 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 498 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 499 499 500 500 config_dir = tmp_path / "config" 501 501 config_dir.mkdir() ··· 516 516 517 517 def test_format_principal_role_no_principal(tmp_path, monkeypatch): 518 518 """Test _format_principal_role returns None when no principal.""" 519 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 519 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 520 520 521 521 entities = [ 522 522 {"name": "Alice", "description": "Friend"}, ··· 533 533 """Test _format_principal_role returns None when principal has no description.""" 534 534 import json 535 535 536 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 536 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 537 537 538 538 config_dir = tmp_path / "config" 539 539 config_dir.mkdir() ··· 555 555 556 556 def test_format_principal_role_no_identity(tmp_path, monkeypatch): 557 557 """Test _format_principal_role returns None when no identity configured.""" 558 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 558 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 559 559 # No config file 560 560 561 561 entities = [ ··· 573 573 574 574 def test_facet_summary_with_principal(tmp_path, monkeypatch): 575 575 """Test facet_summary shows principal role and excludes from entities list.""" 576 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 576 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 577 577 578 578 # Create identity config 579 579 config_dir = tmp_path / "config" ··· 615 615 616 616 def test_facet_summary_principal_only_entity(tmp_path, monkeypatch): 617 617 """Test facet_summary when principal is the only entity.""" 618 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 618 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 619 619 620 620 # Create identity config 621 621 config_dir = tmp_path / "config" ··· 650 650 651 651 def test_facet_summaries_detailed_with_principal(tmp_path, monkeypatch): 652 652 """Test facet_summaries detailed mode shows principal role.""" 653 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 653 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 654 654 655 655 # Create identity config 656 656 config_dir = tmp_path / "config" ··· 690 690 691 691 def test_facet_summaries_simple_mode_with_principal(tmp_path, monkeypatch): 692 692 """Test facet_summaries simple mode also filters principal consistently.""" 693 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 693 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 694 694 695 695 # Create identity config 696 696 config_dir = tmp_path / "config" ··· 728 728 729 729 def test_facet_summaries_detailed_with_activities(monkeypatch): 730 730 """Test facet_summaries detailed mode includes activity details.""" 731 - monkeypatch.setenv("JOURNAL_PATH", str(FIXTURES_PATH)) 731 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 732 732 733 733 summary = facet_summaries(detailed=True) 734 734
+29 -13
tests/test_formatters.py
··· 9 9 10 10 import pytest 11 11 12 - # Set JOURNAL_PATH to fixtures for tests 13 - os.environ["JOURNAL_PATH"] = str(Path(__file__).parent / "fixtures" / "journal") 12 + # Set _SOLSTONE_JOURNAL_OVERRIDE to fixtures for tests 13 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str( 14 + Path(__file__).parent / "fixtures" / "journal" 15 + ) 14 16 15 17 16 18 class TestRegistry: ··· 86 88 from think.formatters import load_jsonl 87 89 88 90 path = ( 89 - Path(os.environ["JOURNAL_PATH"]) / "20240101/default/123456_300/audio.jsonl" 91 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 92 + / "20240101/default/123456_300/audio.jsonl" 90 93 ) 91 94 entries = load_jsonl(path) 92 95 ··· 140 143 from think.formatters import format_file 141 144 142 145 path = ( 143 - Path(os.environ["JOURNAL_PATH"]) 146 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 144 147 / "20240102/default/234567_300/screen.jsonl" 145 148 ) 146 149 chunks, meta = format_file(path) ··· 157 160 from think.formatters import format_file 158 161 159 162 path = ( 160 - Path(os.environ["JOURNAL_PATH"]) / "20240101/default/123456_300/audio.jsonl" 163 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 164 + / "20240101/default/123456_300/audio.jsonl" 161 165 ) 162 166 chunks, meta = format_file(path) 163 167 ··· 194 198 from think.formatters import format_file 195 199 196 200 # Create a file under journal that won't match any pattern 197 - journal_path = Path(os.environ["JOURNAL_PATH"]) 201 + journal_path = Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 198 202 temp_file = journal_path / "unknown_file.txt" 199 203 temp_file.write_text("test content") 200 204 ··· 466 470 from observe.hear import load_transcript 467 471 468 472 path = ( 469 - Path(os.environ["JOURNAL_PATH"]) / "20240101/default/123456_300/audio.jsonl" 473 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 474 + / "20240101/default/123456_300/audio.jsonl" 470 475 ) 471 476 metadata, entries, formatted_text = load_transcript(path) 472 477 ··· 494 499 from think.formatters import format_file 495 500 496 501 path = ( 497 - Path(os.environ["JOURNAL_PATH"]) / "facets/personal/entities/20250101.jsonl" 502 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 503 + / "facets/personal/entities/20250101.jsonl" 498 504 ) 499 505 chunks, meta = format_file(path) 500 506 ··· 787 793 from think.formatters import format_file 788 794 789 795 path = ( 790 - Path(os.environ["JOURNAL_PATH"]) 796 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 791 797 / "facets/personal/entities/alice_johnson/observations.jsonl" 792 798 ) 793 799 chunks, meta = format_file(path) ··· 821 827 """Test basic todos formatting with fixture file.""" 822 828 from think.formatters import format_file 823 829 824 - path = Path(os.environ["JOURNAL_PATH"]) / "facets/personal/todos/20240101.jsonl" 830 + path = ( 831 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 832 + / "facets/personal/todos/20240101.jsonl" 833 + ) 825 834 chunks, meta = format_file(path) 826 835 827 836 assert len(chunks) == 4 # 4 items in fixture ··· 955 964 """Test basic events formatting with fixture file.""" 956 965 from think.formatters import format_file 957 966 958 - path = Path(os.environ["JOURNAL_PATH"]) / "facets/work/events/20240101.jsonl" 967 + path = ( 968 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 969 + / "facets/work/events/20240101.jsonl" 970 + ) 959 971 chunks, meta = format_file(path) 960 972 961 973 assert len(chunks) == 2 # 2 events in fixture ··· 1268 1280 """Test format_file with a markdown file.""" 1269 1281 from think.formatters import format_file 1270 1282 1271 - path = Path(os.environ["JOURNAL_PATH"]) / "20240101/agents/flow.md" 1283 + path = ( 1284 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) / "20240101/agents/flow.md" 1285 + ) 1272 1286 chunks, meta = format_file(path) 1273 1287 1274 1288 assert len(chunks) > 0 ··· 1279 1293 """Test load_markdown utility.""" 1280 1294 from think.formatters import load_markdown 1281 1295 1282 - path = Path(os.environ["JOURNAL_PATH"]) / "20240101/agents/flow.md" 1296 + path = ( 1297 + Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) / "20240101/agents/flow.md" 1298 + ) 1283 1299 text = load_markdown(path) 1284 1300 1285 1301 assert isinstance(text, str)
+6 -6
tests/test_gemini_importer.py
··· 175 175 176 176 try: 177 177 with tempfile.TemporaryDirectory() as journal: 178 - os.environ["JOURNAL_PATH"] = journal 178 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 179 179 result = importer.process(Path(f.name), Path(journal)) 180 180 assert result.entries_written == 4 181 181 assert result.errors == [] ··· 198 198 assert entries[1]["speaker"] == "Assistant" 199 199 finally: 200 200 os.unlink(f.name) 201 - os.environ.pop("JOURNAL_PATH", None) 201 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 202 202 203 203 204 204 def test_process_zip(): ··· 211 211 ) 212 212 try: 213 213 with tempfile.TemporaryDirectory() as journal: 214 - os.environ["JOURNAL_PATH"] = journal 214 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 215 215 result = importer.process(Path(tmp.name), Path(journal)) 216 216 assert result.entries_written == 2 217 217 assert result.segments is not None ··· 219 219 assert any(Path(p).suffix == ".jsonl" for p in result.files_created) 220 220 finally: 221 221 os.unlink(tmp.name) 222 - os.environ.pop("JOURNAL_PATH", None) 222 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 223 223 224 224 225 225 def test_process_multiple_windows(): ··· 237 237 f.flush() 238 238 try: 239 239 with tempfile.TemporaryDirectory() as journal: 240 - os.environ["JOURNAL_PATH"] = journal 240 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 241 241 result = importer.process(Path(f.name), Path(journal)) 242 242 assert result.entries_written == 4 243 243 assert result.segments is not None ··· 245 245 assert len(result.files_created) == 2 246 246 finally: 247 247 os.unlink(f.name) 248 - os.environ.pop("JOURNAL_PATH", None) 248 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 249 249 250 250 251 251 # --- Registry test ---
+9 -9
tests/test_generate_full.py
··· 22 22 23 23 24 24 def copy_day(tmp_path: Path) -> Path: 25 - os.environ["JOURNAL_PATH"] = str(tmp_path) 25 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 26 26 dest = day_path("20240101") 27 27 src = FIXTURES / "journal" / "20240101" 28 28 # Copy contents from fixture to the day_path created directory ··· 91 91 lambda *a, **k: MOCK_RESULT, 92 92 ) 93 93 monkeypatch.setenv("GOOGLE_API_KEY", "x") 94 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 94 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 95 95 96 96 config = { 97 97 "name": "test_gen", ··· 158 158 lambda *a, **k: MOCK_RESULT, 159 159 ) 160 160 monkeypatch.setenv("GOOGLE_API_KEY", "x") 161 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 161 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 162 162 163 163 config = { 164 164 "name": "hooked_gen", ··· 210 210 lambda *a, **k: MOCK_RESULT, 211 211 ) 212 212 monkeypatch.setenv("GOOGLE_API_KEY", "x") 213 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 213 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 214 214 215 215 config = { 216 216 "name": "nohook_gen", ··· 234 234 mod = importlib.import_module("think.agents") 235 235 copy_day(tmp_path) 236 236 237 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 237 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 238 238 239 239 config = { 240 240 "name": "nonexistent_generator", ··· 255 255 mod = importlib.import_module("think.agents") 256 256 257 257 # Create empty day directory (no transcripts) 258 - os.environ["JOURNAL_PATH"] = str(tmp_path) 258 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 259 259 day_dir = day_path("20240101") 260 260 day_dir.mkdir(parents=True, exist_ok=True) 261 261 ··· 269 269 ) 270 270 271 271 monkeypatch.setenv("GOOGLE_API_KEY", "x") 272 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 272 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 273 273 274 274 config = { 275 275 "name": "empty_gen", ··· 292 292 mod = importlib.import_module("think.agents") 293 293 294 294 # Create empty day directory (no transcripts) 295 - os.environ["JOURNAL_PATH"] = str(tmp_path) 295 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 296 296 day_dir = day_path("20240101") 297 297 day_dir.mkdir(parents=True, exist_ok=True) 298 298 ··· 307 307 ) 308 308 309 309 monkeypatch.setenv("GOOGLE_API_KEY", "x") 310 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 310 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 311 311 312 312 config = mod.prepare_config( 313 313 {
+2 -2
tests/test_generate_scan_day.py
··· 12 12 13 13 14 14 def copy_day(tmp_path: Path) -> Path: 15 - os.environ["JOURNAL_PATH"] = str(tmp_path) 15 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 16 16 dest = day_path("20240101") 17 17 src = FIXTURES / "journal" / "20240101" 18 18 # Copy contents from fixture to the day_path created directory ··· 30 30 def test_scan_day(tmp_path, monkeypatch): 31 31 mod = importlib.import_module("think.agents") 32 32 day_dir = copy_day(tmp_path) 33 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 33 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 34 34 35 35 info = mod.scan_day("20240101") 36 36 assert "agents/flow.md" in info["processed"]
+1 -1
tests/test_get_raw_file.py
··· 8 8 9 9 def test_get_raw_file(tmp_path, monkeypatch): 10 10 utils = importlib.import_module("think.utils") 11 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 11 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 12 12 day_dir = day_path("20240101") 13 13 14 14 # Create segments under default stream
+2 -2
tests/test_google.py
··· 76 76 journal = tmp_path / "journal" 77 77 journal.mkdir() 78 78 79 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 79 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 80 80 monkeypatch.setenv("GOOGLE_API_KEY", "x") 81 81 monkeypatch.setattr( 82 82 "think.providers.cli.shutil.which", ··· 154 154 journal = tmp_path / "journal" 155 155 journal.mkdir() 156 156 157 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 157 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 158 158 monkeypatch.setenv("GOOGLE_API_KEY", "x") 159 159 monkeypatch.setattr("think.providers.cli.shutil.which", lambda _name: None) 160 160
+1 -1
tests/test_google_thinking.py
··· 31 31 journal = tmp_path / "journal" 32 32 journal.mkdir() 33 33 34 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 34 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 35 35 monkeypatch.setenv("GOOGLE_API_KEY", "x") 36 36 monkeypatch.setattr( 37 37 "think.providers.cli.shutil.which",
+3 -3
tests/test_health_cli.py
··· 12 12 13 13 14 14 def test_health_check_no_socket(tmp_path, monkeypatch, capsys): 15 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 15 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 16 17 17 result = health_check() 18 18 ··· 51 51 52 52 53 53 def test_health_check_timeout(tmp_path, monkeypatch, capsys): 54 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 54 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 55 55 sock = tmp_path / "health" / "callosum.sock" 56 56 sock.parent.mkdir(parents=True) 57 57 sock.touch() ··· 71 71 72 72 73 73 def test_health_check_receives_status(tmp_path, monkeypatch, capsys): 74 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 74 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 75 75 sock = tmp_path / "health" / "callosum.sock" 76 76 sock.parent.mkdir(parents=True) 77 77 sock.touch()
+1 -1
tests/test_help_cli.py
··· 16 16 17 17 @pytest.fixture(autouse=True) 18 18 def _set_journal_path(monkeypatch): 19 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 19 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 20 20 21 21 22 22 def _make_popen(stdout_lines, *, returncode=0):
+6 -6
tests/test_import_dedup.py
··· 137 137 def test_reimport_same_entries_no_duplicates(): 138 138 """Re-importing identical entries should not create duplicates.""" 139 139 with tempfile.TemporaryDirectory() as journal: 140 - os.environ["JOURNAL_PATH"] = journal 140 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 141 141 try: 142 142 entries = [ 143 143 { ··· 172 172 header2 = json.loads(lines2[0]) 173 173 assert header2["entry_count"] == 2 174 174 finally: 175 - os.environ.pop("JOURNAL_PATH", None) 175 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 176 176 177 177 178 178 def test_reimport_with_new_entries_merges(): 179 179 """Re-importing with new entries should merge (add new, keep old).""" 180 180 with tempfile.TemporaryDirectory() as journal: 181 - os.environ["JOURNAL_PATH"] = journal 181 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 182 182 try: 183 183 original = [ 184 184 { ··· 214 214 assert "Standup" in titles 215 215 assert "New meeting" in titles 216 216 finally: 217 - os.environ.pop("JOURNAL_PATH", None) 217 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 218 218 219 219 220 220 def test_first_import_no_merge_needed(): 221 221 """First import should work normally with no existing file.""" 222 222 with tempfile.TemporaryDirectory() as journal: 223 - os.environ["JOURNAL_PATH"] = journal 223 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 224 224 try: 225 225 entries = [ 226 226 { ··· 234 234 assert len(files) == 1 235 235 assert Path(files[0]).exists() 236 236 finally: 237 - os.environ.pop("JOURNAL_PATH", None) 237 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 238 238 239 239 240 240 def test_load_existing_entries():
+5 -5
tests/test_import_formatting.py
··· 324 324 from think.formatters import format_file 325 325 326 326 with tempfile.TemporaryDirectory() as tmpdir: 327 - # Set JOURNAL_PATH for format_file 328 - old_journal = os.environ.get("JOURNAL_PATH") 329 - os.environ["JOURNAL_PATH"] = tmpdir 327 + # Set _SOLSTONE_JOURNAL_OVERRIDE for format_file 328 + old_journal = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 329 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 330 330 331 331 try: 332 332 import_dir = Path(tmpdir) / "20260115" / "import.ics" ··· 348 348 assert meta["indexer"]["agent"] == "import.ics" 349 349 finally: 350 350 if old_journal is not None: 351 - os.environ["JOURNAL_PATH"] = old_journal 351 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = old_journal 352 352 else: 353 - os.environ.pop("JOURNAL_PATH", None) 353 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None)
+17 -17
tests/test_importer.py
··· 93 93 txt = tmp_path / "sample.txt" 94 94 txt.write_text(transcript) 95 95 96 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 96 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 97 97 monkeypatch.setattr( 98 98 mod, "detect_created", lambda p, **kw: {"day": "20240101", "time": "120000"} 99 99 ) ··· 175 175 pdf = tmp_path / "meeting.pdf" 176 176 pdf.write_bytes(b"%PDF-1.4 fake") 177 177 178 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 178 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 179 179 monkeypatch.setattr( 180 180 mod, "detect_created", lambda p, **kw: {"day": "20251205", "time": "163000"} 181 181 ) ··· 308 308 def test_write_markdown_segments(tmp_path, monkeypatch): 309 309 """write_markdown_segments creates segment dirs with imported.md files.""" 310 310 mod = importlib.import_module("think.importers.shared") 311 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 311 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 312 312 313 313 windows = [ 314 314 ("20260301", "120000_300", [{"text": "hello"}, {"text": "world"}]), ··· 401 401 with zipfile.ZipFile(archive, "w") as zf: 402 402 zf.writestr("conversations.json", json.dumps(conversations)) 403 403 404 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 404 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 405 405 406 406 fixed_dt = dt.datetime(2026, 1, 20, 8, 30, 0) 407 407 ··· 541 541 with zipfile.ZipFile(archive, "w") as zf: 542 542 zf.writestr("conversations.json", json.dumps(conversations)) 543 543 544 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 544 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 545 545 546 546 fixed_dt = dt.datetime(2026, 1, 20, 8, 30, 0) 547 547 ··· 715 715 """Test prepare_audio_segments creates segment directories with audio slices.""" 716 716 mod = importlib.import_module("think.importers.audio") 717 717 718 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 718 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 719 719 720 720 audio_file = tmp_path / "test.mp3" 721 721 audio_file.write_bytes(b"fake audio content") ··· 768 768 """Test prepare_audio_segments handles segment key collisions.""" 769 769 mod = importlib.import_module("think.importers.audio") 770 770 771 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 771 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 772 772 773 773 audio_file = tmp_path / "test.mp3" 774 774 audio_file.write_bytes(b"fake audio content") ··· 816 816 txt = tmp_path / "sample.txt" 817 817 txt.write_text("hello\nworld\n") 818 818 819 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 819 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 820 820 monkeypatch.setattr( 821 821 "sys.argv", 822 822 ["sol import", str(txt), "--timestamp", "20240101_120000", "--dry-run"], ··· 850 850 mp3 = tmp_path / "sample.mp3" 851 851 mp3.write_bytes(b"fake audio") 852 852 853 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 853 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 854 854 monkeypatch.setattr(mod, "_get_audio_duration", lambda p: 420.0) 855 855 callosum_cls = MagicMock() 856 856 monkeypatch.setattr(mod, "CallosumConnection", callosum_cls) ··· 890 890 txt = tmp_path / "notes.txt" 891 891 txt.write_text("meeting notes") 892 892 893 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 893 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 894 894 monkeypatch.setattr( 895 895 mod, "detect_created", lambda p, **kw: {"day": "20240315", "time": "140000"} 896 896 ) ··· 931 931 mock_imp = _make_mock_file_importer() 932 932 callosum = MagicMock() 933 933 934 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 934 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 935 935 monkeypatch.setattr("sys.argv", ["sol import", str(ics_file), "--source", "ics"]) 936 936 monkeypatch.setattr( 937 937 "think.importers.file_importer.get_file_importer", lambda name: mock_imp ··· 967 967 mock_imp = _make_mock_file_importer() 968 968 callosum = MagicMock() 969 969 970 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 970 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 971 971 monkeypatch.setattr( 972 972 "sys.argv", 973 973 [ ··· 1281 1281 END:VCALENDAR""" 1282 1282 ) 1283 1283 1284 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 1284 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1285 1285 1286 1286 result = mod.ICSImporter().process(ics_path, tmp_path, facet="work") 1287 1287 ··· 1373 1373 1374 1374 mock_imp = _make_mock_file_importer() 1375 1375 1376 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 1376 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1377 1377 monkeypatch.setattr( 1378 1378 "sys.argv", 1379 1379 ["sol import", str(ics_file), "--source", "ics", "--dry-run", "--json"], ··· 1413 1413 mock_imp = _make_mock_file_importer() 1414 1414 callosum = MagicMock() 1415 1415 1416 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 1416 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1417 1417 monkeypatch.setattr( 1418 1418 "sys.argv", 1419 1419 ["sol import", str(ics_file), "--source", "ics", "--json"], ··· 1448 1448 1449 1449 mock_imp = _make_mock_file_importer() 1450 1450 1451 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 1451 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1452 1452 monkeypatch.setattr( 1453 1453 "sys.argv", 1454 1454 ["sol import", str(ics_file), "--source", "ics"], ··· 1501 1501 os.utime(note2, (base_ts + 60, base_ts + 60)) # 1 min later, same window 1502 1502 os.utime(note3, (base_ts + 600, base_ts + 600)) # 10 min later, different window 1503 1503 1504 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 1504 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1505 1505 1506 1506 result = mod.ObsidianImporter().process(vault, tmp_path) 1507 1507
+7 -7
tests/test_importer_documents.py
··· 73 73 mod = importlib.import_module("think.importers.documents") 74 74 pdf = tmp_path / "contract.pdf" 75 75 pdf.write_bytes(b"%PDF-1.4") 76 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 76 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 77 77 monkeypatch.setattr(mod, "PdfReader", MockPdfReader) 78 78 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) 79 79 monkeypatch.setattr( ··· 105 105 mod = importlib.import_module("think.importers.documents") 106 106 pdf = tmp_path / "original.pdf" 107 107 pdf.write_bytes(b"%PDF-1.4 fake") 108 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 108 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 109 109 monkeypatch.setattr(mod, "PdfReader", MockPdfReader) 110 110 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) 111 111 monkeypatch.setattr( ··· 134 134 135 135 pdf = tmp_path / "scan.pdf" 136 136 pdf.write_bytes(b"%PDF-1.4") 137 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 137 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 138 138 monkeypatch.setattr(mod, "PdfReader", ScannedReader) 139 139 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) 140 140 monkeypatch.setattr( ··· 176 176 177 177 pdf = tmp_path / "scan.pdf" 178 178 pdf.write_bytes(b"%PDF-1.4") 179 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 179 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 180 180 monkeypatch.setattr(mod, "PdfReader", ScannedReader) 181 181 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) 182 182 monkeypatch.setattr( ··· 217 217 218 218 pdf = tmp_path / "scan.pdf" 219 219 pdf.write_bytes(b"%PDF-1.4") 220 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 220 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 221 221 monkeypatch.setattr(mod, "PdfReader", ScannedReader) 222 222 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) 223 223 monkeypatch.setattr( ··· 260 260 pdf_b = tmp_path / "b.pdf" 261 261 pdf_a.write_bytes(b"%PDF-1.4") 262 262 pdf_b.write_bytes(b"%PDF-1.4") 263 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 263 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 264 264 monkeypatch.setattr(mod, "PdfReader", MockPdfReader) 265 265 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) 266 266 monkeypatch.setattr( ··· 291 291 292 292 pdf = tmp_path / "parties.pdf" 293 293 pdf.write_bytes(b"%PDF-1.4") 294 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 294 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 295 295 monkeypatch.setattr(mod, "PdfReader", EntityReader) 296 296 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / day) 297 297 monkeypatch.setattr(
+9 -9
tests/test_importer_granola.py
··· 498 498 from think.importers.sync import load_sync_state 499 499 500 500 # Point journal to tmp_path 501 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 501 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 502 502 503 503 muesli_dir = tmp_path / "muesli" 504 504 _write_transcript(muesli_dir, "2025-10-28_q1.md", SAMPLE_TRANSCRIPT) ··· 561 561 from think.importers.cli import main 562 562 563 563 monkeypatch.setattr(sys, "argv", ["sol import", "--backends"]) 564 - monkeypatch.setenv("JOURNAL_PATH", "/tmp/test-journal") 564 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 565 565 main() 566 566 captured = capsys.readouterr() 567 567 assert "granola" in captured.out ··· 581 581 "argv", 582 582 ["sol import", "--sync", "granola", "--path", str(muesli_dir)], 583 583 ) 584 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 584 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 585 585 586 586 main() 587 587 captured = capsys.readouterr() ··· 630 630 from think.entities.observations import load_observations 631 631 from think.importers.granola import GranolaBackend 632 632 633 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 633 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 634 634 635 635 muesli_dir = tmp_path / "muesli" 636 636 _write_transcript(muesli_dir, "2025-10-28_enriched.md", ENRICHED_TRANSCRIPT) ··· 671 671 from think.entities.observations import load_observations 672 672 from think.importers.granola import GranolaBackend 673 673 674 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 674 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 675 675 676 676 muesli_dir = tmp_path / "muesli" 677 677 _write_transcript(muesli_dir, "2025-10-28_enriched.md", ENRICHED_TRANSCRIPT) ··· 697 697 from think.entities.observations import load_observations 698 698 from think.importers.granola import GranolaBackend 699 699 700 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 700 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 701 701 702 702 muesli_dir = tmp_path / "muesli" 703 703 _write_transcript(muesli_dir, "2025-10-28_enriched.md", ENRICHED_TRANSCRIPT) ··· 713 713 """seed_entities() works unchanged when no observations are provided.""" 714 714 from think.importers.shared import seed_entities 715 715 716 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 716 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 717 717 718 718 entities = [ 719 719 {"name": "Test Person", "type": "Person", "email": "test@example.com"}, ··· 728 728 from think.entities.observations import load_observations 729 729 from think.importers.shared import seed_entities 730 730 731 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 731 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 732 732 733 733 entities = [ 734 734 # title + company ··· 786 786 from think.entities.observations import load_observations 787 787 from think.importers.shared import seed_entities 788 788 789 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 789 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 790 790 791 791 entities = [ 792 792 {
+12 -12
tests/test_importer_obsidian_sync.py
··· 96 96 from think.importers.obsidian import ObsidianSyncBackend 97 97 from think.importers.sync import load_sync_state 98 98 99 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 99 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 100 100 vault = tmp_path / "vault" 101 101 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 102 102 ··· 118 118 from think.importers.obsidian import ObsidianSyncBackend 119 119 from think.importers.sync import load_sync_state 120 120 121 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 121 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 122 122 vault = tmp_path / "vault" 123 123 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 124 124 ··· 149 149 """Mtime-only changes are skipped when content hash matches.""" 150 150 from think.importers.obsidian import ObsidianSyncBackend 151 151 152 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 152 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 153 153 vault = tmp_path / "vault" 154 154 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 155 155 ··· 166 166 from think.importers.obsidian import ObsidianSyncBackend 167 167 from think.importers.sync import load_sync_state 168 168 169 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 169 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 170 170 vault = tmp_path / "vault" 171 171 note = _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 172 172 ··· 184 184 """Force re-detects notes by clearing state.""" 185 185 from think.importers.obsidian import ObsidianSyncBackend 186 186 187 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 187 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 188 188 vault = tmp_path / "vault" 189 189 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 190 190 ··· 214 214 """Wikilinks are converted into Topic entities on import.""" 215 215 from think.importers.obsidian import ObsidianSyncBackend 216 216 217 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 217 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 218 218 vault = tmp_path / "vault" 219 219 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 220 220 ··· 247 247 """Incremental sync imports only newly added notes.""" 248 248 from think.importers.obsidian import ObsidianSyncBackend 249 249 250 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 250 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 251 251 vault = tmp_path / "vault" 252 252 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 253 253 ··· 341 341 """Notes in typed folders produce typed entities.""" 342 342 from think.importers.obsidian import ObsidianSyncBackend 343 343 344 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 344 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 345 345 vault = tmp_path / "vault" 346 346 347 347 _write_note( ··· 376 376 """Wikilinks with @ prefix produce Person entities.""" 377 377 from think.importers.obsidian import ObsidianSyncBackend 378 378 379 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 379 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 380 380 vault = tmp_path / "vault" 381 381 382 382 _write_note( ··· 408 408 """Numeric-prefixed folder names are matched after stripping.""" 409 409 from think.importers.obsidian import ObsidianSyncBackend 410 410 411 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 411 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 412 412 vault = tmp_path / "vault" 413 413 414 414 _write_note(vault, "00 People/Jane.md", "# Jane\nA person.", mtime=1_700_000_000) ··· 511 511 from think.importers.obsidian import ObsidianSyncBackend 512 512 from think.importers.sync import load_sync_state 513 513 514 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 514 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 515 515 vault = tmp_path / "vault" 516 516 _write_note(vault, "Notes/real.md", SAMPLE_NOTE, mtime=1_700_000_000) 517 517 _write_note( ··· 541 541 from think.importers.cli import main 542 542 543 543 monkeypatch.setattr(sys, "argv", ["sol import", "--backends"]) 544 - monkeypatch.setenv("JOURNAL_PATH", "/tmp/test-journal") 544 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 545 545 546 546 main() 547 547 captured = capsys.readouterr()
+2 -2
tests/test_importer_sync.py
··· 95 95 from think.importers.cli import main 96 96 97 97 monkeypatch.setattr(sys, "argv", ["sol import", "--backends"]) 98 - monkeypatch.setenv("JOURNAL_PATH", "/tmp/test-journal") 98 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 99 99 main() 100 100 captured = capsys.readouterr() 101 101 assert "plaud" in captured.out ··· 419 419 from think.importers.cli import main 420 420 421 421 monkeypatch.setattr(sys, "argv", ["sol import", "--sync", "plaud"]) 422 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 422 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 423 423 monkeypatch.setenv("PLAUD_ACCESS_TOKEN", "test-token") 424 424 425 425 with patch("think.importers.plaud.list_files", side_effect=_mock_list_files):
+36 -36
tests/test_journal_index.py
··· 78 78 def journal_fixture(tmp_path): 79 79 """Create a temporary journal with test data.""" 80 80 journal = tmp_path 81 - os.environ["JOURNAL_PATH"] = str(journal) 81 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 82 82 83 83 # Create daily insight 84 84 day = journal / "20240101" ··· 460 460 from think.tools.search import search_journal 461 461 462 462 # Use fixtures journal 463 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 463 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 464 464 465 465 result = search_journal("test") 466 466 ··· 481 481 """Test search tool returns query echo.""" 482 482 from think.tools.search import search_journal 483 483 484 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 484 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 485 485 486 486 result = search_journal("test query", facet="work", agent="flow") 487 487 ··· 495 495 """Test search tool results include path and idx.""" 496 496 from think.tools.search import search_journal 497 497 498 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 498 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 499 499 500 500 result = search_journal("") 501 501 ··· 511 511 512 512 from think.tools.search import _MAX_RESULT_TEXT, search_journal 513 513 514 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 514 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 515 515 516 516 big_text = "x" * 10_000 517 517 fake_results = [ ··· 612 612 from think.indexer.journal import scan_journal, search_journal 613 613 614 614 journal = tmp_path 615 - os.environ["JOURNAL_PATH"] = str(journal) 615 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 616 616 617 617 # Create content for today (which is in light scan scope) 618 618 today = datetime.now().strftime("%Y%m%d") ··· 647 647 from think.indexer.journal import scan_journal, search_journal 648 648 649 649 journal = tmp_path 650 - os.environ["JOURNAL_PATH"] = str(journal) 650 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 651 651 652 652 # Create historical day content 653 653 day_dir = journal / "20200101" ··· 682 682 from think.indexer.journal import scan_journal, search_journal 683 683 684 684 journal = tmp_path 685 - os.environ["JOURNAL_PATH"] = str(journal) 685 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 686 686 687 687 # Create historical day content 688 688 day_dir = journal / "20200101" ··· 835 835 """search_journal filters by stream name.""" 836 836 from think.indexer.journal import scan_journal, search_journal 837 837 838 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 839 - scan_journal(os.environ["JOURNAL_PATH"], full=True) 838 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 839 + scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 840 840 841 841 # Search with matching stream 842 842 total, results = search_journal("", stream="default") ··· 853 853 """search_journal results include stream in metadata.""" 854 854 from think.indexer.journal import scan_journal, search_journal 855 855 856 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 857 - scan_journal(os.environ["JOURNAL_PATH"], full=True) 856 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 857 + scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 858 858 859 859 # Filter to segment content which has stream markers 860 860 total, results = search_journal("", stream="default") ··· 869 869 """search_counts filters by stream and includes streams aggregation.""" 870 870 from think.indexer.journal import scan_journal, search_counts 871 871 872 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 873 - scan_journal(os.environ["JOURNAL_PATH"], full=True) 872 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 873 + scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 874 874 875 875 # Unfiltered counts should include streams 876 876 counts = search_counts("") ··· 890 890 from think.indexer.journal import scan_journal 891 891 from think.tools.search import search_journal 892 892 893 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 894 - scan_journal(os.environ["JOURNAL_PATH"], full=True) 893 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 894 + scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 895 895 896 896 result = search_journal("", stream="default") 897 897 assert "results" in result ··· 901 901 902 902 def test_entity_schema_creation(): 903 903 """Verify entities table exists after schema init.""" 904 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 904 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 905 905 conn, _ = get_journal_index() 906 906 907 907 tables = conn.execute( ··· 915 915 """Verify journal entity identity rows are indexed.""" 916 916 from think.indexer.journal import scan_journal 917 917 918 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 918 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 919 919 scan_journal("tests/fixtures/journal", full=True) 920 920 921 921 conn, _ = get_journal_index("tests/fixtures/journal") ··· 928 928 """Verify facet relationship rows are indexed.""" 929 929 from think.indexer.journal import scan_journal 930 930 931 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 931 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 932 932 scan_journal("tests/fixtures/journal", full=True) 933 933 934 934 conn, _ = get_journal_index("tests/fixtures/journal") ··· 941 941 """Verify detected entity rows are indexed.""" 942 942 from think.indexer.journal import scan_journal 943 943 944 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 944 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 945 945 scan_journal("tests/fixtures/journal", full=True) 946 946 947 947 conn, _ = get_journal_index("tests/fixtures/journal") ··· 954 954 """Verify observation summary rows are indexed.""" 955 955 from think.indexer.journal import scan_journal 956 956 957 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 957 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 958 958 scan_journal("tests/fixtures/journal", full=True) 959 959 960 960 conn, _ = get_journal_index("tests/fixtures/journal") ··· 973 973 """Verify second scan is a no-op.""" 974 974 from think.indexer.journal import scan_journal 975 975 976 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 976 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 977 977 scan_journal("tests/fixtures/journal", full=True) 978 978 979 979 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1023 1023 """Verify FTS5 chunks still work after entity scan.""" 1024 1024 from think.indexer.journal import scan_journal 1025 1025 1026 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1026 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1027 1027 scan_journal("tests/fixtures/journal", full=True) 1028 1028 total, results = search_journal("Alice", limit=5) 1029 1029 assert isinstance(total, int) ··· 1031 1031 1032 1032 def test_signal_schema_creation(): 1033 1033 """Verify entity_signals table exists after schema init.""" 1034 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1034 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1035 1035 conn, _ = get_journal_index() 1036 1036 1037 1037 tables = conn.execute( ··· 1045 1045 """Verify KG appearance signals are extracted.""" 1046 1046 from think.indexer.journal import scan_journal 1047 1047 1048 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1048 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1049 1049 scan_journal("tests/fixtures/journal", full=True) 1050 1050 1051 1051 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1075 1075 """Verify KG edge signals are extracted.""" 1076 1076 from think.indexer.journal import scan_journal 1077 1077 1078 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1078 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1079 1079 scan_journal("tests/fixtures/journal", full=True) 1080 1080 1081 1081 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1098 1098 """Verify event participant signals are extracted.""" 1099 1099 from think.indexer.journal import scan_journal 1100 1100 1101 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1101 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1102 1102 scan_journal("tests/fixtures/journal", full=True) 1103 1103 1104 1104 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1122 1122 """Verify second scan is a no-op.""" 1123 1123 from think.indexer.journal import scan_journal 1124 1124 1125 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1125 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1126 1126 scan_journal("tests/fixtures/journal", full=True) 1127 1127 1128 1128 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1172 1172 """Verify KG signals get facet assigned from detection data.""" 1173 1173 from think.indexer.journal import scan_journal 1174 1174 1175 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1175 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1176 1176 scan_journal("tests/fixtures/journal", full=True) 1177 1177 1178 1178 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1233 1233 """Entity search chunks are generated from identity + relationship data.""" 1234 1234 from think.indexer.journal import scan_journal 1235 1235 1236 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1236 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1237 1237 scan_journal("tests/fixtures/journal", full=True) 1238 1238 conn, _ = get_journal_index("tests/fixtures/journal") 1239 1239 count = conn.execute("SELECT count(*) FROM chunks WHERE agent='entity'").fetchone()[ ··· 1248 1248 """Entity search chunks use entity_search: path prefix.""" 1249 1249 from think.indexer.journal import scan_journal 1250 1250 1251 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1251 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1252 1252 scan_journal("tests/fixtures/journal", full=True) 1253 1253 conn, _ = get_journal_index("tests/fixtures/journal") 1254 1254 rows = conn.execute( ··· 1262 1262 """Entity name is searchable via FTS.""" 1263 1263 from think.indexer.journal import scan_journal 1264 1264 1265 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1265 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1266 1266 scan_journal("tests/fixtures/journal", full=True) 1267 1267 total, results = search_journal("Alice Johnson", agent="entity") 1268 1268 assert total >= 1 ··· 1273 1273 """Entity type is searchable via FTS.""" 1274 1274 from think.indexer.journal import scan_journal 1275 1275 1276 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1276 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1277 1277 scan_journal("tests/fixtures/journal", full=True) 1278 1278 total, results = search_journal("Person", agent="entity") 1279 1279 assert total >= 1 ··· 1283 1283 """Entity search chunks include relationship descriptions.""" 1284 1284 from think.indexer.journal import scan_journal 1285 1285 1286 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1286 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1287 1287 scan_journal("tests/fixtures/journal", full=True) 1288 1288 # Alice has description "Close friend from college" in personal facet 1289 1289 total, results = search_journal("college", agent="entity") ··· 1296 1296 """Entity search chunks have facet metadata from relationships.""" 1297 1297 from think.indexer.journal import scan_journal 1298 1298 1299 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1299 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1300 1300 scan_journal("tests/fixtures/journal", full=True) 1301 1301 total, results = search_journal("Alice Johnson", agent="entity", facet="personal") 1302 1302 assert total >= 1 ··· 1307 1307 """Two full scans produce identical entity chunk count (no duplicates).""" 1308 1308 from think.indexer.journal import scan_journal 1309 1309 1310 - os.environ["JOURNAL_PATH"] = "tests/fixtures/journal" 1310 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1311 1311 scan_journal("tests/fixtures/journal", full=True) 1312 1312 conn, _ = get_journal_index("tests/fixtures/journal") 1313 1313 count1 = conn.execute(
+5 -5
tests/test_journal_stats.py
··· 49 49 } 50 50 (events_dir / "20240101.jsonl").write_text(json.dumps(event)) 51 51 52 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 52 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 53 53 js = stats_mod.JournalStats() 54 54 day_data = js.scan_day("20240101", str(day)) 55 55 js._apply_day_stats("20240101", day_data) ··· 138 138 ) 139 139 (tokens_dir / "20240102.jsonl").write_text(json.dumps(token4) + "\n") 140 140 141 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 141 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 142 142 js = stats_mod.JournalStats() 143 143 js.scan(str(journal)) 144 144 ··· 195 195 '{"start": "10:01:00", "text": "world"}\n' 196 196 ) 197 197 198 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 198 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 199 199 200 200 # First scan - should create cache 201 201 js1 = stats_mod.JournalStats() ··· 249 249 # Write token as JSONL format 250 250 (tokens_dir / "20240101.jsonl").write_text(json.dumps(token_new) + "\n") 251 251 252 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 252 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 253 253 js = stats_mod.JournalStats() 254 254 js.scan(str(journal)) 255 255 ··· 312 312 + "\n" 313 313 ) 314 314 315 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 315 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 316 316 js = stats_mod.JournalStats() 317 317 js.scan(str(journal)) 318 318
+15 -15
tests/test_kindle_importer.py
··· 133 133 f.flush() 134 134 try: 135 135 with tempfile.TemporaryDirectory() as journal: 136 - os.environ["JOURNAL_PATH"] = journal 136 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 137 137 result = importer.process(Path(f.name), Path(journal)) 138 138 assert result.entries_written == 2 139 139 assert result.errors == [] ··· 149 149 assert "Page 42" in md 150 150 finally: 151 151 os.unlink(f.name) 152 - os.environ.pop("JOURNAL_PATH", None) 152 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 153 153 154 154 155 155 def test_process_multiple_windows(): ··· 171 171 f.flush() 172 172 try: 173 173 with tempfile.TemporaryDirectory() as journal: 174 - os.environ["JOURNAL_PATH"] = journal 174 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 175 175 result = importer.process(Path(f.name), Path(journal)) 176 176 assert result.entries_written == 2 177 177 assert result.segments is not None ··· 179 179 assert len(result.files_created) == 2 180 180 finally: 181 181 os.unlink(f.name) 182 - os.environ.pop("JOURNAL_PATH", None) 182 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 183 183 184 184 185 185 def test_process_note_markdown(): ··· 197 197 f.flush() 198 198 try: 199 199 with tempfile.TemporaryDirectory() as journal: 200 - os.environ["JOURNAL_PATH"] = journal 200 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 201 201 result = importer.process(Path(f.name), Path(journal)) 202 202 md = Path(result.files_created[0]).read_text() 203 203 assert "Note: My personal note" in md 204 204 finally: 205 205 os.unlink(f.name) 206 - os.environ.pop("JOURNAL_PATH", None) 206 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 207 207 208 208 209 209 def test_observations_author_of(tmp_path, monkeypatch): ··· 212 212 f.write(content) 213 213 f.flush() 214 214 try: 215 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 215 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 216 216 importer.process(Path(f.name), tmp_path, facet="test.kindle") 217 217 author_obs = load_observations("test.kindle", "Author Name") 218 218 author_contents = [o["content"] for o in author_obs] ··· 227 227 f.write(content) 228 228 f.flush() 229 229 try: 230 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 230 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 231 231 importer.process(Path(f.name), tmp_path, facet="test.kindle") 232 232 book_obs = load_observations("test.kindle", "Test Book") 233 233 book_contents = [o["content"] for o in book_obs] ··· 268 268 f.write(content) 269 269 f.flush() 270 270 try: 271 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 271 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 272 272 importer.process(Path(f.name), tmp_path, facet="test.kindle") 273 273 book_obs = load_observations("test.kindle", "Test Book") 274 274 book_contents = [o["content"] for o in book_obs] ··· 282 282 f.write(highlights_only) 283 283 f.flush() 284 284 try: 285 - monkeypatch.setenv("JOURNAL_PATH", str(second_tmp_path)) 285 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(second_tmp_path)) 286 286 importer.process(Path(f.name), second_tmp_path, facet="test.kindle") 287 287 book_obs = load_observations("test.kindle", "Test Book") 288 288 book_contents = [o["content"] for o in book_obs] ··· 302 302 f.write(content) 303 303 f.flush() 304 304 try: 305 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 305 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 306 306 importer.process(Path(f.name), tmp_path, facet="test.kindle") 307 307 author_obs = load_observations("test.kindle", "Author Name") 308 308 author_contents = [o["content"] for o in author_obs] ··· 318 318 f.write(content) 319 319 f.flush() 320 320 try: 321 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 321 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 322 322 importer.process(Path(f.name), tmp_path, facet="test.kindle") 323 323 book_obs = load_observations("test.kindle", "Title Without Author") 324 324 book_contents = [o["content"] for o in book_obs] ··· 341 341 f.write(content) 342 342 f.flush() 343 343 try: 344 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 344 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 345 345 importer.process(Path(f.name), tmp_path, facet="test.kindle") 346 346 first = load_observations("test.kindle", "Test Book") 347 347 first_by_author = [ ··· 381 381 f.write(content) 382 382 f.flush() 383 383 try: 384 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 384 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 385 385 importer.process(Path(f.name), tmp_path, facet="test.kindle") 386 386 book_obs = load_observations("test.kindle", "Test Book") 387 387 book_contents = [o["content"] for o in book_obs] ··· 408 408 f.write(content) 409 409 f.flush() 410 410 try: 411 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 411 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 412 412 importer.process(Path(f.name), tmp_path, facet="test.kindle") 413 413 book_obs = load_observations("test.kindle", "Test Book") 414 414 book_contents = [o["content"] for o in book_obs]
+12 -12
tests/test_logs_cli.py
··· 148 148 day = datetime.now().strftime("%Y%m%d") 149 149 lines = [f"2026-02-09T10:{i:02d}:00 [echo:stdout] line {i}" for i in range(10)] 150 150 make_journal(tmp_path, day, {"echo": lines}) 151 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 151 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 152 152 153 153 logs_cli.collect_and_print(_args(c=5)) 154 154 ··· 164 164 day = datetime.now().strftime("%Y%m%d") 165 165 lines = [f"2026-02-09T11:{i:02d}:00 [echo:stdout] line {i}" for i in range(10)] 166 166 make_journal(tmp_path, day, {"echo": lines}) 167 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 167 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 168 168 169 169 logs_cli.collect_and_print(_args(c=2)) 170 170 ··· 187 187 "2026-02-09T10:01:00 [observer:stdout] line b", 188 188 ] 189 189 make_journal(tmp_path, day, {"echo": echo_lines, "observer": observer_lines}) 190 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 190 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 191 191 monkeypatch.setattr("sys.stdout.isatty", lambda: True) 192 192 193 193 logs_cli.collect_and_print(_args(c=5)) ··· 216 216 "2026-02-09T10:01:00 [observer:stdout] line b", 217 217 ] 218 218 make_journal(tmp_path, day, {"echo": echo_lines, "observer": observer_lines}) 219 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 219 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 220 220 monkeypatch.setattr("sys.stdout.isatty", lambda: False) 221 221 222 222 logs_cli.collect_and_print(_args(c=5)) ··· 239 239 "2026-02-09T10:01:30 [observer:stdout] delta", 240 240 ] 241 241 make_journal(tmp_path, day, {"echo": echo_lines, "observer": observer_lines}) 242 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 242 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 243 243 244 244 logs_cli.collect_and_print(_args(service="echo")) 245 245 ··· 258 258 "2026-02-09T10:02:00 [echo:stdout] unrelated text", 259 259 ] 260 260 make_journal(tmp_path, day, {"echo": lines}) 261 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 261 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 262 262 263 263 logs_cli.collect_and_print(_args(grep=re.compile("normal|special"))) 264 264 ··· 278 278 "2026-02-09T10:02:00 [echo:stdout] beta entry", 279 279 ] 280 280 make_journal(tmp_path, day, {"echo": lines}) 281 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 281 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 282 282 283 283 logs_cli.collect_and_print(_args(grep=re.compile("alpha|special"))) 284 284 ··· 297 297 "2026-02-09T10:20:00 [echo:stdout] new", 298 298 ] 299 299 make_journal(tmp_path, day, {"echo": lines}) 300 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 300 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 301 301 302 302 logs_cli.collect_and_print(_args(since=datetime(2026, 2, 9, 10, 10, 0))) 303 303 ··· 314 314 f"2026-02-09T10:{i:02d}:00 [echo:stdout] special line {i}" for i in range(10) 315 315 ] 316 316 make_journal(tmp_path, day, {"echo": lines}) 317 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 317 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 318 318 319 319 logs_cli.collect_and_print(_args(c=3, grep=re.compile("special"))) 320 320 ··· 337 337 "2026-02-09T10:00:30 [observer:stdout] special but wrong service", 338 338 ] 339 339 make_journal(tmp_path, day, {"echo": echo_lines, "observer": observer_lines}) 340 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 340 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 341 341 342 342 logs_cli.collect_and_print(_args(service="echo", grep=re.compile("special"))) 343 343 ··· 362 362 "2026-02-09T10:05:00 [supervisor:log] INFO c", 363 363 ] 364 364 make_journal(tmp_path, day, {"echo": echo_lines}, supervisor_lines=supervisor_lines) 365 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 365 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 366 366 367 367 logs_cli.collect_and_print(_args(c=2)) 368 368 ··· 383 383 "2026-02-09T10:01:00 [supervisor:log] INFO special", 384 384 ] 385 385 make_journal(tmp_path, day, {"echo": echo_lines}, supervisor_lines=supervisor_lines) 386 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 386 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 387 387 388 388 logs_cli.collect_and_print(_args(grep=re.compile("special"))) 389 389
+4 -4
tests/test_markers.py
··· 10 10 11 11 def test_stream_updated_marker_created(tmp_path, monkeypatch): 12 12 """stream.updated marker file is created in day health directory.""" 13 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 13 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 14 14 day = "20260215" 15 15 health_dir = day_path(day) / "health" 16 16 health_dir.mkdir(parents=True, exist_ok=True) ··· 20 20 21 21 def test_daily_updated_marker_created(tmp_path, monkeypatch): 22 22 """daily.updated marker file is created in day health directory.""" 23 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 23 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 24 24 day = "20260215" 25 25 health_dir = day_path(day) / "health" 26 26 health_dir.mkdir(parents=True, exist_ok=True) ··· 30 30 31 31 def test_marker_mtime_updates(tmp_path, monkeypatch): 32 32 """Touching a marker file again updates its mtime.""" 33 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 33 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 34 34 day = "20260215" 35 35 health_dir = day_path(day) / "health" 36 36 health_dir.mkdir(parents=True, exist_ok=True) ··· 45 45 46 46 def test_marker_not_created_when_day_is_none(tmp_path, monkeypatch): 47 47 """When day is None, no marker file should be created.""" 48 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 48 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 49 49 day = None 50 50 if day: 51 51 health_dir = day_path(day) / "health"
+49 -13
tests/test_matching.py
··· 25 25 class TestExistingTiers: 26 26 def test_exact_name_match(self): 27 27 entities = [_entity("Robert Johnson")] 28 - assert find_matching_entity("Robert Johnson", entities)["id"] == "robert_johnson" 28 + assert ( 29 + find_matching_entity("Robert Johnson", entities)["id"] == "robert_johnson" 30 + ) 29 31 30 32 def test_exact_id_match(self): 31 33 entities = [_entity("Robert Johnson")] 32 - assert find_matching_entity("robert_johnson", entities)["id"] == "robert_johnson" 34 + assert ( 35 + find_matching_entity("robert_johnson", entities)["id"] == "robert_johnson" 36 + ) 33 37 34 38 def test_exact_aka_match(self): 35 39 entities = [_entity("Robert Johnson", aka=["Bob"])] ··· 37 41 38 42 def test_case_insensitive_match(self): 39 43 entities = [_entity("Robert Johnson")] 40 - assert find_matching_entity("robert johnson", entities)["id"] == "robert_johnson" 44 + assert ( 45 + find_matching_entity("robert johnson", entities)["id"] == "robert_johnson" 46 + ) 41 47 42 48 def test_no_match_returns_none(self): 43 49 entities = [_entity("Robert Johnson")] ··· 94 100 def test_subset_match_short_in_long(self): 95 101 """Shorter name's tokens are a subset of longer entity's tokens.""" 96 102 entities = [_entity("Josh Jones Dilworth")] 97 - assert find_matching_entity("Jones Dilworth", entities)["id"] == "josh_jones_dilworth" 103 + assert ( 104 + find_matching_entity("Jones Dilworth", entities)["id"] 105 + == "josh_jones_dilworth" 106 + ) 98 107 99 108 def test_subset_match_long_detected(self): 100 109 """Detected name has more tokens than entity.""" 101 110 entities = [_entity("Jones Dilworth")] 102 - assert find_matching_entity("Josh Jones Dilworth", entities)["id"] == "jones_dilworth" 111 + assert ( 112 + find_matching_entity("Josh Jones Dilworth", entities)["id"] 113 + == "jones_dilworth" 114 + ) 103 115 104 116 def test_single_token_not_subset(self): 105 117 """Single-token names don't trigger subset match (min 2 tokens).""" ··· 118 130 def test_subset_both_directions(self): 119 131 """Token-subset works regardless of which name is in entities.""" 120 132 entities = [_entity("Josh Jones Dilworth")] 121 - assert find_matching_entity("Jones Dilworth", entities)["id"] == "josh_jones_dilworth" 133 + assert ( 134 + find_matching_entity("Jones Dilworth", entities)["id"] 135 + == "josh_jones_dilworth" 136 + ) 122 137 123 138 entities = [_entity("Jones Dilworth")] 124 - assert find_matching_entity("Josh Jones Dilworth", entities)["id"] == "jones_dilworth" 139 + assert ( 140 + find_matching_entity("Josh Jones Dilworth", entities)["id"] 141 + == "jones_dilworth" 142 + ) 125 143 126 144 127 145 # --- Enhancement 3: Prefix-token match --- ··· 131 149 def test_prefix_match_nickname(self): 132 150 """Nickname prefix matching (Chris → Christopher).""" 133 151 entities = [_entity("Christopher DeWolfe")] 134 - assert find_matching_entity("Chris DeWolfe", entities)["id"] == "christopher_dewolfe" 152 + assert ( 153 + find_matching_entity("Chris DeWolfe", entities)["id"] 154 + == "christopher_dewolfe" 155 + ) 135 156 136 157 def test_prefix_match_reverse(self): 137 158 """Reverse direction: full name detected, nickname entity.""" 138 159 entities = [_entity("Chris DeWolfe")] 139 - assert find_matching_entity("Christopher DeWolfe", entities)["id"] == "chris_dewolfe" 160 + assert ( 161 + find_matching_entity("Christopher DeWolfe", entities)["id"] 162 + == "chris_dewolfe" 163 + ) 140 164 141 165 def test_prefix_min_length(self): 142 166 """Prefix must be >= 4 chars.""" ··· 172 196 def test_chris_dewolfe(self): 173 197 """Chris DeWolfe ↔ Christopher DeWolfe (prefix-token match).""" 174 198 entities = [_entity("Christopher DeWolfe")] 175 - assert find_matching_entity("Chris DeWolfe", entities)["id"] == "christopher_dewolfe" 199 + assert ( 200 + find_matching_entity("Chris DeWolfe", entities)["id"] 201 + == "christopher_dewolfe" 202 + ) 176 203 177 204 entities = [_entity("Chris DeWolfe")] 178 - assert find_matching_entity("Christopher DeWolfe", entities)["id"] == "chris_dewolfe" 205 + assert ( 206 + find_matching_entity("Christopher DeWolfe", entities)["id"] 207 + == "chris_dewolfe" 208 + ) 179 209 180 210 def test_javier_garcia(self): 181 211 """Javier ↔ Javier Garcia (bidirectional first-word match).""" ··· 188 218 def test_jones_dilworth(self): 189 219 """Jones Dilworth ↔ Josh Jones Dilworth (token-subset match).""" 190 220 entities = [_entity("Josh Jones Dilworth")] 191 - assert find_matching_entity("Jones Dilworth", entities)["id"] == "josh_jones_dilworth" 221 + assert ( 222 + find_matching_entity("Jones Dilworth", entities)["id"] 223 + == "josh_jones_dilworth" 224 + ) 192 225 193 226 entities = [_entity("Jones Dilworth")] 194 - assert find_matching_entity("Josh Jones Dilworth", entities)["id"] == "jones_dilworth" 227 + assert ( 228 + find_matching_entity("Josh Jones Dilworth", entities)["id"] 229 + == "jones_dilworth" 230 + ) 195 231 196 232 197 233 # --- build_name_resolution_map ---
+8 -8
tests/test_models.py
··· 126 126 @pytest.fixture 127 127 def use_fixtures_journal(monkeypatch): 128 128 """Use the fixtures journal for provider config tests.""" 129 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 129 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 130 130 131 131 132 132 def test_resolve_provider_default_generate(use_fixtures_journal): ··· 182 182 # Use a journal path with no config 183 183 empty_journal = tmp_path / "empty_journal" 184 184 empty_journal.mkdir() 185 - monkeypatch.setenv("JOURNAL_PATH", str(empty_journal)) 185 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(empty_journal)) 186 186 187 187 provider, model = resolve_provider("anything", "generate") 188 188 assert provider == "google" ··· 384 384 } 385 385 } 386 386 (config_dir / "journal.json").write_text(json.dumps(config)) 387 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 387 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 388 388 389 389 # Invalid tier 99 should fall back to generate default tier (2) 390 390 provider, model = resolve_provider("test.invalid", "generate") ··· 627 627 628 628 from think.models import log_token_usage 629 629 630 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 630 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 631 631 632 632 # Codex CLI format: no total_tokens 633 633 log_token_usage( ··· 649 649 650 650 from think.models import log_token_usage 651 651 652 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 652 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 653 653 654 654 log_token_usage( 655 655 model="gpt-5.2", ··· 668 668 669 669 from think.models import log_token_usage 670 670 671 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 671 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 672 672 673 673 log_token_usage( 674 674 model="gpt-5.2", ··· 692 692 693 693 from think.models import log_token_usage 694 694 695 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 695 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 696 696 697 697 # Normalized usage from Google provider (the bug: reasoning_tokens were dropped) 698 698 log_token_usage( ··· 720 720 721 721 from think.models import log_token_usage 722 722 723 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 723 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 724 724 725 725 log_token_usage( 726 726 model="claude-sonnet-4-5",
+10 -10
tests/test_muse.py
··· 80 80 import think.prompts 81 81 82 82 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 83 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 83 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 84 84 85 85 result = compose_instructions() 86 86 ··· 98 98 import think.prompts 99 99 100 100 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 101 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 101 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 102 102 103 103 result = compose_instructions( 104 104 config_overrides={"system": "custom"}, ··· 123 123 # default user_prompt_dir, and load_prompt uses prompts.__file__ for defaults 124 124 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 125 125 monkeypatch.setattr(think.muse, "__file__", str(think_dir / "muse.py")) 126 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 126 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 127 127 128 128 result = compose_instructions(user_prompt="default") 129 129 ··· 139 139 import think.prompts 140 140 141 141 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 142 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 142 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 143 143 144 144 result = compose_instructions() 145 145 ··· 155 155 import think.prompts 156 156 157 157 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 158 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 158 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 159 159 160 160 result = compose_instructions( 161 161 config_overrides={"facets": False, "now": False, "day": False}, ··· 174 174 import think.prompts 175 175 176 176 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 177 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 177 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 178 178 179 179 result = compose_instructions( 180 180 config_overrides={"facets": False, "now": False}, ··· 193 193 import think.prompts 194 194 195 195 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 196 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 196 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 197 197 198 198 result = compose_instructions( 199 199 config_overrides={"facets": False, "now": True}, ··· 211 211 import think.prompts 212 212 213 213 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 214 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 214 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 215 215 216 216 result = compose_instructions( 217 217 analysis_day="20250115", ··· 230 230 import think.prompts 231 231 232 232 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 233 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 233 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 234 234 235 235 result = compose_instructions() 236 236 ··· 247 247 import think.prompts 248 248 249 249 monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 250 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 250 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 251 251 252 252 result = compose_instructions( 253 253 config_overrides={
+1 -1
tests/test_observation.py
··· 11 11 @pytest.fixture(autouse=True) 12 12 def _temp_journal(monkeypatch, tmp_path): 13 13 """Isolate all tests to a temporary journal.""" 14 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 14 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 15 15 16 16 17 17 class TestPreHook:
+6 -6
tests/test_onboarding.py
··· 13 13 @pytest.fixture(autouse=True) 14 14 def _temp_journal(monkeypatch, tmp_path): 15 15 """Ensure journaling defaults remain isolated from developer data.""" 16 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 16 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 17 17 18 18 19 19 class _ImmediateEvent: ··· 337 337 338 338 from convey.apps import _resolve_attention 339 339 340 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 340 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 341 341 342 342 today = datetime.now().strftime("%Y%m%d") 343 343 agents_dir = tmp_path / "agents" ··· 379 379 380 380 from convey.apps import _resolve_attention 381 381 382 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 382 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 383 383 384 384 today = datetime.now().strftime("%Y%m%d") 385 385 agents_dir = tmp_path / "agents" ··· 418 418 419 419 from convey.apps import _resolve_attention 420 420 421 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 421 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 422 422 423 423 today = datetime.now().strftime("%Y%m%d") 424 424 agents_dir = tmp_path / "agents" ··· 489 489 490 490 from convey.apps import _resolve_attention 491 491 492 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 492 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 493 493 494 494 today = datetime.now().strftime("%Y%m%d") 495 495 agents_dir = tmp_path / "agents" ··· 526 526 527 527 from convey.apps import _resolve_attention 528 528 529 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 529 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 530 530 531 531 today = datetime.now().strftime("%Y%m%d") 532 532 agents_dir = tmp_path / today / "agents"
+6 -6
tests/test_output_hooks.py
··· 23 23 24 24 25 25 def copy_day(tmp_path: Path) -> Path: 26 - os.environ["JOURNAL_PATH"] = str(tmp_path) 26 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 27 27 dest = day_path("20240101") 28 28 src = FIXTURES / "journal" / "20240101" 29 29 for item in src.iterdir(): ··· 192 192 lambda *a, **k: MOCK_RESULT, 193 193 ) 194 194 monkeypatch.setenv("GOOGLE_API_KEY", "x") 195 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 195 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 196 196 197 197 config = { 198 198 "name": "hooked_test", ··· 242 242 lambda *a, **k: MOCK_RESULT, 243 243 ) 244 244 monkeypatch.setenv("GOOGLE_API_KEY", "x") 245 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 245 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 246 246 247 247 config = { 248 248 "name": "noop_test", ··· 288 288 lambda *a, **k: MOCK_RESULT, 289 289 ) 290 290 monkeypatch.setenv("GOOGLE_API_KEY", "x") 291 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 291 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 292 292 293 293 config = { 294 294 "name": "broken_test", ··· 413 413 414 414 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 415 415 monkeypatch.setenv("GOOGLE_API_KEY", "x") 416 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 416 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 417 417 418 418 config = { 419 419 "name": "prehooked_test", ··· 470 470 471 471 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 472 472 monkeypatch.setenv("GOOGLE_API_KEY", "x") 473 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 473 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 474 474 475 475 config = { 476 476 "name": "both_hooks_test",
+2 -2
tests/test_output_path.py
··· 8 8 9 9 from think.muse import get_output_name, get_output_path 10 10 11 - os.environ.setdefault("JOURNAL_PATH", "tests/fixtures/journal") 11 + os.environ.setdefault("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 12 12 13 13 14 14 class TestGetOutputName: ··· 90 90 path = get_activity_output_path( 91 91 "work", "20260209", "coding_100000_300", "session_review" 92 92 ) 93 - journal = os.environ["JOURNAL_PATH"] 93 + journal = os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 94 94 expected = ( 95 95 Path(journal) 96 96 / "facets/work/activities/20260209/coding_100000_300/session_review.md"
+1 -1
tests/test_planner.py
··· 49 49 def test_planner_main(tmp_path, monkeypatch, capsys): 50 50 sys.modules.pop("think.planner", None) 51 51 mod = importlib.import_module("think.planner") 52 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 52 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 53 53 monkeypatch.setattr(mod, "generate_plan", lambda *a, **k: "ok") 54 54 task = tmp_path / "t.txt" 55 55 task.write_text("hi")
+1 -1
tests/test_retention.py
··· 304 304 (day3 / "audio.flac").write_bytes(b"x" * 600) 305 305 (day3 / "stream.json").write_text('{"stream":"default"}') 306 306 307 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 307 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 308 308 # Clear cached journal path 309 309 import think.utils 310 310
+3 -3
tests/test_runner.py
··· 15 15 """Set up a temporary journal path.""" 16 16 journal = tmp_path / "journal" 17 17 journal.mkdir() 18 - os.environ["JOURNAL_PATH"] = str(journal) 18 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 19 19 yield journal 20 20 # Cleanup 21 - if "JOURNAL_PATH" in os.environ: 22 - del os.environ["JOURNAL_PATH"] 21 + if "_SOLSTONE_JOURNAL_OVERRIDE" in os.environ: 22 + del os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 23 23 24 24 25 25 def test_managed_process_has_ref_and_pid(journal_path, mock_callosum):
+1 -1
tests/test_scheduler.py
··· 64 64 @pytest.fixture 65 65 def journal_path(tmp_path, monkeypatch): 66 66 """Create a temp journal with config/ and health/ dirs.""" 67 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 67 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 68 68 (tmp_path / "config").mkdir() 69 69 (tmp_path / "health").mkdir() 70 70 return tmp_path
+2 -2
tests/test_sense.py
··· 284 284 """Test scan_unprocessed finds only unprocessed media files.""" 285 285 from observe.utils import AUDIO_EXTENSIONS, VIDEO_EXTENSIONS 286 286 287 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 287 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 288 288 289 289 day_dir = tmp_path / "20250101" 290 290 segment_dir = day_dir / "default" / "143022_300" ··· 314 314 """Test scan_unprocessed honors segment filters.""" 315 315 from observe.utils import AUDIO_EXTENSIONS, VIDEO_EXTENSIONS 316 316 317 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 317 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 318 318 319 319 day_dir = tmp_path / "20250101" 320 320 segment_1 = day_dir / "default" / "143022_300"
+22 -25
tests/test_sol.py
··· 124 124 class TestGetStatus: 125 125 """Tests for get_status() function.""" 126 126 127 - def test_status_with_journal_path(self, monkeypatch, tmp_path): 128 - """Test status when JOURNAL_PATH is set and exists.""" 129 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 127 + def test_status_with_override(self, monkeypatch, tmp_path): 128 + """Test status when journal override is set and exists.""" 129 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 130 130 131 131 status = sol.get_status() 132 132 assert status["journal_path"] == str(tmp_path) 133 - assert status["journal_source"] == "shell" 133 + assert status["journal_source"] == "override" 134 134 assert status["journal_exists"] is True 135 135 136 136 def test_status_with_nonexistent_journal(self, monkeypatch, tmp_path): 137 - """Test status when JOURNAL_PATH points to nonexistent dir.""" 137 + """Test status when override points to nonexistent dir.""" 138 138 nonexistent = tmp_path / "nonexistent" 139 - monkeypatch.setenv("JOURNAL_PATH", str(nonexistent)) 139 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(nonexistent)) 140 140 141 141 status = sol.get_status() 142 142 assert status["journal_path"] == str(nonexistent) 143 - assert status["journal_source"] == "shell" 143 + assert status["journal_source"] == "override" 144 144 assert status["journal_exists"] is False 145 145 146 - def test_status_without_journal_path(self, monkeypatch): 147 - """Test status when JOURNAL_PATH is not set falls back to platform default.""" 148 - monkeypatch.delenv("JOURNAL_PATH", raising=False) 149 - with patch("think.utils.load_dotenv"): 150 - status = sol.get_status() 151 - assert status["journal_path"] != "(not set)" 152 - assert status["journal_source"] == "default" 153 - assert isinstance(status["journal_exists"], bool) 146 + def test_status_without_override(self, monkeypatch): 147 + """Test status when no override is set uses project root.""" 148 + monkeypatch.delenv("_SOLSTONE_JOURNAL_OVERRIDE", raising=False) 149 + status = sol.get_status() 150 + assert status["journal_path"].endswith("/journal") 151 + assert status["journal_source"] == "project" 152 + assert isinstance(status["journal_exists"], bool) 154 153 155 154 156 155 class TestMain: ··· 159 158 def test_main_no_args_shows_help(self, monkeypatch, capsys): 160 159 """Test that running with no args shows help.""" 161 160 monkeypatch.setattr(sys, "argv", ["sol"]) 162 - monkeypatch.setenv("JOURNAL_PATH", "/tmp/test") 161 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test") 163 162 164 163 sol.main() 165 164 ··· 170 169 def test_main_help_flag(self, monkeypatch, capsys): 171 170 """Test --help flag shows help.""" 172 171 monkeypatch.setattr(sys, "argv", ["sol", "--help"]) 173 - monkeypatch.setenv("JOURNAL_PATH", "/tmp/test") 172 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test") 174 173 175 174 sol.main() 176 175 ··· 180 179 def test_main_help_command_without_question(self, monkeypatch, capsys): 181 180 """Test bare 'help' command shows static help.""" 182 181 monkeypatch.setattr(sys, "argv", ["sol", "help"]) 183 - monkeypatch.setenv("JOURNAL_PATH", "/tmp/test") 182 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test") 184 183 185 184 sol.main() 186 185 ··· 199 198 def test_main_path_flag(self, monkeypatch, capsys): 200 199 """Test --path flag prints resolved journal path.""" 201 200 monkeypatch.setattr(sys, "argv", ["sol", "--path"]) 202 - monkeypatch.setenv("JOURNAL_PATH", "/tmp/test-journal") 201 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 203 202 204 203 sol.main() 205 204 ··· 207 206 assert captured.out.strip() == "/tmp/test-journal" 208 207 209 208 def test_main_path_flag_default(self, monkeypatch, capsys): 210 - """Test --path prints platform default when JOURNAL_PATH not set.""" 209 + """Test --path prints project root journal when no override set.""" 211 210 monkeypatch.setattr(sys, "argv", ["sol", "--path"]) 212 - monkeypatch.delenv("JOURNAL_PATH", raising=False) 213 - with patch("think.utils.load_dotenv"): 214 - sol.main() 211 + monkeypatch.delenv("_SOLSTONE_JOURNAL_OVERRIDE", raising=False) 212 + sol.main() 215 213 216 214 captured = capsys.readouterr() 217 215 path = captured.out.strip() 218 - assert path != "(not set)" 219 216 assert path != "" 220 - assert "solstone" in path or "journal" in path 217 + assert path.endswith("/journal") 221 218 222 219 def test_main_unknown_command_exits(self, monkeypatch): 223 220 """Test that unknown command exits with code 1."""
+6 -6
tests/test_streams.py
··· 90 90 91 91 def test_update_stream_first_segment(tmp_path, monkeypatch): 92 92 """First segment creates state, prev=None, seq=1.""" 93 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 93 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 94 94 95 95 result = update_stream("archon", "20250119", "142500_300", type="observer") 96 96 ··· 110 110 111 111 def test_update_stream_subsequent(tmp_path, monkeypatch): 112 112 """Subsequent segments increment seq and return correct prev.""" 113 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 113 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 114 114 115 115 update_stream("archon", "20250119", "142500_300", type="observer") 116 116 result = update_stream("archon", "20250119", "143000_300") ··· 126 126 127 127 def test_update_stream_cross_day(tmp_path, monkeypatch): 128 128 """Prev points to different day when crossing midnight.""" 129 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 129 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 130 130 131 131 update_stream("archon", "20250119", "235500_300") 132 132 result = update_stream("archon", "20250120", "000000_300") ··· 180 180 181 181 def test_list_streams(tmp_path, monkeypatch): 182 182 """Discovers all stream state files.""" 183 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 183 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 184 184 185 185 update_stream("archon", "20250119", "142500_300", type="observer") 186 186 update_stream("laptop", "20250119", "142500_300", type="observer") ··· 199 199 200 200 def test_rebuild_stream_state(tmp_path, monkeypatch): 201 201 """Reconstructs state from segment markers.""" 202 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 202 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 203 203 204 204 # Create segment dirs with stream markers under default stream 205 205 day_dir = tmp_path / "20250119" ··· 234 234 235 235 def test_update_stream_atomicity(tmp_path, monkeypatch): 236 236 """Concurrent writes don't corrupt state file.""" 237 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 237 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 238 238 239 239 errors = [] 240 240
+3 -3
tests/test_supervisor.py
··· 171 171 return proc 172 172 173 173 monkeypatch.setattr(mod.subprocess, "Popen", fake_popen) 174 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 174 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 175 175 176 176 # Test start_observer() 177 177 observer_proc = mod.start_observer() ··· 221 221 return proc 222 222 223 223 monkeypatch.setattr(mod.subprocess, "Popen", fake_popen) 224 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 224 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 225 225 226 226 # Test start_sync() 227 227 remote_url = "https://server:5000/app/remote/ingest/abc123" ··· 391 391 monkeypatch.setattr(mod.time, "time", fake_time) 392 392 monkeypatch.setattr(mod.asyncio, "sleep", fake_sleep) 393 393 394 - monkeypatch.setenv("JOURNAL_PATH", "/test/journal") 394 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/test/journal") 395 395 396 396 with caplog.at_level(logging.INFO): 397 397 await mod.supervise(threshold=1, interval=1, schedule=False, procs=[])
+21 -21
tests/test_sync.py
··· 68 68 69 69 journal = sync_journal["path"] 70 70 day = sync_journal["day"] 71 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 71 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 72 72 73 73 path = get_sync_state_path(day) 74 74 assert path == journal / day / "health" / "sync.jsonl" ··· 80 80 81 81 journal = sync_journal["path"] 82 82 day = sync_journal["day"] 83 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 83 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 84 84 85 85 # Initially empty 86 86 records = load_sync_state(day) ··· 116 116 117 117 journal = sync_journal["path"] 118 118 day = sync_journal["day"] 119 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 119 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 120 120 121 121 # Add pending segment 122 122 append_sync_record( ··· 176 176 from observe.sync import get_pending_segments 177 177 178 178 journal = sync_journal["path"] 179 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 179 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 180 180 181 181 pending = get_pending_segments(days_back=7) 182 182 assert pending == [] ··· 207 207 from observe.sync import SyncService 208 208 209 209 journal = sync_journal["path"] 210 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 210 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 211 211 212 212 service = SyncService( 213 213 remote_url="https://server/ingest/key", ··· 225 225 226 226 journal = sync_journal["path"] 227 227 day = sync_journal["day"] 228 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 228 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 229 229 230 230 service = SyncService("https://server/ingest/key") 231 231 service._client = mock_remote_client ··· 262 262 263 263 journal = sync_journal["path"] 264 264 day = sync_journal["day"] 265 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 265 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 266 266 267 267 service = SyncService("https://server/ingest/key") 268 268 service._client = mock_remote_client ··· 296 296 297 297 journal = sync_journal["path"] 298 298 day = sync_journal["day"] 299 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 299 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 300 300 301 301 service = SyncService("https://server/ingest/key") 302 302 service._client = mock_remote_client ··· 321 321 322 322 journal = sync_journal["path"] 323 323 day = sync_journal["day"] 324 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 324 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 325 325 326 326 service = SyncService("https://server/ingest/key") 327 327 ··· 350 350 351 351 journal = sync_journal["path"] 352 352 day = sync_journal["day"] 353 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 353 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 354 354 355 355 service = SyncService("https://server/ingest/key") 356 356 service._callosum = mock_callosum ··· 394 394 395 395 journal = sync_journal["path"] 396 396 day = sync_journal["day"] 397 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 397 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 398 398 399 399 # Add pending segment with metadata 400 400 append_sync_record( ··· 431 431 432 432 journal = sync_journal["path"] 433 433 day = sync_journal["day"] 434 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 434 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 435 435 436 436 # Create SegmentInfo 437 437 seg_info = SegmentInfo( ··· 484 484 485 485 journal = sync_journal["path"] 486 486 day = sync_journal["day"] 487 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 487 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 488 488 489 489 seg_info = SegmentInfo( 490 490 day=day, ··· 537 537 538 538 journal = sync_journal["path"] 539 539 day = sync_journal["day"] 540 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 540 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 541 541 542 542 # Create SegmentInfo with metadata 543 543 seg_info = SegmentInfo( ··· 603 603 604 604 journal = sync_journal["path"] 605 605 day = sync_journal["day"] 606 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 606 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 607 607 608 608 # Create segment directory with mixed files 609 609 seg_dir = journal / day / "default" / "120000_300" ··· 642 642 643 643 journal = sync_journal["path"] 644 644 day = sync_journal["day"] 645 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 645 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 646 646 647 647 # Create segment directory with only 0-byte files 648 648 seg_dir = journal / day / "default" / "120000_300" ··· 677 677 678 678 journal = sync_journal["path"] 679 679 day = sync_journal["day"] 680 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 680 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 681 681 682 682 seg_info = SegmentInfo( 683 683 day=day, ··· 735 735 736 736 journal = sync_journal["path"] 737 737 day = sync_journal["day"] 738 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 738 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 739 739 740 740 with ( 741 741 patch("observe.sync.RemoteClient"), ··· 784 784 from observe.sync import SyncService 785 785 786 786 journal = sync_journal["path"] 787 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 787 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 788 788 789 789 mock_callosum = MagicMock() 790 790 shutdown_order: list[str] = [] ··· 806 806 from observe.sync import SyncService 807 807 808 808 journal = sync_journal["path"] 809 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 809 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 810 810 811 811 with ( 812 812 patch("observe.sync.get_pending_segments", return_value=[]), ··· 828 828 829 829 journal = sync_journal["path"] 830 830 day = sync_journal["day"] 831 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 831 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 832 832 833 833 service = SyncService("https://server/ingest/key") 834 834 service._client.upload_segment = MagicMock(return_value=UploadResult(True))
+7 -7
tests/test_template_substitution.py
··· 44 44 with open(config_dir / "journal.json", "w") as f: 45 45 json.dump(config, f) 46 46 47 - # Set JOURNAL_PATH for the test 48 - old_journal = os.environ.get("JOURNAL_PATH") 49 - os.environ["JOURNAL_PATH"] = str(tmp_path) 47 + # Set _SOLSTONE_JOURNAL_OVERRIDE for the test 48 + old_journal = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 49 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 50 50 51 51 yield tmp_path 52 52 53 - # Restore original JOURNAL_PATH 53 + # Restore original _SOLSTONE_JOURNAL_OVERRIDE 54 54 if old_journal: 55 - os.environ["JOURNAL_PATH"] = old_journal 55 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = old_journal 56 56 else: 57 - os.environ.pop("JOURNAL_PATH", None) 57 + os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 58 58 59 59 60 60 @pytest.fixture ··· 158 158 def test_load_prompt_missing_config_graceful(tmp_path, mock_prompt_dir): 159 159 """Test that load_prompt works even without config (safe_substitute).""" 160 160 # Point to a journal without config 161 - os.environ["JOURNAL_PATH"] = str(tmp_path) 161 + os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 162 162 163 163 result = load_prompt("test_template", base_dir=mock_prompt_dir) 164 164
+22 -22
tests/test_think_utils.py
··· 106 106 ], 107 107 ) 108 108 109 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 109 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 110 110 result = load_entity_names() 111 111 112 112 # Check that names are extracted without duplicates ··· 123 123 def test_load_entity_names_missing_file(monkeypatch): 124 124 """Test that missing file returns None.""" 125 125 with tempfile.TemporaryDirectory() as tmpdir: 126 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 126 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 127 127 result = load_entity_names() 128 128 assert result is None 129 129 ··· 135 135 facet_dir = Path(tmpdir) / "facets" / "test" 136 136 facet_dir.mkdir(parents=True, exist_ok=True) 137 137 138 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 138 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 139 139 result = load_entity_names() 140 140 assert result is None 141 141 ··· 147 147 entities_dir = Path(tmpdir) / "facets" / "test" / "entities" 148 148 entities_dir.mkdir(parents=True, exist_ok=True) 149 149 150 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 150 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 151 151 result = load_entity_names() 152 152 assert result is None 153 153 ··· 166 166 ], 167 167 ) 168 168 169 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 169 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 170 170 result = load_entity_names() 171 171 172 172 names = result.split("; ") ··· 189 189 ], 190 190 ) 191 191 192 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 192 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 193 193 result = load_entity_names() 194 194 assert "Jean-Pierre O'Malley" in result 195 195 assert "AT&T" in result ··· 198 198 199 199 200 200 def test_load_entity_names_with_env_var(monkeypatch): 201 - """Test loading using JOURNAL_PATH environment variable.""" 201 + """Test loading using _SOLSTONE_JOURNAL_OVERRIDE environment variable.""" 202 202 with tempfile.TemporaryDirectory() as tmpdir: 203 203 setup_entities_new_structure( 204 204 Path(tmpdir), ··· 206 206 [("Person", "Test User", "A test person")], 207 207 ) 208 208 209 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 209 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 210 210 211 211 # Should use env var 212 212 result = load_entity_names() ··· 215 215 216 216 def test_load_entity_names_empty_journal(tmp_path, monkeypatch): 217 217 """Test that empty journal directory returns None.""" 218 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 218 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 219 219 220 220 result = load_entity_names() 221 221 assert result is None ··· 240 240 ], 241 241 ) 242 242 243 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 243 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 244 244 result = load_entity_names(spoken=True) 245 245 246 246 # Should return a list, not a string ··· 290 290 ], 291 291 ) 292 292 293 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 293 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 294 294 result = load_entity_names(spoken=True) 295 295 # Tools are now included (uniform processing) 296 296 assert isinstance(result, list) ··· 312 312 ], 313 313 ) 314 314 315 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 315 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 316 316 result = load_entity_names(spoken=True) 317 317 318 318 # Should have only one "John" and one "Acme" even though there are two of each ··· 339 339 ], 340 340 ) 341 341 342 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 342 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 343 343 result = load_entity_names(spoken=True) 344 344 345 345 assert isinstance(result, list) ··· 397 397 ], 398 398 ) 399 399 400 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 400 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 401 401 result = load_entity_names(spoken=True) 402 402 403 403 assert isinstance(result, list) ··· 438 438 ], 439 439 ) 440 440 441 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 441 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 442 442 result = load_entity_names(spoken=True) 443 443 444 444 assert isinstance(result, list) ··· 473 473 ], 474 474 ) 475 475 476 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 476 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 477 477 result = load_entity_names(spoken=True) 478 478 479 479 # Should have only one "John" even though it appears in aka and as main name ··· 511 511 ], 512 512 ) 513 513 514 - monkeypatch.setenv("JOURNAL_PATH", tmpdir) 514 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 515 515 result = load_entity_names(spoken=False) 516 516 517 517 # Check all entities are present with their aka ··· 646 646 647 647 Returns a helper function to write config and run setup_cli. 648 648 """ 649 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 649 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 650 650 monkeypatch.setattr(sys, "argv", ["test"]) 651 651 652 652 def write_config_and_run(config: dict | None = None): ··· 758 758 """Test writing and reading a service port file.""" 759 759 from think.utils import read_service_port, write_service_port 760 760 761 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 761 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 762 762 763 763 # Write port 764 764 write_service_port("test_service", 12345) ··· 776 776 """Test that reading missing port file returns None.""" 777 777 from think.utils import read_service_port 778 778 779 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 779 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 780 780 781 781 port = read_service_port("nonexistent") 782 782 assert port is None ··· 785 785 """Test that reading invalid port file content returns None.""" 786 786 from think.utils import read_service_port 787 787 788 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 788 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 789 789 790 790 # Create port file with invalid content 791 791 health_dir = tmp_path / "health" ··· 800 800 """Test that write_service_port creates health directory if needed.""" 801 801 from think.utils import write_service_port 802 802 803 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 803 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 804 804 805 805 # Health dir doesn't exist yet 806 806 health_dir = tmp_path / "health"
+10 -30
tests/test_transcribe_cli.py
··· 11 11 12 12 13 13 def test_main_accepts_journal_relative_path(tmp_path, monkeypatch): 14 - """main() resolves audio_path relative to JOURNAL_PATH when absolute path fails.""" 14 + """main() resolves audio_path relative to journal when absolute path fails.""" 15 15 seg_dir = tmp_path / "20260201" / "default" / "090000_300" 16 16 seg_dir.mkdir(parents=True) 17 17 audio_file = seg_dir / "audio.wav" 18 18 audio_file.touch() 19 19 20 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 20 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 21 21 monkeypatch.setattr( 22 22 "sys.argv", ["sol transcribe", "20260201/default/090000_300/audio.wav"] 23 23 ) ··· 46 46 47 47 def test_main_errors_on_nonexistent_absolute_path(tmp_path, monkeypatch, capsys): 48 48 """main() errors clearly when path doesn't exist as absolute or journal-relative.""" 49 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 49 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 50 50 monkeypatch.setattr("sys.argv", ["sol transcribe", "/nonexistent/path/audio.wav"]) 51 51 52 52 from observe.transcribe.main import main ··· 58 58 assert "Tried absolute" in captured.err or "not found" in captured.err.lower() 59 59 60 60 61 - def test_setup_cli_prints_message_on_default_journal(tmp_path, monkeypatch, capsys): 62 - """setup_cli() prints an informational message when JOURNAL_PATH uses the default.""" 63 - monkeypatch.delenv("JOURNAL_PATH", raising=False) 64 - 65 - with ( 66 - patch("think.utils.get_journal_info", return_value=(str(tmp_path), "default")), 67 - patch("think.utils.get_journal", return_value=str(tmp_path)), 68 - patch("think.utils.get_config", return_value={}), 69 - ): 70 - from think.utils import setup_cli 71 - 72 - parser = argparse.ArgumentParser() 73 - monkeypatch.setattr("sys.argv", ["test"]) 74 - setup_cli(parser) 75 - 76 - captured = capsys.readouterr() 77 - assert "docs/INSTALL.md" in captured.err 78 - 79 - 80 - def test_setup_cli_no_message_when_journal_path_set(tmp_path, monkeypatch, capsys): 81 - """setup_cli() prints no informational message when JOURNAL_PATH is explicitly set.""" 82 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 61 + def test_setup_cli_no_message_on_project_journal(tmp_path, monkeypatch, capsys): 62 + """setup_cli() prints no informational message — journal path is always deterministic.""" 63 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 83 64 84 65 with ( 85 - patch("think.utils.get_journal_info", return_value=(str(tmp_path), "shell")), 86 66 patch("think.utils.get_journal", return_value=str(tmp_path)), 87 67 patch("think.utils.get_config", return_value={}), 88 68 ): ··· 119 99 ): 120 100 """--all processes unprocessed audio, skips already-transcribed, ignores non-audio.""" 121 101 journal = _make_batch_journal(tmp_path) 122 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 102 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 123 103 monkeypatch.setattr("sys.argv", ["sol transcribe", "--all"]) 124 104 125 105 mock_process_one = MagicMock() ··· 145 125 def test_all_redo_reprocesses_transcribed(tmp_path, monkeypatch): 146 126 """--all --redo reprocesses even segments that already have .jsonl.""" 147 127 journal = _make_batch_journal(tmp_path) 148 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 128 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 149 129 monkeypatch.setattr("sys.argv", ["sol transcribe", "--all", "--redo"]) 150 130 151 131 mock_process_one = MagicMock() ··· 163 143 164 144 def test_all_and_audio_path_mutually_exclusive(tmp_path, monkeypatch): 165 145 """Providing both --all and audio_path produces a clear error.""" 166 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 146 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 167 147 monkeypatch.setattr("sys.argv", ["sol transcribe", "--all", "some/audio.wav"]) 168 148 169 149 with patch("think.entities.load_recent_entity_names", return_value=[]): ··· 175 155 176 156 def test_neither_all_nor_audio_path_errors(tmp_path, monkeypatch): 177 157 """Providing neither --all nor audio_path produces a clear error.""" 178 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 158 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 179 159 monkeypatch.setattr("sys.argv", ["sol transcribe"]) 180 160 181 161 with patch("think.entities.load_recent_entity_names", return_value=[]):
+9 -9
tests/test_transfer.py
··· 97 97 (segment_dir / "audio.flac").write_bytes(b"fake audio data") 98 98 (segment_dir / "audio.jsonl").write_text('{"raw": "audio.flac"}\n') 99 99 100 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 100 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 101 101 102 102 # Clear cache 103 103 import think.utils ··· 132 132 day_dir = journal_path / "20250101" 133 133 day_dir.mkdir(parents=True) 134 134 135 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 135 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 136 136 137 137 import think.utils 138 138 ··· 148 148 journal_path = tmp_path / "journal" 149 149 journal_path.mkdir(parents=True) 150 150 151 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 151 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 152 152 153 153 import think.utils 154 154 ··· 222 222 journal_path = tmp_path / "journal" 223 223 journal_path.mkdir() 224 224 225 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 225 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 226 226 227 227 import think.utils 228 228 ··· 252 252 segment_dir.mkdir(parents=True) 253 253 (segment_dir / "audio.flac").write_bytes(content) 254 254 255 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 255 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 256 256 257 257 import think.utils 258 258 ··· 279 279 segment_dir.mkdir(parents=True) 280 280 (segment_dir / "audio.flac").write_bytes(b"existing different data") 281 281 282 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 282 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 283 283 284 284 import think.utils 285 285 ··· 312 312 journal_path = tmp_path / "journal" 313 313 journal_path.mkdir() 314 314 315 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 315 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 316 316 317 317 import think.utils 318 318 ··· 343 343 journal_path = tmp_path / "journal" 344 344 journal_path.mkdir() 345 345 346 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 346 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 347 347 348 348 import think.utils 349 349 ··· 371 371 segment_dir.mkdir(parents=True) 372 372 (segment_dir / "audio.flac").write_bytes(content) 373 373 374 - monkeypatch.setenv("JOURNAL_PATH", str(journal_path)) 374 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 375 375 376 376 import think.utils 377 377
+4 -4
tests/test_updated_days.py
··· 11 11 12 12 def test_updated_days_fixture(monkeypatch): 13 13 """20250101 has stream.updated but no daily.updated — should be updated.""" 14 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 14 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 15 15 monkeypatch.setattr(think.utils, "_journal_path_cache", None) 16 16 days = updated_days() 17 17 assert "20250101" in days ··· 19 19 20 20 def test_updated_days_exclude(monkeypatch): 21 21 """Excluded days should not appear in results.""" 22 - monkeypatch.setenv("JOURNAL_PATH", "tests/fixtures/journal") 22 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 23 23 monkeypatch.setattr(think.utils, "_journal_path_cache", None) 24 24 days = updated_days(exclude={"20250101"}) 25 25 assert "20250101" not in days ··· 27 27 28 28 def test_updated_days_clean(tmp_path, monkeypatch): 29 29 """Day with daily.updated newer than stream.updated is not updated.""" 30 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 30 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 31 31 day_dir = tmp_path / "20260101" / "health" 32 32 day_dir.mkdir(parents=True) 33 33 (day_dir / "stream.updated").touch() ··· 38 38 39 39 def test_updated_days_no_stream(tmp_path, monkeypatch): 40 40 """Day without stream.updated is not updated (no stream data).""" 41 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 41 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 42 42 (tmp_path / "20260101").mkdir() 43 43 assert updated_days() == []
+4 -4
tests/verify_api.py
··· 386 386 raw_journal = str(journal_path) 387 387 if raw_journal != resolved_journal: 388 388 path_replacements.append((raw_journal, "<JOURNAL>")) 389 - # Match the JOURNAL_PATH env var if set (may be relative) 390 - env_journal = os.environ.get("JOURNAL_PATH", "") 389 + # Match the _SOLSTONE_JOURNAL_OVERRIDE env var if set (may be relative) 390 + env_journal = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE", "") 391 391 if env_journal and env_journal not in (resolved_journal, raw_journal): 392 392 path_replacements.append((env_journal, "<JOURNAL>")) 393 393 path_replacements.append((project_root, "<PROJECT>")) ··· 554 554 """Resolve journal path from env or sandbox metadata.""" 555 555 556 556 env_path = Path.cwd() / "tests" / "fixtures" / "journal" 557 - journal = Path(os.environ.get("JOURNAL_PATH", str(env_path))) 557 + journal = Path(os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE", str(env_path))) 558 558 if journal.is_absolute(): 559 559 return str(journal) 560 560 return str(Path(journal).resolve()) ··· 595 595 596 596 def resolve_journal_for_mode(base_url: str | None) -> str: 597 597 if base_url: 598 - env_path = os.environ.get("JOURNAL_PATH") 598 + env_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 599 599 if env_path: 600 600 return str(Path(env_path).resolve()) 601 601 sandbox_path = _resolve_sandbox_journal()
+2 -2
think/callosum.py
··· 238 238 """Initialize connection (does not connect immediately). 239 239 240 240 Args: 241 - socket_path: Path to Unix socket (defaults to $JOURNAL_PATH/health/callosum.sock) 241 + socket_path: Path to Unix socket (defaults to journal/health/callosum.sock) 242 242 defaults: Default fields merged into every emit() call. None values are filtered out. 243 243 """ 244 244 if socket_path is None: ··· 418 418 Args: 419 419 tract: Message category/namespace 420 420 event: Event type 421 - socket_path: Optional socket path (defaults to $JOURNAL_PATH/health/callosum.sock) 421 + socket_path: Optional socket path (defaults to journal/health/callosum.sock) 422 422 timeout: Connection timeout in seconds (default: 2.0) 423 423 **fields: Additional message fields 424 424
+4 -8
think/config_cli.py
··· 3 3 4 4 """CLI for inspecting journal configuration. 5 5 6 - Shows the resolved journal configuration as JSON, or prints JOURNAL_PATH with 7 - its source for shell integration. 6 + Shows the resolved journal configuration as JSON, or prints the journal path. 8 7 9 8 Usage: 10 9 sol config Show full config JSON 11 - sol config env Show JOURNAL_PATH and source 10 + sol config env Show journal path 12 11 sol config facet rename OLD NEW Rename a facet 13 12 """ 14 13 ··· 33 32 rename_parser.add_argument("old_name", help="Current facet name") 34 33 rename_parser.add_argument("new_name", help="New facet name") 35 34 36 - # Capture journal info BEFORE setup_cli() loads .env 37 - journal_info = get_journal_info() 38 - 39 35 args = setup_cli(parser) 40 36 41 37 if args.subcommand == "env": 42 - path, source = journal_info 43 - print(f"JOURNAL_PATH={path} (from {source})") 38 + path, _source = get_journal_info() 39 + print(path) 44 40 elif args.subcommand == "facet": 45 41 if args.facet_action == "rename": 46 42 from think.facets import rename_facet
+1 -1
think/cortex.py
··· 277 277 env_overrides = config.get("env") 278 278 if env_overrides and isinstance(env_overrides, dict): 279 279 env.update({k: str(v) for k, v in env_overrides.items()}) 280 - env["JOURNAL_PATH"] = str(self.journal_path) 280 + env["_SOLSTONE_JOURNAL_OVERRIDE"] = str(self.journal_path) 281 281 282 282 # Spawn the subprocess 283 283 self.logger.info(f"Spawning {process_type} {agent_id}: {cmd}")
+11 -5
think/entities/matching.py
··· 249 249 250 250 # Tier 4b: Token-subset match (unambiguous only) 251 251 subset_matches = [ 252 - e for e in entities 252 + e 253 + for e in entities 253 254 if e.get("name") and _token_subset_match(detected_lower, e["name"].lower()) 254 255 ] 255 256 if len(subset_matches) == 1: ··· 257 258 258 259 # Tier 4c: Prefix-token match (unambiguous only) 259 260 prefix_matches = [ 260 - e for e in entities 261 + e 262 + for e in entities 261 263 if e.get("name") and _prefix_token_match(detected_lower, e["name"].lower()) 262 264 ] 263 265 if len(prefix_matches) == 1: ··· 322 324 id_set: set[str] = set() # all entity IDs for slug matching 323 325 first_word_map: dict[str, list[str]] = {} # lowercase first word → [entity_ids] 324 326 fuzzy_candidates: dict[str, str] = {} # candidate string → entity_id 325 - entity_name_info: list[tuple[str, str]] = [] # (entity_id, name_lower) for new tiers 327 + entity_name_info: list[ 328 + tuple[str, str] 329 + ] = [] # (entity_id, name_lower) for new tiers 326 330 327 331 for entity in entities: 328 332 name = entity.get("name", "") ··· 407 411 408 412 # Tier 4b: Token-subset match (unambiguous only) 409 413 subset_matches = [ 410 - eid for eid, ename in entity_name_info 414 + eid 415 + for eid, ename in entity_name_info 411 416 if _token_subset_match(sname_lower, ename) 412 417 ] 413 418 if len(subset_matches) == 1: ··· 416 421 417 422 # Tier 4c: Prefix-token match (unambiguous only) 418 423 prefix_matches = [ 419 - eid for eid, ename in entity_name_info 424 + eid 425 + for eid, ename in entity_name_info 420 426 if _prefix_token_match(sname_lower, ename) 421 427 ] 422 428 if len(prefix_matches) == 1:
+1 -1
think/formatters.py
··· 291 291 ) -> tuple[list[dict[str, Any]], dict[str, Any]]: 292 292 """Load file, detect formatter, return formatted chunks and metadata. 293 293 294 - File must be under JOURNAL_PATH. Supports JSONL, JSON, and Markdown files. 294 + File must be under the journal root. Supports JSONL, JSON, and Markdown files. 295 295 296 296 Args: 297 297 file_path: Absolute or journal-relative path to file
+1 -1
think/indexer/journal.py
··· 816 816 """Return SQLite connection for the journal index. 817 817 818 818 Args: 819 - journal: Path to journal root. Uses JOURNAL_PATH env var if not provided. 819 + journal: Path to journal root. Uses _SOLSTONE_JOURNAL_OVERRIDE env var if not provided. 820 820 821 821 Returns: 822 822 Tuple of (connection, db_path)
+1 -1
think/models.py
··· 990 990 991 991 992 992 def load_health_status() -> Optional[dict]: 993 - """Load health status from $JOURNAL_PATH/health/agents.json. 993 + """Load health status from journal/health/agents.json. 994 994 995 995 Returns parsed dict or None if file is missing/unreadable. 996 996 """
+11 -11
think/runner.py
··· 5 5 """Unified process spawning and lifecycle management utilities. 6 6 7 7 All subprocess output is automatically logged to: 8 - {JOURNAL_PATH}/{YYYYMMDD}/health/{ref}_{process_name}.log 8 + journal/{YYYYMMDD}/health/{ref}_{process_name}.log 9 9 10 10 Where process_name is derived from cmd[0] basename, and ref is a unique correlation ID. 11 11 12 12 Symlinks provide stable access paths: 13 - {JOURNAL_PATH}/{YYYYMMDD}/health/{process_name}.log (day-level symlink) 14 - {JOURNAL_PATH}/health/{process_name}.log (journal-level symlink) 13 + journal/{YYYYMMDD}/health/{process_name}.log (day-level symlink) 14 + journal/health/{process_name}.log (journal-level symlink) 15 15 16 16 Logs automatically roll over at midnight for long-running processes. 17 17 """ ··· 46 46 def _day_health_log_path(day: str, ref: str, name: str) -> Path: 47 47 """Build path to day health log. 48 48 49 - Returns: {JOURNAL_PATH}/{day}/health/{ref}_{name}.log 49 + Returns: journal/{day}/health/{ref}_{name}.log 50 50 """ 51 51 return _get_journal_path() / day / "health" / f"{ref}_{name}.log" 52 52 ··· 91 91 When ``day`` is provided, the writer is pinned to that day directory 92 92 and midnight rollover is disabled (batch processing of historical days). 93 93 94 - Writes to: {JOURNAL_PATH}/{YYYYMMDD}/health/{ref}_{name}.log 94 + Writes to: journal/{YYYYMMDD}/health/{ref}_{name}.log 95 95 96 96 Creates and maintains symlinks: 97 - - {JOURNAL_PATH}/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) 98 - - {JOURNAL_PATH}/health/{name}.log -> {YYYYMMDD}/health/{ref}_{name}.log (journal-level) 97 + - journal/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) 98 + - journal/health/{name}.log -> {YYYYMMDD}/health/{ref}_{name}.log (journal-level) 99 99 100 100 When the day changes, automatically closes old file, opens new file, and updates symlinks. 101 101 """ ··· 168 168 """Subprocess wrapper with automatic output logging and lifecycle management. 169 169 170 170 All output is automatically logged to: 171 - {JOURNAL_PATH}/{YYYYMMDD}/health/{ref}_{name}.log 171 + journal/{YYYYMMDD}/health/{ref}_{name}.log 172 172 173 173 Where name is derived from cmd[0] basename, and ref is a unique correlation ID. 174 174 175 175 Symlinks are automatically created and maintained: 176 - {JOURNAL_PATH}/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) 177 - {JOURNAL_PATH}/health/{name}.log -> {YYYYMMDD}/health/{ref}_{name}.log (journal-level) 176 + journal/{YYYYMMDD}/health/{name}.log -> {ref}_{name}.log (day-level) 177 + journal/health/{name}.log -> {YYYYMMDD}/health/{ref}_{name}.log (journal-level) 178 178 179 179 Logs roll over automatically at midnight for long-running processes. 180 180 ··· 442 442 """Run a task to completion with automatic logging (blocking). 443 443 444 444 Spawns process, waits for completion, cleans up resources. 445 - Output is automatically logged to: {JOURNAL_PATH}/{YYYYMMDD}/health/{ref}_{name}.log 445 + Output is automatically logged to: journal/{YYYYMMDD}/health/{ref}_{name}.log 446 446 where name is derived from cmd[0] basename. 447 447 448 448 Args:
+3 -3
think/streams.py
··· 17 17 Import (text): import.text 18 18 19 19 Storage: 20 - JOURNAL_PATH/streams/{name}.json - per-stream state (last segment, seq) 20 + journal/streams/{name}.json - per-stream state (last segment, seq) 21 21 {segment_dir}/stream.json - per-segment marker (stream, prev, seq) 22 22 """ 23 23 ··· 127 127 128 128 129 129 def get_stream_state(name: str) -> dict | None: 130 - """Load stream state from JOURNAL_PATH/streams/{name}.json. 130 + """Load stream state from journal/streams/{name}.json. 131 131 132 132 Returns 133 133 ------- ··· 287 287 288 288 289 289 def list_streams() -> list[dict]: 290 - """List all stream state files from JOURNAL_PATH/streams/. 290 + """List all stream state files from journal/streams/. 291 291 292 292 Returns 293 293 -------
+1 -1
think/supervisor.py
··· 1269 1269 """Log observe, dream, and activity events with day+segment to segment/events.jsonl. 1270 1270 1271 1271 Any observe, dream, or activity tract message with both day and segment fields 1272 - gets logged to JOURNAL_PATH/day/segment/events.jsonl if that directory exists. 1272 + gets logged to journal/day/segment/events.jsonl if that directory exists. 1273 1273 """ 1274 1274 if message.get("tract") not in {"observe", "dream", "activity"}: 1275 1275 return
+22 -81
think/utils.py
··· 22 22 from pathlib import Path 23 23 from typing import Any, Optional 24 24 25 - import platformdirs 26 - from dotenv import load_dotenv 27 25 from timefhuman import timefhuman 28 26 29 27 from media import MIME_TYPES 30 28 31 29 DATE_RE = re.compile(r"\d{8}") 32 - _journal_path_cache: str | None = None 33 30 34 31 35 32 def now_ms() -> int: ··· 83 80 def get_journal_info() -> tuple[str, str]: 84 81 """Return the journal path and its source. 85 82 86 - Determines where JOURNAL_PATH came from: 87 - - "shell": Set in shell environment before process started 88 - - "dotenv": Loaded from .env file 89 - - "default": Platform-specific default 90 - 91 - This function does NOT auto-create directories or modify the environment. 92 - Use get_journal() for normal operations that need the path created. 93 - 94 83 Returns 95 84 ------- 96 85 tuple[str, str] 97 - (path, source) where path is the journal directory and source is 98 - one of "shell", "dotenv", or "default". 86 + (path, source) where source is "override" when 87 + _SOLSTONE_JOURNAL_OVERRIDE is set, otherwise "project". 99 88 """ 100 - # Check if already set in shell environment (before loading .env) 101 - shell_value = os.environ.get("JOURNAL_PATH") 102 - 103 - if shell_value: 104 - return shell_value, "shell" 105 - 106 - # Load .env and check if it provides JOURNAL_PATH 107 - load_dotenv() 108 - dotenv_value = os.environ.get("JOURNAL_PATH") 89 + override = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 90 + if override: 91 + return override, "override" 109 92 110 - if dotenv_value: 111 - return dotenv_value, "dotenv" 112 - 113 - # Fall back to platform default 114 - data_dir = platformdirs.user_data_dir("solstone") 115 - default_journal = os.path.join(data_dir, "journal") 116 - return default_journal, "default" 93 + project_root = Path(__file__).resolve().parent.parent 94 + journal = str(project_root / "journal") 95 + return journal, "project" 117 96 118 97 119 98 def get_journal() -> str: 120 - """Return the journal path, auto-creating it if it doesn't exist. 121 - 122 - Resolution order: 123 - 1. JOURNAL_PATH environment variable (from .env or shell) - created if missing 124 - 2. Cached platform default from previous call 125 - 3. Platform-specific default: <user_data_dir>/solstone/journal 126 - 127 - When using the platform default, the path is cached and set in os.environ. 128 - Environment variable changes are always respected (no caching for explicit config). 129 - An INFO log message is emitted when auto-creating the default path. 99 + """Return the journal path: <project_root>/journal/ 130 100 131 - Returns 132 - ------- 133 - str 134 - Absolute path to the journal directory. 101 + The journal always lives at ./journal/ relative to the solstone 102 + project root. Auto-creates the directory if it doesn't exist. 135 103 """ 136 - global _journal_path_cache 104 + # Internal override for tests 105 + override = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 106 + if override: 107 + os.makedirs(override, exist_ok=True) 108 + return override 137 109 138 - # Always check environment first (allows tests to override) 139 - load_dotenv() 140 - journal = os.getenv("JOURNAL_PATH") 141 - 142 - if journal: 143 - # User explicitly configured a path - create it if needed and use it 144 - os.makedirs(journal, exist_ok=True) 145 - return journal 146 - 147 - # Use cached platform default if available 148 - if _journal_path_cache: 149 - return _journal_path_cache 150 - 151 - # Create platform-specific default 152 - data_dir = platformdirs.user_data_dir("solstone") 153 - default_journal = os.path.join(data_dir, "journal") 154 - 155 - # Create directory if needed 156 - os.makedirs(default_journal, exist_ok=True) 157 - 158 - # Set environment for this process and children 159 - os.environ["JOURNAL_PATH"] = default_journal 160 - _journal_path_cache = default_journal 161 - 162 - logging.info("Using default journal path: %s", default_journal) 163 - return default_journal 110 + project_root = Path(__file__).resolve().parent.parent 111 + journal = str(project_root / "journal") 112 + os.makedirs(journal, exist_ok=True) 113 + return journal 164 114 165 115 166 116 def day_path(day: Optional[str] = None) -> Path: ··· 689 639 690 640 logging.basicConfig(level=log_level) 691 641 692 - journal_path, journal_source = get_journal_info() 693 - if journal_source == "default": 694 - print( 695 - f"Note: JOURNAL_PATH not set; using platform default: {journal_path}\n" 696 - "To configure a custom path, set JOURNAL_PATH in your shell or .env file.\n" 697 - "See docs/INSTALL.md for setup instructions.", 698 - file=sys.stderr, 699 - ) 700 - 701 - # Initialize journal path (may auto-create default) 642 + # Initialize journal path (auto-creates if needed) 702 643 get_journal() 703 644 704 645 # Load config env as fallback for missing environment variables ··· 915 856 def write_service_port(service: str, port: int) -> None: 916 857 """Write a service's port to the health directory. 917 858 918 - Creates $JOURNAL_PATH/health/{service}.port with the port number. 859 + Creates journal/health/{service}.port with the port number. 919 860 920 861 Args: 921 862 service: Service name (e.g., "convey", "cortex")
-2
uv.lock
··· 3600 3600 { name = "opencv-python" }, 3601 3601 { name = "pdf2image" }, 3602 3602 { name = "pillow" }, 3603 - { name = "platformdirs" }, 3604 3603 { name = "playwright" }, 3605 3604 { name = "psutil" }, 3606 3605 { name = "pygobject", marker = "sys_platform == 'linux'" }, ··· 3651 3650 { name = "opencv-python" }, 3652 3651 { name = "pdf2image" }, 3653 3652 { name = "pillow" }, 3654 - { name = "platformdirs" }, 3655 3653 { name = "playwright", specifier = ">=1.40.0" }, 3656 3654 { name = "psutil" }, 3657 3655 { name = "pygobject", marker = "sys_platform == 'linux'" },