personal memory agent
0
fork

Configure Feed

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

Fix coverage warnings from orphaned .pyc files in muse/

Tests were writing temporary hook/prompt files directly into muse/ and
only cleaning up the .py sources, leaving .pyc bytecache behind.
Coverage's source=muse directive then tried to report on those orphans.

- Monkeypatch think.muse.MUSE_DIR to tmp_path in 8 tests so test files
never touch the real muse/ directory
- Remove try/finally cleanup blocks (tmp_path auto-cleans)
- Add [report] omit for __pycache__ in .coveragerc as defense in depth
- Delete 6 stale .pyc files from muse/__pycache__/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+257 -294
+4
.coveragerc
··· 11 11 */__pycache__/* 12 12 */site-packages/* 13 13 config*.py 14 + 15 + [report] 16 + omit = 17 + */__pycache__/*
+112 -128
tests/test_generate_full.py
··· 73 73 mod = importlib.import_module("think.agents") 74 74 copy_day(tmp_path) 75 75 76 - # Create a test generator in muse directory (with explicit sources) 77 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 78 - test_generator = muse_dir / "test_gen.md" 76 + import think.muse 77 + 78 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 79 + 80 + test_generator = tmp_path / "test_gen.md" 79 81 test_generator.write_text( 80 82 '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 81 83 ) 82 84 83 - try: 84 - # Mock the underlying generation function in think.models 85 - import think.models 86 - 87 - monkeypatch.setattr( 88 - think.models, 89 - "generate_with_result", 90 - lambda *a, **k: MOCK_RESULT, 91 - ) 92 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 93 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 85 + # Mock the underlying generation function in think.models 86 + import think.models 94 87 95 - config = { 96 - "name": "test_gen", 97 - "day": "20240101", 98 - "output": "md", 99 - "provider": "google", 100 - "model": "gemini-2.0-flash", 101 - } 88 + monkeypatch.setattr( 89 + think.models, 90 + "generate_with_result", 91 + lambda *a, **k: MOCK_RESULT, 92 + ) 93 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 94 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 102 95 103 - events = run_generator_with_config(mod, config, monkeypatch) 96 + config = { 97 + "name": "test_gen", 98 + "day": "20240101", 99 + "output": "md", 100 + "provider": "google", 101 + "model": "gemini-2.0-flash", 102 + } 104 103 105 - # Should have start and finish events 106 - assert len(events) >= 2 107 - assert events[0]["event"] == "start" 108 - assert events[0]["name"] == "test_gen" 104 + events = run_generator_with_config(mod, config, monkeypatch) 109 105 110 - # Find finish event 111 - finish_events = [e for e in events if e["event"] == "finish"] 112 - assert len(finish_events) == 1 113 - assert finish_events[0]["result"] == MOCK_RESULT["text"] 106 + # Should have start and finish events 107 + assert len(events) >= 2 108 + assert events[0]["event"] == "start" 109 + assert events[0]["name"] == "test_gen" 114 110 115 - finally: 116 - if test_generator.exists(): 117 - test_generator.unlink() 111 + # Find finish event 112 + finish_events = [e for e in events if e["event"] == "finish"] 113 + assert len(finish_events) == 1 114 + assert finish_events[0]["result"] == MOCK_RESULT["text"] 118 115 119 116 120 117 def test_generate_hook_invoked_with_context(tmp_path, monkeypatch): ··· 122 119 mod = importlib.import_module("think.agents") 123 120 copy_day(tmp_path) 124 121 125 - # Create the hook file in muse/ directory 126 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 127 - hook_file = muse_dir / "test_hook.py" 122 + import think.muse 123 + 124 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 125 + 126 + hook_file = tmp_path / "test_hook.py" 128 127 hook_file.write_text(""" 129 128 def post_process(result, context): 130 129 import json ··· 145 144 return None 146 145 """) 147 146 148 - # Create generator with hook (new format, with explicit sources) 149 - test_generator = muse_dir / "hooked_gen.md" 147 + test_generator = tmp_path / "hooked_gen.md" 150 148 test_generator.write_text( 151 149 '{\n "type": "generate",\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "test_hook"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 152 150 ) 153 151 154 - try: 155 - # Mock the underlying generation function in think.models 156 - import think.models 152 + # Mock the underlying generation function in think.models 153 + import think.models 157 154 158 - monkeypatch.setattr( 159 - think.models, 160 - "generate_with_result", 161 - lambda *a, **k: MOCK_RESULT, 162 - ) 163 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 164 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 165 - 166 - config = { 167 - "name": "hooked_gen", 168 - "day": "20240101", 169 - "output": "md", 170 - "provider": "google", 171 - "model": "gemini-2.0-flash", 172 - } 155 + monkeypatch.setattr( 156 + think.models, 157 + "generate_with_result", 158 + lambda *a, **k: MOCK_RESULT, 159 + ) 160 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 161 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 173 162 174 - events = run_generator_with_config(mod, config, monkeypatch) 163 + config = { 164 + "name": "hooked_gen", 165 + "day": "20240101", 166 + "output": "md", 167 + "provider": "google", 168 + "model": "gemini-2.0-flash", 169 + } 175 170 176 - # Should have start and finish events 177 - finish_events = [e for e in events if e["event"] == "finish"] 178 - assert len(finish_events) == 1 171 + events = run_generator_with_config(mod, config, monkeypatch) 179 172 180 - # Read captured context 181 - captured_path = tmp_path / "20240101" / "agents" / "context_captured.json" 182 - captured = json.loads(captured_path.read_text()) 173 + # Should have start and finish events 174 + finish_events = [e for e in events if e["event"] == "finish"] 175 + assert len(finish_events) == 1 183 176 184 - assert captured["day"] == "20240101" 185 - assert captured["segment"] is None 186 - # span_mode is a bool in the new config structure 187 - assert captured["span"] is False 188 - assert captured["name"] == "hooked_gen" 189 - assert captured["has_transcript"] is True 190 - assert captured["has_hook"] is True # Frontmatter fields now directly in config 177 + # Read captured context 178 + captured_path = tmp_path / "20240101" / "agents" / "context_captured.json" 179 + captured = json.loads(captured_path.read_text()) 191 180 192 - finally: 193 - # Clean up test files 194 - if hook_file.exists(): 195 - hook_file.unlink() 196 - if test_generator.exists(): 197 - test_generator.unlink() 181 + assert captured["day"] == "20240101" 182 + assert captured["segment"] is None 183 + # span_mode is a bool in the new config structure 184 + assert captured["span"] is False 185 + assert captured["name"] == "hooked_gen" 186 + assert captured["has_transcript"] is True 187 + assert captured["has_hook"] is True # Frontmatter fields now directly in config 198 188 199 189 200 190 def test_generate_without_hook_succeeds(tmp_path, monkeypatch): ··· 202 192 mod = importlib.import_module("think.agents") 203 193 copy_day(tmp_path) 204 194 205 - # Create generator without hook (with explicit sources) 206 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 207 - test_generator = muse_dir / "nohook_gen.md" 195 + import think.muse 196 + 197 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 198 + 199 + test_generator = tmp_path / "nohook_gen.md" 208 200 test_generator.write_text( 209 201 '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nNo hook prompt' 210 202 ) 211 203 212 - try: 213 - # Mock the underlying generation function in think.models 214 - import think.models 204 + # Mock the underlying generation function in think.models 205 + import think.models 215 206 216 - monkeypatch.setattr( 217 - think.models, 218 - "generate_with_result", 219 - lambda *a, **k: MOCK_RESULT, 220 - ) 221 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 222 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 223 - 224 - config = { 225 - "name": "nohook_gen", 226 - "day": "20240101", 227 - "output": "md", 228 - "provider": "google", 229 - "model": "gemini-2.0-flash", 230 - } 207 + monkeypatch.setattr( 208 + think.models, 209 + "generate_with_result", 210 + lambda *a, **k: MOCK_RESULT, 211 + ) 212 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 213 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 231 214 232 - events = run_generator_with_config(mod, config, monkeypatch) 215 + config = { 216 + "name": "nohook_gen", 217 + "day": "20240101", 218 + "output": "md", 219 + "provider": "google", 220 + "model": "gemini-2.0-flash", 221 + } 233 222 234 - # Should have start and finish events 235 - assert len(events) >= 2 236 - finish_events = [e for e in events if e["event"] == "finish"] 237 - assert len(finish_events) == 1 238 - assert finish_events[0]["result"] == MOCK_RESULT["text"] 223 + events = run_generator_with_config(mod, config, monkeypatch) 239 224 240 - finally: 241 - if test_generator.exists(): 242 - test_generator.unlink() 225 + # Should have start and finish events 226 + assert len(events) >= 2 227 + finish_events = [e for e in events if e["event"] == "finish"] 228 + assert len(finish_events) == 1 229 + assert finish_events[0]["result"] == MOCK_RESULT["text"] 243 230 244 231 245 232 def test_generate_error_event_on_missing_generator(tmp_path, monkeypatch): ··· 272 259 day_dir = day_path("20240101") 273 260 day_dir.mkdir(parents=True, exist_ok=True) 274 261 275 - # Create a test generator (with explicit sources) 276 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 277 - test_generator = muse_dir / "empty_gen.md" 262 + import think.muse 263 + 264 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 265 + 266 + test_generator = tmp_path / "empty_gen.md" 278 267 test_generator.write_text( 279 268 '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 280 269 ) 281 270 282 - try: 283 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 284 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 271 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 272 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 285 273 286 - config = { 287 - "name": "empty_gen", 288 - "day": "20240101", 289 - "output": "md", 290 - "provider": "google", 291 - "model": "gemini-2.0-flash", 292 - } 293 - 294 - events = run_generator_with_config(mod, config, monkeypatch) 274 + config = { 275 + "name": "empty_gen", 276 + "day": "20240101", 277 + "output": "md", 278 + "provider": "google", 279 + "model": "gemini-2.0-flash", 280 + } 295 281 296 - # Should have start and finish with skipped 297 - finish_events = [e for e in events if e["event"] == "finish"] 298 - assert len(finish_events) == 1 299 - assert finish_events[0].get("skipped") == "no_input" 282 + events = run_generator_with_config(mod, config, monkeypatch) 300 283 301 - finally: 302 - if test_generator.exists(): 303 - test_generator.unlink() 284 + # Should have start and finish with skipped 285 + finish_events = [e for e in events if e["event"] == "finish"] 286 + assert len(finish_events) == 1 287 + assert finish_events[0].get("skipped") == "no_input" 304 288 305 289 306 290 def test_named_hook_resolution(tmp_path, monkeypatch):
+141 -166
tests/test_output_hooks.py
··· 163 163 mod = importlib.import_module("think.agents") 164 164 copy_day(tmp_path) 165 165 166 - # Create generator with hook in muse directory 167 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 166 + # Use tmp_path as muse directory to avoid polluting real muse/ 167 + import think.muse 168 168 169 - prompt_file = muse_dir / "hooked_test.md" 169 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 170 + 171 + prompt_file = tmp_path / "hooked_test.md" 170 172 prompt_file.write_text( 171 173 '{\n "type": "generate",\n "title": "Hooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "hooked_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 172 174 ) 173 175 174 - hook_file = muse_dir / "hooked_test.py" 176 + hook_file = tmp_path / "hooked_test.py" 175 177 hook_file.write_text(""" 176 178 def post_process(result, context): 177 179 # Verify context has expected fields ··· 181 183 return result + "\\n\\n## Hook was here" 182 184 """) 183 185 184 - try: 185 - # Mock the underlying generation function in think.models 186 - import think.models 186 + # Mock the underlying generation function in think.models 187 + import think.models 187 188 188 - monkeypatch.setattr( 189 - think.models, 190 - "generate_with_result", 191 - lambda *a, **k: MOCK_RESULT, 192 - ) 193 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 194 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 189 + monkeypatch.setattr( 190 + think.models, 191 + "generate_with_result", 192 + lambda *a, **k: MOCK_RESULT, 193 + ) 194 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 195 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 195 196 196 - config = { 197 - "name": "hooked_test", 198 - "day": "20240101", 199 - "output": "md", 200 - "provider": "google", 201 - "model": "gemini-2.0-flash", 202 - } 197 + config = { 198 + "name": "hooked_test", 199 + "day": "20240101", 200 + "output": "md", 201 + "provider": "google", 202 + "model": "gemini-2.0-flash", 203 + } 203 204 204 - events = run_generator_with_config(mod, config, monkeypatch) 205 + events = run_generator_with_config(mod, config, monkeypatch) 205 206 206 - # Find finish event 207 - finish_events = [e for e in events if e["event"] == "finish"] 208 - assert len(finish_events) == 1 207 + # Find finish event 208 + finish_events = [e for e in events if e["event"] == "finish"] 209 + assert len(finish_events) == 1 209 210 210 - content = finish_events[0]["result"] 211 - assert "## Original Result" in content 212 - assert "## Hook was here" in content 213 - 214 - finally: 215 - if hook_file.exists(): 216 - hook_file.unlink() 217 - if prompt_file.exists(): 218 - prompt_file.unlink() 211 + content = finish_events[0]["result"] 212 + assert "## Original Result" in content 213 + assert "## Hook was here" in content 219 214 220 215 221 216 def test_output_hook_returns_none(tmp_path, monkeypatch): ··· 223 218 mod = importlib.import_module("think.agents") 224 219 copy_day(tmp_path) 225 220 226 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 221 + import think.muse 222 + 223 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 227 224 228 - prompt_file = muse_dir / "noop_test.md" 225 + prompt_file = tmp_path / "noop_test.md" 229 226 prompt_file.write_text( 230 227 '{\n "type": "generate",\n "title": "Noop",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "noop_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 231 228 ) 232 229 233 - hook_file = muse_dir / "noop_test.py" 230 + hook_file = tmp_path / "noop_test.py" 234 231 hook_file.write_text(""" 235 232 def post_process(result, context): 236 233 return None # Signal to use original 237 234 """) 238 235 239 - try: 240 - # Mock the underlying generation function in think.models 241 - import think.models 242 - 243 - monkeypatch.setattr( 244 - think.models, 245 - "generate_with_result", 246 - lambda *a, **k: MOCK_RESULT, 247 - ) 248 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 249 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 236 + # Mock the underlying generation function in think.models 237 + import think.models 250 238 251 - config = { 252 - "name": "noop_test", 253 - "day": "20240101", 254 - "output": "md", 255 - "provider": "google", 256 - "model": "gemini-2.0-flash", 257 - } 239 + monkeypatch.setattr( 240 + think.models, 241 + "generate_with_result", 242 + lambda *a, **k: MOCK_RESULT, 243 + ) 244 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 245 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 258 246 259 - events = run_generator_with_config(mod, config, monkeypatch) 247 + config = { 248 + "name": "noop_test", 249 + "day": "20240101", 250 + "output": "md", 251 + "provider": "google", 252 + "model": "gemini-2.0-flash", 253 + } 260 254 261 - finish_events = [e for e in events if e["event"] == "finish"] 262 - assert len(finish_events) == 1 263 - assert finish_events[0]["result"] == MOCK_RESULT["text"] 255 + events = run_generator_with_config(mod, config, monkeypatch) 264 256 265 - finally: 266 - if hook_file.exists(): 267 - hook_file.unlink() 268 - if prompt_file.exists(): 269 - prompt_file.unlink() 257 + finish_events = [e for e in events if e["event"] == "finish"] 258 + assert len(finish_events) == 1 259 + assert finish_events[0]["result"] == MOCK_RESULT["text"] 270 260 271 261 272 262 def test_output_hook_error_fallback(tmp_path, monkeypatch): ··· 274 264 mod = importlib.import_module("think.agents") 275 265 copy_day(tmp_path) 276 266 277 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 267 + import think.muse 278 268 279 - prompt_file = muse_dir / "broken_test.md" 269 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 270 + 271 + prompt_file = tmp_path / "broken_test.md" 280 272 prompt_file.write_text( 281 273 '{\n "type": "generate",\n "title": "Broken",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"post": "broken_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nTest prompt' 282 274 ) 283 275 284 - hook_file = muse_dir / "broken_test.py" 276 + hook_file = tmp_path / "broken_test.py" 285 277 hook_file.write_text(""" 286 278 def post_process(result, context): 287 279 raise RuntimeError("Hook exploded!") 288 280 """) 289 281 290 - try: 291 - # Mock the underlying generation function in think.models 292 - import think.models 282 + # Mock the underlying generation function in think.models 283 + import think.models 293 284 294 - monkeypatch.setattr( 295 - think.models, 296 - "generate_with_result", 297 - lambda *a, **k: MOCK_RESULT, 298 - ) 299 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 300 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 285 + monkeypatch.setattr( 286 + think.models, 287 + "generate_with_result", 288 + lambda *a, **k: MOCK_RESULT, 289 + ) 290 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 291 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 301 292 302 - config = { 303 - "name": "broken_test", 304 - "day": "20240101", 305 - "output": "md", 306 - "provider": "google", 307 - "model": "gemini-2.0-flash", 308 - } 293 + config = { 294 + "name": "broken_test", 295 + "day": "20240101", 296 + "output": "md", 297 + "provider": "google", 298 + "model": "gemini-2.0-flash", 299 + } 309 300 310 - # Should not raise, should fall back gracefully 311 - events = run_generator_with_config(mod, config, monkeypatch) 301 + # Should not raise, should fall back gracefully 302 + events = run_generator_with_config(mod, config, monkeypatch) 312 303 313 - finish_events = [e for e in events if e["event"] == "finish"] 314 - assert len(finish_events) == 1 315 - assert finish_events[0]["result"] == MOCK_RESULT["text"] 316 - 317 - finally: 318 - if hook_file.exists(): 319 - hook_file.unlink() 320 - if prompt_file.exists(): 321 - prompt_file.unlink() 304 + finish_events = [e for e in events if e["event"] == "finish"] 305 + assert len(finish_events) == 1 306 + assert finish_events[0]["result"] == MOCK_RESULT["text"] 322 307 323 308 324 309 # ============================================================================= ··· 396 381 mod = importlib.import_module("think.agents") 397 382 copy_day(tmp_path) 398 383 399 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 384 + import think.muse 385 + 386 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 400 387 401 - prompt_file = muse_dir / "prehooked_test.md" 388 + prompt_file = tmp_path / "prehooked_test.md" 402 389 prompt_file.write_text( 403 390 '{\n "type": "generate",\n "title": "Prehooked",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "prehooked_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nOriginal prompt' 404 391 ) 405 392 406 - hook_file = muse_dir / "prehooked_test.py" 393 + hook_file = tmp_path / "prehooked_test.py" 407 394 hook_file.write_text(""" 408 395 def pre_process(context): 409 396 # Verify context has expected fields ··· 414 401 return {"prompt": context["prompt"] + " [pre-processed]"} 415 402 """) 416 403 417 - try: 418 - # Track what generate_with_result receives 419 - received_kwargs = {} 404 + # Track what generate_with_result receives 405 + received_kwargs = {} 420 406 421 - def mock_generate(*args, **kwargs): 422 - received_kwargs.update(kwargs) 423 - received_kwargs["contents"] = args[0] if args else kwargs.get("contents") 424 - return MOCK_RESULT 407 + def mock_generate(*args, **kwargs): 408 + received_kwargs.update(kwargs) 409 + received_kwargs["contents"] = args[0] if args else kwargs.get("contents") 410 + return MOCK_RESULT 425 411 426 - import think.models 412 + import think.models 427 413 428 - monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 429 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 430 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 414 + monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 415 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 416 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 431 417 432 - config = { 433 - "name": "prehooked_test", 434 - "day": "20240101", 435 - "output": "md", 436 - "provider": "google", 437 - "model": "gemini-2.0-flash", 438 - } 418 + config = { 419 + "name": "prehooked_test", 420 + "day": "20240101", 421 + "output": "md", 422 + "provider": "google", 423 + "model": "gemini-2.0-flash", 424 + } 439 425 440 - events = run_generator_with_config(mod, config, monkeypatch) 426 + events = run_generator_with_config(mod, config, monkeypatch) 441 427 442 - # Verify pre-hook modified the prompt - check in contents 443 - contents = received_kwargs.get("contents", []) 444 - # The prompt should contain [pre-processed] 445 - prompt_found = any("[pre-processed]" in str(c) for c in contents) 446 - assert prompt_found, f"Expected [pre-processed] in contents: {contents}" 428 + # Verify pre-hook modified the prompt - check in contents 429 + contents = received_kwargs.get("contents", []) 430 + # The prompt should contain [pre-processed] 431 + prompt_found = any("[pre-processed]" in str(c) for c in contents) 432 + assert prompt_found, f"Expected [pre-processed] in contents: {contents}" 447 433 448 - # Verify generator still completed successfully 449 - finish_events = [e for e in events if e["event"] == "finish"] 450 - assert len(finish_events) == 1 451 - 452 - finally: 453 - if hook_file.exists(): 454 - hook_file.unlink() 455 - if prompt_file.exists(): 456 - prompt_file.unlink() 434 + # Verify generator still completed successfully 435 + finish_events = [e for e in events if e["event"] == "finish"] 436 + assert len(finish_events) == 1 457 437 458 438 459 439 def test_both_pre_and_post_hooks(tmp_path, monkeypatch): ··· 461 441 mod = importlib.import_module("think.agents") 462 442 copy_day(tmp_path) 463 443 464 - muse_dir = Path(mod.__file__).resolve().parent.parent / "muse" 444 + import think.muse 445 + 446 + monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) 465 447 466 - prompt_file = muse_dir / "both_hooks_test.md" 448 + prompt_file = tmp_path / "both_hooks_test.md" 467 449 prompt_file.write_text( 468 450 '{\n "type": "generate",\n "title": "Both Hooks",\n "schedule": "daily",\n "priority": 10,\n "output": "md",\n "hook": {"pre": "both_hooks_test", "post": "both_hooks_test"},\n "instructions": {"system": "journal", "sources": {"audio": true, "screen": true}}\n}\n\nOriginal prompt' 469 451 ) 470 452 471 - hook_file = muse_dir / "both_hooks_test.py" 453 + hook_file = tmp_path / "both_hooks_test.py" 472 454 hook_file.write_text(""" 473 455 def pre_process(context): 474 456 return {"prompt": context["prompt"] + " [pre]"} ··· 477 459 return result + "\\n\\n[post]" 478 460 """) 479 461 480 - try: 481 - received_kwargs = {} 462 + received_kwargs = {} 482 463 483 - def mock_generate(*args, **kwargs): 484 - received_kwargs.update(kwargs) 485 - received_kwargs["contents"] = args[0] if args else kwargs.get("contents") 486 - return MOCK_RESULT 464 + def mock_generate(*args, **kwargs): 465 + received_kwargs.update(kwargs) 466 + received_kwargs["contents"] = args[0] if args else kwargs.get("contents") 467 + return MOCK_RESULT 487 468 488 - import think.models 469 + import think.models 489 470 490 - monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 491 - monkeypatch.setenv("GOOGLE_API_KEY", "x") 492 - monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 493 - 494 - config = { 495 - "name": "both_hooks_test", 496 - "day": "20240101", 497 - "output": "md", 498 - "provider": "google", 499 - "model": "gemini-2.0-flash", 500 - } 471 + monkeypatch.setattr(think.models, "generate_with_result", mock_generate) 472 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 473 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 501 474 502 - events = run_generator_with_config(mod, config, monkeypatch) 475 + config = { 476 + "name": "both_hooks_test", 477 + "day": "20240101", 478 + "output": "md", 479 + "provider": "google", 480 + "model": "gemini-2.0-flash", 481 + } 503 482 504 - # Verify pre-hook modified the prompt - check in contents 505 - contents = received_kwargs.get("contents", []) 506 - prompt_found = any("[pre]" in str(c) for c in contents) 507 - assert prompt_found, f"Expected [pre] in contents: {contents}" 483 + events = run_generator_with_config(mod, config, monkeypatch) 508 484 509 - # Verify post-hook modified the result 510 - finish_events = [e for e in events if e["event"] == "finish"] 511 - assert len(finish_events) == 1 512 - assert "[post]" in finish_events[0]["result"] 485 + # Verify pre-hook modified the prompt - check in contents 486 + contents = received_kwargs.get("contents", []) 487 + prompt_found = any("[pre]" in str(c) for c in contents) 488 + assert prompt_found, f"Expected [pre] in contents: {contents}" 513 489 514 - finally: 515 - if hook_file.exists(): 516 - hook_file.unlink() 517 - if prompt_file.exists(): 518 - prompt_file.unlink() 490 + # Verify post-hook modified the result 491 + finish_events = [e for e in events if e["event"] == "finish"] 492 + assert len(finish_events) == 1 493 + assert "[post]" in finish_events[0]["result"]