personal memory agent
0
fork

Configure Feed

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

journal: managed wrapper bootstrap, rename env var, two-branch resolver

Replace the ~/.local/bin/sol symlink with a managed POSIX shell wrapper
that embeds the journal path and the venv binary path as install-time
literals. The wrapper becomes the canonical setter for SOLSTONE_JOURNAL
on installed runs — services no longer carry a journal env key, and the
resolver's branch-1 reads SOLSTONE_JOURNAL set by the wrapper or by the
test autouse fixture.

Why this matters: the journal path needs one source of truth that
survives packaged installs (where there is no source tree to fall back
to) and tracks user reconfiguration cleanly. The wrapper does both —
sol config journal <path> rewrites it atomically under flock — and gets
the env-var coupling out of the service files where it didn't belong.

Resolver collapses to two branches: SOLSTONE_JOURNAL → source-tree
fallback (project root has both pyproject.toml and .git) → typed
SolstoneNotConfigured. Source labels become {"env", "source"}.

The env var is renamed from _SOLSTONE_JOURNAL_OVERRIDE to SOLSTONE_JOURNAL
across all 161 tracked files (1020 line hits) — clean break, no
backwards-compat alias.

Adds:
- think/install_guard.py: render/parse/atomic-write/flock helpers,
AliasState.FOREIGN, dual-OWNED state, --force, current/upgrade tokens
- think/config_cli.py: sol config show / sol config journal <path>
- think/utils.py: SolstoneNotConfigured, two-branch resolver
- think/service.py: services invoke ~/.local/bin/sol; no journal env
key in generated env blocks
- scripts/doctor.py: resolve_alias_target reads managed wrappers
- Makefile: current) no-op case in install-service
- Tests: install-guard wrapper round-trips, resolver branches,
config_cli show/journal, autouse fixture sentinel, service-file
shape
- Docs: docs/environment.md, AGENTS.md §3/§6/§8, docs/CORTEX.md, et al.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+2202 -1294
+4 -4
AGENTS.md
··· 54 54 55 55 **Key concepts, priority-ordered:** 56 56 57 - - **Journal** — the on-disk record rooted at `journal/` in the repo. Every day is a `journal/chronicle/YYYYMMDD/` directory. Segments (timestamped capture windows) are anchored to creation/modification time, not content "about" time. `get_journal()` from `think.utils` is the single source of truth for journal path resolution; trust it unconditionally. Never set `_SOLSTONE_JOURNAL_OVERRIDE` from application code (see §8). 57 + - **Journal** — the on-disk record rooted at `journal/` in the repo. Every day is a `journal/chronicle/YYYYMMDD/` directory. Segments (timestamped capture windows) are anchored to creation/modification time, not content "about" time. `get_journal()` from `think.utils` is the single source of truth for journal path resolution; trust it unconditionally. Installed runs inherit `SOLSTONE_JOURNAL` from the managed wrapper at `~/.local/bin/sol`; tests use the autouse fixture; sandboxes set it explicitly. Application code must not set it itself (see §8). 58 58 - **Talents** — AI processors (markdown prompt + optional Python post-hook). Each has a config in `talent/<name>.md` with frontmatter that declares hooks, priority, model, and output. Cortex spawns them as subprocesses. 59 59 - **Callosum** — Unix-socket JSON message bus at `journal/health/callosum.sock`. Real-time event distribution across services (`tract` + `event` + payload). If components need to talk asynchronously, they talk through callosum. 60 60 - **Cortex** — process manager for talent runs. Listens on callosum (`tract="cortex"`, `event="request"`), spawns `python -m think.talents` subprocesses, writes `<talent>/<ts>_active.jsonl` then renames to `<talent>/<ts>.jsonl` on completion, broadcasts all events back through callosum. Read `docs/CORTEX.md` before modifying talent execution. ··· 156 156 ## 6. Testing quickstart 157 157 158 158 - **Framework:** pytest. Files `test_*.py`, functions `test_*`. Shared fixtures in `tests/conftest.py`. 159 - - **Fixture journal:** `tests/fixtures/journal/` — a complete mock journal with facets, entities, segments, index state. Tests set `os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal"` (or use `monkeypatch.setenv`); this is the **only** place that env var is valid (see §8). 159 + - **Fixture journal:** `tests/fixtures/journal/` — a complete mock journal with facets, entities, segments, index state. The autouse `set_test_journal_path` fixture in `tests/conftest.py` sets `SOLSTONE_JOURNAL` to this path for unit tests. Individual tests may override it with `monkeypatch.setenv` when they need an isolated tmp journal (see §8). 160 160 - **Run one test:** `make test-only TEST=tests/test_utils.py::test_foo` or `TEST="-k test_foo"`. 161 161 - **Run app tests:** `make test-apps` or `make test-app APP=<name>`. 162 162 - **Integration tests** (`tests/integration/`): hit real provider APIs, require `.env` keys, run via `make test-integration`. ··· 243 243 The rules above govern *where* code lives. The rules below govern *how* code behaves. They exist because we got burned. 244 244 245 245 - **No backwards-compatibility shims.** All code that depends on this project lives in this repository — never add fallback aliases, re-exports for moved symbols, deprecated-parameter handling, or legacy support code. When renaming or removing something, update every usage directly. For journal data-format changes, write a migration script (see `docs/APPS.md` for `maint` commands); do not add a compatibility layer. Cogitate agents default to adding shims; resist this. 246 - - **Trust `get_journal()` unconditionally.** `get_journal()` from `think.utils` is the single source of truth for journal path resolution. **Never** set `_SOLSTONE_JOURNAL_OVERRIDE` from application code, agent prompts, subprocess environments, or service files. The env var exists exclusively for two external contexts: test harnesses (`monkeypatch.setenv`) and Makefile sandboxes. If you think you need to override the path from app code, you don't — fix the actual problem. See `docs/environment.md`. 246 + - **Trust `get_journal()` unconditionally.** `get_journal()` from `think.utils` is the single source of truth for journal path resolution. The managed wrapper at `~/.local/bin/sol` sets `SOLSTONE_JOURNAL` for installed runs; tests use the autouse fixture; Makefile sandboxes set it explicitly. Application code, agent prompts, subprocess environments, and service files must not set `SOLSTONE_JOURNAL` themselves. To rewrite the wrapper's embedded path use `sol config journal <path>`. See `docs/environment.md`. 247 247 - **SPDX header on every source file.** All Python (and other source) files begin with: 248 248 249 249 ```python ··· 289 289 | `docs/PROMPT_TEMPLATES.md` | Modifying talent prompt format or frontmatter | 290 290 | `docs/PROVIDERS.md` | Adding a new AI provider; debugging model selection | 291 291 | `docs/testing.md` | Writing integration tests; setting up fixtures; debugging test isolation | 292 - | `docs/environment.md` | Journal path resolution, service install details, `_SOLSTONE_JOURNAL_OVERRIDE` rules | 292 + | `docs/environment.md` | Journal path resolution, managed-wrapper behavior, service install details, and `SOLSTONE_JOURNAL` rules | 293 293 | `docs/coding-standards.md` | Full naming conventions, ruff / mypy config, dep-management details — reference for everything not promoted into this file | 294 294 | `docs/project-structure.md` | Canonical directory layout; resolving "where does this file go" debates | 295 295 | `docs/DOCTOR.md` | Diagnostics and debugging a running system |
+14 -10
Makefile
··· 175 175 echo "$$SANDBOX_JOURNAL" > .sandbox.journal; \ 176 176 echo "Sandbox journal: $$SANDBOX_JOURNAL"; \ 177 177 # Boot supervisor in background \ 178 - _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" PATH=$(CURDIR)/$(VENV_BIN):$$PATH \ 178 + SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" PATH=$(CURDIR)/$(VENV_BIN):$$PATH \ 179 179 $(VENV_BIN)/sol supervisor 0 --no-daily \ 180 180 > "$$SANDBOX_JOURNAL/health/supervisor.log" 2>&1 & \ 181 181 echo $$! > .sandbox.pid; \ ··· 184 184 echo "Waiting for services..."; \ 185 185 READY=false; \ 186 186 for i in $$(seq 1 20); do \ 187 - if _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol health > /dev/null 2>&1; then \ 187 + if SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol health > /dev/null 2>&1; then \ 188 188 READY=true; \ 189 189 break; \ 190 190 fi; \ ··· 229 229 .PHONY: sandbox-seed-observers 230 230 sandbox-seed-observers: ## Seed 4 sample observers into the running sandbox journal 231 231 @test -s .sandbox.journal || (echo "No sandbox running. Run 'make sandbox' first." && exit 1) 232 - @_SOLSTONE_JOURNAL_OVERRIDE=$$(cat .sandbox.journal) $(VENV_BIN)/python tests/fixtures/seed_observers.py 232 + @SOLSTONE_JOURNAL=$$(cat .sandbox.journal) $(VENV_BIN)/python tests/fixtures/seed_observers.py 233 233 234 234 # Verify API baselines against running sandbox 235 235 verify-api: .installed ··· 238 238 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 239 239 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 240 240 RESULT=0; \ 241 - _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol indexer --rescan-full > /dev/null; \ 242 - _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 241 + SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol indexer --rescan-full > /dev/null; \ 242 + SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 243 243 $(MAKE) sandbox-stop; \ 244 244 exit $$RESULT 245 245 ··· 254 254 SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 255 255 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 256 256 RESULT=0; \ 257 - _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol indexer --rescan-full > /dev/null; \ 258 - _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py update --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 257 + SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol indexer --rescan-full > /dev/null; \ 258 + SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py update --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 259 259 $(MAKE) sandbox-stop; \ 260 260 exit $$RESULT; \ 261 261 else \ ··· 322 322 BASE_URL="http://localhost:$$CONVEY_PORT"; \ 323 323 RESULT_API=0; \ 324 324 RESULT_BROWSER=0; \ 325 - _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol indexer --rescan-full > /dev/null; \ 325 + SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol indexer --rescan-full > /dev/null; \ 326 326 echo ""; \ 327 327 echo "=== API baseline verification ==="; \ 328 - _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "$$BASE_URL" || RESULT_API=$$?; \ 328 + SOLSTONE_JOURNAL="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "$$BASE_URL" || RESULT_API=$$?; \ 329 329 echo ""; \ 330 330 echo "=== Browser scenario verification ==="; \ 331 331 $(VENV_BIN)/python tests/verify_browser.py verify --base-url "$$BASE_URL" || RESULT_BROWSER=$$?; \ ··· 353 353 fi 354 354 355 355 # Test environment - use fixtures journal for all tests 356 - TEST_ENV = _SOLSTONE_JOURNAL_OVERRIDE=tests/fixtures/journal 356 + TEST_ENV = SOLSTONE_JOURNAL=tests/fixtures/journal 357 357 LINK_LIVE_TESTS = --ignore=tests/link/test_integration.py --ignore=tests/link/test_privacy_scan.py 358 358 359 359 # Venv tool shortcuts ··· 479 479 ;; \ 480 480 up""grade) \ 481 481 echo "mode: up""grade"; \ 482 + $(MAKE) install-checks || exit $$?; \ 483 + ;; \ 484 + current) \ 485 + echo "mode: current"; \ 482 486 $(MAKE) install-checks || exit $$?; \ 483 487 ;; \ 484 488 fresh) \
+1 -1
apps/activities/tests/conftest.py
··· 45 45 encoding="utf-8", 46 46 ) 47 47 48 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 48 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 49 49 monkeypatch.setenv("SOL_DAY", day) 50 50 monkeypatch.setenv("SOL_FACET", facet) 51 51 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1")
+2 -2
apps/chat/tests/test_routes.py
··· 32 32 src = Path("tests/fixtures/journal").resolve() 33 33 dst = tmp_path / "journal" 34 34 copytree_tracked(src, dst) 35 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(dst.resolve())) 35 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(dst.resolve())) 36 36 return dst 37 37 38 38 39 39 def _make_env(journal, monkeypatch) -> ChatTestEnv: 40 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 40 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 41 41 app = create_app(str(journal)) 42 42 app.config["TESTING"] = True 43 43 client = app.test_client()
+5 -5
apps/entities/tests/conftest.py
··· 46 46 src = Path(__file__).resolve().parents[3] / "tests" / "fixtures" / "journal" 47 47 dst = tmp_path / "journal" 48 48 copytree_tracked(src, dst) 49 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(dst.resolve())) 49 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(dst.resolve())) 50 50 clear_journal_entity_cache() 51 51 clear_entity_loading_cache() 52 52 clear_relationship_caches() ··· 59 59 60 60 @pytest.fixture 61 61 def client(journal_copy, monkeypatch): 62 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_copy)) 62 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_copy)) 63 63 app = create_app(str(journal_copy)) 64 64 return app.test_client() 65 65 ··· 78 78 entity_env(attached=[ 79 79 {"type": "Person", "name": "Alice", "description": "Friend"} 80 80 ]) 81 - # _SOLSTONE_JOURNAL_OVERRIDE is set, entity files exist 81 + # SOLSTONE_JOURNAL is set, entity files exist 82 82 """ 83 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 83 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 84 84 clear_journal_entity_cache() 85 85 clear_entity_loading_cache() 86 86 clear_relationship_caches() ··· 119 119 @pytest.fixture 120 120 def entity_move_env(tmp_path, monkeypatch): 121 121 """Create a two-facet environment for entity move tests.""" 122 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 122 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 123 123 clear_journal_entity_cache() 124 124 clear_entity_loading_cache() 125 125 clear_relationship_caches()
+1 -1
apps/health/tests/conftest.py
··· 39 39 log_file.parent.mkdir(parents=True, exist_ok=True) 40 40 log_file.write_text(log_content, encoding="utf-8") 41 41 42 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 42 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 43 43 44 44 from convey import create_app 45 45
+1 -1
apps/observer/tests/conftest.py
··· 40 40 ) 41 41 42 42 # Set environment 43 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 43 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 44 44 45 45 # Create Flask test client 46 46 from convey import create_app
+1 -1
apps/observer/tests/test_utils.py
··· 30 30 31 31 journal = tmp_path / "journal" 32 32 journal.mkdir() 33 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 33 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 34 34 monkeypatch.setattr(state, "journal_root", str(journal)) 35 35 36 36 # Create observers directory
+5 -5
apps/photos/tests/test_call.py
··· 103 103 [{"id": "alice_johnson", "name": "Alice Johnson", "type": "Person"}], 104 104 ) 105 105 106 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_dir)) 106 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_dir)) 107 107 monkeypatch.setattr(sys, "platform", "darwin") 108 108 109 109 result = runner.invoke( ··· 148 148 [{"id": "alice_johnson", "name": "Alice Johnson", "type": "Person"}], 149 149 ) 150 150 151 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_dir)) 151 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_dir)) 152 152 monkeypatch.setattr(sys, "platform", "darwin") 153 153 154 154 first = runner.invoke(call_app, ["photos", "sync", "--library", str(photos_db)]) ··· 185 185 [(1, 1, 1)], 186 186 ) 187 187 188 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_dir)) 188 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_dir)) 189 189 monkeypatch.setattr(sys, "platform", "darwin") 190 190 191 191 result = runner.invoke( ··· 211 211 [{"id": "alice_johnson", "name": "Alice Johnson", "type": "Person"}], 212 212 ) 213 213 214 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_dir)) 214 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_dir)) 215 215 monkeypatch.setattr(sys, "platform", "darwin") 216 216 217 217 runner.invoke(call_app, ["photos", "sync", "--library", str(photos_db)]) ··· 259 259 [{"id": "alice_johnson", "name": "Alice Johnson", "type": "Person"}], 260 260 ) 261 261 262 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_dir)) 262 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_dir)) 263 263 monkeypatch.setattr(sys, "platform", "darwin") 264 264 265 265 result = runner.invoke(
+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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 17 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 18 18 19 19 20 20 def _create_old_schema(tmp_path):
+1 -1
apps/settings/tests/conftest.py
··· 84 84 "observe": {"tmux": {"enabled": True, "capture_interval": 5}}, 85 85 } 86 86 config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8") 87 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 87 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 88 88 return tmp_path, config 89 89 90 90 return _create
+2 -2
apps/speakers/tests/conftest.py
··· 45 45 env = speakers_env() 46 46 env.create_segment("20240101", "143022_300", ["mic_audio"]) 47 47 env.create_entity("Alice Test") 48 - # Now _SOLSTONE_JOURNAL_OVERRIDE is set and data exists 48 + # Now SOLSTONE_JOURNAL is set and data exists 49 49 """ 50 50 51 51 class SpeakersEnv: 52 52 def __init__(self, journal_path: Path): 53 53 self.journal = journal_path 54 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 54 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 55 55 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 56 56 clear_journal_entity_cache() 57 57 clear_entity_loading_cache()
+1 -1
apps/speakers/tests/test_wipe.py
··· 19 19 tmp_path: Path, monkeypatch: pytest.MonkeyPatch 20 20 ) -> tuple[Path, list[Path]]: 21 21 journal = tmp_path / "journal" 22 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 22 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 23 23 24 24 import think.utils 25 25
+4 -4
apps/todos/tests/conftest.py
··· 34 34 {"text": "First item"}, 35 35 {"text": "Second item", "completed": True} 36 36 ]) 37 - # Now _SOLSTONE_JOURNAL_OVERRIDE is set and todo file exists 37 + # Now SOLSTONE_JOURNAL is set and todo file exists 38 38 """ 39 39 40 40 def _create( ··· 50 50 if entries is not None: 51 51 lines = [json.dumps(e, ensure_ascii=False) for e in entries] 52 52 todo_path.write_text("\n".join(lines) + "\n", encoding="utf-8") 53 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 53 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 54 54 return day, facet, todo_path 55 55 56 56 return _create ··· 80 80 # Create todos directory 81 81 (facet_path / "todos").mkdir() 82 82 83 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 83 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 84 84 return journal, facet 85 85 86 86 return _create ··· 89 89 @pytest.fixture 90 90 def move_env(tmp_path, monkeypatch): 91 91 """Create a two-facet environment for move tests.""" 92 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 92 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 93 93 94 94 def _create( 95 95 entries: list[dict] | None = None,
+31 -31
apps/todos/tests/test_todo.py
··· 38 38 39 39 40 40 def test_get_todos_returns_none_when_missing(monkeypatch, journal_root): 41 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 41 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 42 42 assert get_todos("20240101", "personal") is None 43 43 44 44 45 45 def test_get_todos_parses_basic_fields(monkeypatch, journal_root): 46 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 46 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 47 47 _write_todos( 48 48 journal_root, 49 49 "personal", ··· 76 76 77 77 78 78 def test_get_todos_handles_cancelled(monkeypatch, journal_root): 79 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 79 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 80 80 _write_todos( 81 81 journal_root, 82 82 "work", ··· 106 106 107 107 108 108 def test_get_todos_ignores_blank_lines(monkeypatch, journal_root): 109 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 109 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 110 110 # Write with blank lines mixed in 111 111 todos_dir = journal_root / "facets" / "personal" / "todos" 112 112 todos_dir.mkdir(parents=True, exist_ok=True) ··· 273 273 274 274 275 275 def test_upcoming_groups_future_days(monkeypatch, journal_root): 276 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 276 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 277 277 # Create facet structure 278 278 (journal_root / "facets" / "personal").mkdir(parents=True) 279 279 (journal_root / "facets" / "personal" / "facet.json").write_text( ··· 320 320 321 321 322 322 def test_upcoming_respects_limit(monkeypatch, journal_root): 323 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 323 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 324 324 # Create facet structure 325 325 (journal_root / "facets" / "work").mkdir(parents=True) 326 326 (journal_root / "facets" / "work" / "facet.json").write_text( ··· 347 347 348 348 def test_upcoming_excludes_cancelled(monkeypatch, journal_root): 349 349 """Cancelled todos should not appear in upcoming view.""" 350 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 350 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 351 351 (journal_root / "facets" / "personal").mkdir(parents=True) 352 352 (journal_root / "facets" / "personal" / "facet.json").write_text( 353 353 '{"title": "Personal"}', encoding="utf-8" ··· 372 372 373 373 374 374 def test_upcoming_when_no_future_todos(monkeypatch, journal_root): 375 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 375 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 376 376 (journal_root / "facets" / "personal").mkdir(parents=True) 377 377 378 378 _write_todos( ··· 390 390 391 391 392 392 def test_upcoming_filters_by_facet(monkeypatch, journal_root): 393 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 393 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 394 394 # Create multiple facets 395 395 for facet_name in ["personal", "work"]: 396 396 facet_dir = journal_root / "facets" / facet_name ··· 410 410 411 411 412 412 def test_upcoming_aggregates_all_facets(monkeypatch, journal_root): 413 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 413 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 414 414 # Create multiple facets 415 415 for facet_name in ["personal", "work"]: 416 416 facet_dir = journal_root / "facets" / facet_name ··· 432 432 433 433 def test_checklist_append_entry(monkeypatch, journal_root): 434 434 """Test TodoChecklist.append_entry() creates valid JSONL.""" 435 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 435 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 436 436 437 437 # Create facet directory 438 438 facets_dir = journal_root / "facets" / "work" ··· 455 455 456 456 def test_checklist_cancel_entry(monkeypatch, journal_root): 457 457 """Test TodoChecklist.cancel_entry() soft-deletes items.""" 458 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 458 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 459 459 460 460 _write_todos( 461 461 journal_root, ··· 480 480 monkeypatch, journal_root 481 481 ): 482 482 """Test TodoChecklist.display() always includes cancelled items with strikethrough.""" 483 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 483 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 484 484 485 485 _write_todos( 486 486 journal_root, ··· 503 503 504 504 505 505 def test_get_facets_with_todos(monkeypatch, journal_root): 506 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 506 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 507 507 508 508 # Create todos in multiple facets 509 509 _write_todos(journal_root, "personal", "20240105", [{"text": "Personal task"}]) ··· 528 528 """Test that timestamps are set when creating a new todo.""" 529 529 import time 530 530 531 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 531 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 532 532 533 533 # Create facet directory 534 534 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) ··· 549 549 """Test that updated_at changes when marking todo complete.""" 550 550 import time 551 551 552 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 552 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 553 553 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 554 554 555 555 checklist = TodoChecklist.load("20240110", "personal") ··· 569 569 """Test that updated_at changes when marking todo incomplete.""" 570 570 import time 571 571 572 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 572 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 573 573 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 574 574 575 575 checklist = TodoChecklist.load("20240110", "personal") ··· 594 594 """Test that updated_at changes when cancelling a todo.""" 595 595 import time 596 596 597 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 597 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 598 598 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 599 599 600 600 checklist = TodoChecklist.load("20240110", "personal") ··· 614 614 """Test that updated_at changes when updating todo text.""" 615 615 import time 616 616 617 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 617 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 618 618 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 619 619 620 620 checklist = TodoChecklist.load("20240110", "personal") ··· 632 632 633 633 def test_todo_item_timestamps_serialization(monkeypatch, journal_root): 634 634 """Test that timestamps are properly serialized to and from JSONL.""" 635 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 635 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 636 636 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 637 637 638 638 checklist = TodoChecklist.load("20240110", "personal") ··· 654 654 655 655 def test_todo_item_timestamps_in_as_dict(monkeypatch, journal_root): 656 656 """Test that timestamps are included in as_dict() output.""" 657 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 657 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 658 658 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 659 659 660 660 checklist = TodoChecklist.load("20240110", "personal") ··· 669 669 670 670 def test_todo_item_backward_compatibility_no_timestamps(monkeypatch, journal_root): 671 671 """Test loading files without timestamps (backward compatibility).""" 672 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 672 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 673 673 674 674 # Write old-format todos without timestamps 675 675 _write_todos( ··· 696 696 """Test that append_entry can preserve a provided created_at timestamp.""" 697 697 import time 698 698 699 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 699 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 700 700 (journal_root / "facets" / "personal" / "todos").mkdir(parents=True) 701 701 702 702 checklist = TodoChecklist.load("20240110", "personal") ··· 717 717 718 718 def test_detects_duplicate_in_other_facet(self, monkeypatch, journal_root): 719 719 """Exact duplicate in another facet is detected.""" 720 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 720 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 721 721 _write_todos(journal_root, "work", "20240102", [{"text": "Draft Q1 plan"}]) 722 722 matches = find_cross_facet_matches("Draft Q1 plan", "20240102", "personal") 723 723 assert len(matches) == 1 ··· 728 728 729 729 def test_detects_fuzzy_match(self, monkeypatch, journal_root): 730 730 """Fuzzy match above threshold is detected.""" 731 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 731 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 732 732 _write_todos( 733 733 journal_root, 734 734 "work", ··· 741 741 742 742 def test_no_false_positives(self, monkeypatch, journal_root): 743 743 """Unrelated todos in other facets are not flagged.""" 744 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 744 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 745 745 _write_todos(journal_root, "work", "20240102", [{"text": "Buy groceries"}]) 746 746 matches = find_cross_facet_matches("Draft Q1 plan", "20240102", "personal") 747 747 assert len(matches) == 0 748 748 749 749 def test_excludes_own_facet(self, monkeypatch, journal_root): 750 750 """Todos in the requesting facet are excluded.""" 751 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 751 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 752 752 _write_todos(journal_root, "personal", "20240102", [{"text": "Draft Q1 plan"}]) 753 753 matches = find_cross_facet_matches("Draft Q1 plan", "20240102", "personal") 754 754 assert len(matches) == 0 755 755 756 756 def test_excludes_cancelled(self, monkeypatch, journal_root): 757 757 """Cancelled todos are not matched.""" 758 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 758 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 759 759 _write_todos( 760 760 journal_root, 761 761 "work", ··· 767 767 768 768 def test_excludes_completed(self, monkeypatch, journal_root): 769 769 """Completed todos are not matched.""" 770 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 770 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 771 771 _write_todos( 772 772 journal_root, 773 773 "work", ··· 779 779 780 780 def test_day_range_covers_adjacent_days(self, monkeypatch, journal_root): 781 781 """Matches within ±1 day window are detected.""" 782 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 782 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 783 783 _write_todos(journal_root, "work", "20240101", [{"text": "Draft Q1 plan"}]) 784 784 _write_todos(journal_root, "work", "20240103", [{"text": "Draft Q1 plan"}]) 785 785 matches = find_cross_facet_matches("Draft Q1 plan", "20240102", "personal") ··· 787 787 788 788 def test_empty_journal_returns_empty(self, monkeypatch, journal_root): 789 789 """No facets returns empty list.""" 790 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_root)) 790 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_root)) 791 791 matches = find_cross_facet_matches("Draft Q1 plan", "20240102", "personal") 792 792 assert matches == []
+4 -4
apps/transcripts/tests/conftest.py
··· 29 29 """Point tests at a copied journal when needed, otherwise the tracked fixture.""" 30 30 if "journal_copy" in request.fixturenames: 31 31 journal_copy = request.getfixturevalue("journal_copy") 32 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_copy)) 32 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_copy)) 33 33 return 34 34 35 35 monkeypatch.setenv( 36 - "_SOLSTONE_JOURNAL_OVERRIDE", 36 + "SOLSTONE_JOURNAL", 37 37 os.path.join(os.getcwd(), "tests", "fixtures", "journal"), 38 38 ) 39 39 40 40 41 41 @pytest.fixture 42 42 def client(journal_copy, monkeypatch): 43 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_copy)) 43 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_copy)) 44 44 app = create_app(str(journal_copy)) 45 45 return app.test_client() 46 46 ··· 50 50 src = Path(__file__).resolve().parents[3] / "tests" / "fixtures" / "journal" 51 51 dst = tmp_path / "journal" 52 52 copytree_tracked(src, dst) 53 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(dst.resolve())) 53 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(dst.resolve())) 54 54 return dst
+2 -2
conftest.py
··· 102 102 + "\n".join(lines) 103 103 + "\n\n" 104 104 "To fix, use one of these isolation mechanisms:\n" 105 - " - journal_copy fixture (tests/conftest.py:57) — copies tracked fixtures to tmp_path\n" 106 - " - point _SOLSTONE_JOURNAL_OVERRIDE at a tmp_path directly\n" 105 + " - journal_copy fixture (tests/conftest.py:188) — copies tracked fixtures to tmp_path\n" 106 + " - point SOLSTONE_JOURNAL at a tmp_path directly\n" 107 107 " - mock the subprocess/write path so code never touches tests/fixtures/\n" 108 108 "\n" 109 109 "Prior incidents: f6f382a6, 2996e072\n"
+1 -1
docs/CORTEX.md
··· 276 276 - `env`: Environment variables to set for the agent subprocess (object) 277 277 - Keys are variable names, values are coerced to strings 278 278 - Request-level `env` overrides agent defaults 279 - - Note: `_SOLSTONE_JOURNAL_OVERRIDE` is set by Cortex for child processes 279 + - Note: `SOLSTONE_JOURNAL` is inherited by Cortex from the managed wrapper / test fixture / sandbox env, and child processes inherit it through `os.environ` 280 280 281 281 ### Model Resolution 282 282
+1 -1
docs/coding-standards.md
··· 38 38 - **Conciseness & Maintainability**: Clear code over clever code 39 39 - **Robustness**: Minimize assumptions that must be kept in sync across the codebase, avoid fragility and increasing maintenance burden. 40 40 - **Self-Contained Codebase**: All code that depends on this project lives within this repository—never add backwards-compatibility shims, fallback aliases, re-exports for moved symbols, deprecated parameter handling, or legacy support code. When renaming or removing something, update all usages directly. For journal data format changes, write a migration script (see [docs/APPS.md](docs/APPS.md) for `maint` commands) instead of adding compatibility layers. 41 - - **Trust system path resolution**: Never set `_SOLSTONE_JOURNAL_OVERRIDE` or bypass `get_journal()` from application code, agent prompts, subprocess environments, or service files. The env var exists only for tests and Makefile sandboxes. See `environment.md`. 41 + - **Trust system path resolution**: Never set `SOLSTONE_JOURNAL` or bypass `get_journal()` from application code, agent prompts, subprocess environments, or service files. Installed runs inherit it from the managed wrapper at `~/.local/bin/sol`; tests use the autouse fixture; Makefile sandboxes set it explicitly. See `environment.md`. 42 42 - **Security**: Never expose secrets, validate/sanitize all inputs 43 43 - **Performance**: Profile before optimizing 44 44 - **Git**: Small focused commits, descriptive branch names. Run git commands directly (not `git -C`) since you're already in the repo.
+1 -1
docs/design/push.md
··· 499 499 500 500 ## 8. Tests 501 501 502 - All push tests use `tests/fixtures/journal/` plus `monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", ...)` where necessary, following the existing voice integration and route test setup pattern (`tests/test_voice_routes.py:14-23`, `tests/test_voice_integration.py:102-149`). 502 + All push tests use `tests/fixtures/journal/` plus `monkeypatch.setenv("SOLSTONE_JOURNAL", ...)` where necessary, following the existing voice integration and route test setup pattern (`tests/test_voice_routes.py:14-23`, `tests/test_voice_integration.py:102-149`). 503 503 504 504 ### 8.1 `tests/test_push_config.py` 505 505
+31 -2
docs/environment.md
··· 2 2 3 3 ## Journal Path 4 4 5 - The journal lives at `journal/` in the project root. `get_journal()` from `think.utils` returns the path — trust it unconditionally. Never set `_SOLSTONE_JOURNAL_OVERRIDE` from application code, service files, agent prompts, or subprocess environments. The env var exists exclusively for external use: test harnesses (`monkeypatch.setenv`) and Makefile sandboxes. If you think you need to override the journal path, you don't — fix the actual problem instead. 5 + `get_journal()` / `get_journal_info()` in `think.utils` are the canonical journal resolvers. Trust them unconditionally. 6 + 7 + Resolver order: 8 + 9 + 1. `SOLSTONE_JOURNAL` if it is set 10 + 2. source-tree fallback: `<project_root>/journal` when both `<project_root>/pyproject.toml` and `<project_root>/.git` exist 11 + 3. `SolstoneNotConfigured` if neither branch resolves 12 + 13 + Who sets `SOLSTONE_JOURNAL`: 14 + 15 + - Installed runs: the managed wrapper at `~/.local/bin/sol` 16 + - Unit tests: the `set_test_journal_path` autouse fixture in `tests/conftest.py` 17 + - Makefile sandboxes: explicit per-command env injection in `make sandbox` / verify targets 18 + 19 + Who must **not** set it: 20 + 21 + - application code 22 + - service files 23 + - agent prompts 24 + - ad hoc subprocess environments spawned by app code 25 + 26 + If you think you need to set `SOLSTONE_JOURNAL` from application code, fix the actual resolution problem instead. 6 27 7 28 ## Service Installation 8 29 9 - `make install-service` installs the `sol` CLI alias in `~/.local/bin`, then installs solstone as a systemd user service (Linux) or launchd agent (macOS) with convey on port 5015. Override with `make install-service PORT=8000`. Managed via `sol service <install|start|stop|restart|status|logs>`. 30 + `make install-service` installs the managed wrapper at `~/.local/bin/sol`, then installs solstone as a systemd user service (Linux) or launchd agent (macOS) with convey on port 5015. Override with `make install-service PORT=8000`. 31 + 32 + Installed services invoke `~/.local/bin/sol`. They do **not** write `SOLSTONE_JOURNAL` into the service env block; the wrapper exports it before execing the venv `sol`. 33 + 34 + Use: 35 + 36 + - `sol config show` to display the resolved journal path, user-facing source label, and wrapper status 37 + - `sol config journal <path>` to atomically rewrite the wrapper's embedded journal path 38 + - `sol service <install|start|stop|restart|status|logs>` for service lifecycle management 10 39 11 40 ## API Keys 12 41
+4 -3
docs/testing.md
··· 17 17 ## Fixture Journal 18 18 19 19 ```python 20 - # Use comprehensive mock journal data for testing 21 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 20 + # The autouse set_test_journal_path fixture in tests/conftest.py does this 21 + # for unit tests. Set it explicitly only when a test needs a different journal. 22 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 22 23 # Now all journal operations work with test data 23 24 ``` 24 25 ··· 47 48 In a second terminal, take screenshots or hit endpoints: 48 49 49 50 ```bash 50 - export _SOLSTONE_JOURNAL_OVERRIDE=tests/fixtures/journal 51 + export SOLSTONE_JOURNAL=tests/fixtures/journal 51 52 export PATH=$(pwd)/.venv/bin:$PATH 52 53 sol screenshot / -o scratch/home.png 53 54 curl -s http://localhost:$(cat tests/fixtures/journal/health/convey.port)/
+19 -7
scripts/doctor.py
··· 395 395 396 396 def resolve_alias_target() -> Path | None: 397 397 alias = Path.home() / ".local" / "bin" / "sol" 398 - if not alias.is_symlink(): 398 + if not alias.exists() and not alias.is_symlink(): 399 399 return None 400 - target = Path(os.readlink(alias)) 401 - if not target.is_absolute(): 402 - target = alias.parent / target 403 - return target.resolve() 400 + if alias.is_symlink(): 401 + target = Path(os.readlink(alias)) 402 + if not target.is_absolute(): 403 + target = alias.parent / target 404 + return target.resolve() 405 + 406 + try: 407 + from think.install_guard import parse_wrapper 408 + 409 + content = alias.read_text(encoding="utf-8") 410 + except OSError: 411 + return None 412 + parsed = parse_wrapper(content) 413 + if parsed is None: 414 + return None 415 + return Path(parsed["sol_bin"]).resolve() 404 416 405 417 406 418 def resolve_darwin_exe( ··· 621 633 owned = alias_state_cls.OWNED 622 634 cross_repo = alias_state_cls.CROSS_REPO 623 635 dangling = alias_state_cls.DANGLING 624 - not_symlink = alias_state_cls.NOT_SYMLINK 636 + foreign = alias_state_cls.FOREIGN 625 637 if state is worktree: 626 638 return make_result( 627 639 check, ··· 638 650 detail = f"~/.local/bin/sol points at another repo ({other})" 639 651 elif state is dangling: 640 652 detail = f"~/.local/bin/sol is dangling ({other})" 641 - elif state is not_symlink: 653 + elif state is foreign: 642 654 detail = "~/.local/bin/sol exists but is not a symlink" 643 655 else: 644 656 detail = f"unexpected alias state: {state}"
+4 -4
tests/_baseline_harness.py
··· 60 60 """Patch env so create_app(journal) is fully isolated.""" 61 61 62 62 journal = Path(journal).resolve() 63 - prev_override = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 63 + prev_override = os.environ.get("SOLSTONE_JOURNAL") 64 64 65 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 65 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 66 66 try: 67 67 yield journal 68 68 finally: 69 69 if prev_override is None: 70 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 70 + os.environ.pop("SOLSTONE_JOURNAL", None) 71 71 else: 72 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = prev_override 72 + os.environ["SOLSTONE_JOURNAL"] = prev_override 73 73 74 74 75 75 def make_logged_in_test_client(journal: Path):
+5 -5
tests/conftest.py
··· 132 132 133 133 @pytest.fixture(autouse=True) 134 134 def set_test_journal_path(request, monkeypatch): 135 - """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for all unit tests. 135 + """Set SOLSTONE_JOURNAL to tests/fixtures/journal for all unit tests. 136 136 137 - This ensures all tests have a valid _SOLSTONE_JOURNAL_OVERRIDE without needing 137 + This ensures all tests have a valid SOLSTONE_JOURNAL without needing 138 138 to explicitly set it in each test. Integration tests are excluded. 139 139 """ 140 140 # Skip for integration tests - they may have different requirements 141 141 if "integration" in request.node.keywords: 142 142 return 143 143 144 - # Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for all unit tests 144 + # Set SOLSTONE_JOURNAL to tests/fixtures/journal for all unit tests 145 145 monkeypatch.setenv( 146 - "_SOLSTONE_JOURNAL_OVERRIDE", 146 + "SOLSTONE_JOURNAL", 147 147 str(Path("tests/fixtures/journal").resolve()), 148 148 ) 149 149 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") ··· 191 191 src = Path(__file__).resolve().parent / "fixtures" / "journal" 192 192 dst = tmp_path / "journal" 193 193 copytree_tracked(src, dst) 194 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(dst.resolve())) 194 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(dst.resolve())) 195 195 return dst 196 196 197 197
+1 -1
tests/fixtures/seed_observers.py
··· 87 87 parser = argparse.ArgumentParser( 88 88 description=( 89 89 "Sandbox-only observer seed helper. Requires " 90 - "_SOLSTONE_JOURNAL_OVERRIDE to already point at a sandbox journal." 90 + "SOLSTONE_JOURNAL to already point at a sandbox journal." 91 91 ) 92 92 ) 93 93 parser.parse_args()
+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("_SOLSTONE_JOURNAL_OVERRIDE") 57 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal_dir) 56 + old_path = os.environ.get("SOLSTONE_JOURNAL") 57 + os.environ["SOLSTONE_JOURNAL"] = str(journal_dir) 58 58 yield journal_dir 59 59 if old_path: 60 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = old_path 60 + os.environ["SOLSTONE_JOURNAL"] = old_path 61 61 else: 62 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 62 + os.environ.pop("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE") 28 + journal_path = os.getenv("SOLSTONE_JOURNAL") 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("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 47 + pytest.skip("SOLSTONE_JOURNAL not found in tests/fixtures/.env file") 48 48 49 49 # Prepare environment 50 50 env = os.environ.copy() 51 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 51 + env["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 155 + pytest.skip("SOLSTONE_JOURNAL not found in tests/fixtures/.env file") 156 156 157 157 # Prepare environment 158 158 env = os.environ.copy() 159 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 159 + env["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 229 + os.environ["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 23 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 24 24 yield journal 25 25 # Cleanup 26 - if "_SOLSTONE_JOURNAL_OVERRIDE" in os.environ: 27 - del os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 26 + if "SOLSTONE_JOURNAL" in os.environ: 27 + del os.environ["SOLSTONE_JOURNAL"] 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["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 22 + os.environ["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 63 + os.environ["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 96 + os.environ["SOLSTONE_JOURNAL"] = str(integration_journal_path) 97 97 98 98 # Create a mock agent script that just echoes 99 99 agents_dir = integration_journal_path / "talents" ··· 141 141 @pytest.mark.integration 142 142 def test_cortex_uses_listing(integration_journal_path): 143 143 """Test listing agents from the cortex_uses function.""" 144 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 144 + os.environ["SOLSTONE_JOURNAL"] = str(integration_journal_path) 145 145 146 146 # Create some test agent files 147 147 agents_dir = integration_journal_path / "talents" ··· 187 187 @pytest.mark.integration 188 188 def test_cortex_error_handling(integration_journal_path, callosum_server): 189 189 """Test that Cortex handles errors gracefully.""" 190 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(integration_journal_path) 190 + os.environ["SOLSTONE_JOURNAL"] = str(integration_journal_path) 191 191 192 192 # Listen for events 193 193 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("_SOLSTONE_JOURNAL_OVERRIDE") 28 + journal_path = os.getenv("SOLSTONE_JOURNAL") 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("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 47 + pytest.skip("SOLSTONE_JOURNAL not found in tests/fixtures/.env file") 48 48 49 49 # Prepare environment 50 50 env = os.environ.copy() 51 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 51 + env["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 139 + pytest.skip("SOLSTONE_JOURNAL not found in tests/fixtures/.env file") 140 140 141 141 # Prepare environment 142 142 env = os.environ.copy() 143 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 143 + env["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE") 28 + journal_path = os.getenv("SOLSTONE_JOURNAL") 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("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 47 + pytest.skip("SOLSTONE_JOURNAL not found in tests/fixtures/.env file") 48 48 49 49 # Prepare environment 50 50 env = os.environ.copy() 51 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 51 + env["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 147 + pytest.skip("SOLSTONE_JOURNAL not found in tests/fixtures/.env file") 148 148 149 149 # Prepare environment 150 150 env = os.environ.copy() 151 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 151 + env["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE not found in tests/fixtures/.env file") 232 + pytest.skip("SOLSTONE_JOURNAL not found in tests/fixtures/.env file") 233 233 234 234 # Prepare environment 235 235 env = os.environ.copy() 236 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = journal_path 236 + env["SOLSTONE_JOURNAL"] = journal_path 237 237 env["OPENAI_API_KEY"] = api_key 238 238 239 239 # Include extra_context like get_talent() does in production
+1 -1
tests/link/live_helpers.py
··· 150 150 repo_root = Path(__file__).resolve().parents[2] 151 151 sol_bin = Path(sys.executable).with_name("sol") 152 152 env = os.environ.copy() 153 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal_path) 153 + env["SOLSTONE_JOURNAL"] = str(journal_path) 154 154 env["SOL_LINK_RELAY_URL"] = relay_url 155 155 env["SOL_SKIP_SUPERVISOR_CHECK"] = "1" 156 156 env["PYTHONUNBUFFERED"] = "1"
+2 -2
tests/link/test_integration.py
··· 37 37 ) -> None: 38 38 tmp_journal = tmp_path / "journal" 39 39 tmp_journal.mkdir() 40 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_journal)) 40 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_journal)) 41 41 42 42 with ( 43 43 running_convey_server(tmp_journal) as base_url, ··· 113 113 ) -> None: 114 114 tmp_journal = tmp_path / "journal" 115 115 tmp_journal.mkdir() 116 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_journal)) 116 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_journal)) 117 117 118 118 with ( 119 119 running_convey_server(tmp_journal) as base_url,
+1 -1
tests/link/test_paths.py
··· 20 20 21 21 22 22 def _set_journal(monkeypatch: pytest.MonkeyPatch, journal: Path) -> None: 23 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 23 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 24 24 25 25 26 26 def test_link_state_load_or_create_creates_state(
+1 -1
tests/link/test_privacy_scan.py
··· 39 39 async def test_privacy_scan(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: 40 40 tmp_journal = tmp_path / "journal" 41 41 tmp_journal.mkdir() 42 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_journal)) 42 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_journal)) 43 43 44 44 capture = None 45 45 with (
+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 _SOLSTONE_JOURNAL_OVERRIDE.""" 18 + """Set up a test facet with SOLSTONE_JOURNAL.""" 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("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 30 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 31 31 return journal, "test_facet" 32 32 33 33
+64 -64
tests/test_activities.py
··· 68 68 assert "email" in always_on_ids 69 69 70 70 with tempfile.TemporaryDirectory() as tmpdir: 71 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 72 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 71 + original_path = os.environ.get("SOLSTONE_JOURNAL") 72 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 73 73 74 74 facet_path = Path(tmpdir) / "facets" / "test_facet" 75 75 facet_path.mkdir(parents=True) ··· 90 90 91 91 finally: 92 92 if original_path: 93 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 93 + os.environ["SOLSTONE_JOURNAL"] = original_path 94 94 95 95 96 96 def test_generate_activity_id(): ··· 134 134 from think.activities import DEFAULT_ACTIVITIES, get_facet_activities 135 135 136 136 with tempfile.TemporaryDirectory() as tmpdir: 137 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 138 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 137 + original_path = os.environ.get("SOLSTONE_JOURNAL") 138 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 139 139 140 140 facet_path = Path(tmpdir) / "facets" / "new_facet" 141 141 facet_path.mkdir(parents=True) ··· 154 154 155 155 finally: 156 156 if original_path: 157 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 157 + os.environ["SOLSTONE_JOURNAL"] = original_path 158 158 159 159 160 160 def test_configured_facet_includes_meeting_always_on(): ··· 162 162 from think.activities import get_facet_activities, save_facet_activities 163 163 164 164 with tempfile.TemporaryDirectory() as tmpdir: 165 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 166 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 165 + original_path = os.environ.get("SOLSTONE_JOURNAL") 166 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 167 167 168 168 facet_path = Path(tmpdir) / "facets" / "work" 169 169 facet_path.mkdir(parents=True) ··· 182 182 183 183 finally: 184 184 if original_path: 185 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 185 + os.environ["SOLSTONE_JOURNAL"] = original_path 186 186 187 187 188 188 def test_facet_activities_roundtrip(): ··· 196 196 197 197 # Create a temp journal 198 198 with tempfile.TemporaryDirectory() as tmpdir: 199 - # Temporarily override _SOLSTONE_JOURNAL_OVERRIDE 200 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 201 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 199 + # Temporarily override SOLSTONE_JOURNAL 200 + original_path = os.environ.get("SOLSTONE_JOURNAL") 201 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 202 202 203 203 # Create facet directory 204 204 facet_path = Path(tmpdir) / "facets" / "test_facet" ··· 264 264 265 265 finally: 266 266 if original_path: 267 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 267 + os.environ["SOLSTONE_JOURNAL"] = original_path 268 268 269 269 270 270 def test_add_activity_to_facet(): ··· 276 276 ) 277 277 278 278 with tempfile.TemporaryDirectory() as tmpdir: 279 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 280 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 279 + original_path = os.environ.get("SOLSTONE_JOURNAL") 280 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 281 281 282 282 facet_path = Path(tmpdir) / "facets" / "test_facet" 283 283 facet_path.mkdir(parents=True) ··· 337 337 338 338 finally: 339 339 if original_path: 340 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 340 + os.environ["SOLSTONE_JOURNAL"] = original_path 341 341 342 342 343 343 def test_update_activity_in_facet(): ··· 349 349 ) 350 350 351 351 with tempfile.TemporaryDirectory() as tmpdir: 352 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 353 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 352 + original_path = os.environ.get("SOLSTONE_JOURNAL") 353 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 354 354 355 355 facet_path = Path(tmpdir) / "facets" / "test_facet" 356 356 facet_path.mkdir(parents=True) ··· 422 422 423 423 finally: 424 424 if original_path: 425 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 425 + os.environ["SOLSTONE_JOURNAL"] = original_path 426 426 427 427 428 428 def test_format_activities_context_includes_instructions(): ··· 430 430 from think.activities import save_facet_activities 431 431 432 432 with tempfile.TemporaryDirectory() as tmpdir: 433 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 434 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 433 + original_path = os.environ.get("SOLSTONE_JOURNAL") 434 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 435 435 436 436 facet_path = Path(tmpdir) / "facets" / "test_facet" 437 437 facet_path.mkdir(parents=True) ··· 465 465 466 466 finally: 467 467 if original_path: 468 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 468 + os.environ["SOLSTONE_JOURNAL"] = original_path 469 469 470 470 471 471 # --------------------------------------------------------------------------- ··· 514 514 from think.activities import append_activity_record, load_activity_records 515 515 516 516 with tempfile.TemporaryDirectory() as tmpdir: 517 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 517 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 518 518 519 519 record = { 520 520 "id": "coding_100000_300", ··· 540 540 from think.activities import append_activity_record, load_activity_records 541 541 542 542 with tempfile.TemporaryDirectory() as tmpdir: 543 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 543 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 544 544 545 545 record = { 546 546 "id": "coding_100000_300", ··· 559 559 from think.activities import load_activity_records 560 560 561 561 with tempfile.TemporaryDirectory() as tmpdir: 562 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 562 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 563 563 assert load_activity_records("work", "20260209") == [] 564 564 565 565 def test_update_description(self, monkeypatch): ··· 570 570 ) 571 571 572 572 with tempfile.TemporaryDirectory() as tmpdir: 573 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 573 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 574 574 575 575 record = { 576 576 "id": "coding_100000_300", ··· 599 599 ) 600 600 601 601 with tempfile.TemporaryDirectory() as tmpdir: 602 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 602 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 603 603 604 604 record = { 605 605 "id": "coding_100000_300", ··· 635 635 ) 636 636 637 637 with tempfile.TemporaryDirectory() as tmpdir: 638 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 638 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 639 639 640 640 append_activity_record( 641 641 "work", ··· 672 672 from think.activities import update_record_description 673 673 674 674 with tempfile.TemporaryDirectory() as tmpdir: 675 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 675 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 676 676 assert ( 677 677 update_record_description("work", "20260209", "nonexistent", "desc") 678 678 is False ··· 686 686 ) 687 687 688 688 with tempfile.TemporaryDirectory() as tmpdir: 689 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 689 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 690 690 691 691 r1 = { 692 692 "id": "coding_100000_300", ··· 723 723 ) 724 724 725 725 with tempfile.TemporaryDirectory() as tmpdir: 726 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 726 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 727 727 728 728 append_activity_record( 729 729 "work", ··· 759 759 from think.activities import update_activity_record 760 760 761 761 with tempfile.TemporaryDirectory() as tmpdir: 762 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 762 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 763 763 764 764 with pytest.raises(ValueError, match="patch cannot be empty"): 765 765 update_activity_record( ··· 828 828 ) 829 829 830 830 with tempfile.TemporaryDirectory() as tmpdir: 831 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 831 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 832 832 833 833 append_activity_record( 834 834 "work", ··· 919 919 from talent.activities import _list_facets_with_activity_state 920 920 921 921 with tempfile.TemporaryDirectory() as tmpdir: 922 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 922 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 923 923 924 924 _setup_segment(tmpdir, "20260209", "100000_300", "personal", []) 925 925 _setup_segment(tmpdir, "20260209", "100000_300", "work", []) ··· 933 933 from talent.activities import _list_facets_with_activity_state 934 934 935 935 with tempfile.TemporaryDirectory() as tmpdir: 936 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 936 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 937 937 assert ( 938 938 _list_facets_with_activity_state( 939 939 "20260209", "100000_300", stream="default" ··· 1024 1024 from talent.activities import _walk_activity_segments 1025 1025 1026 1026 with tempfile.TemporaryDirectory() as tmpdir: 1027 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1027 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1028 1028 1029 1029 _setup_segment( 1030 1030 tmpdir, ··· 1072 1072 from talent.activities import _walk_activity_segments 1073 1073 1074 1074 with tempfile.TemporaryDirectory() as tmpdir: 1075 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1075 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1076 1076 1077 1077 _setup_segment( 1078 1078 tmpdir, ··· 1115 1115 from talent.activities import _walk_activity_segments 1116 1116 1117 1117 with tempfile.TemporaryDirectory() as tmpdir: 1118 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1118 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1119 1119 (Path(tmpdir) / "chronicle" / "20260209").mkdir(parents=True) 1120 1120 1121 1121 result = _walk_activity_segments( ··· 1131 1131 from talent.activities import pre_process 1132 1132 1133 1133 with tempfile.TemporaryDirectory() as tmpdir: 1134 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1134 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1135 1135 1136 1136 day_dir = Path(tmpdir) / "chronicle" / "20260209" 1137 1137 day_dir.mkdir(parents=True) ··· 1147 1147 from talent.activities import pre_process 1148 1148 1149 1149 with tempfile.TemporaryDirectory() as tmpdir: 1150 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1150 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1151 1151 1152 1152 _setup_segment( 1153 1153 tmpdir, ··· 1189 1189 from think.activities import load_activity_records 1190 1190 1191 1191 with tempfile.TemporaryDirectory() as tmpdir: 1192 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1192 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1193 1193 1194 1194 _setup_segment( 1195 1195 tmpdir, ··· 1227 1227 from think.activities import load_activity_records 1228 1228 1229 1229 with tempfile.TemporaryDirectory() as tmpdir: 1230 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1230 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1231 1231 1232 1232 _setup_segment( 1233 1233 tmpdir, ··· 1258 1258 from think.activities import load_activity_records 1259 1259 1260 1260 with tempfile.TemporaryDirectory() as tmpdir: 1261 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1261 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1262 1262 1263 1263 _setup_segment( 1264 1264 tmpdir, ··· 1313 1313 from think.activities import load_activity_records 1314 1314 1315 1315 with tempfile.TemporaryDirectory() as tmpdir: 1316 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1316 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1317 1317 1318 1318 _setup_segment( 1319 1319 tmpdir, ··· 1386 1386 from think.activities import append_activity_record, load_activity_records 1387 1387 1388 1388 with tempfile.TemporaryDirectory() as tmpdir: 1389 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1389 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1390 1390 1391 1391 record = { 1392 1392 "id": "coding_100000_300", ··· 1425 1425 from think.activities import append_activity_record, load_activity_records 1426 1426 1427 1427 with tempfile.TemporaryDirectory() as tmpdir: 1428 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1428 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1429 1429 1430 1430 append_activity_record( 1431 1431 "work", ··· 1475 1475 from talent.activities import post_process 1476 1476 1477 1477 with tempfile.TemporaryDirectory() as tmpdir: 1478 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1478 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1479 1479 1480 1480 result = post_process("{}", {"day": "20260209"}) 1481 1481 assert result is None ··· 1505 1505 from talent.activities import pre_process 1506 1506 1507 1507 with tempfile.TemporaryDirectory() as tmpdir: 1508 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1508 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1509 1509 1510 1510 _setup_segment( 1511 1511 tmpdir, ··· 1549 1549 from talent.activities import pre_process 1550 1550 1551 1551 with tempfile.TemporaryDirectory() as tmpdir: 1552 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1552 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1553 1553 1554 1554 _setup_segment( 1555 1555 tmpdir, ··· 1601 1601 from think.activities import append_activity_record 1602 1602 1603 1603 with tempfile.TemporaryDirectory() as tmpdir: 1604 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1604 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1605 1605 1606 1606 record = { 1607 1607 "id": "coding_100000_300", ··· 1664 1664 from talent.activities import post_process 1665 1665 1666 1666 with tempfile.TemporaryDirectory() as tmpdir: 1667 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1667 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1668 1668 1669 1669 meta = { 1670 1670 "activity_records": { ··· 1702 1702 from talent.activities import post_process 1703 1703 1704 1704 with tempfile.TemporaryDirectory() as tmpdir: 1705 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1705 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1706 1706 1707 1707 with patch("talent.activities.callosum_send") as mock_send: 1708 1708 post_process("{}", {"day": "20260209", "segment": "100500_300"}) ··· 1714 1714 from talent.activities import post_process 1715 1715 1716 1716 with tempfile.TemporaryDirectory() as tmpdir: 1717 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1717 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1718 1718 1719 1719 meta = { 1720 1720 "activity_records": { ··· 1858 1858 from think.activities import load_activity_records 1859 1859 1860 1860 with tempfile.TemporaryDirectory() as tmpdir: 1861 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1861 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1862 1862 1863 1863 # Set up a segment with an active activity (no following segment) 1864 1864 _setup_segment( ··· 1902 1902 from talent.activities import pre_process 1903 1903 1904 1904 with tempfile.TemporaryDirectory() as tmpdir: 1905 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1905 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1906 1906 1907 1907 # Set up a segment with only ended activities 1908 1908 _setup_segment( ··· 1933 1933 from talent.activities import pre_process 1934 1934 1935 1935 with tempfile.TemporaryDirectory() as tmpdir: 1936 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1936 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1937 1937 1938 1938 # Create segment dir but no activity_state files 1939 1939 seg_dir = Path(tmpdir) / "chronicle" / "20260209" / "default" / "100000_300" ··· 1954 1954 from think.activities import load_activity_records 1955 1955 1956 1956 with tempfile.TemporaryDirectory() as tmpdir: 1957 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1957 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1958 1958 1959 1959 _setup_segment( 1960 1960 tmpdir, ··· 2009 2009 from think.activities import load_activity_records 2010 2010 2011 2011 with tempfile.TemporaryDirectory() as tmpdir: 2012 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 2012 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 2013 2013 2014 2014 _setup_segment( 2015 2015 tmpdir, ··· 2043 2043 from talent.activities import pre_process 2044 2044 2045 2045 with tempfile.TemporaryDirectory() as tmpdir: 2046 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 2046 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 2047 2047 2048 2048 _setup_segment( 2049 2049 tmpdir, ··· 2264 2264 from think.activities import dedup_anticipation 2265 2265 2266 2266 with tempfile.TemporaryDirectory() as tmpdir: 2267 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 2267 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 2268 2268 2269 2269 should_write, superseded_ids = dedup_anticipation( 2270 2270 "work", ··· 2280 2280 from think.activities import dedup_anticipation 2281 2281 2282 2282 with tempfile.TemporaryDirectory() as tmpdir: 2283 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 2283 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 2284 2284 _seed_activity_records( 2285 2285 tmpdir, 2286 2286 "work", ··· 2310 2310 from think.activities import dedup_anticipation 2311 2311 2312 2312 with tempfile.TemporaryDirectory() as tmpdir: 2313 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 2313 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 2314 2314 _seed_activity_records( 2315 2315 tmpdir, 2316 2316 "work", ··· 2343 2343 from think.activities import dedup_anticipation 2344 2344 2345 2345 with tempfile.TemporaryDirectory() as tmpdir: 2346 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 2346 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 2347 2347 _seed_activity_records( 2348 2348 tmpdir, 2349 2349 "work", ··· 2384 2384 from think.activities import dedup_anticipation 2385 2385 2386 2386 with tempfile.TemporaryDirectory() as tmpdir: 2387 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 2387 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 2388 2388 _seed_activity_records( 2389 2389 tmpdir, 2390 2390 "work",
+1 -1
tests/test_activities_cli_create.py
··· 20 20 21 21 22 22 def _configure_cli_env(tmp_path, monkeypatch) -> None: 23 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 23 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 24 24 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 25 25 26 26 import think.utils
+1 -1
tests/test_activities_locking.py
··· 13 13 locked_modify, 14 14 ) 15 15 16 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 17 17 18 18 facet = "work" 19 19 day = "20260418"
+3 -3
tests/test_activity_record_merge.py
··· 33 33 34 34 facet = "work" 35 35 day = "20260418" 36 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 36 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 37 37 38 38 _write_detected_entities( 39 39 tmp_path, ··· 95 95 96 96 facet = "work" 97 97 day = "20260418" 98 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 98 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 99 99 100 100 append_activity_record(facet, day, _activity_record()) 101 101 record_path = tmp_path / "facets" / facet / "activities" / f"{day}.jsonl" ··· 118 118 119 119 facet = "work" 120 120 day = "20260418" 121 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 121 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 122 122 123 123 append_activity_record(facet, day, _activity_record()) 124 124 record_path = tmp_path / "facets" / facet / "activities" / f"{day}.jsonl"
+72 -72
tests/test_activity_state.py
··· 49 49 from talent.activity_state import find_previous_segment 50 50 51 51 with tempfile.TemporaryDirectory() as tmpdir: 52 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 53 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 52 + original_path = os.environ.get("SOLSTONE_JOURNAL") 53 + os.environ["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 70 + os.environ["SOLSTONE_JOURNAL"] = original_path 71 71 72 72 def test_returns_none_for_nonexistent_day(self): 73 73 from talent.activity_state import find_previous_segment 74 74 75 75 with tempfile.TemporaryDirectory() as tmpdir: 76 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 77 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 76 + original_path = os.environ.get("SOLSTONE_JOURNAL") 77 + os.environ["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 83 + os.environ["SOLSTONE_JOURNAL"] = original_path 84 84 85 85 def test_handles_segments_with_suffix(self): 86 86 from talent.activity_state import find_previous_segment 87 87 88 88 with tempfile.TemporaryDirectory() as tmpdir: 89 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 90 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 89 + original_path = os.environ.get("SOLSTONE_JOURNAL") 90 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 91 91 92 92 try: 93 93 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 103 103 104 104 finally: 105 105 if original_path: 106 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 106 + os.environ["SOLSTONE_JOURNAL"] = original_path 107 107 108 108 109 109 class TestCheckTimeout: ··· 137 137 from talent.activity_state import load_previous_state 138 138 139 139 with tempfile.TemporaryDirectory() as tmpdir: 140 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 141 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 140 + original_path = os.environ.get("SOLSTONE_JOURNAL") 141 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 142 142 143 143 try: 144 144 # Create state file (new flat format) ··· 170 170 171 171 finally: 172 172 if original_path: 173 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 173 + os.environ["SOLSTONE_JOURNAL"] = original_path 174 174 175 175 def test_returns_none_for_missing_file(self): 176 176 from talent.activity_state import load_previous_state 177 177 178 178 with tempfile.TemporaryDirectory() as tmpdir: 179 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 180 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 179 + original_path = os.environ.get("SOLSTONE_JOURNAL") 180 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 181 181 182 182 try: 183 183 segment_dir = ( ··· 194 194 195 195 finally: 196 196 if original_path: 197 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 197 + os.environ["SOLSTONE_JOURNAL"] = original_path 198 198 199 199 def test_rejects_non_array(self): 200 200 from talent.activity_state import load_previous_state 201 201 202 202 with tempfile.TemporaryDirectory() as tmpdir: 203 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 204 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 203 + original_path = os.environ.get("SOLSTONE_JOURNAL") 204 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 205 205 206 206 try: 207 207 segment_dir = ( ··· 223 223 224 224 finally: 225 225 if original_path: 226 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 226 + os.environ["SOLSTONE_JOURNAL"] = original_path 227 227 228 228 229 229 class TestFormatActivitiesContext: ··· 233 233 from talent.activity_state import format_activities_context 234 234 235 235 with tempfile.TemporaryDirectory() as tmpdir: 236 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 237 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 236 + original_path = os.environ.get("SOLSTONE_JOURNAL") 237 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 238 238 239 239 try: 240 240 # Create facet with activities ··· 257 257 258 258 finally: 259 259 if original_path: 260 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 260 + os.environ["SOLSTONE_JOURNAL"] = original_path 261 261 262 262 def test_handles_empty_activities(self): 263 263 """Facet with no activities.jsonl still gets always-on defaults.""" 264 264 from talent.activity_state import format_activities_context 265 265 266 266 with tempfile.TemporaryDirectory() as tmpdir: 267 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 268 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 267 + original_path = os.environ.get("SOLSTONE_JOURNAL") 268 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 269 269 270 270 try: 271 271 # Create facet without activities ··· 280 280 281 281 finally: 282 282 if original_path: 283 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 283 + os.environ["SOLSTONE_JOURNAL"] = original_path 284 284 285 285 286 286 class TestFormatPreviousState: ··· 356 356 from talent.activity_state import pre_process 357 357 358 358 with tempfile.TemporaryDirectory() as tmpdir: 359 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 360 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 359 + original_path = os.environ.get("SOLSTONE_JOURNAL") 360 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 361 361 362 362 try: 363 363 # Create day and segments ··· 417 417 418 418 finally: 419 419 if original_path: 420 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 420 + os.environ["SOLSTONE_JOURNAL"] = original_path 421 421 422 422 def test_returns_none_without_day(self): 423 423 from talent.activity_state import pre_process ··· 477 477 from talent.activity_state import post_process 478 478 479 479 with tempfile.TemporaryDirectory() as tmpdir: 480 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 481 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 480 + original_path = os.environ.get("SOLSTONE_JOURNAL") 481 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 482 482 483 483 try: 484 484 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 529 529 530 530 finally: 531 531 if original_path: 532 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 532 + os.environ["SOLSTONE_JOURNAL"] = original_path 533 533 534 534 def test_ended_activity_copies_since(self): 535 535 from talent.activity_state import post_process 536 536 537 537 with tempfile.TemporaryDirectory() as tmpdir: 538 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 539 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 538 + original_path = os.environ.get("SOLSTONE_JOURNAL") 539 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 540 540 541 541 try: 542 542 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 586 586 587 587 finally: 588 588 if original_path: 589 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 589 + os.environ["SOLSTONE_JOURNAL"] = original_path 590 590 591 591 def test_no_previous_state_continuing_becomes_new(self): 592 592 from talent.activity_state import post_process ··· 659 659 from talent.activity_state import post_process 660 660 661 661 with tempfile.TemporaryDirectory() as tmpdir: 662 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 663 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 662 + original_path = os.environ.get("SOLSTONE_JOURNAL") 663 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 664 664 665 665 try: 666 666 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 707 707 708 708 finally: 709 709 if original_path: 710 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 710 + os.environ["SOLSTONE_JOURNAL"] = original_path 711 711 712 712 def test_unmatched_ended_novel_desc_with_prev_ended_becomes_active(self): 713 713 """Ended activity with novel description (different from prev ended) ··· 715 715 from talent.activity_state import post_process 716 716 717 717 with tempfile.TemporaryDirectory() as tmpdir: 718 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 719 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 718 + original_path = os.environ.get("SOLSTONE_JOURNAL") 719 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 720 720 721 721 try: 722 722 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 765 765 766 766 finally: 767 767 if original_path: 768 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 768 + os.environ["SOLSTONE_JOURNAL"] = original_path 769 769 770 770 def test_empty_array_passthrough(self): 771 771 from talent.activity_state import post_process ··· 797 797 from talent.activity_state import post_process 798 798 799 799 with tempfile.TemporaryDirectory() as tmpdir: 800 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 801 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 800 + original_path = os.environ.get("SOLSTONE_JOURNAL") 801 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 802 802 803 803 try: 804 804 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 859 859 860 860 finally: 861 861 if original_path: 862 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 862 + os.environ["SOLSTONE_JOURNAL"] = original_path 863 863 864 864 def test_default_level_for_new(self): 865 865 """New activity without level gets default 'medium'.""" ··· 918 918 from talent.activity_state import post_process 919 919 920 920 with tempfile.TemporaryDirectory() as tmpdir: 921 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 922 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 921 + original_path = os.environ.get("SOLSTONE_JOURNAL") 922 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 923 923 924 924 try: 925 925 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 968 968 969 969 finally: 970 970 if original_path: 971 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 971 + os.environ["SOLSTONE_JOURNAL"] = original_path 972 972 973 973 def test_fuzzy_match_disambiguates_same_type(self): 974 974 """Multiple same-type previous activities matched by description.""" 975 975 from talent.activity_state import post_process 976 976 977 977 with tempfile.TemporaryDirectory() as tmpdir: 978 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 979 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 978 + original_path = os.environ.get("SOLSTONE_JOURNAL") 979 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 980 980 981 981 try: 982 982 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 1032 1032 1033 1033 finally: 1034 1034 if original_path: 1035 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1035 + os.environ["SOLSTONE_JOURNAL"] = original_path 1036 1036 1037 1037 1038 1038 class TestActivityId: ··· 1060 1060 from talent.activity_state import post_process 1061 1061 1062 1062 with tempfile.TemporaryDirectory() as tmpdir: 1063 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1064 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1063 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1064 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1065 1065 1066 1066 try: 1067 1067 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 1109 1109 1110 1110 finally: 1111 1111 if original_path: 1112 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1112 + os.environ["SOLSTONE_JOURNAL"] = original_path 1113 1113 1114 1114 def test_ended_activity_gets_id(self): 1115 1115 from talent.activity_state import post_process 1116 1116 1117 1117 with tempfile.TemporaryDirectory() as tmpdir: 1118 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1119 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1118 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1119 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1120 1120 1121 1121 try: 1122 1122 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 1163 1163 1164 1164 finally: 1165 1165 if original_path: 1166 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1166 + os.environ["SOLSTONE_JOURNAL"] = original_path 1167 1167 1168 1168 def test_promoted_ended_gets_new_id(self): 1169 1169 """Ended activity promoted to active gets id with current segment.""" ··· 1235 1235 from talent.activity_state import post_process 1236 1236 1237 1237 with tempfile.TemporaryDirectory() as tmpdir: 1238 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1239 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1238 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1239 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1240 1240 1241 1241 try: 1242 1242 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 1289 1289 1290 1290 finally: 1291 1291 if original_path: 1292 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1292 + os.environ["SOLSTONE_JOURNAL"] = original_path 1293 1293 1294 1294 def test_no_live_event_for_ended_activity(self): 1295 1295 from unittest.mock import patch ··· 1297 1297 from talent.activity_state import post_process 1298 1298 1299 1299 with tempfile.TemporaryDirectory() as tmpdir: 1300 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1301 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1300 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1301 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1302 1302 1303 1303 try: 1304 1304 day_dir = Path(tmpdir) / "chronicle" / "20260130" ··· 1345 1345 1346 1346 finally: 1347 1347 if original_path: 1348 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1348 + os.environ["SOLSTONE_JOURNAL"] = original_path 1349 1349 1350 1350 def test_no_live_events_without_day_or_facet(self): 1351 1351 from unittest.mock import patch ··· 1410 1410 from talent.activity_state import post_process 1411 1411 1412 1412 with tempfile.TemporaryDirectory() as tmpdir: 1413 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1414 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1413 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1414 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1415 1415 1416 1416 try: 1417 1417 # Create facet with only coding and meeting configured ··· 1452 1452 1453 1453 finally: 1454 1454 if original_path: 1455 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1455 + os.environ["SOLSTONE_JOURNAL"] = original_path 1456 1456 1457 1457 def test_logs_warning_on_dropped_activities(self, caplog): 1458 1458 """Post-hook logs a warning when dropping unrecognized activity IDs.""" ··· 1462 1462 from talent.activity_state import post_process 1463 1463 1464 1464 with tempfile.TemporaryDirectory() as tmpdir: 1465 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1466 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1465 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1466 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1467 1467 1468 1468 try: 1469 1469 facet_dir = Path(tmpdir) / "facets" / "work" / "activities" ··· 1495 1495 1496 1496 finally: 1497 1497 if original_path: 1498 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1498 + os.environ["SOLSTONE_JOURNAL"] = original_path 1499 1499 1500 1500 def test_valid_activity_ids_pass_through(self): 1501 1501 """Post-hook preserves entries with valid activity IDs.""" ··· 1504 1504 from talent.activity_state import post_process 1505 1505 1506 1506 with tempfile.TemporaryDirectory() as tmpdir: 1507 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1508 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1507 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1508 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1509 1509 1510 1510 try: 1511 1511 facet_dir = Path(tmpdir) / "facets" / "work" / "activities" ··· 1553 1553 1554 1554 finally: 1555 1555 if original_path: 1556 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1556 + os.environ["SOLSTONE_JOURNAL"] = original_path 1557 1557 1558 1558 def test_unconfigured_facet_allows_all_defaults(self): 1559 1559 """Post-hook allows all default activity IDs for unconfigured facets.""" ··· 1562 1562 from talent.activity_state import post_process 1563 1563 1564 1564 with tempfile.TemporaryDirectory() as tmpdir: 1565 - original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 1566 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 1565 + original_path = os.environ.get("SOLSTONE_JOURNAL") 1566 + os.environ["SOLSTONE_JOURNAL"] = tmpdir 1567 1567 1568 1568 try: 1569 1569 # Create facet dir but no activities.jsonl ··· 1601 1601 1602 1602 finally: 1603 1603 if original_path: 1604 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path 1604 + os.environ["SOLSTONE_JOURNAL"] = original_path
+5 -5
tests/test_anthropic.py
··· 230 230 agents_dir = journal / "talents" 231 231 agents_dir.mkdir() 232 232 233 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 233 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 234 234 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 235 235 236 236 ndjson_input = json.dumps( ··· 274 274 agents_dir = journal / "talents" 275 275 agents_dir.mkdir() 276 276 277 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 277 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 278 278 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 279 279 280 280 ndjson_input = json.dumps( ··· 322 322 agents_dir = journal / "talents" 323 323 agents_dir.mkdir() 324 324 325 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 325 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 326 326 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 327 327 328 328 ndjson_input = json.dumps( ··· 365 365 agents_dir = journal / "talents" 366 366 agents_dir.mkdir() 367 367 368 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 368 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 369 369 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 370 370 371 371 ndjson_input = json.dumps( ··· 406 406 agents_dir = journal / "talents" 407 407 agents_dir.mkdir() 408 408 409 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 409 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 410 410 monkeypatch.setenv("ANTHROPIC_API_KEY", "x") 411 411 412 412 ndjson_input = json.dumps(
+1 -1
tests/test_api_baselines.py
··· 80 80 breaking both determinism and the module-scoped `isolated_app_env` harness. 81 81 """ 82 82 journal = _baseline_journal.resolve() 83 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 83 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 84 84 85 85 86 86 @pytest.mark.parametrize(
+6 -6
tests/test_app_activities.py
··· 13 13 14 14 @pytest.fixture 15 15 def fixture_journal(): 16 - """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 17 - old = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 18 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 16 + """Set SOLSTONE_JOURNAL to tests/fixtures/journal for testing.""" 17 + old = os.environ.get("SOLSTONE_JOURNAL") 18 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 19 19 yield 20 20 if old is None: 21 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 21 + os.environ.pop("SOLSTONE_JOURNAL", None) 22 22 else: 23 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = old 23 + os.environ["SOLSTONE_JOURNAL"] = old 24 24 25 25 26 26 @pytest.fixture ··· 108 108 from convey import state 109 109 110 110 journal = tmp_path / "journal" 111 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 111 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 112 112 113 113 for facet in ("work", "personal"): 114 114 facet_dir = journal / "facets" / facet
+2 -2
tests/test_app_sol.py
··· 15 15 16 16 @pytest.fixture 17 17 def fixture_journal(): 18 - """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 19 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 18 + """Set SOLSTONE_JOURNAL to tests/fixtures/journal for testing.""" 19 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 20 20 yield 21 21 22 22
+1 -1
tests/test_apps_skills_call.py
··· 25 25 26 26 @pytest.fixture 27 27 def skill_cli_env(monkeypatch, tmp_path): 28 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 28 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 29 29 return Path(tmp_path) 30 30 31 31
+1 -1
tests/test_awareness.py
··· 13 13 @pytest.fixture(autouse=True) 14 14 def _temp_journal(monkeypatch, tmp_path): 15 15 """Isolate all tests to a temporary journal.""" 16 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 17 17 18 18 19 19 def _read_identity_history(journal_path):
+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("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 43 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 44 44 import think.utils 45 45 46 46 think.utils._journal_path_cache = None ··· 117 117 encoding="utf-8", 118 118 ) 119 119 120 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 120 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 121 121 import think.utils 122 122 123 123 think.utils._journal_path_cache = None ··· 234 234 """News --write saves content from stdin.""" 235 235 journal = tmp_path / "journal" 236 236 (journal / "facets" / "work").mkdir(parents=True) 237 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 237 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 238 238 # Clear cached journal path 239 239 import think.utils 240 240
+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["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 23 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 24 24 yield journal 25 25 # Cleanup 26 - if "_SOLSTONE_JOURNAL_OVERRIDE" in os.environ: 27 - del os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 26 + if "SOLSTONE_JOURNAL" in os.environ: 27 + del os.environ["SOLSTONE_JOURNAL"] 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 _SOLSTONE_JOURNAL_OVERRIDE env var for socket path.""" 220 + """Test that server uses SOLSTONE_JOURNAL 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 _SOLSTONE_JOURNAL_OVERRIDE env var for socket path.""" 236 + """Test that client uses SOLSTONE_JOURNAL 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 255 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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")
+8 -8
tests/test_chat_context.py
··· 58 58 monkeypatch, tmp_path 59 59 ): 60 60 journal = tmp_path / "journal" 61 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 61 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 62 62 (journal / "identity").mkdir(parents=True, exist_ok=True) 63 63 (journal / "identity" / "digest.md").write_text( 64 64 "Digest notes for today.", ··· 171 171 monkeypatch, tmp_path 172 172 ): 173 173 journal = tmp_path / "journal" 174 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 174 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 175 175 176 176 routines_config = {"_meta": {"suggestions_enabled": True, "suggestions": {}}} 177 177 save_calls: list[dict] = [] ··· 216 216 217 217 def test_chat_context_talent_finished_marks_report_back_only(monkeypatch, tmp_path): 218 218 journal = tmp_path / "journal" 219 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 219 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 220 220 221 221 append_chat_event( 222 222 "owner_message", ··· 285 285 286 286 def test_chat_context_talent_errored_marks_report_back_only(monkeypatch, tmp_path): 287 287 journal = tmp_path / "journal" 288 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 288 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 289 289 290 290 append_chat_event( 291 291 "owner_message", ··· 354 354 355 355 def test_chat_context_includes_identity_grounding(monkeypatch, tmp_path): 356 356 journal = tmp_path / "journal" 357 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 357 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 358 358 _write_journal_config(journal, {}) 359 359 ensure_identity_directory() 360 360 ··· 379 379 380 380 def test_chat_context_preserves_save_routines_config_side_effect(monkeypatch, tmp_path): 381 381 journal = tmp_path / "journal" 382 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 382 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 383 383 384 384 routines_config = {"_meta": {"suggestions_enabled": True, "suggestions": {}}} 385 385 save_calls: list[dict] = [] ··· 409 409 410 410 def test_chat_context_routines_omitted_when_empty(monkeypatch, tmp_path): 411 411 journal = tmp_path / "journal" 412 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 412 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 413 413 monkeypatch.setattr("think.routines.get_routine_state", lambda: []) 414 414 monkeypatch.setattr( 415 415 "think.routines.get_config", ··· 427 427 428 428 def test_chat_context_enrichment_errors_are_graceful(monkeypatch, tmp_path): 429 429 journal = tmp_path / "journal" 430 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 430 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 431 431 432 432 module = _load_chat_context_module() 433 433
+1 -1
tests/test_chat_runtime.py
··· 27 27 def _setup_journal(tmp_path, monkeypatch): 28 28 journal = tmp_path / "journal" 29 29 journal.mkdir() 30 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 30 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 31 31 return journal 32 32 33 33
+1 -1
tests/test_chat_stream.py
··· 20 20 def _setup_journal(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: 21 21 journal = tmp_path / "journal" 22 22 journal.mkdir() 23 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 23 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 24 24 return journal 25 25 26 26
+1 -1
tests/test_chat_stream_indexing.py
··· 6 6 from convey.chat_stream import append_chat_event 7 7 from think.indexer.journal import search_journal 8 8 9 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 9 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 10 10 11 11 total, results = search_journal("nebula phrase X42", stream="chat") 12 12 assert total == 0
+21 -21
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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 13 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 39 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 68 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 107 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 155 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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_load_entries_from_toplevel_segment(tmp_path, monkeypatch): 195 195 """_load_entries_from_segment resolves the day for top-level segment dirs.""" 196 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 196 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 197 197 day_dir = day_path("20240101") 198 198 segment = day_dir / "100000_300" 199 199 segment.mkdir() ··· 212 212 213 213 def test_cluster_range_with_agents(tmp_path, monkeypatch): 214 214 """Test cluster_range with agents source loads all *.md files.""" 215 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 215 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 216 216 day_dir = day_path("20240101") 217 217 218 218 mod = importlib.import_module("think.cluster") ··· 252 252 253 253 def test_cluster_range_with_screen(tmp_path, monkeypatch): 254 254 """Test cluster_range with screen source loads raw screen.jsonl data.""" 255 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 255 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 256 256 day_dir = day_path("20240101") 257 257 258 258 mod = importlib.import_module("think.cluster") ··· 284 284 285 285 def test_cluster_range_with_multiple_screen_files(tmp_path, monkeypatch): 286 286 """Test cluster_range loads multiple *_screen.jsonl files per segment.""" 287 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 287 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 288 288 day_dir = day_path("20240101") 289 289 290 290 mod = importlib.import_module("think.cluster") ··· 318 318 319 319 def test_cluster_scan_with_split_screen(tmp_path, monkeypatch): 320 320 """Test cluster_scan detects *_screen.jsonl files.""" 321 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 321 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 322 322 day_dir = day_path("20240101") 323 323 324 324 mod = importlib.import_module("think.cluster") ··· 337 337 338 338 def test_cluster_segments_with_split_screen(tmp_path, monkeypatch): 339 339 """Test cluster_segments detects *_screen.jsonl files.""" 340 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 340 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 341 341 day_dir = day_path("20240101") 342 342 343 343 mod = importlib.import_module("think.cluster") ··· 357 357 358 358 def test_cluster_span(tmp_path, monkeypatch): 359 359 """Test cluster_span processes a span of segments.""" 360 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 360 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 361 361 day_dir = day_path("20240101") 362 362 363 363 mod = importlib.import_module("think.cluster") ··· 401 401 402 402 def test_cluster_span_missing_segment(tmp_path, monkeypatch): 403 403 """Test cluster_span fails fast when segment is missing.""" 404 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 404 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 405 405 day_dir = day_path("20240101") 406 406 407 407 mod = importlib.import_module("think.cluster") ··· 426 426 427 427 def test_cluster_with_agent_filter_dict(tmp_path, monkeypatch): 428 428 """Test cluster() with dict-valued agents source for selective filtering.""" 429 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 429 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 430 430 day_dir = day_path("20240101") 431 431 432 432 mod = importlib.import_module("think.cluster") ··· 455 455 456 456 def test_cluster_with_agent_filter_multiple(tmp_path, monkeypatch): 457 457 """Test cluster() with dict selecting multiple agents.""" 458 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 458 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 459 459 day_dir = day_path("20240101") 460 460 461 461 mod = importlib.import_module("think.cluster") ··· 488 488 489 489 def test_cluster_with_agent_filter_app_namespaced(tmp_path, monkeypatch): 490 490 """Test cluster() with dict filtering app-namespaced agent outputs.""" 491 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 491 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 492 492 day_dir = day_path("20240101") 493 493 494 494 mod = importlib.import_module("think.cluster") ··· 520 520 521 521 def test_cluster_with_empty_agent_filter(tmp_path, monkeypatch): 522 522 """Test cluster() with empty dict means no agents.""" 523 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 523 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 524 524 day_dir = day_path("20240101") 525 525 526 526 mod = importlib.import_module("think.cluster") ··· 579 579 580 580 581 581 def test_scan_day_combined(tmp_path, monkeypatch): 582 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 582 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 583 583 day_dir = day_path("20240101") 584 584 585 585 mod = importlib.import_module("think.cluster") ··· 620 620 621 621 622 622 def test_scan_day_empty(tmp_path, monkeypatch): 623 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 623 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 624 624 625 625 mod = importlib.import_module("think.cluster") 626 626 ··· 628 628 629 629 630 630 def test_day_path_create_false(tmp_path, monkeypatch): 631 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 631 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 632 632 633 633 missing = day_path("29990101", create=False) 634 634 assert not missing.exists() ··· 640 640 def test_find_segment_dir_missing_streamed_segment_does_not_create_directory( 641 641 tmp_path, monkeypatch 642 642 ): 643 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 643 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 644 644 645 645 mod = importlib.import_module("think.cluster") 646 646 result = mod._find_segment_dir("29990101", "090000_300", "default")
+4 -4
tests/test_cluster_full.py
··· 12 12 13 13 14 14 def copy_day(tmp_path: Path) -> Path: 15 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 15 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 16 16 dest = day_path("20240101") 17 17 src = FIXTURES / "journal" / "chronicle" / "20240101" 18 18 copytree_tracked(src, dest) ··· 22 22 def test_cluster_full(tmp_path, monkeypatch): 23 23 mod = importlib.import_module("think.cluster") 24 24 copy_day(tmp_path) 25 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 25 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 26 26 md, counts = mod.cluster( 27 27 "20240101", sources={"transcripts": True, "percepts": False, "agents": True} 28 28 ) ··· 38 38 def test_cluster_default_sources(tmp_path, monkeypatch): 39 39 mod = importlib.import_module("think.cluster") 40 40 copy_day(tmp_path) 41 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 41 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 42 42 out, _counts = mod.cluster( 43 43 "20240101", sources={"transcripts": True, "percepts": False, "agents": True} 44 44 ) ··· 49 49 def test_cluster_range_raw_screen(tmp_path, monkeypatch): 50 50 mod = importlib.import_module("think.cluster") 51 51 copy_day(tmp_path) 52 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 52 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 53 53 out = mod.cluster_range( 54 54 "20240101", 55 55 "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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 47 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 73 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(config_journal)) 86 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 106 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 133 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 142 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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 _SOLSTONE_JOURNAL_OVERRIDE to fixtures 170 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 169 + # Set SOLSTONE_JOURNAL to fixtures 170 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 171 171 172 172 config = get_config() 173 173
+214
tests/test_config_cli.py
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 sol pbc 3 + 4 + from __future__ import annotations 5 + 6 + from pathlib import Path 7 + from unittest.mock import MagicMock 8 + 9 + import pytest 10 + 11 + from think import config_cli, install_guard 12 + 13 + 14 + @pytest.fixture 15 + def home_root(monkeypatch, tmp_path): 16 + home = tmp_path / "home" 17 + home.mkdir() 18 + monkeypatch.setattr(Path, "home", classmethod(lambda cls: home)) 19 + return home 20 + 21 + 22 + def ensure_expected_target(repo: Path) -> Path: 23 + target = install_guard.expected_target(repo) 24 + target.parent.mkdir(parents=True, exist_ok=True) 25 + target.write_text("", encoding="utf-8") 26 + return target 27 + 28 + 29 + def make_alias(home_root: Path, target: Path | str) -> Path: 30 + alias = home_root / ".local" / "bin" / "sol" 31 + alias.parent.mkdir(parents=True, exist_ok=True) 32 + alias.symlink_to(target) 33 + return alias 34 + 35 + 36 + def make_managed_wrapper(home_root: Path, *, journal: str, sol_bin: str) -> Path: 37 + alias = home_root / ".local" / "bin" / "sol" 38 + alias.parent.mkdir(parents=True, exist_ok=True) 39 + alias.write_text( 40 + install_guard.render_wrapper(journal, sol_bin), 41 + encoding="utf-8", 42 + ) 43 + alias.chmod(0o755) 44 + return alias 45 + 46 + 47 + def test_config_command_registered(): 48 + from think import sol_cli as sol 49 + 50 + assert sol.COMMANDS["config"] == "think.config_cli" 51 + assert "config" in sol.GROUPS["Specialized tools"] 52 + 53 + 54 + def test_show_reports_wrapper_embedded(home_root, monkeypatch, tmp_path, capsys): 55 + journal = str((tmp_path / "journal").resolve()) 56 + target = ensure_expected_target(tmp_path / "repo") 57 + make_managed_wrapper(home_root, journal=journal, sol_bin=str(target)) 58 + monkeypatch.setenv("SOLSTONE_JOURNAL", journal) 59 + 60 + rc = config_cli.cmd_show() 61 + captured = capsys.readouterr() 62 + 63 + assert rc == 0 64 + assert captured.err == "" 65 + assert captured.out.splitlines() == [ 66 + f"path: {journal}", 67 + "source: wrapper-embedded", 68 + "wrapper-status: managed", 69 + ] 70 + 71 + 72 + def test_show_reports_caller_override(home_root, monkeypatch, tmp_path, capsys): 73 + embedded = str((tmp_path / "embedded").resolve()) 74 + override = str((tmp_path / "override").resolve()) 75 + target = ensure_expected_target(tmp_path / "repo") 76 + make_managed_wrapper(home_root, journal=embedded, sol_bin=str(target)) 77 + monkeypatch.setenv("SOLSTONE_JOURNAL", override) 78 + 79 + rc = config_cli.cmd_show() 80 + captured = capsys.readouterr() 81 + 82 + assert rc == 0 83 + assert captured.err == "" 84 + assert captured.out.splitlines() == [ 85 + f"path: {override}", 86 + "source: caller-override", 87 + "wrapper-status: managed", 88 + ] 89 + 90 + 91 + def test_show_reports_source_tree_fallback(home_root, monkeypatch, capsys): 92 + monkeypatch.delenv("SOLSTONE_JOURNAL", raising=False) 93 + 94 + rc = config_cli.cmd_show() 95 + captured = capsys.readouterr() 96 + 97 + assert rc == 0 98 + assert captured.err == "" 99 + assert captured.out.splitlines() == [ 100 + f"path: {Path(config_cli.get_project_root()) / 'journal'}", 101 + "source: source-tree fallback", 102 + "wrapper-status: absent", 103 + ] 104 + 105 + 106 + def test_journal_noops_when_path_already_embedded( 107 + home_root, monkeypatch, tmp_path, capsys 108 + ): 109 + target_path = str((tmp_path / "journal").resolve()) 110 + target = ensure_expected_target(tmp_path / "repo") 111 + alias = make_managed_wrapper(home_root, journal=target_path, sol_bin=str(target)) 112 + original = alias.read_text(encoding="utf-8") 113 + 114 + rc = config_cli.cmd_journal(target_path) 115 + captured = capsys.readouterr() 116 + 117 + assert rc == 0 118 + assert captured.err == "" 119 + assert captured.out == f"sol config: journal already set to {target_path}\n" 120 + assert alias.read_text(encoding="utf-8") == original 121 + 122 + 123 + def test_journal_rewrites_wrapper(home_root, monkeypatch, tmp_path, capsys): 124 + source_path = str((tmp_path / "source").resolve()) 125 + target_path = str((tmp_path / "target").resolve()) 126 + target = ensure_expected_target(tmp_path / "repo") 127 + alias = make_managed_wrapper(home_root, journal=source_path, sol_bin=str(target)) 128 + run_mock = MagicMock(return_value=MagicMock(returncode=0)) 129 + monkeypatch.setattr(config_cli.subprocess, "run", run_mock) 130 + monkeypatch.chdir(home_root) 131 + 132 + rc = config_cli.cmd_journal(target_path) 133 + captured = capsys.readouterr() 134 + 135 + assert rc == 0 136 + assert captured.err == "" 137 + assert captured.out == f"sol config: journal set to {target_path}\n" 138 + assert install_guard.parse_wrapper(alias.read_text(encoding="utf-8")) == { 139 + "journal": target_path, 140 + "sol_bin": str(target), 141 + } 142 + run_mock.assert_called_once_with( 143 + [str(target), "service", "restart", "--if-installed"], 144 + check=False, 145 + ) 146 + 147 + 148 + def test_journal_refuses_without_managed_wrapper(home_root, tmp_path, capsys): 149 + target_path = str((tmp_path / "journal").resolve()) 150 + 151 + rc = config_cli.cmd_journal(target_path) 152 + captured = capsys.readouterr() 153 + 154 + assert rc == 1 155 + assert captured.out == "" 156 + assert "make install-service" in captured.err 157 + 158 + 159 + def test_journal_refuses_legacy_symlink(home_root, tmp_path, capsys): 160 + target_path = str((tmp_path / "journal").resolve()) 161 + make_alias(home_root, "/tmp/elsewhere/.venv/bin/sol") 162 + 163 + rc = config_cli.cmd_journal(target_path) 164 + captured = capsys.readouterr() 165 + 166 + assert rc == 1 167 + assert captured.out == "" 168 + assert "make install-service" in captured.err 169 + 170 + 171 + def test_journal_refuses_invalid_chars(home_root, capsys): 172 + rc = config_cli.cmd_journal("/tmp/bad$path") 173 + captured = capsys.readouterr() 174 + 175 + assert rc == 1 176 + assert captured.out == "" 177 + assert "shell-active character '$'" in captured.err 178 + 179 + 180 + def test_journal_refuses_source_tree_path_outside_source_checkout( 181 + home_root, monkeypatch, tmp_path, capsys 182 + ): 183 + monkeypatch.setattr(config_cli, "get_project_root", lambda: str(tmp_path)) 184 + 185 + rc = config_cli.cmd_journal(str((tmp_path / "journal").resolve())) 186 + captured = capsys.readouterr() 187 + 188 + assert rc == 1 189 + assert captured.out == "" 190 + assert "source-tree fallback path" in captured.err 191 + 192 + 193 + def test_journal_exits_2_on_restart_failure(home_root, monkeypatch, tmp_path, capsys): 194 + source_path = str((tmp_path / "source").resolve()) 195 + target_path = str((tmp_path / "target").resolve()) 196 + target = ensure_expected_target(tmp_path / "repo") 197 + alias = make_managed_wrapper(home_root, journal=source_path, sol_bin=str(target)) 198 + monkeypatch.setattr( 199 + config_cli.subprocess, 200 + "run", 201 + MagicMock(return_value=MagicMock(returncode=1)), 202 + ) 203 + monkeypatch.chdir(home_root) 204 + 205 + rc = config_cli.cmd_journal(target_path) 206 + captured = capsys.readouterr() 207 + 208 + assert rc == 2 209 + assert captured.out == "" 210 + assert "wrapper rewritten to" in captured.err 211 + assert install_guard.parse_wrapper(alias.read_text(encoding="utf-8")) == { 212 + "journal": target_path, 213 + "sol_bin": str(target), 214 + }
+1 -1
tests/test_config_ingest.py
··· 26 26 @pytest.fixture 27 27 def journal_env(tmp_path, monkeypatch): 28 28 monkeypatch.setattr(convey.state, "journal_root", str(tmp_path), raising=False) 29 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 29 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 30 30 think.utils._journal_path_cache = None 31 31 (tmp_path / "apps" / "import" / "journal_sources").mkdir( 32 32 parents=True, exist_ok=True
+3 -3
tests/test_content_manifest.py
··· 12 12 13 13 14 14 def test_write_content_manifest(tmp_path, monkeypatch): 15 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 15 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 91 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 141 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 142 142 ics_path = tmp_path / "calendar.ics" 143 143 ics_path.write_bytes( 144 144 b"""BEGIN:VCALENDAR
+6 -6
tests/test_convey_apps.py
··· 9 9 @pytest.fixture(autouse=True) 10 10 def _temp_journal(monkeypatch, tmp_path): 11 11 """Ensure journaling defaults remain isolated from developer data.""" 12 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 12 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 13 13 14 14 15 15 # --- Placeholder resolution --- ··· 114 114 115 115 from convey.apps import _resolve_attention 116 116 117 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 117 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 118 118 119 119 today = datetime.now().strftime("%Y%m%d") 120 120 agents_dir = tmp_path / "talents" ··· 156 156 157 157 from convey.apps import _resolve_attention 158 158 159 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 159 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 160 160 161 161 today = datetime.now().strftime("%Y%m%d") 162 162 agents_dir = tmp_path / "talents" ··· 195 195 196 196 from convey.apps import _resolve_attention 197 197 198 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 198 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 199 199 200 200 today = datetime.now().strftime("%Y%m%d") 201 201 agents_dir = tmp_path / "talents" ··· 240 240 241 241 from convey.apps import _resolve_attention 242 242 243 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 243 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 244 244 245 245 today = datetime.now().strftime("%Y%m%d") 246 246 agents_dir = tmp_path / "talents" ··· 273 273 274 274 from convey.apps import _resolve_attention 275 275 276 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 276 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 277 277 278 278 today = datetime.now().strftime("%Y%m%d") 279 279 agents_dir = tmp_path / today / "talents"
+1 -1
tests/test_convey_bind.py
··· 52 52 f"run_service(app, host=_resolve_bind_host(), port={port}, start_watcher=False)" 53 53 ) 54 54 env = os.environ.copy() 55 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal_copy) 55 + env["SOLSTONE_JOURNAL"] = str(journal_copy) 56 56 env["SOL_SKIP_SUPERVISOR_CHECK"] = "1" 57 57 return subprocess.Popen( 58 58 [sys.executable, "-c", code],
+1 -1
tests/test_convey_chat.py
··· 16 16 def _setup_journal(tmp_path, monkeypatch): 17 17 journal = tmp_path / "journal" 18 18 journal.mkdir() 19 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 19 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 20 20 return journal 21 21 22 22
+1 -1
tests/test_convey_utils.py
··· 51 51 52 52 53 53 def test_list_day_folders(tmp_path, monkeypatch): 54 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 54 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 55 55 from think.utils import day_dirs 56 56 57 57 day_path("20240101")
+1 -1
tests/test_cortex.py
··· 45 45 agents_path = journal_path / "talents" 46 46 agents_path.mkdir() 47 47 48 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 48 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 49 49 return journal_path 50 50 51 51
+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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 38 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 39 39 (tmp_path / "talents").mkdir(parents=True, exist_ok=True) 40 40 41 41 server = CallosumServer() ··· 160 160 def test_cortex_request_empty_journal(tmp_path, monkeypatch): 161 161 """Test cortex_request works with an empty journal directory.""" 162 162 monkeypatch.setattr("think.cortex_client.callosum_send", lambda *a, **kw: True) 163 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 163 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 164 164 165 165 use_id = cortex_request("test", "chat", "openai") 166 166 assert use_id is not None ··· 172 172 173 173 def test_cortex_agents_empty(tmp_path, monkeypatch): 174 174 """Test cortex_uses with no agents.""" 175 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 175 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 176 176 177 177 result = cortex_uses() 178 178 ··· 185 185 186 186 def test_cortex_agents_with_active(tmp_path, monkeypatch): 187 187 """Test cortex_uses with active (running) agents.""" 188 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 188 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 189 189 talents_dir = tmp_path / "talents" 190 190 talents_dir.mkdir() 191 191 ··· 235 235 236 236 def test_cortex_agents_with_completed(tmp_path, monkeypatch): 237 237 """Test cortex_uses with completed (historical) agents.""" 238 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 238 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 239 239 talents_dir = tmp_path / "talents" 240 240 talents_dir.mkdir() 241 241 ··· 270 270 271 271 def test_cortex_agents_pagination(tmp_path, monkeypatch): 272 272 """Test cortex_uses pagination.""" 273 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 273 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 274 274 talents_dir = tmp_path / "talents" 275 275 talents_dir.mkdir() 276 276 ··· 303 303 304 304 def test_cortex_agents_empty_journal(tmp_path, monkeypatch): 305 305 """Test cortex_uses works with an empty journal directory.""" 306 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 306 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 307 307 308 308 result = cortex_uses() 309 309 assert "uses" in result ··· 313 313 314 314 def test_get_agent_log_status_completed(tmp_path, monkeypatch): 315 315 """Test get_use_log_status returns 'completed' for finished agents.""" 316 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 316 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 317 317 talents_dir = tmp_path / "talents" 318 318 talents_dir.mkdir() 319 319 unified_dir = talents_dir / "chat" ··· 327 327 328 328 def test_get_agent_log_status_running(tmp_path, monkeypatch): 329 329 """Test get_use_log_status returns 'running' for active agents.""" 330 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 330 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 331 331 talents_dir = tmp_path / "talents" 332 332 talents_dir.mkdir() 333 333 unified_dir = talents_dir / "chat" ··· 341 341 342 342 def test_get_agent_log_status_not_found(tmp_path, monkeypatch): 343 343 """Test get_use_log_status returns 'not_found' for missing agents.""" 344 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 344 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 345 345 (tmp_path / "talents").mkdir() 346 346 347 347 assert get_use_log_status("nonexistent") == "not_found" ··· 349 349 350 350 def test_get_agent_log_status_prefers_completed(tmp_path, monkeypatch): 351 351 """Test get_use_log_status returns 'completed' when both files exist.""" 352 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 352 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 353 353 talents_dir = tmp_path / "talents" 354 354 talents_dir.mkdir() 355 355 unified_dir = talents_dir / "chat" ··· 365 365 366 366 def test_get_agent_end_state_finish(tmp_path, monkeypatch): 367 367 """Test get_use_end_state returns 'finish' for successful agents.""" 368 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 368 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 369 369 talents_dir = tmp_path / "talents" 370 370 talents_dir.mkdir() 371 371 unified_dir = talents_dir / "chat" ··· 382 382 383 383 def test_get_agent_end_state_error(tmp_path, monkeypatch): 384 384 """Test get_use_end_state returns 'error' for failed agents.""" 385 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 385 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 386 386 talents_dir = tmp_path / "talents" 387 387 talents_dir.mkdir() 388 388 unified_dir = talents_dir / "chat" ··· 399 399 400 400 def test_get_agent_end_state_running(tmp_path, monkeypatch): 401 401 """Test get_use_end_state returns 'running' for active agents.""" 402 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 402 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 403 403 talents_dir = tmp_path / "talents" 404 404 talents_dir.mkdir() 405 405 unified_dir = talents_dir / "chat" ··· 415 415 416 416 def test_get_agent_end_state_unknown(tmp_path, monkeypatch): 417 417 """Test get_use_end_state returns 'unknown' for missing agents.""" 418 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 418 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 419 419 (tmp_path / "talents").mkdir() 420 420 421 421 assert get_use_end_state("nonexistent") == "unknown" ··· 426 426 427 427 def test_wait_for_agents_already_complete(tmp_path, monkeypatch): 428 428 """Test wait_for_uses returns immediately if agents already completed.""" 429 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 429 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 430 430 talents_dir = tmp_path / "talents" 431 431 talents_dir.mkdir() 432 432 unified_dir = talents_dir / "chat" ··· 519 519 520 520 def test_wait_for_agents_initial_file_check(tmp_path, monkeypatch): 521 521 """Test wait_for_uses finds already-completed agents via initial file check.""" 522 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 522 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 523 523 talents_dir = tmp_path / "talents" 524 524 talents_dir.mkdir() 525 525 unified_dir = talents_dir / "chat" ··· 540 540 541 541 def test_wait_for_agents_timeout_actual(tmp_path, monkeypatch): 542 542 """Test wait_for_uses times out for agents that never complete.""" 543 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 543 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 544 544 talents_dir = tmp_path / "talents" 545 545 talents_dir.mkdir() 546 546 unified_dir = talents_dir / "chat" ··· 601 601 """Test that missed events are recovered via final file check with INFO log.""" 602 602 import logging 603 603 604 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 604 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 605 605 talents_dir = tmp_path / "talents" 606 606 talents_dir.mkdir() 607 607 unified_dir = talents_dir / "chat"
+19
tests/test_doctor.py
··· 344 344 assert result.severity == "advisory" 345 345 assert "timed out" in result.detail 346 346 347 + def test_resolve_alias_target_reads_managed_wrapper_sol_bin( 348 + self, doctor, home_root, tmp_path 349 + ): 350 + sol_bin = tmp_path / "repo" / ".venv" / "bin" / "sol" 351 + sol_bin.parent.mkdir(parents=True, exist_ok=True) 352 + sol_bin.write_text("", encoding="utf-8") 353 + alias = home_root / ".local" / "bin" / "sol" 354 + alias.parent.mkdir(parents=True, exist_ok=True) 355 + alias.write_text( 356 + install_guard.render_wrapper( 357 + str((tmp_path / "journal").resolve()), 358 + str(sol_bin), 359 + ), 360 + encoding="utf-8", 361 + ) 362 + alias.chmod(0o755) 363 + 364 + assert doctor.resolve_alias_target() == sol_bin.resolve() 365 + 347 366 348 367 class TestDiskSpace: 349 368 def test_warn_when_low(self, doctor, monkeypatch):
+89 -89
tests/test_entities.py
··· 42 42 43 43 @pytest.fixture 44 44 def fixture_journal(): 45 - """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 46 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 45 + """Set SOLSTONE_JOURNAL to tests/fixtures/journal for testing.""" 46 + os.environ["SOLSTONE_JOURNAL"] = "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 _SOLSTONE_JOURNAL_OVERRIDE to temp directory 208 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 207 + # Update SOLSTONE_JOURNAL to temp directory 208 + os.environ["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 252 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 253 253 254 254 # Save unsorted entities 255 255 unsorted = [ ··· 295 295 populated the cache and a subsequent save did not invalidate it — the second 296 296 load returned stale data from the cache rather than re-reading disk. 297 297 """ 298 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 298 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 299 299 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 300 300 301 301 save_entities( ··· 322 322 323 323 def test_save_entities_attached_invalidates_loading_cache(fixture_journal, tmp_path): 324 324 """Regression: save_entities (attached path, day=None) must invalidate the loading cache.""" 325 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 325 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 326 326 (tmp_path / "facets" / "test_facet").mkdir(parents=True) 327 327 328 328 save_entities( ··· 344 344 345 345 def test_save_detected_entity_basic(fixture_journal, tmp_path): 346 346 """Test save_detected_entity adds an entity with locking.""" 347 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 347 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 348 348 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 349 349 350 350 result = save_detected_entity("test_facet", "20250101", "Person", "Alice", "Friend") ··· 358 358 359 359 def test_save_detected_entity_duplicate(fixture_journal, tmp_path): 360 360 """Test save_detected_entity raises on duplicate.""" 361 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 361 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 362 362 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 363 363 364 364 save_detected_entity("test_facet", "20250101", "Person", "Alice", "Friend") ··· 373 373 """Test concurrent save_detected_entity calls don't lose data.""" 374 374 import threading 375 375 376 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 376 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 377 377 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 378 378 379 379 errors = [] ··· 406 406 """Test that save_detected_entity retries on transient OSError.""" 407 407 from unittest.mock import patch 408 408 409 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 409 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 410 410 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 411 411 412 412 call_count = 0 ··· 432 432 433 433 def test_update_detected_entity(fixture_journal, tmp_path): 434 434 """Test update_detected_entity with locking.""" 435 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 435 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 436 436 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 437 437 438 438 save_detected_entity("test_facet", "20250101", "Person", "Alice", "Friend") ··· 445 445 446 446 def test_update_detected_entity_not_found(fixture_journal, tmp_path): 447 447 """Test update_detected_entity raises when entity missing.""" 448 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 448 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 449 449 (tmp_path / "facets" / "test_facet" / "entities").mkdir(parents=True) 450 450 451 451 import pytest as _pytest ··· 476 476 facet1_path.mkdir(parents=True) 477 477 facet2_path.mkdir(parents=True) 478 478 479 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 479 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 480 480 481 481 # Save same entity name in both facets with different descriptions 482 482 entities1 = [ ··· 511 511 """Test sorting entities by last_seen.""" 512 512 facet_path = tmp_path / "facets" / "test_facet" 513 513 facet_path.mkdir(parents=True) 514 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 514 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 515 515 516 516 # Create entities with varying last_seen values 517 517 entities = [ ··· 544 544 """Test limiting number of entities returned.""" 545 545 facet_path = tmp_path / "facets" / "test_facet" 546 546 facet_path.mkdir(parents=True) 547 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 547 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 548 548 549 549 # Create 5 entities 550 550 entities = [ ··· 562 562 """Test sorting and limiting together.""" 563 563 facet_path = tmp_path / "facets" / "test_facet" 564 564 facet_path.mkdir(parents=True) 565 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 565 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 566 566 567 567 # Create entities with last_seen 568 568 entities = [ ··· 587 587 """Test basic functionality of load_recent_entity_names.""" 588 588 facet_path = tmp_path / "facets" / "test_facet" 589 589 facet_path.mkdir(parents=True) 590 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 590 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 591 591 592 592 # Create entities with last_seen 593 593 entities = [ ··· 609 609 """Test that result is a list of names.""" 610 610 facet_path = tmp_path / "facets" / "test_facet" 611 611 facet_path.mkdir(parents=True) 612 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 612 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 613 613 614 614 # Create 10 entities with speakable names (no digits) 615 615 names = [ ··· 641 641 """Test with no entities returns None.""" 642 642 facet_path = tmp_path / "facets" / "test_facet" 643 643 facet_path.mkdir(parents=True) 644 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 644 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 645 645 646 646 result = load_recent_entity_names() 647 647 assert result is None ··· 651 651 """Test that aka values are included in spoken names.""" 652 652 facet_path = tmp_path / "facets" / "test_facet" 653 653 facet_path.mkdir(parents=True) 654 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 654 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 655 655 656 656 entities = [ 657 657 { ··· 676 676 """Test that limit parameter is respected.""" 677 677 facet_path = tmp_path / "facets" / "test_facet" 678 678 facet_path.mkdir(parents=True) 679 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 679 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 680 680 681 681 # Create 30 entities with speakable names (no digits) 682 682 # Use unique first names that won't collide ··· 734 734 """Test that names with underscores or no letters are filtered out.""" 735 735 facet_path = tmp_path / "facets" / "test_facet" 736 736 facet_path.mkdir(parents=True) 737 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 737 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 738 738 739 739 entities = [ 740 740 # Speakable - should be included (letters required, digits OK) ··· 788 788 """Test that aka field is preserved during save/load operations.""" 789 789 facet_path = tmp_path / "facets" / "test_facet" 790 790 facet_path.mkdir(parents=True) 791 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 791 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 792 792 793 793 # Save entities with aka fields 794 794 test_entities = [ ··· 845 845 facet_path = tmp_path / "facets" / "test_facet" 846 846 entities_dir = facet_path / "entities" 847 847 entities_dir.mkdir(parents=True) 848 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 848 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 849 849 850 850 # Create attached entity with aka 851 851 attached = [ ··· 885 885 facet_path = tmp_path / "facets" / "test_facet" 886 886 entities_dir = facet_path / "entities" 887 887 entities_dir.mkdir(parents=True) 888 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 888 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 889 889 890 890 # Create same entity across multiple days 891 891 save_entities( ··· 917 917 facet_path = tmp_path / "facets" / "test_facet" 918 918 entities_dir = facet_path / "entities" 919 919 entities_dir.mkdir(parents=True) 920 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 920 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 921 921 922 922 # Create entity across multiple days with different descriptions 923 923 save_entities( ··· 955 955 facet_path = tmp_path / "facets" / "test_facet" 956 956 entities_dir = facet_path / "entities" 957 957 entities_dir.mkdir(parents=True) 958 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 958 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 959 959 960 960 from datetime import datetime, timedelta 961 961 ··· 989 989 """Test that empty or non-existent facet returns empty list.""" 990 990 facet_path = tmp_path / "facets" / "empty_facet" 991 991 facet_path.mkdir(parents=True) 992 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 992 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 993 993 994 994 # No entities directory 995 995 detected = load_detected_entities_recent("empty_facet") ··· 1001 1001 facet_path = tmp_path / "facets" / "test_facet" 1002 1002 entities_dir = facet_path / "entities" 1003 1003 entities_dir.mkdir(parents=True) 1004 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1004 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1005 1005 1006 1006 # Same name, different types - should be treated as separate entities 1007 1007 save_entities( ··· 1025 1025 """Test that attached_at and updated_at timestamps are preserved through save/load.""" 1026 1026 facet_path = tmp_path / "facets" / "test_facet" 1027 1027 facet_path.mkdir(parents=True) 1028 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1028 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1029 1029 1030 1030 # Save entities with timestamps 1031 1031 test_entities = [ ··· 1066 1066 """Test that load_entities excludes detached entities by default.""" 1067 1067 facet_path = tmp_path / "facets" / "test_facet" 1068 1068 facet_path.mkdir(parents=True) 1069 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1069 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1070 1070 1071 1071 # Save entities with one detached 1072 1072 test_entities = [ ··· 1094 1094 """Test that load_entities includes detached entities when include_detached=True.""" 1095 1095 facet_path = tmp_path / "facets" / "test_facet" 1096 1096 facet_path.mkdir(parents=True) 1097 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1097 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1098 1098 1099 1099 # Save entities with one detached 1100 1100 test_entities = [ ··· 1126 1126 facet2_path = tmp_path / "facets" / "facet2" 1127 1127 facet1_path.mkdir(parents=True) 1128 1128 facet2_path.mkdir(parents=True) 1129 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1129 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1130 1130 1131 1131 # Save entities - one active, one detached per facet 1132 1132 save_entities( ··· 1164 1164 facet_path = tmp_path / "facets" / "test_facet" 1165 1165 entities_dir = facet_path / "entities" 1166 1166 entities_dir.mkdir(parents=True) 1167 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1167 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1168 1168 1169 1169 # Create attached entity with detached=True 1170 1170 attached = [ ··· 1211 1211 """Test that detached entities preserve all fields including custom ones.""" 1212 1212 facet_path = tmp_path / "facets" / "test_facet" 1213 1213 facet_path.mkdir(parents=True) 1214 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1214 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1215 1215 1216 1216 # Save entity with custom fields and detached flag 1217 1217 test_entities = [ ··· 1249 1249 facet_path = tmp_path / "facets" / "test_facet" 1250 1250 entities_dir = facet_path / "entities" 1251 1251 entities_dir.mkdir(parents=True) 1252 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1252 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1253 1253 1254 1254 # Create detected entity for a specific day 1255 1255 detected_entities = [ ··· 1316 1316 1317 1317 def test_entity_memory_path(fixture_journal, tmp_path): 1318 1318 """Test entity memory path generation.""" 1319 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1319 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1320 1320 1321 1321 path = entity_memory_path("personal", "Alice Johnson") 1322 1322 expected = tmp_path / "facets" / "personal" / "entities" / "alice_johnson" ··· 1325 1325 1326 1326 def test_entity_memory_path_empty_name(fixture_journal, tmp_path): 1327 1327 """Test entity memory path with empty name raises ValueError.""" 1328 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1328 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1329 1329 1330 1330 with pytest.raises(ValueError, match="slugifies to empty string"): 1331 1331 entity_memory_path("personal", "") ··· 1333 1333 1334 1334 def test_ensure_entity_memory(fixture_journal, tmp_path): 1335 1335 """Test entity memory folder creation.""" 1336 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1336 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1337 1337 1338 1338 folder = ensure_entity_memory("personal", "Bob Smith") 1339 1339 assert folder.exists() ··· 1343 1343 1344 1344 def test_ensure_entity_memory_idempotent(fixture_journal, tmp_path): 1345 1345 """Test that ensure_entity_memory is idempotent.""" 1346 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1346 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1347 1347 1348 1348 folder1 = ensure_entity_memory("personal", "Charlie Brown") 1349 1349 folder2 = ensure_entity_memory("personal", "Charlie Brown") ··· 1353 1353 1354 1354 def test_rename_entity_memory(fixture_journal, tmp_path): 1355 1355 """Test renaming entity memory folder.""" 1356 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1356 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1357 1357 1358 1358 # Create original folder 1359 1359 old_folder = ensure_entity_memory("work", "Alice Johnson") ··· 1377 1377 1378 1378 def test_rename_entity_memory_not_exists(fixture_journal, tmp_path): 1379 1379 """Test renaming non-existent folder returns False.""" 1380 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1380 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1381 1381 1382 1382 result = rename_entity_memory("work", "NonExistent", "NewName") 1383 1383 assert result is False ··· 1385 1385 1386 1386 def test_rename_entity_memory_same_normalized(fixture_journal, tmp_path): 1387 1387 """Test renaming when normalized names are the same.""" 1388 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1388 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1389 1389 1390 1390 # Create folder 1391 1391 ensure_entity_memory("work", "Alice Johnson") ··· 1397 1397 1398 1398 def test_rename_entity_memory_target_exists(fixture_journal, tmp_path): 1399 1399 """Test renaming when target folder already exists raises OSError.""" 1400 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1400 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1401 1401 1402 1402 # Create both folders 1403 1403 ensure_entity_memory("work", "Alice") ··· 1620 1620 """Test touch_entity updates last_seen on attached entity.""" 1621 1621 facet_path = tmp_path / "facets" / "test_facet" 1622 1622 facet_path.mkdir(parents=True) 1623 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1623 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1624 1624 1625 1625 # Create attached entity without last_seen 1626 1626 entities = [ ··· 1642 1642 """Test touch_entity only updates if day is more recent.""" 1643 1643 facet_path = tmp_path / "facets" / "test_facet" 1644 1644 facet_path.mkdir(parents=True) 1645 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1645 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1646 1646 1647 1647 # Create attached entity with existing last_seen 1648 1648 entities = [ ··· 1678 1678 """Test touch_entity returns False when entity not found.""" 1679 1679 facet_path = tmp_path / "facets" / "test_facet" 1680 1680 facet_path.mkdir(parents=True) 1681 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1681 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1682 1682 1683 1683 # Create attached entity 1684 1684 entities = [ ··· 1695 1695 """Test touch_entity skips detached entities.""" 1696 1696 facet_path = tmp_path / "facets" / "test_facet" 1697 1697 facet_path.mkdir(parents=True) 1698 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1698 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1699 1699 1700 1700 # Create detached entity 1701 1701 entities = [ ··· 1721 1721 facet_path = tmp_path / "facets" / "test_facet" 1722 1722 entities_dir = facet_path / "entities" 1723 1723 entities_dir.mkdir(parents=True) 1724 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1724 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1725 1725 1726 1726 # Create attached entity 1727 1727 attached = [ ··· 1762 1762 facet_path = tmp_path / "facets" / "test_facet" 1763 1763 entities_dir = facet_path / "entities" 1764 1764 entities_dir.mkdir(parents=True) 1765 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1765 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1766 1766 1767 1767 # Create attached entity 1768 1768 attached = [ ··· 1794 1794 1795 1795 def test_parse_knowledge_graph_entities(tmp_path): 1796 1796 """Test parsing entity names from knowledge graph markdown.""" 1797 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1797 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1798 1798 1799 1799 # Create a knowledge graph file 1800 1800 day_dir = tmp_path / "chronicle" / "20260108" / "talents" ··· 1835 1835 1836 1836 def test_parse_knowledge_graph_entities_missing_file(tmp_path): 1837 1837 """Test parsing returns empty list when KG doesn't exist.""" 1838 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1838 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1839 1839 1840 1840 entities = parse_knowledge_graph_entities("20260108") 1841 1841 assert entities == [] ··· 1843 1843 1844 1844 def test_parse_knowledge_graph_entities_empty_file(tmp_path): 1845 1845 """Test parsing returns empty list for empty KG.""" 1846 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1846 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1847 1847 1848 1848 day_dir = tmp_path / "chronicle" / "20260108" / "talents" 1849 1849 day_dir.mkdir(parents=True) ··· 1860 1860 """Test updating last_seen from activity names.""" 1861 1861 facet_path = tmp_path / "facets" / "test_facet" 1862 1862 facet_path.mkdir(parents=True) 1863 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1863 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1864 1864 1865 1865 # Create attached entities 1866 1866 attached = [ ··· 1900 1900 """Test with empty names list.""" 1901 1901 facet_path = tmp_path / "facets" / "test_facet" 1902 1902 facet_path.mkdir(parents=True) 1903 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1903 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1904 1904 1905 1905 attached = [{"type": "Person", "name": "Alice", "description": "Test"}] 1906 1906 save_entities("test_facet", attached) ··· 1916 1916 """Test with no attached entities.""" 1917 1917 facet_path = tmp_path / "facets" / "test_facet" 1918 1918 facet_path.mkdir(parents=True) 1919 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1919 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1920 1920 1921 1921 result = touch_entities_from_activity("test_facet", ["Alice"], "20260108") 1922 1922 ··· 1929 1929 """Test that same entity matched multiple times is only updated once.""" 1930 1930 facet_path = tmp_path / "facets" / "test_facet" 1931 1931 facet_path.mkdir(parents=True) 1932 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1932 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1933 1933 1934 1934 attached = [ 1935 1935 { ··· 1957 1957 1958 1958 def test_observations_file_path(fixture_journal, tmp_path): 1959 1959 """Test observations file path generation.""" 1960 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1960 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1961 1961 1962 1962 path = observations_file_path("personal", "Alice Johnson") 1963 1963 expected = ( ··· 1973 1973 1974 1974 def test_load_observations_empty(fixture_journal, tmp_path): 1975 1975 """Test loading observations for entity with no observations.""" 1976 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1976 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1977 1977 1978 1978 # No file exists yet 1979 1979 observations = load_observations("personal", "Alice Johnson") ··· 1982 1982 1983 1983 def test_save_and_load_observations(fixture_journal, tmp_path): 1984 1984 """Test saving and loading observations.""" 1985 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 1985 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 1986 1986 1987 1987 # Save observations 1988 1988 test_observations = [ ··· 2006 2006 2007 2007 def test_add_observation_success(fixture_journal, tmp_path): 2008 2008 """Test adding observations sequentially.""" 2009 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2009 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2010 2010 2011 2011 result = add_observation( 2012 2012 "personal", "Alice", "Prefers async communication", "20250113" ··· 2028 2028 2029 2029 def test_add_observation_empty_content(fixture_journal, tmp_path): 2030 2030 """Test adding observation with empty content fails.""" 2031 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2031 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2032 2032 2033 2033 with pytest.raises(ValueError, match="cannot be empty"): 2034 2034 add_observation("personal", "Alice", "") ··· 2039 2039 2040 2040 def test_observations_with_entity_rename(fixture_journal, tmp_path): 2041 2041 """Test that observations are preserved when entity memory folder is renamed.""" 2042 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2042 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2043 2043 2044 2044 # Create entity memory folder and add observations 2045 2045 ensure_entity_memory("work", "Alice Johnson") ··· 2065 2065 2066 2066 def test_observations_atomic_write(fixture_journal, tmp_path): 2067 2067 """Test that observations are written atomically.""" 2068 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2068 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2069 2069 2070 2070 # Save observations 2071 2071 test_observations = [ ··· 2096 2096 """Test extracting identity names from journal config.""" 2097 2097 import json 2098 2098 2099 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2099 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2100 2100 2101 2101 # Create config with identity 2102 2102 config_dir = tmp_path / "config" ··· 2117 2117 2118 2118 def test_get_identity_names_no_config(tmp_path): 2119 2119 """Test that missing config returns empty list.""" 2120 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2120 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2121 2121 # No config file 2122 2122 2123 2123 names = get_identity_names() ··· 2128 2128 """Test that empty identity config returns empty list.""" 2129 2129 import json 2130 2130 2131 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2131 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2132 2132 2133 2133 config_dir = tmp_path / "config" 2134 2134 config_dir.mkdir() ··· 2143 2143 """Test that save_entities flags an entity as principal when it matches identity name.""" 2144 2144 import json 2145 2145 2146 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2146 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2147 2147 2148 2148 # Create config with identity 2149 2149 config_dir = tmp_path / "config" ··· 2177 2177 """Test that save_entities flags principal when matching preferred name.""" 2178 2178 import json 2179 2179 2180 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2180 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2181 2181 2182 2182 # Create config with identity - preferred name differs from entity name 2183 2183 config_dir = tmp_path / "config" ··· 2204 2204 """Test that save_entities flags principal when matching an alias.""" 2205 2205 import json 2206 2206 2207 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2207 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2208 2208 2209 2209 # Create config with alias 2210 2210 config_dir = tmp_path / "config" ··· 2230 2230 """Test that save_entities flags principal when entity aka matches identity.""" 2231 2231 import json 2232 2232 2233 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2233 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2234 2234 2235 2235 # Create config 2236 2236 config_dir = tmp_path / "config" ··· 2261 2261 """Test that save_entities doesn't change principal if one already exists.""" 2262 2262 import json 2263 2263 2264 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2264 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2265 2265 2266 2266 # Create config 2267 2267 config_dir = tmp_path / "config" ··· 2297 2297 2298 2298 def test_save_entities_no_principal_without_identity(tmp_path): 2299 2299 """Test that save_entities doesn't flag principal when no identity configured.""" 2300 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2300 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2301 2301 # No config file 2302 2302 2303 2303 # Create facet directory ··· 2317 2317 """Test that detached entities are not flagged as principal.""" 2318 2318 import json 2319 2319 2320 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2320 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2321 2321 2322 2322 # Create config 2323 2323 config_dir = tmp_path / "config" ··· 2355 2355 """Test that principal matching is case-insensitive.""" 2356 2356 import json 2357 2357 2358 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2358 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2359 2359 2360 2360 # Create config with lowercase name 2361 2361 config_dir = tmp_path / "config" ··· 2381 2381 """Test that save_entities with day (detected) doesn't flag principal.""" 2382 2382 import json 2383 2383 2384 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2384 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2385 2385 2386 2386 # Create config 2387 2387 config_dir = tmp_path / "config" ··· 2413 2413 """Test blocking a journal entity sets blocked flag and detaches facets.""" 2414 2414 import json 2415 2415 2416 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2416 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2417 2417 2418 2418 # Create journal entity 2419 2419 entity_dir = tmp_path / "entities" / "alice" ··· 2446 2446 2447 2447 def test_block_journal_entity_not_found(tmp_path): 2448 2448 """Test blocking non-existent entity raises error.""" 2449 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2449 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2450 2450 2451 2451 with pytest.raises(ValueError, match="not found"): 2452 2452 block_journal_entity("nonexistent") ··· 2456 2456 """Test blocking principal entity is rejected.""" 2457 2457 import json 2458 2458 2459 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2459 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2460 2460 2461 2461 # Create principal entity 2462 2462 entity_dir = tmp_path / "entities" / "myself" ··· 2477 2477 """Test unblocking a blocked journal entity.""" 2478 2478 import json 2479 2479 2480 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2480 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2481 2481 2482 2482 # Create blocked entity 2483 2483 entity_dir = tmp_path / "entities" / "alice" ··· 2499 2499 """Test unblocking an entity that isn't blocked raises error.""" 2500 2500 import json 2501 2501 2502 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2502 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2503 2503 2504 2504 entity_dir = tmp_path / "entities" / "alice" 2505 2505 entity_dir.mkdir(parents=True) ··· 2519 2519 """Test deleting a journal entity removes all data.""" 2520 2520 import json 2521 2521 2522 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2522 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2523 2523 2524 2524 # Create journal entity with observations 2525 2525 entity_dir = tmp_path / "entities" / "alice" ··· 2549 2549 """Test deleting principal entity is rejected.""" 2550 2550 import json 2551 2551 2552 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2552 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2553 2553 2554 2554 # Create principal entity 2555 2555 entity_dir = tmp_path / "entities" / "myself" ··· 2570 2570 """Test that load_entities excludes blocked entities by default.""" 2571 2571 import json 2572 2572 2573 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2573 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2574 2574 2575 2575 # Create journal entities - one normal, one blocked 2576 2576 normal_dir = tmp_path / "entities" / "alice" ··· 2612 2612 """Test that load_entities includes blocked entities when include_blocked=True.""" 2613 2613 import json 2614 2614 2615 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2615 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2616 2616 2617 2617 # Create blocked journal entity 2618 2618 blocked_dir = tmp_path / "entities" / "bob" ··· 2643 2643 """Test that resolve_entity doesn't find blocked entities by default.""" 2644 2644 import json 2645 2645 2646 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2646 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2647 2647 2648 2648 # Create blocked journal entity 2649 2649 blocked_dir = tmp_path / "entities" / "bob" ··· 2672 2672 """Test that resolve_entity finds blocked entities when include_blocked=True.""" 2673 2673 import json 2674 2674 2675 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2675 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2676 2676 2677 2677 # Create blocked journal entity 2678 2678 blocked_dir = tmp_path / "entities" / "bob" ··· 2703 2703 """Test that load_all_attached_entities excludes blocked entities.""" 2704 2704 import json 2705 2705 2706 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 2706 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 2707 2707 2708 2708 # Create journal entities - one normal, one blocked 2709 2709 normal_dir = tmp_path / "entities" / "alice"
+1 -1
tests/test_entity_ingest.py
··· 32 32 @pytest.fixture 33 33 def journal_env(tmp_path, monkeypatch): 34 34 monkeypatch.setattr(convey.state, "journal_root", str(tmp_path), raising=False) 35 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 35 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 36 36 (tmp_path / "apps" / "import" / "journal_sources").mkdir( 37 37 parents=True, exist_ok=True 38 38 )
+1 -1
tests/test_entity_observer_context.py
··· 18 18 19 19 20 20 def _set_journal(path: str) -> None: 21 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = path 21 + os.environ["SOLSTONE_JOURNAL"] = path 22 22 clear_entity_loading_cache() 23 23 clear_observation_cache() 24 24 clear_relationship_caches()
+2 -2
tests/test_entity_talents.py
··· 12 12 13 13 @pytest.fixture 14 14 def fixture_journal(): 15 - """Set _SOLSTONE_JOURNAL_OVERRIDE to tests/fixtures/journal for testing.""" 16 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 15 + """Set SOLSTONE_JOURNAL to tests/fixtures/journal for testing.""" 16 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 17 17 yield 18 18 # No cleanup needed - just testing reads 19 19
+6 -6
tests/test_exec_context.py
··· 45 45 46 46 def test_exec_pre_process_populated_state(monkeypatch, tmp_path): 47 47 journal = tmp_path / "journal" 48 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 48 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 49 49 _write_journal_config( 50 50 journal, {"agent": {"name": "Sol-agent", "name_status": "custom"}} 51 51 ) ··· 98 98 99 99 def test_exec_pre_process_empty_state(monkeypatch, tmp_path): 100 100 journal = tmp_path / "journal" 101 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 101 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 102 102 _write_journal_config( 103 103 journal, {"agent": {"name": "Sol-agent", "name_status": "custom"}} 104 104 ) ··· 119 119 120 120 def test_exec_pre_process_errors_swallowed(monkeypatch, tmp_path): 121 121 journal = tmp_path / "journal" 122 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 122 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 123 123 _write_journal_config( 124 124 journal, {"agent": {"name": "Sol-agent", "name_status": "custom"}} 125 125 ) ··· 150 150 151 151 def test_exec_pre_process_returned_dict_shape(monkeypatch, tmp_path): 152 152 journal = tmp_path / "journal" 153 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 153 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 154 154 155 155 monkeypatch.setattr("think.routines.get_routine_state", lambda: []) 156 156 monkeypatch.setattr( ··· 167 167 168 168 def test_exec_pre_process_never_calls_save_config(monkeypatch, tmp_path): 169 169 journal = tmp_path / "journal" 170 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 170 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 171 171 _write_journal_config( 172 172 journal, {"agent": {"name": "Sol-agent", "name_status": "custom"}} 173 173 ) ··· 203 203 204 204 def test_exec_and_chat_render_identical_routine_vars(monkeypatch, tmp_path): 205 205 journal = tmp_path / "journal" 206 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 206 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 207 207 _write_journal_config( 208 208 journal, {"agent": {"name": "Sol-agent", "name_status": "custom"}} 209 209 )
+1 -1
tests/test_export.py
··· 34 34 35 35 36 36 def _set_journal_override(monkeypatch, journal_path): 37 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 37 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 38 38 think.utils._journal_path_cache = None 39 39 40 40
+4 -4
tests/test_export_integration.py
··· 37 37 38 38 39 39 def _set_active_journal(journal: Path) -> None: 40 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 40 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 41 41 think.utils._journal_path_cache = None 42 42 clear_journal_entity_cache() 43 43 ··· 121 121 @pytest.fixture 122 122 def export_integration_env(tmp_path, monkeypatch): 123 123 """Set up source journal + target Flask app for integration testing.""" 124 - previous_override = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 124 + previous_override = os.environ.get("SOLSTONE_JOURNAL") 125 125 source_journal = tmp_path / "source" 126 126 source_journal.mkdir() 127 127 _set_active_journal(source_journal) ··· 174 174 } 175 175 176 176 if previous_override is None: 177 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 177 + os.environ.pop("SOLSTONE_JOURNAL", None) 178 178 else: 179 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = previous_override 179 + os.environ["SOLSTONE_JOURNAL"] = previous_override 180 180 think.utils._journal_path_cache = None 181 181 clear_journal_entity_cache() 182 182
+1 -1
tests/test_facet_ingest.py
··· 28 28 @pytest.fixture 29 29 def journal_env(tmp_path, monkeypatch): 30 30 monkeypatch.setattr(convey.state, "journal_root", str(tmp_path), raising=False) 31 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 31 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 32 32 (tmp_path / "apps" / "import" / "journal_sources").mkdir( 33 33 parents=True, exist_ok=True 34 34 )
+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", {"_SOLSTONE_JOURNAL_OVERRIDE": str(journal_path)}): 65 + with patch.dict("os.environ", {"SOLSTONE_JOURNAL": 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 17 17 18 18 # Create facet directory with facet.json 19 19 facet_dir = tmp_path / "facets" / "old-name"
+41 -41
tests/test_facets.py
··· 124 124 125 125 def test_facet_summary_full(monkeypatch): 126 126 """Test facet_summary with full metadata.""" 127 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 127 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 128 128 129 129 summary = facet_summary("full-featured") 130 130 ··· 153 153 154 154 def test_facet_summary_short_mode(monkeypatch): 155 155 """Test facet_summary with detailed=False shows names only.""" 156 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 156 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 157 157 158 158 summary = facet_summary("full-featured", detailed=False) 159 159 ··· 177 177 178 178 def test_facet_summary_minimal(monkeypatch): 179 179 """Test facet_summary with minimal metadata.""" 180 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 180 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 181 181 182 182 summary = facet_summary("minimal-facet") 183 183 ··· 192 192 193 193 def test_facet_summary_test_facet(monkeypatch): 194 194 """Test facet_summary with the existing test-facet fixture.""" 195 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 195 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 196 196 197 197 summary = facet_summary("test-facet") 198 198 ··· 208 208 209 209 def test_facet_summary_nonexistent(monkeypatch): 210 210 """Test facet_summary with nonexistent facet.""" 211 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 211 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 212 212 213 213 with pytest.raises(FileNotFoundError, match="Facet 'nonexistent' not found"): 214 214 facet_summary("nonexistent") ··· 216 216 217 217 def test_facet_summary_empty_journal(tmp_path, monkeypatch): 218 218 """Test facet_summary raises FileNotFoundError with empty journal.""" 219 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 219 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 220 220 221 221 with pytest.raises(FileNotFoundError, match="not found"): 222 222 facet_summary("any-facet") ··· 224 224 225 225 def test_facet_summary_missing_facet_json(monkeypatch): 226 226 """Test facet_summary with missing facet.json.""" 227 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 227 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 228 228 229 229 with pytest.raises(FileNotFoundError, match="facet.json not found"): 230 230 facet_summary("broken-facet") ··· 232 232 233 233 def test_facet_summary_empty_entities(monkeypatch): 234 234 """Test facet_summary with empty entities file.""" 235 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 235 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 236 236 237 237 summary = facet_summary("empty-entities") 238 238 ··· 242 242 243 243 def test_get_facets_with_entities(monkeypatch): 244 244 """Test that get_facets() returns metadata and load_entity_names() works with facets.""" 245 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 245 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 246 246 247 247 facets = get_facets() 248 248 ··· 278 278 279 279 def test_get_facets_empty_entities(monkeypatch): 280 280 """Test get_facets() with facet that has no entities.""" 281 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 281 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 282 282 283 283 facets = get_facets() 284 284 ··· 297 297 298 298 def test_facet_summaries(monkeypatch): 299 299 """Test facet_summaries() generates correct agent prompt format.""" 300 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 300 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 301 301 302 302 summary = facet_summaries() 303 303 ··· 344 344 encoding="utf-8", 345 345 ) 346 346 347 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 347 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 348 348 349 349 summary = facet_summaries() 350 350 ··· 356 356 """Test facet_summaries() when no facets exist.""" 357 357 empty_journal = tmp_path / "empty_journal" 358 358 empty_journal.mkdir() 359 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(empty_journal)) 359 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(empty_journal)) 360 360 361 361 summary = facet_summaries() 362 362 assert summary == "No facets found." ··· 364 364 365 365 def test_facet_summaries_empty_journal(tmp_path, monkeypatch): 366 366 """Test facet_summaries() returns 'No facets found' with empty journal.""" 367 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 367 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 368 368 369 369 summary = facet_summaries() 370 370 assert summary == "No facets found." ··· 372 372 373 373 def test_facet_summaries_mixed_entities(monkeypatch): 374 374 """Test facet_summaries() with facets having different entity configurations.""" 375 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 375 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 376 376 377 377 summary = facet_summaries() 378 378 ··· 428 428 ) 429 429 ) 430 430 431 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 431 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 432 432 433 433 active = get_active_facets("20240115") 434 434 ··· 450 450 seg2.mkdir(parents=True) 451 451 (seg2 / "facets.json").write_text("") 452 452 453 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 453 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 454 454 455 455 active = get_active_facets("20240115") 456 456 ··· 462 462 journal = tmp_path / "journal" 463 463 (journal / "chronicle" / "20240115").mkdir(parents=True) 464 464 465 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 465 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 466 466 467 467 active = get_active_facets("20240115") 468 468 ··· 474 474 journal = tmp_path / "journal" 475 475 journal.mkdir() 476 476 477 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 477 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 478 478 479 479 active = get_active_facets("20240115") 480 480 ··· 502 502 ) 503 503 ) 504 504 505 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 505 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 506 506 507 507 active = get_active_facets("20240115") 508 508 ··· 518 518 """Test _get_principal_display_name returns preferred name.""" 519 519 import json 520 520 521 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 521 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 522 522 523 523 config_dir = tmp_path / "config" 524 524 config_dir.mkdir() ··· 532 532 """Test _get_principal_display_name falls back to name when no preferred.""" 533 533 import json 534 534 535 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 535 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 536 536 537 537 config_dir = tmp_path / "config" 538 538 config_dir.mkdir() ··· 544 544 545 545 def test_get_principal_display_name_none_when_empty(tmp_path, monkeypatch): 546 546 """Test _get_principal_display_name returns None when identity empty.""" 547 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 547 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 548 548 # No config file 549 549 550 550 assert _get_principal_display_name() is None ··· 554 554 """Test _format_principal_role extracts and formats principal.""" 555 555 import json 556 556 557 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 557 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 558 558 559 559 config_dir = tmp_path / "config" 560 560 config_dir.mkdir() ··· 575 575 576 576 def test_format_principal_role_no_principal(tmp_path, monkeypatch): 577 577 """Test _format_principal_role returns None when no principal.""" 578 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 578 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 579 579 580 580 entities = [ 581 581 {"name": "Alice", "description": "Friend"}, ··· 592 592 """Test _format_principal_role returns None when principal has no description.""" 593 593 import json 594 594 595 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 595 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 596 596 597 597 config_dir = tmp_path / "config" 598 598 config_dir.mkdir() ··· 614 614 615 615 def test_format_principal_role_no_identity(tmp_path, monkeypatch): 616 616 """Test _format_principal_role returns None when no identity configured.""" 617 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 617 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 618 618 # No config file 619 619 620 620 entities = [ ··· 632 632 633 633 def test_facet_summary_with_principal(tmp_path, monkeypatch): 634 634 """Test facet_summary shows principal role and excludes from entities list.""" 635 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 635 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 636 636 637 637 # Create identity config 638 638 config_dir = tmp_path / "config" ··· 674 674 675 675 def test_facet_summary_principal_only_entity(tmp_path, monkeypatch): 676 676 """Test facet_summary when principal is the only entity.""" 677 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 677 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 678 678 679 679 # Create identity config 680 680 config_dir = tmp_path / "config" ··· 709 709 710 710 def test_facet_summaries_detailed_with_principal(tmp_path, monkeypatch): 711 711 """Test facet_summaries detailed mode shows principal role.""" 712 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 712 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 713 713 714 714 # Create identity config 715 715 config_dir = tmp_path / "config" ··· 749 749 750 750 def test_facet_summaries_simple_mode_with_principal(tmp_path, monkeypatch): 751 751 """Test facet_summaries simple mode also filters principal consistently.""" 752 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 752 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 753 753 754 754 # Create identity config 755 755 config_dir = tmp_path / "config" ··· 787 787 788 788 def test_facet_summaries_detailed_with_activities(monkeypatch): 789 789 """Test facet_summaries detailed mode includes activity details.""" 790 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(FIXTURES_PATH)) 790 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(FIXTURES_PATH)) 791 791 792 792 summary = facet_summaries(detailed=True) 793 793 ··· 805 805 monkeypatch, 806 806 ): 807 807 """Rank entities by observation count, then recency, then name.""" 808 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 808 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 809 809 setup_facet(tmp_path, "signals", title="Signals") 810 810 entities = [ 811 811 {"type": "Person", "name": "Alpha", "description": "A"}, ··· 835 835 836 836 def test_rank_entities_by_signal_uses_casefold_name_tiebreaker(tmp_path, monkeypatch): 837 837 """Identical signals fall back to case-insensitive name ordering.""" 838 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 838 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 839 839 setup_facet(tmp_path, "signals", title="Signals") 840 840 entities = [ 841 841 {"type": "Person", "name": "bravo", "description": "B"}, ··· 858 858 """Detailed mode caps entities and appends the trailing bullet.""" 859 859 from think.activities import save_facet_activities 860 860 861 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 861 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 862 862 setup_facet( 863 863 tmp_path, 864 864 "entity-cap", ··· 895 895 """Detailed mode caps activities and appends the trailing bullet.""" 896 896 from think.activities import DEFAULT_ACTIVITIES, save_facet_activities 897 897 898 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 898 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 899 899 setup_facet( 900 900 tmp_path, 901 901 "activity-cap", ··· 931 931 """Simple mode only switches capped sections to bullet lists.""" 932 932 from think.activities import save_facet_activities 933 933 934 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 934 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 935 935 setup_facet( 936 936 tmp_path, 937 937 "simple-cap", ··· 968 968 """Exactly-at-cap output stays uncapped and keeps simple one-line formatting.""" 969 969 from think.activities import DEFAULT_ACTIVITIES, save_facet_activities 970 970 971 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 971 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 972 972 setup_facet( 973 973 tmp_path, 974 974 "exact-cap", ··· 1007 1007 """None entity cap restores the full entity list.""" 1008 1008 from think.activities import save_facet_activities 1009 1009 1010 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1010 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1011 1011 setup_facet( 1012 1012 tmp_path, 1013 1013 "entity-unbounded", ··· 1038 1038 """None activity cap restores the full activity list.""" 1039 1039 from think.activities import DEFAULT_ACTIVITIES, save_facet_activities 1040 1040 1041 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1041 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1042 1042 setup_facet( 1043 1043 tmp_path, 1044 1044 "activity-unbounded", ··· 1068 1068 """Principal role line does not count against the entity cap.""" 1069 1069 from think.activities import save_facet_activities 1070 1070 1071 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1071 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1072 1072 write_identity_config(tmp_path) 1073 1073 setup_facet( 1074 1074 tmp_path,
+13 -18
tests/test_formatters.py
··· 9 9 10 10 import pytest 11 11 12 - # Set _SOLSTONE_JOURNAL_OVERRIDE to fixtures for tests 13 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str( 14 - Path(__file__).parent / "fixtures" / "journal" 15 - ) 12 + # Set SOLSTONE_JOURNAL to fixtures for tests 13 + os.environ["SOLSTONE_JOURNAL"] = str(Path(__file__).parent / "fixtures" / "journal") 16 14 17 15 18 16 class TestRegistry: ··· 131 129 from think.formatters import load_jsonl 132 130 133 131 path = ( 134 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 132 + Path(os.environ["SOLSTONE_JOURNAL"]) 135 133 / "chronicle/20240101/default/123456_300/audio.jsonl" 136 134 ) 137 135 entries = load_jsonl(path) ··· 186 184 from think.formatters import format_file 187 185 188 186 path = ( 189 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 187 + Path(os.environ["SOLSTONE_JOURNAL"]) 190 188 / "chronicle/20240102/default/234567_300/screen.jsonl" 191 189 ) 192 190 chunks, meta = format_file(path) ··· 203 201 from think.formatters import format_file 204 202 205 203 path = ( 206 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 204 + Path(os.environ["SOLSTONE_JOURNAL"]) 207 205 / "chronicle/20240101/default/123456_300/audio.jsonl" 208 206 ) 209 207 chunks, meta = format_file(path) ··· 241 239 from think.formatters import format_file 242 240 243 241 # Create a file under journal that won't match any pattern 244 - journal_path = Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 242 + journal_path = Path(os.environ["SOLSTONE_JOURNAL"]) 245 243 temp_file = journal_path / "unknown_file.txt" 246 244 temp_file.write_text("test content") 247 245 ··· 519 517 from observe.hear import load_transcript 520 518 521 519 path = ( 522 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 520 + Path(os.environ["SOLSTONE_JOURNAL"]) 523 521 / "chronicle/20240101/default/123456_300/audio.jsonl" 524 522 ) 525 523 metadata, entries, formatted_text = load_transcript(path) ··· 548 546 from think.formatters import format_file 549 547 550 548 path = ( 551 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 549 + Path(os.environ["SOLSTONE_JOURNAL"]) 552 550 / "facets/personal/entities/20250101.jsonl" 553 551 ) 554 552 chunks, meta = format_file(path) ··· 842 840 from think.formatters import format_file 843 841 844 842 path = ( 845 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 843 + Path(os.environ["SOLSTONE_JOURNAL"]) 846 844 / "facets/personal/entities/alice_johnson/observations.jsonl" 847 845 ) 848 846 chunks, meta = format_file(path) ··· 877 875 from think.formatters import format_file 878 876 879 877 path = ( 880 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 878 + Path(os.environ["SOLSTONE_JOURNAL"]) 881 879 / "facets/personal/todos/20240101.jsonl" 882 880 ) 883 881 chunks, meta = format_file(path) ··· 1014 1012 from think.formatters import format_file 1015 1013 1016 1014 path = ( 1017 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 1018 - / "facets/work/events/20240101.jsonl" 1015 + Path(os.environ["SOLSTONE_JOURNAL"]) / "facets/work/events/20240101.jsonl" 1019 1016 ) 1020 1017 chunks, meta = format_file(path) 1021 1018 ··· 1330 1327 from think.formatters import format_file 1331 1328 1332 1329 path = ( 1333 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 1334 - / "chronicle/20240101/talents/flow.md" 1330 + Path(os.environ["SOLSTONE_JOURNAL"]) / "chronicle/20240101/talents/flow.md" 1335 1331 ) 1336 1332 chunks, meta = format_file(path) 1337 1333 ··· 1344 1340 from think.formatters import load_markdown 1345 1341 1346 1342 path = ( 1347 - Path(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"]) 1348 - / "chronicle/20240101/talents/flow.md" 1343 + Path(os.environ["SOLSTONE_JOURNAL"]) / "chronicle/20240101/talents/flow.md" 1349 1344 ) 1350 1345 text = load_markdown(path) 1351 1346
+6 -6
tests/test_gemini_importer.py
··· 175 175 176 176 try: 177 177 with tempfile.TemporaryDirectory() as journal: 178 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 178 + os.environ["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE", None) 201 + os.environ.pop("SOLSTONE_JOURNAL", 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["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 214 + os.environ["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE", None) 222 + os.environ.pop("SOLSTONE_JOURNAL", 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["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 240 + os.environ["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE", None) 248 + os.environ.pop("SOLSTONE_JOURNAL", None) 249 249 250 250 251 251 # --- Registry test ---
+13 -13
tests/test_generate_full.py
··· 23 23 24 24 25 25 def copy_day(tmp_path: Path) -> Path: 26 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 26 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 27 27 dest = day_path("20240101") 28 28 src = FIXTURES / "journal" / "chronicle" / "20240101" 29 29 copytree_tracked(src, dest) ··· 103 103 lambda *a, **k: MOCK_RESULT, 104 104 ) 105 105 monkeypatch.setenv("GOOGLE_API_KEY", "x") 106 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 106 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 107 107 108 108 config = { 109 109 "name": "test_gen", ··· 158 158 ) 159 159 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 160 160 monkeypatch.setenv("GOOGLE_API_KEY", "x") 161 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 161 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 162 162 163 163 events = run_generator_with_config( 164 164 mod, ··· 201 201 mock_generate = MagicMock(return_value=MOCK_RESULT) 202 202 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 203 203 monkeypatch.setenv("GOOGLE_API_KEY", "x") 204 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 204 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 205 205 206 206 run_generator_with_config( 207 207 mod, ··· 255 255 ), 256 256 ) 257 257 monkeypatch.setenv("GOOGLE_API_KEY", "x") 258 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 258 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 259 259 260 260 events = run_generator_with_config( 261 261 mod, ··· 301 301 MagicMock(return_value=MOCK_RESULT), 302 302 ) 303 303 monkeypatch.setenv("GOOGLE_API_KEY", "x") 304 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 304 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 305 305 306 306 events = run_generator_with_config( 307 307 mod, ··· 364 364 lambda *a, **k: MOCK_RESULT, 365 365 ) 366 366 monkeypatch.setenv("GOOGLE_API_KEY", "x") 367 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 367 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 368 368 369 369 config = { 370 370 "name": "hooked_gen", ··· 418 418 lambda *a, **k: MOCK_RESULT, 419 419 ) 420 420 monkeypatch.setenv("GOOGLE_API_KEY", "x") 421 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 421 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 422 422 423 423 config = { 424 424 "name": "nohook_gen", ··· 442 442 mod = importlib.import_module("think.talents") 443 443 copy_day(tmp_path) 444 444 445 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 445 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 446 446 447 447 config = { 448 448 "name": "nonexistent_generator", ··· 463 463 mod = importlib.import_module("think.talents") 464 464 465 465 # Create empty day directory (no transcripts) 466 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 466 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 467 467 day_dir = day_path("20240101") 468 468 day_dir.mkdir(parents=True, exist_ok=True) 469 469 ··· 477 477 ) 478 478 479 479 monkeypatch.setenv("GOOGLE_API_KEY", "x") 480 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 480 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 481 481 482 482 config = { 483 483 "name": "empty_gen", ··· 500 500 mod = importlib.import_module("think.talents") 501 501 502 502 # Create empty day directory (no transcripts) 503 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 503 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 504 504 day_dir = day_path("20240101") 505 505 day_dir.mkdir(parents=True, exist_ok=True) 506 506 ··· 514 514 ) 515 515 516 516 monkeypatch.setenv("GOOGLE_API_KEY", "x") 517 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 517 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 518 518 519 519 config = mod.prepare_config( 520 520 {
+2 -2
tests/test_generate_scan_day.py
··· 12 12 13 13 14 14 def copy_day(tmp_path: Path) -> Path: 15 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 15 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 16 16 dest = day_path("20240101") 17 17 src = FIXTURES / "journal" / "chronicle" / "20240101" 18 18 copytree_tracked(src, dest) ··· 25 25 def test_scan_day(tmp_path, monkeypatch): 26 26 mod = importlib.import_module("think.talents") 27 27 day_dir = copy_day(tmp_path) 28 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 28 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 29 29 30 30 info = mod.scan_day("20240101") 31 31 assert "talents/schedule.json" 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 11 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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
··· 85 85 journal = tmp_path / "journal" 86 86 journal.mkdir() 87 87 88 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 88 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 89 89 monkeypatch.setenv("GOOGLE_API_KEY", "x") 90 90 monkeypatch.setattr( 91 91 "think.providers.cli.shutil.which", ··· 164 164 journal = tmp_path / "journal" 165 165 journal.mkdir() 166 166 167 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 167 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 168 168 monkeypatch.setenv("GOOGLE_API_KEY", "x") 169 169 monkeypatch.setattr("think.providers.cli.shutil.which", lambda _name: None) 170 170
+1 -1
tests/test_google_thinking.py
··· 31 31 journal = tmp_path / "journal" 32 32 journal.mkdir() 33 33 34 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 34 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 15 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 54 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 74 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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_heartbeat.py
··· 9 9 10 10 @pytest.fixture 11 11 def journal_path(tmp_path, monkeypatch): 12 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 12 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 13 13 (tmp_path / "health").mkdir() 14 14 return tmp_path 15 15
+7 -7
tests/test_home_routines.py
··· 34 34 35 35 def test_collect_routines_empty_config(monkeypatch, tmp_path): 36 36 """Missing routines config yields no pulse routines.""" 37 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 37 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 38 38 39 39 assert _collect_routines() == [] 40 40 41 41 42 42 def test_collect_routines_with_recent_output(monkeypatch, tmp_path): 43 43 """Recent enabled routine output is returned with an extracted summary.""" 44 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 44 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 45 45 46 46 routine_id = "morning-briefing" 47 47 _write_routines_config( ··· 76 76 """When multiple outputs exist for a routine, the newest by mtime is used.""" 77 77 import time 78 78 79 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 79 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 80 80 81 81 routine_id = "multi-run" 82 82 _write_routines_config( ··· 110 110 111 111 def test_collect_routines_stale_excluded(monkeypatch, tmp_path): 112 112 """Stale routine runs are excluded from pulse.""" 113 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 113 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 114 114 115 115 _write_routines_config( 116 116 tmp_path, ··· 130 130 131 131 def test_collect_routines_disabled_excluded(monkeypatch, tmp_path): 132 132 """Disabled routines are excluded even with recent runs.""" 133 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 133 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 134 134 135 135 _write_routines_config( 136 136 tmp_path, ··· 150 150 151 151 def test_collect_routines_seen_flag(monkeypatch, tmp_path): 152 152 """Routine runs before the last-seen marker are marked seen.""" 153 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 153 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 154 154 155 155 last_run = datetime.now() - timedelta(hours=2) 156 156 _write_routines_config( ··· 177 177 178 178 def test_api_routines_seen(monkeypatch, tmp_path, home_client): 179 179 """Seen endpoint persists the routines seen timestamp.""" 180 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 180 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 181 181 182 182 resp = home_client.post("/app/home/api/routines/seen") 183 183
+9 -9
tests/test_home_skills.py
··· 102 102 103 103 def test_collect_skills_no_facets(monkeypatch, tmp_path): 104 104 """No owner-wide skills directory yields an empty list.""" 105 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 105 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 106 106 107 107 assert _collect_skills() == [] 108 108 109 109 110 110 def test_collect_skills_no_patterns(monkeypatch, tmp_path): 111 111 """An empty owner-wide skills directory yields an empty list.""" 112 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 112 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 113 113 114 114 (tmp_path / "skills").mkdir(parents=True) 115 115 ··· 118 118 119 119 def test_collect_skills_with_owner_wide_profile(monkeypatch, tmp_path): 120 120 """Pulse collects owner-wide profiles with the new payload shape.""" 121 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 121 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 122 122 123 123 _write_skill_fixtures( 124 124 tmp_path, ··· 178 178 179 179 def test_collect_skills_hides_pattern_without_profile(monkeypatch, tmp_path): 180 180 """Observer-only patterns stay hidden until a profile exists.""" 181 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 181 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 182 182 183 183 _write_skill_fixtures( 184 184 tmp_path, ··· 191 191 192 192 def test_collect_skills_seen_flag(monkeypatch, tmp_path): 193 193 """Profiles older than the last seen marker are marked seen.""" 194 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 194 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 195 195 196 196 _write_skill_fixtures( 197 197 tmp_path, ··· 218 218 219 219 def test_collect_skills_shows_dormant(monkeypatch, tmp_path): 220 220 """Dormant skills stay visible in Pulse.""" 221 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 221 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 222 222 223 223 _write_skill_fixtures( 224 224 tmp_path, ··· 240 240 241 241 def test_collect_skills_hides_retired(monkeypatch, tmp_path): 242 242 """Retired skills are excluded from Pulse.""" 243 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 243 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 244 244 245 245 _write_skill_fixtures( 246 246 tmp_path, ··· 259 259 260 260 def test_collect_skills_sorts_by_confidence_then_last_seen(monkeypatch, tmp_path): 261 261 """Skills sort by confidence first, then recency.""" 262 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 262 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 263 263 264 264 _write_skill_fixtures( 265 265 tmp_path, ··· 325 325 326 326 def test_api_skills_seen(monkeypatch, tmp_path, home_client): 327 327 """Seen endpoint persists the skills seen timestamp.""" 328 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 328 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 329 329 330 330 resp = home_client.post("/app/home/api/skills/seen") 331 331
+2 -2
tests/test_home_yesterdays_processing.py
··· 66 66 def _seed_journal(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: 67 67 journal = tmp_path / "journal" 68 68 journal.mkdir() 69 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 69 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 70 70 71 71 for day, transcript_seconds in (("20260415", 3600), ("20260414", 2700)): 72 72 facet_data = {"work": {"count": 1, "minutes": 15}} ··· 266 266 ): 267 267 journal = tmp_path / "journal" 268 268 journal.mkdir() 269 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 269 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 270 270 271 271 today = "20260418" 272 272 now_ms = int(datetime.now().timestamp() * 1000)
+1 -1
tests/test_identity_writes.py
··· 16 16 17 17 @pytest.fixture(autouse=True) 18 18 def _temp_journal(monkeypatch, tmp_path): 19 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 19 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 20 20 config_dir = tmp_path / "config" 21 21 config_dir.mkdir() 22 22 (config_dir / "journal.json").write_text("{}", encoding="utf-8")
+1 -1
tests/test_import_call.py
··· 57 57 """Set up a temp journal with an import source and state directory.""" 58 58 59 59 monkeypatch.setattr(convey.state, "journal_root", str(tmp_path), raising=False) 60 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 60 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 61 61 think.utils._journal_path_cache = None 62 62 clear_journal_entity_cache() 63 63 (tmp_path / "apps" / "import" / "journal_sources").mkdir(
+6 -6
tests/test_import_dedup.py
··· 170 170 def test_reimport_same_entries_no_duplicates(): 171 171 """Re-importing identical entries should not create duplicates.""" 172 172 with tempfile.TemporaryDirectory() as journal: 173 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 173 + os.environ["SOLSTONE_JOURNAL"] = journal 174 174 try: 175 175 entries = [ 176 176 { ··· 205 205 header2 = json.loads(lines2[0]) 206 206 assert header2["entry_count"] == 2 207 207 finally: 208 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 208 + os.environ.pop("SOLSTONE_JOURNAL", None) 209 209 210 210 211 211 def test_reimport_with_new_entries_merges(): 212 212 """Re-importing with new entries should merge (add new, keep old).""" 213 213 with tempfile.TemporaryDirectory() as journal: 214 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 214 + os.environ["SOLSTONE_JOURNAL"] = journal 215 215 try: 216 216 original = [ 217 217 { ··· 247 247 assert "Standup" in titles 248 248 assert "New meeting" in titles 249 249 finally: 250 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 250 + os.environ.pop("SOLSTONE_JOURNAL", None) 251 251 252 252 253 253 def test_first_import_no_merge_needed(): 254 254 """First import should work normally with no existing file.""" 255 255 with tempfile.TemporaryDirectory() as journal: 256 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 256 + os.environ["SOLSTONE_JOURNAL"] = journal 257 257 try: 258 258 entries = [ 259 259 { ··· 267 267 assert len(files) == 1 268 268 assert Path(files[0]).exists() 269 269 finally: 270 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 270 + os.environ.pop("SOLSTONE_JOURNAL", None) 271 271 272 272 273 273 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 _SOLSTONE_JOURNAL_OVERRIDE for format_file 328 - old_journal = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 329 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = tmpdir 327 + # Set SOLSTONE_JOURNAL for format_file 328 + old_journal = os.environ.get("SOLSTONE_JOURNAL") 329 + os.environ["SOLSTONE_JOURNAL"] = 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["_SOLSTONE_JOURNAL_OVERRIDE"] = old_journal 351 + os.environ["SOLSTONE_JOURNAL"] = old_journal 352 352 else: 353 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 353 + os.environ.pop("SOLSTONE_JOURNAL", None)
+20 -20
tests/test_importer.py
··· 137 137 txt = tmp_path / "sample.txt" 138 138 txt.write_text(transcript) 139 139 140 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 140 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 141 141 monkeypatch.setattr( 142 142 mod, "detect_created", lambda p, **kw: {"day": "20240101", "time": "120000"} 143 143 ) ··· 223 223 pdf = tmp_path / "meeting.pdf" 224 224 pdf.write_bytes(b"%PDF-1.4 fake") 225 225 226 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 226 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 227 227 monkeypatch.setattr( 228 228 mod, "detect_created", lambda p, **kw: {"day": "20251205", "time": "163000"} 229 229 ) ··· 364 364 def test_write_markdown_segments(tmp_path, monkeypatch): 365 365 """write_markdown_segments creates segment dirs with imported.md files.""" 366 366 mod = importlib.import_module("think.importers.shared") 367 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 367 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 368 368 369 369 windows = [ 370 370 ("20260301", "120000_300", [{"text": "hello"}, {"text": "world"}]), ··· 457 457 with zipfile.ZipFile(archive, "w") as zf: 458 458 zf.writestr("conversations.json", json.dumps(conversations)) 459 459 460 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 460 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 461 461 462 462 fixed_dt = dt.datetime(2026, 1, 20, 8, 30, 0) 463 463 ··· 597 597 with zipfile.ZipFile(archive, "w") as zf: 598 598 zf.writestr("conversations.json", json.dumps(conversations)) 599 599 600 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 600 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 601 601 602 602 fixed_dt = dt.datetime(2026, 1, 20, 8, 30, 0) 603 603 ··· 771 771 """Test prepare_audio_segments creates segment directories with audio slices.""" 772 772 mod = importlib.import_module("think.importers.audio") 773 773 774 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 774 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 775 775 776 776 audio_file = tmp_path / "test.mp3" 777 777 audio_file.write_bytes(b"fake audio content") ··· 824 824 """Test prepare_audio_segments handles segment key collisions.""" 825 825 mod = importlib.import_module("think.importers.audio") 826 826 827 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 827 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 828 828 829 829 audio_file = tmp_path / "test.mp3" 830 830 audio_file.write_bytes(b"fake audio content") ··· 872 872 txt = tmp_path / "sample.txt" 873 873 txt.write_text("hello\nworld\n") 874 874 875 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 875 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 876 876 monkeypatch.setattr( 877 877 "sys.argv", 878 878 ["sol import", str(txt), "--timestamp", "20240101_120000", "--dry-run"], ··· 906 906 mp3 = tmp_path / "sample.mp3" 907 907 mp3.write_bytes(b"fake audio") 908 908 909 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 909 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 910 910 monkeypatch.setattr(mod, "_get_audio_duration", lambda p: 420.0) 911 911 callosum_cls = MagicMock() 912 912 monkeypatch.setattr(mod, "CallosumConnection", callosum_cls) ··· 946 946 txt = tmp_path / "notes.txt" 947 947 txt.write_text("meeting notes") 948 948 949 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 949 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 950 950 monkeypatch.setattr( 951 951 mod, "detect_created", lambda p, **kw: {"day": "20240315", "time": "140000"} 952 952 ) ··· 988 988 txt = tmp_path / "replacement.txt" 989 989 txt.write_text("replacement transcript") 990 990 991 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 991 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 992 992 _configure_text_import_runtime(monkeypatch, mod) 993 993 monkeypatch.setattr( 994 994 "sys.argv", ··· 1039 1039 txt = tmp_path / "replacement.txt" 1040 1040 txt.write_text("replacement transcript") 1041 1041 1042 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1042 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1043 1043 monkeypatch.setattr( 1044 1044 "sys.argv", 1045 1045 ["sol import", str(txt), "--timestamp", timestamp, "--force", "--dry-run"], ··· 1071 1071 txt = tmp_path / "replacement.txt" 1072 1072 txt.write_text("replacement transcript") 1073 1073 1074 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1074 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1075 1075 monkeypatch.setattr( 1076 1076 "sys.argv", 1077 1077 ["sol import", str(txt), "--timestamp", timestamp], ··· 1102 1102 mock_imp = _make_mock_file_importer() 1103 1103 callosum = MagicMock() 1104 1104 1105 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1105 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1106 1106 monkeypatch.setattr("sys.argv", ["sol import", str(ics_file), "--source", "ics"]) 1107 1107 monkeypatch.setattr( 1108 1108 "think.importers.file_importer.get_file_importer", lambda name: mock_imp ··· 1138 1138 mock_imp = _make_mock_file_importer() 1139 1139 callosum = MagicMock() 1140 1140 1141 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1141 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1142 1142 monkeypatch.setattr( 1143 1143 "sys.argv", 1144 1144 [ ··· 1452 1452 END:VCALENDAR""" 1453 1453 ) 1454 1454 1455 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1455 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1456 1456 1457 1457 result = mod.ICSImporter().process(ics_path, tmp_path, facet="work") 1458 1458 ··· 1544 1544 1545 1545 mock_imp = _make_mock_file_importer() 1546 1546 1547 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1547 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1548 1548 monkeypatch.setattr( 1549 1549 "sys.argv", 1550 1550 ["sol import", str(ics_file), "--source", "ics", "--dry-run", "--json"], ··· 1584 1584 mock_imp = _make_mock_file_importer() 1585 1585 callosum = MagicMock() 1586 1586 1587 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1587 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1588 1588 monkeypatch.setattr( 1589 1589 "sys.argv", 1590 1590 ["sol import", str(ics_file), "--source", "ics", "--json"], ··· 1619 1619 1620 1620 mock_imp = _make_mock_file_importer() 1621 1621 1622 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1622 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1623 1623 monkeypatch.setattr( 1624 1624 "sys.argv", 1625 1625 ["sol import", str(ics_file), "--source", "ics"], ··· 1672 1672 os.utime(note2, (base_ts + 60, base_ts + 60)) # 1 min later, same window 1673 1673 os.utime(note3, (base_ts + 600, base_ts + 600)) # 10 min later, different window 1674 1674 1675 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1675 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1676 1676 1677 1677 result = mod.ObsidianImporter().process(vault, tmp_path) 1678 1678
+6 -6
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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 76 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 77 77 monkeypatch.setattr(mod, "PdfReader", MockPdfReader) 78 78 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 108 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 109 109 monkeypatch.setattr(mod, "PdfReader", MockPdfReader) 110 110 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) 111 111 monkeypatch.setattr( ··· 141 141 142 142 pdf = tmp_path / "scan.pdf" 143 143 pdf.write_bytes(b"%PDF-1.4") 144 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 144 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 145 145 monkeypatch.setattr(mod, "PdfReader", ScannedReader) 146 146 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) 147 147 monkeypatch.setattr( ··· 184 184 185 185 pdf = tmp_path / "scan.pdf" 186 186 pdf.write_bytes(b"%PDF-1.4") 187 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 187 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 188 188 monkeypatch.setattr(mod, "PdfReader", ScannedReader) 189 189 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) 190 190 monkeypatch.setattr( ··· 223 223 pdf_b = tmp_path / "b.pdf" 224 224 pdf_a.write_bytes(b"%PDF-1.4") 225 225 pdf_b.write_bytes(b"%PDF-1.4") 226 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 226 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 227 227 monkeypatch.setattr(mod, "PdfReader", MockPdfReader) 228 228 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) 229 229 monkeypatch.setattr( ··· 254 254 255 255 pdf = tmp_path / "parties.pdf" 256 256 pdf.write_bytes(b"%PDF-1.4") 257 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 257 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 258 258 monkeypatch.setattr(mod, "PdfReader", EntityReader) 259 259 monkeypatch.setattr(mod, "day_path", lambda day: tmp_path / "chronicle" / day) 260 260 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 501 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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) ··· 570 570 from think.importers.cli import main 571 571 572 572 monkeypatch.setattr(sys, "argv", ["sol import", "--backends"]) 573 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 573 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/tmp/test-journal") 574 574 main() 575 575 captured = capsys.readouterr() 576 576 assert "granola" in captured.out ··· 590 590 "argv", 591 591 ["sol import", "--sync", "granola", "--path", str(muesli_dir)], 592 592 ) 593 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 593 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 594 594 595 595 main() 596 596 captured = capsys.readouterr() ··· 639 639 from think.entities.observations import load_observations 640 640 from think.importers.granola import GranolaBackend 641 641 642 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 642 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 643 643 644 644 muesli_dir = tmp_path / "muesli" 645 645 _write_transcript(muesli_dir, "2025-10-28_enriched.md", ENRICHED_TRANSCRIPT) ··· 680 680 from think.entities.observations import load_observations 681 681 from think.importers.granola import GranolaBackend 682 682 683 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 683 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 684 684 685 685 muesli_dir = tmp_path / "muesli" 686 686 _write_transcript(muesli_dir, "2025-10-28_enriched.md", ENRICHED_TRANSCRIPT) ··· 706 706 from think.entities.observations import load_observations 707 707 from think.importers.granola import GranolaBackend 708 708 709 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 709 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 710 710 711 711 muesli_dir = tmp_path / "muesli" 712 712 _write_transcript(muesli_dir, "2025-10-28_enriched.md", ENRICHED_TRANSCRIPT) ··· 722 722 """seed_entities() works unchanged when no observations are provided.""" 723 723 from think.entities.seeding import seed_entities 724 724 725 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 725 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 726 726 727 727 entities = [ 728 728 {"name": "Test Person", "type": "Person", "email": "test@example.com"}, ··· 737 737 from think.entities.observations import load_observations 738 738 from think.entities.seeding import seed_entities 739 739 740 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 740 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 741 741 742 742 entities = [ 743 743 # title + company ··· 795 795 from think.entities.observations import load_observations 796 796 from think.entities.seeding import seed_entities 797 797 798 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 798 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 799 799 800 800 entities = [ 801 801 {
+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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 99 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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 ··· 127 127 from think.importers.obsidian import ObsidianSyncBackend 128 128 from think.importers.sync import load_sync_state 129 129 130 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 130 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 131 131 vault = tmp_path / "vault" 132 132 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 133 133 ··· 176 176 """Mtime-only changes are skipped when content hash matches.""" 177 177 from think.importers.obsidian import ObsidianSyncBackend 178 178 179 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 179 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 180 180 vault = tmp_path / "vault" 181 181 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 182 182 ··· 193 193 from think.importers.obsidian import ObsidianSyncBackend 194 194 from think.importers.sync import load_sync_state 195 195 196 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 196 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 197 197 vault = tmp_path / "vault" 198 198 note = _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 199 199 ··· 211 211 """Force re-detects notes by clearing state.""" 212 212 from think.importers.obsidian import ObsidianSyncBackend 213 213 214 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 214 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 215 215 vault = tmp_path / "vault" 216 216 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 217 217 ··· 241 241 """Wikilinks are converted into Topic entities on import.""" 242 242 from think.importers.obsidian import ObsidianSyncBackend 243 243 244 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 244 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 245 245 vault = tmp_path / "vault" 246 246 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 247 247 ··· 274 274 """Incremental sync imports only newly added notes.""" 275 275 from think.importers.obsidian import ObsidianSyncBackend 276 276 277 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 277 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 278 278 vault = tmp_path / "vault" 279 279 _write_note(vault, "Projects/Alpha.md", SAMPLE_NOTE, mtime=1_700_000_000) 280 280 ··· 368 368 """Notes in typed folders produce typed entities.""" 369 369 from think.importers.obsidian import ObsidianSyncBackend 370 370 371 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 371 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 372 372 vault = tmp_path / "vault" 373 373 374 374 _write_note( ··· 403 403 """Wikilinks with @ prefix produce Person entities.""" 404 404 from think.importers.obsidian import ObsidianSyncBackend 405 405 406 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 406 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 407 407 vault = tmp_path / "vault" 408 408 409 409 _write_note( ··· 435 435 """Numeric-prefixed folder names are matched after stripping.""" 436 436 from think.importers.obsidian import ObsidianSyncBackend 437 437 438 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 438 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 439 439 vault = tmp_path / "vault" 440 440 441 441 _write_note(vault, "00 People/Jane.md", "# Jane\nA person.", mtime=1_700_000_000) ··· 538 538 from think.importers.obsidian import ObsidianSyncBackend 539 539 from think.importers.sync import load_sync_state 540 540 541 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 541 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 542 542 vault = tmp_path / "vault" 543 543 _write_note(vault, "Notes/real.md", SAMPLE_NOTE, mtime=1_700_000_000) 544 544 _write_note( ··· 568 568 from think.importers.cli import main 569 569 570 570 monkeypatch.setattr(sys, "argv", ["sol import", "--backends"]) 571 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 571 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/tmp/test-journal") 572 572 573 573 main() 574 574 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("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 98 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 422 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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):
+1 -1
tests/test_imports_ingest.py
··· 26 26 @pytest.fixture 27 27 def journal_env(tmp_path, monkeypatch): 28 28 monkeypatch.setattr(convey.state, "journal_root", str(tmp_path), raising=False) 29 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 29 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 30 30 (tmp_path / "apps" / "import" / "journal_sources").mkdir( 31 31 parents=True, exist_ok=True 32 32 )
+308 -182
tests/test_install_guard.py
··· 24 24 repo = tmp_path / "repo" 25 25 repo.mkdir() 26 26 if worktree: 27 - (repo / ".git").write_text("gitdir: /tmp/worktree\n") 27 + (repo / ".git").write_text("gitdir: /tmp/worktree\n", encoding="utf-8") 28 28 else: 29 29 (repo / ".git").mkdir() 30 30 return repo ··· 33 33 def ensure_expected_target(repo: Path) -> Path: 34 34 target = install_guard.expected_target(repo) 35 35 target.parent.mkdir(parents=True, exist_ok=True) 36 - target.write_text("") 36 + target.write_text("", encoding="utf-8") 37 37 return target 38 38 39 39 ··· 44 44 return alias 45 45 46 46 47 + def make_managed_wrapper( 48 + home_root: Path, 49 + *, 50 + journal: str, 51 + sol_bin: str, 52 + mode: int = 0o755, 53 + ) -> Path: 54 + alias = home_root / ".local" / "bin" / "sol" 55 + alias.parent.mkdir(parents=True, exist_ok=True) 56 + alias.write_text( 57 + install_guard.render_wrapper(journal, sol_bin), 58 + encoding="utf-8", 59 + ) 60 + alias.chmod(mode) 61 + return alias 62 + 63 + 47 64 def other_target(tmp_path: Path) -> Path: 48 65 target = tmp_path / "other" / ".venv" / "bin" / "sol" 49 66 target.parent.mkdir(parents=True, exist_ok=True) 50 - target.write_text("") 67 + target.write_text("", encoding="utf-8") 51 68 return target 52 69 53 70 ··· 58 75 return rc, captured.out, captured.err 59 76 60 77 61 - def alias_error(curdir: Path, installed: str) -> str: 62 - return ( 78 + def alias_error(curdir: Path, installed: str, *, allow_force: bool = False) -> str: 79 + message = ( 63 80 "ERROR: Another solstone install owns ~/.local/bin/sol.\n" 64 81 f" this repo: {curdir}\n" 65 82 f"{installed}\n" 66 83 "Run 'make uninstall-service' from the installed repo first,\n" 67 - "or remove ~/.local/bin/sol manually if that repo is gone. No --force available.\n" 84 + "or remove ~/.local/bin/sol manually if that repo is gone.\n" 68 85 ) 86 + if allow_force: 87 + message += "Rerun 'python -m think.install_guard install --force' only if you intend to replace it from this repo.\n" 88 + return message 69 89 70 90 71 91 def worktree_error(curdir: Path) -> str: 72 92 return f"ERROR: refusing to run from a git worktree ({curdir}). Run from the primary clone.\n" 73 93 74 94 95 + class TestWrapperHelpers: 96 + def test_render_wrapper_round_trip_simple(self): 97 + journal = "/tmp/solstone" 98 + sol_bin = "/tmp/repo/.venv/bin/sol" 99 + 100 + content = install_guard.render_wrapper(journal, sol_bin) 101 + 102 + assert install_guard.parse_wrapper(content) == { 103 + "journal": journal, 104 + "sol_bin": sol_bin, 105 + } 106 + 107 + def test_render_wrapper_round_trip_tricky_paths(self): 108 + journal = "/tmp/solstone notes/über" 109 + sol_bin = "/tmp/it's a test/über/.venv/bin/sol" 110 + 111 + content = install_guard.render_wrapper(journal, sol_bin) 112 + 113 + assert install_guard.parse_wrapper(content) == { 114 + "journal": journal, 115 + "sol_bin": sol_bin, 116 + } 117 + 118 + def test_render_wrapper_matches_spec_template(self): 119 + journal = "/Users/jer/Documents/Solstone" 120 + sol_bin = "/Users/jer/projects/solstone/.venv/bin/sol" 121 + 122 + content = install_guard.render_wrapper(journal, sol_bin) 123 + 124 + assert ( 125 + content == "#!/bin/sh\n" 126 + "# sol — managed by 'sol config'. Edits will be overwritten.\n" 127 + "# managed-version: 1\n" 128 + ': "${SOLSTONE_JOURNAL:=/Users/jer/Documents/Solstone}"\n' 129 + "export SOLSTONE_JOURNAL\n" 130 + "SOL_BIN='/Users/jer/projects/solstone/.venv/bin/sol'\n" 131 + 'if [ ! -x "$SOL_BIN" ]; then\n' 132 + " printf 'sol: venv binary missing or not executable: %s\\n' \"$SOL_BIN\" >&2\n" 133 + " exit 127\n" 134 + "fi\n" 135 + 'exec "$SOL_BIN" "$@"\n' 136 + ) 137 + 138 + @pytest.mark.parametrize("char", ["$", "`", '"', "\\"]) 139 + def test_validate_journal_path_for_wrapper_rejects_invalid_chars(self, char: str): 140 + with pytest.raises(ValueError, match="shell-active character"): 141 + install_guard.validate_journal_path_for_wrapper(f"/tmp/bad{char}path") 142 + 143 + 75 144 class TestCheckAlias: 76 145 def test_absent(self, home_root, tmp_path): 77 146 repo = make_repo(tmp_path) 147 + 78 148 state, other = install_guard.check_alias(repo) 149 + 79 150 assert state is install_guard.AliasState.ABSENT 80 151 assert other is None 81 152 82 - def test_owned(self, home_root, tmp_path): 153 + def test_owned_legacy_symlink(self, home_root, tmp_path): 83 154 repo = make_repo(tmp_path) 84 155 target = ensure_expected_target(repo) 85 156 make_alias(home_root, target) 157 + 86 158 state, other = install_guard.check_alias(repo) 159 + 87 160 assert state is install_guard.AliasState.OWNED 88 - assert other == target 161 + assert other == target.resolve() 162 + 163 + def test_owned_managed_wrapper(self, home_root, tmp_path): 164 + repo = make_repo(tmp_path) 165 + target = ensure_expected_target(repo) 166 + make_managed_wrapper( 167 + home_root, 168 + journal="/tmp/solstone", 169 + sol_bin=str(target), 170 + ) 171 + 172 + state, other = install_guard.check_alias(repo) 173 + 174 + assert state is install_guard.AliasState.OWNED 175 + assert other == target.resolve() 89 176 90 177 def test_cross_repo(self, home_root, tmp_path): 91 178 repo = make_repo(tmp_path) 92 179 target = other_target(tmp_path) 93 180 make_alias(home_root, target) 181 + 94 182 state, other = install_guard.check_alias(repo) 183 + 95 184 assert state is install_guard.AliasState.CROSS_REPO 96 - assert other == target 185 + assert other == target.resolve() 97 186 98 187 def test_dangling(self, home_root, tmp_path): 99 188 repo = make_repo(tmp_path) 100 189 target = tmp_path / "missing" / ".venv" / "bin" / "sol" 101 190 make_alias(home_root, target) 191 + 102 192 state, other = install_guard.check_alias(repo) 193 + 103 194 assert state is install_guard.AliasState.DANGLING 104 - assert other == target 195 + assert other == target.resolve() 105 196 106 - def test_not_symlink(self, home_root, tmp_path): 197 + def test_foreign_regular_file(self, home_root, tmp_path): 107 198 repo = make_repo(tmp_path) 108 199 alias = install_guard.alias_path() 109 200 alias.parent.mkdir(parents=True, exist_ok=True) 110 - alias.write_text("not a symlink") 201 + alias.write_text("not a wrapper", encoding="utf-8") 202 + 111 203 state, other = install_guard.check_alias(repo) 112 - assert state is install_guard.AliasState.NOT_SYMLINK 204 + 205 + assert state is install_guard.AliasState.FOREIGN 113 206 assert other is None 114 207 115 208 def test_worktree(self, home_root, tmp_path): 116 209 repo = make_repo(tmp_path, worktree=True) 210 + 117 211 state, other = install_guard.check_alias(repo) 212 + 118 213 assert state is install_guard.AliasState.WORKTREE 119 214 assert other is None 120 215 ··· 122 217 repo = make_repo(tmp_path, worktree=True) 123 218 target = ensure_expected_target(repo) 124 219 make_alias(home_root, target) 220 + 125 221 state, other = install_guard.check_alias(repo) 222 + 126 223 assert state is install_guard.AliasState.WORKTREE 127 224 assert other is None 128 225 129 226 130 - class TestErrorFormat: 227 + class TestCheckCommand: 131 228 def test_worktree(self, home_root, tmp_path, capsys): 132 229 repo = make_repo(tmp_path, worktree=True).resolve() 230 + 133 231 rc = install_guard.cmd_check(repo) 134 232 captured = capsys.readouterr() 233 + 135 234 assert rc == 1 136 235 assert captured.out == "worktree\n" 137 236 assert captured.err == worktree_error(repo) 138 237 238 + def test_absent(self, home_root, tmp_path, capsys): 239 + repo = make_repo(tmp_path).resolve() 240 + 241 + rc = install_guard.cmd_check(repo) 242 + captured = capsys.readouterr() 243 + 244 + assert rc == 0 245 + assert captured.out == "fresh\n" 246 + assert captured.err == "" 247 + 248 + def test_check_reports_current_for_managed_wrapper_with_matching_paths( 249 + self, home_root, tmp_path, capsys 250 + ): 251 + repo = make_repo(tmp_path) 252 + target = ensure_expected_target(repo) 253 + make_managed_wrapper( 254 + home_root, 255 + journal=install_guard._current_journal_for_alias(), 256 + sol_bin=str(target), 257 + ) 258 + 259 + rc = install_guard.cmd_check(repo) 260 + captured = capsys.readouterr() 261 + 262 + assert rc == 0 263 + assert captured.out == "current\n" 264 + assert captured.err == "" 265 + 266 + def test_check_reports_upgrade_for_legacy_symlink( 267 + self, home_root, tmp_path, capsys 268 + ): 269 + repo = make_repo(tmp_path) 270 + make_alias(home_root, ensure_expected_target(repo)) 271 + 272 + rc = install_guard.cmd_check(repo) 273 + captured = capsys.readouterr() 274 + 275 + assert rc == 0 276 + assert captured.out == "upgrade\n" 277 + assert captured.err == "" 278 + 279 + def test_check_reports_upgrade_for_wrapper_with_stale_paths( 280 + self, home_root, tmp_path, monkeypatch, capsys 281 + ): 282 + repo = make_repo(tmp_path) 283 + target = ensure_expected_target(repo) 284 + old_journal = str((tmp_path / "old-journal").resolve()) 285 + new_journal = str((tmp_path / "new-journal").resolve()) 286 + make_managed_wrapper(home_root, journal=old_journal, sol_bin=str(target)) 287 + monkeypatch.setenv("SOLSTONE_JOURNAL", new_journal) 288 + 289 + rc = install_guard.cmd_check(repo) 290 + captured = capsys.readouterr() 291 + 292 + assert rc == 0 293 + assert captured.out == "upgrade\n" 294 + assert captured.err == "" 295 + 139 296 def test_cross_repo(self, home_root, tmp_path, capsys): 140 297 repo = make_repo(tmp_path).resolve() 141 298 target = other_target(tmp_path).resolve() 142 299 make_alias(home_root, target) 300 + 143 301 rc = install_guard.cmd_check(repo) 144 302 captured = capsys.readouterr() 303 + 145 304 assert rc == 1 146 305 assert captured.out == "cross_repo\n" 147 - assert captured.err == alias_error(repo, f" installed: {target}") 306 + assert captured.err == alias_error( 307 + repo, 308 + f" installed: {target}", 309 + allow_force=True, 310 + ) 148 311 149 312 def test_dangling(self, home_root, tmp_path, capsys): 150 313 repo = make_repo(tmp_path).resolve() 151 314 target = (tmp_path / "missing" / ".venv" / "bin" / "sol").resolve() 152 315 make_alias(home_root, target) 316 + 153 317 rc = install_guard.cmd_check(repo) 154 318 captured = capsys.readouterr() 319 + 155 320 assert rc == 1 156 321 assert captured.out == "dangling\n" 157 322 assert captured.err == alias_error( 158 - repo, f" installed: dangling: {target} does not exist" 323 + repo, 324 + f" installed: dangling: {target} does not exist", 325 + allow_force=True, 159 326 ) 160 327 161 - def test_not_symlink(self, home_root, tmp_path, capsys): 328 + def test_foreign(self, home_root, tmp_path, capsys): 162 329 repo = make_repo(tmp_path).resolve() 163 330 alias = install_guard.alias_path() 164 331 alias.parent.mkdir(parents=True, exist_ok=True) 165 - alias.write_text("not a symlink") 332 + alias.write_text("not a wrapper", encoding="utf-8") 333 + 166 334 rc = install_guard.cmd_check(repo) 167 335 captured = capsys.readouterr() 336 + 168 337 assert rc == 1 169 338 assert captured.out == "not_symlink\n" 170 - assert captured.err == alias_error(repo, " installed: not a symlink") 339 + assert captured.err == alias_error( 340 + repo, 341 + " installed: not a symlink", 342 + allow_force=True, 343 + ) 171 344 172 345 173 346 class TestInstall: ··· 178 351 lambda _path: True, 179 352 ) 180 353 181 - def test_creates_symlink_on_absent(self, home_root, tmp_path, monkeypatch, capsys): 354 + def test_install_upgrades_legacy_symlink_to_managed_wrapper( 355 + self, home_root, tmp_path, monkeypatch, capsys 356 + ): 182 357 repo = make_repo(tmp_path) 358 + target = ensure_expected_target(repo) 359 + alias = make_alias(home_root, target) 360 + 183 361 rc, out, err = run_main(monkeypatch, capsys, repo, "install") 184 - alias = install_guard.alias_path() 362 + 185 363 assert rc == 0 186 364 assert out == "installed\npath: ~/.local/bin already on PATH\n" 187 365 assert err == "" 188 - assert alias.is_symlink() 189 - assert alias.resolve() == install_guard.expected_target(repo).resolve() 366 + assert not alias.is_symlink() 367 + assert os.access(alias, os.X_OK) 368 + assert alias.read_text(encoding="utf-8") == install_guard.render_wrapper( 369 + install_guard._current_journal_for_alias(), 370 + str(target), 371 + ) 372 + assert install_guard.parse_wrapper(alias.read_text(encoding="utf-8")) == { 373 + "journal": install_guard._current_journal_for_alias(), 374 + "sol_bin": str(target), 375 + } 190 376 191 - def test_rewrites_owned_symlink(self, home_root, tmp_path, monkeypatch, capsys): 192 - repo = make_repo(tmp_path) 193 - original = ensure_expected_target(repo) 194 - alias = make_alias(home_root, original) 377 + def test_install_refuses_foreign_regular_file_without_force( 378 + self, home_root, tmp_path, monkeypatch, capsys 379 + ): 380 + repo = make_repo(tmp_path).resolve() 381 + alias = install_guard.alias_path() 382 + alias.parent.mkdir(parents=True, exist_ok=True) 383 + alias.write_text("foreign", encoding="utf-8") 384 + 195 385 rc, out, err = run_main(monkeypatch, capsys, repo, "install") 196 - assert rc == 0 197 - assert out == "installed\npath: ~/.local/bin already on PATH\n" 198 - assert err == "" 199 - assert alias.is_symlink() 200 - assert alias.resolve() == original.resolve() 201 386 202 - def test_path_already_on_path_absent( 387 + assert rc == 1 388 + assert out == "" 389 + assert err == alias_error( 390 + repo, 391 + " installed: not a symlink", 392 + allow_force=True, 393 + ) 394 + assert alias.read_text(encoding="utf-8") == "foreign" 395 + 396 + def test_install_force_overwrites_foreign_regular_file( 203 397 self, home_root, tmp_path, monkeypatch, capsys 204 398 ): 205 399 repo = make_repo(tmp_path) 206 - append_mock = Mock(return_value=True) 207 - monkeypatch.setattr("think.install_guard.userpath.append", append_mock) 208 - rc, out, err = run_main(monkeypatch, capsys, repo, "install") 400 + target = ensure_expected_target(repo) 209 401 alias = install_guard.alias_path() 402 + alias.parent.mkdir(parents=True, exist_ok=True) 403 + alias.write_text("foreign", encoding="utf-8") 404 + 405 + rc, out, err = run_main(monkeypatch, capsys, repo, "install", "--force") 406 + 210 407 assert rc == 0 211 - assert out.endswith("path: ~/.local/bin already on PATH\n") 408 + assert out == "installed\npath: ~/.local/bin already on PATH\n" 212 409 assert err == "" 213 - assert alias.is_symlink() 214 - assert alias.resolve() == install_guard.expected_target(repo).resolve() 215 - append_mock.assert_not_called() 410 + assert install_guard.parse_wrapper(alias.read_text(encoding="utf-8")) == { 411 + "journal": install_guard._current_journal_for_alias(), 412 + "sol_bin": str(target), 413 + } 414 + assert os.access(alias, os.X_OK) 216 415 217 - def test_path_appended_restart_needed_absent( 416 + def test_install_is_idempotent(self, home_root, tmp_path, monkeypatch, capsys): 417 + repo = make_repo(tmp_path) 418 + target = ensure_expected_target(repo) 419 + 420 + rc1, out1, err1 = run_main(monkeypatch, capsys, repo, "install") 421 + alias = install_guard.alias_path() 422 + first_content = alias.read_text(encoding="utf-8") 423 + 424 + rc2, out2, err2 = run_main(monkeypatch, capsys, repo, "install") 425 + 426 + assert rc1 == 0 427 + assert out1 == "installed\npath: ~/.local/bin already on PATH\n" 428 + assert err1 == "" 429 + assert rc2 == 0 430 + assert out2 == "installed\npath: ~/.local/bin already on PATH\n" 431 + assert err2 == "" 432 + assert alias.read_text(encoding="utf-8") == first_content 433 + assert install_guard.parse_wrapper(first_content) == { 434 + "journal": install_guard._current_journal_for_alias(), 435 + "sol_bin": str(target), 436 + } 437 + 438 + def test_install_refuses_invalid_journal_path( 218 439 self, home_root, tmp_path, monkeypatch, capsys 219 440 ): 220 441 repo = make_repo(tmp_path) 221 - append_mock = Mock(return_value=True) 222 - restart_mock = Mock(return_value=True) 223 - monkeypatch.setattr( 224 - "think.install_guard.userpath.in_current_path", 225 - lambda _path: False, 226 - ) 227 - monkeypatch.setattr("think.install_guard.userpath.append", append_mock) 228 - monkeypatch.setattr( 229 - "think.install_guard.userpath.need_shell_restart", 230 - restart_mock, 231 - ) 442 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/tmp/bad$path") 443 + 232 444 rc, out, err = run_main(monkeypatch, capsys, repo, "install") 233 - alias = install_guard.alias_path() 234 - assert rc == 0 235 - assert ( 236 - out == "installed\n" 237 - "path: added ~/.local/bin to shell PATH — restart your shell or run 'exec $SHELL -l' to pick it up\n" 238 - ) 239 - assert err == "" 240 - assert alias.is_symlink() 241 - assert alias.resolve() == install_guard.expected_target(repo).resolve() 242 - append_mock.assert_called_once_with( 243 - str(alias.parent), 244 - app_name="solstone", 245 - all_shells=True, 246 - ) 247 - restart_mock.assert_called_once_with(str(alias.parent)) 445 + 446 + assert rc == 1 447 + assert out == "" 448 + assert "refused: journal path contains shell-active character '$'" in err 449 + assert not install_guard.alias_path().exists() 248 450 249 - def test_path_appended_no_restart_owned( 451 + def test_path_appended_when_not_on_path( 250 452 self, home_root, tmp_path, monkeypatch, capsys 251 453 ): 252 454 repo = make_repo(tmp_path) 253 - alias = make_alias(home_root, ensure_expected_target(repo)) 455 + target = ensure_expected_target(repo) 254 456 append_mock = Mock(return_value=True) 255 457 restart_mock = Mock(return_value=False) 256 458 monkeypatch.setattr( ··· 262 464 "think.install_guard.userpath.need_shell_restart", 263 465 restart_mock, 264 466 ) 467 + 265 468 rc, out, err = run_main(monkeypatch, capsys, repo, "install") 469 + alias = install_guard.alias_path() 470 + 266 471 assert rc == 0 267 472 assert out == "installed\npath: added ~/.local/bin to shell PATH\n" 268 473 assert err == "" 269 - assert alias.is_symlink() 270 - assert alias.resolve() == install_guard.expected_target(repo).resolve() 474 + assert install_guard.parse_wrapper(alias.read_text(encoding="utf-8")) == { 475 + "journal": install_guard._current_journal_for_alias(), 476 + "sol_bin": str(target), 477 + } 271 478 append_mock.assert_called_once_with( 272 479 str(alias.parent), 273 480 app_name="solstone", ··· 275 482 ) 276 483 restart_mock.assert_called_once_with(str(alias.parent)) 277 484 278 - def test_path_append_returns_false(self, home_root, tmp_path, monkeypatch, capsys): 485 + 486 + class TestUninstall: 487 + def test_uninstall_removes_managed_wrapper( 488 + self, home_root, tmp_path, monkeypatch, capsys 489 + ): 279 490 repo = make_repo(tmp_path) 280 - append_mock = Mock(return_value=False) 281 - monkeypatch.setattr( 282 - "think.install_guard.userpath.in_current_path", 283 - lambda _path: False, 491 + target = ensure_expected_target(repo) 492 + alias = make_managed_wrapper( 493 + home_root, 494 + journal=install_guard._current_journal_for_alias(), 495 + sol_bin=str(target), 284 496 ) 285 - monkeypatch.setattr("think.install_guard.userpath.append", append_mock) 286 - rc, out, err = run_main(monkeypatch, capsys, repo, "install") 287 - alias = install_guard.alias_path() 288 - assert rc == 0 289 - assert ( 290 - out 291 - == 'installed\npath: could not auto-add ~/.local/bin to PATH — add this line to your shell rc manually: export PATH="$HOME/.local/bin:$PATH"\n' 292 - ) 293 - assert err == "" 294 - assert alias.is_symlink() 295 - assert alias.resolve() == install_guard.expected_target(repo).resolve() 296 - append_mock.assert_called_once_with( 297 - str(alias.parent), 298 - app_name="solstone", 299 - all_shells=True, 300 - ) 497 + 498 + rc, out, err = run_main(monkeypatch, capsys, repo, "uninstall") 301 499 302 - def test_path_unexpected_exception(self, home_root, tmp_path, monkeypatch, capsys): 303 - repo = make_repo(tmp_path) 304 - append_mock = Mock(return_value=True) 305 - monkeypatch.setattr( 306 - "think.install_guard.userpath.in_current_path", 307 - Mock(side_effect=RuntimeError("boom")), 308 - ) 309 - monkeypatch.setattr("think.install_guard.userpath.append", append_mock) 310 - rc, out, err = run_main(monkeypatch, capsys, repo, "install") 311 - alias = install_guard.alias_path() 312 500 assert rc == 0 313 - assert ( 314 - out 315 - == 'installed\npath: could not auto-add ~/.local/bin to PATH (RuntimeError: boom) — add this line to your shell rc manually: export PATH="$HOME/.local/bin:$PATH"\n' 316 - ) 501 + assert out == "uninstalled\n" 317 502 assert err == "" 318 - assert alias.is_symlink() 319 - assert alias.resolve() == install_guard.expected_target(repo).resolve() 320 - append_mock.assert_not_called() 321 - 322 - def test_refuses_cross_repo(self, home_root, tmp_path, monkeypatch, capsys): 323 - repo = make_repo(tmp_path).resolve() 324 - target = other_target(tmp_path).resolve() 325 - alias = make_alias(home_root, target) 326 - rc, out, err = run_main(monkeypatch, capsys, repo, "install") 327 - assert rc == 1 328 - assert out == "" 329 - assert err == alias_error(repo, f" installed: {target}") 330 - assert alias.is_symlink() 331 - assert alias.resolve() == target 332 - 333 - def test_refuses_dangling(self, home_root, tmp_path, monkeypatch, capsys): 334 - repo = make_repo(tmp_path).resolve() 335 - target = (tmp_path / "missing" / ".venv" / "bin" / "sol").resolve() 336 - alias = make_alias(home_root, target) 337 - rc, out, err = run_main(monkeypatch, capsys, repo, "install") 338 - assert rc == 1 339 - assert out == "" 340 - assert err == alias_error( 341 - repo, f" installed: dangling: {target} does not exist" 342 - ) 343 - assert alias.is_symlink() 344 - assert Path(os.readlink(alias)).name == "sol" 345 - 346 - def test_refuses_not_symlink(self, home_root, tmp_path, monkeypatch, capsys): 347 - repo = make_repo(tmp_path).resolve() 348 - alias = install_guard.alias_path() 349 - alias.parent.mkdir(parents=True, exist_ok=True) 350 - alias.write_text("not a symlink") 351 - rc, out, err = run_main(monkeypatch, capsys, repo, "install") 352 - assert rc == 1 353 - assert out == "" 354 - assert err == alias_error(repo, " installed: not a symlink") 355 - assert alias.read_text() == "not a symlink" 503 + assert not alias.exists() 504 + assert not alias.is_symlink() 356 505 357 - def test_refuses_worktree(self, home_root, tmp_path, monkeypatch, capsys): 358 - repo = make_repo(tmp_path, worktree=True).resolve() 359 - rc, out, err = run_main(monkeypatch, capsys, repo, "install") 360 - assert rc == 1 361 - assert out == "" 362 - assert err == worktree_error(repo) 363 - assert not install_guard.alias_path().exists() 364 - 365 - 366 - class TestUninstall: 367 - def test_removes_owned_alias(self, home_root, tmp_path, monkeypatch, capsys): 506 + def test_uninstall_removes_legacy_symlink( 507 + self, home_root, tmp_path, monkeypatch, capsys 508 + ): 368 509 repo = make_repo(tmp_path) 369 510 target = ensure_expected_target(repo) 370 511 alias = make_alias(home_root, target) 512 + 371 513 rc, out, err = run_main(monkeypatch, capsys, repo, "uninstall") 514 + 372 515 assert rc == 0 373 - assert out == "removed\n" 516 + assert out == "uninstalled\n" 374 517 assert err == "" 375 518 assert not alias.exists() 376 519 assert not alias.is_symlink() 377 520 378 521 def test_noop_on_absent(self, home_root, tmp_path, monkeypatch, capsys): 379 522 repo = make_repo(tmp_path) 523 + 380 524 rc, out, err = run_main(monkeypatch, capsys, repo, "uninstall") 525 + 381 526 assert rc == 0 382 527 assert out == "absent\n" 383 528 assert err == "" 384 529 assert not install_guard.alias_path().exists() 385 530 386 - def test_refuses_cross_repo(self, home_root, tmp_path, monkeypatch, capsys): 531 + def test_refuses_foreign(self, home_root, tmp_path, monkeypatch, capsys): 387 532 repo = make_repo(tmp_path).resolve() 388 - target = other_target(tmp_path).resolve() 389 - alias = make_alias(home_root, target) 390 - rc, out, err = run_main(monkeypatch, capsys, repo, "uninstall") 391 - assert rc == 1 392 - assert out == "" 393 - assert err == alias_error(repo, f" installed: {target}") 394 - assert alias.is_symlink() 395 - assert alias.resolve() == target 533 + alias = install_guard.alias_path() 534 + alias.parent.mkdir(parents=True, exist_ok=True) 535 + alias.write_text("foreign", encoding="utf-8") 396 536 397 - def test_refuses_dangling(self, home_root, tmp_path, monkeypatch, capsys): 398 - repo = make_repo(tmp_path).resolve() 399 - target = (tmp_path / "missing" / ".venv" / "bin" / "sol").resolve() 400 - alias = make_alias(home_root, target) 401 537 rc, out, err = run_main(monkeypatch, capsys, repo, "uninstall") 402 - assert rc == 1 403 - assert out == "" 404 - assert err == alias_error( 405 - repo, f" installed: dangling: {target} does not exist" 406 - ) 407 - assert alias.is_symlink() 408 538 409 - def test_refuses_not_symlink(self, home_root, tmp_path, monkeypatch, capsys): 410 - repo = make_repo(tmp_path).resolve() 411 - alias = install_guard.alias_path() 412 - alias.parent.mkdir(parents=True, exist_ok=True) 413 - alias.write_text("not a symlink") 414 - rc, out, err = run_main(monkeypatch, capsys, repo, "uninstall") 415 539 assert rc == 1 416 540 assert out == "" 417 541 assert err == alias_error(repo, " installed: not a symlink") 418 - assert alias.read_text() == "not a symlink" 542 + assert alias.read_text(encoding="utf-8") == "foreign" 419 543 420 544 def test_refuses_worktree(self, home_root, tmp_path, monkeypatch, capsys): 421 545 repo = make_repo(tmp_path, worktree=True).resolve() 546 + 422 547 rc, out, err = run_main(monkeypatch, capsys, repo, "uninstall") 548 + 423 549 assert rc == 1 424 550 assert out == "" 425 551 assert err == worktree_error(repo)
+37 -37
tests/test_journal_index.py
··· 278 278 def journal_fixture(tmp_path): 279 279 """Create a temporary journal with test data.""" 280 280 journal = tmp_path 281 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 281 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 282 282 283 283 # Create daily insight 284 284 day = journal / "chronicle" / "20240101" ··· 697 697 from think.tools.search import search_journal 698 698 699 699 # Use fixtures journal 700 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 700 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 701 701 702 702 result = search_journal("test") 703 703 ··· 718 718 """Test search tool returns query echo.""" 719 719 from think.tools.search import search_journal 720 720 721 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 721 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 722 722 723 723 result = search_journal("test query", facet="work", agent="flow") 724 724 ··· 732 732 """Test search tool results include path and idx.""" 733 733 from think.tools.search import search_journal 734 734 735 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 735 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 736 736 737 737 result = search_journal("") 738 738 ··· 748 748 749 749 from think.tools.search import _MAX_RESULT_TEXT, search_journal 750 750 751 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 751 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 752 752 753 753 big_text = "x" * 10_000 754 754 fake_results = [ ··· 849 849 from think.indexer.journal import scan_journal, search_journal 850 850 851 851 journal = tmp_path 852 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 852 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 853 853 854 854 # Create content for today (which is in light scan scope) 855 855 today = datetime.now().strftime("%Y%m%d") ··· 884 884 from think.indexer.journal import scan_journal, search_journal 885 885 886 886 journal = tmp_path 887 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 887 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 888 888 889 889 # Create historical day content 890 890 day_dir = journal / "chronicle" / "20200101" ··· 919 919 from think.indexer.journal import scan_journal, search_journal 920 920 921 921 journal = tmp_path 922 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 922 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 923 923 924 924 # Create historical day content 925 925 day_dir = journal / "chronicle" / "20200101" ··· 1096 1096 """search_journal filters by stream name.""" 1097 1097 from think.indexer.journal import scan_journal, search_journal 1098 1098 1099 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1100 - scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 1099 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1100 + scan_journal(os.environ["SOLSTONE_JOURNAL"], full=True) 1101 1101 1102 1102 # Search with matching stream 1103 1103 total, results = search_journal("", stream="default") ··· 1114 1114 """search_journal results include stream in metadata.""" 1115 1115 from think.indexer.journal import scan_journal, search_journal 1116 1116 1117 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1118 - scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 1117 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1118 + scan_journal(os.environ["SOLSTONE_JOURNAL"], full=True) 1119 1119 1120 1120 # Filter to segment content which has stream markers 1121 1121 total, results = search_journal("", stream="default") ··· 1130 1130 """search_counts filters by stream and includes streams aggregation.""" 1131 1131 from think.indexer.journal import scan_journal, search_counts 1132 1132 1133 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1134 - scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 1133 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1134 + scan_journal(os.environ["SOLSTONE_JOURNAL"], full=True) 1135 1135 1136 1136 # Unfiltered counts should include streams 1137 1137 counts = search_counts("") ··· 1151 1151 from think.indexer.journal import scan_journal 1152 1152 from think.tools.search import search_journal 1153 1153 1154 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1155 - scan_journal(os.environ["_SOLSTONE_JOURNAL_OVERRIDE"], full=True) 1154 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1155 + scan_journal(os.environ["SOLSTONE_JOURNAL"], full=True) 1156 1156 1157 1157 result = search_journal("", stream="default") 1158 1158 assert "results" in result ··· 1162 1162 1163 1163 def test_entity_schema_creation(): 1164 1164 """Verify entities table exists after schema init.""" 1165 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1165 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1166 1166 conn, _ = get_journal_index() 1167 1167 1168 1168 tables = conn.execute( ··· 1176 1176 """Verify journal entity identity rows are indexed.""" 1177 1177 from think.indexer.journal import scan_journal 1178 1178 1179 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1179 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1180 1180 scan_journal("tests/fixtures/journal", full=True) 1181 1181 1182 1182 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1189 1189 """Verify facet relationship rows are indexed.""" 1190 1190 from think.indexer.journal import scan_journal 1191 1191 1192 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1192 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1193 1193 scan_journal("tests/fixtures/journal", full=True) 1194 1194 1195 1195 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1202 1202 """Verify detected entity rows are indexed.""" 1203 1203 from think.indexer.journal import scan_journal 1204 1204 1205 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1205 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1206 1206 scan_journal("tests/fixtures/journal", full=True) 1207 1207 1208 1208 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1215 1215 """Verify observation summary rows are indexed.""" 1216 1216 from think.indexer.journal import scan_journal 1217 1217 1218 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1218 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1219 1219 scan_journal("tests/fixtures/journal", full=True) 1220 1220 1221 1221 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1234 1234 """Verify second scan is a no-op.""" 1235 1235 from think.indexer.journal import scan_journal 1236 1236 1237 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1237 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1238 1238 scan_journal("tests/fixtures/journal", full=True) 1239 1239 1240 1240 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1254 1254 dst = tmp_path / "journal" 1255 1255 copytree_tracked(src, dst) 1256 1256 j = str(dst) 1257 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", j) 1257 + monkeypatch.setenv("SOLSTONE_JOURNAL", j) 1258 1258 1259 1259 from think.indexer.journal import scan_journal 1260 1260 ··· 1283 1283 """Verify FTS5 chunks still work after entity scan.""" 1284 1284 from think.indexer.journal import scan_journal 1285 1285 1286 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1286 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1287 1287 scan_journal("tests/fixtures/journal", full=True) 1288 1288 total, results = search_journal("Alice", limit=5) 1289 1289 assert isinstance(total, int) ··· 1291 1291 1292 1292 def test_signal_schema_creation(): 1293 1293 """Verify entity_signals table exists after schema init.""" 1294 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1294 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1295 1295 conn, _ = get_journal_index() 1296 1296 1297 1297 tables = conn.execute( ··· 1305 1305 """Verify KG appearance signals are extracted.""" 1306 1306 from think.indexer.journal import scan_journal 1307 1307 1308 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1308 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1309 1309 scan_journal("tests/fixtures/journal", full=True) 1310 1310 1311 1311 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1335 1335 """Verify KG edge signals are extracted.""" 1336 1336 from think.indexer.journal import scan_journal 1337 1337 1338 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1338 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1339 1339 scan_journal("tests/fixtures/journal", full=True) 1340 1340 1341 1341 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1358 1358 """Verify event participant signals are extracted.""" 1359 1359 from think.indexer.journal import scan_journal 1360 1360 1361 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1361 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1362 1362 scan_journal("tests/fixtures/journal", full=True) 1363 1363 1364 1364 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1382 1382 """Verify second scan is a no-op.""" 1383 1383 from think.indexer.journal import scan_journal 1384 1384 1385 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1385 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1386 1386 scan_journal("tests/fixtures/journal", full=True) 1387 1387 1388 1388 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1430 1430 """Verify KG signals get facet assigned from detection data.""" 1431 1431 from think.indexer.journal import scan_journal 1432 1432 1433 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1433 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1434 1434 scan_journal("tests/fixtures/journal", full=True) 1435 1435 1436 1436 conn, _ = get_journal_index("tests/fixtures/journal") ··· 1491 1491 """Entity search chunks are generated from identity + relationship data.""" 1492 1492 from think.indexer.journal import scan_journal 1493 1493 1494 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1494 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1495 1495 scan_journal("tests/fixtures/journal", full=True) 1496 1496 conn, _ = get_journal_index("tests/fixtures/journal") 1497 1497 count = conn.execute("SELECT count(*) FROM chunks WHERE agent='entity'").fetchone()[ ··· 1506 1506 """Entity search chunks use entity_search: path prefix.""" 1507 1507 from think.indexer.journal import scan_journal 1508 1508 1509 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1509 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1510 1510 scan_journal("tests/fixtures/journal", full=True) 1511 1511 conn, _ = get_journal_index("tests/fixtures/journal") 1512 1512 rows = conn.execute( ··· 1520 1520 """Entity name is searchable via FTS.""" 1521 1521 from think.indexer.journal import scan_journal 1522 1522 1523 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1523 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1524 1524 scan_journal("tests/fixtures/journal", full=True) 1525 1525 total, results = search_journal("Alice Johnson", agent="entity") 1526 1526 assert total >= 1 ··· 1531 1531 """Entity type is searchable via FTS.""" 1532 1532 from think.indexer.journal import scan_journal 1533 1533 1534 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1534 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1535 1535 scan_journal("tests/fixtures/journal", full=True) 1536 1536 total, results = search_journal("Person", agent="entity") 1537 1537 assert total >= 1 ··· 1541 1541 """Entity search chunks include relationship descriptions.""" 1542 1542 from think.indexer.journal import scan_journal 1543 1543 1544 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1544 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1545 1545 scan_journal("tests/fixtures/journal", full=True) 1546 1546 # Alice has description "Close friend from college" in personal facet 1547 1547 total, results = search_journal("college", agent="entity") ··· 1554 1554 """Entity search chunks have facet metadata from relationships.""" 1555 1555 from think.indexer.journal import scan_journal 1556 1556 1557 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1557 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1558 1558 scan_journal("tests/fixtures/journal", full=True) 1559 1559 total, results = search_journal("Alice Johnson", agent="entity", facet="personal") 1560 1560 assert total >= 1 ··· 1565 1565 """Two full scans produce identical entity chunk count (no duplicates).""" 1566 1566 from think.indexer.journal import scan_journal 1567 1567 1568 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = "tests/fixtures/journal" 1568 + os.environ["SOLSTONE_JOURNAL"] = "tests/fixtures/journal" 1569 1569 scan_journal("tests/fixtures/journal", full=True) 1570 1570 conn, _ = get_journal_index("tests/fixtures/journal") 1571 1571 count1 = conn.execute(
+1 -1
tests/test_journal_merge.py
··· 123 123 encoding="utf-8", 124 124 ) 125 125 126 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(target)) 126 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(target)) 127 127 import think.utils 128 128 129 129 think.utils._journal_path_cache = None
+5 -5
tests/test_journal_stats.py
··· 43 43 } 44 44 (activities_dir / "20240101.jsonl").write_text(json.dumps(activity) + "\n") 45 45 46 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 46 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 47 47 js = stats_mod.JournalStats() 48 48 day_data = js.scan_day("20240101", str(day)) 49 49 js._apply_day_stats("20240101", day_data) ··· 132 132 ) 133 133 (tokens_dir / "20240102.jsonl").write_text(json.dumps(token4) + "\n") 134 134 135 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 135 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 136 136 js = stats_mod.JournalStats() 137 137 js.scan(str(journal)) 138 138 ··· 191 191 '{"start": "10:01:00", "text": "world"}\n' 192 192 ) 193 193 194 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 194 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 195 195 196 196 # First scan - should create cache 197 197 js1 = stats_mod.JournalStats() ··· 245 245 # Write token as JSONL format 246 246 (tokens_dir / "20240101.jsonl").write_text(json.dumps(token_new) + "\n") 247 247 248 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 248 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 249 249 js = stats_mod.JournalStats() 250 250 js.scan(str(journal)) 251 251 ··· 308 308 + "\n" 309 309 ) 310 310 311 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 311 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 312 312 js = stats_mod.JournalStats() 313 313 js.scan(str(journal)) 314 314
+15 -15
tests/test_kindle_importer.py
··· 133 133 f.flush() 134 134 try: 135 135 with tempfile.TemporaryDirectory() as journal: 136 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 136 + os.environ["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE", None) 152 + os.environ.pop("SOLSTONE_JOURNAL", 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["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 174 + os.environ["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE", None) 182 + os.environ.pop("SOLSTONE_JOURNAL", 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["_SOLSTONE_JOURNAL_OVERRIDE"] = journal 200 + os.environ["SOLSTONE_JOURNAL"] = 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("_SOLSTONE_JOURNAL_OVERRIDE", None) 206 + os.environ.pop("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 215 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 230 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 271 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(second_tmp_path)) 285 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 305 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 321 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 344 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 384 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 411 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 151 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 167 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 190 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 219 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 242 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 261 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 281 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 300 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 317 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 340 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 365 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 386 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 13 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 23 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 33 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 48 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 49 49 day = None 50 50 if day: 51 51 health_dir = day_path(day) / "health"
+8 -8
tests/test_models.py
··· 136 136 @pytest.fixture 137 137 def use_fixtures_journal(monkeypatch): 138 138 """Use the fixtures journal for provider config tests.""" 139 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 139 + monkeypatch.setenv("SOLSTONE_JOURNAL", "tests/fixtures/journal") 140 140 141 141 142 142 def test_resolve_provider_default_generate(use_fixtures_journal): ··· 192 192 # Use a journal path with no config 193 193 empty_journal = tmp_path / "empty_journal" 194 194 empty_journal.mkdir() 195 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(empty_journal)) 195 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(empty_journal)) 196 196 197 197 provider, model = resolve_provider("anything", "generate") 198 198 assert provider == "google" ··· 394 394 } 395 395 } 396 396 (config_dir / "journal.json").write_text(json.dumps(config)) 397 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 397 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 398 398 399 399 # Invalid tier 99 should fall back to generate default tier (2) 400 400 provider, model = resolve_provider("test.invalid", "generate") ··· 637 637 638 638 from think.models import log_token_usage 639 639 640 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 640 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 641 641 642 642 # Codex CLI format: no total_tokens 643 643 log_token_usage( ··· 659 659 660 660 from think.models import log_token_usage 661 661 662 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 662 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 663 663 664 664 log_token_usage( 665 665 model="gpt-5.2", ··· 678 678 679 679 from think.models import log_token_usage 680 680 681 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 681 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 682 682 683 683 log_token_usage( 684 684 model="gpt-5.2", ··· 702 702 703 703 from think.models import log_token_usage 704 704 705 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 705 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 706 706 707 707 # Normalized usage from Google provider (the bug: reasoning_tokens were dropped) 708 708 log_token_usage( ··· 730 730 731 731 from think.models import log_token_usage 732 732 733 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 733 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 734 734 735 735 log_token_usage( 736 736 model="claude-sonnet-4-5",
+8 -8
tests/test_output_hooks.py
··· 24 24 25 25 26 26 def copy_day(tmp_path: Path) -> Path: 27 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 27 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 28 28 dest = day_path("20240101") 29 29 src = FIXTURES / "journal" / "chronicle" / "20240101" 30 30 copytree_tracked(src, dest) ··· 189 189 lambda *a, **k: MOCK_RESULT, 190 190 ) 191 191 monkeypatch.setenv("GOOGLE_API_KEY", "x") 192 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 192 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 193 193 194 194 config = { 195 195 "name": "hooked_test", ··· 239 239 lambda *a, **k: MOCK_RESULT, 240 240 ) 241 241 monkeypatch.setenv("GOOGLE_API_KEY", "x") 242 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 242 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 243 243 244 244 config = { 245 245 "name": "noop_test", ··· 285 285 lambda *a, **k: MOCK_RESULT, 286 286 ) 287 287 monkeypatch.setenv("GOOGLE_API_KEY", "x") 288 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 288 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 289 289 290 290 config = { 291 291 "name": "broken_test", ··· 410 410 411 411 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 412 412 monkeypatch.setenv("GOOGLE_API_KEY", "x") 413 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 413 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 414 414 415 415 config = { 416 416 "name": "prehooked_test", ··· 570 570 571 571 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 572 572 monkeypatch.setenv("GOOGLE_API_KEY", "x") 573 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 573 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 574 574 575 575 config = { 576 576 "name": "prehook_template_vars", ··· 624 624 625 625 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 626 626 monkeypatch.setenv("GOOGLE_API_KEY", "x") 627 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 627 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 628 628 629 629 config = { 630 630 "name": "prehook_template_with_mods", ··· 678 678 679 679 monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 680 680 monkeypatch.setenv("GOOGLE_API_KEY", "x") 681 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 681 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 682 682 683 683 config = { 684 684 "name": "both_hooks_test",
+2 -2
tests/test_output_path.py
··· 8 8 9 9 from think.talent import get_output_name, get_output_path 10 10 11 - os.environ.setdefault("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 11 + os.environ.setdefault("SOLSTONE_JOURNAL", "tests/fixtures/journal") 12 12 13 13 14 14 class TestGetOutputName: ··· 92 92 path = get_activity_output_path( 93 93 "work", "20260209", "coding_100000_300", "session_review" 94 94 ) 95 - journal = os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 95 + journal = os.environ["SOLSTONE_JOURNAL"] 96 96 expected = ( 97 97 Path(journal) 98 98 / "facets/work/activities/20260209/coding_100000_300/session_review.md"
+1 -1
tests/test_participation_resolver.py
··· 21 21 22 22 facet = "work" 23 23 day = "20260418" 24 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 24 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 25 25 26 26 _write_detected_entities( 27 27 tmp_path,
+2 -2
tests/test_password.py
··· 61 61 config["convey"].pop("password_hash", None) 62 62 config["convey"]["password"] = "migrate-me" 63 63 config_path.write_text(json.dumps(config, indent=2)) 64 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(dst)) 64 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(dst)) 65 65 66 66 create_app(str(dst)) 67 67 ··· 80 80 config["convey"].pop("password_hash", None) 81 81 config["convey"]["password"] = "" 82 82 config_path.write_text(json.dumps(config, indent=2)) 83 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(dst)) 83 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(dst)) 84 84 85 85 create_app(str(dst)) 86 86
+1 -1
tests/test_password_cli.py
··· 65 65 66 66 def test_no_config_file(self, tmp_path, monkeypatch, capsys): 67 67 """Works when no journal.json exists yet.""" 68 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 68 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 69 69 monkeypatch.setattr("sys.argv", ["sol password", "set"]) 70 70 _mock_getpass(monkeypatch, "freshpass", "freshpass") 71 71
+1 -1
tests/test_pipeline_health.py
··· 25 25 def pipeline_journal(tmp_path, monkeypatch): 26 26 journal = tmp_path / "journal" 27 27 journal.mkdir() 28 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 28 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 29 29 return journal 30 30 31 31
+1 -1
tests/test_pipeline_smoke.py
··· 167 167 class TestPipelineSmokeTest: 168 168 def test_full_pipeline_smoke(self, tmp_path: Path, monkeypatch): 169 169 journal = tmp_path / "journal" 170 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 170 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 171 171 172 172 state_machine = ActivityStateMachine() 173 173 activity_calls = []
+1 -1
tests/test_prompts_facet_integration.py
··· 76 76 for index in range(1, 26) 77 77 ], 78 78 ) 79 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 79 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 80 80 81 81 resolved = _resolve_facets(None) 82 82
+1 -1
tests/test_providers_check.py
··· 11 11 12 12 13 13 def test_run_check_writes_health_file(tmp_path, monkeypatch): 14 - """_run_check writes provider health results to _SOLSTONE_JOURNAL_OVERRIDE/health/talents.json.""" 14 + """_run_check writes provider health results to SOLSTONE_JOURNAL/health/talents.json.""" 15 15 import think.providers_cli as providers_cli 16 16 17 17 fake_registry = {"fake": object()}
+7 -7
tests/test_push_config.py
··· 18 18 19 19 20 20 def test_push_config_defaults(monkeypatch, tmp_path): 21 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 21 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 22 22 _write_config(tmp_path, {"agent": {"name": "sol"}}) 23 23 24 24 assert config.get_apns_key_path() is None ··· 30 30 31 31 32 32 def test_push_config_reads_journal_values(monkeypatch, tmp_path): 33 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 33 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 34 34 key_path = tmp_path / "keys" / "apns.p8" 35 35 key_path.parent.mkdir(parents=True, exist_ok=True) 36 36 key_path.write_text("PRIVATE KEY", encoding="utf-8") ··· 56 56 57 57 58 58 def test_push_config_blank_values_normalize_to_none(monkeypatch, tmp_path): 59 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 59 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 60 60 _write_config( 61 61 tmp_path, 62 62 { ··· 79 79 80 80 81 81 def test_push_config_invalid_environment_raises(monkeypatch, tmp_path): 82 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 82 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 83 83 _write_config(tmp_path, {"push": {"environment": "staging"}}) 84 84 85 85 with pytest.raises( ··· 91 91 92 92 93 93 def test_push_config_missing_key_file_is_unconfigured(monkeypatch, tmp_path): 94 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 94 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 95 95 _write_config( 96 96 tmp_path, 97 97 { ··· 109 109 110 110 111 111 def test_push_config_relative_key_path_is_unconfigured(monkeypatch, tmp_path): 112 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 112 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 113 113 relative_key_path = Path("keys/apns.p8") 114 114 _write_config( 115 115 tmp_path, ··· 129 129 130 130 131 131 def test_push_config_ignores_env_fallback(monkeypatch, tmp_path): 132 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 132 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 133 133 monkeypatch.setenv("OPENAI_API_KEY", "sk-env") 134 134 monkeypatch.setenv("APNS_KEY_ID", "ENVKEY") 135 135 _write_config(tmp_path, {"push": {}})
+6 -6
tests/test_push_devices.py
··· 14 14 15 15 16 16 def test_load_devices_returns_empty_for_missing_store(monkeypatch, tmp_path): 17 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 17 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 18 18 19 19 assert devices.load_devices() == [] 20 20 21 21 22 22 def test_register_load_remove_round_trip(monkeypatch, tmp_path): 23 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 23 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 24 24 25 25 count = devices.register_device( 26 26 token="a" * 64, ··· 47 47 48 48 49 49 def test_register_device_updates_existing_token(monkeypatch, tmp_path): 50 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 50 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 51 51 times = iter([1000, 2000]) 52 52 monkeypatch.setattr(devices.time, "time", lambda: next(times)) 53 53 ··· 78 78 79 79 80 80 def test_remove_device_returns_false_for_unknown_token(monkeypatch, tmp_path): 81 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 81 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 82 82 devices.register_device( 83 83 token="c" * 64, 84 84 bundle_id="org.solpbc.solstone-swift", ··· 91 91 92 92 93 93 def test_load_devices_returns_empty_for_malformed_store(monkeypatch, tmp_path, caplog): 94 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 94 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 95 95 path = _devices_path(tmp_path) 96 96 path.parent.mkdir(parents=True, exist_ok=True) 97 97 path.write_text('{"devices": "bad"}', encoding="utf-8") ··· 103 103 104 104 105 105 def test_status_view_masks_token(monkeypatch, tmp_path): 106 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 106 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 107 107 path = _devices_path(tmp_path) 108 108 path.parent.mkdir(parents=True, exist_ok=True) 109 109 path.write_text(
+12 -12
tests/test_push_triggers.py
··· 15 15 16 16 17 17 def test_handle_briefing_finish_polls_until_briefing_exists(monkeypatch, tmp_path): 18 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 18 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 19 19 responses = iter( 20 20 [ 21 21 ({}, None, []), ··· 53 53 54 54 55 55 def test_handle_briefing_finish_is_idempotent(monkeypatch, tmp_path): 56 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 56 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 57 57 sent_calls: list[str] = [] 58 58 monkeypatch.setattr( 59 59 triggers, ··· 82 82 83 83 84 84 def test_check_pre_meeting_prep_skips_muted_facets(monkeypatch, tmp_path): 85 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 85 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 86 86 monkeypatch.setattr(triggers, "_eligible_devices", lambda: [{"token": "a" * 64}]) 87 87 monkeypatch.setattr(triggers, "get_enabled_facets", lambda: {}) 88 88 sent_calls: list[str] = [] ··· 100 100 101 101 102 102 def test_check_pre_meeting_prep_skips_non_anticipated(monkeypatch, tmp_path): 103 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 103 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 104 104 monkeypatch.setattr(triggers, "_eligible_devices", lambda: [{"token": "a" * 64}]) 105 105 monkeypatch.setattr(triggers, "get_enabled_facets", lambda: {"work": {}}) 106 106 monkeypatch.setattr( ··· 123 123 124 124 125 125 def test_check_pre_meeting_prep_fires_for_hhmm_and_hhmmss(monkeypatch, tmp_path): 126 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 126 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 127 127 monkeypatch.setattr(triggers, "_eligible_devices", lambda: [{"token": "a" * 64}]) 128 128 monkeypatch.setattr(triggers, "get_enabled_facets", lambda: {"work": {}}) 129 129 monkeypatch.setattr( ··· 162 162 163 163 164 164 def test_check_pre_meeting_prep_zero_devices_skips_log(monkeypatch, tmp_path): 165 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 165 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 166 166 monkeypatch.setattr(triggers, "_eligible_devices", lambda: []) 167 167 monkeypatch.setattr(triggers, "get_enabled_facets", lambda: {"work": {}}) 168 168 monkeypatch.setattr( ··· 184 184 185 185 186 186 def test_send_agent_alert_same_context_id_fires_once(monkeypatch, tmp_path): 187 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 187 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 188 188 monkeypatch.setattr(triggers, "_eligible_devices", lambda: [{"token": "a" * 64}]) 189 189 sent_calls: list[dict[str, object]] = [] 190 190 monkeypatch.setattr( ··· 228 228 229 229 230 230 def test_send_agent_alert_forwards_route(monkeypatch, tmp_path): 231 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 231 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 232 232 monkeypatch.setattr(triggers, "_eligible_devices", lambda: [{"token": "a" * 64}]) 233 233 payloads: list[dict[str, object]] = [] 234 234 monkeypatch.setattr( ··· 251 251 def test_handle_weekly_reflection_finish_sends_once_and_appends_chat_event( 252 252 monkeypatch, tmp_path 253 253 ): 254 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 254 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 255 255 reflection_path = tmp_path / "reflections" / "weekly" / "20260308.md" 256 256 reflection_path.parent.mkdir(parents=True, exist_ok=True) 257 257 reflection_path.write_text("# reflection\n", encoding="utf-8") ··· 320 320 def test_handle_weekly_reflection_finish_ignores_unrelated_events( 321 321 monkeypatch, tmp_path 322 322 ): 323 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 323 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 324 324 send_calls: list[tuple] = [] 325 325 monkeypatch.setattr( 326 326 triggers, ··· 364 364 def test_handle_weekly_reflection_finish_skips_when_file_never_appears( 365 365 monkeypatch, tmp_path 366 366 ): 367 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 367 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 368 368 sleeps: list[int] = [] 369 369 send_calls: list[dict[str, object]] = [] 370 370 chat_events: list[dict[str, object]] = [] ··· 397 397 def test_handle_weekly_reflection_finish_dedupes_chat_event_without_devices( 398 398 monkeypatch, tmp_path 399 399 ): 400 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 400 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 401 401 reflection_path = tmp_path / "reflections" / "weekly" / "20260308.md" 402 402 reflection_path.parent.mkdir(parents=True, exist_ok=True) 403 403 reflection_path.write_text("# reflection\n", encoding="utf-8")
+1 -1
tests/test_retention.py
··· 309 309 (day3 / "audio.flac").write_bytes(b"x" * 600) 310 310 (day3 / "stream.json").write_text('{"stream":"default"}') 311 311 312 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 312 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 313 313 # Clear cached journal path 314 314 import think.utils 315 315
+1 -1
tests/test_retention_config_cli.py
··· 14 14 15 15 @pytest.fixture 16 16 def journal_env(tmp_path, monkeypatch): 17 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 17 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 18 18 return tmp_path 19 19 20 20
+1 -1
tests/test_routines.py
··· 90 90 @pytest.fixture 91 91 def journal_path(tmp_path, monkeypatch): 92 92 """Create a temp journal with routines/ and health/ dirs.""" 93 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 93 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 94 94 (tmp_path / "routines").mkdir() 95 95 (tmp_path / "health").mkdir() 96 96 return tmp_path
+3 -3
tests/test_runner.py
··· 16 16 """Set up a temporary journal path.""" 17 17 journal = tmp_path / "journal" 18 18 journal.mkdir() 19 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal) 19 + os.environ["SOLSTONE_JOURNAL"] = str(journal) 20 20 yield journal 21 21 # Cleanup 22 - if "_SOLSTONE_JOURNAL_OVERRIDE" in os.environ: 23 - del os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] 22 + if "SOLSTONE_JOURNAL" in os.environ: 23 + del os.environ["SOLSTONE_JOURNAL"] 24 24 25 25 26 26 def test_managed_process_has_ref_and_pid(journal_path, mock_callosum):
+9 -9
tests/test_schedule_hook.py
··· 38 38 from talent.schedule import post_process 39 39 from think.activities import load_activity_records 40 40 41 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 41 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 42 42 _write_facet(tmp_path, "work") 43 43 _write_detected_entities( 44 44 tmp_path, ··· 110 110 from talent.schedule import post_process 111 111 from think.activities import load_activity_records 112 112 113 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 113 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 114 114 _write_facet(tmp_path, "work") 115 115 116 116 payload = [ ··· 147 147 from talent.schedule import post_process 148 148 from think.activities import load_activity_records 149 149 150 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 150 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 151 151 _write_facet(tmp_path, "work") 152 152 caplog.set_level(logging.WARNING, logger="talent.schedule") 153 153 ··· 178 178 def test_schedule_post_process_skips_unknown_facet(tmp_path, monkeypatch, caplog): 179 179 from talent.schedule import post_process 180 180 181 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 181 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 182 182 _write_facet(tmp_path, "work") 183 183 caplog.set_level(logging.WARNING, logger="talent.schedule") 184 184 ··· 210 210 from talent.schedule import post_process 211 211 from think.activities import load_activity_records 212 212 213 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 213 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 214 214 _write_facet(tmp_path, "work") 215 215 caplog.set_level(logging.WARNING, logger="talent.schedule") 216 216 ··· 244 244 ): 245 245 from talent.schedule import post_process 246 246 247 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 247 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 248 248 _write_facet(tmp_path, "work") 249 249 caplog.set_level(logging.ERROR, logger="talent.schedule") 250 250 ··· 256 256 from talent.schedule import post_process 257 257 from think.activities import load_activity_records 258 258 259 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 259 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 260 260 _write_facet(tmp_path, "work") 261 261 262 262 payload = json.dumps( ··· 289 289 from talent.schedule import post_process 290 290 from think.activities import append_activity_record, load_activity_records 291 291 292 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 292 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 293 293 _write_facet(tmp_path, "work") 294 294 295 295 append_activity_record( ··· 358 358 from talent.schedule import post_process 359 359 from think.activities import append_activity_record, load_activity_records 360 360 361 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 361 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 362 362 _write_facet(tmp_path, "work") 363 363 364 364 append_activity_record(
+1 -1
tests/test_scheduler.py
··· 70 70 @pytest.fixture 71 71 def journal_path(tmp_path, monkeypatch): 72 72 """Create a temp journal with config/ and health/ dirs.""" 73 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 73 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 74 74 (tmp_path / "config").mkdir() 75 75 (tmp_path / "health").mkdir() 76 76 return tmp_path
+28 -28
tests/test_segment.py
··· 40 40 41 41 42 42 def test_list_basic(tmp_path, monkeypatch, capsys): 43 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 43 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 44 44 _make_segment( 45 45 tmp_path, 46 46 "20240101", ··· 82 82 83 83 84 84 def test_list_stream_filter(tmp_path, monkeypatch, capsys): 85 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 85 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 86 86 _make_segment( 87 87 tmp_path, 88 88 "20240101", ··· 119 119 120 120 121 121 def test_list_json(tmp_path, monkeypatch, capsys): 122 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 122 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 123 123 _make_segment( 124 124 tmp_path, 125 125 "20240101", ··· 147 147 148 148 149 149 def test_list_empty_day(tmp_path, monkeypatch, capsys): 150 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 150 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 151 151 152 152 args = argparse.Namespace( 153 153 day="20240101", stream=None, json_output=False, subcommand="list" ··· 159 159 160 160 161 161 def test_inspect_basic(tmp_path, monkeypatch, capsys): 162 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 162 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 163 163 _make_segment( 164 164 tmp_path, 165 165 "20240101", ··· 188 188 189 189 190 190 def test_inspect_bad_path(tmp_path, monkeypatch, capsys): 191 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 191 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 192 192 193 193 args = argparse.Namespace(path="bad/path", json_output=False, subcommand="inspect") 194 194 with pytest.raises(SystemExit) as excinfo: ··· 200 200 201 201 202 202 def test_inspect_missing_segment(tmp_path, monkeypatch, capsys): 203 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 203 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 204 204 205 205 args = argparse.Namespace( 206 206 path="20240101/default/090000_300", json_output=False, subcommand="inspect" ··· 214 214 215 215 216 216 def test_inspect_json(tmp_path, monkeypatch, capsys): 217 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 217 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 218 218 _make_segment( 219 219 tmp_path, 220 220 "20240101", ··· 242 242 243 243 244 244 def test_inspect_chain(tmp_path, monkeypatch, capsys): 245 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 245 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 246 246 _make_segment( 247 247 tmp_path, 248 248 "20240101", ··· 291 291 292 292 293 293 def test_verify_all_pass(tmp_path, monkeypatch, capsys): 294 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 294 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 295 295 _make_segment( 296 296 tmp_path, 297 297 "20240101", ··· 328 328 329 329 330 330 def test_verify_missing_stream_json(tmp_path, monkeypatch, capsys): 331 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 331 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 332 332 _make_segment(tmp_path, "20240101", "default", "090000_300", stream_json=None) 333 333 streams_dir = tmp_path / "streams" 334 334 streams_dir.mkdir() ··· 351 351 352 352 353 353 def test_verify_missing_content(tmp_path, monkeypatch, capsys): 354 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 354 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 355 355 _make_segment( 356 356 tmp_path, 357 357 "20240101", ··· 387 387 388 388 389 389 def test_verify_broken_backward_chain(tmp_path, monkeypatch, capsys): 390 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 390 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 391 391 _make_segment( 392 392 tmp_path, 393 393 "20240101", ··· 424 424 425 425 426 426 def test_verify_day_mode(tmp_path, monkeypatch, capsys): 427 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 427 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 428 428 _make_segment( 429 429 tmp_path, 430 430 "20240101", ··· 469 469 470 470 471 471 def test_verify_json(tmp_path, monkeypatch, capsys): 472 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 472 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 473 473 _make_segment( 474 474 tmp_path, 475 475 "20240101", ··· 504 504 505 505 506 506 def test_verify_no_args(tmp_path, monkeypatch, capsys): 507 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 507 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 508 508 509 509 args = argparse.Namespace( 510 510 path=None, day=None, json_output=False, subcommand="verify" ··· 532 532 533 533 def test_move_basic(tmp_path, monkeypatch, capsys): 534 534 """Basic move from one day to another.""" 535 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 535 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 536 536 _make_segment( 537 537 tmp_path, 538 538 "20240101", ··· 573 573 574 574 def test_move_with_to_time(tmp_path, monkeypatch, capsys): 575 575 """Move with --to-time changes the segment key, preserving duration.""" 576 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 576 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 577 577 _make_segment( 578 578 tmp_path, 579 579 "20240101", ··· 607 607 608 608 def test_move_dry_run(tmp_path, monkeypatch, capsys): 609 609 """--dry-run prints plan without moving.""" 610 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 610 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 611 611 _make_segment( 612 612 tmp_path, 613 613 "20240101", ··· 643 643 644 644 def test_move_collision_no_to_time(tmp_path, monkeypatch, capsys): 645 645 """Collision without --to-time is an error.""" 646 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 646 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 647 647 _make_segment( 648 648 tmp_path, 649 649 "20240101", ··· 691 691 692 692 def test_move_no_events_jsonl(tmp_path, monkeypatch, capsys): 693 693 """Segment with no events.jsonl moves cleanly.""" 694 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 694 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 695 695 _make_segment( 696 696 tmp_path, 697 697 "20240101", ··· 728 728 729 729 def test_move_patches_successor(tmp_path, monkeypatch, capsys): 730 730 """Moving a segment patches the successor's prev_day/prev_segment.""" 731 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 731 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 732 732 _make_segment( 733 733 tmp_path, 734 734 "20240101", ··· 779 779 780 780 def test_move_stream_tail(tmp_path, monkeypatch, capsys): 781 781 """Moving the stream tail (no successor) works cleanly.""" 782 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 782 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 783 783 _make_segment( 784 784 tmp_path, 785 785 "20240101", ··· 826 826 827 827 def test_move_rewrites_events_jsonl(tmp_path, monkeypatch, capsys): 828 828 """events.jsonl day and segment fields are updated after move.""" 829 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 829 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 830 830 seg_dir = _make_segment( 831 831 tmp_path, 832 832 "20240101", ··· 883 883 884 884 def test_move_touches_health_markers(tmp_path, monkeypatch, capsys): 885 885 """Health markers are touched on both source and destination days.""" 886 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 886 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 887 887 _make_segment( 888 888 tmp_path, 889 889 "20240101", ··· 917 917 918 918 def test_move_same_location_refused(tmp_path, monkeypatch, capsys): 919 919 """Moving to the same location is refused.""" 920 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 920 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 921 921 _make_segment( 922 922 tmp_path, 923 923 "20240101", ··· 946 946 947 947 def test_move_invalid_to_time(tmp_path, monkeypatch, capsys): 948 948 """Invalid --to-time format is rejected.""" 949 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 949 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 950 950 _make_segment( 951 951 tmp_path, 952 952 "20240101", ··· 975 975 976 976 def test_move_invalid_to_day(tmp_path, monkeypatch, capsys): 977 977 """Invalid --to-day format is rejected.""" 978 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 978 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 979 979 _make_segment( 980 980 tmp_path, 981 981 "20240101",
+1 -1
tests/test_segment_ingest.py
··· 28 28 def journal_env(tmp_path, monkeypatch): 29 29 """Set up journal root and source storage.""" 30 30 monkeypatch.setattr(convey.state, "journal_root", str(tmp_path), raising=False) 31 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 31 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 32 32 (tmp_path / "apps" / "import" / "journal_sources").mkdir( 33 33 parents=True, exist_ok=True 34 34 )
+10 -10
tests/test_sense.py
··· 219 219 journal_a.mkdir() 220 220 journal_b.mkdir() 221 221 222 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_a)) 222 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_a)) 223 223 monkeypatch.setattr(runner, "_current_day", lambda: "20241101") 224 224 225 225 ref = "test_ref" 226 226 writer = ProcessLogWriter(ref, "echo") 227 227 228 228 # Drift: env var changes and day changes before the next flush. 229 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_b)) 229 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_b)) 230 230 monkeypatch.setattr(runner, "_current_day", lambda: "20241102") 231 231 232 232 writer.write("hello\n") ··· 311 311 """Test scan_unprocessed finds only unprocessed media files.""" 312 312 from observe.utils import AUDIO_EXTENSIONS, VIDEO_EXTENSIONS 313 313 314 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 314 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 315 315 316 316 day_dir = tmp_path / "chronicle" / "20250101" 317 317 segment_dir = day_dir / "default" / "143022_300" ··· 341 341 """Test scan_unprocessed honors segment filters.""" 342 342 from observe.utils import AUDIO_EXTENSIONS, VIDEO_EXTENSIONS 343 343 344 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 344 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 345 345 346 346 day_dir = tmp_path / "chronicle" / "20250101" 347 347 segment_1 = day_dir / "default" / "143022_300" ··· 405 405 mock_day, tmp_path, monkeypatch, mock_callosum 406 406 ): 407 407 """Test that duplicate file processing is prevented.""" 408 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 408 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 409 409 mock_day.return_value = "20250101" 410 410 411 411 # Create journal/day structure ··· 437 437 mock_day, tmp_path, monkeypatch, mock_callosum 438 438 ): 439 439 """Test spawning a real process and monitoring completion.""" 440 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 440 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 441 441 mock_day.return_value = "20241101" 442 442 443 443 sensor = FileSensor(tmp_path) ··· 468 468 @patch("think.runner._current_day") 469 469 def test_file_sensor_spawn_handler_failing_process(mock_day, tmp_path, monkeypatch): 470 470 """Test handling of failing process.""" 471 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 471 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 472 472 mock_day.return_value = "20241101" 473 473 474 474 sensor = FileSensor(tmp_path) ··· 489 489 @patch("think.runner._current_day") 490 490 def test_file_sensor_failing_process_notifies(mock_day, tmp_path, monkeypatch): 491 491 """Test that a failing handler process emits a notification event.""" 492 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 492 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 493 493 mock_day.return_value = "20241101" 494 494 495 495 sensor = FileSensor(tmp_path) ··· 659 659 """Test that observe.observed event includes day field.""" 660 660 from think.callosum import CallosumConnection 661 661 662 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 662 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 663 663 mock_day.return_value = "20250101" 664 664 665 665 # Create journal/day/stream/segment structure ··· 715 715 """ 716 716 from think.callosum import CallosumConnection 717 717 718 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 718 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 719 719 720 720 # Create journal/day/stream/segment structure 721 721 day_dir = tmp_path / "chronicle" / "20250101"
+4 -4
tests/test_sense_contamination_guard.py
··· 123 123 day = "20260418" 124 124 stream = "default" 125 125 segments = ["090000_300", "090500_300"] 126 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 126 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 127 127 128 128 _write_detected_entities( 129 129 tmp_path, ··· 165 165 day = "20260418" 166 166 stream = "default" 167 167 segments = ["090000_300", "090500_300"] 168 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 168 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 169 169 170 170 _write_detected_entities( 171 171 tmp_path, ··· 205 205 day = "20260418" 206 206 stream = "default" 207 207 segments = ["090000_300", "090500_300"] 208 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 208 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 209 209 210 210 _write_detected_entities( 211 211 tmp_path, ··· 256 256 day = "20260418" 257 257 stream = "default" 258 258 segments = ["090000_300", "090500_300"] 259 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 259 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 260 260 261 261 _write_detected_entities( 262 262 tmp_path,
+17 -12
tests/test_service.py
··· 37 37 env = { 38 38 "HOME": "/Users/test", 39 39 "PATH": "/usr/bin", 40 - "_SOLSTONE_JOURNAL_OVERRIDE": "/Users/test/journal", 41 40 } 42 41 data = service._generate_plist(env) 43 42 plist = plistlib.loads(data) 44 43 assert plist["Label"] == "org.solpbc.solstone" 44 + assert plist["ProgramArguments"][0] == str( 45 + Path.home() / ".local" / "bin" / "sol" 46 + ) 45 47 assert plist["ProgramArguments"][1] == "supervisor" 46 48 assert plist["EnvironmentVariables"] == env 47 49 assert plist["KeepAlive"] is True ··· 55 57 env = { 56 58 "HOME": "/home/test", 57 59 "PATH": "/usr/bin", 58 - "_SOLSTONE_JOURNAL_OVERRIDE": "/home/test/journal", 59 60 } 60 61 unit = service._generate_systemd_unit(env) 61 62 lines = unit.splitlines() ··· 67 68 68 69 assert "Type=simple" in unit 69 70 assert "Restart=on-failure" in unit 70 - assert "ExecStart=" in unit 71 + assert ( 72 + f"ExecStart={Path.home() / '.local' / 'bin' / 'sol'} supervisor 5015" 73 + in unit 74 + ) 71 75 assert "supervisor" in unit 72 76 assert "Environment=HOME=/home/test" in unit 73 - assert "Environment=_SOLSTONE_JOURNAL_OVERRIDE=/home/test/journal" in unit 77 + assert "Environment=PATH=/usr/bin" in unit 78 + assert "SOLSTONE_JOURNAL" not in unit 74 79 assert "WantedBy=default.target" in unit 75 80 76 81 77 82 class TestEnvCollection: 78 83 def test_no_api_keys_in_env(self, monkeypatch, tmp_path): 79 84 """Service env must NOT contain API keys — they load at runtime via setup_cli.""" 80 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 85 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 81 86 82 87 config_dir = tmp_path / "config" 83 88 config_dir.mkdir(exist_ok=True) ··· 99 104 assert "GOOGLE_API_KEY" not in env 100 105 101 106 def test_includes_venv_in_path(self, monkeypatch, tmp_path): 102 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 107 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 103 108 monkeypatch.setenv("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin") 104 109 monkeypatch.setattr( 105 110 sys, "executable", str(tmp_path / ".venv" / "bin" / "python") ··· 112 117 ) 113 118 114 119 def test_path_fallback_when_unset(self, monkeypatch, tmp_path): 115 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 120 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 116 121 monkeypatch.delenv("PATH", raising=False) 117 122 monkeypatch.setattr( 118 123 sys, "executable", str(tmp_path / ".venv" / "bin" / "python") ··· 123 128 assert env["PATH"] == f"{venv_bin}:/usr/local/bin:/usr/bin:/bin" 124 129 125 130 def test_path_deduplicates_venv_bin(self, monkeypatch, tmp_path): 126 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 131 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 127 132 monkeypatch.setattr( 128 133 sys, "executable", str(tmp_path / ".venv" / "bin" / "python") 129 134 ) ··· 135 140 assert parts[0] == venv_bin 136 141 assert parts.count(venv_bin) == 1 137 142 138 - def test_journal_override_not_propagated(self, monkeypatch, tmp_path): 139 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 143 + def test_journal_env_not_propagated(self, monkeypatch, tmp_path): 144 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 140 145 141 146 env = service._collect_env() 142 - assert "_SOLSTONE_JOURNAL_OVERRIDE" not in env 147 + assert "SOLSTONE_JOURNAL" not in env 143 148 144 149 145 150 class TestStatus: ··· 219 224 class TestInstall: 220 225 def test_linux_idempotent(self, monkeypatch, tmp_path, capsys): 221 226 monkeypatch.setattr(sys, "platform", "linux") 222 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 227 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 223 228 224 229 unit_path = tmp_path / "solstone.service" 225 230 monkeypatch.setattr(service, "_unit_path", lambda: unit_path)
+1 -1
tests/test_skill_editor_hook.py
··· 19 19 20 20 @pytest.fixture 21 21 def skill_editor_env(monkeypatch, tmp_path): 22 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 22 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 23 23 return Path(tmp_path) 24 24 25 25
+1 -1
tests/test_skill_editor_prompt.py
··· 25 25 26 26 def test_skill_editor_prompt_substitutes_template_vars(monkeypatch, tmp_path): 27 27 _seed_config(tmp_path) 28 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 28 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 29 29 repo_root = Path(__file__).resolve().parent.parent 30 30 31 31 prompt = load_prompt(
+14 -14
tests/test_sol.py
··· 125 125 """Tests for get_status() function.""" 126 126 127 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)) 128 + """Test status when journal env is set and exists.""" 129 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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"] == "override" 133 + assert status["journal_source"] == "env" 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 override points to nonexistent dir.""" 137 + """Test status when the journal env points to a nonexistent dir.""" 138 138 nonexistent = tmp_path / "nonexistent" 139 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(nonexistent)) 139 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(nonexistent)) 140 140 141 141 status = sol.get_status() 142 142 assert status["journal_path"] == str(nonexistent) 143 - assert status["journal_source"] == "override" 143 + assert status["journal_source"] == "env" 144 144 assert status["journal_exists"] is False 145 145 146 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) 147 + """Test status when no journal env is set uses source-tree fallback.""" 148 + monkeypatch.delenv("SOLSTONE_JOURNAL", raising=False) 149 149 status = sol.get_status() 150 150 assert status["journal_path"].endswith("/journal") 151 - assert status["journal_source"] == "project" 151 + assert status["journal_source"] == "source" 152 152 assert isinstance(status["journal_exists"], bool) 153 153 154 154 ··· 158 158 def test_main_no_args_shows_help(self, monkeypatch, capsys): 159 159 """Test that running with no args shows help.""" 160 160 monkeypatch.setattr(sys, "argv", ["sol"]) 161 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test") 161 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/tmp/test") 162 162 163 163 sol.main() 164 164 ··· 169 169 def test_main_help_flag(self, monkeypatch, capsys): 170 170 """Test --help flag shows help.""" 171 171 monkeypatch.setattr(sys, "argv", ["sol", "--help"]) 172 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test") 172 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/tmp/test") 173 173 174 174 sol.main() 175 175 ··· 179 179 def test_main_help_command_without_question(self, monkeypatch, capsys): 180 180 """Test bare 'help' command shows static help.""" 181 181 monkeypatch.setattr(sys, "argv", ["sol", "help"]) 182 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test") 182 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/tmp/test") 183 183 184 184 sol.main() 185 185 ··· 198 198 def test_main_path_flag(self, monkeypatch, capsys): 199 199 """Test --path flag prints resolved journal path.""" 200 200 monkeypatch.setattr(sys, "argv", ["sol", "--path"]) 201 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "/tmp/test-journal") 201 + monkeypatch.setenv("SOLSTONE_JOURNAL", "/tmp/test-journal") 202 202 203 203 sol.main() 204 204 ··· 208 208 def test_main_path_flag_default(self, monkeypatch, capsys): 209 209 """Test --path prints project root journal when no override set.""" 210 210 monkeypatch.setattr(sys, "argv", ["sol", "--path"]) 211 - monkeypatch.delenv("_SOLSTONE_JOURNAL_OVERRIDE", raising=False) 211 + monkeypatch.delenv("SOLSTONE_JOURNAL", raising=False) 212 212 sol.main() 213 213 214 214 captured = capsys.readouterr()
+5 -5
tests/test_sol_call.py
··· 49 49 @pytest.fixture 50 50 def journal_with_identity(tmp_path, monkeypatch): 51 51 """Set up a journal with identity/ containing self.md, agency.md, and partner.md.""" 52 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 52 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 53 53 54 54 # Provide minimal config for ensure_identity_directory 55 55 config_dir = tmp_path / "config" ··· 135 135 assert "Test User" in result.output 136 136 137 137 def test_read_self_missing(self, tmp_path, monkeypatch): 138 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 138 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 139 139 config_dir = tmp_path / "config" 140 140 config_dir.mkdir() 141 141 (config_dir / "journal.json").write_text(json.dumps({})) ··· 211 211 assert "## work patterns" in result.output 212 212 213 213 def test_read_partner_missing(self, tmp_path, monkeypatch): 214 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 214 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 215 215 config_dir = tmp_path / "config" 216 216 config_dir.mkdir() 217 217 (config_dir / "journal.json").write_text(json.dumps({})) ··· 279 279 assert "## curation" in result.output 280 280 281 281 def test_read_agency_missing(self, tmp_path, monkeypatch): 282 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 282 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 283 283 config_dir = tmp_path / "config" 284 284 config_dir.mkdir() 285 285 (config_dir / "journal.json").write_text(json.dumps({})) ··· 314 314 assert "Test narrative" in result.output 315 315 316 316 def test_read_pulse_missing(self, tmp_path, monkeypatch): 317 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 317 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 318 318 config_dir = tmp_path / "config" 319 319 config_dir.mkdir() 320 320 (config_dir / "journal.json").write_text(json.dumps({}))
+1 -1
tests/test_sol_call_identity_digest.py
··· 47 47 48 48 @pytest.fixture 49 49 def digest_journal(tmp_path, monkeypatch): 50 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 50 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 51 51 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 52 52 53 53 config_dir = tmp_path / "config"
+1 -1
tests/test_sol_call_identity_hydrate.py
··· 23 23 env = os.environ.copy() 24 24 env.update( 25 25 { 26 - "_SOLSTONE_JOURNAL_OVERRIDE": str(journal_path), 26 + "SOLSTONE_JOURNAL": str(journal_path), 27 27 "SOL_SKIP_SUPERVISOR_CHECK": "1", 28 28 } 29 29 )
+3 -3
tests/test_stats_contract.py
··· 127 127 stats_mod = importlib.import_module("think.journal_stats") 128 128 schema_mod = importlib.import_module("think.stats_schema") 129 129 journal = _build_journal(tmp_path) 130 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 130 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 131 131 132 132 output = _scan_output(journal, stats_mod) 133 133 ··· 139 139 def test_contract_fields_exist_in_output(tmp_path, monkeypatch): 140 140 stats_mod = importlib.import_module("think.journal_stats") 141 141 journal = _build_journal(tmp_path) 142 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 142 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 143 143 144 144 output = _scan_output(journal, stats_mod) 145 145 ··· 162 162 stats_mod = importlib.import_module("think.journal_stats") 163 163 schema_mod = importlib.import_module("think.stats_schema") 164 164 journal = _build_journal(tmp_path) 165 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 165 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 166 166 167 167 output = _scan_output(journal, stats_mod) 168 168 day_entry = next(iter(output["days"].values()))
+3 -3
tests/test_stats_schema.py
··· 21 21 '{"raw": "raw.flac"}\n{"start": "10:00:00", "text": "hello"}\n' 22 22 ) 23 23 24 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 24 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 25 25 js = stats_mod.JournalStats() 26 26 js.scan(str(journal)) 27 27 ··· 65 65 def test_save_json_raises_on_invalid(tmp_path, monkeypatch): 66 66 """save_json() must raise ValueError when validation fails.""" 67 67 stats_mod = importlib.import_module("think.journal_stats") 68 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 68 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 69 69 js = stats_mod.JournalStats() 70 70 # Corrupt the schema version so validation fails 71 71 original = js.to_dict ··· 92 92 '{"header": true}\n{"frame_id": 1, "timestamp": "10:00:00"}\n' 93 93 ) 94 94 95 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 95 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 96 96 js = stats_mod.JournalStats() 97 97 day_data = js.scan_day("20240101", str(day)) 98 98
+12 -12
tests/test_story_hook.py
··· 89 89 from talent.story import post_process 90 90 from think.activities import append_activity_record 91 91 92 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 92 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 93 93 94 94 append_activity_record("work", "20260418", _activity_record()) 95 95 ··· 122 122 from talent.story import post_process 123 123 from think.activities import append_activity_record 124 124 125 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 125 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 126 126 append_activity_record("work", "20260418", _activity_record()) 127 127 128 128 post_process( ··· 143 143 from talent.story import post_process 144 144 from think.activities import append_activity_record 145 145 146 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 146 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 147 147 append_activity_record("work", "20260418", _activity_record()) 148 148 149 149 post_process( ··· 177 177 from talent.story import post_process 178 178 from think.activities import append_activity_record 179 179 180 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 180 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 181 181 append_activity_record("work", "20260418", _activity_record()) 182 182 183 183 post_process( ··· 238 238 from talent.story import post_process 239 239 from think.activities import append_activity_record 240 240 241 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 241 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 242 242 _write_detected_entities( 243 243 tmp_path, 244 244 "work", ··· 304 304 from talent.story import post_process 305 305 from think.activities import append_activity_record 306 306 307 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 307 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 308 308 append_activity_record("work", "20260418", _activity_record()) 309 309 310 310 post_process(_valid_result(), _context(tmp_path)) ··· 352 352 from talent.story import post_process 353 353 from think.activities import append_activity_record 354 354 355 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 355 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 356 356 append_activity_record("work", "20260418", _activity_record()) 357 357 358 358 post_process( ··· 370 370 from talent.story import post_process 371 371 from think.activities import append_activity_record, load_activity_records 372 372 373 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 373 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 374 374 caplog.set_level(logging.WARNING) 375 375 376 376 append_activity_record( ··· 402 402 from talent.story import post_process 403 403 from think.activities import append_activity_record 404 404 405 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 405 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 406 406 append_activity_record("work", "20260418", _activity_record()) 407 407 408 408 returned = post_process( ··· 419 419 from talent.story import post_process 420 420 from think.activities import append_activity_record 421 421 422 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 422 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 423 423 append_activity_record("work", "20260418", _activity_record()) 424 424 425 425 returned = post_process( ··· 435 435 def test_story_hook_missing_record_logs_and_returns(tmp_path, monkeypatch, caplog): 436 436 from talent.story import post_process 437 437 438 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 438 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 439 439 440 440 returned = post_process(_valid_result(), _context(tmp_path)) 441 441 ··· 447 447 from talent.story import post_process 448 448 from think.activities import append_activity_record 449 449 450 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 450 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 451 451 append_activity_record("work", "20260418", _activity_record()) 452 452 453 453 output_path = (
+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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 93 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 113 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 129 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 183 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 202 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 203 203 204 204 # Create segment dirs with stream markers under default stream 205 205 day_dir = tmp_path / "chronicle" / "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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 237 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 238 238 239 239 errors = [] 240 240
+9 -9
tests/test_supervisor.py
··· 98 98 return proc 99 99 100 100 monkeypatch.setattr(mod.subprocess, "Popen", fake_popen) 101 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 101 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 102 102 103 103 # Test start_sense() 104 104 sense_proc = mod.start_sense() ··· 591 591 def test_supervisor_singleton_lock_acquired(tmp_path, monkeypatch): 592 592 mod = importlib.reload(importlib.import_module("think.supervisor")) 593 593 594 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 594 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 595 595 (tmp_path / "health").mkdir(parents=True, exist_ok=True) 596 596 monkeypatch.setattr(sys, "argv", ["supervisor"]) 597 597 ··· 622 622 623 623 mod = importlib.reload(importlib.import_module("think.supervisor")) 624 624 625 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 625 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 626 626 health_dir = tmp_path / "health" 627 627 health_dir.mkdir(parents=True, exist_ok=True) 628 628 lock_file = open(health_dir / "supervisor.lock", "w") ··· 651 651 652 652 mod = importlib.reload(importlib.import_module("think.supervisor")) 653 653 654 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 654 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 655 655 health_dir = tmp_path / "health" 656 656 health_dir.mkdir(parents=True, exist_ok=True) 657 657 lock_file = open(health_dir / "supervisor.lock", "w") ··· 682 682 def test_is_supervisor_up_without_pid_file(tmp_path, monkeypatch): 683 683 mod = importlib.reload(importlib.import_module("think.supervisor")) 684 684 685 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 685 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 686 686 (tmp_path / "health").mkdir(parents=True, exist_ok=True) 687 687 688 688 assert mod.is_supervisor_up() is False ··· 691 691 def test_is_supervisor_up_with_dead_pid(tmp_path, monkeypatch): 692 692 mod = importlib.reload(importlib.import_module("think.supervisor")) 693 693 694 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 694 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 695 695 health_dir = tmp_path / "health" 696 696 health_dir.mkdir(parents=True, exist_ok=True) 697 697 ··· 705 705 def test_is_supervisor_up_with_live_pid_missing_start_time(tmp_path, monkeypatch): 706 706 mod = importlib.reload(importlib.import_module("think.supervisor")) 707 707 708 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 708 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 709 709 health_dir = tmp_path / "health" 710 710 health_dir.mkdir(parents=True, exist_ok=True) 711 711 (health_dir / "supervisor.pid").write_text(str(os.getpid())) ··· 716 716 def test_is_supervisor_up_with_live_pid_mismatched_start_time(tmp_path, monkeypatch): 717 717 mod = importlib.reload(importlib.import_module("think.supervisor")) 718 718 719 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 719 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 720 720 health_dir = tmp_path / "health" 721 721 health_dir.mkdir(parents=True, exist_ok=True) 722 722 (health_dir / "supervisor.pid").write_text(str(os.getpid())) ··· 729 729 def test_is_supervisor_up_with_matching_process_identity(tmp_path, monkeypatch): 730 730 mod = importlib.reload(importlib.import_module("think.supervisor")) 731 731 732 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 732 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 733 733 health_dir = tmp_path / "health" 734 734 health_dir.mkdir(parents=True, exist_ok=True) 735 735 (health_dir / "supervisor.pid").write_text(str(os.getpid()))
+4 -4
tests/test_supervisor_schedule.py
··· 146 146 def test_handle_think_daily_complete_submits_heartbeat( 147 147 mock_callosum, tmp_path, monkeypatch, submit_mock 148 148 ): 149 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 149 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 150 150 (tmp_path / "health").mkdir(exist_ok=True) 151 151 152 152 mod._handle_think_daily_complete(daily_complete_message()) ··· 167 167 def test_ignores_non_think_daily_complete( 168 168 mock_callosum, tmp_path, monkeypatch, submit_mock, message 169 169 ): 170 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 170 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 171 171 (tmp_path / "health").mkdir(exist_ok=True) 172 172 173 173 mod._handle_think_daily_complete(message) ··· 176 176 177 177 178 178 def test_skips_when_pid_alive(mock_callosum, tmp_path, monkeypatch, submit_mock): 179 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 179 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 180 180 health = tmp_path / "health" 181 181 health.mkdir(exist_ok=True) 182 182 (health / "heartbeat.pid").write_text(str(os.getpid())) ··· 187 187 188 188 189 189 def test_proceeds_on_dead_pid(mock_callosum, tmp_path, monkeypatch, submit_mock): 190 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 190 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 191 191 health = tmp_path / "health" 192 192 health.mkdir(exist_ok=True) 193 193 (health / "heartbeat.pid").write_text("99999999")
+1 -1
tests/test_surfaces_health.py
··· 19 19 20 20 21 21 def _configure_env(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: 22 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 22 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 23 23 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 24 24 25 25 from think.entities.journal import clear_journal_entity_cache
+19 -19
tests/test_surfaces_ledger.py
··· 187 187 def test_pairing_happy_path(tmp_path, monkeypatch): 188 188 from think.surfaces import ledger as ledger_surface 189 189 190 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 190 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 191 191 _minimal_facet_tree(tmp_path) 192 192 _write_story_activity( 193 193 "work", ··· 215 215 def test_action_fuzzy_just_above_threshold(tmp_path, monkeypatch): 216 216 from think.surfaces import ledger as ledger_surface 217 217 218 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 218 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 219 219 _minimal_facet_tree(tmp_path) 220 220 _write_story_activity( 221 221 "work", ··· 242 242 def test_action_fuzzy_just_below_threshold(tmp_path, monkeypatch): 243 243 from think.surfaces import ledger as ledger_surface 244 244 245 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 245 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 246 246 _minimal_facet_tree(tmp_path) 247 247 _write_story_activity( 248 248 "work", ··· 270 270 def test_cross_facet_dedup(tmp_path, monkeypatch): 271 271 from think.surfaces import ledger as ledger_surface 272 272 273 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 273 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 274 274 _minimal_facet_tree(tmp_path, facets=("work", "personal")) 275 275 _write_story_activity( 276 276 "work", ··· 297 297 from think.activities import load_activity_records 298 298 from think.surfaces import ledger as ledger_surface 299 299 300 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 300 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 301 301 _minimal_facet_tree(tmp_path) 302 302 _write_story_activity( 303 303 "work", ··· 325 325 from think.activities import load_activity_records 326 326 from think.surfaces import ledger as ledger_surface 327 327 328 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 328 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 329 329 _minimal_facet_tree(tmp_path) 330 330 _write_story_activity( 331 331 "work", ··· 349 349 def test_close_as_dropped(tmp_path, monkeypatch): 350 350 from think.surfaces import ledger as ledger_surface 351 351 352 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 352 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 353 353 _minimal_facet_tree(tmp_path) 354 354 _write_story_activity( 355 355 "work", ··· 368 368 from think.activities import load_activity_records 369 369 from think.surfaces import ledger as ledger_surface 370 370 371 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 371 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 372 372 _minimal_facet_tree(tmp_path) 373 373 _write_story_activity( 374 374 "work", ··· 395 395 def test_decisions_dedup(tmp_path, monkeypatch): 396 396 from think.surfaces import ledger as ledger_surface 397 397 398 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 398 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 399 399 _minimal_facet_tree(tmp_path) 400 400 _write_story_activity( 401 401 "work", ··· 422 422 def test_missing_entity_id_pairing(tmp_path, monkeypatch): 423 423 from think.surfaces import ledger as ledger_surface 424 424 425 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 425 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 426 426 _minimal_facet_tree(tmp_path, facets=("work", "personal")) 427 427 428 428 _write_story_activity( ··· 469 469 def test_missing_counterparty_id_pairing_falls_back_to_text(tmp_path, monkeypatch): 470 470 from think.surfaces import ledger as ledger_surface 471 471 472 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 472 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 473 473 _minimal_facet_tree(tmp_path, facets=("work", "personal")) 474 474 _write_story_activity( 475 475 "work", ··· 506 506 def test_explicit_facets_include_muted_facet(tmp_path, monkeypatch): 507 507 from think.surfaces import ledger as ledger_surface 508 508 509 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 509 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 510 510 _minimal_facet_tree(tmp_path, facets=("work", "quiet"), muted_facets=("quiet",)) 511 511 _write_story_activity( 512 512 "quiet", ··· 524 524 def test_hidden_record_exclusion(tmp_path, monkeypatch): 525 525 from think.surfaces import ledger as ledger_surface 526 526 527 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 527 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 528 528 _minimal_facet_tree(tmp_path) 529 529 _write_story_activity( 530 530 "work", ··· 541 541 def test_sort_default_varies_by_state(tmp_path, monkeypatch): 542 542 from think.surfaces import ledger as ledger_surface 543 543 544 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 544 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 545 545 _minimal_facet_tree(tmp_path) 546 546 now_ms = int(datetime.now(UTC).timestamp() * 1000) 547 547 ··· 600 600 from think.activities import load_activity_records 601 601 from think.surfaces import ledger as ledger_surface 602 602 603 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 603 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 604 604 _minimal_facet_tree(tmp_path) 605 605 _write_story_activity( 606 606 "work", ··· 637 637 def test_cli_list_smoke(tmp_path, monkeypatch): 638 638 from think.tools.ledger import app 639 639 640 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 640 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 641 641 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 642 642 _minimal_facet_tree(tmp_path) 643 643 _write_story_activity( ··· 660 660 from think.surfaces import ledger as ledger_surface 661 661 from think.tools.ledger import app 662 662 663 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 663 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 664 664 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 665 665 _minimal_facet_tree(tmp_path) 666 666 _write_story_activity( ··· 684 684 from think.surfaces import ledger as ledger_surface 685 685 from think.tools.ledger import app 686 686 687 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 687 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 688 688 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 689 689 _minimal_facet_tree(tmp_path) 690 690 _write_story_activity( ··· 707 707 def test_cli_decisions_smoke(tmp_path, monkeypatch): 708 708 from think.tools.ledger import app 709 709 710 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 710 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 711 711 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 712 712 _minimal_facet_tree(tmp_path) 713 713 _write_story_activity(
+1 -1
tests/test_surfaces_profile.py
··· 13 13 14 14 15 15 def _configure_env(tmp_path, monkeypatch) -> None: 16 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 17 17 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 18 18 19 19 import think.utils
+2 -2
tests/test_system_status.py
··· 22 22 23 23 @pytest.fixture(autouse=True) 24 24 def _temp_journal(monkeypatch, tmp_path): 25 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 25 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 26 26 27 27 28 28 @pytest.fixture 29 29 def client(tmp_path, monkeypatch): 30 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 30 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 31 31 journal = tmp_path 32 32 (journal / "config").mkdir(parents=True, exist_ok=True) 33 33 config = {
+1 -1
tests/test_talents_ndjson.py
··· 22 22 agents_path = journal_path / "talents" 23 23 agents_path.mkdir() 24 24 25 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 25 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 26 26 return journal_path 27 27 28 28
+9 -9
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 _SOLSTONE_JOURNAL_OVERRIDE for the test 48 - old_journal = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 49 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 47 + # Set SOLSTONE_JOURNAL for the test 48 + old_journal = os.environ.get("SOLSTONE_JOURNAL") 49 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 50 50 51 51 yield tmp_path 52 52 53 - # Restore original _SOLSTONE_JOURNAL_OVERRIDE 53 + # Restore original SOLSTONE_JOURNAL 54 54 if old_journal: 55 - os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = old_journal 55 + os.environ["SOLSTONE_JOURNAL"] = old_journal 56 56 else: 57 - os.environ.pop("_SOLSTONE_JOURNAL_OVERRIDE", None) 57 + os.environ.pop("SOLSTONE_JOURNAL", 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["_SOLSTONE_JOURNAL_OVERRIDE"] = str(tmp_path) 161 + os.environ["SOLSTONE_JOURNAL"] = str(tmp_path) 162 162 163 163 result = load_prompt("test_template", base_dir=mock_prompt_dir) 164 164 ··· 280 280 write_journal(journal_one, "first awareness") 281 281 write_journal(journal_two, "second awareness") 282 282 283 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_one)) 283 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_one)) 284 284 first = load_prompt("identity_vars", base_dir=prompt_dir) 285 285 assert "first awareness" in first.text 286 286 287 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_two)) 287 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_two)) 288 288 second = load_prompt("identity_vars", base_dir=prompt_dir) 289 289 assert "second awareness" in second.text 290 290 assert "first awareness" not in second.text
+24 -24
tests/test_think_activity.py
··· 28 28 from think.thinking import run_activity_prompts 29 29 30 30 with tempfile.TemporaryDirectory() as tmpdir: 31 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 31 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 32 32 33 33 result = run_activity_prompts( 34 34 day="20260209", ··· 41 41 from think.thinking import run_activity_prompts 42 42 43 43 with tempfile.TemporaryDirectory() as tmpdir: 44 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 44 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 45 45 46 46 self._write_record( 47 47 tmpdir, ··· 73 73 from think.thinking import run_activity_prompts 74 74 75 75 with tempfile.TemporaryDirectory() as tmpdir: 76 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 76 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 77 77 78 78 self._write_record( 79 79 tmpdir, ··· 135 135 from think.thinking import run_activity_prompts 136 136 137 137 with tempfile.TemporaryDirectory() as tmpdir: 138 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 138 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 139 139 140 140 self._write_record( 141 141 tmpdir, ··· 189 189 from think.thinking import run_activity_prompts 190 190 191 191 with tempfile.TemporaryDirectory() as tmpdir: 192 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 192 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 193 193 194 194 record = { 195 195 "id": "coding_100000_300", ··· 252 252 from think.thinking import run_activity_prompts 253 253 254 254 with tempfile.TemporaryDirectory() as tmpdir: 255 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 255 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 256 256 257 257 self._write_record( 258 258 tmpdir, ··· 300 300 from think.thinking import run_activity_prompts 301 301 302 302 with tempfile.TemporaryDirectory() as tmpdir: 303 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 303 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 304 304 305 305 self._write_record( 306 306 tmpdir, ··· 328 328 from think.thinking import run_activity_prompts 329 329 330 330 with tempfile.TemporaryDirectory() as tmpdir: 331 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 331 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 332 332 333 333 self._write_record( 334 334 tmpdir, ··· 362 362 from think.thinking import run_activity_prompts 363 363 364 364 with tempfile.TemporaryDirectory() as tmpdir: 365 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 365 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 366 366 367 367 self._write_record( 368 368 tmpdir, ··· 446 446 from think.thinking import run_activity_prompts 447 447 448 448 with tempfile.TemporaryDirectory() as tmpdir: 449 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 449 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 450 450 451 451 activity_id = f"{activity_type}_100000_300" 452 452 self._write_record( ··· 520 520 from think.thinking import run_activity_prompts 521 521 522 522 with tempfile.TemporaryDirectory() as tmpdir: 523 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 523 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 524 524 525 525 configs = { 526 526 "work": { ··· 611 611 from think.thinking import run_activity_prompts 612 612 613 613 with tempfile.TemporaryDirectory() as tmpdir: 614 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 614 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 615 615 616 616 full_record = { 617 617 "description": "skip me", ··· 675 675 from think.activity_state_machine import ActivityStateMachine 676 676 677 677 with tempfile.TemporaryDirectory() as tmpdir: 678 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 678 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 679 679 680 680 sm = ActivityStateMachine() 681 681 sm.update( ··· 761 761 from think.activity_state_machine import ActivityStateMachine 762 762 763 763 with tempfile.TemporaryDirectory() as tmpdir: 764 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 764 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 765 765 766 766 sm = ActivityStateMachine() 767 767 sm.update(self._sense(content_type="coding"), "090000_300", "20260304") ··· 800 800 from think.activity_state_machine import ActivityStateMachine 801 801 802 802 with tempfile.TemporaryDirectory() as tmpdir: 803 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 803 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 804 804 805 805 sm = ActivityStateMachine() 806 806 sm.update(self._sense(content_type="coding"), "090000_300", "20260304") ··· 826 826 from think.activity_state_machine import ActivityStateMachine 827 827 828 828 with tempfile.TemporaryDirectory() as tmpdir: 829 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 829 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 830 830 831 831 sm = ActivityStateMachine() 832 832 sm.update(self._sense(content_type="coding"), "090000_300", "20260304") ··· 850 850 from think.activity_state_machine import ActivityStateMachine 851 851 852 852 with tempfile.TemporaryDirectory() as tmpdir: 853 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 853 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 854 854 855 855 sm = ActivityStateMachine() 856 856 # Activity 1 ends ··· 892 892 from think.activity_state_machine import ActivityStateMachine 893 893 894 894 with tempfile.TemporaryDirectory() as tmpdir: 895 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 895 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 896 896 897 897 entities = [ 898 898 {"type": "Person", "name": "Alice", "context": "dev"}, ··· 929 929 from think.activity_state_machine import ActivityStateMachine 930 930 931 931 with tempfile.TemporaryDirectory() as tmpdir: 932 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 932 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 933 933 934 934 sm = ActivityStateMachine() 935 935 sm.update( ··· 962 962 from think.activity_state_machine import ActivityStateMachine 963 963 964 964 with tempfile.TemporaryDirectory() as tmpdir: 965 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 965 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 966 966 967 967 two = [ 968 968 {"facet": "work", "activity": "coding", "level": "high"}, ··· 1011 1011 from think.activity_state_machine import ActivityStateMachine 1012 1012 1013 1013 with tempfile.TemporaryDirectory() as tmpdir: 1014 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1014 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1015 1015 1016 1016 sm = ActivityStateMachine() 1017 1017 sm.update( ··· 1074 1074 from think.activity_state_machine import ActivityStateMachine 1075 1075 1076 1076 with tempfile.TemporaryDirectory() as tmpdir: 1077 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 1077 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 1078 1078 1079 1079 sm = ActivityStateMachine() 1080 1080 sm.update( ··· 1235 1235 def test_facet_and_activity_md_dir_populated(self, monkeypatch, tmp_path): 1236 1236 from think.talents import _build_prompt_context 1237 1237 1238 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1238 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1239 1239 1240 1240 ctx = _build_prompt_context( 1241 1241 day="20260418", ··· 1266 1266 1267 1267 mod = importlib.import_module("think.talents") 1268 1268 1269 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1269 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 1270 1270 day_dir = day_path("20260418") 1271 1271 day_dir.mkdir(parents=True, exist_ok=True) 1272 1272
+1 -1
tests/test_think_segment.py
··· 19 19 segment_path.mkdir(parents=True) 20 20 (segment_path / "talents").mkdir(parents=True) 21 21 22 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 22 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 23 23 return segment_path 24 24 25 25
+1 -1
tests/test_think_skills.py
··· 35 35 36 36 @pytest.fixture 37 37 def skill_journal(monkeypatch, tmp_path): 38 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 38 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 39 39 return Path(tmp_path) 40 40 41 41
+89 -28
tests/test_think_utils.py
··· 17 17 from think.entities import load_entity_names 18 18 from think.utils import ( 19 19 DEFAULT_STREAM, 20 + SolstoneNotConfigured, 20 21 day_from_path, 22 + get_journal, 23 + get_journal_info, 24 + get_project_root, 21 25 iter_segments, 22 26 segment_key, 23 27 segment_parse, ··· 115 119 ], 116 120 ) 117 121 118 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 122 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 119 123 result = load_entity_names() 120 124 121 125 # Check that names are extracted without duplicates ··· 132 136 def test_load_entity_names_missing_file(monkeypatch): 133 137 """Test that missing file returns None.""" 134 138 with tempfile.TemporaryDirectory() as tmpdir: 135 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 139 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 136 140 result = load_entity_names() 137 141 assert result is None 138 142 ··· 144 148 facet_dir = Path(tmpdir) / "facets" / "test" 145 149 facet_dir.mkdir(parents=True, exist_ok=True) 146 150 147 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 151 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 148 152 result = load_entity_names() 149 153 assert result is None 150 154 ··· 156 160 entities_dir = Path(tmpdir) / "facets" / "test" / "entities" 157 161 entities_dir.mkdir(parents=True, exist_ok=True) 158 162 159 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 163 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 160 164 result = load_entity_names() 161 165 assert result is None 162 166 ··· 175 179 ], 176 180 ) 177 181 178 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 182 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 179 183 result = load_entity_names() 180 184 181 185 names = result.split("; ") ··· 198 202 ], 199 203 ) 200 204 201 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 205 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 202 206 result = load_entity_names() 203 207 assert "Jean-Pierre O'Malley" in result 204 208 assert "AT&T" in result ··· 207 211 208 212 209 213 def test_load_entity_names_with_env_var(monkeypatch): 210 - """Test loading using _SOLSTONE_JOURNAL_OVERRIDE environment variable.""" 214 + """Test loading using SOLSTONE_JOURNAL environment variable.""" 211 215 with tempfile.TemporaryDirectory() as tmpdir: 212 216 setup_entities_new_structure( 213 217 Path(tmpdir), ··· 215 219 [("Person", "Test User", "A test person")], 216 220 ) 217 221 218 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 222 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 219 223 220 224 # Should use env var 221 225 result = load_entity_names() ··· 224 228 225 229 def test_load_entity_names_empty_journal(tmp_path, monkeypatch): 226 230 """Test that empty journal directory returns None.""" 227 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 231 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 228 232 229 233 result = load_entity_names() 230 234 assert result is None ··· 249 253 ], 250 254 ) 251 255 252 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 256 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 253 257 result = load_entity_names(spoken=True) 254 258 255 259 # Should return a list, not a string ··· 299 303 ], 300 304 ) 301 305 302 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 306 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 303 307 result = load_entity_names(spoken=True) 304 308 # Tools are now included (uniform processing) 305 309 assert isinstance(result, list) ··· 321 325 ], 322 326 ) 323 327 324 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 328 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 325 329 result = load_entity_names(spoken=True) 326 330 327 331 # Should have only one "John" and one "Acme" even though there are two of each ··· 348 352 ], 349 353 ) 350 354 351 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 355 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 352 356 result = load_entity_names(spoken=True) 353 357 354 358 assert isinstance(result, list) ··· 406 410 ], 407 411 ) 408 412 409 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 413 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 410 414 result = load_entity_names(spoken=True) 411 415 412 416 assert isinstance(result, list) ··· 447 451 ], 448 452 ) 449 453 450 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 454 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 451 455 result = load_entity_names(spoken=True) 452 456 453 457 assert isinstance(result, list) ··· 482 486 ], 483 487 ) 484 488 485 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 489 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 486 490 result = load_entity_names(spoken=True) 487 491 488 492 # Should have only one "John" even though it appears in aka and as main name ··· 520 524 ], 521 525 ) 522 526 523 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) 527 + monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 524 528 result = load_entity_names(spoken=False) 525 529 526 530 # Check all entities are present with their aka ··· 661 665 662 666 Returns a helper function to write config and run setup_cli. 663 667 """ 664 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 668 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 665 669 monkeypatch.setattr(sys, "argv", ["test"]) 666 670 667 671 def write_config_and_run(config: dict | None = None): ··· 773 777 """Test writing and reading a service port file.""" 774 778 from think.utils import read_service_port, write_service_port 775 779 776 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 780 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 777 781 778 782 # Write port 779 783 write_service_port("test_service", 12345) ··· 791 795 """Test that reading missing port file returns None.""" 792 796 from think.utils import read_service_port 793 797 794 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 798 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 795 799 796 800 port = read_service_port("nonexistent") 797 801 assert port is None ··· 800 804 """Test that reading invalid port file content returns None.""" 801 805 from think.utils import read_service_port 802 806 803 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 807 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 804 808 805 809 # Create port file with invalid content 806 810 health_dir = tmp_path / "health" ··· 815 819 """Test that write_service_port creates health directory if needed.""" 816 820 from think.utils import write_service_port 817 821 818 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 822 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 819 823 820 824 # Health dir doesn't exist yet 821 825 health_dir = tmp_path / "health" ··· 836 840 from think.utils import is_solstone_up 837 841 838 842 monkeypatch.delenv("SOL_SKIP_SUPERVISOR_CHECK", raising=False) 839 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 843 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 840 844 841 845 assert is_solstone_up() is False 842 846 ··· 845 849 from think.utils import is_solstone_up, write_service_port 846 850 847 851 monkeypatch.delenv("SOL_SKIP_SUPERVISOR_CHECK", raising=False) 848 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 852 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 849 853 850 854 with socket.socket() as sock: 851 855 sock.bind(("127.0.0.1", 0)) ··· 859 863 from think.utils import is_solstone_up, write_service_port 860 864 861 865 monkeypatch.delenv("SOL_SKIP_SUPERVISOR_CHECK", raising=False) 862 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 866 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 863 867 864 868 with socket.socket() as server: 865 869 server.bind(("127.0.0.1", 0)) ··· 874 878 from think.utils import require_solstone 875 879 876 880 monkeypatch.delenv("SOL_SKIP_SUPERVISOR_CHECK", raising=False) 877 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 881 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 878 882 879 883 with pytest.raises(SystemExit) as excinfo: 880 884 require_solstone() ··· 894 898 from think.utils import require_solstone, write_service_port 895 899 896 900 monkeypatch.delenv("SOL_SKIP_SUPERVISOR_CHECK", raising=False) 897 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 901 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 898 902 899 903 with socket.socket() as server: 900 904 server.bind(("127.0.0.1", 0)) ··· 912 916 """SOL_SKIP_SUPERVISOR_CHECK bypasses availability probing.""" 913 917 import think.utils as utils 914 918 915 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 919 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 916 920 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 917 921 monkeypatch.setattr( 918 922 utils, ··· 970 974 assert results[2][1] == "120000_600" 971 975 assert results[0][0] == "default" 972 976 assert results[2][0] == "import.apple" 977 + 978 + 979 + class TestJournalResolution: 980 + def test_autouse_fixture_get_journal_info_returns_env_label(self): 981 + """Sentinel for the unit-test autouse journal fixture.""" 982 + path, source = get_journal_info() 983 + 984 + assert source == "env" 985 + assert path == str(Path("tests/fixtures/journal").resolve()) 986 + 987 + def test_get_journal_info_prefers_solstone_journal_env(self, monkeypatch, tmp_path): 988 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 989 + 990 + path, source = get_journal_info() 991 + 992 + assert path == str(tmp_path) 993 + assert source == "env" 994 + 995 + def test_get_journal_info_source_tree_fallback(self, monkeypatch): 996 + monkeypatch.delenv("SOLSTONE_JOURNAL", raising=False) 997 + 998 + path, source = get_journal_info() 999 + 1000 + assert path == str(Path(get_project_root()) / "journal") 1001 + assert source == "source" 1002 + 1003 + def test_get_journal_info_raises_when_unconfigured(self, monkeypatch, tmp_path): 1004 + import think.utils as utils 1005 + 1006 + monkeypatch.delenv("SOLSTONE_JOURNAL", raising=False) 1007 + monkeypatch.setattr(utils, "get_project_root", lambda: str(tmp_path)) 1008 + 1009 + with pytest.raises(SolstoneNotConfigured) as excinfo: 1010 + get_journal_info() 1011 + 1012 + message = str(excinfo.value) 1013 + assert "SOLSTONE_JOURNAL" in message 1014 + assert str(tmp_path) in message 1015 + 1016 + def test_get_journal_mkdir_failure_raises_solstone_not_configured( 1017 + self, monkeypatch, tmp_path 1018 + ): 1019 + import think.utils as utils 1020 + 1021 + target = tmp_path / "journal" 1022 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(target)) 1023 + 1024 + def raise_permission_error(*_args, **_kwargs): 1025 + raise PermissionError("denied") 1026 + 1027 + monkeypatch.setattr(utils.os, "makedirs", raise_permission_error) 1028 + 1029 + with pytest.raises(SolstoneNotConfigured) as excinfo: 1030 + get_journal() 1031 + 1032 + assert excinfo.value.path == str(target) 1033 + assert isinstance(excinfo.value.error, PermissionError)
+7 -7
tests/test_transcribe_cli.py
··· 17 17 audio_file = seg_dir / "audio.wav" 18 18 audio_file.touch() 19 19 20 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 20 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 49 + monkeypatch.setenv("SOLSTONE_JOURNAL", 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 ··· 60 60 61 61 def test_setup_cli_no_message_on_project_journal(tmp_path, monkeypatch, capsys): 62 62 """setup_cli() prints no informational message — journal path is always deterministic.""" 63 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 63 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 64 64 65 65 with ( 66 66 patch("think.utils.get_journal", return_value=str(tmp_path)), ··· 99 99 ): 100 100 """--all processes unprocessed audio, skips already-transcribed, ignores non-audio.""" 101 101 journal = _make_batch_journal(tmp_path) 102 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 102 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 103 103 monkeypatch.setattr("sys.argv", ["sol transcribe", "--all"]) 104 104 105 105 mock_process_one = MagicMock() ··· 125 125 def test_all_redo_reprocesses_transcribed(tmp_path, monkeypatch): 126 126 """--all --redo reprocesses even segments that already have .jsonl.""" 127 127 journal = _make_batch_journal(tmp_path) 128 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 128 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 129 129 monkeypatch.setattr("sys.argv", ["sol transcribe", "--all", "--redo"]) 130 130 131 131 mock_process_one = MagicMock() ··· 143 143 144 144 def test_all_and_audio_path_mutually_exclusive(tmp_path, monkeypatch): 145 145 """Providing both --all and audio_path produces a clear error.""" 146 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 146 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 147 147 monkeypatch.setattr("sys.argv", ["sol transcribe", "--all", "some/audio.wav"]) 148 148 149 149 with patch("think.entities.load_recent_entity_names", return_value=[]): ··· 155 155 156 156 def test_neither_all_nor_audio_path_errors(tmp_path, monkeypatch): 157 157 """Providing neither --all nor audio_path produces a clear error.""" 158 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 158 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 159 159 monkeypatch.setattr("sys.argv", ["sol transcribe"]) 160 160 161 161 with patch("think.entities.load_recent_entity_names", return_value=[]):
+10 -10
tests/test_transfer.py
··· 98 98 (segment_dir / "audio.flac").write_bytes(b"fake audio data") 99 99 (segment_dir / "audio.jsonl").write_text('{"raw": "audio.flac"}\n') 100 100 101 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 101 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 102 102 103 103 # Clear cache 104 104 import think.utils ··· 133 133 day_dir = journal_path / "chronicle" / "20250101" 134 134 day_dir.mkdir(parents=True) 135 135 136 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 136 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 137 137 138 138 import think.utils 139 139 ··· 149 149 journal_path = tmp_path / "journal" 150 150 journal_path.mkdir(parents=True) 151 151 152 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 152 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 153 153 154 154 import think.utils 155 155 ··· 223 223 journal_path = tmp_path / "journal" 224 224 journal_path.mkdir() 225 225 226 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 226 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 227 227 228 228 import think.utils 229 229 ··· 253 253 segment_dir.mkdir(parents=True) 254 254 (segment_dir / "audio.flac").write_bytes(content) 255 255 256 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 256 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 257 257 258 258 import think.utils 259 259 ··· 280 280 segment_dir.mkdir(parents=True) 281 281 (segment_dir / "audio.flac").write_bytes(b"existing different data") 282 282 283 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 283 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 284 284 285 285 import think.utils 286 286 ··· 313 313 journal_path = tmp_path / "journal" 314 314 journal_path.mkdir() 315 315 316 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 316 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 317 317 318 318 import think.utils 319 319 ··· 344 344 journal_path = tmp_path / "journal" 345 345 journal_path.mkdir() 346 346 347 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 347 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 348 348 349 349 import think.utils 350 350 ··· 372 372 segment_dir.mkdir(parents=True) 373 373 (segment_dir / "audio.flac").write_bytes(content) 374 374 375 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal_path)) 375 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal_path)) 376 376 377 377 import think.utils 378 378 ··· 449 449 return journal 450 450 451 451 def _set_journal_override(self, monkeypatch, journal: Path) -> None: 452 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 452 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(journal)) 453 453 454 454 import think.utils 455 455
+4 -4
tests/test_updated_days.py
··· 10 10 11 11 def test_updated_days_fixture(monkeypatch): 12 12 """20250101 has stream.updated but no daily.updated — should be updated.""" 13 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 13 + monkeypatch.setenv("SOLSTONE_JOURNAL", "tests/fixtures/journal") 14 14 days = updated_days() 15 15 assert "20250101" in days 16 16 17 17 18 18 def test_updated_days_exclude(monkeypatch): 19 19 """Excluded days should not appear in results.""" 20 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") 20 + monkeypatch.setenv("SOLSTONE_JOURNAL", "tests/fixtures/journal") 21 21 days = updated_days(exclude={"20250101"}) 22 22 assert "20250101" not in days 23 23 24 24 25 25 def test_updated_days_clean(tmp_path, monkeypatch): 26 26 """Day with daily.updated newer than stream.updated is not updated.""" 27 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 27 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 28 28 day_dir = tmp_path / "chronicle" / "20260101" / "health" 29 29 day_dir.mkdir(parents=True) 30 30 (day_dir / "stream.updated").touch() ··· 35 35 36 36 def test_updated_days_no_stream(tmp_path, monkeypatch): 37 37 """Day without stream.updated is not updated (no stream data).""" 38 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 38 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 39 39 (tmp_path / "chronicle" / "20260101").mkdir(parents=True) 40 40 assert updated_days() == []
+2 -2
tests/test_voice_brain.py
··· 116 116 brain.clear_brain_state() 117 117 initial_journal = tmp_path / "initial-journal" 118 118 later_journal = tmp_path / "later-journal" 119 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(initial_journal)) 119 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(initial_journal)) 120 120 app = Flask(__name__) 121 121 122 122 async def fake_run_claude(message, extra_args, *, timeout): ··· 127 127 128 128 start_voice_runtime(app) 129 129 try: 130 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(later_journal)) 130 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(later_journal)) 131 131 assert brain.wait_until_ready(app, 1.0) is True 132 132 assert (initial_journal / "health" / "voice-brain-session").read_text( 133 133 encoding="utf-8"
+2 -2
tests/test_voice_tools.py
··· 135 135 136 136 137 137 def test_commitments_list_happy(monkeypatch, tmp_path): 138 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 138 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 139 139 _minimal_facet_tree(tmp_path) 140 140 _write_story_activity( 141 141 "work", ··· 159 159 160 160 161 161 def test_commitments_complete_happy(monkeypatch, tmp_path): 162 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 162 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 163 163 _minimal_facet_tree(tmp_path) 164 164 _write_story_activity( 165 165 "work",
+4 -4
tests/verify_api.py
··· 432 432 raw_journal = str(journal_path) 433 433 if raw_journal != resolved_journal: 434 434 path_replacements.append((raw_journal, "<JOURNAL>")) 435 - # Match the _SOLSTONE_JOURNAL_OVERRIDE env var if set (may be relative) 436 - env_journal = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE", "") 435 + # Match the SOLSTONE_JOURNAL env var if set (may be relative) 436 + env_journal = os.environ.get("SOLSTONE_JOURNAL", "") 437 437 if env_journal and env_journal not in (resolved_journal, raw_journal): 438 438 path_replacements.append((env_journal, "<JOURNAL>")) 439 439 path_replacements.append((project_root, "<PROJECT>")) ··· 666 666 """Resolve journal path from env or sandbox metadata.""" 667 667 668 668 env_path = Path.cwd() / "tests" / "fixtures" / "journal" 669 - journal = Path(os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE", str(env_path))) 669 + journal = Path(os.environ.get("SOLSTONE_JOURNAL", str(env_path))) 670 670 if journal.is_absolute(): 671 671 return str(journal) 672 672 return str(Path(journal).resolve()) ··· 701 701 702 702 703 703 def _resolve_http_journal() -> str: 704 - env_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 704 + env_path = os.environ.get("SOLSTONE_JOURNAL") 705 705 if env_path: 706 706 return str(Path(env_path).resolve()) 707 707 sandbox_path = _resolve_sandbox_journal()
+205
think/config_cli.py
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 sol pbc 3 + 4 + """sol config — show and rewrite the embedded journal path in the wrapper.""" 5 + 6 + from __future__ import annotations 7 + 8 + import argparse 9 + import os 10 + import subprocess 11 + import sys 12 + from pathlib import Path 13 + 14 + from think.install_guard import ( 15 + alias_path, 16 + parse_wrapper, 17 + render_wrapper, 18 + validate_journal_path_for_wrapper, 19 + wrapper_lock, 20 + write_wrapper_atomic, 21 + ) 22 + from think.utils import SolstoneNotConfigured, get_journal_info, get_project_root 23 + 24 + 25 + def _read_wrapper_status() -> tuple[str, str | None]: 26 + alias = alias_path() 27 + if not alias.exists() and not alias.is_symlink(): 28 + return "absent", None 29 + if alias.is_symlink(): 30 + return "legacy-symlink", None 31 + 32 + try: 33 + content = alias.read_text(encoding="utf-8") 34 + except OSError: 35 + return "foreign", None 36 + 37 + parsed = parse_wrapper(content) 38 + if parsed is None: 39 + return "foreign", None 40 + return "managed", parsed["journal"] 41 + 42 + 43 + def cmd_show() -> int: 44 + wrapper_status, embedded_journal = _read_wrapper_status() 45 + 46 + try: 47 + path, info_source = get_journal_info() 48 + except SolstoneNotConfigured as exc: 49 + print(f"sol config: {exc}", file=sys.stderr) 50 + return 1 51 + 52 + if info_source == "env": 53 + if ( 54 + embedded_journal is not None 55 + and os.environ.get("SOLSTONE_JOURNAL") == embedded_journal 56 + ): 57 + user_source = "wrapper-embedded" 58 + else: 59 + user_source = "caller-override" 60 + else: 61 + user_source = "source-tree fallback" 62 + 63 + print(f"path: {path}") 64 + print(f"source: {user_source}") 65 + print(f"wrapper-status: {wrapper_status}") 66 + return 0 67 + 68 + 69 + def cmd_journal(target_path: str) -> int: 70 + target = Path(target_path).expanduser().resolve() 71 + target_str = str(target) 72 + 73 + try: 74 + validate_journal_path_for_wrapper(target_str) 75 + except ValueError as exc: 76 + print(f"sol config: refused: {exc}", file=sys.stderr) 77 + return 1 78 + 79 + project_root = Path(get_project_root()) 80 + is_source_checkout = (project_root / "pyproject.toml").exists() and ( 81 + project_root / ".git" 82 + ).exists() 83 + source_tree_journal = (project_root / "journal").resolve() 84 + if target == source_tree_journal and not is_source_checkout: 85 + print( 86 + "sol config: refused: " 87 + f"{target_str} is the source-tree fallback path but this is not a " 88 + "source checkout", 89 + file=sys.stderr, 90 + ) 91 + return 1 92 + 93 + try: 94 + target.mkdir(parents=True, exist_ok=True) 95 + except OSError as exc: 96 + print( 97 + f"sol config: refused: cannot create {target_str}: {exc}", file=sys.stderr 98 + ) 99 + return 1 100 + 101 + alias = alias_path() 102 + if not alias.exists() or alias.is_symlink(): 103 + print( 104 + "sol config: refused: " 105 + f"{alias} is not a managed wrapper (run 'make install-service' to " 106 + "install the wrapper first)", 107 + file=sys.stderr, 108 + ) 109 + return 1 110 + 111 + try: 112 + content = alias.read_text(encoding="utf-8") 113 + except OSError as exc: 114 + print(f"sol config: refused: cannot read {alias}: {exc}", file=sys.stderr) 115 + return 1 116 + 117 + parsed = parse_wrapper(content) 118 + if parsed is None: 119 + print( 120 + "sol config: refused: " 121 + f"{alias} is not a managed wrapper (run 'make install-service' to " 122 + "install the wrapper first)", 123 + file=sys.stderr, 124 + ) 125 + return 1 126 + 127 + if parsed["journal"] == target_str: 128 + print(f"sol config: journal already set to {target_str}") 129 + return 0 130 + 131 + restart_sol = parsed["sol_bin"] 132 + with wrapper_lock(): 133 + try: 134 + current_content = alias.read_text(encoding="utf-8") 135 + except OSError as exc: 136 + print(f"sol config: refused: cannot read {alias}: {exc}", file=sys.stderr) 137 + return 1 138 + 139 + current = parse_wrapper(current_content) 140 + if current is None: 141 + print( 142 + "sol config: refused: " 143 + f"{alias} is not a managed wrapper (run 'make install-service' to " 144 + "install the wrapper first)", 145 + file=sys.stderr, 146 + ) 147 + return 1 148 + 149 + if current["journal"] == target_str: 150 + print(f"sol config: journal already set to {target_str}") 151 + return 0 152 + 153 + new_content = render_wrapper(target_str, current["sol_bin"]) 154 + write_wrapper_atomic(alias, new_content) 155 + restart_sol = current["sol_bin"] 156 + 157 + try: 158 + result = subprocess.run( 159 + [restart_sol, "service", "restart", "--if-installed"], 160 + check=False, 161 + ) 162 + except FileNotFoundError as exc: 163 + print( 164 + "sol config: wrapper rewritten to " 165 + f"{target_str} but service restart could not run ({exc}); restart " 166 + "manually", 167 + file=sys.stderr, 168 + ) 169 + return 2 170 + 171 + if result.returncode != 0: 172 + print( 173 + "sol config: wrapper rewritten to " 174 + f"{target_str} but 'sol service restart --if-installed' exited " 175 + f"{result.returncode}; investigate and restart manually", 176 + file=sys.stderr, 177 + ) 178 + return 2 179 + 180 + print(f"sol config: journal set to {target_str}") 181 + return 0 182 + 183 + 184 + def main() -> int: 185 + parser = argparse.ArgumentParser(prog="sol config") 186 + subparsers = parser.add_subparsers(dest="cmd", required=True) 187 + subparsers.add_parser("show", help="show the configured journal path and source") 188 + journal_parser = subparsers.add_parser( 189 + "journal", 190 + help="rewrite the wrapper's embedded journal path", 191 + ) 192 + journal_parser.add_argument( 193 + "path", help="absolute path to the new journal directory" 194 + ) 195 + args = parser.parse_args() 196 + 197 + if args.cmd == "show": 198 + return cmd_show() 199 + if args.cmd == "journal": 200 + return cmd_journal(args.path) 201 + return 1 202 + 203 + 204 + if __name__ == "__main__": 205 + sys.exit(main())
+1 -1
think/indexer/journal.py
··· 850 850 """Return SQLite connection for the journal index. 851 851 852 852 Args: 853 - journal: Path to journal root. Uses _SOLSTONE_JOURNAL_OVERRIDE env var if not provided. 853 + journal: Path to journal root. Uses SOLSTONE_JOURNAL env var if not provided. 854 854 855 855 Returns: 856 856 Tuple of (connection, db_path)
+252 -47
think/install_guard.py
··· 5 5 6 6 from __future__ import annotations 7 7 8 + import argparse 9 + import fcntl 8 10 import os 11 + import re 9 12 import sys 13 + from contextlib import contextmanager 10 14 from enum import Enum 11 15 from pathlib import Path 16 + from typing import Iterator 12 17 13 18 try: 14 19 import userpath # type: ignore[import-not-found] ··· 16 21 userpath = None # type: ignore[assignment] 17 22 18 23 24 + WRAPPER_TEMPLATE = """\ 25 + #!/bin/sh 26 + # sol — managed by 'sol config'. Edits will be overwritten. 27 + # managed-version: 1 28 + : "${{SOLSTONE_JOURNAL:={journal}}}" 29 + export SOLSTONE_JOURNAL 30 + SOL_BIN='{sol_bin}' 31 + if [ ! -x "$SOL_BIN" ]; then 32 + printf 'sol: venv binary missing or not executable: %s\\n' "$SOL_BIN" >&2 33 + exit 127 34 + fi 35 + exec "$SOL_BIN" "$@" 36 + """ 37 + 38 + WRAPPER_MARKER = "# managed-version: 1" 39 + WRAPPER_VERSION = 1 40 + 41 + _RE_MARKER = re.compile(r"(?m)^# managed-version: 1$") 42 + _RE_JOURNAL = re.compile(r'(?m)^: "\$\{SOLSTONE_JOURNAL:=(?P<journal>[^\n]*)\}"$') 43 + _RE_SOL_BIN = re.compile(r"(?m)^SOL_BIN='(?P<sol_bin>(?:[^']|'\\'')*)'$") 44 + 45 + _INVALID_JOURNAL_CHARS = ("$", "`", '"', "\\") 46 + 47 + 19 48 class AliasState(Enum): 20 49 WORKTREE = "worktree" 21 50 ABSENT = "absent" 22 51 OWNED = "owned" 23 52 CROSS_REPO = "cross_repo" 24 53 DANGLING = "dangling" 25 - NOT_SYMLINK = "not_symlink" 54 + FOREIGN = "foreign" 26 55 27 56 28 57 def alias_path() -> Path: ··· 33 62 return curdir / ".venv" / "bin" / "sol" 34 63 35 64 65 + def render_wrapper(journal: str, sol_bin: str) -> str: 66 + """Render the managed wrapper for ~/.local/bin/sol.""" 67 + escaped_sol_bin = sol_bin.replace("'", "'\\''") 68 + return WRAPPER_TEMPLATE.format(journal=journal, sol_bin=escaped_sol_bin) 69 + 70 + 71 + def parse_wrapper(content: str) -> dict[str, str] | None: 72 + """Return embedded paths if the content is a managed wrapper.""" 73 + if not _RE_MARKER.search(content): 74 + return None 75 + journal_match = _RE_JOURNAL.search(content) 76 + sol_bin_match = _RE_SOL_BIN.search(content) 77 + if not journal_match or not sol_bin_match: 78 + return None 79 + return { 80 + "journal": journal_match.group("journal"), 81 + "sol_bin": sol_bin_match.group("sol_bin").replace("'\\''", "'"), 82 + } 83 + 84 + 85 + def write_wrapper_atomic(path: Path, content: str) -> None: 86 + """Atomically rewrite the managed wrapper and restore exec mode.""" 87 + from think.entities.core import atomic_write 88 + 89 + atomic_write(path, content) 90 + os.chmod(path, 0o755) 91 + 92 + 93 + @contextmanager 94 + def wrapper_lock(lock_path: Path | None = None) -> Iterator[None]: 95 + """Hold an exclusive advisory lock while rewriting the wrapper.""" 96 + if lock_path is None: 97 + lock_path = Path.home() / ".local" / "bin" / ".sol.lock" 98 + lock_path.parent.mkdir(parents=True, exist_ok=True) 99 + with open(lock_path, "w", encoding="utf-8") as lock_fd: 100 + try: 101 + fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX) 102 + yield 103 + finally: 104 + fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN) 105 + 106 + 107 + def validate_journal_path_for_wrapper(path: str) -> None: 108 + """Reject shell-active characters that would corrupt wrapper embedding.""" 109 + for char in _INVALID_JOURNAL_CHARS: 110 + if char in path: 111 + raise ValueError( 112 + f"journal path contains shell-active character {char!r}: {path!r}" 113 + ) 114 + 115 + 36 116 def check_alias(curdir: Path) -> tuple[AliasState, Path | None]: 37 117 if (curdir / ".git").is_file(): 38 118 return AliasState.WORKTREE, None ··· 52 132 return AliasState.OWNED, target 53 133 return AliasState.CROSS_REPO, target 54 134 55 - return AliasState.NOT_SYMLINK, None 135 + try: 136 + content = alias.read_text(encoding="utf-8") 137 + except OSError: 138 + return AliasState.FOREIGN, None 139 + 140 + parsed = parse_wrapper(content) 141 + if parsed is None: 142 + return AliasState.FOREIGN, None 143 + 144 + target = Path(parsed["sol_bin"]) 145 + if target.resolve() == expected_target(curdir).resolve(): 146 + return AliasState.OWNED, target.resolve() 147 + return AliasState.FOREIGN, None 148 + 149 + 150 + def _current_journal_for_alias() -> str: 151 + """Return the journal path a wrapper install would embed right now.""" 152 + from think import utils as think_utils 153 + 154 + try: 155 + path, _ = think_utils.get_journal_info() 156 + except getattr(think_utils, "SolstoneNotConfigured", RuntimeError): 157 + path = str(Path.home() / "Documents" / "Solstone") 158 + return path 159 + 160 + 161 + def check_alias_detail(curdir: Path) -> tuple[AliasState, str]: 162 + """Return alias state plus the cmd_check token for owned aliases.""" 163 + state, _other_target = check_alias(curdir) 164 + if state is not AliasState.OWNED: 165 + return state, state.value 166 + 167 + alias = alias_path() 168 + if alias.is_symlink(): 169 + return state, "upgrade" 170 + 171 + try: 172 + content = alias.read_text(encoding="utf-8") 173 + except OSError: 174 + return state, "upgrade" 175 + 176 + parsed = parse_wrapper(content) 177 + if parsed is None: 178 + return state, "upgrade" 179 + 180 + if ( 181 + parsed["journal"] == _current_journal_for_alias() 182 + and parsed["sol_bin"] == str(expected_target(curdir)) 183 + and (alias.stat().st_mode & 0o111) == 0o111 184 + ): 185 + return state, "current" 186 + 187 + return state, "upgrade" 56 188 57 189 58 190 def format_error( ··· 60 192 curdir: Path, 61 193 _alias: Path, 62 194 other_target: Path | None, 195 + *, 196 + allow_force: bool = False, 63 197 ) -> str: 64 198 if state is AliasState.WORKTREE: 65 199 return ( ··· 74 208 else: 75 209 installed = " installed: not a symlink" 76 210 77 - return "\n".join( 78 - [ 79 - "ERROR: Another solstone install owns ~/.local/bin/sol.", 80 - f" this repo: {curdir}", 81 - installed, 82 - "Run 'make uninstall-service' from the installed repo first,", 83 - "or remove ~/.local/bin/sol manually if that repo is gone. No --force available.", 84 - ] 85 - ) 211 + lines = [ 212 + "ERROR: Another solstone install owns ~/.local/bin/sol.", 213 + f" this repo: {curdir}", 214 + installed, 215 + "Run 'make uninstall-service' from the installed repo first,", 216 + "or remove ~/.local/bin/sol manually if that repo is gone.", 217 + ] 218 + if allow_force: 219 + lines.append( 220 + "Rerun 'python -m think.install_guard install --force' only if you intend to replace it from this repo." 221 + ) 222 + return "\n".join(lines) 86 223 87 224 88 225 def _print_error( ··· 90 227 curdir: Path, 91 228 alias: Path, 92 229 other_target: Path | None, 230 + *, 231 + allow_force: bool = False, 93 232 ) -> None: 94 - sys.stderr.write(format_error(state, curdir, alias, other_target) + "\n") 233 + sys.stderr.write( 234 + format_error( 235 + state, 236 + curdir, 237 + alias, 238 + other_target, 239 + allow_force=allow_force, 240 + ) 241 + + "\n" 242 + ) 95 243 96 244 97 245 def _ensure_user_bin_on_path(user_bin: Path) -> None: ··· 126 274 127 275 def cmd_check(curdir: Path) -> int: 128 276 alias = alias_path() 129 - state, other_target = check_alias(curdir) 277 + state, token = check_alias_detail(curdir) 130 278 279 + if state is AliasState.WORKTREE: 280 + print("worktree") 281 + _print_error(state, curdir, alias, None) 282 + return 1 131 283 if state is AliasState.ABSENT: 132 284 print("fresh") 133 285 return 0 134 286 if state is AliasState.OWNED: 135 - print("upgrade") 287 + print(token) 136 288 return 0 289 + if state is AliasState.CROSS_REPO: 290 + print("cross_repo") 291 + _print_error(state, curdir, alias, check_alias(curdir)[1], allow_force=True) 292 + return 1 293 + if state is AliasState.DANGLING: 294 + print("dangling") 295 + _print_error(state, curdir, alias, check_alias(curdir)[1], allow_force=True) 296 + return 1 297 + if state is AliasState.FOREIGN: 298 + print("not_symlink") 299 + _print_error(state, curdir, alias, None, allow_force=True) 300 + return 1 137 301 138 - print(state.value) 139 - _print_error(state, curdir, alias, other_target) 140 302 return 1 141 303 142 304 143 - def cmd_install(curdir: Path) -> int: 305 + def cmd_install(curdir: Path, *, force: bool = False) -> int: 144 306 alias = alias_path() 145 307 state, other_target = check_alias(curdir) 146 308 147 309 if state is AliasState.WORKTREE: 148 310 _print_error(state, curdir, alias, other_target) 149 311 return 1 150 - if state is AliasState.ABSENT: 151 - alias.parent.mkdir(parents=True, exist_ok=True) 152 - alias.symlink_to(expected_target(curdir)) 153 - print("installed") 154 - _ensure_user_bin_on_path(alias.parent) 155 - return 0 156 - if state is AliasState.OWNED: 157 - alias.unlink() 158 - alias.symlink_to(expected_target(curdir)) 159 - print("installed") 160 - _ensure_user_bin_on_path(alias.parent) 161 - return 0 312 + if ( 313 + state 314 + in { 315 + AliasState.CROSS_REPO, 316 + AliasState.DANGLING, 317 + AliasState.FOREIGN, 318 + } 319 + and not force 320 + ): 321 + _print_error(state, curdir, alias, other_target, allow_force=True) 322 + return 1 323 + 324 + journal = _current_journal_for_alias() 325 + try: 326 + validate_journal_path_for_wrapper(journal) 327 + except ValueError as exc: 328 + print(f"refused: {exc}", file=sys.stderr) 329 + return 1 330 + 331 + content = render_wrapper(journal, str(expected_target(curdir))) 332 + alias.parent.mkdir(parents=True, exist_ok=True) 333 + with wrapper_lock(): 334 + locked_state, locked_other_target = check_alias(curdir) 335 + if locked_state is AliasState.WORKTREE: 336 + _print_error(locked_state, curdir, alias, locked_other_target) 337 + return 1 338 + if ( 339 + locked_state 340 + in { 341 + AliasState.CROSS_REPO, 342 + AliasState.DANGLING, 343 + AliasState.FOREIGN, 344 + } 345 + and not force 346 + ): 347 + _print_error( 348 + locked_state, 349 + curdir, 350 + alias, 351 + locked_other_target, 352 + allow_force=True, 353 + ) 354 + return 1 355 + if alias.is_symlink(): 356 + alias.unlink() 357 + write_wrapper_atomic(alias, content) 162 358 163 - _print_error(state, curdir, alias, other_target) 164 - return 1 359 + print("installed") 360 + _ensure_user_bin_on_path(alias.parent) 361 + return 0 165 362 166 363 167 364 def cmd_uninstall(curdir: Path) -> int: ··· 174 371 if state is AliasState.ABSENT: 175 372 print("absent") 176 373 return 0 177 - if state is AliasState.OWNED: 374 + if state is not AliasState.OWNED: 375 + _print_error(state, curdir, alias, other_target) 376 + return 1 377 + 378 + with wrapper_lock(): 379 + locked_state, locked_other_target = check_alias(curdir) 380 + if locked_state is AliasState.ABSENT: 381 + print("absent") 382 + return 0 383 + if locked_state is not AliasState.OWNED: 384 + _print_error(locked_state, curdir, alias, locked_other_target) 385 + return 1 178 386 alias.unlink() 179 - print("removed") 180 - return 0 181 387 182 - _print_error(state, curdir, alias, other_target) 183 - return 1 388 + print("uninstalled") 389 + return 0 184 390 185 391 186 392 def main(argv: list[str] | None = None) -> int: 187 - if argv is None: 188 - argv = sys.argv[1:] 189 - if len(argv) != 1 or argv[0] not in {"check", "install", "uninstall"}: 190 - sys.stderr.write( 191 - "usage: python -m think.install_guard <check|install|uninstall>\n" 192 - ) 193 - return 2 194 - 393 + parser = argparse.ArgumentParser(prog="python -m think.install_guard") 394 + subparsers = parser.add_subparsers(dest="cmd", required=True) 395 + subparsers.add_parser("check") 396 + install_parser = subparsers.add_parser("install") 397 + install_parser.add_argument("--force", action="store_true") 398 + subparsers.add_parser("uninstall") 399 + args = parser.parse_args(argv) 195 400 curdir = Path.cwd().resolve() 196 - if argv[0] == "check": 401 + if args.cmd == "check": 197 402 return cmd_check(curdir) 198 - if argv[0] == "install": 199 - return cmd_install(curdir) 403 + if args.cmd == "install": 404 + return cmd_install(curdir, force=args.force) 200 405 return cmd_uninstall(curdir) 201 406 202 407
+13 -9
think/service.py
··· 55 55 56 56 57 57 def _sol_bin() -> str: 58 - """Return absolute path to the sol binary in the current venv.""" 59 - return str(Path(sys.executable).parent / "sol") 58 + """Return absolute path to the managed sol wrapper.""" 59 + return str(Path.home() / ".local" / "bin" / "sol") 60 60 61 61 62 62 def _collect_env() -> dict[str, str]: 63 63 """Collect environment variables for the service file. 64 64 65 - Captures HOME and PATH (with venv bin prepended). The real PATH is read 66 - from os.environ so installed services inherit the shell's tool visibility. 67 - Falls back to /usr/local/bin:/usr/bin:/bin if PATH is unset. API keys are 68 - NOT written into service files — the supervisor reads them from journal.json 69 - at process startup via setup_cli(). Never propagate _SOLSTONE_JOURNAL_OVERRIDE 70 - into service files — installed services should use default path resolution. 65 + Captures HOME and PATH (with venv bin prepended). Real PATH is read 66 + from os.environ so installed services inherit the shell's tool 67 + visibility. Falls back to /usr/local/bin:/usr/bin:/bin if PATH is unset. 68 + API keys are NOT written into service files — the supervisor reads them 69 + from journal.json at process startup via setup_cli(). 70 + 71 + Never propagate SOLSTONE_JOURNAL into service files. Installed services 72 + invoke ~/.local/bin/sol, which is a managed wrapper that sets 73 + SOLSTONE_JOURNAL itself. The service's job is to start the wrapper, not 74 + to configure the journal path. 71 75 """ 72 76 venv_bin = str(Path(sys.executable).parent) 73 77 base_path = os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin") ··· 147 151 if not extracted.endswith("/sol"): 148 152 continue 149 153 150 - if extracted == current and Path(extracted).exists(): 154 + if extracted == current: 151 155 continue 152 156 153 157 result = subprocess.run(
+2
think/sol_cli.py
··· 46 46 "top": "think.top", 47 47 "health": "think.health_cli", 48 48 "notify": "think.notify_cli", 49 + "config": "think.config_cli", 49 50 "password": "think.password_cli", 50 51 "streams": "think.streams", 51 52 "segment": "think.segment", ··· 126 127 ], 127 128 "Specialized tools": [ 128 129 "password", 130 + "config", 129 131 "streams", 130 132 "segment", 131 133 "journal-stats",
+52 -26
think/utils.py
··· 35 35 EXIT_TEMPFAIL = 75 36 36 37 37 38 + class SolstoneNotConfigured(RuntimeError): 39 + def __init__( 40 + self, 41 + message: str, 42 + *, 43 + path: str | None = None, 44 + error: OSError | None = None, 45 + ): 46 + super().__init__(message) 47 + self.path = path 48 + self.error = error 49 + 50 + 38 51 def now_ms() -> int: 39 52 """Return current time as Unix epoch milliseconds.""" 40 53 return int(time.time() * 1000) ··· 89 102 90 103 91 104 def get_journal_info() -> tuple[str, str]: 92 - """Return the journal path and its source. 105 + """Resolve the journal path and its source. 106 + 107 + Returns ``(path, source)`` where source is one of ``{"env", "source"}``: 108 + 109 + - ``"env"`` — ``SOLSTONE_JOURNAL`` is set 110 + - ``"source"`` — running from a source checkout; journal is 111 + ``<project_root>/journal`` 93 112 94 - Returns 95 - ------- 96 - tuple[str, str] 97 - (path, source) where source is "override" when 98 - _SOLSTONE_JOURNAL_OVERRIDE is set, otherwise "project". 113 + Raises ``SolstoneNotConfigured`` if neither branch resolves. The wrapper 114 + at ``~/.local/bin/sol`` is responsible for setting ``SOLSTONE_JOURNAL`` 115 + on installed runs; tests set it via the autouse fixture. 99 116 """ 100 - override = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 101 - if override: 102 - return override, "override" 117 + env_path = os.environ.get("SOLSTONE_JOURNAL") 118 + if env_path: 119 + return env_path, "env" 103 120 104 - journal = str(Path(get_project_root()) / "journal") 105 - return journal, "project" 121 + project_root = Path(get_project_root()) 122 + if (project_root / "pyproject.toml").exists() and (project_root / ".git").exists(): 123 + return str(project_root / "journal"), "source" 124 + 125 + raise SolstoneNotConfigured( 126 + "solstone is not configured: set SOLSTONE_JOURNAL or run from a " 127 + f"source checkout (project_root={project_root})" 128 + ) 106 129 107 130 108 131 def get_journal() -> str: 109 - """Return the journal path: <project_root>/journal/ 132 + """Return the journal path. Auto-creates the directory. 110 133 111 - The journal always lives at ./journal/ relative to the solstone 112 - project root. Auto-creates the directory if it doesn't exist. 134 + Reads ``SOLSTONE_JOURNAL`` if set, otherwise falls back to the 135 + source-tree journal at ``<project_root>/journal``. Raises 136 + ``SolstoneNotConfigured`` if neither branch resolves or if mkdir fails. 113 137 114 138 Trust this function — never bypass it, cache its result, or set 115 - _SOLSTONE_JOURNAL_OVERRIDE from application code. The env var 116 - exists for external use only (tests, Makefile sandboxes). See 117 - ``docs/environment.md``. 139 + ``SOLSTONE_JOURNAL`` from application code, agent prompts, subprocess 140 + environments, or service files. The wrapper at ``~/.local/bin/sol`` is 141 + the canonical setter; tests use the autouse fixture; everywhere else, 142 + let it resolve on its own. See ``docs/environment.md``. 118 143 """ 119 - override = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") 120 - if override: 121 - os.makedirs(override, exist_ok=True) 122 - return override 123 - 124 - project_root = Path(__file__).resolve().parent.parent 125 - journal = str(project_root / "journal") 126 - os.makedirs(journal, exist_ok=True) 127 - return journal 144 + path, source = get_journal_info() 145 + try: 146 + os.makedirs(path, exist_ok=True) 147 + except OSError as exc: 148 + raise SolstoneNotConfigured( 149 + f"could not create journal directory ({source}): {path}: {exc}", 150 + path=path, 151 + error=exc, 152 + ) from exc 153 + return path 128 154 129 155 130 156 def resolve_journal_path(journal: str | Path, rel: str) -> Path: