From ed4fe731dfbc68f161328e1930f4fa95eeee82ea Mon Sep 17 00:00:00 2001 From: windyboy Date: Wed, 31 Dec 2025 17:57:12 +0800 Subject: [PATCH] Remove ignored files from cache --- ./.git/HEAD | 1 - ./.git/config | 13 - ./.git/description | 1 - ./.git/hooks/applypatch-msg.sample | 15 - ./.git/hooks/commit-msg.sample | 24 - ./.git/hooks/fsmonitor-watchman.sample | 174 --- ./.git/hooks/post-update.sample | 8 - ./.git/hooks/pre-applypatch.sample | 14 - ./.git/hooks/pre-commit.sample | 49 - ./.git/hooks/pre-merge-commit.sample | 13 - ./.git/hooks/pre-push.sample | 53 - ./.git/hooks/pre-rebase.sample | 169 --- ./.git/hooks/pre-receive.sample | 24 - ./.git/hooks/prepare-commit-msg.sample | 42 - ./.git/hooks/push-to-checkout.sample | 78 - ./.git/hooks/sendemail-validate.sample | 77 - ./.git/hooks/update.sample | 128 -- ./.git/info/exclude | 6 - .../00/449ff03053c280c520d16e89966203c2e8b81d | Bin 292 -> 0 bytes .../03/4f72ed25667cb3deffdcc6d465f9848509138e | Bin 369 -> 0 bytes .../06/37a088a01e8ddab3bf3fa98dbe804cbde1a0dc | Bin 17 -> 0 bytes .../07/bdcc9321511a09acd06ce940d5ebcb7779f7d2 | Bin 11376 -> 0 bytes .../07/c927356f27b94033174f603d00a81d6d4aaa48 | Bin 36 -> 0 bytes .../07/cd8c956ab4ecf04ab6ff2486d36bce00970e19 | Bin 208 -> 0 bytes .../07/db11d87c859f7cd01b235d4967f385ac3278a7 | Bin 2836 -> 0 bytes .../08/11ab93fca69d22511a4d25c1e9bcc6aa2e499d | 4 - .../08/ba29183c1dfdec98c1ae013a7478021f0493d4 | Bin 46 -> 0 bytes .../09/b020ff2a1f424e35e877ce897f4e452ba51275 | Bin 1771 -> 0 bytes .../0a/04893c2e935f261ad508e5e8e9dec92a85d3fb | Bin 2520 -> 0 bytes .../11/ae2c5d3a87cb269933290fe25a6df0bd0e5aef | Bin 3579 -> 0 bytes .../16/05b976404982b0f1551ce1c9cb04c1fbe26d8e | Bin 60 -> 0 bytes .../16/0fa0cf472dbf06c9ed66353b666275cc45223e | Bin 72 -> 0 bytes .../16/c9e9236fd6eec677b275348766ae2d07c83f87 | Bin 15308 -> 0 bytes .../16/dd449404cf6f10aa080544c17d367b1cfa36bf | Bin 3309 -> 0 bytes .../18/9f6c0865274aa00a9a3454b15ea6b426acb17e | Bin 2611 -> 0 bytes .../19/0917cb4de45c030117d03705c9498c7e213c4d | Bin 347 -> 0 bytes .../1a/50372ba7ed5fb7b7a7622adf0b101cb7c80ec0 | Bin 179 -> 0 bytes .../1a/75f16df054f2c5acceb6dfa08dfb10481bc063 | Bin 5494 -> 0 bytes .../1a/e21a592999db50b5d51dd1e63c87171ad26a9e | Bin 1843 -> 0 bytes .../1c/a056129dbe5c84dd17e68dae716ca209cfc2f0 | Bin 6735 -> 0 bytes .../1d/2eb0843eab5c2f654da4829b76b1fab9de9b91 | Bin 4737 -> 0 bytes .../21/5a4e3c2eb5908c2f7c7859a8734a45ede055da | Bin 3763 -> 0 bytes .../24/4654666579e0e718b48c5d9ec659015e4d566d | Bin 8479 -> 0 bytes .../24/58c27830928d35ed4f7d9729be4f03bb36e543 | Bin 5768 -> 0 bytes .../25/1b65444d5270c57616041d76f8b1e969879388 | Bin 2985 -> 0 bytes .../25/40beeaa5edf975e19ae9e4c686ca5ec56098ed | Bin 2160 -> 0 bytes .../25/8f8a6703236ed9dfb322c41ca3cf67211df42c | Bin 241 -> 0 bytes .../25/a8d2c5f9ea7918a2489d89d2cfa99875a5474f | Bin 1996 -> 0 bytes .../26/b16184e59b49037059cbc8d393feca8c28b618 | Bin 3815 -> 0 bytes .../29/57ac381cd898d5e8df9fe39a364541ea92f3a3 | Bin 8472 -> 0 bytes .../29/6b6de1a040c9f55d3a696cff14fcca8a00309b | Bin 7965 -> 0 bytes .../29/b7a89e959b9cac6fcb88ddbd1f98510f41908a | Bin 3793 -> 0 bytes .../2d/071a7891e073d79eef49d99466fb2166db756c | Bin 223 -> 0 bytes .../2d/304f04e01e4642d3e3967a79bf8ee14068cc85 | Bin 3752 -> 0 bytes .../2d/47277733083a7d5bb003fd09c7771a84b6a8e0 | Bin 4928 -> 0 bytes .../2d/592941081b725f398b85952bf8c8bcbc0738f1 | Bin 242 -> 0 bytes .../2d/8a7d8b75f1657219604e8a796b4c5f97edff34 | Bin 4527 -> 0 bytes .../2d/f39f3b4c20f845dddc2b685847241cd89aa789 | Bin 165 -> 0 bytes .../2e/b5e2cc7f9e2baa1e29ceb848bbbcc212d0f643 | Bin 3034 -> 0 bytes .../2f/99e45bfa0ac5b5aff91bbb946798943b209bd1 | Bin 4903 -> 0 bytes .../2f/d4d0f8d97c50440b62e47a3f3e027cae115e70 | Bin 6014 -> 0 bytes .../31/5a2520c05e1358c2833bac37a85f1db4bdcc77 | Bin 389 -> 0 bytes .../31/9d3be980a4c324c5d70bea9d7475f50e124670 | Bin 10384 -> 0 bytes .../33/2f10f7331e8d7a42cec6a7242bbc0dcdb9027f | Bin 2139 -> 0 bytes .../33/a845ba54d09871951f4e4cea1541615028a31e | Bin 4934 -> 0 bytes .../36/7b4da555ca4317127492a964d72358f0e32087 | Bin 4437 -> 0 bytes .../36/bc3e559fb43177d99b70e4bbbd0e0d02612a76 | Bin 1073 -> 0 bytes .../38/2460d73ef14b725f41130ef05a2f2e99a1b5ac | 3 - .../39/4c150fdf47fa3950fed889f8cfccf474733668 | Bin 4729 -> 0 bytes .../3c/d644f559c90a51e2cc0508224c8ee41205fe06 | Bin 7125 -> 0 bytes .../3f/7b4780ae0ba64adfd193a60ca265954d33e709 | Bin 4015 -> 0 bytes .../45/e639d6be112eb3e32c6073ecd51ea9b69d04fd | Bin 3161 -> 0 bytes .../46/657c1ddf065b126573fcd2854cd7e388eb51a5 | Bin 2036 -> 0 bytes .../47/8f1ae2938d64030683de324a3363551d9a86a1 | Bin 3473 -> 0 bytes .../49/8b267a8c7812490d6479839c5577eaaec79d62 | Bin 77 -> 0 bytes .../4a/6fa8353e3a6632531ea3efad5cadddf8dba5b9 | 2 - .../4a/8ab4fa17dc3510701c1f796b52929a65973ca0 | Bin 6765 -> 0 bytes .../4c/76c67db4697071912a15f7b04f369a8af9beb1 | Bin 1542 -> 0 bytes .../4f/0c23ce27ffd734a22b81c92109b5603f8df1e9 | Bin 3211 -> 0 bytes .../52/cd13e06c5798897100e2e35db307bde7bf3bdc | Bin 5316 -> 0 bytes .../57/185ff087c798f9f7146e793be1edb064541d3c | Bin 20793 -> 0 bytes .../58/2f34a7e2b6a1384b3825f9fa891c914054fdd8 | Bin 259 -> 0 bytes .../58/b0a4edb15f1be307674f2c501e4b7ad34ef69f | Bin 3629 -> 0 bytes .../59/3554a4fb4e13b8597dcb44a3dcea2a5180e506 | Bin 18476 -> 0 bytes .../5b/5b36529a4b633f56457b48daaefcd016215fd9 | Bin 7535 -> 0 bytes .../64/1e5779b8317678ce9f2e781ed4d568ae4754d9 | Bin 1792 -> 0 bytes .../64/5bccb0831429aee986c3434a3c14f227bd6c0b | Bin 4438 -> 0 bytes .../64/787105e33d1d19f357b599125197419ee11053 | Bin 5358 -> 0 bytes .../66/1b025a0d8dcdd7d2e5dad661621418230e9966 | Bin 12593 -> 0 bytes .../68/392f3e5de584ee540df2e55a149d1e843ebf79 | Bin 157 -> 0 bytes .../69/4ad99197b8043fa0d8954f5ed37ec29f630217 | Bin 4416 -> 0 bytes .../6a/11d38ca2396b25e4515d14b1ea2545d5c1ec6b | Bin 3193 -> 0 bytes .../6d/5a23d6e9e5b75239f81eba9de06fcbc815c233 | Bin 2747 -> 0 bytes .../6d/e824b46d41385095c3ddb4cab9a8be4feb7843 | Bin 149 -> 0 bytes .../6e/55f323a0354b9fddc639a857ebb894d0c5a036 | Bin 5642 -> 0 bytes .../6f/5e6ebdb19822b7cee429dd468aaa16805c6c9e | Bin 1885 -> 0 bytes .../72/7fee65b945b727e139a62c996df843908ffb67 | Bin 860 -> 0 bytes .../74/3bbd51d21495f13529d971d7ef9fa8b367ffdd | Bin 3833 -> 0 bytes .../74/ee156443dc4a27675758bffc43436df8f03620 | Bin 253 -> 0 bytes .../75/51238f8b5907f092f4c5a0466046b7c4bb3b29 | Bin 76 -> 0 bytes .../75/5936e16e892c359d31c332f5fadf0eb00d5e8d | Bin 12723 -> 0 bytes .../75/7fdb88cf3a418a75b31704f9951435090da85d | Bin 2815 -> 0 bytes .../79/c024854bc984e2bc37cd104c54f9d6c6e3d13b | Bin 5774 -> 0 bytes .../79/e90c5aaf2baef9f4396c8cc670a542453ae7e3 | Bin 228 -> 0 bytes .../7d/84cd2d20604e2c7a462468e7e37b1c5d9d2538 | Bin 347 -> 0 bytes .../7e/a192291e5b2baa52949f38bacb0f8268022049 | Bin 10357 -> 0 bytes .../81/bfcc49bf7e4bee32e2049cffd6d8c196102dbf | Bin 8651 -> 0 bytes .../82/6a81bde48f220a0da379d90cdb729dfc0922e9 | Bin 253 -> 0 bytes .../83/27dfdb8294cdfbb3988ba7089bdf2b94cd182e | Bin 3303 -> 0 bytes .../86/c83b9e09aa27427d85a1d5b7f50d21e0389f15 | Bin 260 -> 0 bytes .../87/caf1f45bb4e7b509af084cfa044e4d11d5487f | Bin 1522 -> 0 bytes .../8a/c281428cae1d17e6f16f51f1b92cbe7405e5bd | Bin 5832 -> 0 bytes .../8c/b04aac4b020eb5667c964ae0e0e0ea219acf2e | Bin 9003 -> 0 bytes .../8e/8bc7f215a407467ef65c79160a26f8398de656 | Bin 64 -> 0 bytes .../8e/dde00908b3119dee73efed8dce06074b86c38d | Bin 4885 -> 0 bytes .../90/23d17919d590f1e3bcf490d5fe3d067a96d7a0 | Bin 5283 -> 0 bytes .../93/a2d4b2328f5f16624374d34abc5b59fe66b7d8 | Bin 45 -> 0 bytes .../94/f53c3644fd3c9390e5f2b18ea61caa0ef7fae5 | Bin 5476 -> 0 bytes .../96/9ef5eef4671ef48ef4b6bdf8b60f91a88b44d7 | Bin 6575 -> 0 bytes .../96/bfdb7609dc0d1a0a1e35b23e58c0ebfba1c773 | Bin 12615 -> 0 bytes .../9b/dbc86b5917da9d6e7f1dae017b8d5dafcae90f | Bin 2964 -> 0 bytes .../a0/94bfff7aa7820618464b22332d9053e700e01d | Bin 1787 -> 0 bytes .../a5/23b07b1031f7a7dbae559a82e58746a42a2f8f | Bin 6053 -> 0 bytes .../a8/13ca61b052c8018d11eb567fc0657b00792518 | Bin 197 -> 0 bytes .../a8/639fe68e6d8431c44e84baa3834e88eabe16c9 | Bin 4907 -> 0 bytes .../a8/8c41f9df6f5918c33f517b1c8b292e7027236d | Bin 12745 -> 0 bytes .../b4/2f35cef819793a338c9270d86db481627036b0 | Bin 2206 -> 0 bytes .../b5/d87cbad56c43ba21c0238fcb3560ad4b84bb66 | Bin 4882 -> 0 bytes .../b8/70d82622c1a9ca6bcaf5df639680424a1904b0 | Bin 35 -> 0 bytes .../b8/9018ced91c0a8af7f3f23ce8901870da89f3a0 | 1 - .../b8/97c0ab709ed44630b6d38107a423b4f9960b85 | Bin 33 -> 0 bytes .../b8/aa9e8c1cc4642ff0644bfb4a2690c13789015d | Bin 4064 -> 0 bytes .../bc/1a1f6167d09c909aad37280b760bb715d0f1da | Bin 53 -> 0 bytes .../bf/397e828fc58cac608bbcf9393a14ed6bef410f | Bin 6428 -> 0 bytes .../c7/157a31aaf215a5757ccd6c92ec00cada79e074 | Bin 3731 -> 0 bytes .../c7/44684ab56c48a223709a9a4d362bbd1276bfbd | Bin 9196 -> 0 bytes .../c8/da781411176db48cf36d5b98a95595c9cff9de | Bin 5279 -> 0 bytes .../ca/dcb7a5f850751001ff630205dd0e265df9d474 | Bin 4538 -> 0 bytes .../cb/380eb387281b9ebdb73f176913849a6086d2e6 | 1 - .../ce/8333fc3cd0e5180a9ae004f51b02fb4e42d37c | Bin 2998 -> 0 bytes .../ce/ac0a44aa05affeec11750b384a379848ff9018 | Bin 419 -> 0 bytes .../cf/6d3bb9a24a552399b5c3c208cc9933cdeb4d23 | Bin 2327 -> 0 bytes .../d0/1c6e22b8d48e13b209127c8224352b18973c4e | Bin 3198 -> 0 bytes .../d2/1afda8534bab95dd8c58b3303fda9ac52f6749 | Bin 9752 -> 0 bytes .../d3/46add1fddfc0997c9b66d7b0a258782d29ca1c | Bin 438 -> 0 bytes .../d4/054f300638131c731b04ec78da6249b8f2b370 | Bin 4064 -> 0 bytes .../d5/1e6373fb2af32f4a1c13b95f0f7e8a5abe6327 | Bin 1437 -> 0 bytes .../d5/b059c8779ab6e04275c326b76134c7cd45cb7f | Bin 7986 -> 0 bytes .../d7/84485e21548da0347e3af1f858cbe2bb122d41 | Bin 145 -> 0 bytes .../d8/88dc60d94295e8917694360f370dd90a924cda | Bin 6161 -> 0 bytes .../db/783106501f9ccd99a3f7f92c0483059ad52ec3 | Bin 7538 -> 0 bytes .../db/c529540e886c977e12f1472306aa9adaf78de3 | Bin 2227 -> 0 bytes .../dd/e771a8dabd7b86d1f25ef33ed382b59a746e64 | Bin 3073 -> 0 bytes .../e1/f4b83ab8e4590c165b84062197c9f12f808155 | Bin 12065 -> 0 bytes .../e2/7cd7ab186dc0322923f7d7e9d31059aba3df4d | Bin 43 -> 0 bytes .../e2/b11339b4073eba790e8b98a04459961d25d096 | Bin 202 -> 0 bytes .../e6/a33a9dbea7d256da5d758dd0c2987cd55fb5e7 | Bin 12062 -> 0 bytes .../e6/bbc0c8f80d31e787e972b5b98dea65ef56c5e6 | Bin 2750 -> 0 bytes .../e8/66b589d11555e729c1505db86028cd201f780d | Bin 3360 -> 0 bytes .../e8/b91fbe25d966aa316594e17ff846ce37e2cf80 | Bin 2977 -> 0 bytes .../ee/d008e28a5cfc0c5f830c2ccf638ccf3fed18a0 | Bin 188 -> 0 bytes .../f3/1a08505be5571fe31d90f2df416a1dbe7aa95c | Bin 7944 -> 0 bytes .../f4/6589e7d7cf999185328ed5895be23e47d93d93 | Bin 279 -> 0 bytes .../f4/fda41afe041add5c5d063f3a581900650be6f8 | Bin 5755 -> 0 bytes .../f5/7492a1b3fea6c90071afeb5a1eb9af044474ce | Bin 3339 -> 0 bytes .../f9/5bbc13a0263a974d42d01b4f86a8f598523a2f | Bin 4153 -> 0 bytes .../f9/ba597c093ec6dc5c2be41e536f00bd4eba9720 | Bin 409 -> 0 bytes .../fc/9e876a2b06e5604e0c7a5943e121faa84d9fe9 | Bin 222 -> 0 bytes .../fc/e15ad7eaa74e5682b644c84efb75334c112f95 | Bin 159 -> 0 bytes ./.gitignore | 235 --- .../unicode_data/13.0.0/charmap.json.gz | Bin 20988 -> 0 bytes .../unicode_data/13.0.0/codec-utf-8.json.gz | Bin 60 -> 0 bytes .../specs/claude-api-configuration/design.md | 387 ----- .../claude-api-configuration/requirements.md | 87 -- .../specs/claude-api-configuration/tasks.md | 169 --- .../specs/code-quality-improvements/design.md | 253 ---- .../code-quality-improvements/requirements.md | 99 -- .../specs/code-quality-improvements/tasks.md | 221 --- ./.kiro/steering/product.md | 30 - ./.kiro/steering/structure.md | 141 -- ./.kiro/steering/tech.md | 102 -- ./.pytest_cache/.gitignore | 2 - ./.pytest_cache/CACHEDIR.TAG | 4 - ./.pytest_cache/README.md | 8 - ./.pytest_cache/v/cache/lastfailed | 13 - ./.pytest_cache/v/cache/nodeids | 133 -- ./.pytest_cache/v/cache/stepwise | 1 - ./DEVELOPER_GUIDE.md | 963 ------------ ./OBSIDIAN_INTEGRATION.md | 266 ---- ./README.md | 206 --- ./README_V2.md | 185 --- ./TROUBLESHOOTING.md | 427 ------ ./__init__.py | 20 - ./__main__.py | 18 - ./__pycache__/__init__.cpython-310.pyc | Bin 559 -> 0 bytes ./__pycache__/__init__.cpython-311.pyc | Bin 649 -> 0 bytes ./__pycache__/__main__.cpython-310.pyc | Bin 398 -> 0 bytes ./__pycache__/__main__.cpython-311.pyc | Bin 553 -> 0 bytes ./__pycache__/agent_core.cpython-310.pyc | Bin 11266 -> 0 bytes ./__pycache__/agent_core.cpython-311.pyc | Bin 16847 -> 0 bytes .../api_response_validation.cpython-310.pyc | Bin 14590 -> 0 bytes ./__pycache__/chat_main.cpython-310.pyc | Bin 6242 -> 0 bytes .../claude_api_client.cpython-310.pyc | Bin 11109 -> 0 bytes ./__pycache__/config.cpython-310.pyc | Bin 9948 -> 0 bytes .../config_validation.cpython-310.pyc | Bin 40820 -> 0 bytes .../configuration_loader.cpython-310.pyc | Bin 8607 -> 0 bytes .../configuration_migrator.cpython-310.pyc | Bin 8628 -> 0 bytes ./__pycache__/date_validation.cpython-310.pyc | Bin 15072 -> 0 bytes .../dependency_manager.cpython-310.pyc | Bin 11303 -> 0 bytes ./__pycache__/error_handling.cpython-310.pyc | Bin 28254 -> 0 bytes .../input_validation.cpython-310.pyc | Bin 25762 -> 0 bytes ./__pycache__/main.cpython-310.pyc | Bin 7442 -> 0 bytes ./__pycache__/main.cpython-311.pyc | Bin 12445 -> 0 bytes ./__pycache__/path_security.cpython-310.pyc | Bin 14254 -> 0 bytes ./__pycache__/server.cpython-310.pyc | Bin 6328 -> 0 bytes ...ation_startup.cpython-310-pytest-7.3.1.pyc | Bin 8742 -> 0 bytes ...functionality.cpython-310-pytest-7.3.1.pyc | Bin 14412 -> 0 bytes ...r_integration.cpython-310-pytest-7.3.1.pyc | Bin 6804 -> 0 bytes ...e_reliability.cpython-310-pytest-7.3.1.pyc | Bin 14108 -> 0 bytes ...up_validation.cpython-310-pytest-7.3.1.pyc | Bin 18057 -> 0 bytes ./agent_core.py | 354 ----- ./api_response_validation.py | 657 -------- ./chat_main.py | 254 ---- ./claude_api_client.py | 473 ------ ./commands/__init__.py | 14 - .../__pycache__/__init__.cpython-310.pyc | Bin 371 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 307 -> 0 bytes .../organize_command.cpython-310.pyc | Bin 9834 -> 0 bytes .../organize_command.cpython-311.pyc | Bin 17525 -> 0 bytes ./commands/organize_command.py | 422 ------ ./config.example.yaml | 101 -- ./config.py | 333 ----- ./config_validation.py | 1316 ----------------- ./configuration_loader.py | 268 ---- ./configuration_migrator.py | 349 ----- ./conversation/__init__.py | 21 - .../__pycache__/__init__.cpython-310.pyc | Bin 649 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 759 -> 0 bytes .../conversation_state.cpython-310.pyc | Bin 8769 -> 0 bytes .../conversation_state.cpython-311.pyc | Bin 12686 -> 0 bytes .../conversational_agent.cpython-310.pyc | Bin 5990 -> 0 bytes .../conversational_agent.cpython-311.pyc | Bin 9537 -> 0 bytes .../intent_understanding.cpython-310.pyc | Bin 10153 -> 0 bytes .../intent_understanding.cpython-311.pyc | Bin 13566 -> 0 bytes .../response_generator.cpython-310.pyc | Bin 7195 -> 0 bytes .../response_generator.cpython-311.pyc | Bin 9139 -> 0 bytes ./conversation/conversation_state.py | 295 ---- ./conversation/conversational_agent.py | 218 --- ./conversation/intent_understanding.py | 402 ----- ./conversation/response_generator.py | 234 --- ./date_validation.py | 675 --------- ./dependency_manager.py | 341 ----- ./error_handling.py | 1118 -------------- ./input_validation.py | 1297 ---------------- ./main.py | 284 ---- ./path_security.py | 583 -------- ./pytest.ini | 20 - ./requirements.txt | 14 - ./run_tests.py | 91 -- ./server.py | 260 ---- ./skills/__init__.py | 23 - ./skills/__pycache__/__init__.cpython-310.pyc | Bin 483 -> 0 bytes ./skills/__pycache__/__init__.cpython-311.pyc | Bin 593 -> 0 bytes .../__pycache__/claude_skill.cpython-310.pyc | Bin 10818 -> 0 bytes .../__pycache__/claude_skill.cpython-311.pyc | Bin 11623 -> 0 bytes .../obsidian_skill.cpython-310.pyc | Bin 11935 -> 0 bytes .../obsidian_skill.cpython-311.pyc | Bin 19125 -> 0 bytes ./skills/claude_skill.py | 467 ------ ./skills/obsidian_skill.py | 517 ------- ./test_application_startup.py | 282 ---- ./test_basic_functionality.py | 292 ---- ./test_config_loader_integration.py | 160 -- ./test_performance_reliability.py | 496 ------- ./test_startup_validation.py | 484 ------ ./tests/README.md | 247 ---- ./tests/__init__.py | 1 - ./tests/__pycache__/__init__.cpython-310.pyc | Bin 162 -> 0 bytes .../conftest.cpython-310-pytest-7.3.1.pyc | Bin 3818 -> 0 bytes ./tests/conftest.py | 138 -- ./tests/integration/__init__.py | 1 - .../__pycache__/__init__.cpython-310.pyc | Bin 174 -> 0 bytes ...i_integration.cpython-310-pytest-7.3.1.pyc | Bin 8868 -> 0 bytes ...claude_skills.cpython-310-pytest-7.3.1.pyc | Bin 20595 -> 0 bytes ...ational_agent.cpython-310-pytest-7.3.1.pyc | Bin 30756 -> 0 bytes ...sidian_skills.cpython-310-pytest-7.3.1.pyc | Bin 19763 -> 0 bytes ...anize_command.cpython-310-pytest-7.3.1.pyc | Bin 20109 -> 0 bytes .../test_claude_api_integration.py | 191 --- ./tests/integration/test_claude_skills.py | 516 ------- .../integration/test_conversational_agent.py | 431 ------ ./tests/integration/test_obsidian_skills.py | 458 ------ ./tests/integration/test_organize_command.py | 488 ------ ./tests/property/__init__.py | 1 - ./tests/unit/__init__.py | 1 - .../unit/__pycache__/__init__.cpython-310.pyc | Bin 167 -> 0 bytes ...st_agent_core.cpython-310-pytest-7.3.1.pyc | Bin 35674 -> 0 bytes ...configuration.cpython-310-pytest-7.3.1.pyc | Bin 33463 -> 0 bytes ...ig_validation.cpython-310-pytest-7.3.1.pyc | Bin 28214 -> 0 bytes ...rror_handling.cpython-310-pytest-7.3.1.pyc | Bin 36345 -> 0 bytes ./tests/unit/test_agent_core.py | 457 ------ ./tests/unit/test_claude_api_configuration.py | 710 --------- ./tests/unit/test_error_handling.py | 526 ------- 301 files changed, 22151 deletions(-) delete mode 100644 ./.git/HEAD delete mode 100644 ./.git/config delete mode 100644 ./.git/description delete mode 100755 ./.git/hooks/applypatch-msg.sample delete mode 100755 ./.git/hooks/commit-msg.sample delete mode 100755 ./.git/hooks/fsmonitor-watchman.sample delete mode 100755 ./.git/hooks/post-update.sample delete mode 100755 ./.git/hooks/pre-applypatch.sample delete mode 100755 ./.git/hooks/pre-commit.sample delete mode 100755 ./.git/hooks/pre-merge-commit.sample delete mode 100755 ./.git/hooks/pre-push.sample delete mode 100755 ./.git/hooks/pre-rebase.sample delete mode 100755 ./.git/hooks/pre-receive.sample delete mode 100755 ./.git/hooks/prepare-commit-msg.sample delete mode 100755 ./.git/hooks/push-to-checkout.sample delete mode 100755 ./.git/hooks/sendemail-validate.sample delete mode 100755 ./.git/hooks/update.sample delete mode 100644 ./.git/info/exclude delete mode 100644 ./.git/objects/00/449ff03053c280c520d16e89966203c2e8b81d delete mode 100644 ./.git/objects/03/4f72ed25667cb3deffdcc6d465f9848509138e delete mode 100644 ./.git/objects/06/37a088a01e8ddab3bf3fa98dbe804cbde1a0dc delete mode 100644 ./.git/objects/07/bdcc9321511a09acd06ce940d5ebcb7779f7d2 delete mode 100644 ./.git/objects/07/c927356f27b94033174f603d00a81d6d4aaa48 delete mode 100644 ./.git/objects/07/cd8c956ab4ecf04ab6ff2486d36bce00970e19 delete mode 100644 ./.git/objects/07/db11d87c859f7cd01b235d4967f385ac3278a7 delete mode 100644 ./.git/objects/08/11ab93fca69d22511a4d25c1e9bcc6aa2e499d delete mode 100644 ./.git/objects/08/ba29183c1dfdec98c1ae013a7478021f0493d4 delete mode 100644 ./.git/objects/09/b020ff2a1f424e35e877ce897f4e452ba51275 delete mode 100644 ./.git/objects/0a/04893c2e935f261ad508e5e8e9dec92a85d3fb delete mode 100644 ./.git/objects/11/ae2c5d3a87cb269933290fe25a6df0bd0e5aef delete mode 100644 ./.git/objects/16/05b976404982b0f1551ce1c9cb04c1fbe26d8e delete mode 100644 ./.git/objects/16/0fa0cf472dbf06c9ed66353b666275cc45223e delete mode 100644 ./.git/objects/16/c9e9236fd6eec677b275348766ae2d07c83f87 delete mode 100644 ./.git/objects/16/dd449404cf6f10aa080544c17d367b1cfa36bf delete mode 100644 ./.git/objects/18/9f6c0865274aa00a9a3454b15ea6b426acb17e delete mode 100644 ./.git/objects/19/0917cb4de45c030117d03705c9498c7e213c4d delete mode 100644 ./.git/objects/1a/50372ba7ed5fb7b7a7622adf0b101cb7c80ec0 delete mode 100644 ./.git/objects/1a/75f16df054f2c5acceb6dfa08dfb10481bc063 delete mode 100644 ./.git/objects/1a/e21a592999db50b5d51dd1e63c87171ad26a9e delete mode 100644 ./.git/objects/1c/a056129dbe5c84dd17e68dae716ca209cfc2f0 delete mode 100644 ./.git/objects/1d/2eb0843eab5c2f654da4829b76b1fab9de9b91 delete mode 100644 ./.git/objects/21/5a4e3c2eb5908c2f7c7859a8734a45ede055da delete mode 100644 ./.git/objects/24/4654666579e0e718b48c5d9ec659015e4d566d delete mode 100644 ./.git/objects/24/58c27830928d35ed4f7d9729be4f03bb36e543 delete mode 100644 ./.git/objects/25/1b65444d5270c57616041d76f8b1e969879388 delete mode 100644 ./.git/objects/25/40beeaa5edf975e19ae9e4c686ca5ec56098ed delete mode 100644 ./.git/objects/25/8f8a6703236ed9dfb322c41ca3cf67211df42c delete mode 100644 ./.git/objects/25/a8d2c5f9ea7918a2489d89d2cfa99875a5474f delete mode 100644 ./.git/objects/26/b16184e59b49037059cbc8d393feca8c28b618 delete mode 100644 ./.git/objects/29/57ac381cd898d5e8df9fe39a364541ea92f3a3 delete mode 100644 ./.git/objects/29/6b6de1a040c9f55d3a696cff14fcca8a00309b delete mode 100644 ./.git/objects/29/b7a89e959b9cac6fcb88ddbd1f98510f41908a delete mode 100644 ./.git/objects/2d/071a7891e073d79eef49d99466fb2166db756c delete mode 100644 ./.git/objects/2d/304f04e01e4642d3e3967a79bf8ee14068cc85 delete mode 100644 ./.git/objects/2d/47277733083a7d5bb003fd09c7771a84b6a8e0 delete mode 100644 ./.git/objects/2d/592941081b725f398b85952bf8c8bcbc0738f1 delete mode 100644 ./.git/objects/2d/8a7d8b75f1657219604e8a796b4c5f97edff34 delete mode 100644 ./.git/objects/2d/f39f3b4c20f845dddc2b685847241cd89aa789 delete mode 100644 ./.git/objects/2e/b5e2cc7f9e2baa1e29ceb848bbbcc212d0f643 delete mode 100644 ./.git/objects/2f/99e45bfa0ac5b5aff91bbb946798943b209bd1 delete mode 100644 ./.git/objects/2f/d4d0f8d97c50440b62e47a3f3e027cae115e70 delete mode 100644 ./.git/objects/31/5a2520c05e1358c2833bac37a85f1db4bdcc77 delete mode 100644 ./.git/objects/31/9d3be980a4c324c5d70bea9d7475f50e124670 delete mode 100644 ./.git/objects/33/2f10f7331e8d7a42cec6a7242bbc0dcdb9027f delete mode 100644 ./.git/objects/33/a845ba54d09871951f4e4cea1541615028a31e delete mode 100644 ./.git/objects/36/7b4da555ca4317127492a964d72358f0e32087 delete mode 100644 ./.git/objects/36/bc3e559fb43177d99b70e4bbbd0e0d02612a76 delete mode 100644 ./.git/objects/38/2460d73ef14b725f41130ef05a2f2e99a1b5ac delete mode 100644 ./.git/objects/39/4c150fdf47fa3950fed889f8cfccf474733668 delete mode 100644 ./.git/objects/3c/d644f559c90a51e2cc0508224c8ee41205fe06 delete mode 100644 ./.git/objects/3f/7b4780ae0ba64adfd193a60ca265954d33e709 delete mode 100644 ./.git/objects/45/e639d6be112eb3e32c6073ecd51ea9b69d04fd delete mode 100644 ./.git/objects/46/657c1ddf065b126573fcd2854cd7e388eb51a5 delete mode 100644 ./.git/objects/47/8f1ae2938d64030683de324a3363551d9a86a1 delete mode 100644 ./.git/objects/49/8b267a8c7812490d6479839c5577eaaec79d62 delete mode 100644 ./.git/objects/4a/6fa8353e3a6632531ea3efad5cadddf8dba5b9 delete mode 100644 ./.git/objects/4a/8ab4fa17dc3510701c1f796b52929a65973ca0 delete mode 100644 ./.git/objects/4c/76c67db4697071912a15f7b04f369a8af9beb1 delete mode 100644 ./.git/objects/4f/0c23ce27ffd734a22b81c92109b5603f8df1e9 delete mode 100644 ./.git/objects/52/cd13e06c5798897100e2e35db307bde7bf3bdc delete mode 100644 ./.git/objects/57/185ff087c798f9f7146e793be1edb064541d3c delete mode 100644 ./.git/objects/58/2f34a7e2b6a1384b3825f9fa891c914054fdd8 delete mode 100644 ./.git/objects/58/b0a4edb15f1be307674f2c501e4b7ad34ef69f delete mode 100644 ./.git/objects/59/3554a4fb4e13b8597dcb44a3dcea2a5180e506 delete mode 100644 ./.git/objects/5b/5b36529a4b633f56457b48daaefcd016215fd9 delete mode 100644 ./.git/objects/64/1e5779b8317678ce9f2e781ed4d568ae4754d9 delete mode 100644 ./.git/objects/64/5bccb0831429aee986c3434a3c14f227bd6c0b delete mode 100644 ./.git/objects/64/787105e33d1d19f357b599125197419ee11053 delete mode 100644 ./.git/objects/66/1b025a0d8dcdd7d2e5dad661621418230e9966 delete mode 100644 ./.git/objects/68/392f3e5de584ee540df2e55a149d1e843ebf79 delete mode 100644 ./.git/objects/69/4ad99197b8043fa0d8954f5ed37ec29f630217 delete mode 100644 ./.git/objects/6a/11d38ca2396b25e4515d14b1ea2545d5c1ec6b delete mode 100644 ./.git/objects/6d/5a23d6e9e5b75239f81eba9de06fcbc815c233 delete mode 100644 ./.git/objects/6d/e824b46d41385095c3ddb4cab9a8be4feb7843 delete mode 100644 ./.git/objects/6e/55f323a0354b9fddc639a857ebb894d0c5a036 delete mode 100644 ./.git/objects/6f/5e6ebdb19822b7cee429dd468aaa16805c6c9e delete mode 100644 ./.git/objects/72/7fee65b945b727e139a62c996df843908ffb67 delete mode 100644 ./.git/objects/74/3bbd51d21495f13529d971d7ef9fa8b367ffdd delete mode 100644 ./.git/objects/74/ee156443dc4a27675758bffc43436df8f03620 delete mode 100644 ./.git/objects/75/51238f8b5907f092f4c5a0466046b7c4bb3b29 delete mode 100644 ./.git/objects/75/5936e16e892c359d31c332f5fadf0eb00d5e8d delete mode 100644 ./.git/objects/75/7fdb88cf3a418a75b31704f9951435090da85d delete mode 100644 ./.git/objects/79/c024854bc984e2bc37cd104c54f9d6c6e3d13b delete mode 100644 ./.git/objects/79/e90c5aaf2baef9f4396c8cc670a542453ae7e3 delete mode 100644 ./.git/objects/7d/84cd2d20604e2c7a462468e7e37b1c5d9d2538 delete mode 100644 ./.git/objects/7e/a192291e5b2baa52949f38bacb0f8268022049 delete mode 100644 ./.git/objects/81/bfcc49bf7e4bee32e2049cffd6d8c196102dbf delete mode 100644 ./.git/objects/82/6a81bde48f220a0da379d90cdb729dfc0922e9 delete mode 100644 ./.git/objects/83/27dfdb8294cdfbb3988ba7089bdf2b94cd182e delete mode 100644 ./.git/objects/86/c83b9e09aa27427d85a1d5b7f50d21e0389f15 delete mode 100644 ./.git/objects/87/caf1f45bb4e7b509af084cfa044e4d11d5487f delete mode 100644 ./.git/objects/8a/c281428cae1d17e6f16f51f1b92cbe7405e5bd delete mode 100644 ./.git/objects/8c/b04aac4b020eb5667c964ae0e0e0ea219acf2e delete mode 100644 ./.git/objects/8e/8bc7f215a407467ef65c79160a26f8398de656 delete mode 100644 ./.git/objects/8e/dde00908b3119dee73efed8dce06074b86c38d delete mode 100644 ./.git/objects/90/23d17919d590f1e3bcf490d5fe3d067a96d7a0 delete mode 100644 ./.git/objects/93/a2d4b2328f5f16624374d34abc5b59fe66b7d8 delete mode 100644 ./.git/objects/94/f53c3644fd3c9390e5f2b18ea61caa0ef7fae5 delete mode 100644 ./.git/objects/96/9ef5eef4671ef48ef4b6bdf8b60f91a88b44d7 delete mode 100644 ./.git/objects/96/bfdb7609dc0d1a0a1e35b23e58c0ebfba1c773 delete mode 100644 ./.git/objects/9b/dbc86b5917da9d6e7f1dae017b8d5dafcae90f delete mode 100644 ./.git/objects/a0/94bfff7aa7820618464b22332d9053e700e01d delete mode 100644 ./.git/objects/a5/23b07b1031f7a7dbae559a82e58746a42a2f8f delete mode 100644 ./.git/objects/a8/13ca61b052c8018d11eb567fc0657b00792518 delete mode 100644 ./.git/objects/a8/639fe68e6d8431c44e84baa3834e88eabe16c9 delete mode 100644 ./.git/objects/a8/8c41f9df6f5918c33f517b1c8b292e7027236d delete mode 100644 ./.git/objects/b4/2f35cef819793a338c9270d86db481627036b0 delete mode 100644 ./.git/objects/b5/d87cbad56c43ba21c0238fcb3560ad4b84bb66 delete mode 100644 ./.git/objects/b8/70d82622c1a9ca6bcaf5df639680424a1904b0 delete mode 100644 ./.git/objects/b8/9018ced91c0a8af7f3f23ce8901870da89f3a0 delete mode 100644 ./.git/objects/b8/97c0ab709ed44630b6d38107a423b4f9960b85 delete mode 100644 ./.git/objects/b8/aa9e8c1cc4642ff0644bfb4a2690c13789015d delete mode 100644 ./.git/objects/bc/1a1f6167d09c909aad37280b760bb715d0f1da delete mode 100644 ./.git/objects/bf/397e828fc58cac608bbcf9393a14ed6bef410f delete mode 100644 ./.git/objects/c7/157a31aaf215a5757ccd6c92ec00cada79e074 delete mode 100644 ./.git/objects/c7/44684ab56c48a223709a9a4d362bbd1276bfbd delete mode 100644 ./.git/objects/c8/da781411176db48cf36d5b98a95595c9cff9de delete mode 100644 ./.git/objects/ca/dcb7a5f850751001ff630205dd0e265df9d474 delete mode 100644 ./.git/objects/cb/380eb387281b9ebdb73f176913849a6086d2e6 delete mode 100644 ./.git/objects/ce/8333fc3cd0e5180a9ae004f51b02fb4e42d37c delete mode 100644 ./.git/objects/ce/ac0a44aa05affeec11750b384a379848ff9018 delete mode 100644 ./.git/objects/cf/6d3bb9a24a552399b5c3c208cc9933cdeb4d23 delete mode 100644 ./.git/objects/d0/1c6e22b8d48e13b209127c8224352b18973c4e delete mode 100644 ./.git/objects/d2/1afda8534bab95dd8c58b3303fda9ac52f6749 delete mode 100644 ./.git/objects/d3/46add1fddfc0997c9b66d7b0a258782d29ca1c delete mode 100644 ./.git/objects/d4/054f300638131c731b04ec78da6249b8f2b370 delete mode 100644 ./.git/objects/d5/1e6373fb2af32f4a1c13b95f0f7e8a5abe6327 delete mode 100644 ./.git/objects/d5/b059c8779ab6e04275c326b76134c7cd45cb7f delete mode 100644 ./.git/objects/d7/84485e21548da0347e3af1f858cbe2bb122d41 delete mode 100644 ./.git/objects/d8/88dc60d94295e8917694360f370dd90a924cda delete mode 100644 ./.git/objects/db/783106501f9ccd99a3f7f92c0483059ad52ec3 delete mode 100644 ./.git/objects/db/c529540e886c977e12f1472306aa9adaf78de3 delete mode 100644 ./.git/objects/dd/e771a8dabd7b86d1f25ef33ed382b59a746e64 delete mode 100644 ./.git/objects/e1/f4b83ab8e4590c165b84062197c9f12f808155 delete mode 100644 ./.git/objects/e2/7cd7ab186dc0322923f7d7e9d31059aba3df4d delete mode 100644 ./.git/objects/e2/b11339b4073eba790e8b98a04459961d25d096 delete mode 100644 ./.git/objects/e6/a33a9dbea7d256da5d758dd0c2987cd55fb5e7 delete mode 100644 ./.git/objects/e6/bbc0c8f80d31e787e972b5b98dea65ef56c5e6 delete mode 100644 ./.git/objects/e8/66b589d11555e729c1505db86028cd201f780d delete mode 100644 ./.git/objects/e8/b91fbe25d966aa316594e17ff846ce37e2cf80 delete mode 100644 ./.git/objects/ee/d008e28a5cfc0c5f830c2ccf638ccf3fed18a0 delete mode 100644 ./.git/objects/f3/1a08505be5571fe31d90f2df416a1dbe7aa95c delete mode 100644 ./.git/objects/f4/6589e7d7cf999185328ed5895be23e47d93d93 delete mode 100644 ./.git/objects/f4/fda41afe041add5c5d063f3a581900650be6f8 delete mode 100644 ./.git/objects/f5/7492a1b3fea6c90071afeb5a1eb9af044474ce delete mode 100644 ./.git/objects/f9/5bbc13a0263a974d42d01b4f86a8f598523a2f delete mode 100644 ./.git/objects/f9/ba597c093ec6dc5c2be41e536f00bd4eba9720 delete mode 100644 ./.git/objects/fc/9e876a2b06e5604e0c7a5943e121faa84d9fe9 delete mode 100644 ./.git/objects/fc/e15ad7eaa74e5682b644c84efb75334c112f95 delete mode 100644 ./.gitignore delete mode 100644 ./.hypothesis/unicode_data/13.0.0/charmap.json.gz delete mode 100644 ./.hypothesis/unicode_data/13.0.0/codec-utf-8.json.gz delete mode 100644 ./.kiro/specs/claude-api-configuration/design.md delete mode 100644 ./.kiro/specs/claude-api-configuration/requirements.md delete mode 100644 ./.kiro/specs/claude-api-configuration/tasks.md delete mode 100644 ./.kiro/specs/code-quality-improvements/design.md delete mode 100644 ./.kiro/specs/code-quality-improvements/requirements.md delete mode 100644 ./.kiro/specs/code-quality-improvements/tasks.md delete mode 100644 ./.kiro/steering/product.md delete mode 100644 ./.kiro/steering/structure.md delete mode 100644 ./.kiro/steering/tech.md delete mode 100644 ./.pytest_cache/.gitignore delete mode 100644 ./.pytest_cache/CACHEDIR.TAG delete mode 100644 ./.pytest_cache/README.md delete mode 100644 ./.pytest_cache/v/cache/lastfailed delete mode 100644 ./.pytest_cache/v/cache/nodeids delete mode 100644 ./.pytest_cache/v/cache/stepwise delete mode 100644 ./DEVELOPER_GUIDE.md delete mode 100644 ./OBSIDIAN_INTEGRATION.md delete mode 100644 ./README.md delete mode 100644 ./README_V2.md delete mode 100644 ./TROUBLESHOOTING.md delete mode 100644 ./__init__.py delete mode 100644 ./__main__.py delete mode 100644 ./__pycache__/__init__.cpython-310.pyc delete mode 100644 ./__pycache__/__init__.cpython-311.pyc delete mode 100644 ./__pycache__/__main__.cpython-310.pyc delete mode 100644 ./__pycache__/__main__.cpython-311.pyc delete mode 100644 ./__pycache__/agent_core.cpython-310.pyc delete mode 100644 ./__pycache__/agent_core.cpython-311.pyc delete mode 100644 ./__pycache__/api_response_validation.cpython-310.pyc delete mode 100644 ./__pycache__/chat_main.cpython-310.pyc delete mode 100644 ./__pycache__/claude_api_client.cpython-310.pyc delete mode 100644 ./__pycache__/config.cpython-310.pyc delete mode 100644 ./__pycache__/config_validation.cpython-310.pyc delete mode 100644 ./__pycache__/configuration_loader.cpython-310.pyc delete mode 100644 ./__pycache__/configuration_migrator.cpython-310.pyc delete mode 100644 ./__pycache__/date_validation.cpython-310.pyc delete mode 100644 ./__pycache__/dependency_manager.cpython-310.pyc delete mode 100644 ./__pycache__/error_handling.cpython-310.pyc delete mode 100644 ./__pycache__/input_validation.cpython-310.pyc delete mode 100644 ./__pycache__/main.cpython-310.pyc delete mode 100644 ./__pycache__/main.cpython-311.pyc delete mode 100644 ./__pycache__/path_security.cpython-310.pyc delete mode 100644 ./__pycache__/server.cpython-310.pyc delete mode 100644 ./__pycache__/test_application_startup.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./__pycache__/test_basic_functionality.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./__pycache__/test_config_loader_integration.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./__pycache__/test_performance_reliability.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./__pycache__/test_startup_validation.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./agent_core.py delete mode 100644 ./api_response_validation.py delete mode 100644 ./chat_main.py delete mode 100644 ./claude_api_client.py delete mode 100644 ./commands/__init__.py delete mode 100644 ./commands/__pycache__/__init__.cpython-310.pyc delete mode 100644 ./commands/__pycache__/__init__.cpython-311.pyc delete mode 100644 ./commands/__pycache__/organize_command.cpython-310.pyc delete mode 100644 ./commands/__pycache__/organize_command.cpython-311.pyc delete mode 100644 ./commands/organize_command.py delete mode 100644 ./config.example.yaml delete mode 100644 ./config.py delete mode 100644 ./config_validation.py delete mode 100644 ./configuration_loader.py delete mode 100644 ./configuration_migrator.py delete mode 100644 ./conversation/__init__.py delete mode 100644 ./conversation/__pycache__/__init__.cpython-310.pyc delete mode 100644 ./conversation/__pycache__/__init__.cpython-311.pyc delete mode 100644 ./conversation/__pycache__/conversation_state.cpython-310.pyc delete mode 100644 ./conversation/__pycache__/conversation_state.cpython-311.pyc delete mode 100644 ./conversation/__pycache__/conversational_agent.cpython-310.pyc delete mode 100644 ./conversation/__pycache__/conversational_agent.cpython-311.pyc delete mode 100644 ./conversation/__pycache__/intent_understanding.cpython-310.pyc delete mode 100644 ./conversation/__pycache__/intent_understanding.cpython-311.pyc delete mode 100644 ./conversation/__pycache__/response_generator.cpython-310.pyc delete mode 100644 ./conversation/__pycache__/response_generator.cpython-311.pyc delete mode 100644 ./conversation/conversation_state.py delete mode 100644 ./conversation/conversational_agent.py delete mode 100644 ./conversation/intent_understanding.py delete mode 100644 ./conversation/response_generator.py delete mode 100644 ./date_validation.py delete mode 100644 ./dependency_manager.py delete mode 100644 ./error_handling.py delete mode 100644 ./input_validation.py delete mode 100644 ./main.py delete mode 100644 ./path_security.py delete mode 100644 ./pytest.ini delete mode 100644 ./requirements.txt delete mode 100644 ./run_tests.py delete mode 100644 ./server.py delete mode 100644 ./skills/__init__.py delete mode 100644 ./skills/__pycache__/__init__.cpython-310.pyc delete mode 100644 ./skills/__pycache__/__init__.cpython-311.pyc delete mode 100644 ./skills/__pycache__/claude_skill.cpython-310.pyc delete mode 100644 ./skills/__pycache__/claude_skill.cpython-311.pyc delete mode 100644 ./skills/__pycache__/obsidian_skill.cpython-310.pyc delete mode 100644 ./skills/__pycache__/obsidian_skill.cpython-311.pyc delete mode 100644 ./skills/claude_skill.py delete mode 100644 ./skills/obsidian_skill.py delete mode 100644 ./test_application_startup.py delete mode 100644 ./test_basic_functionality.py delete mode 100644 ./test_config_loader_integration.py delete mode 100644 ./test_performance_reliability.py delete mode 100644 ./test_startup_validation.py delete mode 100644 ./tests/README.md delete mode 100644 ./tests/__init__.py delete mode 100644 ./tests/__pycache__/__init__.cpython-310.pyc delete mode 100644 ./tests/__pycache__/conftest.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/conftest.py delete mode 100644 ./tests/integration/__init__.py delete mode 100644 ./tests/integration/__pycache__/__init__.cpython-310.pyc delete mode 100644 ./tests/integration/__pycache__/test_claude_api_integration.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/integration/__pycache__/test_claude_skills.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/integration/__pycache__/test_conversational_agent.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/integration/__pycache__/test_obsidian_skills.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/integration/__pycache__/test_organize_command.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/integration/test_claude_api_integration.py delete mode 100644 ./tests/integration/test_claude_skills.py delete mode 100644 ./tests/integration/test_conversational_agent.py delete mode 100644 ./tests/integration/test_obsidian_skills.py delete mode 100644 ./tests/integration/test_organize_command.py delete mode 100644 ./tests/property/__init__.py delete mode 100644 ./tests/unit/__init__.py delete mode 100644 ./tests/unit/__pycache__/__init__.cpython-310.pyc delete mode 100644 ./tests/unit/__pycache__/test_agent_core.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/unit/__pycache__/test_claude_api_configuration.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/unit/__pycache__/test_config_validation.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/unit/__pycache__/test_error_handling.cpython-310-pytest-7.3.1.pyc delete mode 100644 ./tests/unit/test_agent_core.py delete mode 100644 ./tests/unit/test_claude_api_configuration.py delete mode 100644 ./tests/unit/test_error_handling.py diff --git a/./.git/HEAD b/./.git/HEAD deleted file mode 100644 index b870d82..0000000 --- a/./.git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/main diff --git a/./.git/config b/./.git/config deleted file mode 100644 index cb380eb..0000000 --- a/./.git/config +++ /dev/null @@ -1,13 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true - ignorecase = true - precomposeunicode = true -[user] - name = windyboy - email = windyboy@gmail.com -[remote "origin"] - url = repo.windy.me:journal_organizer - fetch = +refs/heads/*:refs/remotes/origin/* diff --git a/./.git/description b/./.git/description deleted file mode 100644 index 498b267..0000000 --- a/./.git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/./.git/hooks/applypatch-msg.sample b/./.git/hooks/applypatch-msg.sample deleted file mode 100755 index a5d7b84..0000000 --- a/./.git/hooks/applypatch-msg.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message taken by -# applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the commit message file. -# -# To enable this hook, rename this file to "applypatch-msg". - -. git-sh-setup -commitmsg="$(git rev-parse --git-path hooks/commit-msg)" -test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} -: diff --git a/./.git/hooks/commit-msg.sample b/./.git/hooks/commit-msg.sample deleted file mode 100755 index b58d118..0000000 --- a/./.git/hooks/commit-msg.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message. -# Called by "git commit" with one argument, the name of the file -# that has the commit message. The hook should exit with non-zero -# status after issuing an appropriate message if it wants to stop the -# commit. The hook is allowed to edit the commit message file. -# -# To enable this hook, rename this file to "commit-msg". - -# Uncomment the below to add a Signed-off-by line to the message. -# Doing this in a hook is a bad idea in general, but the prepare-commit-msg -# hook is more suited to it. -# -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines. - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/./.git/hooks/fsmonitor-watchman.sample b/./.git/hooks/fsmonitor-watchman.sample deleted file mode 100755 index 23e856f..0000000 --- a/./.git/hooks/fsmonitor-watchman.sample +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use IPC::Open2; - -# An example hook script to integrate Watchman -# (https://facebook.github.io/watchman/) with git to speed up detecting -# new and modified files. -# -# The hook is passed a version (currently 2) and last update token -# formatted as a string and outputs to stdout a new update token and -# all files that have been modified since the update token. Paths must -# be relative to the root of the working tree and separated by a single NUL. -# -# To enable this hook, rename this file to "query-watchman" and set -# 'git config core.fsmonitor .git/hooks/query-watchman' -# -my ($version, $last_update_token) = @ARGV; - -# Uncomment for debugging -# print STDERR "$0 $version $last_update_token\n"; - -# Check the hook interface version -if ($version ne 2) { - die "Unsupported query-fsmonitor hook version '$version'.\n" . - "Falling back to scanning...\n"; -} - -my $git_work_tree = get_working_dir(); - -my $retry = 1; - -my $json_pkg; -eval { - require JSON::XS; - $json_pkg = "JSON::XS"; - 1; -} or do { - require JSON::PP; - $json_pkg = "JSON::PP"; -}; - -launch_watchman(); - -sub launch_watchman { - my $o = watchman_query(); - if (is_work_tree_watched($o)) { - output_result($o->{clock}, @{$o->{files}}); - } -} - -sub output_result { - my ($clockid, @files) = @_; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # binmode $fh, ":utf8"; - # print $fh "$clockid\n@files\n"; - # close $fh; - - binmode STDOUT, ":utf8"; - print $clockid; - print "\0"; - local $, = "\0"; - print @files; -} - -sub watchman_clock { - my $response = qx/watchman clock "$git_work_tree"/; - die "Failed to get clock id on '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - - return $json_pkg->new->utf8->decode($response); -} - -sub watchman_query { - my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') - or die "open2() failed: $!\n" . - "Falling back to scanning...\n"; - - # In the query expression below we're asking for names of files that - # changed since $last_update_token but not from the .git folder. - # - # To accomplish this, we're using the "since" generator to use the - # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - my $last_update_line = ""; - if (substr($last_update_token, 0, 1) eq "c") { - $last_update_token = "\"$last_update_token\""; - $last_update_line = qq[\n"since": $last_update_token,]; - } - my $query = <<" END"; - ["query", "$git_work_tree", {$last_update_line - "fields": ["name"], - "expression": ["not", ["dirname", ".git"]] - }] - END - - # Uncomment for debugging the watchman query - # open (my $fh, ">", ".git/watchman-query.json"); - # print $fh $query; - # close $fh; - - print CHLD_IN $query; - close CHLD_IN; - my $response = do {local $/; }; - - # Uncomment for debugging the watch response - # open ($fh, ">", ".git/watchman-response.json"); - # print $fh $response; - # close $fh; - - die "Watchman: command returned no output.\n" . - "Falling back to scanning...\n" if $response eq ""; - die "Watchman: command returned invalid output: $response\n" . - "Falling back to scanning...\n" unless $response =~ /^\{/; - - return $json_pkg->new->utf8->decode($response); -} - -sub is_work_tree_watched { - my ($output) = @_; - my $error = $output->{error}; - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { - $retry--; - my $response = qx/watchman watch "$git_work_tree"/; - die "Failed to make watchman watch '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - $output = $json_pkg->new->utf8->decode($response); - $error = $output->{error}; - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # close $fh; - - # Watchman will always return all files on the first query so - # return the fast "everything is dirty" flag to git and do the - # Watchman query just to get it over with now so we won't pay - # the cost in git to look up each individual file. - my $o = watchman_clock(); - $error = $output->{error}; - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - output_result($o->{clock}, ("/")); - $last_update_token = $o->{clock}; - - eval { launch_watchman() }; - return 0; - } - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - return 1; -} - -sub get_working_dir { - my $working_dir; - if ($^O =~ 'msys' || $^O =~ 'cygwin') { - $working_dir = Win32::GetCwd(); - $working_dir =~ tr/\\/\//; - } else { - require Cwd; - $working_dir = Cwd::cwd(); - } - - return $working_dir; -} diff --git a/./.git/hooks/post-update.sample b/./.git/hooks/post-update.sample deleted file mode 100755 index ec17ec1..0000000 --- a/./.git/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/./.git/hooks/pre-applypatch.sample b/./.git/hooks/pre-applypatch.sample deleted file mode 100755 index 4142082..0000000 --- a/./.git/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -precommit="$(git rev-parse --git-path hooks/pre-commit)" -test -x "$precommit" && exec "$precommit" ${1+"$@"} -: diff --git a/./.git/hooks/pre-commit.sample b/./.git/hooks/pre-commit.sample deleted file mode 100755 index 29ed5ee..0000000 --- a/./.git/hooks/pre-commit.sample +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=$(git hash-object -t tree /dev/null) -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --type=bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff-index --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -exec git diff-index --check --cached $against -- diff --git a/./.git/hooks/pre-merge-commit.sample b/./.git/hooks/pre-merge-commit.sample deleted file mode 100755 index 399eab1..0000000 --- a/./.git/hooks/pre-merge-commit.sample +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git merge" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message to -# stderr if it wants to stop the merge commit. -# -# To enable this hook, rename this file to "pre-merge-commit". - -. git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" -: diff --git a/./.git/hooks/pre-push.sample b/./.git/hooks/pre-push.sample deleted file mode 100755 index 4ce688d..0000000 --- a/./.git/hooks/pre-push.sample +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# An example hook script to verify what is about to be pushed. Called by "git -# push" after it has checked the remote status, but before anything has been -# pushed. If this script exits with a non-zero status nothing will be pushed. -# -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# -# -# This sample shows how to prevent push of commits where the log message starts -# with "WIP" (work in progress). - -remote="$1" -url="$2" - -zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" - exit 1 - fi - fi -done - -exit 0 diff --git a/./.git/hooks/pre-rebase.sample b/./.git/hooks/pre-rebase.sample deleted file mode 100755 index 6cbef5c..0000000 --- a/./.git/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up to date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -<<\DOC_END - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". - -DOC_END diff --git a/./.git/hooks/pre-receive.sample b/./.git/hooks/pre-receive.sample deleted file mode 100755 index a1fd29e..0000000 --- a/./.git/hooks/pre-receive.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to make use of push options. -# The example simply echoes all push options that start with 'echoback=' -# and rejects all pushes when the "reject" push option is used. -# -# To enable this hook, rename this file to "pre-receive". - -if test -n "$GIT_PUSH_OPTION_COUNT" -then - i=0 - while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" - do - eval "value=\$GIT_PUSH_OPTION_$i" - case "$value" in - echoback=*) - echo "echo from the pre-receive-hook: ${value#*=}" >&2 - ;; - reject) - exit 1 - esac - i=$((i + 1)) - done -fi diff --git a/./.git/hooks/prepare-commit-msg.sample b/./.git/hooks/prepare-commit-msg.sample deleted file mode 100755 index 10fa14c..0000000 --- a/./.git/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first one removes the -# "# Please enter the commit message..." help message. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -COMMIT_MSG_FILE=$1 -COMMIT_SOURCE=$2 -SHA1=$3 - -/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" - -# case "$COMMIT_SOURCE,$SHA1" in -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; -# *) ;; -# esac - -# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" -# if test -z "$COMMIT_SOURCE" -# then -# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" -# fi diff --git a/./.git/hooks/push-to-checkout.sample b/./.git/hooks/push-to-checkout.sample deleted file mode 100755 index af5a0c0..0000000 --- a/./.git/hooks/push-to-checkout.sample +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh - -# An example hook script to update a checked-out tree on a git push. -# -# This hook is invoked by git-receive-pack(1) when it reacts to git -# push and updates reference(s) in its repository, and when the push -# tries to update the branch that is currently checked out and the -# receive.denyCurrentBranch configuration variable is set to -# updateInstead. -# -# By default, such a push is refused if the working tree and the index -# of the remote repository has any difference from the currently -# checked out commit; when both the working tree and the index match -# the current commit, they are updated to match the newly pushed tip -# of the branch. This hook is to be used to override the default -# behaviour; however the code below reimplements the default behaviour -# as a starting point for convenient modification. -# -# The hook receives the commit with which the tip of the current -# branch is going to be updated: -commit=$1 - -# It can exit with a non-zero status to refuse the push (when it does -# so, it must not modify the index or the working tree). -die () { - echo >&2 "$*" - exit 1 -} - -# Or it can make any necessary changes to the working tree and to the -# index to bring them to the desired state when the tip of the current -# branch is updated to the new commit, and exit with a zero status. -# -# For example, the hook can simply run git read-tree -u -m HEAD "$1" -# in order to emulate git fetch that is run in the reverse direction -# with git push, as the two-tree form of git read-tree -u -m is -# essentially the same as git switch or git checkout that switches -# branches while keeping the local changes in the working tree that do -# not interfere with the difference between the branches. - -# The below is a more-or-less exact translation to shell of the C code -# for the default behaviour for git's push-to-checkout hook defined in -# the push_to_deploy() function in builtin/receive-pack.c. -# -# Note that the hook will be executed from the repository directory, -# not from the working tree, so if you want to perform operations on -# the working tree, you will have to adapt your code accordingly, e.g. -# by adding "cd .." or using relative paths. - -if ! git update-index -q --ignore-submodules --refresh -then - die "Up-to-date check failed" -fi - -if ! git diff-files --quiet --ignore-submodules -- -then - die "Working directory has unstaged changes" -fi - -# This is a rough translation of: -# -# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX -if git cat-file -e HEAD 2>/dev/null -then - head=HEAD -else - head=$(git hash-object -t tree --stdin &2 - exit 1 -} - -unset GIT_DIR GIT_WORK_TREE -cd "$worktree" && - -if grep -q "^diff --git " "$1" -then - validate_patch "$1" -else - validate_cover_letter "$1" -fi && - -if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" -then - git config --unset-all sendemail.validateWorktree && - trap 'git worktree remove -ff "$worktree"' EXIT && - validate_series -fi diff --git a/./.git/hooks/update.sample b/./.git/hooks/update.sample deleted file mode 100755 index c4d426b..0000000 --- a/./.git/hooks/update.sample +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -# -# An example hook script to block unannotated tags from entering. -# Called by "git receive-pack" with arguments: refname sha1-old sha1-new -# -# To enable this hook, rename this file to "update". -# -# Config -# ------ -# hooks.allowunannotated -# This boolean sets whether unannotated tags will be allowed into the -# repository. By default they won't be. -# hooks.allowdeletetag -# This boolean sets whether deleting tags will be allowed in the -# repository. By default they won't be. -# hooks.allowmodifytag -# This boolean sets whether a tag may be modified after creation. By default -# it won't be. -# hooks.allowdeletebranch -# This boolean sets whether deleting branches will be allowed in the -# repository. By default they won't be. -# hooks.denycreatebranch -# This boolean sets whether remotely creating branches will be denied -# in the repository. By default this is allowed. -# - -# --- Command line -refname="$1" -oldrev="$2" -newrev="$3" - -# --- Safety check -if [ -z "$GIT_DIR" ]; then - echo "Don't run this script from the command line." >&2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --type=bool hooks.allowunannotated) -allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) -denycreatebranch=$(git config --type=bool hooks.denycreatebranch) -allowdeletetag=$(git config --type=bool hooks.allowdeletetag) -allowmodifytag=$(git config --type=bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero=$(git hash-object --stdin &2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 diff --git a/./.git/info/exclude b/./.git/info/exclude deleted file mode 100644 index a5196d1..0000000 --- a/./.git/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/./.git/objects/00/449ff03053c280c520d16e89966203c2e8b81d b/./.git/objects/00/449ff03053c280c520d16e89966203c2e8b81d deleted file mode 100644 index 6b8ac917bb4a7b6bc6f0549b108cda020749196f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 292 zcmV+<0o(p~0bNhQO2a@9o!uriL`&nbw_bDUA|hU-3L@x51r;%uCCP@eVY6Fy6H5Cd zo;-NdKayNUp}*ik-Lxr)Gt7gT_jr#tbub`XXR`7LB?o6f)nQ_VHF#g7__wx&HW@qet=8RVW diff --git a/./.git/objects/03/4f72ed25667cb3deffdcc6d465f9848509138e b/./.git/objects/03/4f72ed25667cb3deffdcc6d465f9848509138e deleted file mode 100644 index 315a2520c05e1358c2833bac37a85f1db4bdcc77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 369 zcmV-%0gnE70iBRPOT$1I#_#gq*jBCL+%=0q#6c0E2u`A43qqD7>BV|_my}B~w3DOX zpoVGGiU_cK zH-yrp^ZD}l@i4W&L^t4Q`Px~K5KR2&JoIsC$=kJb!ATk}wDnr+jz(cr88>V3ge2)P zpjW#`KAlOscXN)dVmGZ72s+wN7&LN^PtD9zmKIOAeg6!W1Ukx9ti zvP0q`L?V(Rv^5&ftpgV0jE0ANRV2v&OYQ^;6+&1j3e*Z9Vd+pQ!2i%tW$oXv&1K=M Pacj!WTVj}Rip-z5_F1>k diff --git a/./.git/objects/06/37a088a01e8ddab3bf3fa98dbe804cbde1a0dc b/./.git/objects/06/37a088a01e8ddab3bf3fa98dbe804cbde1a0dc deleted file mode 100644 index b897c0ab709ed44630b6d38107a423b4f9960b85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 YcmbEMbace@S%uDiXf?QX?eA{ z8322@+dcK{;W65aWs!0eCKMG}NjWOU0u)OT>{xMRJ91QUq9o;5siYhyR;9|LIIh@^ zBRG{u1&$NJqLlCZd!Bo3{8+Ss}<*_d3|*4UOzJee?ZM*g*gk$*MS*p8!uQN&RZM>}v-GRinA=&pyJYpc3pMlXthv~0cRSWSwIgi*6q}pso*Mq@9%@z#{kH8En@yE2@r}&XQ%>fY z?(8WiyV&Z@o~$RGg3;=kz4n4xFFRY_-Ca_h*8H>T1n#v>C0#ochr6BQ?GsB%y3*$g zk3RRbu8qFbns1{MjN+bnqCIaOJ!kdI1$|=Vs5!f&+P!o7up)oZXU{y_bM%oreP{fH z*=w4?0+`Afw^p3m*;+E%Ub_X;vRWt1rsWPY%LfhrOX1)V{H#4F%tRvz5CrHo(niWi zzm{lZjEs@R->e3WMot3-fUtxYRCcy=X4=mj2P*LV@ze#(xR*Jf zy^uJaux`ioTrYVcsqR88&srzRC-PeyWQ#sqyo)VZLyN4`Qx}r*H+>;#%0)bLF5?o2Y6N%^fbMlTWp`$d^a$(>0lG?P4LQo#7hx$<(pjah|e z^82BgRAXlHRevVGv7s}mU`>j8CQF!Izwn8qb*Ic_R90ndK7_w0%v|%EGK#>bO}5}uZC06}8K7^#p7yLJS@wkBli+{N z#p{d7E2)PbsweF|x9?g!x4&+cKIB$v57k)PQ@3{U@7}sqFD=&|(q|9WEhlaDl#`X- z>KP}ErY!cd*w5nNl`62)R6XhEKHjl-?!K9eNqd?d?(@b%B^YbUomVi_y>;tiT5SP7 z^ZtD-?v3<(NcVgQ53yRuvzj7CJ5D;OwuKvauQH?A){7Fv1O$fGe6!P9Fr8x4Y8^M(gHwG}-hj_MHZ{Ql z7g`|hXBX#N9bqd@-d%Gi@2-)Pm1lN}{`^6wGnP4jTy5rj%bbvYxBJ2e_l6G+=mS08 zq42@|;bwco2m8a#4u%izyO>b#!q`_{oPH57Wlf)HcZ_q>&#CU5Ioq?QyT`1y(Qb97 z=hXC^X0y$%$7iRxxYqO%7`9ZyED$&{wRq0iF6%GI;$GDl)a~$ztqO`l>Wit@ldmT~ zk^EHB-cMADWlB>+dx|hBGAW-?{X-m3CNt7+rNF8Z2FR|FeIvD;>Zj(&u)q)^3@axL zE6?-71PVSosta)e<55#WT2KQ!A_+um5dWgz@?mL7#&uY(YIadHO2QtpM%kzgVN@ev z4e8~KF#>{9wQF38fZ%BIa;DuoS@Xf^bZdUeymS|y5)xASlzGm{b(R*6nd*wjU79@A zF=ez+<7{@7xO|{)31~_WF1b)Qq9c-v0}*lo(xtZH%1phaQBVfGog?4D;!YO3Skzf? z(&}y&_n@ff)n3-+9zupX#Ns{_VQ9Tq-OJMiSY15G29p9ATKwYrG~M;N2^Xy#ic+eQ zJfE`nt^r#sdl(K`zk+T95eUKq#2go{QW3a%p_e`n!AIaK>% mpz{oL1-7Q=lg`K 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 diff --git a/./.git/objects/07/c927356f27b94033174f603d00a81d6d4aaa48 b/./.git/objects/07/c927356f27b94033174f603d00a81d6d4aaa48 deleted file mode 100644 index 43ac18f6614c695c3e85ea2ea2cba6f506aa371a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36 rcmb}j1q4NqM?%`goQpL2db{;Ui&GdW!V=FbbG diff --git a/./.git/objects/07/cd8c956ab4ecf04ab6ff2486d36bce00970e19 b/./.git/objects/07/cd8c956ab4ecf04ab6ff2486d36bce00970e19 deleted file mode 100644 index 79e90c5aaf2baef9f4396c8cc670a542453ae7e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmV;>05AV|0RhDTI{#v9Z(<-ZI5+@!0ZYosPf{>3G-6OvQsN5E&dkXvR(Q5z;nTU( zxj-UZX+`JftWwuQ)Bg zC>L%XSA2Y8PELHhf~`U{*sV$!eo%sV0xp5@1-cB9KhUL6eFBrkbS diff --git a/./.git/objects/07/db11d87c859f7cd01b235d4967f385ac3278a7 b/./.git/objects/07/db11d87c859f7cd01b235d4967f385ac3278a7 deleted file mode 100644 index 0e1e27d7e296a8562cd5dca5888df1da7b0d1a2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2836 zcmV+v3+wcF0RagM_Vi+GZ(<-aI59Nu(cR7T?eQE3PK0uvf8xm3AvxawRsv zHeCd(sH^=DqS3^@PNs}!+?g>Uiy|c~gpdaz4Nx9QBUlGQx5TtXkUFpbrSgoOPvlS7 zbMBqT9gmY5sreAwch2jcdmg`gW>_K^Qz(h z)Qpi4RUh$xTH{utXc{GAkC$PYctXyqdApaK(Z_qqrLwIWx}x=x3#w%&K9ahJe6HvP zje3GDGN#(2WY~azCe;*My-EoX5M|ghw2DnhS+Pg0gl&#z69oR4ZJCmy>b_}T7!?!D zxnzzgy83TwGFJ(-c|*TSO$$UD0Z*T>rf8glUdz+6P5PPF&Y7lRa*aLYyrOBtO8yG5 zjnFRc7_fho>cp(*I(kd7h?O_hvK{LG2vfVYG?B<_ie-_$QN`}pZE6;kJmtp1!5nRa=&m7S)Qq1Z*g(BXUvIXf`|W-cC=rO##2>2?a|6iv|c}hzAHM zC6B4dg5oz$ytRK1%29g-Ld)<^Jhrk4qLW4GvVQ$U?!+Jea%uvOf|C)#A4->oEw!L3 zI&mM@TmRX0zg=%_zHuMCZQh&qieQ@X2zNK0LBWejC`!KY%X;*r-A8-PYTf=NET#mUpn22+$I0faD&dW-AYj$PdI=73lI7Vz)Nj5OdJs+BeUzm^q8J#`e1NX0=s&kQLg4 z&w!sr!z3y+$Urz28md=H(D-bc?4-SyoKAHvI4IC}27JA)PGrdSP;^HRaQZgacnI|G zbbQ!Q%mRaUW~FS$)do?~Rh8>d;XGC680JM_49K;u-{7_cpp~HriJ^NR+<)Av)u6LM z!${K91mqq_gZ@%9a@e)PmWUGY&v(vc{nyob;w&yT@2|B>30q?nj937xH7b8yq2{;; zQQ3Fa$+|)&&ccJn*7xq(daL$448=GQ^~CZ{>XCE%qbNpzj6vjn6k-Ut`~uLX%#Xfq zlb9JUgc4u^zyY`G&Fy9PyOltjQLzC5$OXk#vIOsQi0av$4x#<^fDZR8dD$yv!%Xy+ zdZUD14-n}1N;_GQX_hE6%o3}Op+s#3O%#t7;Cea~AD|j4uGF&;2RoxsF|i!gh_J{~Ph;l}CmG8vD;gKq7FLdr*GZ6>m1QRRfDVl!OEojG zZ5$Ujw%JYV$xRNH@h+AIa56LPes|AZ`N3I2=Em0c!ShG{v)T^Chr2T4)L*!h+Yl%) zE@=J?U)Fq!PzD-KP7)4tcq(`p`gN5r2D=7g-awI_bw;4~{KJPBY>}D90|W>;YCypr zORb>zRX!TH-bp;H5MR2w^)2Gi5p ze=MF)yPmekaEPrJQwL9{0RlKH8z@^0G@b)NxW(hz!#LfCcJ%1j0Yq+e-l1pNC}azN*JS zlSyru9))c|6tEHzkd5#nfWXTlz=n>7mB~}0DMurGl=_c7s2C=O9(9H>=2uY5L)6? z7RC!O%Jj$CgD{)&CJKH{g>viM-i5KM$r$w_?0aMCtT^52EWi*X*l_`Mz2!M+aB&Vust9IOsrHiJ16yDJby``%#Ru>)S29%boSg||M@o@ zziD<7;r2%}_H_-oOf8Uu4(oh(E+&utUo&9f{^_R}f|Duit?^;5V zT~wg9mf<}IڋHjm)}1U'EHa?%)+ʙ[ͨ/T4a8@%W_x!Z8Wo56Ǎ )b7VsQP'; - -z 2t3eܯYquxpUr@;_<} diff --git a/./.git/objects/09/b020ff2a1f424e35e877ce897f4e452ba51275 b/./.git/objects/09/b020ff2a1f424e35e877ce897f4e452ba51275 deleted file mode 100644 index 641e5779b8317678ce9f2e781ed4d568ae4754d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1771 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 diff --git a/./.git/objects/11/ae2c5d3a87cb269933290fe25a6df0bd0e5aef b/./.git/objects/11/ae2c5d3a87cb269933290fe25a6df0bd0e5aef deleted file mode 100644 index 5ccfa05ea8b44d2a59e74f723586b5ec7f6df612..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3579 zcmVR!jwNxezO=rdo`f@uFsG#e|s< zso!1cm9(wcv4UPqnn`dewpvIPJIoGII4G31DuR<99S{ZjV}x}+ah>l6LPz2hw-V`YYkj#}!gZZKCV7}l4Dswz+IwzrX^G9_V&mKH( zFA7h%3Uiwkt3p=tF~_!D-)-} zx%Z-F=xfcp*^t}6_Cx261uhf=4S^0E6{OrUUFH2yWflMNZ7s^9J z`O=V?7;?O4P~R#2Wm81um$fH9Y>rQDNtOeFv#eX zz9+Cvc>UkweJ{8$hQ6cul0Dk@EGrK?InV7YA9C|{-YWGCv%cnZ<)$U|_4ehUtZdQB zmwHEFf!S=nl=rgPdoiL*qo?A!EQu*m|7&Gin_hdHtT{5%=8v{T-F}0|y@{3y(c{o? zNtlr)MG-o)<;GjOgqDa3U5QtvSz#Y+aj!5&ePUISw{z}}*wQdM&M z4hl!^uS!>AvpCu>32YboBxvM=7~?si(W0YeZ&kRe&H^Y}dL%)A9`yJIdJ6P^16|ud z?*RP>UuA+?FjqR7>&X46cT%0V>Q!l0Z0tftOIKAMPB!)vGS024?q2N)U5m6 zy#4G)MmTo@8?7z8-`m@(`yC*+ZOq>mt@o$1w6XB)d~NA^_~FdT{KbAoyQ;`gMn&p1 zd_n{rQfb?nPXM#}+M6r$^AL&{XwI=5&ii25-%RYkn>k~IXU{Lcc4duca}XP0c|cPq z7y_NJA#WAngMwJTG~#lwmVYa|WWr5j84E zH$f-!OXvlcX63y6OU^eybpI8FG@e#8F)enAx%YwS(=3`GeRI-(jO|?oq*V$Ot&}9zbOR?r<0` zlEbRF7m&ffr7Bz|APN)$(qS2of+vPYwWZ6-nC!(pZ8%YtZJA=QX$}&$2QkRK*qh*iNQiw6L$iCRAk+!8qT^Y%nopKK#&S3LD;%_dA$Cs%jj;+?$v&T#5v{q1x0%l{Fcr_Qy7rPXV1G~4EH0$b~sr^3Z|>f=kx zOBZ(P`tnDpTxqU|mOvWuoiF|1h!hkWt zV;Z=@dFw*>>U2cqoE(F|7@;sr_yZI|TN9jGc<= zcE@@vsObi^vz5x#-vuYP>8|Q;M~{a3oBBOvge~p;IuA;Lef3YKfY9}eANCt&+ADij zA=?OY+$KwHX(>E48?hjovOL#JMx37IzSv-!Iv1Y05^-XrF4xcH5GxJ(h3q<0IQOfS zkFT#Wr3Nh|(ssmPrgA=GFeCoyQ#7YXD_-M=p%sf($>!!5p=7_o-SI$1j;w7HSsN|U z$+|w6*=QR$#GAwoX7vDO`v{NArKM<6H-aAeYZ8^ZRJrGF1{DE$MjQb6(5V>6q$ocv zSqSB_2sN|CvhBkkXAxV%DO-g`OFWyk%eicpk>#;}q=Ld*_Dw3jO$A{UH+Du#YdHmc z;pEkM3cno{mwd7&DB^ZW@9chD-=_aVdMj_`Ys#T@?I)u~`R{M=xYtln?mLVd3Y4&N zTb`E3=ma3B^7w7bw9G>?bPxV??6x|(B&gqSgIE-wr z5^wC1XWy-2`uyfpZDD%l_9aAZkq)9H`16~uR+2qEn5n1WyyW2iaNodF`!lg1_QaEq zzwk8MjZw|`!QBT2_6 zflOzhS!`&;VyL=noJzX8|2+o9F! B61o5Y diff --git a/./.git/objects/16/05b976404982b0f1551ce1c9cb04c1fbe26d8e b/./.git/objects/16/05b976404982b0f1551ce1c9cb04c1fbe26d8e deleted file mode 100644 index 5e60237382a53450d43d1aae1dee0be09adabcb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60 zcmV-C0K@-y0ZYosPf{>3W~gBFK6TPR$iOH)And~#ZBJi)FP)QT&IfCF>gs8RX?XaY S^Yig%WjHI(qYnVpK@m|~ZXBWj diff --git a/./.git/objects/16/0fa0cf472dbf06c9ed66353b666275cc45223e b/./.git/objects/16/0fa0cf472dbf06c9ed66353b666275cc45223e deleted file mode 100644 index e7c42aacdef6712434492dd369d7ae603510a4af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72 zcmV-O0Jr~m0RcS#!vA7yZ(<-dGXQu2OUlVlQZP1WP*!j*N=+ eNGvVM&rK}JOis+nsnp}r;sO9tfe!AAf)}Bc-y7Zl diff --git a/./.git/objects/16/c9e9236fd6eec677b275348766ae2d07c83f87 b/./.git/objects/16/c9e9236fd6eec677b275348766ae2d07c83f87 deleted file mode 100644 index e767b62a615eb21f09241b03018d8ee23aab6123..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15308 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ŭJ9=NW%9Ub)Egn?>8TgN6~p~7ly zO-$qpq}j;6u5GW>ILekZ?ApLnwp5!@Q=yRh#<=R>HN4|1_-b7T)^L`sZm6^oZ*9{h>^NOKC>LVqN(OA}nBT1VrU7{@ z|Ize4m}rE}747375DB4JRY#j2BQ&1!F@e##&2L8pjNJ3|>y zW|jCRlP0nQm>7?s9SCciiP997*cnG<3gqYqRtaq*p2k(d?d9KoDgwbw^Ih~W8wEYRj@cQ@g|g7rn@xRu02fHdvP)n|Jeb{cfh$=a?s;Y4(;tFSD>k1KY` zqi=Fh59vda-R2CTqT$4YI~uHwIv@VwrT}0cOO8Pey%D526}h#R;*jpEa2$?^iO!7P zpFw`h%2~&KW(wjyIqd&Q2A!`O*d`l^OyjKfKs0R`Z3LljvEWRq%c0g|n{Cd43N+b3 z$g6m3@Ye?sRTA*4S-Cs*2%zn^oa(f>>UQMdA8voZ1@DrZGJA>L6YPFT?ozL|)p7Pm zh#@k3dk~w-O{oktVLXz?B)bkt1AdRhM=YDOPAPC6JrFsB7q3Gh?tTq=CKG|97LeMm zAG-F0Ae&a8X!Qvz#5knKoiv1Lt^?J$>eN@9utz2uG5}--I%xwby>0YYh_5H6TU?Cw z7na&%lEH&1ym-njDyo$C?I76hC|w;il0ZQT(Qn{Naoow$%q$)u){MXeoGvHZWV4S4 zq)-+y!t`1h+n5 z;b3_YdJtZ3P=<6h45{oC!ETbHq)Ws{QLQ>gX*$A3AL67#`IUJnf*JKQsl&(o6ZIpe zdlT!x@9Xc#iVB6}U;}`k`H=LeqL*a!BZITLR4%Hr3-21aiSL|}WF>V7Ebb22$V01- zSctplp?hIp8(GN1-I#xTl{zX-%o8Ic|lkE;Fh zWWK{#x2m#Z10;s3v40KGm}{E(D)E?BeT9x_FK;o98~;E~!fp~a9daMd1riV8gpq(i#@&@G_rKM0;KGt2hl9Bxu{0sE zCM<7;@UgA*j4^Nw8BxopzC*tESe zORplQg?;bawu59hON^6rg$laqVw4Mh(gQEnAFrUd6d^`2z%mWz~Nk4y*=qZBGKq2*jQDN zs!BOCc|u=aN+}2%6EVWR#e1Th`|%=5`!` zlyf6Pg&h$E99?a&s$A4~YlHi7>qs~f34-Q7U>4n)E2yl+uW6EDdxOl5F69?ZWm-1A zQYZq60hy_T3AgxEG&82aS(1}$ifUUmEI?M?iI2sDx#ddjv92k z?((XuL)F$+cHC$%B1l3Vrm=f&Xf_gT#$}*%vzrrMgjk_wimCb$Up~Bv4MYj8Aj}A@ zwRT)DVg#`mR|3nhL1mM3AwDd^lQ;I+cSaOf5<{7LRcjT^^K>Oi&{eAvrtPY<0{v1h zU#-=#FVgLViMnc4!4hdqK*+UeyZ+7q`#FpF9b)-?`C(nHZCDH;E@s^vk}RRH95GW z7uFV}aQDX^xP{9QM|01!5*^u>FxpOougD;F!;4w|;s-K=2OoUSPCkrCU=$EP5^1*s z>m`SfFMQ?KP(28Mi8LXq`487xGTj0by^eRdqR}vt&E*Jf*5W5pEA^4P%-1Wo8!8~I zzX7+fSXB{;7{*LjD*yJ{Tc?hp?PokBu>|>#;6Qm(@iGE@l^}lQ^6klo3|l zfJ$yo3HMTn#WY%{p*S=eNoUFNmDQ6K#PDj0!>36To3U1@P`ucgI?8cW<` zH?ua*-(y}^sMaq5-nPypE|4DW<8`D3+V#q zi_R#2mvK$h+19%=B>SB%x|a}iuVW6anO{}uLqfXVLF`npO>)gTp<`#46E=t{vQFTN z=~J@=h>!aH=*W)!5Pqo0gmsSN)6^h3n3x{cy_t2ee8B;zvcyBiQ%8dIdS;eAKfP`F zuROc&*}m7Ov27zc9_+WHxr9NbJg3N2P&K;%wUO8a>d8_MFbO8fOUwdHhG4iYU^g0s zR+|}oXgk(j6wASBkh+{N`Edo7)A4BfqO;94|B)W0`NX&j|>Gl%fwk@OQM@DMau(U$7E2^7yALiWSyXjPv?^2&x_QfivCU0)&a(#_|{ zd^Al{c;BIC;2|B{5@MNsu*4xn4YYJF`DYRa#X_=KyPMtTK^eL8iau4_Hl!3b8l zUw9?Jged*4YtTNt>4n{p{x2xLO>5R9zrqE_{-hwCK}=BV+Rk3DV#k4 zO0UDGzr=lHP{Vm*3X$w`5#BlQt6(SeMK`#o8X70ifc=ris3S;Q+?2gLPPZ+=r}rS+ z$XAA2;QW)Vk|P+4a8=7zQb=uW>7gvqo@0Prjo6xbP|JHJS<4%koQ;{HH6M-~o4+f` z2o2KS_&`|h6iEwkLPF)caLrYnSbl@db^;TL=8s7@_e@hCP*`$AtqMbq#zJFuakMH} z!CU2f8Q9qmy>f#Yt|}AUb{jf-naLYnE{}0B817^RBG`z~?Vc?r^0_nmnA6nv4*vhA z)6vIQ@4?{w`0AuTIC!kv2!m533(da$?z`DlHh*f(%|iH+z0xNdW#NiUJ?EIKiExf9|JV+cqrh7-t-0B}W`1kqw~ zbQ0DZM)Zl|g2N6NL;P}}-Ipd|6Qj(W2n(J@r>bSU?_jKFXr70sUk-UG1k1QbK_9|% zc?25{ng|(o3kiNTv)^NTCL)Fx4U0t=%?E+94tL*n&$M0NtAIOx4({zSe4ugy-oYWV z?Eu(fglOmm6hrv%d9y-gZ^0aJp@%;_v}!Nt!zpOBMDYF)M;_y5xQywkl-H^roQ?74 z_+ATAMChKmK$MWG7@H_VcDH`Z2RBlaZ(No#aq4OjM~TFCN6o;LTfP`_#L?i9f943Y zza1nFT`+Qn4Gz1+<(tIGRv=#(=R#%V>YkNqt19afEha@ zUylo`P&VbmJD7ac0jhIi4fqZ+X}T`C)$_VPpr02;^+Gr=KQC)!YVgtWw5u+ezq657ICbU5iFX$!o>xXZA7 zF+xC}?!H3qM>YK9uu8;XJH5)T6{n_|mGbhi9Uf1%r)zDx8(8de$QHM_IQm)JlVl9? zUvVnM+#SjSb8Il#mhZ7643di0+SPHZNQ{VdS27$Fc8SFzg~HxI9hI-G@$7@g#?wCn z<#dxY{RzWdG$czUuTF8dxNGhfxt9UY>+4upS*)5`Re9P_5m8%u4BB3nfS6HnYSy4| zIpG~Gzqaz?#)f8Wh;nc=!Z%=FBGrS9L0kvMAd`Rr^+_-DX(m=#j(ZYSYy!vOT+w^a z0giqv9Xc0~>{G>PC~^<=8}Ng5(6jfp87RgM>dNDWYGGuT?ydTcHJ`VP_>x=@PbEG{ zOs8A%WopX_WX!qC!5dzIKmcYRV}HfH?$vg@lHzZ7clqR^Sz#v_6!zMouc&iZG90jX zlhi8n#Oat#4lEJhiKKSDnuM|C4-OrcCLxw;eb`| zgT&D<;|T`yu&4G6XhKIc?}*}sOqhy(CA1b#j3et>W?xm?mT5na>qjVqsXZ?B@k?P< zajI}(ZfjbZ`W^P+Oq1RqKh3R3j`|kr+OL0?pvi?sh?Nx`MM9cF61{$>R4>*$J{>V5 z>NN?I6`fR|fw7tDq-*Utj>Os@wT_m$MMfmtCj~7vV2Z}-0aKH*akxxR8qR$Zqli{! z!=a8~RLcF>VEq(^>P^ZMa3Coj`dWQ6KIM_kSmfFuj&%-oVB=}yA$n#^QC6zQgiTeI zVu4g@XtkMYTU*?K(a;TC9W6WA=A0WsEs%FPmC6)e*;{(3LAKqcl3D7VrQtlj)`e?u z@NwWRt9Q1rW=GU)c|{cOT5j);yN*|FE!_#M+;rTs>SqlmN2vv!lmKm#rOK@Z6hXH?%KPAEa})^gH4pJOI&!@Ob;X+KY3xCzC9GP=wJ@S;pp4zy-}|+js5lBcy}001esi) z{iomk>=%Fc>EHgv|Z z`S+jvR&4uf?n=#U!5I+{I-grp0mv3n%pG;4g8WWU_LAW@WJynViSdHLP_J1Da)A zSE4RNF7JfQ7jZt738IJJ50H|Bbh#?ZT4cq1p62rdLRPO=@LBMqi*#BK#aX@{ip#Wu zkH4;v>^M`4fwo1KPW0+e@b%!}V4B5MB|JW_h|QurJrM8*n=4lK;RGp0VEUKHnTVn^ zPwOZeR5F{rQkZc8ejOoCvJ~$$bECPeFX^{I6y@!@7^7_rm>(@W^hTd*=U9oB^4-kKFz?5V+w_L>OP2&O5FADPelOMY_ z9Q_cYR{u!g-vPmofN$>n!;#%P0DLG|ufT4WU|qLynUbB^GeZ8fOr9*vZK4dGz$Q1A zmy>`T1JrF5-hPDbLD7R@$$U5z7jhOiSq);&8gNVKxzN|lQkf-I;$#(Iu(&khrEQz& zD+TYLwrmAYs~@k@s3|je*jv=~syaPBhMz`pUN6dGl}<-++AS?2II=HbMn($-=JtsEte|^j=L!(EIJweXrzLVAAE@Zzl){>p9w2{Pe7=WIL+2k zUevORX5e6Ci6md(uOX(AxR$UEoj9)h7yKPvT^(Iqgci@zOh)oc4ZMKY#`U9RqTDF% zjel)gi>6*Rh&n*3`~pm;65@zU>R2HGL)1X%0-X+nUaDsyusW`8T%h0Z z`*Ej$R%J2C4764Til z6TEsM1*i}tevr%|9r(xeB`kMHT^!cH&92siVAk|LufP)ub!d=)5zj(?ft1n4fpjLc zn-}2jM5?o2srU1x+9GDGfv+*6@|D?1`9^6zD+WEqKQNqD6|$>=TXJMldjYOI4TLQ_ z$!SRsZBCgUrhemV^Vobi6lX~yI3fy;ZUV%EQdyO<0uN*sqttW&)ME2U^M&R;8XHKI zSs)HunL10&)>b1No<@)g%RCv(dcXwK6D>Bt&vcRS5R&qtVBJy4@FK22XEjbr77RHk z2^G@!mxF@aK?=OkY`T{W2zXNhjML>`W|j004oVNIk-#dU$RH3~8cQvqoz;eh zEfVp~acM9F^wB))U%N_ac0z9540nHaUFaS7^_Ckl_F~vUvB|OXu!H480o?(saDqL! zp@npUTQ%Cf`vww-L80Wr=rly4ZG z;1QRUY_x+Ku}lBFT+B^9ULY6Kf@sBNx3{HoG2P7iKhDh5@tceX+}|S^R-8GFB&~Iw zNd>z^aUM`RqsN>3D@U$kPb=+iEe<3}PShe#%J~%e(A*DGEo4KX%LJP&5h>g{laYR| z1T<*DTVjGz8d24o3^Wqn#6opRWGGYx?1 zj-8b0=g%TcBk86~d2pz|OHc^MlUL1h3FVhLZ}8+njq%-Tz@?tMZqV64yRSstrS(GG zXEGlIcX;(6rqG3-%c|w@m4X3_wrw0S*&Ub+H=Bv1OL-zE&3w@NShQTLvL%QqkbGe- z3Bdw|rW$u5hElFsp|izilS8Z7b(iSTWyeV0_FBGfAU2NuB7aDC5LonIU)$4yy7!7R zXEyd0v%1FP`vVM^Enl+53sW`QRm@?o#QXMjp438bX$|Z>Hqf1Ul16{suD}r;8-y;) zHVa=39hQpHSuVI-z1&nRMr3;fEjArajG`cg-WOH->Z+U-j_gL$;4Q~gh!zcXlCC<) zm>7|wGZgog9TG+Lo*r@K^&nuZv6gTZzm@#INnoGaLeOuFv7J9!$4h9WK-sF>Laig7_QvufFzPT84cEhTl|s&5Glm8JYola_ERqMzU;hiQxf ziK>Avokgr_Ip5LLF@i?&d~}-<0D-BsEH0nm z&GooJNNR>_en?hMKhi(+hhTgVcA>>axuYJNGo<;Uwiu z*^_!44(RVJc{&QWm52Sxp@Wr=5ZwRwPilN8Kd$IJRnq z=e7rVgmXJ_YHM#cLuNpeM}(Q|F}?7|eYF{o!A9%$5g{`^1ItO4ZR}mMK3l}WU0)6} zPnp|M`q>dloB%+tc?`9ujaV1R4F{AXT$+Kun08}jEumL6o2ikwUNfn z9yI={5h>r`_w99q~OW&5) zknBQYB!gS@s&0+V+cnI_mR0Iqp)HFH2V2*9=!|@=!J407tipL)4PJbsY$FA2gT#oP rxt&E-uOiw5M(reZ9<{^A=ti#LdN!booD;T`5>1eY7kw8<7onw!RobA3 zSxM^bG&50YrpvCRXvIojDt%(c@=-vzGsGIBk>{jdHcAnYmPC>GzgixAd*wQ^Qmr0Yzyc-$<=l#}n4XD|7U~D(zXt z6bb|upuP2Cn++F1QX`jQZ-(I^P}0dFO^>t9IabPosc0=6vFsepuF$c!jV0YU-eN-~ zct|S;bbp+V9iXl>XZwYw_mQ)i-?2*LvVhP7foHr*qpqEuKnrYXwq(?H%=LF{b_Wdi5*p=M+mWuFMtzFz4xglQw5+rAJjP8# zVTwH|(!xvbX%W(s)CO0P3#WxO>qti)D0ZpDriYC3q*0z?D-&!q50-HU2wkk>b!%(d zdx>rAu=*$~kI{u`b4$1Mg4ffI7~5;Ls-^2F=2?Z!=tycD9fNg7i$2RbAIe>@y)G)vd(P8I-RIGTXE z^CzPrAWz8AP&oR)7at6C0BiIcxf+p^A%$GIeVyP%tPy0z&2pHdy5{5oOAVR&77(A( z#SbzPiX#At@EV;Rr`g{i$Zl%1uwI;@)g^N&&(d>(sj6u4SX5K|&^8oRPQ+A~sUY&^ zG8LCw*g5?LbNrN=a>Ot5e&q0^Gg)hY#8&jpFwd7visdq_xJ5TsMQAUIDA>WlC|r#V z41h<;-%0OJ;Yh@Z0{e}pBYF5Wv^wBKJ|QD+23YU%*1K(+<=A`UXc5RU2{0#w;6NZS zpr`|KI3zt#<#<22`@M*Wmb{O@f6x?_T!%QTKgvNxobiZV?Uxf*Z(Q%ApUdTYE}7G_ zZ%2|3!qGnGCt=wIqjrFFN;D+3%Dn2yKRH5f$b*XNvgC)N-S^oZJv5&?6Mq;&LL`4<1RxZ0|K$U?fF` zVEzhL+y^n0-$_8V*}fEMt@Yu@*=j@PMIi}#74=~_sA$aqiN*qbrLWmynkqX%=I73L zaw8T5PIs>L-sS!16Oo?@Em!-WckkXtD;;@4gHu8H0xeqDLg1(bYDiFmVjfdHV{MN? zr*4eRm`A!K;R9%D_y7=uS)Agd6^nnpOy{Pdh^^9lnjN-MBc$)nwJYD>yyhDS-D`Kh zc8EHMd5;a5tyI|CWiId>p>rjeW$w7bdXl(8_hqeLiVr6GAz!)%$bVu<9t3|(eIQ4} z|5a395bEC#Z6k{6BPTjH%f}c%gZ*)s#dK^32c9bdI?!huhL}X$510SKpX508Y=G!s zL*97-9{vSkmnG?AzOX{*LDNV^HYSqer_Gl+JmpDjR&YA`G>(;KUL z#RHuoSHvJ9b_BL|-3b_MBi0&J-<0>BsIo2+=vN}~0O=+H2stEV z0G_iab;u3;javoa??8W8186jev;tvyXb}X7r<|5Uv!o_SahQ2Xj}~cW2A^`$U__Bs zK?%UT@w6Z)#W#DRyG)fw;e;UkIca}sq*71{{04r5j!3Jp=!teFQj^Xsa~JZ3=lTEH z%b-R7oK)07@|C@w)C4|v7L%_q9Bi+Eaeyv|+mBUW1f|HodT;%kuxE!H`LB&at+LG= zkIPEMs2^LSYmgOy0|C8>M27xrCjuuN#d18=XuGA8rQFF<3jRsZ1B<%(h;{y;lVdbs z0T7rHE)BzB^Qzw;j)oI{KS0t`XI{1*==w!E7m1&uI%6ep>6UC`J`2Hts%fkM6`I!p z8VABHPh^g>{lS>Zw*t>?KABpdxC09wJAM{;wRY>&_Q(hW%^wkfe4LOqi+ zmni}BVJ8#*@c*O?{t{C|P-#&0UUqg)!p11x4Opcao@Vx9;Smd1fi3T24{i#N%c?w} z0M_yYxZ{xPv;elZP18@Ut)f-0LyB|m--xw^zpc>Mj7kP^s9ujk)0-p`D(4BiA zI|p5d@90Yvntg)T8{4x`Lw1QkjjWIJfs)>DzGcDXL6Y1laBAX;?E(alz|B}ljs#$^ z?9lW&tyS#}T$GFXY8B$lm*vUIb~j4N7D4aXBwclW0%3dwwV30tL@t+ zIpFSJqQs$S(GVOL85_1H^H+|x9tP? z*+W1hcyHikF4QEVxNl=9$YmnTZ*N0X+IF7wwRH`^N872M*#xKj*zW1@)fRXUY;zU$ z%J6B4P%N47#iH$PAvqz#m3IEwK`wvi@ewx)6?Bptl;geM2SubQ;)*X3>lIeDci5if zxA-4;#vZofy@IRA<1cBYsOq25ipNCEz{b*3t5j;?&1FB99=nap(#!nyRcQ$7DEAsC V5xz+xH?Q9%?lDj7{{cKCmT^Ji4;laf diff --git a/./.git/objects/19/0917cb4de45c030117d03705c9498c7e213c4d b/./.git/objects/19/0917cb4de45c030117d03705c9498c7e213c4d deleted file mode 100644 index e56dbe21299e7c910db0cccc276fbf483460539d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 347 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 diff --git a/./.git/objects/1a/e21a592999db50b5d51dd1e63c87171ad26a9e b/./.git/objects/1a/e21a592999db50b5d51dd1e63c87171ad26a9e deleted file mode 100644 index 9c5b1833675eac175a9599e616a5519e784822a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1843 zcmV-32h8|*0i{??ZyPrd?ODHqKn_U++lU>zD138Wqc)JVMckz5$)I+*5@Rm8C3hvO zzrJsV`Emr{ zJUa}|TGbw&ygNBPes^;6HkLBxA_f2Z;^T+Ih;dMD3wjWrWlOBZ0HE=aX$!TIYwdhr z+@;YtJm*K0ahCdtnZQp$FO1?EYss9;_0fzBWpkItGe{pyrcxXO@p8g$g7I=($80vA ze?|kuZR`B@d_ET!U!9Wr^$Y$by>U@N{>ti(dK8>Q^7D0BEIvP%U? zU<@ia-2zS`HgE{c2qDnf-=-lc;y#dv zC1$;b_d)D}coX3n`M}5;R#M-0qu*WAg}41=#7I*%p}1NH>hzuoA!(4%mZbSvAvn(N z1iU{NA0<2<|HRK=(MEcRJKO`^ja$+F0k8sV6A&Y^LF#*5ueUuE)10H=vo28ee?cd@ui)o+UG>u9d2nB6kS)HDMFyjU)U=iR2 z%5`m2oG{Ch{p=}Oq%${SD;cl!#Fr2g&6ykNkohZscx{ z3Z5<>xR(_7(CD<|Z8=`0AEqz~j3wyuB8EQlyjmKAz zm`_hv`eS4vd(^W|n>0~GEtCc;kcB9$6-)099TU?170c3cvnC!UR}QW8{VX@Q2+&H} zbB`}(Uhx7g4LlC)Oj`geGG#v4MPA~bVP)*U?C>DQD%}yM{U?pxsl&LZ7YJIWv27u{ z7V3efG|+b}JVvdR3A?O5qxE8~y|a89`a*UXcsiA5rI7H**Jv!l6hA$@jhsPzN3vq>uAO>kHpF-l}P zmB)(!#+r;0pYsQ!EQci_ZhfOa^~mCyvjbW`s*E}uAfL!XM$0Q+W0ZIeQ}DHOcv-T` z@E_0nw={7bq&Edu53+7Rktg^)*%u;gWqQ*y#84^5|Dreb$wOz5!^&;wj+rg^NgI3# zjW5SW2)E_xxHIWC7H^%yeP`NK-u-bQCk^1ufg1ASm?PR|=jMQ=3WeA4Rnp%5AbUpM zai`iBFKJ%!TOYh9nRK96_H$0_q^nKLIrcpS&9wP$)D7=z{EVlWjUTpO(-<>ml hlcp15&;#mVfwwhSqEXTKNLn<#(rBam_ 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 diff --git a/./.git/objects/1d/2eb0843eab5c2f654da4829b76b1fab9de9b91 b/./.git/objects/1d/2eb0843eab5c2f654da4829b76b1fab9de9b91 deleted file mode 100644 index 932331a4cda5478981aa0e838c3bb127b56d8000..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4737 zcmV-{5`OJ?0p(o%Z`;V3zMuV9OywV>1hx|IwY}DW4o$sjH@Mwxn)Mz~7=b`bBbytF zR7omXH}L=7=XvLwq-49!x(A$EcqMY?o%j1|W^!3nm+ItqzdQN;e~d<>gV*JCRxb2X zofX-3snx5uZ}2tOWvf2r?X_A|^S$^ylrnb80p^qWy3|Y;z)g+FWMvZs=UFU=B8YnRY2WAh~FYuIs9v zL)^W=8Gd`CP35#C(mTE{5e$T^^IyPw@OyF*LAhY7emn^wIMbrh}`2_C9HLBTJx#Ro-Y=a)}e4Fjmw8*uP4FY@+ADDpz5Mu_x0 zqK=&@vNCHxefWqUf$<;(@U#9!9oWSL!cB1pYrn5b?MVn6Pab!)v^&cwC7!<){zD@@ z$iAK?!wwEaHLUBQA&5&dQiE$5@_5#3^*vtKH_a|zW!}!`lSUV-BkL|t zqs7GvpPAJ;(?Q_(H<+4>b}{wPW1LCn)Bp$D%@;pVUS4L6CQ$!+YF9~z*%i}KKp`C^ z=!7xyvoj+5dEQnZG@^>bXS(*!(y^^xZxDpqZLP!L=wUjV0Y}#uXd@5(&bRVxGM|^( zTF>Xkt&rb->Y@5y>Otfl#unaQBfIi(EjIFn$%AM&OtC!Q#hT`U({|2G znHP&%BMIugw{cXUh=Sdj1{5h92jE{GeO^&3`#mKj>C<_@$s#%Y3!cHM3R;j-Wf+7OkI$5vgnX)l7|;k~OC< zUSQ#=XEI#ddXy?=LdM0|xwr9qkd#36%aJ^JV?4+-FIUxMHTtn(-es{j5m54N$oy1& zPC(s`2CS#na9m|aT$AU|69mmXLFEtGK&DFE>O zm})KaS}$593oVd?p_;hXr7E>vV#GvGuci`+ON?S-9CGBq(-`spXHMx_XG>iV*yF2h zdtKG}KP3N$Rj$T=)L9M58P&A*c{@%6!&nrgY>&@xHX5^F)|(<<2#^;)HC5Ri|6`-; z<5yR(RgV6tf=Xw_aaCVsB`V1J#mV$#cZ#>pSQGDVhGw+%De$=G?ildt}3`V#*; z89&492TX>S446E8ZnSQc2@_{jQ6R&^K7w&K(rO@MAnbf)RVhU_h-bRE){75wvY8nf zTr$eCCe$Com}t1bM7fPDm+l*qJ$wD`eE$7kzJHxA!8F&pZ^W+>o*N##iKnfnWrkVSLjRJloIp|TG`EN(5@>#Dkv;R$fgvE1a|LZ|NLnLysA_NsKc$4d5 z>0Zfr`4a+{b?TayXdQhBT$fc<1bDHp_o+GRLPr&2gji^e08+9b3r6TseJ8QEQ)8Xi zTcp#7q8X$hF0o;*j9)FQoeY=_Zo z7Yo=hx^cx#ltmhFO4%fms+%CL;Whiqn@ZHFXkQA1YH65_-jsp`mS{F;@@kWd2Yd%A zEY4?QLEk+aEu;r+IxKwSJ}JNX)T3@YQ@%??kJ6#T^t{&16&*fgp;K@k6yY8!+t*gj zMlTqBluUO-HfZ!Q4~5G>p|ED#wQ06dbx$DBfzhvDo{Si>!{YGoqivblBug*T*N8Qr zbUo>WM?!mu2v%MO^lvECnIy!Es!WuFhT17Oe0x9ws=7g57CQ*Qn%-A_eOn_PiwxE^ zOO=k#Q)%eJaPQYCdq~HKZIe~CEq@`_k!3DDlh%o><@sVV2i0EpeI>m<%lk!DOKB2R zp7s0_@+~-olzmRSgpD=|507}93s{ZLLWD7fb3aWhb^EERKSV)8+#^Nuv>Zg9HOv@c z-9p!`NlEqasK>Go3FeElu!wa&PORu246!pwp}h(>N6<-AI_sa|%v!VnpT>e88%Ur< zVKdSOHYRCIgY*(PwRftwza&8t7zQ#n zZiONSeVX|Ixn3GnZrPi|7Cxwz287%58lu{b5f*d?u++d>>kS6E6IxTSck*nSHx)K> z)>#|%&&QbiTo9rJN`P@_pSb!R;G2qJlPsJ_-X;)tfG zG==kajC>xgW!N%8Bf3P%=(tH_P?E46H)|wzn|H2&AE+;lSG^dga@xl*p*HC8PNv;c zBlgdCTphnke45BUY}|1veo{T((UW{M#oos!5-}L0MsQYbu@Awu?w zO=^}$iigYH?Zr6iU#4}xO$AbSIWsQqwQa9M)`*bHFO=ZC;hCZ5`0)7F#juoOu}JqRxDLB#sKXZG;q z)YI(8p;OIy1UlIsjm#U7UsW3Je#%{BTfI#EgP%RLUOL^^I!T$l*97h zVtG}Ct<$9|{^EnP#KU2Ds$MOZI6Td39SZ!x_90TQMoq%WU$no&XXmT?{;_ zD@7#W1zX9mCg}=^mZE_Z^IV>d-|yW}a3&3mqD1!YV<^v`hm}$dxr2;&OH0t_ zJ)r_lYwv-L;Mey!pfB8l-gyMV-5g6Og$9jqa#7EVe4V$$-R8cDJfwLVbB-$^R$(Pc zz**?{&a#{)y3{ZNt=2|m*8E=UzvF;L+dNTknGCT_@3~$2m*7gg`yuqP5DhWQb0zAx zhcNkMTd4E_{FqZ6Vwl6s5T|n2zQ*J9$n9i}BWJNQ?%j?DPJl$b6jFSAmc!0Zq+efj zb6q@#a&$tJ|S?nP5Y(RF&gRsm&&25NO zHr{3VFM~N>ch6x66f!aHXAI|Y)?cS><1f`rPZFm$?P8E;$B5?Bv^Nch?Gl`*AnaH||t9&u&rI?f%UmGJ~^+M%3bM7=gHBm^k3Q zfTZ!vExn8P9fh~&`3K)w4Cz`hHAP?WWBzA$#>`N6xiwuwhMGk5)aun#ehv&9w7(Si zeGQPyhO#Mq^DG`HG;KLg|4_-rQS0?sO$3>G2wcLBz;)ZAnHWsdWH`)ox*+>&uy!jl zmIA#a$jKVWqasU4UJ->u^;dd?2t#$Oe!@jc)2Ihhb~CRxv&RC0pjcT7-6pi_Gy+fdIWP8n`OQL|>U%fzXZhk1V7J>_R?hL!^EJ(0ZzY z-QOJ@+aAt=9EZ6rqjxT4M9byJ@9(=xZ&`8`x4Ll;(n!YLd$ngR#Oe1Y(WHfJ9=B(I z%(I*2fAq82-7+ALVVgO6e(0a$9}<{E^LT%2(VD9b(PNWl+a1SevePu!PbF;k8RBbK z30I@fvI@z$g}08Dgb+-7sox0T3O&}ijuIS*i(Vm1V1j9%_9hPD>5jZezS7vnkLPu2 znh8LIg^N%7?@hnfNb>m)5IOkMKCsjic!clK92sIkU(?Rwb83-`45yNfON`r2 P9t&F+^jG>PRGIfd){$1P diff --git a/./.git/objects/21/5a4e3c2eb5908c2f7c7859a8734a45ede055da b/./.git/objects/21/5a4e3c2eb5908c2f7c7859a8734a45ede055da deleted file mode 100644 index 65394e5076a60724a07b2958506f76360f1d8a8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3763 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 N;XCR2{|4hJi&a+uP4@r* diff --git a/./.git/objects/24/58c27830928d35ed4f7d9729be4f03bb36e543 b/./.git/objects/24/58c27830928d35ed4f7d9729be4f03bb36e543 deleted file mode 100644 index ac267737ea60451a88265c2ad022e5aa0c7b1157..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5768 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 diff --git a/./.git/objects/25/40beeaa5edf975e19ae9e4c686ca5ec56098ed b/./.git/objects/25/40beeaa5edf975e19ae9e4c686ca5ec56098ed deleted file mode 100644 index 1f884d30efd93f6f05bb51630ff2e25d0efbd383..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2160 zcmV-$2#@!80Rd$Qn)hOCZ(<-aF*7*;cmbVQ%W@mL5zU%kfikm@oshI?JE>^LP>jR6 zdX*$Q@oEN|0m-=<4ko~iL^_qKTlX8h%-7v7$>|0#50UaBE?3zE(2dj2)0q2Q-;0ma z5AS|A6xT-otddIHR;Es>+Ni-`I231@!J;;Mab?!B$iB%c)5TyA$MN82U7JGYk#;-E zOa{Mw|Lc${e#QQQ1XN0ZXZ~q7o}@ zM7ggvx)7tSP&<{>46ao~Y7PJpnxS}nH-0PT=S!hjqbyP}TjT#Pqe?ciijHZ7vMH1$ zx_DH^vV&v}J(V&mnJ9Nd$2G9MsG>w0^$gok%(3Wktc={MCv6_Y$y{%@1n>{y_94sj z*BxEBbd{~2>2Vg$d8;-INI(JZ-I9gyQV!26+tFi{0r;bOu1fMMEOKXB!#d&~hT6$( znXB<$ZgUrOGKmld7?r{SS*eb!QU9uuWdzNRt6k*;IY`$ox~VEvk*Xr;W4>9;&Mp?? zZQ4kLJ2|+iPHIx>GPX?$Cl-B~K9lred`awV@nvy-b-lQWK7C%Ewb!CX)T7FE$-Z6J zS*r4^=sEZ5ueZyyT(Kdf9WGgK}VFlFK?(T^)pO&Z}bff{NfEPG;X4FLAyg7B5s!g&{w&H6AqHQf7^7TQ-X7{uWe!G`e zF?DRR^=6b)?FwBIVt{gDoVLe>VAnQFjta~-s2P&FMMDEPP@CX<>`)USXYFuEoily} zs7R$rvu`RDa3Yoo-d0v!6XqI$+OfbQ5FIfNE%Lv(8+pgRTQH+t6Gu9G;)r>R2{k5! zfq{XtVbhbi<2TNmv@4C~HY6o9lxt&Z+XmN$jt-6M*)67A-6!2#NtN&d@Mjd$OX^LMD(Z^UQ7t zIres!#GHyTmD6fq?*z1p=)_iX>D~P0f&64!zZ|=~ z45`~3Hfo(&3=*t5(HeOIhXm+dwd`pn5CqH(B=LyRDs?@Za4BOjFJYR~=w^H8I!?vL zah$B|HeR;^RY9334wBxx#rNhLB~z9l$IuT^=t@~Jpfh*8mO*@;%8}NdrM6>V9$=AfqXsY6q_&L!6$T>^XM+mgUMe;X8hJo$99 znJ)E^`u&L)U+PZ%qaO6L!;a~MzNgov6*bs5c5AV)^=g&vfRS(Ut1cuL3J<%~wbU=t zWb*!KSkUU4V~^n&6uQ?S;^#7ujFx2$@uH}=lvbaw=?O8P-7a_tL2kQ4P;)W+ zXLPf;yIC%7T`u*4$_j$3?)If3o9;kTR6w;2vsCx7)qBbh#n;)zIpu!mIYw;B4@i2k zp?zQ`qyG)U*nNs2j~gOPtTw0O)r9_?RC>azudofPs|dcOqe9ds$Ei(KmG*Qp$u-h) zqiuCM{qwu&dpa(Jj{}bF@d06kkhpcUoouy_tc8Y^%q>1ZAdxY*ljc_F_y3+<-u?aN z>UuekF#bnBEx!JT6l^sfUQp^}M(;--M^+bwsz%ed_`>maI^|Px7ghQ}6{w64Z~yYA zLHLS6m!vYw_Ysp-%1ek$}+uF|!dEZS{SiIfJ$qbJYZ7M&W3@pvqBg|R`nn~*w$&NegjVuvx_qM@M! zRUrS+*k&mW^x{%I`RX3L8w#Y-;Z0 zb?i$Exq$~nK?wR4{$Y$c!4EckGhr|a!#L`tVGY*^Iwpj!_>vL6*fGNV$c$rc3WhX* z{P&2|4evY?V1RaCvtZi*;D2Z|lfSd7ojU2k^RaV@hswSWLp4T6(I1hW2ROJKM<%ZJ z6VqIl@;-daL15u3j;edqVH9+!z?ZnSgNI&BVgBr9u7C*@iMcfeeU&9*X9w_(!_b*7 m@$R7K6s26ahde_NLLU_2V~_J;SnfR#4vS6rSWW33s_CE>CHvsdGGFUldI~d=h2y(Gdn9k9#s(Knz@tpr>iQLR1BBH z1OBPpUcNqz(hHiEWa6eoPU>>KvvGlfDgSJ?`DY#pZ9g;TO4157-xbo6iiNy`gEsCw zAgS|s#`GD%k*ztRE;dUv4A)!e?$;2{lMAn@N%Hfn+0(Sv$>hdbFNwNUdaeyC2bY%e!;}|=CU@LSNX|zirkF4*dG$77;+88ZN+i3b ziqo5d!D}jVaznG}9aTB#D`#NN4KKKv575K`FK1F2B6a;Vo1>}qH!x^6;svX}&uLi< zMoN|h1WgKlL%e|Z@YO9;f*TZ(lro#v%NOvxjXc?8n;hx6$ncG0F!LD~X9h^b zd@$G~XU;k^VWgCK1v?^lEK4w5S)+1 z_^}M$kB6kkg~J8f;JyXw+H;;hY{)Qxzq}Kx_jFt^D%S4GM@mmB!;8jr`!!LeQH)t% zg`GDx2kK48V6TUYx$|Ie;sy@PB*3N8;K!@z^7O-HdVGHL@#HiGP^NEBKS$eS>Gj9I z&KF-_ygNBPkFJAUtVAFdu@F==LB^o)QB5Fq;*IQ5AXUx^C?56t6^L{?tRloxrWiFW zwMu-NVJ=!%ZF?N1kaYBbVCr8%&f4_9-Xihu7CMbm0NJq>N2v3i6Id5el-tH(OC5Nx zi^?4mwMfvHkOKND=CmkAmg zvn_<8&y)=~&&;=SFy{a<(Y_L_~Mny*1|^Auj$nCC37f$pj)cnJWMr>&R=aOpN) z8?Xk6(0HJYl3MUp?Y8TysCE*>=84hpfKqg_Q~Fp$tP$t};KV%?1M3wmXRC7+R%ulj z{e7wGCp|Btow!c{Lnid1_NXbrRNe z*FZqzh&Wa}hrq}qXxW{4y{{^+;=50jUr69Os^<=hP8N z{6gxIH+4=Kc3jlca6(FXc48zE?5@u=5B%DIc5mvf>aD&CZS{4NK|v?Y^E!>~(Hv@6 zaKsvI*a;ta05vw?G*R7o~r*|ez+K0XWhB^=J>s$#);;i2p6 zTrw@9#n=R5gsT!hMSe1u*WiX(cx5;58;6(c&GKa1GftHBJ~eX6goZ(SfA{sv9vZwk zU~qE+R}Mh5o4g8+wAHH15&bRU+(mK7?w$aUHp(e4<|(virqhuuAPhLapWxfRO~I*- z0i7zTLAuZIi7zhVlaoGTd)vhB4eSN-8R?TUZ>mn8t+Fz+3Ptyr3NtuUoWX3-HubHL zcLmGGZ0EFD@3RK3&7c7Gv!1)PJ~l)3w9Ryh-*;9zXr?4wIhJjX#=`f0u}=@pCY;?e zyt(XWK57y434og!CD#c!YZ0uE0tL9tHAe*Y#32I=j3q<@*RQU-XtSV#m@8_Fc&hNa z66G|fwZ6C)ay*7fsb66Mez9l7vp?Uw>)Sd44Okn&O@PC*};Pt|OL%uXt zg;(EF3_Epe3ya|!T4>g}vUDxSx2R9yZq;3%KzF*t`L5H)U~CRIu;IS4?qk-@a0c_U z1=c7hqIG+_s(~KcgMYZqZE34@V1zb_wY6(maFSC7m3l$gFi@ZjGz+c~4xi$xqzf}& zln+BmUU4VV<+3ID&>!K~Up4ehW*!)B>xti6q779><6YUX-e%M=FY~~hWbX^=Ub4dQ z;K!~6y93MbBENbQU7T=XG37zx!I)0qR-}NmTZNQ*9Vaa>p^Jxbrg4Zlb6V`^2yw?B zdZ1B+t=J1i_sdG6DA!C60b>#Eyqu=_Jk73mL-UnJzBGuES7mui8Q~yPO@wBwOp0Gd66#! zsKnDWE=w$;huTMK#4@gK(&Wm#J;hh`qFOCM(L6s+rqxg!W~-riuSFP&H%Ws-0QV9 zr!ZAm<*DyaMwHPi%P^C35rLhgilV(zrt^J8p43iXM}#X`fGG{I(Y37J@Uy+XGpc5- zzt2SNNP`K56K}?_Gs1)GmVr$O zPX^0mDF`UQHKKs#>DA=r=#$S!F9+R~Jg83whh~B}O5=Ja#o_6Rn5GFuB%od#o*XRm zdkH5H(>$x_F+x_lDw8rm+IN`A#lVm}@#cuZySSrfLyxM2}ee;}6|_dXtp6Q#|u*r?jxCnND%R;;mG zxe^pd$Ks}{mgV@x3uvp%Q#rC086oDp=zTh(b%f0Bk|NJI@ZZHnLh*1ch7U}ms8;SRYh-WiVP@(>AGQ(fYTB#G zr=#EQfb?>eO~qH|=chit-(QuOl5iU__%cZ)c6h0lDR)BlIw98pn*}{mj=T-o4LuU9zb@hLiz zYLUP{XCGGNH~AD^Z1hIRewPy%AuA^?3XpJh&QDBe=mfrPm?J>%47k(PpAO%?3D~WH zHzpatS zl-G!8MkEUx-^EH+9CR29ma`zWUWEEPVYJ;TAs+q+!hL=%{?fU!FKL5TCBZmy)oZ#9Yyh$W@6=+o-gLc2#Mmty`bhf!sV%q`6+wTuCSh|J=R>_% zmJwC3yZE6RZ98i}i({5cIZfutluFmKR00@x^_|dKot?o89G*#uvo2E;6(uYTVG&IX zTO_IF|7bAF6hUR9^K{%mkA^1Vsw(!BNJFJY<(ihb2I?WkC0BJNIl}*ZSe^He*me&N zACDZj<~dqCw6{OBjD#)uNl?|vsuzzo>=(QrU>k5|1&+>COSh3CDX1XWBO$8LRJ0kM z4!gnh=_lZjwpjQ$6Vrb+M>j*S8fU+2+l!iznba!VpgyoR|`oLNyQ z(eqs)*$O1){X|^28qkj)I~0662yciKjy=)@ee}BMe^Zjml@$4SH|VPTwyPNJbKFcp zjBuxLdcQG>%d-@fCRO4<565+?%rV^Yq*dlqgw&2Aw+Q{9=|^HbFQY1t%A5S&giFqO z+KD;s3;UF0`kVo%rDS8NoZeq$gmVE*aVoK((F*sMcG8e`HLCjG|La@y>{=((aUos5 z;g{}+!IAf6ZveyBiZNaPt&-H8ut8FDK%;tHuWNK?#$ItBq%-M@=i>910eRbaTrW7@ zY%E;V)?_6mU4?j=_y3&z@ZGon;dIFDK<)lScq(Wf`)AdZQYjkVzO*wboepuc9~~2F z=8gW6Cz)$L`1{uW&U8;yZ&Nj46ni8Nr84^Hy-ljAHSSHRd7Wm*v+#;kGq)oISri|AB>D=BErMkzYMGz}HMmLU z?u7Ds)*o$s+j<)$*M`PxD7?2S2i+xM(L0*87giX!-8SrQjZiJ{uDwqS9{Y<49A;l> z8`5v@I9u82T0~rFDY#U)uy07LxeeSVfH-97-mD+_nn~>(S4O<+IL<#BA+9~=$+4_S zpxzan7NlBJL%;#}&T@J0dD4_CrEoDoy9^d9*06 zp_R7Y1mv6dzl0rXWQlcV%k{uv4V_!5K*(u>oViqzmN6d_clC8EuV|KT%%J8gV)W7akB4-RK;)XlgN>jfuO$7Z`+8B??Hs_#Tn_&dJ8c5sDf;!GXI z#iGV_gM)d2n_IJVrOXhVvbGwh#rQ5L?qp>Z@-)~;A69+1P15v?yFx*l-41vYf4&^g z=(L92iJ<1Mr3`LC5nPEVsSdHn)B43|IPdt_Z0y~|p(yL=6t`oCN=*~;-l4H1u7$0E zHW4!)HmxQ@8>k|@L(nVTX3b|LmebNb#<>Nn)S38s#EUv zHS^m1WC14D)7KBw%ueS0(^ST&MfHB#Z3NTFC=d+ag5S?oga~_l*Iheeo74#5u@mqb zgY`fTjrN6psDms&=KlysRC{~jLp4>SLejlG*Ov_?h%w!zS{Cv~W+iR{d)W}$1>Bld zRQ&3n@@=d}xi+It$oN6N%H`Gm&mfVrvU3hKOOPA7^ zjp=CwRR~{8)H1AuCvM$}>X}J{{K)x1Y3z*k?&Tw`8C(l}8mjpz@|g4AX7p&1pi-UN z9A>cCdq`!rXD9DJ?za=0c0a+6Z7xL4#HN^7OHla~=poQZ(_o$O9J*b-XFHcD+G~CP z!S*1nVrepc+Hwi;{onpA9+bfe7=z142&V}=?t5Cbw4gPOf~F{K+N868inqFRqco-` z?x3&W5+uUz+G;Zt6!ySK?^P90f+sjFN}nz{VOmhwyp;-DHENU4kIDiKw?-=_8{0Yu?8@HXttzL!<2H!=@ZCTDzO{WA2yHq%Q@=wM`pP={ z_;ZqH;x+!##{ZicBw2hxvQWyw?X8{{zRkOd>n^U|s_}b7FA(wd6!cor=RLN$oSvm* z)*WBE;d8x}9~1AjSY7TkA1d~{*X;`!{8uZ?mAe-P+N#mRlR4e;_npwqu(fQA%t;Vk zRc|kDl%;EZZ;Nb?bZpv_S#YUK_n&quyPoy!Y#9M)e>WF_%uNWVbB@hUg1}uPZF+NY zfmLp{jv-fxA_p#tdt7ANYI%zCYnWJnyFsH?|B0Bca)VZQVqFV%k0pwNxS}c!|GW~` dTm{0R7^)BN+SGcRY$?ZEO7HTz|6f;2%`-0^aZ&&P diff --git a/./.git/objects/29/57ac381cd898d5e8df9fe39a364541ea92f3a3 b/./.git/objects/29/57ac381cd898d5e8df9fe39a364541ea92f3a3 deleted file mode 100644 index 1f86b60c404be86f5e6b2922aba954800efcb722..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8472 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 diff --git a/./.git/objects/29/6b6de1a040c9f55d3a696cff14fcca8a00309b b/./.git/objects/29/6b6de1a040c9f55d3a696cff14fcca8a00309b deleted file mode 100644 index d5b059c8779ab6e04275c326b76134c7cd45cb7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7965 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 HZ!nwoQEGUv diff --git a/./.git/objects/2d/071a7891e073d79eef49d99466fb2166db756c b/./.git/objects/2d/071a7891e073d79eef49d99466fb2166db756c deleted file mode 100644 index d8021dddc9af336fb6804c665de3e840b33632ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmV<503iQ(0RhwiEB|6_Z(<-aFf#yn0WFR_3c@fLK%IMvfU}|2KO7S91fC(S!3Js) znjmB@xVX5eI0!DT>R>?x^)gy}4Aa;xFW=*RUzmsx1i`S;1mjS#2_qa7yL*{EitVvH zU5fSfyIO$JlyVK;&wZJ1Jg*6g!|n6RY9?IAsgNwf$cI4>`W|5-)>>lJfkTVZ7&1#R z>bv*E`js0%-*Oepm*sg~$E49zX^aML`ll9AhDS`ee%nPVsTGMiMqMYyMhYEM#gtoP Z9eYDcl@wf2`{zR&^nRfOTe=>>VGqOgWYz!x diff --git a/./.git/objects/2d/304f04e01e4642d3e3967a79bf8ee14068cc85 b/./.git/objects/2d/304f04e01e4642d3e3967a79bf8ee14068cc85 deleted file mode 100644 index 929a263b65de0202dffca8468cb8c7d4215e38d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3752 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 diff --git a/./.git/objects/2d/592941081b725f398b85952bf8c8bcbc0738f1 b/./.git/objects/2d/592941081b725f398b85952bf8c8bcbc0738f1 deleted file mode 100644 index d9fefabd90d2f4c0ebcdb9f55bd3b2367ae1c644..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242 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 NeE^mQ{TpSGQ3P>uoxuPA diff --git a/./.git/objects/2d/f39f3b4c20f845dddc2b685847241cd89aa789 b/./.git/objects/2d/f39f3b4c20f845dddc2b685847241cd89aa789 deleted file mode 100644 index 4bd5ecd3a4986b0a90f3463d3d74b3cc840120ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 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 diff --git a/./.git/objects/2f/99e45bfa0ac5b5aff91bbb946798943b209bd1 b/./.git/objects/2f/99e45bfa0ac5b5aff91bbb946798943b209bd1 deleted file mode 100644 index 2cf47f13052e17e83e4b5a87d919749a08597080..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4903 zcmV+?6WHu{0RbEn&`}AzxMhlTC^8kr7@h>gsM@mi49B`}bdc_wCPK{M%07HanL^wIiz=F~9Amb@g&1Ns^7D`MfMfc~{hx zXuG`W`nkxfvAE3JVk9PgHKL}xEV^59gMVWUI%KUb-uePfr;A zW*-luy1J1~%NThnimH>%Bp=B(<*ql1*}QH#k+-+isHn~3k8NF4v*o&~q~+K89IFjAz*;a;B0wlBK_<_2a5rn|hTK$BGEL=zqCNi+=H zl6jI;{AVmDBFl=Z=(23s%5t*BuoMbu2NfLp?LY%KI+$bq4{s0t_NMboboQ}-20pt}US<{QRKiy81efl6SqQNABl z!*huC;1EV-W40g}|NjE9vkYUet#MOq|Aws4Gr}(Kda|wL@BjN(ad!0KLn4ZavQ0A} z5>|)y|NEPGb98d5tewoPkzLX0b#j8AXme{B8L;8aFdg_CMTm1`$okx^20_}8Uf7`{&nQ(F|{q<)I6v! z+hSbg)c^w#v~cT94rj;mpnb5@&E`AIEWaNppG<(}Gl1f{ET0!qas)N#o04cvyKded z?Cg~FC@-gV+a2tGx3~W?LX)vTGP{o+<9VB&mswzNMC)R5o3$r;qb+ z{1g%z!0q>A8I@w-@$&P#qjwj-J%4|8a-1EVon*gz{fGMqPlH)KmMpfSyZv(eSKG)A zD%oxC@9uxQx4XX|Maw^DU41R97My&$`~9y&E{SodV>Xu_hfnc)6_&-UB5+pk{5fH^;qpJ9Y8 z(lCNgXXC+xyw-g;?}>DTRrwi07qUXw^H}{|Revhw_)6})cI`24N<_=Lild&YwisHM zx7R+>1wHSqRM5PsFH1S|F&u|yx-r&IKAINDfM%GnapWTpAVvT=VpBgv;KT!XcocLJ z79R#qwrDUZMT;>)!%pp>((Y*E%j)JTuZq9OCdKQ8-mOKr`DtGvqf|c7c|hgf1a)J< z*cH@`~L)T&{>zEiu5OB}AwZrGE)*|WH8pUNG z{=;FWlzG+9m*Y$rUkFNI)kGbxiZ6a>F-ZV3s8vFtkJn96wo>^S3z2r{gU6$WR*fiF`Fp=^fyJHuG^nA9$OF2?LY)C-E63Y}I?L?Or}G&NsXl@VY} z76}g%%q3d=y(KDd6(3kSkYMz&U%KYjF+nXa!~!3q8cQYJw?!jJ{f-Avj=_YNeNcM< ztjVq^()&C9Cl;A+33G5~Sp1flvy0O=||MiG>WsCO~p3nEs#g zrosRWCs;@I!K{5s4vEG+S2Qnj9;&-D@7wUiywvj z_4E|&?ol1BHknWbYEAJ)3h+_Cy7+!)j|y+6l&icx(M~XTZPC`uE&p9!X6&=pwHY5b z^&bqOuctu2w`kXos&ln1`LttdLTw1^yq~NjMdn|iBGvJu3PLhl%8J$mlt=1JYg7E@ zP<(qpsoGJ8_MFQtE=d#Zl=GHPb#uL>A(b(vel2v8d{C9zNne(?TH8qI?jl{^8*@*# zA`4)O14+nia$qnt_u#7xhGZZ}^3Tx+r_Nywqo&ACdGi{%yO?EvmNS1nj*??`Om^iM&(YlCs1Jb;V)dtd&*Kq?Qt;`EI@rKYc64qD5Vr9 zA!$t#*yHUMT&c0-cC4W3@tKxkGh$SfgHaS4F6_(hAY+%88E6{Dpx`7PLN+mT9-&9X$txJ#G;H%2u{0nE@q z)D+kpPClsM$itDF@QJ-bj16}d5(*gfW}Vqb)MISj8{DyO;$Rw$l1YApNuHw!Yc+vz zXZT!>kBK(gvd+gG887R)RQ5(`7iLtb$AvIyMflP%qs-WfsCRTY_B>OhQBvs5qKxFE z`V097-gH_lZaG(qK*H6kfB@<^Oh9}p=-fo!6g74V+mWp3tKIh|J5?hZt)CsUX3YlH zsUqq5MyuAPknsiK!V)8dZ8JQeEo2TO-gXw*R0Z_Z+PjhrC*!uY1fwH2S)O5T7n~*no zxn`QJW2!j!c=Bw)+Um1AI%Rub;D}Zl_tR569dg^j)4Ih z7>Eb5j)gQU9AY~-G-C}_oa2HSn3M~L$xNQX4MWFbIDeM&uYjQTV=f7Ufau0g#K_Tw zl4zOCa#q zHHGSY?rii}O(}Re=Rw6Y`)F69HC0rosOX;NsC^>Q!o%9`IiRGnak~uWZ2W6^YrPZX z`ZrYyL`qx5&P!$~F!A8wOWgdAs=}gb;m_mlt;%>A?y(;!B!J}`+jm6RVZdL?`QDZY zUn;S~$q?v8yRfGe8601zG;puHj1ShdTr^v+A`p;M{4UW`ub)y72v>@M$e&~hpChwb zdqwf-Q(ka(t96Yt+`3BWpGTp{wj7!~N_^s*iUa?ps0)2+Gw{jC4B$$m!HKE_9ywDe zSV4_W1EA_aW$N3ITp(%O$@{Zr(I>1`I7je95*Ue$UFbs<5>Ne zD($KS)dD#`gnG!tRSqRN0i$QBcpyBH1#Y7ip65I*XNw(-yDYbMWuEoF$EbZXgES%c;bef0-lmGTNnne_ny>3>atf_j3qv|n~rPpS(9tN4U$c0;BPex;*CXj_G@h}Uh@crFiEkxBxc5j{x23wx#ul= z1C8UJerJ0$_&B()n_T%@zuIx6c>$f8&VF@u$g9jYc9*~eTQB- z_%NsRdZ5}CQFx3VS5H*PgX^EeVAciFo`vgf>eKKxZTFop1V6A}5y5msk>viJH>*2l@ z;i6pzDvAX;RueXxmEUwePplc|)WpwVK#rzPt%iA8g@81Dc}2Ji*faJZOR-vYc5Gi! z(D0OPgNN3=GX(PKOL+yo^oM>z#zNN+#GOafMUQZ_37bC3b6}WNX~n}_Sb-2QkEp_E z&HV}W7&>5r^;x4pq~St94Si=1O4T1vT=)gNq_KIe<+Lyv1KZ`jcy6+X^LX)0Zj zo~R=M^0OTE{JRSe=dVPIJ~3_hoGx>qb;3Y=xF`U3@YX_bK1$trvTwN4QRE_21lzw^ zrIe?VHzr2ckIdpluee=CjRAAk2RHN|dX*1$``yNI zHAmg5(SEVDh6!}6F(fCp1e}8&R?Is zKfiF`8y|W&B6Tt9g3hJ#l_>lo!JWqo-2^;iX7JwUiW=USJ!4-MYS1Tb#W@z4z#ICA zJ0A64?CtJu9;ADd`)@Y*S3s~kOF!|@9`J8_D3kTFR&qW{tBXo@BTqUEE@3%zK zFkf-=%uW^uy68e2u$6L-I(i5HPeMkTU%*gV4MfuGu$OR4QU;UT|OBu_AJZ%<&oeNIP;15H3$0H9z|XqChIG@;yprvNJTMN zD=*7nJWdz(7jjb873JJs^wpg2!9c*fSq+Lq{E|hc(yuI0c1&7BmOiE%!}>>jXe@`y ZnpA)E^EU*Cm7xaS{3q*GgYC9sXyc$aZNC5j diff --git a/./.git/objects/2f/d4d0f8d97c50440b62e47a3f3e027cae115e70 b/./.git/objects/2f/d4d0f8d97c50440b62e47a3f3e027cae115e70 deleted file mode 100644 index 78bc3d0f943bd030ccafe84f83c0958080734e44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6014 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 diff --git a/./.git/objects/31/9d3be980a4c324c5d70bea9d7475f50e124670 b/./.git/objects/31/9d3be980a4c324c5d70bea9d7475f50e124670 deleted file mode 100644 index 3e5f4b1779e7b84e8538930f52e486d1d890ee00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10384 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 diff --git a/./.git/objects/33/2f10f7331e8d7a42cec6a7242bbc0dcdb9027f b/./.git/objects/33/2f10f7331e8d7a42cec6a7242bbc0dcdb9027f deleted file mode 100644 index 2540beeaa5edf975e19ae9e4c686ca5ec56098ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2139 zcmV-h2&DIT0i9UOavQr5&6;0{+lk@6xgSJ?#6jnmK5nEPDci;vR}?|wHF*GB)Wl1kiGrcSEb zsKH=36la;iqBeVRW!AFDzR4=n#b6M}@!)4&n?mN1c00;U2ETp(?{D9K!Cw(YS&>yy zG%oj|_Xju05-V*)xvw_55TmV7JC)Q7u2n>8 z4ge6Ep?G{ZekC;cvQx+gJcdpl`<=tD0f50 zHL$&?qC^|@4BJr5vFLHEjNGaxZ63tQTyM7o@DJklAuIk>4#YEtSlwoM8r z7JZpMlk{PHN$hO#WpRFWy|{@!eO{im*P=$$qsn#3zFpT@s`9MpIrr+Xx68BT>@r$j z-Yq`e%jzPa$sYU%Q{tE9fWSqt77(oir^sRc2n2g5F9W2T<(<_P^6B#zrWMfjZVs)!55*A zLH~r{6VKFlk3<&v-u4xQfPQzX|CuaxDKw)RZ7m=2^+CsG_p}XuyO&lmb!@WrW|ULy3SAOnfO2A- zw#S8F*EUOz3d}dC8Irn1LjyQao8WxxP!l0%?Qlq)Gkyf9NTo@$Zz>gVB9;l>R#sgT z<{E+8vA`k_9Wf3q^1rwndB?t6Fr!@)M>>1rhw_%L zsq(~0?60;HfcAS9Ei5DmiUBUp&@f$lvZB&LCXg!g%x(xd_I9F@_Q9SqZa?}cuZ=v| z>yMz;d~Y)=h@^YXlK6X->nEsbpk&2HnG8WTdMjeT9J{;>soNYjYMogO60ADW8hHYT z1n6D0>}e$s1k4R2@rcnXbv>JKDPu4%VVcwEW_#y4PQ}J?oUH6NUbg~OL76BHlHR+; z_vRZVQFV7G?RwW zWZv(jFrnE4DxYxLupLWgi_cxMaK%tsad0WZkc7#l$;!}|+e?_BoEDl;t)M0$Hi!wm zIw*5}n_~olNInIUAxZxOvIij0P??pORzPT5N(#0g4VwBrJD2l;@4C=wjYPm*w}pIA zQ4-j~kD{C=ry@QwHN_ALjkQ1xTCi@FY?@Ts3owv>T0uPzO?VrmF4l1~osgcU{yY2L znHUe4H+g=sma$$-o5*|~uyPlHFMt71pLMVJk7znP`E;|HF7=T5{fQS}>Q4Qm9`v)r zj_HKHr`M$wHP|R*V)B6<$mWmMr_FsNP4lMePAY|{|&;}eTpHE8zM}s zHmBm%g#MjWdcv!(unnuL2)?DGLewV5sZCXt_H;7IHPUjUZFM^R^SkMLIxd8d1CH(S z0bzuYxOKFhY_*T9g@%>PEj~aXkukTE=2qzU|DIjm{r%?ZdO43U{zpG8zW#?4Y&9NU zQ0inx??)d;Ru_e;M$@3GEHV!{)#`Ni;L0O*{i`o*Hu|pq(bdLni-y|%C;(gD)dvX(zTi_+HF#a zlm^D5C(qp$of?Yqcr0{K?os z3&*sgz#_FXBE8tUH(cCytHE?Emc3Esuob+=k?{0vYVPE9>`M!|fd@oE2>KQNVT?J! z4>o)=VK55AIO?Tg4c7=dCWNl|k`cbxF~a=FjALyIhBScu_lVRD?>rM=fOcQAVA}xT ze`qw5zq6{HI_bglv2%%s%DxXnHAY9#ACa90IJg`~Ca(4q(_EJFK77kTVBso`s(aL7 z6m+S;m$_K?(*`MPZ7gFqLUl)ltP&b2M?)9UWJ_YSdCrs%AQ-O6`nlx@I|+D5t8K zTGq+ddYqnG&dJq!onFXImagV&1*ag&R<*Cz@ATIOoPpY)GgKRPw$(-NLen|R-WK2i~*dQc>Lb6>*hS)G9!$NW$-n5O4pgwY1b9OTAh%!0acpkHp{rH)) zmoL6^`JL6vFF(8X_G3n}716ym$HUKDhkBo0orbX6=Qi z*3P{kA8Wq!^xBVqnLSWnDAr3Jvrm@81^dtvE7rqu$v#o6mRT_@`*pimXLiZ2Epl(c zs|V#1p6zk&a~sdGYPmk2{TO3WW+$~cgC~lmYB31n)LhxCvPm^I4i&?NNh3D)mrG%+ z@2xMz)`N@Gaj_a3_m_k4W2~`)mfPmN&}H7DS7%hn1N;IW|0%EfxpuCT9KY%p){p@WH__d9-rF(H@2tI=W|gDW=_}h^wAl zjk^?~EW@&A498*?RyD_a(M_X(to13jT&p;HqS&a0?jq(g&T%gY-Qr@| zZSd;4+Hr5`UNrQ0d2Y!Kf@*AXFT@AWmS}=oKPuC(_aOz3AzM}|3b!aYtiZ0{^w;?x#{3gitmV70}j8!$5hAc%gX5zEbpWd1*k_SDaB}|si zF3yD>x6Ac%SjH+wVm+hr&ZpX?V!iH%_A#hjZ82PmCfc8lkG;lSOw7i+=d~!k5Qd9; zW@aLb3IUaNwL7FoYKJ5%8^6{)7K z&*zKva^&$DIc?L6OL4F3F8Wx0nl#s)M32D$vOdjH`&5~7xEfzX6D=K*8a&HH_p0l&1a1&m2t%V^TaM+Rn0;|hLWG>5Mn7I81vWsVWqN6{G^krN4rBwK zOx-s1^{K$G*S&CR_paTy|MsrkyW0(?HKybOR+TD}AIO zwc-w9(`^ zVNJBX^QqFnV9iir*!&4^9u35=Rk$bm>bgm2w?F2ECq1uj@0#8vwxiwCyX0FpF?2qT z9-~_meEh%Jbeew+5Ap4kO;83>qVQdmO;dIqWjiRd+nLa6Qul8m1@uxf!Yp)p2ub^* z%NWPkD_XZH@*DB!WG}x77k)EkU!m-)l-)uZ89AS#OpJww7VG7DD8`44w(1wdt?lks zH2*qM0JTf(E(fclKUf~!-P-c*!rlL;6t_)oAo(PG8`1LO{4_dlwna7I3 zG^q17sI%C-m;rT8Np;?N_24#YcC1cEH+7HbDn%>W%nxVL>1`OxI@O}ZxVAz)j1~bA zWKyUE&9`@Pm(4UN^CcQ|JH1!Dd`pa`#d?5VP75Y6I?)rlv$dYwkGq@e$wu}g>Zybc z0jWhj4P?#fRuf@ba;wMt1dZ1dh1*-L+R_SaVt7Ft_v(IqN-&?< zNsE62FXVSnb|+OqDv>lQP3H6~3CltyPYNxdz8|qRugvgTnC^gy8WlkRHd+~s?dz^y_C^NxJ6lK-}^RIX-J%wo|E-$ zz1PvookS92BbC~w?Icwq`Bkb!@4bf_6EJq87tOngK??~l&MDJRewIXn$k~092>NSY@#R{x~1XmLkh^c=iyTR zOa-P8-l!*3-WMVuvaG(YywUQ>2xWjmE4siLghd2P>uhV2sW`R&z_%a1k5jKs`sm)n zvj=DIb5aKmAAaz#W9>V9@S%hI_TC>2i*dJA$FF;~KW9f{G*$sm8#lvQsOEn3Db?oy zm54}9Y7&QXB9!2YUW3g*=qwtG9mmqLYF@Z80G{n#{`Y3;wE2c7M$*wAF`S(=Cc8Tm z^o`cI__bwmm_WG)gTe-}F*w z(Zh03p$wBX>5ycTT7mW9UJq(HFD3fxX9JM*qL)E7gi@XjQ}6&i86qrXI0e-DprZW} z*Vrf%+&OJ!uJ`P$p|l>MbKpxL-bx}Yv;L5cLU^&>qct|D}G z8HHtaF}19$D60TBR|UAyRS%7oRA{b3gWYtLg{JB&6mw98By&`$j~Yst4SVLarQ1~H zl%^`8ReB}I)MbtId=;L`(+bm0X-{e=)syITrFT_P>6)jj+)*2+X*_oFYd-%*^RIr; zeDM|e&Dr*iy?j2{)2>nLb>FuqasAd_dG68&e}Cmy?>2vZ=93R!Y`*pM+E342y70a| zOGeRr{i&#t`l7nd$lEUw*UG@Q)4j8e&v*&cg#vJj8DGSDSAljOa$ z@AKcq*g0{pI3tXvh;1fL11TsF; z$Z3PU%O?anUgZYUCLt8=zfq98XhjqBOU2cwKmjYVWVTD7US~nhTrCueSIJc5y-bDF zQANEcx#p5xIOw+>l&%GE>~K8SOq+bjLoiUbbAXDT>h| zBm=f;ALf^m)xzg`Z81}41J*p%Uy&4$OxJ&Dsv z+-$>81(A$RUj%!vJ!(M|VheF+5ee>m0;dguf0wfMr)cnT1M1A*HU!NeID5@gV@SAyNtuYYbgoiSU>@)gSako;3xSc ze4LI>R`g{c?+9=fcYw^x0$oFMxy2@wm2~oqzO3`zOlJn3(bqj=$!A#V2twLWM>G27 zA&C)o2P4)UBLpIFLta*ydG>-=U7TxF?fC{y8NfoL9TainfYgze z%|-)+U-ls&rQMRpY8Ej}{vCX2+~0=3Bpww=Ei)>9xz5Q)<%cK}m^avNOgowgtOri| zWRceawXhFzq2hefky{qSa54fY-C8go_Yhzw@(pmu598T?K?=yo^6;$k!i$%sKGpa< z`buk2uMMkROH1AZZM?L!|4BQpT-sFyldnco_j#e1RXCE|4!khLci4?Z+pC`_bH7gR zE>?q=i@=8f<|(G^I@*cWwE(8e7A`H&rW(%h=6|Ol&{(osbW`#v*X(J_58);Bg5mGn zYWTD|(kX6@d63>DW*=YKSloJ~bLjd%khq5|8eccg4NrHCPDf)(G|hs0hv43)h3!yO zl;eD48TBLBc8F^;Z97>JHXX8&vnoYXmvMr!U7YQFS}mLV7lYz&k5Cp1K5)_z&1Bc~rSNo{4sulH zl~Za&dt3*GFo6IQ0XafWArpcpfRY3mRXGY8c?3unrk3si9W+1(3%F~73>qymcvN|m z=%DeWL2YG;h~cCj-Aw9KtS_y@AIZQ1I?G9{JctG!!Ld_v$|+2n2wGa02LB_BUE&93 z8c|2aAA=;$9C)JSiQ~)IEH2VnB^E@0g(-Rnc#$4FM%fX{>~RK1cf0CeOor@?vBb(JNn+m`6XY0%5kM(OH@WW}0ua(`YDJfv!hlOGK&>JGsg! zYJ7ycIR%-YI_%@1E{zW<|37k(%~>C%N?;C}?{b+LE* z(uJq4JojGb6^F%%@&?yQhW6!{(81-`FEro%2kADoKPG`K?$A=)MwtjjlcU~p9tbU* ztPH*ru52_N0sNiVyx0RKXvqj~vOReOEOF{$bRqqQ=3Rz@M>|mUeN@OyL@XC@#ME;l zqATLLx^O6T;V|fxG}F!zWLcGr$jN=MDm_d;NAcfWrB~$SGUO@+k(1kytB})>s|+yX z9D+$G4_1cC#LR{(g-Wwu3+?FFWsj_@*s;&75F2R31)s2C7fmd zJ_#PBOsvcwQ0WgTBeFX=D(|j)6-&cMQ7ij>XuP!@hUN%FDKV=I`pw~ zmBO#lqVGn(reT6jnJH7HKjSxMT3kOfO(_wzPpyphMbfevHLt1t; zduzTgZ{&@ET(*CpXS`3eg4;v~f{szaQHNNYSn~q}idz40gMc#uKPS|2(qul>^09;| zgiDJcgX;z)K8t*4M2W8Ye;ZEbzkrhg!l>bB0>?)*n0w$E@?l?ohLE&N_W|#SNV-?YHv38u%cM{Z`6o z;x}87*t$!$y_>)x*oDkcEa9T{feMYPIqeqR?8zc>(Wh!dNFzuEq%ov?pE^+ZU%(;$ EbV3WJssI20 diff --git a/./.git/objects/36/7b4da555ca4317127492a964d72358f0e32087 b/./.git/objects/36/7b4da555ca4317127492a964d72358f0e32087 deleted file mode 100644 index a34ce8f06684b8642c127544e42febb154637afa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4437 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 diff --git a/./.git/objects/36/bc3e559fb43177d99b70e4bbbd0e0d02612a76 b/./.git/objects/36/bc3e559fb43177d99b70e4bbbd0e0d02612a76 deleted file mode 100644 index c7c5394cc7fc49694596de73f4752c4257c3eab8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1073 zcmV-11kU?-0ku}mZsRr(?paSUs~qgKvD|L|fS^94*E}>r8g_55}aU4fyXe{VPO4ML7UDOtGrNJ%`u2iF?C_(ADkj0N=P-}ICnN6Y- zt(L_M4WKY95Lt%CfK}`WW<{Q(MrpyWYBEtdSPEhi7cVNRT4@VrW!8V2DLG~rQLa@5 zwXln_m_o2Tq1PyiW|*`4G*eY2*2PFDTc2gN!0gTBOgDG{nW$}}F;$JN8#{R=N`v0%!+hTV z`O`zpFoKh#)6fbcrwD z)of=Dx4!|fH!d&#M!O~Km`YYXD7*=9Mj9y|pAcZjvntmk%N&rg3 z!&oEX73XXhhBo64xQ}~X*afxEmxH*<6TEBx0?WFMH-giJx~7>c47l}*7(~_V?ejcv z%<~gLGLf=q@En9*UVL(ylSh@7|a3We3|dUjHWDAi0lQD47L4ls?WnCT_~ zyFY$`)l8pQW^zyA%Vs-jC2YYpV0pnE|A$9y$$7b|}u0WqZ} rx)f(<$;c$Zt%uUyf})7vrl}MarYTHF!qb#hJ52*qy@dY*g=IlnwkQpK diff --git a/./.git/objects/38/2460d73ef14b725f41130ef05a2f2e99a1b5ac b/./.git/objects/38/2460d73ef14b725f41130ef05a2f2e99a1b5ac deleted file mode 100644 index 2d071a7..0000000 --- a/./.git/objects/38/2460d73ef14b725f41130ef05a2f2e99a1b5ac +++ /dev/null @@ -1,3 +0,0 @@ -x-= -0@{?!j d.8.`A2Z{ -/}_P #{=<]Zєr }o< j&\ǩ,ȇAa"ZbԁC2K0@r˗y]ǤTinQGL~E*9F]'F -SŔ[c{CJ\QC~[ \ No newline at end of file diff --git a/./.git/objects/39/4c150fdf47fa3950fed889f8cfccf474733668 b/./.git/objects/39/4c150fdf47fa3950fed889f8cfccf474733668 deleted file mode 100644 index 2a16b1253c48b5314b571fa80773065a6d5dacef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4729 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 H;zj&+0J1av diff --git a/./.git/objects/3c/d644f559c90a51e2cc0508224c8ee41205fe06 b/./.git/objects/3c/d644f559c90a51e2cc0508224c8ee41205fe06 deleted file mode 100644 index 8fbf2e88a27f781c1a939efba906a0222adca000..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7125 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 LN{ diff --git a/./.git/objects/3f/7b4780ae0ba64adfd193a60ca265954d33e709 b/./.git/objects/3f/7b4780ae0ba64adfd193a60ca265954d33e709 deleted file mode 100644 index c348dee6a095af9dc0995070bd32a369218d2a94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4015 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 VtXjrp9^p*l_>= diff --git a/./.git/objects/45/e639d6be112eb3e32c6073ecd51ea9b69d04fd b/./.git/objects/45/e639d6be112eb3e32c6073ecd51ea9b69d04fd deleted file mode 100644 index c33de4e73d5033930b8c52b4328d11416a4189c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3161 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 diff --git a/./.git/objects/46/657c1ddf065b126573fcd2854cd7e388eb51a5 b/./.git/objects/46/657c1ddf065b126573fcd2854cd7e388eb51a5 deleted file mode 100644 index 284a36a1e3b27f3ff3d691b3b73ed4652236cd45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2036 zcmV3`9aAY6xHf z(6;i|_uBOzHhb}c zu3QSf?y6+Go6YVT6DhX3GAmOlenzUUgN{*0s)9*)YN_(IroTF6P1=%jHN~w<>WNH@ z9oCr|!4W{$WE`bN({IaYYNH(e>AT<%!H2bS=8F#02LGq7(kb0xUEIc`H&k^YAcWmg zqmWJxu0!x4&JnWmE*dO!iR2+xx!M}Hp3?>?;0L8`tEn+K?K~%_A9TRJDriJ}Y;@3c zy-f|yx70Bq3xx_)NFFQ5`9v#kt$)&MisWR*-JV(xs~IeoSaE9zEL#Qwzk}lytr7S z7@yC5(EZC5e$y(bjehju9vQ5R2ASpJ+}`tXa0ne%3N?_B=Z#9A0gn7^ z7uKPWQILUh4)35`fgWC+{y?kF36=yL`BWS}a)d?l8&mBqq0Siql3(sYM@hx|NdLOI zSiGInr<<$0i#aVmEZ+U`fBd++S$w$t^|zb%|ISI~V@U8zvu<8mt~7C}CB+EzsRXoD z4Q?6Fa9f>CrnQ>n)cLG;K+ES#97X~aTnL1e0~Sgs&T^>}Dv}9M7aA3s;GVDEUQq1X z)`!fkeb(`Hk9bHwp3$%7iOzdg-DaF$2+n7-lat>gwz^F|Y|lGpE*?wwE&go7}Z^E_p)$JwhAQ@L$V;XWETiqd~IUQh-#%4L=+gN~duR4&e5@1?q5 zhnQ4g6*-OaWTh1Jm#ut|a3x1CGE4AQg;G+QO=y3)$;4 zy2|cInO2to0Q)gYQJlKMYu_noUv+ng1&gl(8OKrv%-5z$)cD6D65`@8IJE{1v&t>I zb>E-p)e3?LiX4ZVs23|u_Q*jD8A*Asi9!YqxI)q$T7&; z;+pjthAHwf5gk|YfDQ{FLn{5uOK)kF zCU#)mqBS}qjo@&Q9M6KEwC+sJ8b2v%)~|>9r+v_n*m6lMb}RIj=z$X441JRw@VZlD z@)df(iPX3=Ll5O4o{?paZxLNcmQ3ze(QuBdaR}F+&gi{xf~C8d{W*8E*9BkS4b^=q zWbe&ABeQ0YapV4Shh3BYiAxxINBE)0rnH^twQ~y$Z@^O(>i9jvIC4-nnAOYdE$GP| z8fI0KU-fnaKtPMN%k4{UiT8*;Xk%{4vnz{fsnl4rJVmt{>D#Jpg+Rpv%DTk@1`mJ(lIT+@GXk}b-{m_*0zfczc5pi{jJ_R|4nmE5H` zfv-JRmev1JkIG;?7?AN?``(<50VWDnQq>rCWU_?e^Zh{=F?T?a z!Ok>j+9(*|Rh|-gp>YytB7U!O!&vZ))l=vytv6cM2NS&G5f_`mww6E%R6{{9vP6s$ zVl9)iv-IBUZBFg$^4O=A?<_*>TcH;#xwMi1-U0H5dI?*Ar-m&ARr3~N!JN!K5RoWm z+e6j*d1taPTeP|I`di&<^jxmm(tEMHJl9VGZkR$xsDotYa8Me|-l$nltgy1)ZP4>* zCt)M~gQfISeeby?H*{KZ+fuM&C_JbfQ&KIXx7>NRfM7_q0i}^Mqvxr8H5%(IEW6%w| zKm%cOs=31u&OtmtS2ph5Fxqn?54J+@iI_20EIa+M0k5X0Ryu8mZf!41mxHw779Z}oWmJ}5M3X#H#3IoH(1z2oLPFbHRN&h=^^11G Sh7S|iN@m_t|Gxo11~wsq!R?#? diff --git a/./.git/objects/47/8f1ae2938d64030683de324a3363551d9a86a1 b/./.git/objects/47/8f1ae2938d64030683de324a3363551d9a86a1 deleted file mode 100644 index e265e7f79274a0a841ddb0aa4c0f1a6c49970972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3473 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 diff --git a/./.git/objects/49/8b267a8c7812490d6479839c5577eaaec79d62 b/./.git/objects/49/8b267a8c7812490d6479839c5577eaaec79d62 deleted file mode 100644 index b558235fa77fbc9e56902bc61148d32e42a70755..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmV-T0J8sh0ZYosPf{>9W(dv8OUzA8Q7B3+$S=+;$uFw3R!B|BEKw-Q$ShV!%gjkt jP)|uMPA9Zjg diff --git a/./.git/objects/4a/6fa8353e3a6632531ea3efad5cadddf8dba5b9 b/./.git/objects/4a/6fa8353e3a6632531ea3efad5cadddf8dba5b9 deleted file mode 100644 index 1e471ec..0000000 --- a/./.git/objects/4a/6fa8353e3a6632531ea3efad5cadddf8dba5b9 +++ /dev/null @@ -1,2 +0,0 @@ -xkA zA,.R0baf[2+UKUJ-KFgϼa|ހA}gu::,^#23 P@FmQ]axCmcqP @7`?( Zd"|~x墊&Vvwk'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 PYF8Nys-OHf)-LuSo69N- diff --git a/./.git/objects/4c/76c67db4697071912a15f7b04f369a8af9beb1 b/./.git/objects/4c/76c67db4697071912a15f7b04f369a8af9beb1 deleted file mode 100644 index 13cc6bd7e0054372037d04dc348da7c0505a14ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1542 zcmV+h2Ko7T0gYE%Pa8)R?z4ZzXnBY&VPgzQTO?OSCB4%|OQ_cN|7N6EC`gkSj1C)B6l3genN^j(QYmKw}n=*i5&_k8LkS|Q-2z(%QA8~3)3h5`{g|vK| z5>``T01*qU&|(C|`URoWua%cD|ENrHR7sNc(ma*s;6*cFo@+Prg_2JfQ}_9Tl5M)w zbCia@s*>$7L4m%{JLL>klj*oT!xgAbEJuqs2{1q=8GKWs-x6;~PHBEX%eGfojQ|QlH(j55ilE{GDSkk(pudCqQAPdzzWfla zV5n<*UO{E_T{??dGqErer!Y)pj2M_TQ&Dxo28`%3hEfGz{nB=Vg7fOAiiOPy*KsZ1 zm`dF$%0Ls)um=Vgh0(qO#R|A0O)GHsDN)prdq6PiQU+pbY|LY}<<*&xQcoNSM7~$Aa$3ncOGBrF0Hhm{?fkxEAX^l-iS8tch~QA-fncZR)9I){5ih8 z49xLg_qrGFD&5hu$I^yplx}QC6?cPQ9HN4lWW3>BY{OnPCcMa6X*Dgl? zJOR%3t*f0U50G=?@95DRv|{!5&hzKd!^=R5pKe9FH`OEEOH2BZ?w#%K{dEwXKJi)S z#ZT?kr_rUI=*4Ph=SqBM2kyI9uD9Q8$7@Ti*E{hayP%-Ec@Z3oS6_h}N?E*qLr0^t zy%jD0212df^=M_SuM+KmXgt3iZ~hxCqgJpFoB^%z)&sTdAG+Z5mIQ>80B{{PxITm? z>HRv{BhU}hV>>L#gJEMNC4qP`LKHB31!L~Cf7(A)cLjkT+yZbYU}=?7!^VK^He+(x s(rw^)A3ru(sgx(*E8}N?QEIw1{s)hM|BFD)#L+(J=_z&LKf8#Mb_lHelK=n! diff --git a/./.git/objects/4f/0c23ce27ffd734a22b81c92109b5603f8df1e9 b/./.git/objects/4f/0c23ce27ffd734a22b81c92109b5603f8df1e9 deleted file mode 100644 index 963e0d8c0d25fd769417d3440dbdac1cfce539bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3211 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 W45Km3Hhu*xqM~VAzV-hOUHo4XM{DW; diff --git a/./.git/objects/57/185ff087c798f9f7146e793be1edb064541d3c b/./.git/objects/57/185ff087c798f9f7146e793be1edb064541d3c deleted file mode 100644 index 5a26a408ced1f9661ef5523ce4a1886bf12e6caf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20793 zcmV(pK=8kK0i63|bR}K*?hD6u(&^Yn$F|+E*|E{F)3I%>*tV@sIyP5qn=2>J-v9SK zdyMntjPvEHt7cWrIqRNpKpT}#6!(l7KVi60S=&@Buq$=E-1%a(68lz>F>${h576l5|1kS9}`Ty zpbLQiN0#Bo?Zi`-Va#J`=d#!Pp{8Aq&_{WZ;pZdMbH{KSV5AS-=A75`_PN2XEX?+~{UeQ8n_IU|^$w?EVJvZZ?L7DUOWWjxJu=#v(Z^OPK zzl(Q&*uPQn(e1#RA})a6v1E+@X<9rtZW$=~_$st`E7UZoOELN(8O?-5_~^z^7NmV1zMoS(m*u59P!;YzZ3r;v5y z-B?6rkFM-XO7c5w<>Eu_2(TK(ia|g?+WW-Qj=xp`{zxw8O8mjofgtyQ>*}s!U8X$u z?`A>T)>`*Lb1{2wVo5Q0MECa8NVTun=|dFs&3ti>j9O(pZ8lZSEf>hP6jQK_1- zxXPwoSLXSLXU^S1%H-hHlBQHmt(;E0M`(zD5#NetEwEaU!cLm^_kCh$kFKkk&E5EQ ziAy)FFZ3&t9C(DqN?U4YiVMk2&$)(0o30GvTw-#(i>VZoe)mGuCYRKb9c&RTd~epP z{VtN~a)aYOUZP`9wIHNsD(~&KyN{x`59P3hmDsL60{_WOQmhxHjU4m;0@z>1pP!@w`nDClaZ2teP7pg{gl#m8smw+RY`$VmY1L%MbSH{FoP2a(`16FFurlp!~-3TEp`PH*UP6`>3Y-ll}K%FXu}) zX#{DJ^1ZQ|D{F&}`By2c?ap!oPQOule8b(b1$S3i8^n{J8_kI)K6Xa>_&IY`HnZ|B z>(CM1O(EpVsV*et(=mcP4U$nEtkoff2MiI@4ldcVqs(di^G_h)D$zygsKzEE8+Nox+r;uGQKflkeui5WOu4qK^ttmQ zl3Qz{r2fS2IyW{b$R%+dMY@X#@yY838ONqjadR`Iv(rV0%dfS)X5*lxhSFsk;)#KK zev6Z&e){;Kb4EJW>(g2eo}9joH>Rw8+kR25TdFxF*g&P5;61i-zAxLgRZJy^6!vql zzfquLO0{x_8&CUan{VpxUFAzvOY*6VVz=sHfYn7BEr}yT zIie)9l5LE!`>tD@q_exPr$399cHi(G5}!XC7TGEoi{~P&<;u-_I*Xfw-V$|G8=;G4 zu1?DrQs@2*b$X}#SP9fyycAoldwT9<(U55}sG)O{-A$TGs6%?Ob>1CkhjH#l(&ZmK zaXpH&9hpBnmr^|FVPyx?HB3BnD@+K{qC>{}`v8%j-z7-U%S{X<1o^!@RT_jnG8}d` z(oSq`@b|Jmtl`GD({1MTy|^FybS8tbC!+pSpX_)N@qO+#U+Ww^LpAj8i?5Po`^IqF!>Z^N&#{HYA4d=;5Q_0)+@~CmwNCwBzJvHVqUq}T(=$Fx?y zFAjL%`}w5`cWx)c2*u@%2W;R4*Q{N(av}5VD-L;5G~RA z;^6@{D?Rd(Ie3!J=in70U|T$X zavaI4mya`Ce$nJXt(<+Ri2!jJZsgzrEh&h{kN)z5j-NMY06q-vu+{@P&8`9o&!2Pm z))vZ3SD527cF*rixX+;H*StCh8=q!25;>_=!^yO_6VuK#4CNU1e1p4XKb^JE9nHyj ze%iCoEAY4zqVg;1+^6}?OI}C`o4MKs8;X?kx87gL7u7Egzyr93rMS+Zx5j3J4Z<=f zKA(?>o7bkA%I2NV;>)8@n?242dF#?w)XBJ3V5Xvd;hV2IWtq!d$|cH(jU}jqPf5+> z<=GX+%op|OJKkR$VC&73S*BVerIlMkmGa_d4Zfg;SckUWMd?fap0{Vp;x=uAWYyp8 z)b@o(o+;dZwQpI3(`?PThQ4^8pQpB`{@Z@qHl1FNVS7iVAnM#vyL!;yQ+Pm6+Vj@!)~}S~lRsT3?kfmv=px6N{DeKgJ5BT-s`E|GiW1c z@~XAx>b?80a*ZcBZr{tmlEGnIw`DmDG*SwZ?EYLNST{dbywlaTdIuf9y@=!z2)UQ; zcwMm@^U##;N1SCeY6~WB0tX)cEizJ@efr&b^Z=U&??R@(^KX6n-+n578dk-%@|+Ui zIdM#~zAk%d@wI`v?|rsoMaeyPKEtk14}t5p6U?0JjrQmp<)I(?aN>yiYC{eDEe)?6V#OHRdS?neDwkuTU<-E&!HrI2#gU%ig@N zLfKyrks^LT7wwrB>ZJRNxAl>dn?LLa@qZly9pgt$J>|do9?u93&ek0x?_F@FvKZKt z`-@aQvDg|W9|fR5eE@%Qi9C5+62$30Zfy2HUu2?QqSW-_{q@Lg@!##yx|hS`n>x%woqNVyMjY1NQf)!uO?;kzaSWw`aM}{Nr5^(2wZ-}Y>B&^I(%9m zb$IN35Ln`3PvtUuQAfilll&m3K{I*vN&2vh3F{0xOY!@@+>_z>Z>-Q6wHCO}=V@5k z06U`b|*Lcz4HtvA+EI0igALs^EY`30S{H zx8SPlZ3_NgBm^t`bFMIRUk!Y}*MJRpjF9hsLvlmd~(wN7W4O(~1^BS=_@ z<_@8YQ#jF|!S~iL!+2TzQ-WLO8daZeDYwU26 z>Z^Xwb?+Mgt)aee7J%V%zU7$pj!)0q#rnZzns6Rw z?LrZc(5iIuN#;1|sWSG{>`LWg=R}R~T}A=U()Do`zh+dB=G>0=WUDp+Pq-6EeQfa> zbB0*M(XQLpOsy7)!JwmBV2*1rTW zTFCbW5cn2+_U!lxuLG%70ng{+D|rvpK7qO#@Q)pWo*ym`^T77dDGq~ale44`K^fm& z|NbY!i#KtB>E&r8UyLhE(0ub&ZEB5UH@)7eMYmsSjYapFc1P|*jm4SQt9gX_~rvB61?eM2|8N4(ze|s z_MWqebKkRMq-jI+Lh)R`BpQS-&V5O5!4tai?HRYd#f!ju_>M(%EKVlgBiig!8=VvE|lE-Iy zDMN&SLBnnfC*!Y+0u@z^V(}Fd>Q9YIsZEb}I-pjk>WMy_xviRg9@`1-4rw@RU0sJ| zWMJ%HLj$vpeWl5zHRQjej9a)=y1+Ldf@;R~vO{7Ls-rI1;#=0azfL4<2OG>%- zTuXm*(eGa@eX)Da_bbjv`AnbS_Xh|xQQpUw>W1lzv;EH}E|o!7;D(=nQLyV!7pG^q zdf(`Kc=U8UHBbM*nXG`bV6oN*Xc3@a&$;E-!6CBeqQ(pCT!W2c&Y{%t?VMW+1vfyB z#kRG)R$uD`*uYn-TPuIxLh2hpqeIE#&fmr}au96+U!l^&H?kA00dJ$S#S5?&1%Ufd zooinp!b_HApqYS0JN5c`2cwdc$kQ+h_q3rZr7`OVdpi+d|0wK4ur*`XFDmZhnH%)>(e z86X4yUA^}k2K)>iY2|4Mj%phnSV6EfM7#_)RX|d-F+{jbS@GKU+;TtqeXW?{{(5#W zojol)j#QXY4B;U$El#k>;9vydgkW(9G#PG+zT9X{2w@o<3b4dBR7Z>}Y7#2~ws3J2 z(mObD47&2mc1Pg=aNhxH@OTP-6$$8XCgQhhTA3d7&g^-US-Urn>o;!X15&C4Z{c+shP+k@RJ#%P*}#FeqcG7e@l z7Swkx)EQ6_%A1rWt-ci^3m108D}zaXMG${L5&c>G9ivZvfEE0ZB3V_xakuM1&ooV5 zJp4s3j};D5;7mW)CyhE!H&UBeG%q-S4lH>_sqzt5v)bM5CMQJd_f3vOlRYk}`b-Jc zx!Q*Y%c@sN)HK#C|7t^J?#V8jK1VHrGcw$pu@BZ325D%(M;a%xx{fwj<(A>E2F>nX z9cPX1-l(f9QML3droP|-3`};qw5Z3+iYMjt8}(^@yp4*Zp63lsfhWohToNnV zY}|sf$W<0mh)>J_o`C~3z+b^%0pqXoeXiU;(R{HVuNUULxHZ6W1PLk~!K4iXAUr(m zz)%1SEC&a>&4ggT_dm;7JY8z=lETgq^BhG7a_|6=X`FexnMzIvlm)fYbAe41&|Q*` zxI5C?ZN%s6aW=9+h<9`xO%~hN)=T`lVeW+!t>AARGKB@*Z}H+#ODmrXJ4q*xu<&XW z@ZCsn+1TKck6g3s6cIht52Cectj?CYTzf9t)pfN6{Y18pup(*S?JH*mtsaR)8dF+3 zz!5hyyMNuItqL{4i+gcX48%vXl8ud}lZi%Bpc|R`pzlaY5~F7q863+r5Yx`e2@+sX zJcueKvnb!s6nAUfOReHd^rm$i+#9U!g&4&8n%)->zWE=2B#8SbbW7YLtul#UCHjia zEDAk9Fl)4e;#i)$81Cs-O2JT#HL(@b!lbb|?ZmvXEuGd{E`oJe<&y8o|4NDV#!met zjubGscNY*s2L<^WHgVb787;uC8h9{DpN0ssdXF84)Gdl5sub4DSJZRJcw4W+iuWdX z{plvv?{Hnyo_G|h_Fo6x^YPb03P84OSxw3n5bvyjN*);DyN4QMtr+q3CFQg+yy|_W zrZWZnajHM^XDnfbY%)_Dt4-I&T1>2lWeakwbmJcj?WXedE%Dyl0lS1K25M zHS`kW%}Qnw9y(<{vF~TcM*qz6vg`aosi~&8C+j6AQB*)*<775LnM{XdJDiDzzgES3 zEC=VENiKCP?ZW%ph${t)vy#jXZJabUoA1Ic8WIq{+pUBm_PiI6iKYXDMD($gKn8;q z9ee!Bo`~)*-64|3LdFlyAb71qM%(~=XQB$c@%smT7Z627M6F@2Z%BRK?PjA2mXxP! zTM}W4NUcP45+PKidE*!4#F!$n*c^}BNR52*-$RA{#Atd+w^QIqeaLvdPEMhBG!6te zu)AFz=4RzInFzm3ARn<jEOgzj4$~Mla?2A81I(!-wF*^~-ees%Yc1 zZC{1Ss9Tk5Dxzbn=-t8ryjTF_U_-H4>HXHwTiu|14JjRy!nA4wG;AaZ=pFpZuizB0 zgzGi0sUX}J96Nw9pyeAtbieeWX@RhG)z5>pNXgJL59mlTws0g!=(mH&5C zJz|;p*tCo8e=c3ujxzsyjJ`0h7+jVxJ414c(>l1!tWxym!iq#sU4m+>K>PyYh`seW zYj_S`XFzl)`C3GM7G7sWePx?22_bhfGC@EpNZg#oP1WRk9b;h&L`J|(LntieLj+4Y zUeNz3H2dZxgCqp*QY%d@i8E0nM#e}bBP6V-Vjjg{?;S14%rL4Z@%dI#CE)My2Q)AZ z-Zu2`-IhpHBY9||trATCRaRy@vR?2#Mr259Fu@@mgH)HyNItkKLKxfh>on4Qcvg|( z=>=mA*kRC%kyeTFm0qKY3cttn58(q*+(R^t;2R*WD@62>>>17cM;m0I9s+6loJ@M_b;A&aWw`QW#a?o~Ij zEz%?q^E41jQyf={3PPX=OPYd%*@{~JQTFTW3~5qjlh86rlf951yh#h2sS-ttmw3>r z@`K-1R$uh~B%ju%%7WQxk&srkp)pv35xX&mB#A_bF?&2}dxy)2X~DPBig5?+-@Kg5 zX6Cg-!5|m@ny{Q<$2%fq5?e^6Aw`R=+#tj&oWl51iyC8wS2RN2_Zc>lS}m{pw7g>I z>>u;N@xRT#-x)1y)RT`^oxXF9fR+tvg(s_Z-#HIJ%NF(IlhvE=TmWEib&IuUlQ`zt zir#N78ql(raWHq-xNkNtq-!wp-gsy>CFE@Y^U-*0Hn&~^U_ULbewAtTt2rW!_wM^( zd~DqyZuBWhU`g7WuOICk1fAoi6V%T4{E<28YjQW56JWj|PIp0*_lBd=hng1*l%ejA zKI)B!9Q<(NSM)}p(ubb!36!zuZ)!D~(`UY5N_WA*X`iLihnW|I4Asa|N>4c$W~jMp z+@S5f3Tk*IC&^<3P7lAe8!X{mrKNLU)~#P#OyFzr1>M$l(e&;G{nippJ|L9xApDBv z_e-V~!?s={{~b`^N64GpW7=h$w#pRV>9t5f{Xfv$2j!dGlluyAqJj7Ps;zerlnv-Y zdE>jhsuMD!O%OH76f1OtIr!zNtl^)fZP+QqLlbr&w)%nkD75iu^f~xtxHf=B{(~RL z{~1&N`45FhuFWqqMYo>xq431D3Uu+0`rx5w^!OZn68cX7+S;3*ivs`u_5Uf<+iYJJ z@a&y`;`>kVD+KW-Ub|~xPrsJuwe7tlpv*U$|7{2eWkhY5@_r{^;>>Q>g0unhq(BtaNmbw;hN$E9&aM+ zw&fp40KiPz%SYZzeUxy&=-G~k08#DH{FUoR*xrN3|6|MlSx7V;W%|2|Fs^m*#*KdqWpo|)%H#3s#*KSx z%168Me0uM~(T6aO2?yPhfo;MygA_u$U~!C(IuTC7O{UT{>w<>fIGg;=S%Z4FX6kq5 zKe7sVP1c6T!@U<#h7Zp1%(X0S>r6%Z&1h}2sa+X!i35yC0FAo^BS2P)aiO7}S;9pk z+S)5b&?o{-v_m!zrer z^RfNVAR_&@_b88NJ+JfLYa#u}#;Qnhxa&JEFh9;a$K>I_Y}gy_Wbcd>xx+P@c^FR! z0tc`GN^}fuNYQ9TWVC21B*{UN8pc^oCHNo*d04s{qKM~Z-sj~p--kcHPBGI~(&Fb- zL^0xY6!`QtW}9iL14vbmV^KV$+;h3-hyEf4Qiy8b;44>de%O#OyHv)G-L^xc~a=Utfj7|5;t_Dkywq++sW z#rpSq$XfnytQ@VS8AW604F<-(7(YJpQ4b1(%vG+<4wSITWDJ9s z$7HjO_r<`5R?N{-ob%blwZp&bRv#R^*)=ZC`XB3--?=AexlI7Lue?BENo$4KNoVIh zy=o*gBhMKc6L01qZ|*%PXDjz2&{la+bnIa?iV80P}JhFXqY zp%_r`=_Z!8CFu#AD$k!Xy#ZAWg}+WnDV?~(@IOnbG&*3+@s64}JS?*KJuZ~yvYp1d zX+R>+2bcFALql4P;+QsMUFO|4c2^&ey=3J5gbCXrqluel%&0 zDjnb6Zg^7m&#ervhPO)+BseN1J(766e=3NQ0 zrl*p;rgz(6?02VON0SljSjtDi^8Oy0Q*z&IyC|nL=Wfc4MNS;Q?@r$%maWf^lM)Cf z6CJsIw7z>Z2v@#rFg67*BemP>kb`>DU&C!Em0fEyc@lp9FoP+U`LP+PjE)o=8R^=! zt)EaEi@wT1<{gm5JoFM~T2D0M9cZP(iK9@``7LTEpvVI8?obETm_=`&4~vaV3;LV( zF;1ucY*4I+h-TNe^|TS)do&$om711{Bh&c0g6qPXC9GMX>okgBk>5~*XCFd&c5$rx zaC-!yt&V=XEOdFOX%}7W0#2~uyM60*B{4U0hz_)m8o%rwVt{&x-@A1aN_ETL;8iAi zWpEXOAwu*t+K5G;jX*NFKs+jQCN7xJfL4eJv{FmubgW=!UG0p%B!KsKqRC8j&^9`j zH5PXSY4(ut_$%7_x^R~doD?(K0kPy7$~7xW&M$e{*BbL-7LRFZF}7$>Fi1;x$Ak~8 zLG|CCfHAiniq5XiYn;P3LKss)y&jsX<7E+`guy98>3GQus{M%+!?!qn*vZ))A zr+IoYdlNS5&=CEBDh4iCD@C7S2q{W5#N;dF)yfPXCz@irky`S zv-wzE7^WAe{-*jaf8D7zOD1`5KcUkl_l^5=sQ!<#%SRDsMU{b=?BykvCqx*xX%Y=8kl^m1Pw9Kq4i#>6TQIXh%jIS0 zgy}@XmeN4$Od+`7X{T@``JZHT&}F=^@H=>&bVuV6I)KX^yc{uj5Fxm3fFC`*U-YJw z#44DFJntzOha3aV>Mn_$Hqmaqv9D>Yg|K{G+Po{~Wz%xg_GrYuWF(Xv(Srj)_61ja zmA3R{UGTQmZbGK+fc-J?L!l5lZcehanzY0})6k9q{B>>#UBpLd2}9l4zc+c#3Mvl+ z*-&t+faCCU{l))vf&sC(2e*A>?3ksOASpho*3P@-%`(RGad5&Lwz;a*%`|6K2h%@G zz!N9Ck;!`ryR`9v|1SIWvDL1drEuFb@&tE!G@EX^;AIX>q-KPbKjct6cV8}js+Gx2 zUB|tFGsDoZ$&t5*MNDL6X%~L*X4B_4b4tl-Fm>9i^#bvnRcXc_>Z>ce(5l`)Z=Ra= zcYhP!7d_ET=2W>Rfg|#N^0NmAwiQHgw!8vB$;diGzuyhq*0gB-+RglU%uUjPu?Z>l z-903;I5B2KpMG&6Ht7~_V7!_!)xbC(S3jcuPbS4V3gBQ9iqeo<*wU@VSq6~FzxTx( zXxP_q7)Km(-<)D+(Zi>+{+Rrm6z}AY^0Qa7r2j_nLM8{y7^^zx79l=TzsZdjH);mM z*yYZPOJ#k?0k)^jUXWK_i`;*f&=K%*Hl|s+>JwXXHUI9a2iNg(wM~>c z-daKE;r0!e$(S!&F(HMlMecT$<8FQD^#Zp?j_Kthb2+vCuhdfppe8BqXC-nx@op>;J5HTK<+RIpuy zb<@gT|F+yt;?*V$m<6z}_rJT@bQt?AxoJ-Fv5#c`Hj5vtNl_HO*|*I3Z$|D)1#Yz- zeuYto>#lX@RWxCb>^C_ut4rD4B5|7W-<8`K@G)aL1~_en`)=!9DO5GeH}GzgP8@lt zz`C`(>%4yGo>T=CvjdK7f2+s$s~wxC0<*!_Y?f)!jp-^^0?ww_Rwva?`q(H1MQ76O za++Q>jedr{C2aaw=J+`6W&#;Xf8)mhp=`S>*M>@ApnINI?gd`P=dR>_-cDx3v6i0} zzu-Ch?;B!%n%sI?WKJV#*AJgs;C?=hZ7~$E-xgZ&6O(w)NRNE-i1uJP;XO|z6T3E3 z(iB*@Pi*V*ufLmLyV|o?*z8PL``0l1P`Ue^tRcEacRiUr z>KnzMnxxxhyUl_>(H5tU4~w>z%q;CI<2x z;wDJVfYR_ESFycOjhH;wzelNM2 z1=cS&hxd)aQh};PdTJ^;(ow6nmQDOl$sQpL(_Z?@;tKMSJ|Nlke|At5J~caI)m73) z11yzcl)sm^vd)?6HR%drMtoOJ6J!ZbXd$-jK#~h_gUUXG97x>w#eF`9a|ZkP@W)VS z-kGPrIw9B;ju&aXkQRdif4ra&x3f2xvt+sYG2ASVF+b)hf;M!CoDjkHmc~U;`Pe5; z^awL#iX5N~kO}j%3>Tl8C&K;t9rvey5wHHo{qpoo`v~8%()8XeF7hmFj1)1uco*AN zd&v}s;k4?tTDDYN&j7-CfqZNtm z&7z&89~AMG5Rb-BU_$*sV$BR@ggb+^oA46<@i(AgAXH*oWfaR75iq51bj|tAS57q# zy@TLbw`H*AGpddr+v!i3@|jdO7!^Z2GZJ~s$&fm*dJa}8Rs<^pKJ=fO2e(LivR4qs z!Jf*=H%2$%Rf)&eABlw37wK`Dk+Sz1Xvp>>`U zaXmluu@bF9Kb!crS{YLFm|7$fOLMs+et{iA3+Ymc59TkZ^Ohf_U{Aq==V(nnp=|blv6`t@jk7CSG@qw zI)82fXbtEt31Rg8NZ^p_$_Pi^anXbWccf3it?}}Q?t%yv+pbr?s=Y^``8G%M#W4!lgH41@LycIm zVn}C*A^Zcnl@>y@K>H;IUj)M$Oel9iT{iN=8xLC%*K1v7RXy34*X=bp(rgMsVA@mmY7)hWTV4xvj zR4W(#;^FS_@^AAuLy>+N%RL5ludXYR{mD%(qtJ?Q()c8YVI-UED2`upv48dibo1xLfY`j z`=S_Z>DJ9YB))Z|CemyJsmei#U9^Ib_#-YiWiRp3s z_hjbZdD`Jr+`(x_@CofrA82HboqLtY$>UHMSf`HbNDIS%ybh)3Q%Ir_egO;RZsyFB zCQ6=j`i?fvrz-paH*~KD^8dhwG{Wp_=#$FS#Zc|*vSa(4* z-nco<-(>L@@^!vGrQ76e)G!L$P}X*Z(n(U4e!~XHVB1t^1(w*=M+_|I>+c%rhex@# zb_{k!zsia-QAJRyy`v9Bb}fr{Q(QJP%FC@Rq1q9De>C1$`DKDpok^6kUq&TL$x=Zv zSH!y^FRJz+x<17zC|SvVQ_^8t0#4c77_<}&SO*n8Sgc=#n=S+w6k&3})6fc2Cgiaz z%*u@al*v0=unh6qDgtoP94p{?1x0QZ6>`y>DCl%FL>{c!1vs$xbKz-WW({~CD|eNU z7zClVMex@D;j53}um2-ZPpdbBHfckq)Wx`SzAh8~kH={oj!lx_(R@KS6KNq~8tdaa zosnl@8|cyw=Oy<|53VoDnjYQ^<3LM_b`I?yN%)p#`RPj8NuBx}cu1vKNToxRf!Y@M zaTJ;~EtpD0^_aR~e5Fq!Z_y7c(ac08NZ}u#fG{A6?I8x-RKKO_O1liOM06IMTL&%{ zJD@Pch!}i9>c8B7iWZn!^?xbtaom}88jD|GYIttNuykxgN=#*Mq&PCnXCl}0VDIc| z5I?jM25Hg%cmxbs{IPU_Tlog&wy1&Ol|Y{Sg6Vb!aj&i`b1Xqlv7LbY?OHhDT8W21 z!vsO(2BQ8|sSMO|ED@`Vg@)QL6J%h@mjKD}V_y}iC}H8N0%e{wqvo)$<}NT0CIgTJ zp&k*7bc2l``e{Oh?ZB0EX@>A=42fuNCjm7!4bt65e+e!{}_VH91giuIZHZDP&vyO9WR79 zEZA)Gw{`@tSseN3{skOy7j`TkB;isq5)lm;#FT_uh+ia{%1k&xKjsE91!P;0_H{y4 z7{7*%9R^n+z8RgFOY~8aM2Bsd?t?g)A#o%U={5tEp0waWN{O?0$NVB6 z8hC2i@u)<{!lHK?cqv&r77FDRQfOrvZV?r*6euNrXe>pX%M%Pp6=48q0R~e3_H+?t zvgdG6CXR!jp zWe*tfTq^meu%1;^o(_}rGf<|qzvADWEyU?E%H-7aM}oEQD@Z<2u{4S>r~>yZm%qG0 ze6}qgq)x4Hw+awo2wM4=E%VgK4_0!%b83K= zMe3M`mnzqcvuT60evnE1gsHnf>l$G3N8_g3ThWf7UAtoD<$McYS@G=uN`lv?-?J;s z>_2A{0h0Ed*Iq52>nA^a5Ps9O33Y(!VH7Lyf|qoICD{j93H23`8?9k7pCF_YO(*Sn zdhsiEK$d)fC!zeYiUX@;WdE*fS(P_$E?673L39~+HP~bk@Su}9(af%4V>Qtv7PaT; zJ55IVtZB4>B1DWgk#yVCMcWI+Bw(1kL;-HQ+-hCa3C)u*%#kqs|1@9#H^xeN#jryt zFylbGitvkPFs**Al|+tDX!+25F$|%={4^^JY%7dZD-1A6ag&S>9%QKPij&~BztP<6 zsnljkwMmh_Lac6c^;h=H93~KSohn}k`nUO)Eb+QFle@!yg_>)FxPKi6@kybs)e+`v z`d!JJNxl6aaGS9I8);Nev(rHZy@6n_{GY$1x4$>%g?4ke*83V1cIE#+g3qv#F*x@> zOoIquU2lU75N6gD%R`;{pD+FQxxqgf)dv zAu6G0SbE-=LZ74ok&2CSmU}Vl!&6XpN~)bW2Ej^6emxCgc;m@0h&p<7^UO~G7~Q*b z^db6xS9s<4P8e4^c;m|7GZl|GF^Wof1>)(69bMje<|Q%=+dJ{#CyXsM(a9Cl$u*%1 z5X;OoVdmaFZ&h}(URqjPAO6F&!946ncFlaybd*;dS%}vK?LPKGtL_o(!^=Z-1Fs&L z^l~gywx7dBg#LkRDKRDlsln&v*@^wW67g?d-CHD?=m+4dRUZN12q-oZZ|7witYlo!X8kD)n<3!8R3R4_z{6Vch-ELaKstrr6 zHm{OkHcp|sM?NzW603D0{hWy!u}LS~pIIdaO#@^5LaGbe`mLs{5$} zZb`DVD_#e#>Q0l(D(oqQCGsnF1x;c`ICSRt=Q$PoXmJ7VL&~PsV<7J zRH)HAWavLM6AX)oN@tFE5HMofjGWOk3fnhm%LkFD zSHz*`fDf2Quy*TwH7NTad(T^i-Vbv9A`a%7DVg0JUu)7%&H`^}$cXO`IqT zU{fVUPL|5KIBD1xUYuZxdUj_QJKl*%iKFW{wd>60oIQfDAx|?(&xXrDDv7=j+fgG= zh*#B{Y$8Y=j7XPWu#u$apkj5S7Db_=LLnAPr{N(0Coi$0Nia)%$-|p}X7KCHrh)I8 ztGnQlKH;&oK~XVAQ8h+Ukw#IK{vYM{VoyzKz$EAD{jOupn&66%rOcQ0TZ_s-u45fa z4>AVRiTAD*z)!3A^{T&Nh2Ew|RK2NIDKMYD3MrS&I z_+-nT6koZX8&IYn0`^9BZ@A)b z8ou(hu6)?qlBndb-ehfr)AT)YW2)pgm2+c6)|(_*-;(cY|9CYot9GY4E@YlH=32fO z{TUs@JPTEa5+Wg@(=xAoT46-?$qXAZt9HCXp!}4+>SdzSa&Tuh();E7qzRLvR6Ww0 zW<=0Fskd)gF}e|cu{wTVu9U)OrC_8}vVpe-J+m=BUI04tXL96iyAorwE_U+@~NT!m( zj?70Y1V3#eJ--I~HqAZtBVgh>DdxWa*JDOB!oMW1IZQ>b3dd3L{C--mfrul)hYjj? z6xHDms+s)8)v%vb=H758MlJqvRhy4gNtiFQE{VUk4t|0+e0Rm%NR~7B3tb9KjC1I( zZMJu zbr_n=vpkd>7}BWvJ(#@`$!;G#CZKue@ek*|Z8{U?bE4*+5zLJ+H3iof4m> ziVC{)1ujNS5w z^jnyMZ#L9)_PxDA2JXR9Q2vN+rFn~|&QbArXLbcL^xR|FZ3`u75C)jsy(kmH~Hsae{sJly!UMPr*9SWb|MKSgs@X+{~OlMuJ6x9+! zFtBnU<*~nNuiS$0-|zSqkyg4t{x)c<46&Co;+%12SP@qco#+jh^Tso^NRHpz6%;EN zf;fv`%&SbZ0tJt4P}k=T$+ix}Z=&59t^5C5L}0lC{bh<|Kv`pS%VWz@ z+$RYB*F=~$5+_9sdcx>pR?rX~mF$ajHd#R$g;egH5?8+BqWnbI?F~bVbanvZnFa;M1?*3U@1qj59iZ0BOO+~@5LbTqjwJd)CHeihS?)v8oviEKFYFx> zC-)F+%}9j(pryMVfl6|;0vc6Fb88k6(8hqUstqC45i9G3ie~|H42t8R+^Dtdhs3sF z%7Q?xB1iZ#Blt4Mh|=FpP#^RYAndr}rkmacl-Wxdw_wM_kA`4b?i#GvHtMEk``$&i8-}aDxW-1Q zb;MHBcxP6&)NWc~v`nl~TM47%FEP{cF7~mr(*b7PEwB|!wH5oQ#fu{({ub%NR${XO z^_c$6ZH?Z#?{>SVcV)g-56*|ifG=A{^C#|M$ST`nTKdn_?%%_CHUDag^;FE27YO-F zfS(`!@vsF9HhTZ`^>YX$#SU;MRHi{JsKH%WlTaZ z!o4do#1hyIpxY9(yY9mj{m%Lse{M+@3KaR#X+p=%+Ml6S<;=5m`e%5)$ASTxuAL|c zvrvw!`Uiw6W^kg?oXaHAJ2q zGb0~7m_&FQ`-w_%G@S-q;ndBTSYX%QEd~K95!LQ2PG;mD`x!2obW*N}XxX%KSDfGb zbV#mHiJH%&H(LaY9* z22W&Zru;UE{L0ilO}wzF88E>vlq#HfkPCT~KXBmFIgU+yjj8j^g>M#%o-U89Em|TM zzDW+E^cYIQlpGbIQ@EMRxOLH48TZu**Xr{0P2SRJDzv?V)TJs~SAzhu(0g|WRF}&Z z$%ckiq(>ebM6*dl-LXlXDPP8BrTgsy_M3Ivw+pbBe`(RpWOcXQ)w<%J`%NQj)Ct@wVI@s&FTIz(^RALFZL(u>8ehK z>q}FdcLFe!ja{6WqQW#aCL`X`xQYXFkP7n{qrsVW*dlABj*4Y?{J_;d-mos-ux^}SD1W}#1U0+-JMn8!J(;V7U!`)_6S0DPWWorS zRU?@`gyq6m3o%+W?q7NmXlfWWgqG$E7$#gO-(q3uSYoQ!K)Gz8T*%;+PiGBgKA0xG zB{O@{nLVmsmo7NWaA8Yx05JW#JO8^6(Fd46U@G-re%S@!PBo$}ZsPR!yA9|h8fyiR zmHl0AkU?+AyEBaRG9tk7Q-epN0z*PkiG%s~2>*pxgYw1!w_ifrC-7}4d|M);J%?vw z6c874RALrSaiJx!Oev54`ADzw_()I)`1{OV_@Qc3HI@fuvmIjnv{$5df^t}9f5%*i z$XNNzx%QDV<1=T*wbsyIH_u5Y(TQ0mKGIBl=9&0NH1U~f;v?0>D**V>0C-4HRonZN zaZx*+a<@DwPs^L~w#@#9``$@s4_-$!mKSB+57yiI zTyU!;3x%gkGA0X!r{9}oq40E}@Z_5Lm>>I*q4i`Da9IEOs1F&nJ7^XT+joQJ-7sQz za9SEqlWVGN)*jO&W&fUL-FT=p8g=4PySD{v=v0P8v2T+O{pUTU9F|pyBYiyS%!RV6 z9eP=FTBeuvd0~_NrTaUo?BV`c4DNF=IMP=y(^oH4JwjudbnWeZpOn+ZL7+339(ut` zg+?;xp%I>ONvA#@-8({C)p(xy3-w17_*SkeUZT`H#AQA8#w3uNX_k}53X#nPBCi_S zNO#jLlME*x8xD`pH!pL%IjTYs2T&ZkzW{c6o~wdCz0-;i}<$P^AY{{*bPg+Bo9WoS%Wfr7ci~0ByuF z5Xq}nG-+hNs;DVrm^FZD8L_ zdaVttwZ`jothHSdP(MBrH_myBWJwjrStF0BSxm`cx^%~ZHE+V2H>3+T`4$?dKH)7J z@E$FU0Rl^mMn(SykT$}Q;1%k^S#?AqySy_U3ARkA`I+ccsTDJ! zE5Z>EOkE*X#{{=jYTYM`f2x;+_bKJcPB^kte(c*~Zd>YQW3~v*76IKh)MwyG&s>Wu zeMFYa-MPrZoo^K9OInR1#_m z@iYZm6fT6q`0TZ%T*&34=I}jROPWHn=5T0nSY}-@@%5Es3D+ahi^{A=XA{x9O!-Z*(_=LuVY509U?+MPQT;M9S zGFW(J*i+W&DN}r#@!c(dc)3X$y(O>k2q8F@u}-4Vdx>`w=SOZTeDVCqEF7VDCF_S*6PAgupUzfU^fNEVX93u0 zdVeeMIv9U-p$IMu@+Abh55imr6&O8?kG?_;lkv~(_$FaR`xRDLj90$+<7WI(@kZj~ zyLr^^d4VU7hXIOQBQgYyuqlYcI$ z_HiZtyz&8diN-R$tjdsFOp#olS89T1*JmFAd~#?k^Y>n<8?)$}OR6&UR#WcYlYv7+qB{u5AZIzl&6t552ZxpbsF)T?{s04{4SH!7}pm0&V_U zN^~cYTI0+bB-SWf1_rirWolozF0AUt=7L&OnA%$1wAR&1sdXMGAzEla+=v$e3x@pA z35RvcW1Wa-r0!4x^Pb9MP2k#%AxvOXQdfL6vGV0Stj1&^RkPj`Pjnk|2?e=43rffSUA!8-a@1ojh|%j$?7@9VW{|DP$7T#8`oX{ zMTv7S?7IFB{*9h79x$I7%9*%w3DTYD_o%@wj0mc~dv@kz-0Yh{*432lyI~h^dCCGd zb0`7}ds^|11b8b|Rf2)JoQ{vzbeOV(6TYBSy_^JrQVL_6pg+3XAt@nog@^fn_>RK3n`B+0Q<+i+%LP+E+bUx^TL5-~zQf8q2gGy9-Zqo#?L=C@o5tHeG`j z>ac}6SPFE!(w(&UmMv0Z7nVIH#lbPe#bE{yISsy7C+S<_DiNbW?J?x=t;ZIkIzUfD zx_gXee%m#WzMUZkkB1}C+2fr(Sb@pd7;t$saeBBek?JqscKIz9;sVZ0Tq<6Gd**sfv2A~wn}J1j+Wl(~iMEb(rjezj5h15TqM1jDsW5PMwCq{4Udg@UVf z$hsa$s=5MyxfupfdF^h^#YU>Or8*!e^3ha% zVzLaqO6DSKMt0oZguy?nnnv*}7D>o8XTga~+NsPDcooG&m`JWpWwa&yq^ZpHL<~EH zN=bOqQx@87!2_D&0Zrtem@yRV-AtViE`8NJNxW*QwI)%1BvpSTQGn!bmn86ciuDwP zc&VI-#G93>!IfaaN#_FzWV%!)Z*MR#wzFhA>eSONCZceoQJqrD8yWw@0pTF9;!U`8dz<2}k&><%_T7vI&R3K` zH3JNNIb@qUh8RN&Xm!$s_lB}tbe{8 zfJK%s8ZU~ff1O?wI~T=nnQEg0yeJ1$jf^qo|1H;tUWbe7v4KIFk0naB+9ANu0dKku zs}aB!v7i09zBApBrsA)V4dXcHx#5CtIJv<#+>FEJ09a2xoc2`yEY{29i+o7gc1+m@ zchx7imE8eBmecdSG5bvwIEh#ABu$R+fa~OvJ+J7?VT`!i2Y^pO*}orfyIzlXOjn7N zvD<^YFMVRzJjn~%F^r>s!V0!G?yoK>=iB!H@Jo<*7xNJRY=2?Z+6#S>4^X;PNOL`d zZ_41C54tr4=JF>;Wjx+{9v^=d4<;6ZvYQwPdxM|2{AN&-xoy5*v7BSP;w;grHpQb| z3;nM!@oX`N@$AcHYw#Z4cE4jtRCilnvr2}#dU;I?#M8!ciIDs>L4@D<)D)6-I2He+ zCJiSnWNeir@&8Iq9C=;|lcUlkpXa8ddw4#B?=&*2Jgh%y{HLc`tvn*mJ0{Sm90X2E zkMQmY!hE`cGDpQ$j>)KNkJt0uxo7fOoL40C2>5*Ltm}dh-qwj2T-?yQAPdXD-qsX& zXY1@?r`VyFuUh{qgm(?k%|}n?#Co6T9v;6b=je{r6QlzDN5ZZXaKqaA^Z)VZ2+9sa z;r>XMHR>D+4pCjI6dB@fOJoo%@19C(N|27F46a0J@9Z`RRHde)T;`wtACtTo+3n*1 I0BJJE#`!5EhX4Qo diff --git a/./.git/objects/58/2f34a7e2b6a1384b3825f9fa891c914054fdd8 b/./.git/objects/58/2f34a7e2b6a1384b3825f9fa891c914054fdd8 deleted file mode 100644 index f46589e7d7cf999185328ed5895be23e47d93d93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 259 zcmV+e0sQ`W0kx7bPs1<}g_-*+qRxm)43+SPIGkKpblmspqQ+))yg&tWhQ-(H?Bj7*Th1dhrD{8`F0Cs1szHvAOf{|t< z`4m@W#_W~Ut`6KkKI}Z{&yA`0W^R)UGje J`v;VKUA8SpfkXfR diff --git a/./.git/objects/58/b0a4edb15f1be307674f2c501e4b7ad34ef69f b/./.git/objects/58/b0a4edb15f1be307674f2c501e4b7ad34ef69f deleted file mode 100644 index 91080deb72a0412af658eddfb33dac2e0e1476dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3629 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 diff --git a/./.git/objects/59/3554a4fb4e13b8597dcb44a3dcea2a5180e506 b/./.git/objects/59/3554a4fb4e13b8597dcb44a3dcea2a5180e506 deleted file mode 100644 index 439f5a6485189b902e863e67a246cbd437415dfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18476 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 fGPHf@0>_ti)z$EhSSS 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 FPkg8>se}Lk diff --git a/./.git/objects/64/1e5779b8317678ce9f2e781ed4d568ae4754d9 b/./.git/objects/64/1e5779b8317678ce9f2e781ed4d568ae4754d9 deleted file mode 100644 index 9bbdb10e68f53e2df262b1f8f2b9f5c4cc745096..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1792 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 diff --git a/./.git/objects/64/787105e33d1d19f357b599125197419ee11053 b/./.git/objects/64/787105e33d1d19f357b599125197419ee11053 deleted file mode 100644 index e2eff12a48d5168ef3689ce4bfaed4d290af6406..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5358 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 L1PcQIdC4z)R2x@& diff --git a/./.git/objects/69/4ad99197b8043fa0d8954f5ed37ec29f630217 b/./.git/objects/69/4ad99197b8043fa0d8954f5ed37ec29f630217 deleted file mode 100644 index 367b4da555ca4317127492a964d72358f0e32087..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4416 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 BLnQzJ diff --git a/./.git/objects/6d/e824b46d41385095c3ddb4cab9a8be4feb7843 b/./.git/objects/6d/e824b46d41385095c3ddb4cab9a8be4feb7843 deleted file mode 100644 index a2e6ded71a7cb0822448ded9b1073d03730dcb15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149 zcmV;G0BZku0ZYosPf{>6GiS)><>g`kg7cR`GDU#&V-N=!FakLaKwQiMBvKfH88jLF zRx%WUgb~CqFa6Ns)S_bj^31%HO8tPM{H)aEl4AY*q~gq!%)~tXtRnra{L-Sl#GLs2 zqV&YP%&OEP{gTvTpqkRW%o6?h_)H)bAFo$Xd5gm)H$SB`C)EyQM==wSU||3N3?(dw D_lryL diff --git a/./.git/objects/6e/55f323a0354b9fddc639a857ebb894d0c5a036 b/./.git/objects/6e/55f323a0354b9fddc639a857ebb894d0c5a036 deleted file mode 100644 index 656e153b859eb39866a10de3023611f91a2f74ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5642 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 diff --git a/./.git/objects/72/7fee65b945b727e139a62c996df843908ffb67 b/./.git/objects/72/7fee65b945b727e139a62c996df843908ffb67 deleted file mode 100644 index 1ad10120dc06284f06ede1937fd4dcc6e6b9d800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 860 zcmV-i1Ec(S0cBJ{ZW~7s%z0nYfG#m35tfDcpc_pgh+zXVAmstg-RACuvoplZELZe3 zK+e7xMjqg+kwZ=f{2kfPNcHTlMA|2b^@(e7ut+X~*eQlBM`8~oV@zp~P5T`K2KP61yXGDnAzGdiXQbTO76 zIdcMQ>&uKEsW+)Hj`o<9HqQ4}s#<8pQRXV!2`zmP`b{`>L1Iyy(QNkeK%|9E7EPSZ zE=j+y0~$nHTJ9>Li}|l~)_i-dz2rx-dcv_lX{R`B8FId ztF&|57l>1kQf)#=UK_W;BtLfLs)}UsE5JN_F}2b6-$elP5ke=bUNx~pw1nfHG#kPz zV@=xU)=TdY?U*eDmg*kXc%$8S&*o3n@4N##tvZKCmR;R3yv39c#Z>Zk07eY6IjHPR zGCNTW(8=?SP1_cxoMBR-tAHm?``xIA94cDAemT@AlZ=(gD~yIh)2yCOi!qw_yQv%G z6%(yZ4WbBk*3jC#3tJA{3W!8PbI{iZa3%PzZgiBPDxOH!`8PyB?<7H8t&r!TPpFdTWS2+NnF<{A^ zlwGExTXif*@hlg@94-9LK+J{2=b|6%I$D&Xc;SIO@uAo-wi7E(5i%jl69LP63AF0c zkcS*XKC(VXLLcb`Q68H;U-~Oz}9_#>4ZA(%g^~zGqOzPL1b)hOFj*7>D!R;GX6Uhdw5Z mB=X_|=2p1HUV5Lqss;zg=J5~?p=+}}V7j)xkN*Q5JXLQ%DzRDs diff --git a/./.git/objects/74/3bbd51d21495f13529d971d7ef9fa8b367ffdd b/./.git/objects/74/3bbd51d21495f13529d971d7ef9fa8b367ffdd deleted file mode 100644 index 17896491960445eccf66f3423a7442db272518d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3833 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 diff --git a/./.git/objects/74/ee156443dc4a27675758bffc43436df8f03620 b/./.git/objects/74/ee156443dc4a27675758bffc43436df8f03620 deleted file mode 100644 index 34c5d6353198e3dd7c90acf41938be8befd56f48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmVz^nUc+l_FCxKi--^4 zRXlhSe4VwAVK$o-D&~^RpMU=OrZ!Z=>3DL&IcL+l(F-jRy4SD$_Pcv|+rNKw&!0a} zJ2+n|^sIL=A?}rHG@%f127FYG%MlN%Z2gMdAYM@GP#T!Jr9{0^4rWT)(0uF2LMi(+ zF(;=$+ch~q6wYSP^>l;8Uy(z8shc4ghh10$ht> zZX6<-N1XTTNMj=PHG^-5>PH9)=?4Rj+6m0ODQ0MX7yjX{P~wL=r`;ajTw#Z D17vsX diff --git a/./.git/objects/75/51238f8b5907f092f4c5a0466046b7c4bb3b29 b/./.git/objects/75/51238f8b5907f092f4c5a0466046b7c4bb3b29 deleted file mode 100644 index 67ff648ebdc5590e38fae5bd9fd758f28b586f85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmV-S0JHyi0ZYosPf{>5V36fd(cfCXA16L@?Av>93Fj>Hz?}HWcTiVkB4q diff --git a/./.git/objects/75/5936e16e892c359d31c332f5fadf0eb00d5e8d b/./.git/objects/75/5936e16e892c359d31c332f5fadf0eb00d5e8d deleted file mode 100644 index a88c41f9df6f5918c33f517b1c8b292e7027236d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12723 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 diff --git a/./.git/objects/75/7fdb88cf3a418a75b31704f9951435090da85d b/./.git/objects/75/7fdb88cf3a418a75b31704f9951435090da85d deleted file mode 100644 index 07db11d87c859f7cd01b235d4967f385ac3278a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2815 zcmV05LSTlynr++^E=^rFXN;tJ&TD4VYXOWy4p}I|)Pi{I(yN&H?>)E_>Yu#D=Z^FG_ zbLXcT^=hO34G?mU1TrVoQrR$VqL?FP#k6Sf+ZxyNs^S0CjFAylAMt-$<5r?*8YNV@=@@FDvEzDD*jXwnRy=4$^QHI2 zgA!zSSWO1f(1CG7GtAuHPEWW^0l()71xo^p1_)$`2M8%8kEzIl;x|sbwSN!FQF{eK z%kWP;wz3JLlSS#We*Hx5#2^21Y66aelM%unN|%N$wV*0GaUa)P|JilFU2ko^aUZ;G z-kbJ{V4Cm9v!HY>KO1|*Rdi12-M|;g`-TmQtYyYnMZpNLe1$mN!aNE?Fbs za%D_WZSK8JCzZj3Q56k}O|sp7+}z<>^&xR)U%7YYvgEoTdZOJT&uM$#H}49zj6&{_ zFU^XV(!PU&66i1BXAf!JeeWzjY~J5+XWqB=mZ2>KyYdMu7B%5i%M}P=$Yj_d`;n9m z*Oscpy|IxcQYr&a)G}-+#;z8LZrDVS5+gxP;3`|yD>NI1mbI{@1H_xz`Ig7Q%_PXKsXi}s#i+T z_-vZ&q`jA%PIWFgDA0EXe7&wtWXSbUbVm~2-xDjF5?}(r0k`YT?Pd47 zl|Y+Ou>k?d1;tjf1n+Z*>e-zRq5buM4)-j1*(+tkO!Ss|ql8`$5a{?yJ6VuvmMAmK z6041&L~R946{-ETkaQw2hR4`fmZ4_~l~UPC3fqMM)vbz2WyQ*?Y7VY*7VU+MF94{| z{VC~fFko+izwZ{54=9MM5lgRB3+%B1__jSoBXjlDVN#SH*$92YmX*-h)oO%9gvE|vyx zGBfRdch6nfe;ZQd~kqBywB#woKs(f4OZ*uIc5&}I7Gj&8FyyM^XArMV{6WRw1Kx* z-=~aPXmif`E)6?32ES^uhp0>wuh^-e>2T!K~ z0yrxhC|e9Po&!O+#pBw;INgVK^yt_DL~eB6p=a4BXb`Lq|H29bWo9UNgtdlsm5x{p zuI#e-K!9a|1V9IP9r0BM>!>>Di-&8=uOmRMu~l!rs>eW+No|-Og>69;uo4lFjqoCX zz{?`QhK`1q2F_1l=a}<;ph*O52d+U0A4}j0c!&^8A{0sz5(Y6APE>GT2qM`8f=gsF zyfrEGEmDF8jp{giK!z0gjGR7oDh?pL=U^S&m%Sf@Ss)#nfT)Tp=RN^Cw0Z&>zegXx zy~iTXgZteFT(knHgo}f2{{yG&nD9qD3CDH?%BREtymKEv@e1J&RH2e$ z!?aFj1L0htct9CD8|wHR`uKUDG~}jb6-K2w+!t?+SSA3I-m5vY8}WHwdE6d_i$c0Y z{HdtnkK8kOnUv9|Ab|^E1D^==hI8yj0!gPCpgjiNFb$2}eT3@sx1G%=;@B7-cB=>K z7|goKNqog{(l$==-Gt<~wXzf3RX&*l*J`DY^bLTke;il4QIQg_juTst1 z9_}`3tW-2RC-q-Ee@URsj~Xh}ncSds_S|3p`8OQDa0(**>&(E#{)=qs+`z!4 z0esds(EnwB-2P#AsOJpfsq{coG~m(I-pqK*ddT0)XtRG_w&;XMcBm*+Y5fu~^k z1s!fan|t^Ifk|b%@LvWDE0&t?V+S7QA#*?uKnEm|ne<3|F@G(5y&$vasnBMgZ#n6c zB-|uywM0`Ez~oPoIvD4DWh}yPuOp;jOvdc-!brG8MqZTQF<>%fqc3-0(~(ygNoFDs zxt0d1)I6Wq=I`f;_n9(^V|n zScvC!gTZCzgg_fCR5cIO^8(b~q8!9A z�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 M#`-S$Kddt_uh@l8cK`qY diff --git a/./.git/objects/79/e90c5aaf2baef9f4396c8cc670a542453ae7e3 b/./.git/objects/79/e90c5aaf2baef9f4396c8cc670a542453ae7e3 deleted file mode 100644 index 258f8a6703236ed9dfb322c41ca3cf67211df42c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 228 zcmV3G-6Ov zQsN5E&dkXvR(Q5z;nTU(xj-UZX+`JftWwuQ)BgC>L%XSA2Y8PELHhf~`U{*sV$!eo%sV0xp5@1-cB9KhUL6 eeFBrkbS diff --git a/./.git/objects/7d/84cd2d20604e2c7a462468e7e37b1c5d9d2538 b/./.git/objects/7d/84cd2d20604e2c7a462468e7e37b1c5d9d2538 deleted file mode 100644 index 10608959ebf75176d632b2afaf7a68c1a4b9ae06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 347 zcmV-h0i^zT0d0^!O9L?&#&iGAqxDY}aqN1F8Fc6%Vi7utimeE@gliK^YH}&b6=}bS ziw-*J>g*@ERTTURPA1noaWIe{ybsU&ym>QZV%XmA>;chiMnIp}w*kGk0C+dW-(&0I zNMF^_z#swG!Zx$Go!Hz-9PTDA_Y%)Cy2E@PBmobT&;nzC-6tzq!CoK4{%lRRNBtYh zm}-AMKYl#iMPGUzh}~l2Y^W%vX?8(UTw01&EnP{Plclywz zq|7e=bry|LIk|HD=9rL_F@#=#Y?yF^?;gBVL@IboGdzdqQcTDwSFj!fClXG?OzJI= tkmEF?3nF1TTS0_qMstLAr}H8_-~!JWIW(WDLJb$}xa)qy`vGPqig*I}pB?}J diff --git a/./.git/objects/7e/a192291e5b2baa52949f38bacb0f8268022049 b/./.git/objects/7e/a192291e5b2baa52949f38bacb0f8268022049 deleted file mode 100644 index 4d3ddf956fb8747ed0cdd4a8353334dfb4547289..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10357 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 DQD}FJ diff --git a/./.git/objects/83/27dfdb8294cdfbb3988ba7089bdf2b94cd182e b/./.git/objects/83/27dfdb8294cdfbb3988ba7089bdf2b94cd182e deleted file mode 100644 index 31bb7a3693bfb523ddf95de9a2b6e09dc78fc966..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3303 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 l?(&f^YUEanYba}~rQ4c84{27lDFfoR5f?1c zb?8Gty|s^1bnG$i+m-EmOk5h3lGijB$Q=@_6?ZKSn0YN7UfRR%n+!={%aF%r^h7?M z{?mcSpo_@%4TWD9rXFNjvOU&!h%sq))(v3HU4A(Gj6y8U(Rnl@_8>v*#8{0spCL=G z>%UX2{O-Gy{)e4EJ1(s4+Z?u>RnXjDMq=$ K0{;L8JDkVi1A&tO diff --git a/./.git/objects/87/caf1f45bb4e7b509af084cfa044e4d11d5487f b/./.git/objects/87/caf1f45bb4e7b509af084cfa044e4d11d5487f deleted file mode 100644 index 036a1a7a25e3c04e3c27831b1b9a61a281e4600e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1522 zcmVH7Lo0sO{UE z*T1-g`Uh1pEDAA1mnzny8eQ15^gTNF&MZu#jtGuv$ezfWHJ#3`3M5E7Dx0L6M=BFB$G=}3ZcK^tyI&3^Xp*JS^goKXCA48LR348{&v zuN)F26zIM?fI9B->KZcGPhr3qpJ;~3X9!)up z5I#79#{i$z;e$sXJo<*Qg6qh1u|x`6_UnvQ8b^2U_9FN<%3Bb|PA-$fLqKNdFeFGS zuERdSia0bDR8`2_QLx5$NwYd&LH$Mc&q#V@2rS(k;!_+EGE&tKra+P@+CVtb$XPstBaPt0c8+< zkz*4lRMN^4si`ikautZdm9F+t)Z;x0;O zE0T>eDP+xUmPA+(rLka4*035JlBFb4TRjChSns0DY$N>?^ znbk2Cp2D+S*CBUS^BOQN5U%bz|)WhMlV-!e{wuQrq_(hj9n~y{WU=y@puogN57}3_JVG| zfUU7dYI9yc7|1~hM?J2Mz;|4o+v#+H?CpPyAcTfR`~XMcBK#yuhez)s&6eSoBh`wf zyO$==19~Y&A)Eg|>hb8E6D8?Mx>j(xCjjxrR8i&U1uApD3SW|b1JEHs!x0${%2BgL z)l8?8bhSiww~X?0mX{-hu9_>lsTW~)o(YY^*G8p6(*=9(~i6 zHJ#%c7l0@x!10CKk)|ZDV+8Ys)sP~_nK!VX!}3{#`xMA-r?L=^6Ola4Sli>)_Df!Z z%fgGILla4Q51`HUO{+8s_Y2^{`Y~#6iN0pN|C^(`6^XD$+=~8x0lY_cD#cn^%#dle z(T!>qUOzZ^oyZr>-5yWz-1*tD#4}H5da<5zgWMNICVB=q18Kr9Ob%-`uzr7zBYVky zQDo{FAb>TF7D;9vUIa+ti8B{)+T>)xoeN2hM8sHx#c;MYXw1qsj0O6ttNXiqKU_vV zec$(AMn81~`g;1{u{#jx4_ro_fu4b$&W>Jo*-0?q?8;%5Ed4qV-y~V+hh{4twb3Y-qKbBUT54n!{m{foK2)vPVES zrn^B2Bh$a#-dB{{efi{{_V@Sqj=2Kp8EF)Emne`MIY*%=EfZ-wOruR|N825cl?G%dD;yX?kP0s4D{$%zD=4{BFqwnUKw- zRNm%#p(c|Y@}5kFwO%d`40sOU&oKp|D|O@~GCtSM3;Az2nH1SdPbLQf$%EvV2kP%x zfqCVoHYx$VXmnN7ey9MSyFn9o9SgnnV+g4loQ_Y&j|L+Fnpb+(l+{)>Rd%VXI$QP( zJ{_CUFE`63hmh60Y{J3@=zj}D!SuUwg&`htr5C!=#Z0#$|MTg`@h7KO2KMvfGOx;F zMLoXEsyv%w9sOaQ!3>~JT*SSq!JpqgefXc*!`~i1{AzOcFMl==e$I<|`My@V2U5WH z%!Be@V&2X!vMQT_v$e_#wa9@4IC&A~x7f__HF*5dLnD@hzYGr5V5#eR(m>}2qx9<6 zyn0^g4D0j`IOKp?xhy+V_j%>lP1)!+(Es9Qc`YEu>aME9_t?BA3dJ56;QQTGbfu7-FVy;4QKY#qym-Ga1+aDgi$cxPfwaiy}<9RQP zdUfj#8m1n6lvA66~E*hzWf3!e)fcuPIOiydeF+vg|MnDO+RX# zuby0H@I26N?0(5Lu%mVkw2)d4mw9T9_WcU zjuyp^qw~N#GQm$uA!K>2TL4Q{)iHLmV$T3P% z>oxKOBu+uZp5VFoCs64K9#?w3%w~ExI6Y-_cr*xDMN6FMRf&Xusk1ql_y`FZwg%v4 zjg0jIszvkjo`SHiTf2pg<@2tkpOLq+KsK$*<)*Rw4(6E9s<04rx2(rpe4N)98<<98 z@dVdRRyFneytx>1$v!nttmqmge;OlHtE|#uJ>p7`4m>pSSZ&}zTAoyVT92~3z1Ku` zNk{vRWD^)I$vyMz6>a2uV!|k$>Pd?U**P$)8<$F9co)f#1&mr@yoXUHpKpspFq5|=flF*GSUYhH0gEiZ zE_JgCE40>8_QvuzEkO2=#kZut6* zEzrSNZ?t?rP#J11sn5A%2D{k*-e%gwhZL+bY|B3CtDf+3pEOp;G+<4M&QRYQr)4Z0 zc$A(_z@Z+`_dxxhN=;3Afk+@s6mv#_1ivf&>jvR}9!S8S!6Dc*-E8hi>e$R3_U3Cg zNMq|K(hok|vD9r#7G_^EFOjk`&9DylqvP)%DqDl`jk3iiMKW9)1QXX8{3W=Px>|2P zq;W?)O=g(X0<5(06S*P-a~&k$K{}L})z{nAv|M7dgM9y;LLQav+UdBF6OOdm_bw&9 zTUsUJp%O@v;?ZnTEu(ImO4$s-30`2zTap^MvQp5fG%Ik|54md$$%615(Y)a~Nm+FA z0SrFtNdZvDGUcl%e?Z3OMhbejuH`t??wtiV0Eg-uTU@(72}tN|=K%x;YjX_oBBb)N z22#=u@0TKZ75m>hcvLqt}}aYlRN5ERD3MjX__eY;VmS z3@^?e-D+*jowPmkh~sr^cgk)Yh@WPC3RoSy3#&lFH2&olvYy9J1hWBZm@4C@$dV8X4_5rPjMo7;Ve69v&3 zE9|Ist=G$K%(YI;^A{;XLbJMm%$&Z(-*LTJEb2(j1M@H&+)ibK>TIZ&!b3@Bo(@reIs!{ddonagO++lt z^c9TXXH|h^hl~C10zNomYp_QY-llM`_5)sCn{48ij+CqN#`Mkd&>hV|Yd15T?`mmz z7@4w_6jZwf9Z9dGS>!EG(MD_g$UduA-(<@gjY76bi5K|tyaoO}c!8xLp}jY~1~1So-X%~D$MSn|b3Ep* z&0Aww!PDRjl{iCqVOf#pqu7-F97}gRb;pi8rF#EW*@p|tDI19XA*=?hSY=yb2hK=3 zC(L~=z!3_|T46cHqYk3=IGfFI7&%PI8}W$fWb*nStvW)AwbLQ5m+n{sN9=R91?-g8 zo$&bi-gvx6f8OozaJ-VO{H9*#vz%>OjHvY^M_*i+?UdsUAG5)T46rUCY zh2f+z^TsrUAch22%AxMnmcg5Vu0s@h0(eGyAOpWb+n5Q`i^9Q=S)o6mnNL_~SmB9N z!!`PsiY7-Bfy-wV&&z{TbMPFAuzs?0XhNgll%_KT{&oO+K^qdRYnnCkSzpMQ>?8OIl1g&h^>_Sj1RA+3%jQD;*MbZF9wZNq~D82!D%dJJ$_03GTidiAkDq<&jlEXlvQE*b8H{G+B zh%jtj<5oq+O+S&^;ecE6PHYE~iF`;Gh)psfq1hj}l#ULRnjlY&?j9VmJBy250~hrG zd$SBOhCRYlwA{@F5kEei+;jAjY*gha1+6*Ubg;1#OqIOhy=zNq^}fW?c@SEf3F@aguOCg`OXE?$1s5)jtm*~juV#fZ-4y{222JLH>D5v6meM!#syFLsm z-$d85Y^{6t+pQu79L+G{NuZLXgkO;`tb#b4V0LE9{VRIqu{ZJ~5p*Im_Qxd2ax9a> zL+?n?I^rWvz4KS^fDVog2&@*}9+?x9N%^j&>V&~pyxYr*MTvCWP&W4-Ws5k~w_H1h zcaHa30j%$gk0{)0>u&>|VQqqH?|C2Bi8u9Bj~>0?FuQ3B#ZxEZt?$^p)o$K~(S&oN z74pW|xx3+vQZa2Fa!?`BjV+|X#Na=pP9WE)PrFAVRM$%Q|{RR=p2H);G`V)MwW!AE|-8mOUs5lpWRLIzCE?2yE633~85X5L4J zu1F(cIqvKVMu2>li;7M}6Z|%<(#2+gEu%m@pR_2PqJTggAOQyaY=t`?E9S6G-czQq?zh%%bN@u{$rC7Gc{vV zSsKrCqp7r{=!~On6wxwwIs4Sf*MMA{DIPK*-heS8u|NSf^xyr*s1vrBZ{|MELwGPcz$WNNC? zBYn-SVdkt+_9yVqrer1HZ(}a5wM>?hp4`|-0s?XEA^aQkWWjqaMK|2B0nxVghJ;2> zx&Pwp4Kg2xirdIA6x)KGJv z8%oj1cfP+KtJc>{OZ%L?c<>|?dL|e2oO$@`?=QrS#-q>-6VI~6*fcGImrcgt2oY4UZ@OJy zp<0nC_R$_?9RC$>dxMw&#>g2op{NkbHcSb$|85zjO za5(YG@}f@hV@+I-k4dU-ypulKtqH3`LIEP^BWx1^!5wh#7Y#rTs^xZk&jgp$(Rjvf8>^G4&YXjp*IlE#Rw<1VFrGeuY*qqqYPk6>D`{E*OvWDbSFfs}D$pJp3wdk|6z#Z`Ig0woe)n%u>KPE9W2c4S09 zNv}}YH`yb1SkK_!U7MdhwRR>X%TeX~-FXql6tNu|}~tuV-LTTHlr5pjPHVd+G=^7>WPera>*MvqG1{W6@|oo?u#B2{|k; zCR&zWid5++`tD)3asx}6mcE(^J1FJJCc6@AX%8l{t(~=>DF4I5?6aXIVkPr6Mt581 z3C~>Vz3V1!3-vJtZt2F>(9CSMJUU5)^*r!jy7n6pNJqh5Q2ym-koz9`q55IF)@=3k zf2kn}A1Sttp}Em?N{&$AS)%nes0P8bS@J@j)I(_YiXbn50{pZ%>e1?L5lI?RZEnoN zKR$71k}u*e=YUykpq>3htKWl);87q+UYENu#)awLR*wV-0eLZ`$}MX!MGNNI9UzqY z6W^m@kPUWEZwJCqwu2ej49)`W1fTv&5PP7a`}1e=91`i-nB&HpAfaRmTwGWly;yd2 z+2yz7pUfjBE|=ye&eI*S6TCh7+=EPX-9U1OV!b^QLSLDmOIosEZb_4?kHiR26vg%y(L@@z5$4*1nIJhb50N;OI|Yhvix?b<_H zp}+N*#=a(z6kl#u;RE;f9;k2ebW9u*@KF>Q9tl|Q(Ul;@!wv|7?@Q&)0K1Kutjs diff --git a/./.git/objects/8c/b04aac4b020eb5667c964ae0e0e0ea219acf2e b/./.git/objects/8c/b04aac4b020eb5667c964ae0e0e0ea219acf2e deleted file mode 100644 index 6f656f1c5f1ed1c7def1800cb8d0f9f08e65e8fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9003 zcmV+`Bh=h@0mXa^a9q`yX5ZTnbzgP2T2D)UHkNs`!IC9x1LhIHKthsn7+@wdN=Vbz zY0E8IYH{yvusXLVHZs^aelZvuu&^Zp#!7;1AOT}zn3=8JnSIP`Wp}f)duOY5wnp8O zrZ&4(Q>13&f@Ht{zqebr+p-bhsxn?0h4(bz|$1q@Pd*#&$G@QX-WY?Ce)N z`!iV;UpAhx7mV7;i>Na-yR5jR+#ud-FR7tnv&K%>6S*>g&z)N|$5eFK?Ru$A=^wX8 zhR3=T37t2Pflbrq5*|EW;Tv@$U$fVZIq>JMx97m_E!t}>CtmO+`ccR$-t?DBE-2lr zyVtB*-L5b#zOT7%(2=8V$7hRviRmqS|n9e1?`UG zgNcEp=}D-4n&|@tn^S!;Nu_TUlhqKt8PXC1L+KO4>MbyI_V+4a~=?cjl> znThfgM89q=2SsUbk4~Q3U3hI`>g63%=k^!&J)^9O7EbOd?m94e_L-US_ot4WMAgjB zz0=3H&+ObYb>fFpuRiyGKiE?^^6Jcs#|nG*Po5v0dF5!~%$b?-vxQv~czyEBzQV~@ zi@V;N{^-c`_#X5#dG?LM^V^Fb9GrUXEZ&{o_g>+{!ziD6<5cnJ_QL+XGskxk-u+J% zc8{UFxO-c1^i*LNUi``A2QN>bJV_YeJYLv!nraW721&)E?-q~lD4rNEe6XL#Vb!B% z^33S;>mN~nZyv|nruOYb4^s!8nSSF(C@*|?vN*c8uzTD>OZZ2(O`dm9+utpmJ~;jE)5XK@()iE6Rd{|c$}zK(=bxYX@g6jud3x{E`IE&%J15V* zTO9iUb1^fz4WFub@O{iazx90K*}YcP^?R#a$@TI+x9F!yQ?_*QyS30 zWBBM(`%V>J9YIS>V&R#0XdaK8ExM_o`CZ5uvQT@c=Jay zue>^aYRBZc_xM{EU)*P{+3Azxg znOLUFhI>R3(NVX!YE%}>Mf+a2KN8A_bW zy@uDyi|)PBoOxeWeN|rESXnRD%;x)i`C3B(uG$4IBhX*lAB=|#X)J8WuvG!}O ze9#E?*X2WET|R7tjBwVKuQTcnNZMjOGFC5cGU_%<;%2JZERTD{EqcRP@ zn~dPNbU;#Nq?;){D4_+VL*o+vhQ}qbe$?IK%16Yl;x;3al8~;~TTr{es2`U`q^BgY z*5k^D@YLnQeMpJh^9`V?HQ#77_P6Dmj3&%zv(aEQKj8wE;e3k`28H*FJMs&-U8~WG zc5Ox*+ATB|7z@#Ek+BHvTF~x)09U>E3PvD_JE<2M2~*bq>dGTR%RZEr#+2q3&M`IG zV`{R0oALHtqh5?>vAA1oFc$OJmKdS2g~pO`39FE({7W1AB4EFVXe(o1P=)=t1N%=d z!rp3QZL@zD;_Z7G`%-bA_^Pp#u`grn%LqGqSY(96*T&_P3p7T>{o?CJlv`b8&;M19 z`ERkmK?8oncpC9E;c3RR0M8;kt$5n<3&l4uv%&tw_!jsQnCk|k;ef2Ivq##1k$#hE z$|G&57^$o+DvwlFqZf^o*39qCYt0bN4f(LxI43=SGH-f%9rSGZ^7PQ$h>dNo{BrP~ z;O z=xFt4)XG@St^PH&qBL4<Rsnzge7pEV z@$DThWBEN$VDi`Wxx^p6D&?;=uD!?g$Q{qRcDnF||5)_w@Eg}~i;#|0kCwza+}icx zJL0=#8r@n0(Wqlb9B``(h#pFV#_uw1B~DQX8Xw`Wew|I%bq=~h`MLx0j~Ovw-;Q4EMcjyi#z(1!XEx!Wr-$e<0!&YwX+RqEB&zA* zJm!vnvFpVq-ft^KuMoycp2Nj9t&5$sVpZ1RX~z@GH+>s?*5mTHMsO|-)_36y(+)j} z{Yn4-7>~1FtbORBJil?u`3AL$C382?xlQ^7gnMXWCHqJSn z*0Rkwuq}zXEGS!&boOr4Si;hm5v{W%h}a@I3oxc>*Jr4E*;e#*DN#H7 z6~h1J#B#=HYY6D+{juz7>U969$#bt4&K@Mec^8Ci5_`+~OIa?o#6(EDF^?L>YwA@5 zmIIiziJ_r%3bTgU%PvAmmCWxH__NodexAyKMK5bB(JCLQ(LS>oSciJf(Lp!m$ zwUia-)R=xPNv0xwxTitO_VrZMk6^q`y?W~H52s!~iheBp_UU(K4xWT=H}%x}q|Z3s#%`bv zK0J(-Z_OndID4`b+Tx2JPCs`Ts$t>K4%Uclb-uXgS*Vy6YqC&T+i3J`@ugD=27#q% zO-|J=D~|rS_{!^~YdMI)=7Qci_3ZnFT|X(lcnUft)-k>(t+MxyO_x@AnMW26y$|KC zG|H(XXTV2_V>_qb{7`9k_8V<4zY=YC4o*>4ty-n5EbKZ`ICmBUnK}Hk^$wHAN@XR8 zES^2EKKh;Su=%5N)*kZ6BP*5c^RUIWM=!;*tQlcWQ+Vd-!pZlQFUi14L&Z4pL5s(q zDtts1gbKriW!8X-&CB27T9&l}EuBUR`*V|mj*UI@($f|TRI0wzXY#|c5yaYpur#2f znQg}kXOEZYt^71j4jgT_CJ*1mVO{yM-{nHr7F{3G)^~QMGrfuQmW-ya@4l(4`^HYT z>2+3r%Z>r@zhUljxmMJ#`;B+PZF*tPh7r#@z&{=hhVlv7w#*Icu%YS+ede$50+kLW zhSQmZFnv^-)v|CBxGscK~joK*3Ht?y{3eWQZ}~k+37>7REP<*=M*Wsu|V{Vo8cOZ zxNeZb`E@ho0lHA;Nz`7F9^tL}9=k(6bUs9v%!# zN0<67njM-VGt|o-B@rLYz$u^-(h2E;bO(Cog@bpk?&!Of7tVDyQcQUC4*r%zw>zC2 zgmicxb^a&;LPH4G<(x|gQ$&R~S9mcXe7U@M9>SxMSESEnxE`s=OE8?l;W{ooCqGWk zPTDuaLrMI)`;vN0HsuaXBN1P1gH>)zr!<{b;T<1Sr-Y%!m3wF|yDNsSU*y}0(?X7y z^trgbx;t*;<3_s!iBkVDPmeMyYnnbw)Wo4-u{P)qvmEE5QPY`3JV09vu$@L7pi!&x zE|(!a;rc@;tB=b96Eh)gCqLm3#2Y%|i*ng&Q2JwY-mJw-L4pFC6S4EF9CF7#yB(Tg&CEAd42oni={` zSb_2#w_^#WX`NwdSxb<%skH>>=zMmImRk{rvzY`~T!*#NDFLkWYk64mf6^7b@q;j2x?lbbi=8=E!Bt;yc3 zo+LKF{M%IS92({|oKVgBIFC8Y4D82WpYDP61;R{s<5_%jSp&C zv#Xw3Yol?-lm^T&f57sotLCVmCcDt;%%-%bl}mLsF{sgi%1AWz9(t*kX3Sd6UCzuJ zXXZvLV@=39XXa*Svude`hMTY^?=!rH4^AvN3AJzQo-wcR z8#OQm2dJD)!HVu1t6>+`&8YVqo^eUN9Vxiy*bJ<2+6%KUmHA-yt!1;cpPZM5pUu7j zom$nB5t$uWKA3vFcz_=cEUyUX6;Kp}^R2c&L@qWsrb32dgy1ak89q1xeAa4QK_|7rP21tsa=XA_D3qQBQraTZL#-K-=tSj-j13n%9_e=V)r} z(ZHR>buhY%1~z6lk@MAP;!!mlUbqI~)`IKGr+s4MZ3NzCqN9y5+^Wr?k7O1LXIRV>opu{DNRF&j7p0Bgp^B=BtkOqmC;?8)=JGq-AHsi zNPvW3;Ot{5o1`8a>6K7U=ED3iA?+dX9RlAa@CbqL0Zc?3g3Sn(?jZ0qfoBNpB(RIX zvjlb%_#uHk1l}P)QIqO^0tWyl7FHkl2dRp`$YDyoNZ=&`FB3RIV3fcs1db9oMqrG< zIDuCQ{D{D71YRd_oWL6dP7s(N@M8i$AwZ#V4l()Kxet|600$NZPAaUYPw<*XJ`8!A zf^&e51|Qga#J5}itu*Rd@$1~J^Jmc)^_jmV#8yOg*I6jYXKLKZN>vqXtasP|IAtrtzz?SG7xiG_HT})Rmit@C|A>)9 z5E&5xyGFbR7sZ@+0mRu zJ8Lu_G3CzDz)#L)V8{FmW-FSRxh@c3uf_r%Rjc7gqy?e}z|;f4+={k@xdoV8%h3>o znV9n|=6^;z3-c%3yAv}-LeLu&2|>RU3DL%w5xE0~-Nu*~GG@dnUkGyIX)rZ47QFI4)3a@J>X>O&Uz7G z?`pX{<@p#$Ip3ht*s|R9mpKMDuxX8qWYNut%G8+mt7M#1$#|-E6Ie@t{FUl$0J)`( zmk=JEjHXiqDLOlPlSz>jpLHTYM;%H*31iHB6sSL^zAIU^`Yx4SmWjsN986SDb2kAG z0fRsuzzp0Ay4XLfZll8O1fC*rMfh%PNj2Xk7NaureSuP>Fa681;cMn*!?slwA0y7^ z7sBRU+DcGc=E7wGJgAU(9hM?8apa?}=>H3UEC*M7oLfJCzFdv2f-N@!ZCPr5fZ1|M zPNo37KB*73D+F)&D2&dQr)%>FIDT!_Q;C{T}T z>t|~b?A9mHt+-3;{j0n6Fv{ zx`ag3V*Lm^0Yle*jehIJRYpCiT3%x3qPP7b+Lw0%tgghf35H zwEve8pa6!;^Vi&cU&Q@YMG0Ijx$EZkdR^R7y#++gF!NkbOwEg`OB+>oIHmUP3Zse@ zukFS$AydS5m3f6E@sfE(sW7j67s|R9k~o=Hf@SkcsBB&#ql#saxEraGc_qYYr+LMa z#QiLZ&uv~I8PI87sh@{=Wfvsz`IuK0@F*JDyn=(H%Dgf=8ZxR_qnWs9G;^6(EJ?hH zCGk1TE0Dxx8#5VLS}!uN%wqmuWz5BkFwbRRu_W;pmc%b*U@1xBt$YB^ZD1)$-t#xG zTr64NT5+UUlJ#oljU{L8Y`7vFDanB$eYk93u`eqy`!`*dfQ@}Or-h(P|2k2mNAloOna6`Cs7Su0isbr% zZ#_~GJZ*Sx4NLq^FPc~Uqr>M;JlaNp%U-jr>F4_crO3BHbL;1IXObgqVHx9htkZp~UU9jQ#z4;o8plEax?E^TBXS;Q>@i7~ zN8nh7hE9%UId{Vb>vE;lAK#6wS`AS(*$1N|JY%9q;dPgwTdN2^^rLe#QmQW9j#SBf zgIc-}s8YE#z3UyeW8FHrS;TBANB6n9YaNV(;go1qTwrsCY8j>>Audgr+lDxEt9 zY(;D>l5y{*%31_-KkP-qj~g}toJb`bk_Y#UrTzZ}iVP_dP-0*@htdI+h5iTWzL2f% z;8sydHL;ZuAE`1>Mk1_?)Y!@hR2AGovdW;KK#lQ38S$2sk^ZoCGYKvS+)V;pu-A<{ zGp#reVeNrxqKFo{U1QWjHGzp=JHbjo8&ncfO;BE?nmkKN0NI|@eJuDRRaQ-)rj(Qc zTQvzWXAZIOAGU>mSbSLcr+XM=@+JpE0QY1RC`lG25>xUkreqmY5}kvRzbRAlpRXJx zP%%K4t%5|la6`z+`<=C-l@*Y3Ys#|%(&BuBN@G{$R?+H%nS}hzu!3J~n5V#|Y~L1Z z^L>V5a0UW%*fuLNP9>qqQWBsU&;UzET|M4aTL?4& zm^FxL-jvaj55s%V#swL+%4i!euR6q}3%-;_&WDoLaEYh{D;kG_uE^s+>II^8u9OyF z!cWfe0cAF{gGM%AgFR~mbr#Pe#>C$EYCV8;15yUmzaVfAflHe1=RC2QgCUB*qO>HJ zlt_MxxFN|4vnRIG-)ebM751bC$s0>%_)mN_5bYuShh-0LQ^PDT1wY;G+aoQV4F5`Q zT=A>iotNdQI%y!6C#!E=9%I`(xFsy-u;t_2r{<}=}oBRJBZ9J$$Ct(Y;9fv7eda#qpEM>lxszpc~nInJndu!heDfJi|sJH z42Rk@9L3+NNZ`*|%%4@UO~EpLkV(OGV_nYj1q5pnH~1_ALab#Tw%$uC^1lR%&*fr^ zh3BzZdxEUA&rema+OR7ww=*^&PKKuWJ=(E1fOdJC-*SwV)o@Gk4$D5NNm8U%_a1ra z$7GaU{&DWEFT5j5cq;@x3L&b&GlEAOgy^@O;|IrGdHjVCY)gnguqS#9x8Z^N2%An@ zqa&t4xD78h1edc*NE$=@1C~4eda&HRqjC!zXe8DeK3?v$DWx$lIl6eclgrAD$BW(> zi;#c`n@|Ms>jrl2{V|!bW*Do&SZj%zD}WV|5{xNSf$6m?j|p)WZj7t0Y9{|XW4fX< zDI#-Wf)ZbM<#Ay|^%zk=_Zb1$n!yyw++irVJPO7MoR2cjMhKYXJVXt%&SqlG#5&R9 zcC^6zxZ)_OcO&hlG}vb|rAY+kU#utoB{qu7_gGg!j>;&pQEOenE?r(%#6~?9E7f7W zCpyP!$LW$NE^do9ToQ#_)Nx4^&Olre4c|j80(#IIkKqA@r^I5h#6j^PP=Xdr@e!hm z(x7+=Q|O>rEM;a(D7s#5=!NUdRJ zm(+5RlE|{kHZtI92>m@HD~4L#gvKC(W2on0IxfZ3v*lR*GrX++Ie`bL8?PBE{lOZ< zDdG}JxSV|wckhyBT{&M0yexUJtPn~wV&}{8j+c%?rr3t?Lmc*3=gax+wGpQ^naQ zJyu^yj@0-yhwQtX8qP+i2+HmxP!3=*Jrvh%dLBy-BkX~?jLe>|J)zQ4wK^W7S6?D9 zO5hZMHvxJ=rR8C@W7&At0?rXFZ&AD2*$LFF09Ov;wwDqg#FNx1&7!%$aS%JF<(D+G zi_wD4-hMeK_e=CYEgg49;0&c0LeKAVYre>gZ;S=iza#?wiom}m@b3uxdjkJ~0P6^U zO{u>j@Lvi1Hv-cHWbxaFh-;X>Ef*xyY=s%d9WY#FFR43}pvXI?ZASi9;&PBu+PrmzpS+3ebzc zUB-XVNhN`T4Lm(v^-O8IJ7u|b-e3ppaEjGEw`osyd~3o1 zV!(Z{dnXGz?Y8HKCEBxOx9B_jFz{Uc-SH+TZFjQN^t~2qSRoJX7H4Xq{#(8cr`uSm z-~Gz2hofxVogQWHwnN)Tc57AEZ3Uk zzQ#4l!&?4r61FbbRkPZ5t?t;|4K=7_6HdELJWjy~e$+m)92*A#NqHEtLcoZBhS(l| znuVTTXu08(@3rl^WbmYK)p@hyum;ChiBrJkX=hbde5m0O0WoecSAi|dmkPn+$U#se z-ql)OqvP=5O@^LLqimBwu}Pr}rro-E2m*d6-V`1n=LhS&9kLxldA9q$=S#5;v9~S9 znXu~nS<~|weK}zH4d`L34SrA9jvY>m(;7`M+F(wiS#1c1PNfJC2n^gy=|K$jGMcc_ zaS2btL5(6)Bf3eI)q5_)lp>lc>HZ|;Y_q}K05aG`q{NBv+*V7H3>%VKltj^0iI4n zA79YRnxLe+cD;9Tsk=NwOP9asE?+`Ak<{yG?!S8{PyJ=)=wRuQWLbChc6a$yQenP- zG9N)_=1=yI-R;evh!zEs>rbL53wcEXs!~Z}8r(V3dwBHwCzndESD0McUtL)LY95)) z;O>#Z-7mj?GQ0gCtOPN=&V%otoWx?eb9ebZLCih?z3V4V1C<2DsiTF@`ky}N&7PMy zN?SE@ut@^+uiYLj9S8EAJV3S+u$|oUUEYuzM8_U=*S^Rs?jL;&)V(V+-L`P4sOgxbN48F zTV0LjZU9~H?Bl`H%YZGtp)sysSX{q(5&Y=Qf7YA52&C`%9x^zC@W=_p1F?`u6bpfE z{nC~G&C{h{XjD*x=)pr`?bFNA!*hbZDVu-9TL>VEzG!hC%Rp1~=yrGQ3eXL{`%XTA zUiGe@Ag&7V(whdapgLMz87wXWDG~wSOCw!ag3idpk#?Pb9G$^o&Uc?&jONcvK%o*a zEn!`MfRqo#nop*2xKx4y0uB53_ut*cdP2DdPtQh|ul85J=v}=69TZ4O(Lx{gWZ2Dd0x5R8}d@6CSIyMGg=-MexvMizut*dZFqF3)ogdL}HPfJXI-Af*NM zGGwBJ!7=H5wDvIi;&lJssc7X+yfYBAy?{4rf#-A#*MyEke|tA(3%C{=QvpR*-Mpq< zr7>oy3hYVOZbDA*c7k@{*m^T}W5W1HHWf+Y^GM3T=f zL5p-dH;+fx61=Z>e4jE)l7SQyeB#N|{kd;@h)zb7L1%K32O&6BM*=vR6sbTPuOvgP z)4~bv@}@1Azkc%*2)uXp+y3Rb9Qi>9d73YbNcwzo9y6=(&G;9_UM|6mO6fp!NtUu? z+ZGiHA`#;UdA$>IoiRvT8OtQXqwHrtqY4G#pXyeTLWrzN=FM?Vkc{6ljwMNmqijJY zp?8ixTkEdg>Ycoc7|>mwqi}lugz!v6IDKMTqWNRdnLo&&8!a5|oqLd4NQ*bK4?QFx zmV+`tc&#y=nu>g`U_DjJ`}s6@pc)P62SiK9*FV2quo(3vcYyNVjp*vB=*;Q<-3RdV z>#G+Bi?^6eP&fa4^Dj0dlDa(HymiagH(uYebt{5t0kYVwk1^>sWi|zj(ZbUD3?lr= zn1_X#RZ=QUvPa56MgnMw{9<-bN-R}u67y6tWF4^VP^P9er7tq${Fo05t{z0`q@@yP zz^WV^&Xy}su3TM1T^CWoj6gRTk2Q+OkomCM2|1ux2qaXa$x4b==?@jrs4d;_ITZq} z_mDpc$6qar6#8RcQ|e-DQ=?_96L7!QLP$bcSuMY9*$(@BhwBrThOE`f4-FemudXC z%Q{)+cy$pA(MO$vIV5-$PI1kz3=3lE8BHn*cpp(Er6aGvvK0j}COsVFyg{d_qLcO2^u829)<*g z?IOKJHa(oehaRa&iwp&-^GBfi<6AcRFOonERZRCXEk?asyI$STQ*^gFLC7YcdD#uY zF-cg)uV{P{o>DlI0LVu&iPp>Lzp%z^S0ZtQDBf66lgf1GnwN?%B2=0 zl#0Mq4p|ID1C8ObSW+Hkibq8$WjQ{KrlsQ}HGLLZq6doz@l+VLgR$3M!=s8BF;rlr z8N4m)U@i3abF}mzh_FDt6vbv8u)ne8U*AlVhx%EhULBv0v8Lm}-{cilVoEFpsx6gY zrZJ^de~4{E;TA&q(n=}^Y06YLkX}m7K}`llUU`)?tLne$cy0t+6|&2}RJ<3^kU@Sy z%;{?UHNPyuo@(NIiY+n6%;RLOL9<8E8{SX`@oI=*?>^OjrZXyc5i?5+6Vs;j;{L$+ zf($;mBjJ^-kB}zQl(xd&LsJM|rar|nMVm&AC;fus2!B&4a1I5IfrKD?^qKXE6ArN* z7**JqxqQqS*K=bDW!RZ3o#gwJ8VSy%y=g3%7szB^o{h~!l|u6tbqL8^G3eFxiKZw+tgS&E_P}7K;%1oeLvNV_SsMLk;(i(z&TZ;w^=@v63*d6Nk$(oQ_nX2NoN(YfRv)^QY`H$hS+YtS?al7 zfRb)Atbf>YaJt4csSF~AGp#ZrG|gh%M1g=d)V#1+>FqY;TD7T1n)7pv-*~0O9UQC`KdZZGjQKjr2~=PbM6%zTYe*Inu2k>h=2A zU*|pYdgg-z664AVE@cskNpGfhnN~~$=<56Euy^82-#bS*J=An zP-`ZpDO&f&h{rU(w51XXUFK0;j(ZVfjIJzEh5%pZPSenuU@J)58o{u_=%>vvpHLHG z#8N6RB%_;N$8C)2CZlg1U_yqpF8Rq2gUxAnmH(81!88)kyHA}vt)CjWj8qU;I%Hpp zCJz^`_r5-d*($DxC5LGlMJDjr;VBm*I6?!sU-3Gc!rSTMML|0%8FJ83cj4+0b;RIK z?i<~DIQZsv!l|VBTELBGADm?l2`N(RD%QB?wE%VdZ;#)}^sI1?%c|yuA?+Nu5g>#N zxp^_D(q=4OwMw5eyChe5gZYtwGU8v|6H(_NvWEiI2_&7qxwgsr^vOCo@6#VoW`S^VpR;# z<}iJyn~ur(OG1s|c~duf_V|_?5GzC{c!N7Va>rpO~u|Y7=&& zWt5n#nPqweTd+J?O(bV)2KLodP0jxPL8fX(uvF_BNAip|M81&9Wr2T(!I~kvK!<;n z%D=#H&Fr0ES%!=_YLOkv0Hre#M6G-%OV5gl#{%g8(`?PKHLi|{PyYkcHA9KKiS zYYasHU_`fT<>X5?%GIJo8EO!!kSk{aNtpBs>iOt6oNQXDCaZwU7G*sF`h*FYIvtLD)x2xUc(S>EsL@24;bD(%f!e*Vz8Jn zF``Da$8>8dXAn^r9bV>NcvgRU+MoC}=XI{RpUli^BL_4u7?%7^Rbg zLEJHjJI_d7sPmleH^%7-+PCyGoCn9%bzsqkkX3qjc}4vw;^b0vdky6}dq;H})UPG* zlZZ@ufC~{V2c5WQA!Wbb^UltVF(mG2)9*8uSCW+V&tAhl_pbPHQGao!_stQICGTz* z5aV@1{hNU2prVGvCunFq;Uu!)(ugQ5R(&e!{Lrg!ajZ~0hs`kZ878d;)= zoEi@5P^g3`vQr~$k%}VE#5t67#BZi#+lcTdd`+@huI956Mz5Zi?IU!{)A%uzedf$I zxk+vqq*7Jr+7FzrzSJP~x9)GFJ%FU9DD&q!4G!0i%H+;XiQJ3X#x{Ba9bE5|-V8 zj7QVG%PjM=RL>wZ8F2){SV9tlKp-Xp?>fRDhu_$-*TTR9Z^=X6l5_eg=VWIjJ?3z7 zk`sfJ@80U}nb{wS8EEFMCUe zZ&Z~3phE9YM_~&t|KE|gilexytCdwp<^P(a;eVo>sOXMfF&sms`+C`|B%Nf%a;!?q zNpYP~wkx({bKWcuRMJkG^U3mHCF5i&L(WiT*cq;@a#mGFoDo&|hT>Xo>Iub7dFdkw zXSJ6^jqMJgX5ff+M0G~FChZQQW{_*vphdSH7Jd^t5I6b zch{mk>aIa~4eqXUwLMC1tWm^k+FPf*YCYSToo|15q%-$q=apaD?YCZ9`1P^uwpyi9 zsJhwqU!BPAd9YM2XV1U%MEl3*&d(h_|L%LJ?L7Tz`_$jro#)@~oOmp|tz2lh9(p06 zF89oMRVvsQ@fc+yrv-_vcWw(Zh5deA6pHnVSAU@9<`O~5Ez}Fea>4flW3uFx-M}pP zZmC$$sey%iUcFTDkk@xq8CscJJD|!%1$P*n0Q(oE=VT@9wP= zdv@Nl`&&+G*Tmi(-`Kr%?~b#ISc3_2^1oX*-Rpb8-*liYN_doO$Abce6c3HjWY*D27?Xb@+o5}x~glq@Mo!fZ;5o{ zZSxd5MAE$%o5M+Xx@WlBW(6`fy`*QYQ&85qoI=^=vVpSc40tx~B{wTxI@}EQN!1zT z+5xm!?GCz`Cv+#{roAE5T9ev3Wm0p8++oxXqt-@gl{zfmlDzCRJ@q=$jw2~iV)5+p0Ag=m(%uZGv$MgmGBsVlSU zoHDD;DSMQ&>O?LflBjhO)!G59zh5IoDAc$}%nxj2a z-WWAFs|G`^H(9`I+xt+}wYl)+oE# zYHfv1B;&PsA+a7wU}KIAZwIXzq@?>^d9p{?Ea$;|ex~Nv^EAzTo*=*{JCN2aH4UY< zE;rZp3cauOiZcD!!Vq;x^@vIf-xxyi!uxM@PX1(J{#^Ub7u!FWKdTB8wSu0wcuvWB zEl&`4Gwyv%>tt!o)xEO_?=N}2fwruCqsY{;axGRqvQTc~B(Gb8q!Z62d)J&@!p=ksD6HK5%kXrbgn zCtx+iwNxY#h|g2<1xiws@I+}cJ%VObBn=SPAR%q>>7llwYnC>w4rBGlR9hQMj_BNC z$wrTX?FDpO9@sp<8sx4zmX`w90fHiki|-8d-H~9I_6EDP+=GBG8Y|Pi2S~I4eyMH^ zK#rJ!?hLsmc4<1ugm!vc4c6w7dV^lO{p|Y-@12o2w=njH>g}+(9=<*PCPg zv)vf#d2V<){SaTKeqW-5799wUEhBEDGOs#o$L&=78YQeTe?&P}huA81F;{#Acj@xU z!bwe7udY|u;##lq-6dK;PufCV?n4r30h5Pe7Y5EH6KCMR=_V&FHeCi8I~OL(#oD_j zw8C_WyXjzZ11P7VDXGwW5r@;AK_(rm0)rud!Gv?!m~M&R!_xzuctT+N#K!V;-M#4QZT?QL{ciTos#ZWWBDb$N4#RA?(M}rpVUt~tf!lU zQgq?HC72ScDSpb>0CPWJw=(HtR z2e8iZ5(mb^#1XiDIfM!w^h+Dx(pk7`a;QHNDk8^6HqSah9Vz1MzfHp2Cz@8Fde z=HI-M^mr2E*$Y2?O!h_iAOtyrsOTr>7@_=U0xDwpp<2<}{N~h!caA{I;;H5|aW|%p zIKm8q5Qs5Iy!saE!~`Y%k_}P?*Uj^6{}Oi@u|@}VHFU%O!uG~L!pLQs5NN;|f@N5=dRx2@`kj55*PE6}9by6)UE&Yu8w7Ls!^Pei{ zt4$mJ?cBq36S9Y^qt--EotH@42M15ZLZLP(wo}?tr>(H9@v^pA*{3{Y>PkIz@L^Rs zgm_i+hHykPY?Z9rE#nB8ejc+v;99aKerD?BglnHn>{CqTdzjlH<55FCRpt+<2k`uv zN6{0&;SQc8>X2!ife_-==rwOw+i+Jgaq*a+!G4`S+F-78aVp_L+At{NmN|tdq=3ueLF`s9*NK2(Gi87k}CQ;kk>?|7bkBliNfC z{O0lY{QTJ-&y{_dz0B74aBz(vgaZ@pw@!49J_8+QFZWsun@h7kGfV_JSt^N^Y)!Zo zc{kE+BK{ksc05w_BqwAFO|}O-DYn`aGN7P{h_^(bdW&`i+p6Q}=o8?=g4-EQQ%Av- zP3>CMR?Wnysvpj1pHr!J?6P%OyEJSzNBC{zp1G>2r=TsJw!^7*6KwMJ z20>q+$tT=ltKY{UA`7vHCQKqBt8l-Cy+=DyomJ+PXSIi*o&1)t<79u~?rIxvWg!G= ze$o2}_U1C;9PpWD?s%6*)sU=Jnm6{mV8ja$si_qUW&aLjqCQycE|e1-i9e_0dP@57 zhv)JJ>O=0Ks~O@Ry~rLtNZdu;e;-M1Xk`vUmV()WbcwpY(zodb`9JLNN;a}05&0(C zJ&4OEOOvq8a#Fu%yhn&_@tkVH_c;CsGwf{B1cKg!cZw!r@yl77Ex zCM8J;U|q>4IMwUMtl8&hB||^!N9Z0SQkS5F)4P&^m7bRL2Cl|XLZ6}Ei6R>uzGLK7 zw2%HhhKlFYw8D9S!&qI>i2E^VX=KxEIP*o~eoHt_ue-&ZhWjm2e^S?ZTpUm}2TTGN z>1_>15097)@6|rzOS_UbKdr>aBquCOfIMR|!*IV;f{gH{D9$P*Gx;ZaKT^504Q3LBQ8xBB3JhL` z6$K>n7y!iX*_8xxRr^e|B<@%oKM8l2wm?`VMyB-$%WB;0rkvZ zVXp_8Ly;&}GQ@ONK|}esA&Cqznc&zMBRDh%7E1;>G|xs{qn|dgy$n8CktSM#%nIh& zz%$4^e@ym-F!BCWWS(0Z8R+M_23qVx8}Rn;;-5sF<+g~unF4}Eve5rc1JhcrSDM#H zuN_&<9`hV`Si&Bn=?Ryf$vU@W63eKg@X-8!aQWm0m`Qy#&G?i^8r%9lRtNdX8Rbh+ZJdf#`+hUc-S{q761^14T9>A~BHCQkuIK zbOJ;|(vNtIp+#kie^?ypFgX4ZKM(N_I$o#vhk?^b5E#_Z0Xv;Uk_)shF!*4dorvBe zcEmbEhC!@B5(^S@^rL)CEXGlYh{cS_G=e-uL_0W5)b&}Tk5ZU84NA@d&q8W3g8^a$ z=~4@x(3P-E5(_!+Wrzi!4u=GYE&kx-`S;(Fr(N-5nkM(_@&$U)z``!2uL=BMD9q&} za@8LO2{L&f!O59?7{ra7cjnS8vwm~H+N%P=C3~`tBV@1xu z{(@gBZj(neNmLi=brErhGUm0|BoKWcCfU$#%HSN|g0p5{YL14I>60|YvU!dA=tG}E z(jxTnD+Fi};;_iYqNB*^L`&POJd{E%RL$w|Tp^4pX^rC;BGwhj#iZ4^l0JqL>n9$I zyZuh2Lk0kt;K}jEvw-LlCMhAD`RX(kw^Bm(E7C}Td|}Nr3aBg7PlkmJI*2SrKS7`mv3Us$C&JL_)dl1>MfnD_rnNN*At}6 z@>mDa94v#YBbH;(88NkK0kjSob?Twb+yFdD%xoZ3nMSbqq<#oUGHuQ#-DFEgPphRN zO|>X)Wm5F1xwd7@S}o)ev9b}dN`~zRxP7{noK8hirB-qu>^|a9_QA&yhqBQ7I&MAS z4sz>EJ>9Z;qEMK*tZ|3pI~vVAj6&ImtVb<;rNI9w0+kzX{3}TY-VFafJ7r^i=kw6m z|KSs>&e0bZIYKlr9q`2ML&*iu6X9MQ>y6cpPm2l ztC5N}DC#c@*&7>V#Bazj~GjO!d>xDq)&gCm2ubcY(p01 z+{d#M4WfwQllmfJ9Hc_~ZXhgdCH@RO8l!Qya4|RlhRro7csG${lH2SW$}!1cbdR^0 zj83ejyL&0&5En(Waw+LF^}nC98Xv~sODcTF)Lm3Tdz3l9xR-L@p=3>*j3E~ch2%Ku zcr$HPI&;2aWwSF=aL#eB3O@7M!icJuyN8|Fn6{3R7AlUNTwRaPb#x^olCZbgHfHZhS~brnudATN&$ z@1z{raHhzjPC4E#4ayy$g!aB@Qqq+u=Sa8314u66;twB*iC?eTLnD?prm6ZTnxYj<|(}Aa)ic3j2$K-jH1ae8doav5D9iu~qp^4mm*l z`CFiS_*7HS>IEjwWN{YX^T7sBnY|Cf4*sT4z7>N2`ytp}d1O p6nh%FVwn~`>lp?2OJh$CCp3Fl)z;xtpG;=M@LeOj{tq8-?TAn8TW0ZYosPf{?lWKdQJNi8l>C`e4sPE1c#$jr+u$xO`2tV%4&%+CV=;kXR~ DpR5xQ diff --git a/./.git/objects/94/f53c3644fd3c9390e5f2b18ea61caa0ef7fae5 b/./.git/objects/94/f53c3644fd3c9390e5f2b18ea61caa0ef7fae5 deleted file mode 100644 index 6b018994dd78ae1c85674ea724abf96aa7e56ca4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5476 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 diff --git a/./.git/objects/9b/dbc86b5917da9d6e7f1dae017b8d5dafcae90f b/./.git/objects/9b/dbc86b5917da9d6e7f1dae017b8d5dafcae90f deleted file mode 100644 index 251b65444d5270c57616041d76f8b1e969879388..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2964 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 K4Dly;n`cv#rNWQ^ diff --git a/./.git/objects/a0/94bfff7aa7820618464b22332d9053e700e01d b/./.git/objects/a0/94bfff7aa7820618464b22332d9053e700e01d deleted file mode 100644 index 69ed9e66644f9aa0a586d674663f3e71a4486f2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1787 zcmV?O9*3kpoZ$EZUNj1{L6g>nMRy+YMsbMc^6)MJ}y{DN@~C z+7<#Id+V*&^yl&=ota&dONnya9IAsLEoWwDXWq=bawQ<3fR z?2av#dX{JVoo=_=IT!Oq%9-X$Go7}Lv3<>ElM~9U?r{lReGYnQ+Hcw%H{S})XScNcYh{t#kz{V1OsJ?qqwyf_cYl{)MDg@D@1^z)3-lq6^+ay8? zF^?fFMjA2Ql$t$I!(jlSLd(iFMm3gT!07%Ih~cFPT}DA(6l9%dIX*b2f;I+$yt z^E4PY>6b__Hum>C%;j4*>#R^bro(KqQ6hnC5Ew49XclHSoAhk{;m9`Q*H?SbK6s1r zz$iBW((ZAK^3W(h9X_(QkkFQU-LGH%WiAd+zk@+^TrR+p8!tv;xgzBtZQA$i?Rg<@H*nc|wxB@*VH9-p5d zpS=$*4?lV^ePFPZP15vetk0OI&iYkD#}pu$b1LD#v^2+qN7F!YPgBTFIX|OvjTdrR zd;VPojN4ntzY9~vcUbp)mKSLZ8M)(3h5{11aTc1v#2+HQ&>MxoKI65UcDF`gF&RqB z9yeVDopNw^YM@lZp`Mt8olOul6DL5zKqLU*wF*9r8KSN+vly|9Plu-`40Ts<_O1C# ziK&rg$M4a};l9K`eDTQ5qI8<-XRobG;78BdX(bm`@_BAtHIuIh_>h9L!k3po~aRgwfEDH zu!KaC1NDTCF=BiE8{(T7Rd~kOJ+gw5Yx-y5oW~PTpk%xQMD%dq1MeONv?`KBJREpF zrUU+iKV0(zs-P8FZ89|I^SX^fLaC((@xhW zdPxCKQI6i4c^$dD7>#i0V+B|V81L4F+N;Xi7PCX1%p$WT&Tgk4?zb{It#na=K;6r$9h?Sc;!e$kXcl&yX4ArB z}#i{nos_x2iS}FDpR6-D7 zhYtd}2?fDC6j=~>W>#2R6PVE0DO$R=`|8B^eIVXd^Jt6;Y%_mnZlVr~F+LE?jbJ d>AI#UXco|Et=7H0DS=We_axA`{uj0+Nb^++diwwX diff --git a/./.git/objects/a5/23b07b1031f7a7dbae559a82e58746a42a2f8f b/./.git/objects/a5/23b07b1031f7a7dbae559a82e58746a42a2f8f deleted file mode 100644 index 305891025ceef0e70e7169490d128852de208454..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6053 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 diff --git a/./.git/objects/a8/13ca61b052c8018d11eb567fc0657b00792518 b/./.git/objects/a8/13ca61b052c8018d11eb567fc0657b00792518 deleted file mode 100644 index d973129bc96ebe5dde5bb77c9b3d8a9378064c60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 197 zcmV;$06PD80ZYosPf{>6w`8bbWL&`z{yz!KHwE!?&pProD+oAT%zLe=R(QK(mevc# zs>W|j%sj0wM-S}y@NH>q`PXp$c>O?U^U~?*75OLJ7fFiTTUv5hC3k&}di6@R>wR9m zt7NxIwykQL&Mz4g!QmyyBjumqtaR9L?tvd{lR0MUyb%62S+h9!j@#Jf diff --git a/./.git/objects/a8/639fe68e6d8431c44e84baa3834e88eabe16c9 b/./.git/objects/a8/639fe68e6d8431c44e84baa3834e88eabe16c9 deleted file mode 100644 index 2d47277733083a7d5bb003fd09c7771a84b6a8e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4907 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 PSor?|vP=~44eQcxLpIkS diff --git a/./.git/objects/b4/2f35cef819793a338c9270d86db481627036b0 b/./.git/objects/b4/2f35cef819793a338c9270d86db481627036b0 deleted file mode 100644 index dbc529540e886c977e12f1472306aa9adaf78de3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2206 zcmV;P2x0el0mWHgZyU!E@AG|%#Th}407KJ(`UgZ<)Uf5&fe{&!-U#AwBA4RTr#trU zm{d^+*h%cFu5HPQ62-QR)JWV4Xd((}YdMnBd>P+SddgSG%k z+=WgM#^jGt;OBpP!Th}I&l{~USRhR&rm?#~NhX*-^SAM02PE@%=iNqJB$wO>{ypn2 za*>wjL|1Z&3;0kZA^lUAq7*Lcwq!}W5rk1Ler{@dhHEJ`0{?9qMo#PozVhIBw!;v~ z3_K?ZIvxXSsG{WxV?hp{4C53zxPE)^-B!B(IQf3v$TnY^b0W9FWh{fh&!cZsubRKU z_|}D~B54KTf)iKGS1c!LAWDUZyhdKJd`JArrb*8WG>X99LSz~RwitM{N^-UHG`#oC~Ct;h>m)_4r?y;48!;x7+ea2ZcHunbelR}+%}73fyV8i8C7fM?RSScMAn48}2fD7p4H+1*LMxSD?cc<`_O;Qen6Ha<;b$f1Y}qu`q} zQ*Tws5;3FLiMx?mA!cv|Ry5ItFt*Uap^Gz9Rmkw2&XHdxx-mwZLHwt(dSp~3wL6574)M+l7d_6#0#mMQnryFf2R#3V;H6vic zmm}~nN2$QDG~{@&oiXdhU-)EZ0gE<4Fr~{o>67L3THh?HVzTFLw~69BGRVdAy6jKE zEHp!KpbgVB>I8m7k8CK*dXjv68*)WoJnBMzMMxv)_?iicwZFA>2Hns9eBG>TdmDyH z)NM2%f#0KH7=-V=n-WxjZzf8`03tCW7^7T1_lc7nW;aW|Fu`%hcZCvcMEh*NaVQ z@5rz;EV+Iwuv%tqNt@NH6FItp9c0UTvL>YCj6oIdL%i?@C~L}deC1(@j&PFlswO`{ zG9gjc>O;^@k#uJ-x%qe5;$(R{<6u;WKUqmMqOt^>Oa11Mv~bFj034t2XNG}CF_{q# zEyUS-SS5yj1Vm)O)LdYKa?&3{fr)FIgj$eEH#E^#&&;F*(c`qe>W~ zmO5d8iJOO-x^2aB1vdb{s0@q(Db`d>*WXeTC<>_4cP#EA$U)`-lj zjNE}%&q3Qkc!$BO1Nf2lfRRBBRLNFQoejIRAY7!mPA)GkQygxDD(ECFT0nhc3(GID zu#7fJZODosTs-}T2H9^x&)6OlOJ_C-+g;Jn0`L_AHfU|<{+*p<BdQY--UaR%6&HPdJiyA&7Op=qOSn-A6?+3ym1n z+z7g?VtUlKa@87Tj8wkkC1Mtn>`*4s2Qu-9sVAnKWX7GJyfisGnIG&L9(BUwq+R;- z`oX;~Pb%$XhRKN;vPTOrHGH+xp%S)ZnnGx}Wpxr)VE8qur@kKP;D&*%*4AX_n^A0l zh~>o~E|s2EOAYw|d+6?UomsXWFIY2c|4+dx)$Ze!2x;Om$^<)Ya@U@PiyO>GUB-b)2{#>C9+f>sY_V#zM_Vf}dRPS$50(%Wze{d$NSnw?k%H=m?e;kFQ& zXgWVjv1>Eo7qzvP8Zv%!rItN~II*H~-cCQ+KKRRuCfe;_kAh*i|NZLzqg(v?O*_<> z*p)#)ieiJmE~gt`B`Z7p8W3$(qg7+KpJ@wNz2y*zv=XkgT1P$c%KJJ~DwoRAzWJsg z3VDyqn(nsaH$Av6o+wZ73sSh?v0KnzG%fai*sJn$GTgpQ5FUs5wW)@V+=QFFmw*2B zb^>?38=Eicyg}($tDqz2R=LoI;%5w-pA^QM2GW{o+VLW}asA+f&CzoKQ0S}BGm7>?tF#J1%U@+ z7aT95Ltn56gxmS#=H|$U91LHzGVnDAdv}wq`-A%*K7YEGZtNx-+sVVLB9yrSLI(T> zUw_2g%$W4EezN)x{#5|(Q1<(uq??bDXDduD4+pK8;6}daE0pjBKa->F>iofjyUFTT z+*s&Aa4Wsa5?Y)4OO=<+0(oY*=v3Heg09b;P(@Wm27DFOWr$bVj3SBKNJF%l9XR1T gSBvDIR7e$kXWI*o>)W=;53`bsnTBlMkI@-5#%z>VJOBUy diff --git a/./.git/objects/b5/d87cbad56c43ba21c0238fcb3560ad4b84bb66 b/./.git/objects/b5/d87cbad56c43ba21c0238fcb3560ad4b84bb66 deleted file mode 100644 index 2f99e45bfa0ac5b5aff91bbb946798943b209bd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4882 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 #,T T9XJͲba-A5p9S:͓ _i'q 6-$g!N†!;f~RP+5 eq$-እf')Zz®~?y\OMi5d9f +\(5m~2lp \ No newline at end of file diff --git a/./.git/objects/b8/97c0ab709ed44630b6d38107a423b4f9960b85 b/./.git/objects/b8/97c0ab709ed44630b6d38107a423b4f9960b85 deleted file mode 100644 index 92237506228b1e76b7c8d23d34cf33fd614564a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33 pcmbIx?>Xn5 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 diff --git a/./.git/objects/bc/1a1f6167d09c909aad37280b760bb715d0f1da b/./.git/objects/bc/1a1f6167d09c909aad37280b760bb715d0f1da deleted file mode 100644 index 160fa0cf472dbf06c9ed66353b666275cc45223e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53 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 diff --git a/./.git/objects/c7/44684ab56c48a223709a9a4d362bbd1276bfbd b/./.git/objects/c7/44684ab56c48a223709a9a4d362bbd1276bfbd deleted file mode 100644 index 5f4342cd55922560ca2474c5df3e415fb499f11d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9196 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 diff --git a/./.git/objects/ca/dcb7a5f850751001ff630205dd0e265df9d474 b/./.git/objects/ca/dcb7a5f850751001ff630205dd0e265df9d474 deleted file mode 100644 index 989a424defb40d5f7b1cb0ede6a2d255869e7fa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4538 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 diff --git a/./.git/objects/ce/ac0a44aa05affeec11750b384a379848ff9018 b/./.git/objects/ce/ac0a44aa05affeec11750b384a379848ff9018 deleted file mode 100644 index 336a4c20f1a66425491ee33393347de1e3502473..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrg0ez4=j}tKzg*_9`Ba>v=2I`ThrpYYD7D$MP(jW>Ju}kCT%89M4gCEE< zvyik%)JO;+N{}dM5Cs*2KjIcCEPsItI|+{pOaAODU!CK7rqWEy!^5k{j7A0W>&2H7 z@g_p(TPXfru`h1=-Q4zIh#(e`h{YskJ%(jZVS-gc5|xrvWh7HM$yJ~9Bjn!`S*QUS zs393fXpUI=HX+v;ERXD*YO&(BJmFOR@Lxt%>XJ>j%O*xCs+miU9||d5u{kEERA@KY z-0$AkR4l6&#U-_U-r3847?1kVN@bgCf zpz%KSW1-oyzGuyX&)TLoyP)fZtry!Q!Te6L1`t}b0OLnXmtRwcb&_8X$u>g|j4;Mg N8Sh8K_!pzcpau*P(K!GB diff --git a/./.git/objects/cf/6d3bb9a24a552399b5c3c208cc9933cdeb4d23 b/./.git/objects/cf/6d3bb9a24a552399b5c3c208cc9933cdeb4d23 deleted file mode 100644 index 92b17aff60da6327117fbb5a4617e05fc242033a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2327 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 diff --git a/./.git/objects/d0/1c6e22b8d48e13b209127c8224352b18973c4e b/./.git/objects/d0/1c6e22b8d48e13b209127c8224352b18973c4e deleted file mode 100644 index fe2c9982d750a91574c7ad43cb6ddb4f4af886c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3198 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 diff --git a/./.git/objects/d3/46add1fddfc0997c9b66d7b0a258782d29ca1c b/./.git/objects/d3/46add1fddfc0997c9b66d7b0a258782d29ca1c deleted file mode 100644 index 12bff682c84ae62214fc3d26e681cd09ffce2edc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 438 zcmV;n0ZION0ZYosPf{>7HD*}O%ge<81oovNnYV%TV-N=hSfGs0T|mZkh7^V|v#E{CE#R}EW0OT<*19Dcw1Q?bv z0(n3T0a0u~6--&|Fn%gq0ni3iHH-`?Ou=BR$yVwDRLS+Ur}g=)mM1%QKW$z5w14sQ zrkz|*TN_`j+wrnt){Fh^3I&xV8Tok%y15Ej`K3j9i8=B4Md^uonN_Jp3NQ9gc(JgD z>m|rWzn36~Xfobn$xY16yTuV752EAaH92mv$H%ASC&$O%Voxlt%uCM9zr|c!SzH7P z7?8TW#N1S%By&+|-Yw45ip-MuTPz^vN`}uM!+&Y%XXNLm>X#;!=9QG{W4J^g zW}99?6Ym2uM$0pTa%EWroX4w>fSX1(dG{C^vZCVBu+SZt>`d g?1-D8c#%cs3X9AI82Z2p)CbmF#0PW(0L1#976!=FHUIzs diff --git a/./.git/objects/d4/054f300638131c731b04ec78da6249b8f2b370 b/./.git/objects/d4/054f300638131c731b04ec78da6249b8f2b370 deleted file mode 100644 index 16befbda6abb65a64f57a4301ea53f9647c72764..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4064 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 SSM}@Kk{EPEz41TpJHPK+{{WHz diff --git a/./.git/objects/d5/1e6373fb2af32f4a1c13b95f0f7e8a5abe6327 b/./.git/objects/d5/1e6373fb2af32f4a1c13b95f0f7e8a5abe6327 deleted file mode 100644 index 54f8bbf88c839b27acd7c4712e74ba362bc571b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1437 zcmV;O1!DSm0gYC{ZW}ic?O9(jun$cfuVl$dn;J*}#a0|4j%}^B&pV2p@v|0m&8=nO4a5(eky*C`?W<;IGkK3PY(@-t@q?Y613I^AF6@#DYo=Dr=Soj0 z7kXNXsSNSLEp?_0$9DPW%o=SEDxn+&I**@dIAl5-IW<;7(}~GMPHAs2Tqo-z0l?u3 z>00?2Rj~_E=02ePQZDI877}>@7lVCydD#%_@}54~Yjz$r_XvL&O;zg(jE0C9J$#(;8fbu#TJKI+zb#o6VG$nJ>YMbK!wH281t6 zl{s)7(*qh@sXSkQ4Y;hnxiCI8oh7+k$hU` zGsnCpr4BO()}>b71wOb!W@>`3Ubrhq_tIYXXqUcB`@`NYbx*q|Z@$8hVcI<%JU>fc z?Na}A*gHu_x z(TWu4Y)}@3u>h&2GI=AjGH}vNHjvk@S&g>FiR8$th14LbQ&Ww+Oq*l`n1W1dm|U)|5A54iYz6Z|nc@ZzSlA!HL*F{2W|iY0BtmS8^GdRR}+GYXNRk zTI#?*KZ8#dMXs^{OK5bP7Hvp6p(9kAF^7XB!|7GKXLc&I`dX4VW3l|y(OX;yQ-dc2 zOHbzO|6G_=sD&=$A?~|MP{|AzeN> zD90H-dn`T8WM16X{VK(sHHA}EUn_fqi!C|6S{5!((~5s6}&}7UQU(9z^X%yP7@5$ z{LTnFpP)JZzKX2myx)Y9B9<}kb$UYgunBl^9}vGw+(D}alNR~RIDgRjyxrNq-+0)B z5M5yPU~obp6tAV|Vr=c<0F0+Hr`S_%u~%K4jXY>Wo0kgvKFZ}57;^a^j(R|s-P7TV z^z6KUc!Btfm%TTaja$ty?Q(9pKF&>NS$JY3`^lrk8Lg#HAl--Uy-w#omr6Zr(Z&ln zan7q|^Bzd3w&E-t*J`>98!wd(pk0y~y@f-$_!M$whCS+96PN%$EMUC8)3Snb{Pw10ZMOT9EbOC$C8^f6w9a#FIl^U|fL2BW?vv^?t18#_M+g;Kzl*@^}9NAC288_aM97 diff --git a/./.git/objects/d5/b059c8779ab6e04275c326b76134c7cd45cb7f b/./.git/objects/d5/b059c8779ab6e04275c326b76134c7cd45cb7f deleted file mode 100644 index a130f1e0da8f6db2e63bd93d44e0e7e74aae476c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7986 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 diff --git a/./.git/objects/db/783106501f9ccd99a3f7f92c0483059ad52ec3 b/./.git/objects/db/783106501f9ccd99a3f7f92c0483059ad52ec3 deleted file mode 100644 index 4c264ff720c8077e76cd155e50efae7a68ef27ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7538 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 I-_m)+QIMF8od5s; diff --git a/./.git/objects/db/c529540e886c977e12f1472306aa9adaf78de3 b/./.git/objects/db/c529540e886c977e12f1472306aa9adaf78de3 deleted file mode 100644 index eb7480d114579c347039dc82664f171f46687308..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2227 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 diff --git a/./.git/objects/e1/f4b83ab8e4590c165b84062197c9f12f808155 b/./.git/objects/e1/f4b83ab8e4590c165b84062197c9f12f808155 deleted file mode 100644 index 6f19f699c41302de6ef6ae18f99712ecc2ad8448..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12065 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 Lqj&vZA!0ZJ+4C=8 diff --git a/./.git/objects/e2/7cd7ab186dc0322923f7d7e9d31059aba3df4d b/./.git/objects/e2/7cd7ab186dc0322923f7d7e9d31059aba3df4d deleted file mode 100644 index 1605b976404982b0f1551ce1c9cb04c1fbe26d8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43 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 IAHF&VM5zCoHvj+t diff --git a/./.git/objects/e6/bbc0c8f80d31e787e972b5b98dea65ef56c5e6 b/./.git/objects/e6/bbc0c8f80d31e787e972b5b98dea65ef56c5e6 deleted file mode 100644 index 5f2d76df4f6a1f5a066335a1f21496ed21c14080..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2750 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 EJCn9)4gdfE diff --git a/./.git/objects/e8/66b589d11555e729c1505db86028cd201f780d b/./.git/objects/e8/66b589d11555e729c1505db86028cd201f780d deleted file mode 100644 index aa9a16db4cbff854091030a3db69e074f06dcdbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3360 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 diff --git a/./.git/objects/e8/b91fbe25d966aa316594e17ff846ce37e2cf80 b/./.git/objects/e8/b91fbe25d966aa316594e17ff846ce37e2cf80 deleted file mode 100644 index ce8333fc3cd0e5180a9ae004f51b02fb4e42d37c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2977 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 Xk-$?ki5udKNk2B(y1n-wyMCLEd%w58 diff --git a/./.git/objects/ee/d008e28a5cfc0c5f830c2ccf638ccf3fed18a0 b/./.git/objects/ee/d008e28a5cfc0c5f830c2ccf638ccf3fed18a0 deleted file mode 100644 index 07cd8c956ab4ecf04ab6ff2486d36bce00970e19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188 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 yu?^*kR+6Hp1*JK?*9YH(_F8uGoAkc diff --git a/./.git/objects/f4/6589e7d7cf999185328ed5895be23e47d93d93 b/./.git/objects/f4/6589e7d7cf999185328ed5895be23e47d93d93 deleted file mode 100644 index eb5280b90a2561ef6e4ab9840484ad51dcaa05ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 279 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 diff --git a/./.git/objects/f5/7492a1b3fea6c90071afeb5a1eb9af044474ce b/./.git/objects/f5/7492a1b3fea6c90071afeb5a1eb9af044474ce deleted file mode 100644 index e866b589d11555e729c1505db86028cd201f780d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3339 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 V>*8qK%RG+W@?-U%{{<0#LIwkYX%GMa diff --git a/./.git/objects/f9/5bbc13a0263a974d42d01b4f86a8f598523a2f b/./.git/objects/f9/5bbc13a0263a974d42d01b4f86a8f598523a2f deleted file mode 100644 index f68614352699238f3c73a18bfed33a8d3e6a146c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4153 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 Dw)VxM diff --git a/./.git/objects/fc/9e876a2b06e5604e0c7a5943e121faa84d9fe9 b/./.git/objects/fc/9e876a2b06e5604e0c7a5943e121faa84d9fe9 deleted file mode 100644 index 2d592941081b725f398b85952bf8c8bcbc0738f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmV<403rW)0RhthEdOF`Z(<-aFfssm0gcW*ZUQk71>n?kiYL=X5mKrkQALTM$^oLt zvNwjc;`OY?L;fy8MM0N~CFj7vDo9kgaPc&H-}8w*ZpU|5#u&Qiu5b%7 diff --git a/./.git/objects/fc/e15ad7eaa74e5682b644c84efb75334c112f95 b/./.git/objects/fc/e15ad7eaa74e5682b644c84efb75334c112f95 deleted file mode 100644 index 1a50372ba7ed5fb7b7a7622adf0b101cb7c80ec0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159 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 diff --git a/./.hypothesis/unicode_data/13.0.0/codec-utf-8.json.gz b/./.hypothesis/unicode_data/13.0.0/codec-utf-8.json.gz deleted file mode 100644 index 7551238f8b5907f092f4c5a0466046b7c4bb3b29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60 zcmb2|=HOstU|?YSUy@s3VUlfBlAfN<(A{TtA<)3obdyQ%)xZujQ*$%Ji-86vCMJxJ PZbUHDMd`1Q1L^?)FL)Bd diff --git a/./.kiro/specs/claude-api-configuration/design.md b/./.kiro/specs/claude-api-configuration/design.md deleted file mode 100644 index 79c0248..0000000 --- a/./.kiro/specs/claude-api-configuration/design.md +++ /dev/null @@ -1,387 +0,0 @@ -# Design Document - -## Overview - -This design enhances the Claude API configuration system in the Obsidian journal organizer to support custom API URLs and comprehensive model selection. The enhancement maintains backward compatibility while providing flexibility for users to connect to different Claude API endpoints, including proxy servers, regional endpoints, and custom deployments. The design follows the existing configuration patterns and integrates seamlessly with the current Command + Skill architecture. - -## Architecture - -The configuration enhancement will extend the existing YAML-based configuration system with: - -1. **Flexible API URL Configuration**: Support for custom Claude API endpoints -2. **Enhanced Model Validation**: Comprehensive model name validation and suggestions -3. **Environment Variable Integration**: Full support for environment-based configuration -4. **Backward Compatibility Layer**: Seamless migration from existing configurations -5. **Configuration Validation Framework**: Robust validation with helpful error messages - -## Components and Interfaces - -### Enhanced Claude Configuration Schema - -```python -from typing import Optional, Dict, Any, List -from pydantic import BaseModel, validator, Field -import re -from urllib.parse import urlparse - -class ClaudeAPIConfig(BaseModel): - """Enhanced Claude API configuration with URL and model validation""" - - api_key: str = Field(..., min_length=1, description="Claude API key") - api_url: Optional[str] = Field( - default="https://api.anthropic.com", - description="Claude API base URL" - ) - model: str = Field( - default="claude-3-5-sonnet-20241022", - description="Claude model name" - ) - max_tokens: int = Field(default=4096, ge=1, le=200000) - temperature: float = Field(default=0.7, ge=0.0, le=1.0) - - @validator('api_url') - def validate_api_url(cls, v: Optional[str]) -> str: - """Validate API URL format and accessibility""" - if v is None: - return "https://api.anthropic.com" - - # Parse URL to validate format - parsed = urlparse(v) - if not parsed.scheme or not parsed.netloc: - raise ValueError(f"Invalid URL format: {v}") - - if parsed.scheme not in ['http', 'https']: - raise ValueError(f"URL must use http or https protocol: {v}") - - # Remove trailing slash for consistency - return v.rstrip('/') - - @validator('model') - def validate_model_name(cls, v: str) -> str: - """Validate Claude model name and provide suggestions""" - valid_models = [ - # Claude 3 series - "claude-3-opus-20240229", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307", - # Claude 3.5 series - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - # Latest aliases - "claude-3-opus-latest", - "claude-3-sonnet-latest", - "claude-3-haiku-latest", - "claude-3-5-sonnet-latest", - "claude-3-5-haiku-latest" - ] - - if v not in valid_models: - # Check if it follows Claude naming pattern - claude_pattern = r'^claude-\d+(\.\d+)?-(opus|sonnet|haiku)(-\d{8}|-latest)?$' - if not re.match(claude_pattern, v, re.IGNORECASE): - suggestions = ", ".join(valid_models[:5]) - raise ValueError( - f"Invalid model name: {v}. " - f"Valid models include: {suggestions}. " - f"Model names should follow pattern: claude-X-Y-YYYYMMDD or claude-X-Y-latest" - ) - - return v -``` - -### Configuration Loading and Environment Variable Support - -```python -import os -import re -from typing import Any, Dict -import yaml - -class ConfigurationLoader: - """Enhanced configuration loader with environment variable expansion""" - - @staticmethod - def expand_environment_variables(config_dict: Dict[str, Any]) -> Dict[str, Any]: - """Recursively expand environment variables in configuration""" - - def expand_value(value: Any) -> Any: - if isinstance(value, str): - # Handle ${VAR} and ${VAR:-default} patterns - pattern = r'\$\{([^}]+)\}' - - def replace_env_var(match): - var_expr = match.group(1) - if ':-' in var_expr: - var_name, default_value = var_expr.split(':-', 1) - return os.getenv(var_name.strip(), default_value) - else: - var_name = var_expr.strip() - env_value = os.getenv(var_name) - if env_value is None: - raise ValueError( - f"Environment variable '{var_name}' is not set. " - f"Please set it or provide a default value using ${{{var_name}:-default}}" - ) - return env_value - - return re.sub(pattern, replace_env_var, value) - - elif isinstance(value, dict): - return {k: expand_value(v) for k, v in value.items()} - elif isinstance(value, list): - return [expand_value(item) for item in value] - else: - return value - - return expand_value(config_dict) - - @classmethod - def load_config(cls, config_path: str) -> Dict[str, Any]: - """Load and validate configuration with environment variable expansion""" - try: - with open(config_path, 'r', encoding='utf-8') as f: - config_dict = yaml.safe_load(f) - - # Expand environment variables - config_dict = cls.expand_environment_variables(config_dict) - - return config_dict - - except FileNotFoundError: - raise ConfigurationError(f"Configuration file not found: {config_path}") - except yaml.YAMLError as e: - raise ConfigurationError(f"Invalid YAML in configuration file: {e}") - except ValueError as e: - raise ConfigurationError(f"Environment variable error: {e}") -``` - -### Enhanced Claude API Client - -```python -import aiohttp -import ssl -from typing import Optional, Dict, Any -from contextlib import asynccontextmanager - -class ClaudeAPIClient: - """Enhanced Claude API client with configurable endpoint support""" - - def __init__(self, config: ClaudeAPIConfig): - self.config = config - self.base_url = config.api_url - self.api_key = config.api_key - self.model = config.model - - @asynccontextmanager - async def create_session(self): - """Create HTTP session with proper configuration""" - headers = { - 'Authorization': f'Bearer {self.api_key}', - 'Content-Type': 'application/json', - 'User-Agent': 'journal-organizer/1.0' - } - - # Configure SSL context for custom endpoints - ssl_context = ssl.create_default_context() - if self.base_url.startswith('https://localhost') or 'localhost' in self.base_url: - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - - connector = aiohttp.TCPConnector(ssl=ssl_context) - - async with aiohttp.ClientSession( - connector=connector, - headers=headers, - timeout=aiohttp.ClientTimeout(total=60) - ) as session: - yield session - - async def validate_connection(self) -> bool: - """Validate API connection and credentials""" - try: - async with self.create_session() as session: - # Try a simple API call to validate connection - url = f"{self.base_url}/v1/messages" - payload = { - "model": self.model, - "max_tokens": 10, - "messages": [{"role": "user", "content": "test"}] - } - - async with session.post(url, json=payload) as response: - if response.status == 401: - raise APIError("Invalid API key or unauthorized access") - elif response.status == 404: - raise APIError(f"API endpoint not found. Check if {self.base_url} is correct") - elif response.status >= 400: - error_text = await response.text() - raise APIError(f"API error ({response.status}): {error_text}") - - return True - - except aiohttp.ClientError as e: - raise APIError(f"Connection error to {self.base_url}: {str(e)}") -``` - -## Data Models - -### Configuration Migration Support - -```python -class ConfigurationMigrator: - """Handle migration from legacy configuration formats""" - - @staticmethod - def migrate_claude_config(config_dict: Dict[str, Any]) -> Dict[str, Any]: - """Migrate legacy Claude configuration to new format""" - claude_config = config_dict.get('claude', {}) - - # Add default api_url if not present - if 'api_url' not in claude_config: - claude_config['api_url'] = "https://api.anthropic.com" - - # Ensure model has a default value - if 'model' not in claude_config: - claude_config['model'] = "claude-3-5-sonnet-20241022" - - # Migrate old model names to new format if needed - model_migrations = { - "claude-3-sonnet": "claude-3-sonnet-20240229", - "claude-3-opus": "claude-3-opus-20240229", - "claude-3-haiku": "claude-3-haiku-20240307" - } - - if claude_config['model'] in model_migrations: - old_model = claude_config['model'] - new_model = model_migrations[old_model] - claude_config['model'] = new_model - print(f"INFO: Migrated model '{old_model}' to '{new_model}'") - - config_dict['claude'] = claude_config - return config_dict -``` - -## Error Handling - -### Comprehensive Error Management - -```python -class ClaudeConfigurationError(ConfigurationError): - """Specific error for Claude API configuration issues""" - pass - -class ModelValidationError(ClaudeConfigurationError): - """Error for invalid model names with suggestions""" - - def __init__(self, message: str, suggestions: List[str] = None): - super().__init__(message) - self.suggestions = suggestions or [] - -class APIConnectionError(ClaudeConfigurationError): - """Error for API connection issues""" - pass - -def validate_claude_configuration(config: Dict[str, Any]) -> ClaudeAPIConfig: - """Validate Claude configuration with helpful error messages""" - try: - claude_config = config.get('claude', {}) - return ClaudeAPIConfig(**claude_config) - - except ValidationError as e: - # Transform pydantic errors into user-friendly messages - error_messages = [] - for error in e.errors(): - field = error['loc'][0] if error['loc'] else 'configuration' - message = error['msg'] - - if field == 'api_url': - error_messages.append( - f"Invalid API URL: {message}. " - f"Please provide a valid HTTP/HTTPS URL like 'https://api.anthropic.com'" - ) - elif field == 'model': - error_messages.append( - f"Invalid model name: {message}" - ) - else: - error_messages.append(f"Invalid {field}: {message}") - - raise ClaudeConfigurationError( - f"Claude configuration errors:\n" + "\n".join(f"- {msg}" for msg in error_messages) - ) -``` - -## Testing Strategy - -### Dual Testing Approach - -The testing strategy combines unit tests for specific functionality with property-based tests for comprehensive validation: - -**Unit Tests:** -- Test configuration loading with various URL formats -- Test model name validation with valid and invalid inputs -- Test environment variable expansion with different patterns -- Test backward compatibility with legacy configurations -- Mock API calls to test connection validation - -**Property-Based Tests:** -- Validate that all valid URL formats are accepted -- Test that model name validation works across all valid model patterns -- Verify environment variable expansion handles all supported formats -- Ensure configuration migration preserves all existing functionality - -**Testing Framework:** -- Use `pytest` for unit testing framework -- Use `hypothesis` for property-based testing -- Configure tests to run minimum 100 iterations per property test -- Tag each property test with: **Feature: claude-api-configuration, Property {number}: {property_text}** - -Now I need to use the prework tool to analyze the acceptance criteria before writing the correctness properties: - -## Correctness Properties - -*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* - -### Property 1: Configuration Field Support -*For any* configuration dictionary containing claude section fields (api_url, model), the system should successfully parse and store these values in the configuration object -**Validates: Requirements 1.1, 2.1** - -### Property 2: URL Validation and Error Handling -*For any* URL string provided as api_url, the system should validate proper URL format, reject malformed URLs with specific error messages, and accept valid HTTP/HTTPS URLs -**Validates: Requirements 1.4, 1.5, 4.1** - -### Property 3: Model Name Validation with Suggestions -*For any* model name string, the system should validate Claude naming conventions, accept all supported model variants, and provide helpful suggestions when invalid model names are provided -**Validates: Requirements 2.2, 2.3, 2.4, 4.2** - -### Property 4: Default Value Behavior -*For any* configuration missing api_url or model fields, the system should use appropriate default values (default Anthropic API URL and sensible default model) without errors -**Validates: Requirements 1.2, 2.5** - -### Property 5: Custom Configuration Usage -*For any* valid custom api_url or model specified in configuration, the system should use these values in API client initialization and requests -**Validates: Requirements 1.3** - -### Property 6: Environment Variable Expansion -*For any* configuration value containing environment variable references, the system should correctly expand variables for both api_url and model fields, and validate expanded values the same as direct configuration -**Validates: Requirements 5.1, 5.2, 5.4, 5.5** - -### Property 7: Environment Variable Error Handling -*For any* undefined environment variable referenced in configuration, the system should provide clear error messages indicating which variables are missing -**Validates: Requirements 5.3** - -### Property 8: Backward Compatibility -*For any* legacy configuration format, the system should load successfully, maintain existing functionality, and provide informational messages about new options without breaking changes -**Validates: Requirements 3.1, 3.2, 3.3, 3.4** - -### Property 9: API Connection Validation -*For any* API configuration (URL and credentials), the system should validate connectivity when possible and provide clear authentication error messages for invalid credentials -**Validates: Requirements 4.3, 4.4** - -### Property 10: Comprehensive Error Messaging -*For any* configuration validation failure, the system should provide helpful error messages with corrective suggestions, appropriate logging, and references to documentation where applicable -**Validates: Requirements 4.5, 6.3, 6.4** - -### Property-Based Testing Configuration - -Each property test will be tagged with comments referencing the design document property and run with sufficient iterations to catch edge cases through randomization. Tests will use the `hypothesis` library for property-based testing with minimum 100 iterations per test. \ No newline at end of file diff --git a/./.kiro/specs/claude-api-configuration/requirements.md b/./.kiro/specs/claude-api-configuration/requirements.md deleted file mode 100644 index 1ae21a5..0000000 --- a/./.kiro/specs/claude-api-configuration/requirements.md +++ /dev/null @@ -1,87 +0,0 @@ -# Requirements Document - -## Introduction - -This specification addresses the need to enhance Claude API configuration flexibility in the Obsidian journal organizer project. Currently, the system has limited Claude API configuration options, lacking support for custom API URLs and comprehensive model selection. This enhancement will provide users with greater flexibility to use different Claude API endpoints and models based on their specific needs and deployment scenarios. - -## Glossary - -- **System**: The Obsidian journal organizer application -- **Claude_API**: Anthropic's Claude AI service API -- **API_URL**: The base URL endpoint for Claude API requests -- **Model_Name**: The specific Claude model identifier (e.g., claude-3-5-sonnet-20241022) -- **Configuration**: YAML-based settings that control system behavior -- **API_Client**: The component responsible for making requests to Claude API - -## Requirements - -### Requirement 1: Add Claude API URL Configuration - -**User Story:** As a user, I want to configure a custom Claude API URL, so that I can use different Claude API endpoints including proxy servers, regional endpoints, or custom deployments. - -#### Acceptance Criteria - -1. THE Configuration SHALL support a configurable api_url field in the claude section -2. WHEN api_url is not specified, THE System SHALL use the default Anthropic API URL -3. WHEN api_url is specified, THE System SHALL use the custom URL for all Claude API requests -4. THE System SHALL validate that the api_url follows proper URL format -5. WHEN the api_url is invalid, THE System SHALL provide clear error messages during configuration validation - -### Requirement 2: Enhance Model Name Configuration - -**User Story:** As a user, I want flexible model name configuration with validation, so that I can easily switch between different Claude models and get clear feedback when using unsupported models. - -#### Acceptance Criteria - -1. THE Configuration SHALL support model name specification in the claude section -2. THE System SHALL validate that the specified model name follows Claude model naming conventions -3. WHEN an invalid model name is provided, THE System SHALL provide helpful suggestions for valid model names -4. THE System SHALL support all current Claude model variants (claude-3-5-sonnet, claude-3-haiku, claude-3-opus) -5. WHEN model configuration is missing, THE System SHALL use a sensible default model - -### Requirement 3: Maintain Backward Compatibility - -**User Story:** As an existing user, I want my current configuration to continue working, so that I don't need to modify my setup when upgrading. - -#### Acceptance Criteria - -1. WHEN existing configuration files lack api_url, THE System SHALL use default values without errors -2. WHEN existing configuration files use the current model format, THE System SHALL continue to work unchanged -3. THE System SHALL not break existing functionality when new configuration options are added -4. WHEN loading legacy configuration, THE System SHALL provide informational messages about new available options - -### Requirement 4: Configuration Validation and Error Handling - -**User Story:** As a user, I want clear validation and error messages for Claude API configuration, so that I can quickly identify and fix configuration issues. - -#### Acceptance Criteria - -1. WHEN the api_url is malformed, THE System SHALL provide specific error messages indicating the URL format issue -2. WHEN the model name is invalid, THE System SHALL suggest valid alternatives -3. THE System SHALL validate API connectivity during startup when possible -4. WHEN API credentials are invalid for the specified endpoint, THE System SHALL provide clear authentication error messages -5. THE System SHALL log configuration validation results at appropriate levels - -### Requirement 5: Environment Variable Support - -**User Story:** As a developer, I want to use environment variables for Claude API configuration, so that I can manage different environments and keep sensitive configuration out of files. - -#### Acceptance Criteria - -1. THE System SHALL support environment variable expansion for api_url configuration -2. THE System SHALL support environment variable expansion for model name configuration -3. WHEN environment variables are undefined, THE System SHALL provide clear error messages -4. THE System SHALL support mixed configuration (some values from files, some from environment) -5. WHEN using environment variables, THE System SHALL validate expanded values the same as direct configuration - -### Requirement 6: Documentation and Examples - -**User Story:** As a user, I want clear documentation and examples for Claude API configuration, so that I can understand how to use the new configuration options. - -#### Acceptance Criteria - -1. THE Configuration example file SHALL include api_url configuration with comments -2. THE Configuration example file SHALL include model name options with descriptions -3. THE System SHALL provide helpful error messages that reference documentation -4. WHEN configuration validation fails, THE System SHALL suggest corrective actions -5. THE Documentation SHALL include examples for common use cases (proxy servers, different regions) \ No newline at end of file diff --git a/./.kiro/specs/claude-api-configuration/tasks.md b/./.kiro/specs/claude-api-configuration/tasks.md deleted file mode 100644 index 0a04893..0000000 --- a/./.kiro/specs/claude-api-configuration/tasks.md +++ /dev/null @@ -1,169 +0,0 @@ -# Implementation Plan: Claude API Configuration - -## Overview - -This implementation plan enhances the Claude API configuration system to support custom API URLs and comprehensive model selection. The approach maintains backward compatibility while adding flexible configuration options through systematic enhancement of the existing configuration framework. - -## Tasks - -- [-] 1. Enhance Configuration Schema and Validation - - [x] 1.1 Create enhanced ClaudeAPIConfig model with pydantic - - Add api_url field with URL validation - - Add comprehensive model name validation with suggestions - - Implement field validators for URL format and model naming conventions - - _Requirements: 1.1, 1.4, 2.1, 2.2_ - - - [ ]* 1.2 Write property test for configuration field support - - **Property 1: Configuration Field Support** - - **Validates: Requirements 1.1, 2.1** - - - [ ]* 1.3 Write property test for URL validation - - **Property 2: URL Validation and Error Handling** - - **Validates: Requirements 1.4, 1.5, 4.1** - - - [ ]* 1.4 Write property test for model validation - - **Property 3: Model Name Validation with Suggestions** - - **Validates: Requirements 2.2, 2.3, 2.4, 4.2** - -- [x] 2. Implement Environment Variable Support - - [x] 2.1 Create ConfigurationLoader with environment variable expansion - - Implement ${VAR} and ${VAR:-default} pattern support - - Add recursive expansion for nested configuration values - - Handle missing environment variables with clear error messages - - _Requirements: 5.1, 5.2, 5.3_ - - - [x] 2.2 Integrate environment variable expansion into config loading - - Update existing configuration loading to use new expansion system - - Ensure validation works on expanded values - - _Requirements: 5.4, 5.5_ - - - [ ]* 2.3 Write property test for environment variable expansion - - **Property 6: Environment Variable Expansion** - - **Validates: Requirements 5.1, 5.2, 5.4, 5.5** - - - [ ]* 2.4 Write property test for environment variable error handling - - **Property 7: Environment Variable Error Handling** - - **Validates: Requirements 5.3** - -- [-] 3. Implement Default Value Handling - - [x] 3.1 Add default value logic to configuration loading - - Set default api_url to "https://api.anthropic.com" - - Set default model to "claude-3-5-sonnet-20241022" - - Ensure defaults are applied when fields are missing - - _Requirements: 1.2, 2.5_ - - - [ ]* 3.2 Write property test for default value behavior - - **Property 4: Default Value Behavior** - - **Validates: Requirements 1.2, 2.5** - -- [x] 4. Enhance Claude API Client - - [x] 4.1 Update ClaudeAPIClient to use configurable URL - - Modify client initialization to accept custom api_url - - Update all API request methods to use configured base URL - - Add SSL context handling for localhost and custom endpoints - - _Requirements: 1.3_ - - - [x] 4.2 Add API connection validation - - Implement validate_connection method for testing API connectivity - - Add authentication error detection and clear error messages - - Handle different types of API errors (404, 401, etc.) - - _Requirements: 4.3, 4.4_ - - - [ ]* 4.3 Write property test for custom configuration usage - - **Property 5: Custom Configuration Usage** - - **Validates: Requirements 1.3** - - - [ ]* 4.4 Write property test for API connection validation - - **Property 9: API Connection Validation** - - **Validates: Requirements 4.3, 4.4** - -- [x] 5. Implement Backward Compatibility - - [x] 5.1 Create ConfigurationMigrator for legacy support - - Add migration logic for configurations missing new fields - - Implement model name migration for old format names - - Add informational logging for migration actions - - _Requirements: 3.1, 3.2, 3.4_ - - - [x] 5.2 Integrate migration into configuration loading process - - Apply migration before validation - - Ensure existing functionality remains unchanged - - _Requirements: 3.3_ - - - [ ]* 5.3 Write property test for backward compatibility - - **Property 8: Backward Compatibility** - - **Validates: Requirements 3.1, 3.2, 3.3, 3.4** - -- [x] 6. Enhance Error Handling and Messaging - - [x] 6.1 Implement comprehensive error handling framework - - Create ClaudeConfigurationError and related exception classes - - Add user-friendly error message transformation - - Include corrective suggestions in error messages - - _Requirements: 4.1, 4.2, 6.3, 6.4_ - - - [x] 6.2 Add configuration validation logging - - Implement appropriate logging levels for validation results - - Add debug logging for configuration loading steps - - _Requirements: 4.5_ - - - [ ]* 6.3 Write property test for error messaging - - **Property 10: Comprehensive Error Messaging** - - **Validates: Requirements 4.5, 6.3, 6.4** - -- [x] 7. Update Configuration Files and Documentation - - [x] 7.1 Update config.example.yaml with new options - - Add api_url configuration with comments and examples - - Add model name options with descriptions - - Include examples for common use cases (proxy servers, regions) - - _Requirements: 6.1, 6.2, 6.5_ - - - [x] 7.2 Update existing Skills to use enhanced configuration - - Modify ClaudeAnalyzeSkill and ClaudeTransformSkill to use new config - - Ensure all Claude API interactions use the enhanced client - - _Requirements: 1.3_ - -- [x] 8. Integration and Testing - - [x] 8.1 Create comprehensive unit tests - - Test configuration loading with various scenarios - - Test error handling for invalid configurations - - Test migration from legacy configurations - - _Requirements: All_ - - - [x] 8.2 Create integration tests - - Test Skills with different Claude API configurations - - Test environment variable scenarios - - Test backward compatibility with existing setups - - _Requirements: 3.1, 3.2, 5.4_ - -- [x] 9. Checkpoint - Ensure all tests pass - - Ensure all tests pass, ask the user if questions arise. - -- [ ] 10. Final Integration and Validation - - [ ] 10.1 Test with real API endpoints - - Validate with default Anthropic API - - Test with proxy server configurations - - Verify different model selections work correctly - - _Requirements: 1.3, 2.4_ - - - [ ] 10.2 Validate backward compatibility - - Test existing configuration files continue to work - - Verify no breaking changes to existing functionality - - Test migration messages are appropriate - - _Requirements: 3.1, 3.2, 3.3, 3.4_ - - - [ ] 10.3 Update documentation and troubleshooting guides - - Add configuration examples for common scenarios - - Update troubleshooting guide with new error messages - - Document environment variable usage patterns - - _Requirements: 6.3, 6.4, 6.5_ - -- [ ] 11. Final checkpoint - Ensure all tests pass - - Ensure all tests pass, ask the user if questions arise. - -## Notes - -- Tasks marked with `*` are optional and can be skipped for faster MVP -- Each task references specific requirements for traceability -- Checkpoints ensure incremental validation -- Property tests validate universal correctness properties -- Unit tests validate specific examples and edge cases -- Focus on maintaining backward compatibility throughout implementation \ No newline at end of file diff --git a/./.kiro/specs/code-quality-improvements/design.md b/./.kiro/specs/code-quality-improvements/design.md deleted file mode 100644 index 29b7a89..0000000 --- a/./.kiro/specs/code-quality-improvements/design.md +++ /dev/null @@ -1,253 +0,0 @@ -# Design Document - -## Overview - -This design addresses critical code quality issues in the Obsidian journal organizer project. The improvements focus on fixing syntax errors, adding comprehensive type hints, modernizing Python patterns, and establishing consistent error handling throughout the codebase. The design maintains backward compatibility while significantly improving maintainability and developer experience. - -## Architecture - -The existing Command + Skill architecture will be preserved, but enhanced with: - -1. **Type Safety Layer**: Comprehensive type hints using Python's typing module -2. **Error Handling Framework**: Consistent error patterns across all components -3. **Configuration Validation**: Robust validation and sanitization of user inputs -4. **Modern Python Patterns**: Updated code to follow current best practices - -## Components and Interfaces - -### Enhanced Agent Core - -The `agent_core.py` module will be updated with: - -```python -from typing import Dict, Any, List, Optional, Union, Protocol, TypeVar -from dataclasses import dataclass, field -from pathlib import Path -from abc import ABC, abstractmethod - -T = TypeVar('T') - -class SkillProtocol(Protocol): - async def execute(self, context: 'CommandContext', **kwargs: Any) -> 'SkillResult': - ... - -@dataclass -class SkillResult: - success: bool - data: Optional[Dict[str, Any]] = None - error: Optional[str] = None - message: str = "" - timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) - - def to_dict(self) -> Dict[str, Any]: - return asdict(self) -``` - -### Type-Safe Configuration Management - -```python -from typing import TypedDict, Optional -from pathlib import Path - -class ObsidianConfig(TypedDict): - vault_path: str - rest_api: Dict[str, Union[str, bool]] - -class ClaudeConfig(TypedDict): - api_key: str - model: str - max_tokens: int - -class SystemConfig(TypedDict): - obsidian: ObsidianConfig - claude: ClaudeConfig - output: Dict[str, str] -``` - -### Enhanced Error Handling - -```python -class JournalOrganizerError(Exception): - """Base exception for journal organizer errors""" - pass - -class ConfigurationError(JournalOrganizerError): - """Raised when configuration is invalid or missing""" - pass - -class APIError(JournalOrganizerError): - """Raised when external API calls fail""" - pass - -class ValidationError(JournalOrganizerError): - """Raised when input validation fails""" - pass -``` - -## Data Models - -### Enhanced SkillResult with Validation - -```python -@dataclass -class SkillResult: - success: bool - data: Optional[Dict[str, Any]] = None - error: Optional[str] = None - message: str = "" - timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) - - def __post_init__(self) -> None: - if not self.success and not self.error: - raise ValueError("Failed results must include error message") - if self.success and self.error: - raise ValueError("Successful results should not include error message") -``` - -### Configuration Validation Models - -```python -from pydantic import BaseModel, validator, Field -from typing import Optional - -class ObsidianRestAPIConfig(BaseModel): - url: str = Field(..., regex=r'^https?://') - api_key: str = Field(..., min_length=1) - verify_ssl: bool = False - -class ObsidianConfig(BaseModel): - vault_path: str = Field(..., min_length=1) - rest_api: ObsidianRestAPIConfig - - @validator('vault_path') - def validate_vault_path(cls, v: str) -> str: - path = Path(v) - if not path.exists(): - raise ValueError(f"Vault path does not exist: {v}") - return str(path.resolve()) -``` - -## Error Handling - -### Centralized Error Management - -```python -class ErrorHandler: - """Centralized error handling and logging""" - - def __init__(self, logger: logging.Logger): - self.logger = logger - - def handle_api_error(self, error: Exception, context: str) -> SkillResult: - """Handle API-related errors consistently""" - self.logger.error(f"API error in {context}: {str(error)}") - return SkillResult( - success=False, - error=f"API call failed: {str(error)}", - message=f"Failed to complete {context}" - ) - - def handle_validation_error(self, error: Exception, field: str) -> SkillResult: - """Handle validation errors with user-friendly messages""" - self.logger.warning(f"Validation error for {field}: {str(error)}") - return SkillResult( - success=False, - error=f"Invalid {field}: {str(error)}", - message="Please check your input and try again" - ) -``` - -### Async Context Managers for Resources - -```python -from contextlib import asynccontextmanager -from typing import AsyncGenerator - -@asynccontextmanager -async def obsidian_client(config: ObsidianConfig) -> AsyncGenerator[aiohttp.ClientSession, None]: - """Async context manager for Obsidian API client""" - ssl_context = ssl.create_default_context() - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - - connector = aiohttp.TCPConnector(ssl=ssl_context) - headers = { - 'Authorization': f'Bearer {config.rest_api.api_key}', - 'Content-Type': 'application/json' - } - - async with aiohttp.ClientSession(connector=connector, headers=headers) as session: - try: - yield session - except Exception as e: - logger.error(f"Error in Obsidian client: {str(e)}") - raise APIError(f"Obsidian API error: {str(e)}") from e -``` - -## Testing Strategy - -### Dual Testing Approach - -The testing strategy combines unit tests for specific functionality with property-based tests for comprehensive validation: - -**Unit Tests:** -- Test specific error conditions and edge cases -- Validate configuration parsing and validation -- Test individual Skill and Command functionality -- Mock external API calls for isolated testing - -**Property-Based Tests:** -- Validate that all SkillResult objects maintain consistency -- Test configuration validation across various input combinations -- Verify error handling patterns work for all error types -- Ensure type hints are correctly applied - -**Testing Framework:** -- Use `pytest` for unit testing framework -- Use `hypothesis` for property-based testing -- Configure tests to run minimum 100 iterations per property test -- Tag each property test with: **Feature: code-quality-improvements, Property {number}: {property_text}** - -## Correctness Properties - -*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* - -### Property 1: Syntax Validity Across All Modules -*For any* Python module in the project, parsing the module with Python's AST parser should succeed without raising SyntaxError exceptions -**Validates: Requirements 1.1, 1.2, 1.3** - -### Property 2: Comprehensive Type Hint Coverage -*For any* public method or function in Agent_Core, Skills, Commands, or Chat_Interface, the method should have complete type annotations for parameters and return values -**Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5** - -### Property 3: Consistent Error Handling Structure -*For any* error condition in Skills or Commands, the returned SkillResult should have a consistent structure with appropriate error messages and no sensitive information exposure -**Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5** - -### Property 4: Modern Python Code Patterns -*For any* source file in the project, the code should use f-strings for formatting, pathlib.Path for file operations, proper dataclass definitions, and follow PEP 8 style guidelines -**Validates: Requirements 4.1, 4.2, 4.3, 4.4, 4.5** - -### Property 5: Configuration Validation Completeness -*For any* configuration input (valid or invalid), the system should validate all required fields, provide specific error messages for invalid values, and handle missing API keys with clear guidance -**Validates: Requirements 5.1, 5.2, 5.4, 7.4** - -### Property 6: Import Organization and Style -*For any* Python module in the project, imports should be organized according to PEP 8 standards, use absolute imports consistently, and handle missing dependencies gracefully -**Validates: Requirements 6.1, 6.2, 6.3, 6.4, 6.5** - -### Property 7: Input Validation and Sanitization -*For any* user input (file paths, dates, configuration values), the system should validate format and constraints, sanitize potentially dangerous inputs, and reject malformed data -**Validates: Requirements 7.1, 7.2, 7.3, 7.5** - -### Property 8: Environment Variable Expansion -*For any* configuration value containing environment variable references, the system should correctly expand the variables or provide clear error messages when variables are undefined -**Validates: Requirements 5.3** - -### Property 9: String Formatting Consistency -*For any* string formatting operation in the codebase, the system should use consistent and appropriate formatting patterns (f-strings, proper escaping, etc.) -**Validates: Requirements 1.4** - -### Property-Based Testing Configuration - -Each property test will be tagged with comments referencing the design document property and run with sufficient iterations to catch edge cases through randomization. \ No newline at end of file diff --git a/./.kiro/specs/code-quality-improvements/requirements.md b/./.kiro/specs/code-quality-improvements/requirements.md deleted file mode 100644 index 46657c1..0000000 --- a/./.kiro/specs/code-quality-improvements/requirements.md +++ /dev/null @@ -1,99 +0,0 @@ -# Requirements Document - -## Introduction - -This specification addresses critical code quality and syntax issues identified in the Obsidian journal organizer project. The system currently has syntax errors, inconsistent error handling, missing type hints, and several areas where Python best practices are not followed. These improvements will enhance maintainability, reliability, and developer experience. - -## Glossary - -- **System**: The Obsidian journal organizer application -- **Agent_Core**: The core framework defining Agent, Command, and Skill interfaces -- **Chat_Interface**: The conversational interface for v2.0 functionality -- **Skills**: Atomic functional units for specific tasks (READ, WRITE, ANALYZE, TRANSFORM) -- **Commands**: High-level operations that orchestrate multiple Skills -- **Type_Hints**: Python type annotations for better code documentation and IDE support - -## Requirements - -### Requirement 1: Fix Critical Syntax Errors - -**User Story:** As a developer, I want the codebase to be syntactically correct, so that I can run and develop the application without encountering basic syntax errors. - -#### Acceptance Criteria - -1. WHEN the chat_main.py file is parsed, THE System SHALL not produce syntax errors -2. WHEN any Python file is imported, THE System SHALL not raise SyntaxError exceptions -3. WHEN the application starts, THE System SHALL initialize without syntax-related failures -4. THE System SHALL use proper string literal formatting throughout all modules - -### Requirement 2: Add Comprehensive Type Hints - -**User Story:** As a developer, I want comprehensive type hints throughout the codebase, so that I can understand function signatures and catch type-related errors early. - -#### Acceptance Criteria - -1. THE Agent_Core SHALL include type hints for all public methods and properties -2. THE Skills SHALL include type hints for all execute methods and parameters -3. THE Commands SHALL include type hints for all public interfaces -4. THE Chat_Interface SHALL include type hints for all async methods -5. WHEN using modern Python features, THE System SHALL import from typing module appropriately - -### Requirement 3: Improve Error Handling Consistency - -**User Story:** As a developer, I want consistent error handling patterns, so that I can predict how errors are managed and debug issues effectively. - -#### Acceptance Criteria - -1. WHEN any Skill encounters an error, THE System SHALL return a SkillResult with consistent error structure -2. WHEN API calls fail, THE System SHALL provide meaningful error messages with context -3. WHEN configuration is missing, THE System SHALL fail gracefully with clear guidance -4. THE System SHALL log errors at appropriate levels with sufficient detail -5. WHEN exceptions occur, THE System SHALL not expose sensitive information in error messages - -### Requirement 4: Modernize Python Code Patterns - -**User Story:** As a developer, I want the codebase to follow modern Python best practices, so that it's maintainable and follows current standards. - -#### Acceptance Criteria - -1. THE System SHALL use f-strings instead of string concatenation where appropriate -2. THE System SHALL use pathlib.Path for file system operations -3. THE System SHALL use dataclasses with proper field definitions -4. THE System SHALL follow PEP 8 style guidelines consistently -5. WHEN handling async operations, THE System SHALL use proper async/await patterns - -### Requirement 5: Enhance Configuration Validation - -**User Story:** As a user, I want clear validation of configuration files, so that I can quickly identify and fix configuration issues. - -#### Acceptance Criteria - -1. WHEN loading configuration, THE System SHALL validate required fields are present -2. WHEN configuration values are invalid, THE System SHALL provide specific error messages -3. THE System SHALL support environment variable expansion in configuration -4. WHEN API keys are missing, THE System SHALL provide clear setup instructions -5. THE System SHALL validate file paths and folder structures exist - -### Requirement 6: Improve Import and Dependency Management - -**User Story:** As a developer, I want clean import statements and proper dependency handling, so that the codebase is organized and dependencies are clear. - -#### Acceptance Criteria - -1. THE System SHALL use absolute imports consistently -2. WHEN optional dependencies are missing, THE System SHALL provide helpful installation messages -3. THE System SHALL organize imports according to PEP 8 standards -4. THE System SHALL handle missing dependencies gracefully without crashing -5. WHEN importing from local modules, THE System SHALL use relative imports appropriately - -### Requirement 7: Add Input Validation and Sanitization - -**User Story:** As a developer, I want robust input validation, so that the system handles edge cases and invalid inputs gracefully. - -#### Acceptance Criteria - -1. WHEN processing user input, THE System SHALL validate input format and constraints -2. WHEN handling file paths, THE System SHALL sanitize and validate path safety -3. WHEN processing dates, THE System SHALL validate date format and ranges -4. THE System SHALL reject empty or malformed configuration values -5. WHEN handling API responses, THE System SHALL validate response structure before processing \ No newline at end of file diff --git a/./.kiro/specs/code-quality-improvements/tasks.md b/./.kiro/specs/code-quality-improvements/tasks.md deleted file mode 100644 index 45e639d..0000000 --- a/./.kiro/specs/code-quality-improvements/tasks.md +++ /dev/null @@ -1,221 +0,0 @@ -# Implementation Plan: Code Quality Improvements - -## Overview - -This implementation plan addresses critical code quality issues in the Obsidian journal organizer project through systematic fixes of syntax errors, addition of comprehensive type hints, modernization of Python patterns, and establishment of consistent error handling. - -## Tasks - -- [x] 1. Fix Critical Syntax Errors - - Fix the malformed docstring in chat_main.py that causes SyntaxError - - Validate all Python files can be parsed without syntax errors - - Test application startup to ensure no syntax-related failures - - _Requirements: 1.1, 1.2, 1.3, 1.4_ - -- [ ]* 1.1 Write property test for syntax validation - - **Property 1: Syntax Validity Across All Modules** - - **Validates: Requirements 1.1, 1.2, 1.3** - -- [x] 2. Add Comprehensive Type Hints - - [x] 2.1 Add type hints to agent_core.py - - Add type hints to Agent, Command, Skill, and SkillResult classes - - Import necessary types from typing module - - _Requirements: 2.1, 2.5_ - - - [x] 2.2 Add type hints to all Skills - - Update ObsidianReadSkill, ObsidianWriteSkill, ObsidianAppendSkill with type hints - - Update ClaudeAnalyzeSkill and ClaudeTransformSkill with type hints - - _Requirements: 2.2_ - - - [x] 2.3 Add type hints to Commands - - Update OrganizeCommand with comprehensive type hints - - _Requirements: 2.3_ - - - [x] 2.4 Add type hints to Chat Interface - - Update chat_main.py and conversation modules with type hints - - Focus on async method signatures - - _Requirements: 2.4_ - -- [ ]* 2.5 Write property test for type hint coverage - - **Property 2: Comprehensive Type Hint Coverage** - - **Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5** - -- [x] 3. Implement Consistent Error Handling - - [x] 3.1 Create centralized error handling framework - - Define custom exception classes (JournalOrganizerError, ConfigurationError, APIError, ValidationError) - - Create ErrorHandler class for consistent error management - - _Requirements: 3.1, 3.2, 3.3_ - - - [x] 3.2 Update Skills with consistent error handling - - Modify all Skills to use new error handling patterns - - Ensure SkillResult objects have consistent error structure - - _Requirements: 3.1, 3.4_ - - - [x] 3.3 Add security-conscious error messages - - Review error messages to ensure no sensitive information exposure - - Implement sanitized error reporting - - _Requirements: 3.5_ - -- [ ]* 3.4 Write property test for error handling consistency - - **Property 3: Consistent Error Handling Structure** - - **Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5** - -- [x] 4. Modernize Python Code Patterns - - [x] 4.1 Replace string concatenation with f-strings - - Scan codebase for string concatenation patterns - - Replace with f-string formatting where appropriate - - _Requirements: 4.1_ - - - [x] 4.2 Update file system operations to use pathlib - - Replace os.path usage with pathlib.Path - - Update file handling in configuration and Skills - - _Requirements: 4.2_ - - - [x] 4.3 Enhance dataclass definitions - - Review and improve existing dataclass field definitions - - Add proper validation and default values - - _Requirements: 4.3_ - - - [x] 4.4 Apply PEP 8 style guidelines - - Run code formatter (black) on entire codebase - - Fix any remaining style issues - - _Requirements: 4.4_ - - - [x] 4.5 Improve async/await patterns - - Review async code for proper patterns - - Add async context managers where appropriate - - _Requirements: 4.5_ - -- [ ]* 4.6 Write property test for modern Python patterns - - **Property 4: Modern Python Code Patterns** - - **Validates: Requirements 4.1, 4.2, 4.3, 4.4, 4.5** - -- [x] 5. Enhance Configuration Validation - - [x] 5.1 Implement configuration validation framework - - Add pydantic models for configuration validation - - Create validation functions for all config sections - - _Requirements: 5.1, 5.2_ - - - [x] 5.2 Add environment variable expansion support - - Implement environment variable substitution in config - - Handle missing environment variables gracefully - - _Requirements: 5.3_ - - - [x] 5.3 Improve API key validation and guidance - - Add specific validation for API key formats - - Provide clear setup instructions when keys are missing - - _Requirements: 5.4_ - - - [x] 5.4 Add file path validation - - Validate that configured paths exist and are accessible - - Provide helpful error messages for path issues - - _Requirements: 5.5_ - -- [ ]* 5.5 Write property test for configuration validation - - **Property 5: Configuration Validation Completeness** - - **Validates: Requirements 5.1, 5.2, 5.4, 7.4** - -- [ ]* 5.6 Write property test for environment variable expansion - - **Property 8: Environment Variable Expansion** - - **Validates: Requirements 5.3** - -- [x] 6. Improve Import and Dependency Management - - [x] 6.1 Standardize import organization - - Organize imports according to PEP 8 (standard, third-party, local) - - Use absolute imports consistently - - _Requirements: 6.1, 6.3_ - - - [x] 6.2 Enhance dependency error handling - - Improve error messages for missing optional dependencies - - Add graceful degradation when dependencies are unavailable - - _Requirements: 6.2, 6.4_ - - - [x] 6.3 Review and fix relative imports - - Ensure relative imports are used appropriately for local modules - - _Requirements: 6.5_ - -- [ ]* 6.4 Write property test for import organization - - **Property 6: Import Organization and Style** - - **Validates: Requirements 6.1, 6.2, 6.3, 6.4, 6.5** - -- [x] 7. Add Input Validation and Sanitization - - [x] 7.1 Implement comprehensive input validation - - Add validation for user input formats and constraints - - Create validation utilities for common input types - - _Requirements: 7.1_ - - - [x] 7.2 Add path sanitization and security - - Implement path traversal protection - - Validate file paths for security issues - - _Requirements: 7.2_ - - - [x] 7.3 Enhance date validation - - Add robust date format validation - - Validate date ranges and constraints - - _Requirements: 7.3_ - - - [x] 7.4 Add API response validation - - Validate API response structure before processing - - Handle malformed responses gracefully - - _Requirements: 7.5_ - -- [ ]* 7.5 Write property test for input validation - - **Property 7: Input Validation and Sanitization** - - **Validates: Requirements 7.1, 7.2, 7.3, 7.5** - -- [ ]* 7.6 Write property test for string formatting consistency - - **Property 9: String Formatting Consistency** - - **Validates: Requirements 1.4** - -- [x] 8. Create comprehensive test suite - - [x] 8.1 Set up testing framework - - Install pytest and hypothesis for property-based testing - - Create test directory structure - - Configure test runner and coverage reporting - - _Requirements: All_ - - - [x] 8.2 Write unit tests for core functionality - - Test agent_core classes (Agent, Command, Skill, SkillResult) - - Test configuration loading and validation - - Test error handling framework - - _Requirements: 1.3, 2.1, 3.1, 5.1_ - - - [x] 8.3 Write integration tests - - Test Skills with mocked API responses - - Test Commands with full skill chains - - Test conversational agent flow - - _Requirements: 2.2, 2.3, 2.4_ - -- [x] 9. Checkpoint - Ensure all tests pass - - Ensure all tests pass, ask the user if questions arise. - -- [x] 10. Final Integration and Validation - - [x] 10.1 Validate application startup and basic functionality - - Test both v1.0 CLI and v2.0 conversational interfaces - - Ensure configuration loading works correctly - - Test with sample configuration files - - _Requirements: 1.3, 5.1_ - - - [x] 10.2 Performance and reliability testing - - Test with various input sizes and edge cases - - Verify memory usage and error recovery - - Test dependency graceful degradation - - _Requirements: 6.2, 7.1_ - - - [x] 10.3 Update documentation for new patterns - - Update developer guide with new error handling patterns - - Document new configuration validation features - - Add troubleshooting guide for common issues - - _Requirements: 5.4_ - -- [x] 11. Final checkpoint - Ensure all tests pass - - Ensure all tests pass, ask the user if questions arise. - -## Notes - -- Tasks marked with `*` are optional and can be skipped for faster MVP -- Each task references specific requirements for traceability -- Checkpoints ensure incremental validation -- Property tests validate universal correctness properties -- Unit tests validate specific examples and edge cases -- Most core implementation tasks are complete - focus is now on testing and validation \ No newline at end of file diff --git a/./.kiro/steering/product.md b/./.kiro/steering/product.md deleted file mode 100644 index 727fee6..0000000 --- a/./.kiro/steering/product.md +++ /dev/null @@ -1,30 +0,0 @@ -# Product Overview - -## Obsidian 智能日记整理 Agent - -An intelligent journal organization system designed for Obsidian users that automatically analyzes daily journal entries, extracts key information (experiences, tasks, problems, etc.), and intelligently organizes them into designated locations within your knowledge base. - -### Core Features - -- **Intelligent Analysis**: Integrates Claude 3.5 Sonnet model for deep understanding of journal content and structured information extraction -- **Automated Organization**: Automatically creates new notes from extracted content and places them in preset folders -- **Bidirectional Linking**: Automatically adds links to original journal entries in generated notes for easy traceability -- **Conversational Interface**: v2.0 introduces natural language conversation capabilities for intuitive interaction -- **Command-Line Driven**: Standard CLI interface for easy integration and automation -- **Highly Configurable**: All key parameters manageable through configuration files - -### Architecture - -The system uses a **Command + Skill** architecture with two main versions: - -1. **v1.0**: Direct command execution with CLI interface -2. **v2.0**: Conversational agent with natural language understanding - -Both versions share the same execution layer but v2.0 adds a conversational layer for intent understanding and response generation. - -### Target Users - -- Obsidian users who maintain daily journals -- Knowledge workers seeking automated content organization -- Users who want to extract actionable insights from their daily notes -- Teams looking for structured knowledge management workflows \ No newline at end of file diff --git a/./.kiro/steering/structure.md b/./.kiro/steering/structure.md deleted file mode 100644 index 332f10f..0000000 --- a/./.kiro/steering/structure.md +++ /dev/null @@ -1,141 +0,0 @@ -# Project Structure - -## Directory Organization - -``` -journal_organizer/ -├── __init__.py # Package initialization -├── __main__.py # Entry point for python -m execution -├── main.py # v1.0 CLI entry and Agent initialization -├── chat_main.py # v2.0 conversational interface entry -├── agent_core.py # Core Agent framework (Command + Skill) -├── config.py # Configuration management utilities -├── server.py # Optional server interface -├── config.example.yaml # Configuration template -├── requirements.txt # Python dependencies -├── README.md # v1.0 documentation -├── README_V2.md # v2.0 documentation -├── DEVELOPER_GUIDE.md # Development guidelines -├── OBSIDIAN_INTEGRATION.md # Obsidian integration guide -├── commands/ # Command implementations -│ ├── __init__.py -│ └── organize_command.py # Main journal organization command -├── skills/ # Skill implementations -│ ├── __init__.py -│ ├── obsidian_skill.py # Obsidian API integration skills -│ └── claude_skill.py # Claude AI integration skills -└── conversation/ # v2.0 conversational layer - ├── __init__.py - ├── conversational_agent.py # Main conversational agent - ├── conversation_state.py # State management - ├── intent_understanding.py # Natural language understanding - └── response_generator.py # Response generation -``` - -## Core Components - -### Agent Core (`agent_core.py`) -- **Agent**: Main orchestrator class -- **Command**: Abstract base for high-level operations -- **Skill**: Abstract base for atomic operations -- **SkillChain**: Sequential skill execution -- **SkillResult**: Standardized result format -- **CommandContext**: Execution context container - -### Entry Points -- **`__main__.py`**: Package entry point (`python -m journal_organizer`) -- **`main.py`**: v1.0 CLI with argparse-based command handling -- **`chat_main.py`**: v2.0 conversational interface with natural language processing - -### Command Layer (`commands/`) -Commands orchestrate multiple Skills to accomplish complex tasks: -- **OrganizeCommand**: Main journal analysis and organization workflow -- Commands inherit from `Command` base class -- Commands register and coordinate Skills -- Commands handle parameter validation and error recovery - -### Skill Layer (`skills/`) -Skills perform atomic operations: -- **ObsidianReadSkill**: Read notes from Obsidian vault -- **ObsidianWriteSkill**: Create/update notes in Obsidian -- **ObsidianAppendSkill**: Append content to existing notes -- **ObsidianListFilesSkill**: List files in vault directories -- **ClaudeAnalyzeSkill**: Analyze journal content with Claude -- **ClaudeTransformSkill**: Transform content formats with Claude - -### Conversational Layer (`conversation/`) -v2.0 natural language interface: -- **ConversationalAgent**: Main conversation coordinator -- **IntentUnderstanding**: Maps natural language to commands/parameters -- **ConversationState**: Manages chat history and context -- **ResponseGenerator**: Generates natural language responses - -## Naming Conventions - -### Files and Modules -- Snake_case for Python files: `organize_command.py` -- Package names match directory structure -- Skills end with `_skill.py` -- Commands end with `_command.py` - -### Classes -- PascalCase for class names: `OrganizeCommand`, `ClaudeAnalyzeSkill` -- Skills inherit from `Skill` base class -- Commands inherit from `Command` base class -- Result objects use `Result` suffix: `SkillResult` - -### Methods and Variables -- Snake_case for methods and variables: `execute_command`, `api_key` -- Async methods use `async def` prefix -- Private methods start with underscore: `_parse_response` - -### Constants and Enums -- UPPER_CASE for constants: `MAX_RETRIES` -- PascalCase for Enums: `SkillType`, `TaskStatus` - -## Configuration Structure - -### YAML Configuration (`config.yaml`) -```yaml -obsidian: - vault_path: "/path/to/vault" - rest_api: - url: "https://localhost:27123" - api_key: "your-key" - verify_ssl: false - -claude: - api_key: "${ANTHROPIC_API_KEY}" - model: "claude-3-5-sonnet-20241022" - max_tokens: 4096 - -journal: - daily_notes_folder: "Daily" - date_format: "YYYY-MM-DD" - -output: - experiences_folder: "Knowledge/Experiences" - lessons_folder: "Knowledge/Lessons" - # ... other output folders -``` - -## Extension Patterns - -### Adding New Skills -1. Create new file in `skills/` directory -2. Inherit from `Skill` base class -3. Implement `execute()` method returning `SkillResult` -4. Register skill in relevant commands - -### Adding New Commands -1. Create new file in `commands/` directory -2. Inherit from `Command` base class -3. Register required skills in `__init__()` -4. Implement `execute()` method with skill orchestration -5. Register command in `main.py` or `chat_main.py` - -### Extending Conversational Capabilities -1. Add new intent patterns in `intent_understanding.py` -2. Update command keyword mappings -3. Extend parameter extraction patterns -4. Add response templates in `response_generator.py` \ No newline at end of file diff --git a/./.kiro/steering/tech.md b/./.kiro/steering/tech.md deleted file mode 100644 index d51e637..0000000 --- a/./.kiro/steering/tech.md +++ /dev/null @@ -1,102 +0,0 @@ -# Technology Stack - -## Core Technologies - -- **Python 3.8+**: Primary programming language -- **asyncio**: Asynchronous programming for concurrent operations -- **aiohttp**: HTTP client for API interactions -- **PyYAML**: Configuration file management -- **Anthropic Claude API**: AI-powered content analysis and understanding -- **Obsidian Local REST API**: Integration with Obsidian vault - -## Key Dependencies - -``` -anthropic>=0.25.0 # Claude API client -aiohttp>=3.9.0 # Async HTTP client -pyyaml>=6.0 # YAML configuration parsing -``` - -## Architecture Patterns - -### Command + Skill Pattern -- **Commands**: High-level operations that orchestrate multiple Skills -- **Skills**: Atomic functional units for specific tasks (READ, WRITE, ANALYZE, TRANSFORM, INTEGRATE) -- **Agent Core**: Base framework defining interfaces and interaction logic - -### Async/Await Pattern -- All Skills and Commands use async/await for non-blocking operations -- HTTP API calls are asynchronous using aiohttp -- Supports concurrent execution of multiple operations - -### Configuration-Driven Design -- YAML-based configuration files for all settings -- Environment variable support for sensitive data -- Separation of code and configuration - -## Common Commands - -### Development Setup -```bash -# Install dependencies -pip install -r requirements.txt - -# Copy and configure settings -cp config.example.yaml config.yaml -# Edit config.yaml with your API keys and paths -``` - -### Running the Application - -#### v1.0 - Direct Commands -```bash -# Organize today's journal -python -m journal_organizer organize - -# Organize specific date -python -m journal_organizer organize --date 2025-12-31 - -# List available commands -python -m journal_organizer list - -# Get help for specific command -python -m journal_organizer help organize -``` - -#### v2.0 - Conversational Interface -```bash -# Start interactive chat -python -m journal_organizer.chat_main - -# Single query mode -python -m journal_organizer.chat_main --query "整理今天的日记" -``` - -### Configuration -```bash -# Use custom config file -python -m journal_organizer --config /path/to/config.yaml organize - -# Set log level -python -m journal_organizer --log-level DEBUG organize -``` - -## API Integration Requirements - -### Obsidian Setup -1. Install "Local REST API" plugin in Obsidian -2. Generate API key in plugin settings -3. Configure API URL (default: https://localhost:27123) -4. Disable SSL verification for local development - -### Claude API Setup -1. Obtain Anthropic API key -2. Set environment variable: `ANTHROPIC_API_KEY` -3. Configure model in config.yaml (default: claude-3-5-sonnet-20241022) - -## Error Handling Patterns - -- All Skills return `SkillResult` objects with success/failure status -- Comprehensive logging at DEBUG, INFO, WARNING, ERROR levels -- Graceful degradation when external APIs are unavailable -- SSL context configuration for local HTTPS endpoints \ No newline at end of file diff --git a/./.pytest_cache/.gitignore b/./.pytest_cache/.gitignore deleted file mode 100644 index bc1a1f6..0000000 --- a/./.pytest_cache/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Created by pytest automatically. -* diff --git a/./.pytest_cache/CACHEDIR.TAG b/./.pytest_cache/CACHEDIR.TAG deleted file mode 100644 index fce15ad..0000000 --- a/./.pytest_cache/CACHEDIR.TAG +++ /dev/null @@ -1,4 +0,0 @@ -Signature: 8a477f597d28d172789f06886806bc55 -# This file is a cache directory tag created by pytest. -# For information about cache directory tags, see: -# https://bford.info/cachedir/spec.html diff --git a/./.pytest_cache/README.md b/./.pytest_cache/README.md deleted file mode 100644 index b89018c..0000000 --- a/./.pytest_cache/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# pytest cache directory # - -This directory contains data from the pytest's cache plugin, -which provides the `--lf` and `--ff` options, as well as the `cache` fixture. - -**Do not** commit this to version control. - -See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information. diff --git a/./.pytest_cache/v/cache/lastfailed b/./.pytest_cache/v/cache/lastfailed deleted file mode 100644 index 582f34a..0000000 --- a/./.pytest_cache/v/cache/lastfailed +++ /dev/null @@ -1,13 +0,0 @@ -{ - "tests/unit/test_config_validation.py": true, - "tests/integration/test_claude_skills.py": true, - "tests/integration/test_conversational_agent.py": true, - "tests/integration/test_obsidian_skills.py": true, - "tests/integration/test_organize_command.py": true, - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_validate_connection_success": true, - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_validate_connection_auth_error": true, - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_validate_model_availability": true, - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_create_message": true, - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_create_message_with_overrides": true, - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_test_api_connectivity_comprehensive": true -} \ No newline at end of file diff --git a/./.pytest_cache/v/cache/nodeids b/./.pytest_cache/v/cache/nodeids deleted file mode 100644 index 09b020f..0000000 --- a/./.pytest_cache/v/cache/nodeids +++ /dev/null @@ -1,133 +0,0 @@ -[ - "test_config_loader_integration.py::test_basic_environment_variable_expansion", - "test_config_loader_integration.py::test_config_file_loading", - "test_config_loader_integration.py::test_missing_required_variable", - "test_config_loader_integration.py::test_validation_methods", - "tests/integration/test_claude_api_integration.py::TestBackwardCompatibilityIntegration::test_legacy_model_migration", - "tests/integration/test_claude_api_integration.py::TestBackwardCompatibilityIntegration::test_migration_needed_detection", - "tests/integration/test_claude_api_integration.py::TestConfigurationValidation::test_claude_api_config_validation", - "tests/integration/test_claude_api_integration.py::TestConfigurationValidation::test_invalid_configuration_handling", - "tests/integration/test_claude_api_integration.py::TestEnvironmentVariableIntegration::test_environment_variable_defaults", - "tests/integration/test_claude_api_integration.py::TestEnvironmentVariableIntegration::test_environment_variable_expansion", - "tests/unit/test_agent_core.py::TestAgent::test_agent_creation", - "tests/unit/test_agent_core.py::TestAgent::test_agent_creation_without_config", - "tests/unit/test_agent_core.py::TestAgent::test_execute_command_by_alias", - "tests/unit/test_agent_core.py::TestAgent::test_execute_command_success", - "tests/unit/test_agent_core.py::TestAgent::test_execute_command_with_exception", - "tests/unit/test_agent_core.py::TestAgent::test_execute_unknown_command", - "tests/unit/test_agent_core.py::TestAgent::test_get_commands_info", - "tests/unit/test_agent_core.py::TestAgent::test_list_commands", - "tests/unit/test_agent_core.py::TestAgent::test_register_command", - "tests/unit/test_agent_core.py::TestCommand::test_command_creation", - "tests/unit/test_agent_core.py::TestCommand::test_command_execute_failure", - "tests/unit/test_agent_core.py::TestCommand::test_command_execute_success", - "tests/unit/test_agent_core.py::TestCommand::test_get_info", - "tests/unit/test_agent_core.py::TestCommand::test_register_skill", - "tests/unit/test_agent_core.py::TestCommand::test_register_skill_chain", - "tests/unit/test_agent_core.py::TestCommandContext::test_context_creation", - "tests/unit/test_agent_core.py::TestCommandContext::test_context_defaults", - "tests/unit/test_agent_core.py::TestCommandContext::test_to_dict", - "tests/unit/test_agent_core.py::TestSkill::test_get_info", - "tests/unit/test_agent_core.py::TestSkill::test_skill_creation", - "tests/unit/test_agent_core.py::TestSkill::test_skill_execute_failure", - "tests/unit/test_agent_core.py::TestSkill::test_skill_execute_success", - "tests/unit/test_agent_core.py::TestSkillChain::test_add_skill", - "tests/unit/test_agent_core.py::TestSkillChain::test_get_info", - "tests/unit/test_agent_core.py::TestSkillChain::test_skill_chain_creation", - "tests/unit/test_agent_core.py::TestSkillChain::test_skill_chain_execute_failure", - "tests/unit/test_agent_core.py::TestSkillChain::test_skill_chain_execute_success", - "tests/unit/test_agent_core.py::TestSkillResult::test_failed_result_creation", - "tests/unit/test_agent_core.py::TestSkillResult::test_failed_result_without_error_raises_exception", - "tests/unit/test_agent_core.py::TestSkillResult::test_successful_result_creation", - "tests/unit/test_agent_core.py::TestSkillResult::test_successful_result_with_error_raises_exception", - "tests/unit/test_agent_core.py::TestSkillResult::test_to_dict", - "tests/unit/test_agent_core.py::TestSkillResult::test_to_json", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_client_initialization", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_client_with_custom_url", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_create_message", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_create_message_with_overrides", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_get_client_info", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_get_client_info_custom_endpoint", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_test_api_connectivity_comprehensive", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_validate_connection_auth_error", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_validate_connection_success", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIClient::test_validate_model_availability", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_api_key_too_short", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_api_url_trailing_slash_removal", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_custom_api_url", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_default_values", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_invalid_api_key_format", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_invalid_api_url_format", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_invalid_api_url_protocol", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_invalid_max_tokens", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_invalid_model_name", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_invalid_temperature", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_valid_claude_config", - "tests/unit/test_claude_api_configuration.py::TestClaudeAPIConfig::test_valid_model_names", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_env_var_override_default", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_env_var_with_default", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_get_environment_variable_references", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_load_nonexistent_config", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_load_yaml_config", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_missing_required_env_var", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_nested_env_var_expansion", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_simple_env_var_expansion", - "tests/unit/test_claude_api_configuration.py::TestConfigurationLoader::test_validate_environment_variables", - "tests/unit/test_claude_api_configuration.py::TestConfigurationMigrator::test_check_migration_needed", - "tests/unit/test_claude_api_configuration.py::TestConfigurationMigrator::test_get_migration_preview", - "tests/unit/test_claude_api_configuration.py::TestConfigurationMigrator::test_get_supported_model_names", - "tests/unit/test_claude_api_configuration.py::TestConfigurationMigrator::test_migrate_claude_config_missing_api_url", - "tests/unit/test_claude_api_configuration.py::TestConfigurationMigrator::test_migrate_complete_configuration", - "tests/unit/test_claude_api_configuration.py::TestConfigurationMigrator::test_migrate_legacy_model_names", - "tests/unit/test_claude_api_configuration.py::TestConfigurationMigrator::test_migrate_missing_claude_section", - "tests/unit/test_claude_api_configuration.py::TestConfigurationValidator::test_load_and_validate_valid_config", - "tests/unit/test_claude_api_configuration.py::TestConfigurationValidator::test_load_and_validate_with_env_vars", - "tests/unit/test_claude_api_configuration.py::TestConfigurationValidator::test_load_and_validate_with_migration", - "tests/unit/test_claude_api_configuration.py::TestConfigurationValidator::test_validate_api_keys", - "tests/unit/test_claude_api_configuration.py::TestConfigurationValidator::test_validate_api_keys_with_env_vars", - "tests/unit/test_claude_api_configuration.py::TestConfigurationValidator::test_validation_error_handling", - "tests/unit/test_error_handling.py::TestAPIError::test_api_error_with_long_response", - "tests/unit/test_error_handling.py::TestAPIError::test_api_error_with_short_response", - "tests/unit/test_error_handling.py::TestAPIError::test_basic_api_error", - "tests/unit/test_error_handling.py::TestAuditErrorMessageSecurity::test_audit_clean_message", - "tests/unit/test_error_handling.py::TestAuditErrorMessageSecurity::test_audit_message_with_api_key", - "tests/unit/test_error_handling.py::TestAuditErrorMessageSecurity::test_audit_message_with_bearer_token", - "tests/unit/test_error_handling.py::TestAuditErrorMessageSecurity::test_audit_message_with_email", - "tests/unit/test_error_handling.py::TestAuditErrorMessageSecurity::test_audit_message_with_ip_address", - "tests/unit/test_error_handling.py::TestAuditErrorMessageSecurity::test_audit_message_with_multiple_issues", - "tests/unit/test_error_handling.py::TestConfigurationError::test_basic_configuration_error", - "tests/unit/test_error_handling.py::TestConfigurationError::test_configuration_error_with_non_sensitive_value", - "tests/unit/test_error_handling.py::TestConfigurationError::test_configuration_error_with_sensitive_value", - "tests/unit/test_error_handling.py::TestErrorContext::test_error_context_defaults", - "tests/unit/test_error_handling.py::TestErrorContext::test_error_context_validation_empty_component", - "tests/unit/test_error_handling.py::TestErrorContext::test_error_context_validation_empty_operation", - "tests/unit/test_error_handling.py::TestErrorContext::test_error_context_validation_invalid_severity", - "tests/unit/test_error_handling.py::TestErrorContext::test_error_context_validation_invalid_technical_details", - "tests/unit/test_error_handling.py::TestErrorContext::test_to_dict", - "tests/unit/test_error_handling.py::TestErrorContext::test_valid_error_context", - "tests/unit/test_error_handling.py::TestErrorHandler::test_error_handler_creation", - "tests/unit/test_error_handling.py::TestErrorHandler::test_handle_api_error", - "tests/unit/test_error_handling.py::TestErrorHandler::test_handle_api_error_with_api_error_instance", - "tests/unit/test_error_handling.py::TestErrorHandler::test_handle_configuration_error", - "tests/unit/test_error_handling.py::TestErrorHandler::test_handle_error_with_custom_error", - "tests/unit/test_error_handling.py::TestErrorHandler::test_handle_error_with_generic_error", - "tests/unit/test_error_handling.py::TestErrorHandler::test_handle_validation_error", - "tests/unit/test_error_handling.py::TestErrorHandler::test_sanitize_context_data", - "tests/unit/test_error_handling.py::TestErrorHandler::test_sanitize_context_data_non_dict", - "tests/unit/test_error_handling.py::TestErrorHandler::test_sanitize_error_message_api_key", - "tests/unit/test_error_handling.py::TestErrorHandler::test_sanitize_error_message_bearer_token", - "tests/unit/test_error_handling.py::TestErrorHandler::test_sanitize_error_message_email", - "tests/unit/test_error_handling.py::TestErrorHandler::test_sanitize_error_message_file_paths", - "tests/unit/test_error_handling.py::TestFileSystemError::test_basic_filesystem_error", - "tests/unit/test_error_handling.py::TestGlobalErrorHandler::test_get_error_handler_singleton", - "tests/unit/test_error_handling.py::TestGlobalErrorHandler::test_set_error_handler", - "tests/unit/test_error_handling.py::TestJournalOrganizerError::test_basic_error_creation", - "tests/unit/test_error_handling.py::TestJournalOrganizerError::test_error_with_cause", - "tests/unit/test_error_handling.py::TestJournalOrganizerError::test_error_with_context", - "tests/unit/test_error_handling.py::TestJournalOrganizerError::test_to_dict", - "tests/unit/test_error_handling.py::TestSecurityError::test_basic_security_error", - "tests/unit/test_error_handling.py::TestSecurityError::test_security_error_with_long_path", - "tests/unit/test_error_handling.py::TestValidationError::test_basic_validation_error", - "tests/unit/test_error_handling.py::TestValidationError::test_validation_error_with_long_value", - "tests/unit/test_error_handling.py::TestValidationError::test_validation_error_with_sensitive_field" -] \ No newline at end of file diff --git a/./.pytest_cache/v/cache/stepwise b/./.pytest_cache/v/cache/stepwise deleted file mode 100644 index 0637a08..0000000 --- a/./.pytest_cache/v/cache/stepwise +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/./DEVELOPER_GUIDE.md b/./DEVELOPER_GUIDE.md deleted file mode 100644 index 2446546..0000000 --- a/./DEVELOPER_GUIDE.md +++ /dev/null @@ -1,963 +0,0 @@ -# 开发者指南 - -本指南说明如何扩展和自定义日记整理 Agent,包括新的错误处理模式、配置验证功能和故障排除指南。 - -## 架构概览 - -Agent 系统基于以下核心概念: - -- **Agent**:主控制器,负责管理 Commands 和 Skills -- **Command**:用户可执行的命令,编排多个 Skills -- **Skill**:原子化的功能单元,执行具体任务 -- **SkillChain**:多个 Skills 的有序执行链 -- **ErrorHandler**:集中式错误处理和日志记录 -- **ConfigurationValidator**:配置验证和环境变量扩展 - -## 核心类 - -### Agent - -```python -from journal_organizer.agent_core import Agent - -# 创建 Agent -agent = Agent("MyAgent", config={}) - -# 注册命令 -agent.register_command(my_command) - -# 执行命令 -result = await agent.execute_command("command_name", args={}) -``` - -### Skill - -所有 Skill 都继承自 `Skill` 基类: - -```python -from journal_organizer.agent_core import Skill, SkillType, SkillResult, CommandContext - -class MySkill(Skill): - def __init__(self): - super().__init__( - name="my_skill", - skill_type=SkillType.ANALYZE, - description="我的自定义 Skill" - ) - - async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - # 实现您的逻辑 - try: - result = do_something(**kwargs) - return SkillResult( - success=True, - data=result, - message="执行成功" - ) - except Exception as e: - return SkillResult( - success=False, - error=str(e), - message="执行失败" - ) -``` - -### Command - -所有 Command 都继承自 `Command` 基类: - -```python -from journal_organizer.agent_core import Command, SkillResult, CommandContext - -class MyCommand(Command): - def __init__(self): - super().__init__( - name="my_command", - description="我的自定义命令", - aliases=["mc"] - ) - # 注册 Skills - self.register_skill(MySkill()) - - async def execute(self, context: CommandContext) -> SkillResult: - # 获取参数 - param1 = context.args.get('param1') - - # 执行 Skill - skill = self.skills['my_skill'] - result = await skill.execute(context, param1=param1) - - return result -``` - -## 添加新的 Skill - -### 步骤 1: 创建 Skill 类 - -在 `skills/` 目录下创建一个新文件,例如 `my_skill.py`: - -```python -from ..agent_core import Skill, SkillType, SkillResult, CommandContext - -class MyCustomSkill(Skill): - def __init__(self): - super().__init__( - name="my_custom_skill", - skill_type=SkillType.TRANSFORM, - description="执行自定义转换" - ) - - async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - try: - input_data = kwargs.get('input_data') - - # 您的自定义逻辑 - output_data = self._process(input_data) - - return SkillResult( - success=True, - data=output_data, - message="处理完成" - ) - except Exception as e: - return SkillResult( - success=False, - error=str(e), - message="处理失败" - ) - - def _process(self, data): - # 实现处理逻辑 - return data -``` - -### 步骤 2: 在 Command 中使用 Skill - -```python -from ..skills.my_skill import MyCustomSkill - -class MyCommand(Command): - def __init__(self): - super().__init__(name="my_command") - self.register_skill(MyCustomSkill()) - - async def execute(self, context: CommandContext) -> SkillResult: - skill = self.skills['my_custom_skill'] - return await skill.execute(context, input_data="test") -``` - -## 添加新的 Command - -### 步骤 1: 创建 Command 类 - -在 `commands/` 目录下创建一个新文件,例如 `my_command.py`: - -```python -from ..agent_core import Command, SkillResult, CommandContext -from ..skills.my_skill import MyCustomSkill - -class MyCommand(Command): - def __init__(self): - super().__init__( - name="my_command", - description="我的自定义命令", - aliases=["mc", "my-cmd"] - ) - self.register_skill(MyCustomSkill()) - - async def execute(self, context: CommandContext) -> SkillResult: - # 获取参数 - param1 = context.args.get('param1') - param2 = context.args.get('param2', 'default') - - # 执行 Skill - skill = self.skills['my_custom_skill'] - result = await skill.execute(context, input_data=param1) - - return result -``` - -### 步骤 2: 在 Agent 中注册 Command - -编辑 `main.py` 的 `_register_commands` 方法: - -```python -def _register_commands(self) -> None: - """注册所有命令""" - self.agent.register_command(OrganizeCommand()) - self.agent.register_command(MyCommand()) # 添加新命令 -``` - -### 步骤 3: 测试新命令 - -```bash -python -m journal_organizer my_command --param1 "value1" -``` - -## 使用 SkillChain - -SkillChain 允许您按顺序执行多个 Skills: - -```python -from journal_organizer.agent_core import SkillChain - -class MyCommand(Command): - def __init__(self): - super().__init__(name="my_command") - - # 创建 Skill 链 - chain = SkillChain("my_chain", "执行一系列操作") - chain.add_skill(Skill1(), {"param1": "value1"}) - chain.add_skill(Skill2(), {"param2": "value2"}) - - self.register_skill_chain(chain) - - async def execute(self, context: CommandContext) -> SkillResult: - chain = self.skill_chains['my_chain'] - return await chain.execute(context) -``` - -## 异步编程 - -所有 Skills 和 Commands 都使用异步编程(async/await)。这允许并发执行多个操作。 - -### 基本示例 - -```python -async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - # 异步调用外部 API - result = await self.call_external_api() - return SkillResult(success=True, data=result) - -async def call_external_api(self): - # 使用 aiohttp 进行异步 HTTP 请求 - async with aiohttp.ClientSession() as session: - async with session.get('https://api.example.com/data') as resp: - return await resp.json() -``` - -## 错误处理 - -### 新的错误处理框架 - -系统现在使用集中式错误处理框架,提供一致的错误管理和日志记录: - -```python -from journal_organizer.error_handling import ( - ErrorHandler, - JournalOrganizerError, - ConfigurationError, - APIError, - ValidationError -) - -# 创建错误处理器 -import logging -logger = logging.getLogger("MySkill") -error_handler = ErrorHandler(logger) - -# 处理 API 错误 -try: - result = await api_call() -except Exception as e: - error_result = error_handler.handle_api_error(e, "claude", "analyze_text") - return SkillResult( - success=False, - error=error_result["error"], - message=error_result["message"] - ) -``` - -### 自定义异常类型 - -使用专门的异常类型来处理不同类型的错误: - -```python -from journal_organizer.error_handling import ( - JournalOrganizerError, - ConfigurationError, - APIError, - ValidationError -) - -# 配置错误 -if not api_key: - raise ConfigurationError("API key is required", context={"service": "claude"}) - -# API 错误 -if response.status_code != 200: - raise APIError( - "API request failed", - api_name="obsidian", - status_code=response.status_code - ) - -# 验证错误 -if not validate_input(data): - raise ValidationError("Invalid input format", field="date") -``` - -### Skill 中的错误处理模式 - -在 Skill 中实现标准化的错误处理: - -```python -from journal_organizer.agent_core import Skill, SkillResult, CommandContext -from journal_organizer.error_handling import ErrorHandler, APIError, ValidationError - -class MySkill(Skill): - def __init__(self): - super().__init__(name="my_skill") - self.error_handler = ErrorHandler(self.logger) - - async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - try: - # 输入验证 - self._validate_inputs(**kwargs) - - # 执行主要逻辑 - result = await self._perform_operation(**kwargs) - - return SkillResult(success=True, data=result, message="操作成功") - - except ValidationError as e: - error_result = self.error_handler.handle_validation_error(e, "input_data") - return SkillResult( - success=False, - error=error_result["error"], - message=error_result["message"] - ) - except APIError as e: - error_result = self.error_handler.handle_api_error(e, "external_service", "operation") - return SkillResult( - success=False, - error=error_result["error"], - message=error_result["message"] - ) - except Exception as e: - # 处理未预期的错误 - self.logger.error(f"Unexpected error in {self.name}: {str(e)}", exc_info=True) - return SkillResult( - success=False, - error="Internal error occurred", - message="操作失败,请检查日志" - ) - - def _validate_inputs(self, **kwargs): - """验证输入参数""" - required_params = ['param1', 'param2'] - for param in required_params: - if param not in kwargs: - raise ValidationError(f"Missing required parameter: {param}", field=param) - - async def _perform_operation(self, **kwargs): - """执行主要操作""" - # 实现您的逻辑 - pass -``` - -## 日志记录 - -使用内置的 logger 记录信息: - -```python -class MySkill(Skill): - async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - self.logger.debug("开始执行") - self.logger.info("处理数据") - self.logger.warning("可能的问题") - self.logger.error("发生错误") - - return SkillResult(success=True) -``` - -## 配置管理 - -### 新的配置验证系统 - -系统现在包含强大的配置验证功能,支持类型检查、环境变量扩展和路径验证: - -```python -from journal_organizer.config_validation import ( - SystemConfig, - ObsidianConfig, - ClaudeConfig, - validate_system_config -) - -# 验证配置 -try: - config = validate_system_config(raw_config) - print("配置验证成功") -except ValidationError as e: - print(f"配置验证失败: {e}") -``` - -### 环境变量扩展 - -配置文件支持环境变量扩展: - -```yaml -# config.yaml -claude: - api_key: "${ANTHROPIC_API_KEY}" # 从环境变量读取 - model: "${CLAUDE_MODEL:-claude-3-5-sonnet-20241022}" # 带默认值 - -obsidian: - vault_path: "${OBSIDIAN_VAULT_PATH}" - rest_api: - api_key: "${OBSIDIAN_API_KEY}" -``` - -### 配置验证示例 - -```python -from journal_organizer.config_validation import validate_obsidian_config, validate_claude_config - -# 验证 Obsidian 配置 -obsidian_config = { - "vault_path": "/path/to/vault", - "rest_api": { - "url": "https://localhost:27123", - "api_key": "your-key", - "verify_ssl": False - } -} - -try: - validated_config = validate_obsidian_config(obsidian_config) - print("Obsidian 配置有效") -except ValidationError as e: - print(f"Obsidian 配置错误: {e}") - -# 验证 Claude 配置 -claude_config = { - "api_key": "sk-ant-...", - "model": "claude-3-5-sonnet-20241022", - "max_tokens": 4096 -} - -try: - validated_config = validate_claude_config(claude_config) - print("Claude 配置有效") -except ValidationError as e: - print(f"Claude 配置错误: {e}") -``` - -### 在 Skill 中访问配置 - -```python -async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - config = context.config or {} - - # 安全地访问配置 - claude_config = config.get('claude', {}) - api_key = claude_config.get('api_key') - - if not api_key: - raise ConfigurationError("Claude API key not configured") - - # 使用配置 - return SkillResult(success=True) -``` - -## 测试 - -### 单元测试示例 - -```python -import pytest -from journal_organizer.agent_core import CommandContext - -@pytest.mark.asyncio -async def test_my_skill(): - skill = MySkill() - context = CommandContext(command_name="test") - - result = await skill.execute(context, input_data="test") - - assert result.success == True - assert result.data is not None -``` - -### 运行测试 - -```bash -pytest tests/ -``` - -## 性能优化 - -### 并发执行 - -```python -import asyncio - -async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - # 并发执行多个操作 - results = await asyncio.gather( - self.operation1(), - self.operation2(), - self.operation3() - ) - - return SkillResult(success=True, data=results) -``` - -### 缓存 - -```python -from functools import lru_cache - -class MySkill(Skill): - @lru_cache(maxsize=128) - def expensive_operation(self, key): - # 缓存昂贵的操作 - return process(key) -``` - -## 最佳实践 - -### 现代 Python 模式 - -系统现在遵循现代 Python 最佳实践: - -#### 1. 使用 f-strings 进行字符串格式化 - -```python -# ✅ 推荐:使用 f-strings -name = "用户" -message = f"欢迎 {name},当前时间是 {datetime.now()}" - -# ❌ 避免:字符串连接 -message = "欢迎 " + name + ",当前时间是 " + str(datetime.now()) -``` - -#### 2. 使用 pathlib 进行文件路径操作 - -```python -from pathlib import Path - -# ✅ 推荐:使用 pathlib -vault_path = Path(config['obsidian']['vault_path']) -daily_folder = vault_path / "Daily" -note_file = daily_folder / f"{date}.md" - -# 检查文件是否存在 -if note_file.exists(): - content = note_file.read_text(encoding='utf-8') - -# ❌ 避免:使用 os.path -import os -note_file = os.path.join(vault_path, "Daily", f"{date}.md") -``` - -#### 3. 使用 dataclasses 定义数据结构 - -```python -from dataclasses import dataclass, field -from typing import Optional, List -from datetime import datetime - -@dataclass -class SkillResult: - success: bool - data: Optional[Dict[str, Any]] = None - error: Optional[str] = None - message: str = "" - timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) - - def to_dict(self) -> Dict[str, Any]: - return asdict(self) -``` - -#### 4. 使用类型提示 - -```python -from typing import Dict, Any, Optional, List, Union - -async def execute_skill( - skill_name: str, - context: CommandContext, - **kwargs: Any -) -> SkillResult: - """ - 执行指定的 Skill - - Args: - skill_name: Skill 名称 - context: 命令执行上下文 - **kwargs: Skill 参数 - - Returns: - SkillResult: 执行结果 - """ - pass -``` - -#### 5. 使用异步上下文管理器 - -```python -from contextlib import asynccontextmanager -import aiohttp - -@asynccontextmanager -async def http_client(config: Dict[str, Any]): - """HTTP 客户端上下文管理器""" - connector = aiohttp.TCPConnector( - ssl=False if not config.get('verify_ssl', True) else None - ) - - async with aiohttp.ClientSession(connector=connector) as session: - try: - yield session - finally: - await session.close() - -# 使用示例 -async def call_api(): - async with http_client(api_config) as client: - async with client.get(url) as response: - return await response.json() -``` - -### 通用最佳实践 - -1. **单一职责**:每个 Skill 只负责一个任务 -2. **错误处理**:使用新的错误处理框架 -3. **日志记录**:使用 logger 记录重要信息 -4. **配置驱动**:使用配置验证系统 -5. **异步编程**:充分利用异步特性提高性能 -6. **类型安全**:使用类型提示和验证 -7. **文档**:为您的 Skills 和 Commands 编写清晰的文档 -8. **测试**:编写单元测试和属性测试确保代码质量 -9. **版本控制**:使用 git 管理代码版本 -10. **代码格式化**:使用 black 和 isort 保持代码风格一致 - -## 示例:完整的自定义 Skill - -```python -""" -自定义 Skill 示例:文本统计 -""" - -from ..agent_core import Skill, SkillType, SkillResult, CommandContext - -class TextStatisticsSkill(Skill): - """计算文本统计信息的 Skill""" - - def __init__(self): - super().__init__( - name="text_statistics", - skill_type=SkillType.ANALYZE, - description="计算文本的字数、词数、句数等统计信息" - ) - - async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - """ - 执行文本统计 - - Args: - context: 命令执行上下文 - **kwargs: 包含 text 参数 - - Returns: - SkillResult: 包含统计结果的结果 - """ - try: - text = kwargs.get('text', '') - - if not text: - return SkillResult( - success=False, - error="缺少文本参数", - message="未提供要统计的文本" - ) - - # 计算统计信息 - stats = { - 'char_count': len(text), - 'word_count': len(text.split()), - 'sentence_count': len(text.split('。')), - 'line_count': len(text.split('\n')), - 'avg_word_length': len(text) / len(text.split()) if text.split() else 0 - } - - self.logger.info(f"文本统计完成: {stats}") - - return SkillResult( - success=True, - data=stats, - message="文本统计完成" - ) - - except Exception as e: - self.logger.error(f"文本统计失败: {str(e)}") - return SkillResult( - success=False, - error=str(e), - message="文本统计异常" - ) -``` - -## 资源 - -- [Python 异步编程](https://docs.python.org/3/library/asyncio.html) -- [Anthropic API 文档](https://docs.anthropic.com/) -- [Obsidian API 文档](https://docs.obsidian.md/Obsidian+API) - ---- - -# 故障排除指南 - -本节提供常见问题的解决方案和调试技巧。 - -## 常见问题 - -### 1. 导入错误 (ImportError) - -**问题**: `ImportError: attempted relative import with no known parent package` - -**解决方案**: -```bash -# 确保以模块方式运行 -python -m journal_organizer --help - -# 而不是直接运行 -python main.py # ❌ 错误方式 -``` - -**原因**: 项目使用相对导入,需要作为包运行。 - -### 2. 配置文件问题 - -**问题**: `ConfigurationError: Missing required configuration` - -**解决方案**: -1. 检查配置文件是否存在: -```bash -ls -la config.yaml -``` - -2. 验证配置格式: -```bash -python -c " -import yaml -with open('config.yaml', 'r') as f: - config = yaml.safe_load(f) - print('配置文件格式正确') -" -``` - -3. 检查环境变量: -```bash -echo $ANTHROPIC_API_KEY -echo $OBSIDIAN_API_KEY -``` - -### 3. API 连接问题 - -**问题**: `APIError: Failed to connect to Claude/Obsidian API` - -**解决方案**: - -**Claude API**: -```bash -# 测试 API 密钥 -curl -H "Authorization: Bearer $ANTHROPIC_API_KEY" \ - -H "Content-Type: application/json" \ - https://api.anthropic.com/v1/messages -``` - -**Obsidian API**: -```bash -# 检查 Obsidian Local REST API 插件状态 -curl -k -H "Authorization: Bearer $OBSIDIAN_API_KEY" \ - https://localhost:27123/ -``` - -### 4. 依赖项问题 - -**问题**: `ModuleNotFoundError: No module named 'xxx'` - -**解决方案**: -```bash -# 检查依赖项状态 -python -m journal_organizer check-deps - -# 安装缺失的依赖项 -pip install -r requirements.txt - -# 安装可选依赖项 -pip install pyyaml aiohttp anthropic -``` - -### 5. 权限问题 - -**问题**: `PermissionError: [Errno 13] Permission denied` - -**解决方案**: -```bash -# 检查文件权限 -ls -la config.yaml -ls -la /path/to/obsidian/vault - -# 修复权限 -chmod 644 config.yaml -chmod -R 755 /path/to/obsidian/vault -``` - -### 6. SSL 证书问题 - -**问题**: `SSL: CERTIFICATE_VERIFY_FAILED` - -**解决方案**: -在配置文件中禁用 SSL 验证(仅用于本地开发): -```yaml -obsidian: - rest_api: - verify_ssl: false -``` - -## 调试技巧 - -### 1. 启用详细日志 - -```bash -# 设置调试级别日志 -python -m journal_organizer --log-level DEBUG organize -``` - -### 2. 使用 Python 调试器 - -```python -# 在 Skill 中添加断点 -import pdb; pdb.set_trace() - -# 或使用 ipdb(更友好的界面) -import ipdb; ipdb.set_trace() -``` - -### 3. 检查配置加载 - -```python -# 测试配置加载 -from journal_organizer.main import JournalOrganizerAgent - -agent = JournalOrganizerAgent("config.yaml") -print(f"配置: {agent.config}") -``` - -### 4. 测试单个 Skill - -```python -# 单独测试 Skill -import asyncio -from journal_organizer.skills.claude_skill import ClaudeAnalyzeSkill -from journal_organizer.agent_core import CommandContext - -async def test_skill(): - skill = ClaudeAnalyzeSkill() - context = CommandContext(command_name="test") - result = await skill.execute(context, text="测试文本") - print(f"结果: {result}") - -asyncio.run(test_skill()) -``` - -## 性能问题 - -### 1. 内存使用过高 - -**诊断**: -```python -import psutil -import os - -process = psutil.Process(os.getpid()) -print(f"内存使用: {process.memory_info().rss / 1024 / 1024:.2f} MB") -``` - -**解决方案**: -- 检查是否有内存泄漏 -- 使用 `gc.collect()` 强制垃圾回收 -- 限制并发操作数量 - -### 2. API 调用缓慢 - -**诊断**: -```python -import time - -start_time = time.time() -result = await api_call() -duration = time.time() - start_time -print(f"API 调用耗时: {duration:.2f} 秒") -``` - -**解决方案**: -- 检查网络连接 -- 增加超时设置 -- 使用连接池 -- 实现重试机制 - -## 错误代码参考 - -| 错误代码 | 描述 | 解决方案 | -|---------|------|----------| -| CONFIG_001 | 配置文件不存在 | 创建 config.yaml 文件 | -| CONFIG_002 | 配置格式错误 | 检查 YAML/JSON 语法 | -| CONFIG_003 | 缺少必需配置项 | 添加缺失的配置项 | -| API_001 | API 密钥无效 | 检查并更新 API 密钥 | -| API_002 | API 连接超时 | 检查网络连接和服务状态 | -| API_003 | API 限流 | 减少请求频率或升级 API 计划 | -| SKILL_001 | Skill 执行失败 | 检查 Skill 输入参数和依赖项 | -| SKILL_002 | Skill 超时 | 增加超时设置或优化 Skill 逻辑 | - -## 获取帮助 - -如果问题仍然存在: - -1. **检查日志文件**: `logs/journal_organizer.log` -2. **运行诊断命令**: `python -m journal_organizer check-deps` -3. **查看详细错误**: 使用 `--log-level DEBUG` -4. **测试基本功能**: 运行简单的命令如 `list` 或 `help` - -## 开发环境设置 - -### 推荐的开发工具 - -```bash -# 安装开发依赖 -pip install pytest pytest-asyncio black isort mypy - -# 代码格式化 -black . -isort . - -# 类型检查 -mypy journal_organizer/ - -# 运行测试 -pytest tests/ -``` - -### 调试配置 (VS Code) - -创建 `.vscode/launch.json`: -```json -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug Journal Organizer", - "type": "python", - "request": "launch", - "module": "journal_organizer", - "args": ["--log-level", "DEBUG", "organize"], - "console": "integratedTerminal", - "cwd": "${workspaceFolder}" - } - ] -} -``` diff --git a/./OBSIDIAN_INTEGRATION.md b/./OBSIDIAN_INTEGRATION.md deleted file mode 100644 index d01c6e2..0000000 --- a/./OBSIDIAN_INTEGRATION.md +++ /dev/null @@ -1,266 +0,0 @@ -# Obsidian 集成指南 - -本指南说明如何在 Obsidian 中集成和使用日记整理 Agent。 - -## 前置条件 - -1. 已安装 Obsidian -2. 已安装 `Local REST API` 插件并配置 API 密钥 -3. 已安装并配置日记整理 Agent -4. 已安装 Obsidian 的 `Templater` 或 `QuickAdd` 插件(用于触发脚本) - -## 方案 1: 使用 Templater 插件 - -### 步骤 1: 安装 Templater 插件 - -1. 在 Obsidian 中,进入 `设置` > `第三方插件` -2. 关闭 `安全模式` -3. 点击 `浏览社区插件`,搜索 `Templater` 并安装 -4. 启用 Templater 插件 - -### 步骤 2: 创建模板 - -1. 在 Obsidian 中创建一个新的笔记,例如 `Templates/OrganizeJournal.md` -2. 在笔记中添加以下内容: - -```javascript -<%* -// 获取当前笔记的标题(假设为日期格式) -const noteTitle = tp.file.title; -const agentPath = "/path/to/journal_organizer"; - -// 构建命令 -const command = `cd ${agentPath} && /path/to/journal_venv/bin/python -m journal_organizer organize --date ${noteTitle}`; - -// 执行命令 -try { - const result = await tp.system.exec(command); - const output = JSON.parse(result); - - if (output.success) { - tp.obsidian.Notice.show(`✓ 成功整理日记 ${noteTitle}\n已生成 ${output.data.successful_writes} 个文件`); - } else { - tp.obsidian.Notice.show(`✗ 整理失败: ${output.message}`); - } -} catch (error) { - tp.obsidian.Notice.show(`✗ 执行错误: ${error.message}`); -} -%> -``` - -3. 根据您的环境修改 `agentPath` 和虚拟环境路径 - -### 步骤 3: 创建快捷键 - -1. 进入 `设置` > `快捷键` -2. 搜索 `Templater: Open Insert Template modal` -3. 为其分配一个快捷键,例如 `Ctrl+Alt+O` - -### 步骤 4: 使用 - -1. 打开或创建一个日记笔记(文件名应为日期格式,如 `2025-12-31.md`) -2. 按下快捷键打开模板选择器 -3. 选择 `OrganizeJournal` 模板 -4. Agent 将自动执行并整理日记 - -## 方案 2: 使用 QuickAdd 插件 - -### 步骤 1: 安装 QuickAdd 插件 - -1. 在 Obsidian 中进入 `设置` > `第三方插件` -2. 点击 `浏览社区插件`,搜索 `QuickAdd` 并安装 -3. 启用 QuickAdd 插件 - -### 步骤 2: 创建宏 - -1. 打开 QuickAdd 插件设置 -2. 点击 `Manage Macros` -3. 创建一个新的宏,例如 `OrganizeJournal` -4. 在宏中添加以下步骤: - - 类型:`User Script` - - 脚本内容: - -```javascript -module.exports = async (params) => { - const { app } = params; - const noteTitle = app.workspace.getActiveFile()?.basename; - - if (!noteTitle) { - new Notice("请先打开一个笔记"); - return; - } - - const agentPath = "/path/to/journal_organizer"; - const command = `cd ${agentPath} && /path/to/journal_venv/bin/python -m journal_organizer organize --date ${noteTitle}`; - - try { - const result = await require('child_process').execSync(command, { encoding: 'utf-8' }); - const output = JSON.parse(result); - - if (output.success) { - new Notice(`✓ 成功整理日记 ${noteTitle}`); - } else { - new Notice(`✗ 整理失败: ${output.message}`); - } - } catch (error) { - new Notice(`✗ 执行错误: ${error.message}`); - } -}; -``` - -### 步骤 3: 创建快捷键 - -1. 在 QuickAdd 设置中,为宏分配一个快捷键 - -### 步骤 4: 使用 - -1. 打开一个日记笔记 -2. 按下快捷键执行宏 -3. Agent 将自动整理日记 - -## 方案 3: 使用 Shell 命令(高级) - -如果您熟悉 Shell 脚本,可以创建一个更复杂的集成方案: - -### 创建 Shell 脚本 - -在 `/home/ubuntu/organize_journal.sh` 中创建以下脚本: - -```bash -#!/bin/bash - -# 日记整理 Agent 调用脚本 - -AGENT_PATH="/path/to/journal_organizer" -VENV_PATH="/path/to/journal_venv" -DATE="${1:-$(date +%Y-%m-%d)}" - -# 激活虚拟环境并运行 Agent -source "${VENV_PATH}/bin/activate" -cd "${AGENT_PATH}" - -# 执行 Agent -python -m journal_organizer organize --date "${DATE}" - -# 返回状态 -exit $? -``` - -### 在 Obsidian 中调用 - -在 Templater 或 QuickAdd 中使用以下命令: - -```javascript -const result = await tp.system.exec("/home/ubuntu/organize_journal.sh 2025-12-31"); -``` - -## 故障排除 - -### 问题 1: "找不到 python 模块" - -**解决方案**:确保虚拟环境路径正确,并且依赖已安装。 - -```bash -/path/to/journal_venv/bin/pip list | grep anthropic -``` - -### 问题 2: "API 密钥错误" - -**解决方案**:检查配置文件中的 API 密钥是否正确。 - -```bash -cat ~/.journal_organizer/config.yaml | grep api_key -``` - -### 问题 3: "找不到日记文件" - -**解决方案**:确保日记文件名格式正确(YYYY-MM-DD.md),并且在配置的文件夹中。 - -### 问题 4: "权限被拒绝" - -**解决方案**:确保脚本有执行权限。 - -```bash -chmod +x /home/ubuntu/organize_journal.sh -``` - -## 高级配置 - -### 定时自动整理 - -您可以使用系统的 cron 任务来定时运行 Agent: - -```bash -# 编辑 crontab -crontab -e - -# 添加以下行,每天晚上 10 点运行 -0 22 * * * /home/ubuntu/organize_journal.sh $(date +\%Y-\%m-\%d) -``` - -### 监听文件变化 - -使用 `watchdog` 库来监听日记文件的变化,并自动触发整理: - -```python -import time -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler -import subprocess - -class JournalHandler(FileSystemEventHandler): - def on_modified(self, event): - if event.src_path.endswith('.md'): - # 延迟 5 秒后执行,避免频繁触发 - time.sleep(5) - subprocess.run(['/home/ubuntu/organize_journal.sh']) - -observer = Observer() -observer.schedule(JournalHandler(), path='/path/to/Daily', recursive=False) -observer.start() - -try: - while True: - time.sleep(1) -except KeyboardInterrupt: - observer.stop() -observer.join() -``` - -## 最佳实践 - -1. **定期备份**:在整理前备份您的 Obsidian vault,以防万一。 -2. **测试配置**:在正式使用前,用一个测试日记进行测试。 -3. **监控日志**:定期检查 Agent 的日志文件,了解执行情况。 -4. **逐步扩展**:先用基础功能,然后根据需要添加更多的 Skills 和 Commands。 -5. **保护密钥**:不要在公开的地方暴露 API 密钥,使用环境变量或安全的配置管理工具。 - -## 常见场景 - -### 场景 1: 每天晚上自动整理 - -使用 cron 任务在每天晚上 10 点自动运行 Agent: - -```bash -0 22 * * * /home/ubuntu/organize_journal.sh -``` - -### 场景 2: 手动触发整理 - -在 Obsidian 中创建一个快捷键,按需整理日记。 - -### 场景 3: 批量整理历史日记 - -使用以下命令整理指定日期范围内的所有日记: - -```bash -for date in {1..31}; do - /home/ubuntu/organize_journal.sh "2025-12-$(printf "%02d" $date)" -done -``` - -## 下一步 - -- 查看 [README.md](README.md) 了解更多关于 Agent 的信息 -- 查看 [config.example.yaml](config.example.yaml) 了解配置选项 -- 根据需要扩展 Agent 功能,添加新的 Skills 和 Commands diff --git a/./README.md b/./README.md deleted file mode 100644 index 8327dfd..0000000 --- a/./README.md +++ /dev/null @@ -1,206 +0,0 @@ -# Obsidian 智能日记整理 Agent - -**版本**: 0.1.0 - -这是一个基于 Agent 架构的智能日记整理系统,专为 Obsidian 用户设计。它能够自动分析您的每日日记,提取关键信息(如经验、待办事项、问题等),并将其智能地整理到您的知识库中的指定位置。 - -该系统采用 Command + Skill 架构,具有高度的可扩展性和灵活性,并支持通过命令行触发,方便与 Obsidian 或其他工具集成。 - -## 系统特性 - -- **智能分析**:集成 Claude 3.5 Sonnet 模型,深度理解日记内容并提取结构化信息。 -- **自动化整理**:自动将提取的内容创建为新的笔记,并放置在预设的文件夹中。 -- **双向链接**:在生成的笔记中自动添加指向原始日记的链接,方便溯源。 -- **Agent 架构**:采用 Command + Skill 模式,逻辑清晰,易于扩展新功能。 -- **命令行驱动**:通过标准命令行接口(CLI)操作,易于集成和自动化。 -- **高度可配置**:所有关键参数(如 API 密钥、文件夹路径、分析规则)均可通过配置文件管理。 -- **通用性设计**:核心框架与具体实现分离,可兼容 Claude Code、OpenCode 等多种 Agent 环境。 - -## 系统架构 - -系统由以下核心组件构成: - -1. **Agent Core**:定义了 `Agent`、`Command` 和 `Skill` 的基础接口和交互逻辑。 -2. **Skills**:原子化的功能单元,负责执行具体任务,如 `ObsidianReadSkill`(读取笔记)和 `ClaudeAnalyzeSkill`(分析内容)。 -3. **Commands**:用户可执行的命令,负责编排一个或多个 Skill 来完成复杂任务,如 `OrganizeCommand`。 -4. **CLI 入口**:提供一个命令行界面,用于接收用户指令并驱动 Agent 执行相应命令。 -5. **配置文件**:使用 YAML 文件管理所有配置,实现代码与配置分离。 - -```mermaid -graph TD - subgraph User Interface - CLI[命令行接口] - Obsidian[Obsidian (via shell command)] - end - - subgraph Agent System - AgentCore[Agent Core] - CLI --> AgentCore - Obsidian --> AgentCore - - AgentCore -- dispatches --> Commands - subgraph Commands - OrganizeCmd[Organize Command] - end - - Commands -- orchestrates --> Skills - subgraph Skills - ReadNote[Obsidian Read Skill] - AnalyzeNote[Claude Analyze Skill] - WriteNote[Obsidian Write Skill] - end - end - - subgraph External Services - ObsidianAPI[Obsidian Local REST API] - ClaudeAPI[Claude API] - end - - ReadNote -- HTTP --> ObsidianAPI - WriteNote -- HTTP --> ObsidianAPI - AnalyzeNote -- HTTP --> ClaudeAPI -``` - -## 安装指南 - -### 1. 先决条件 - -- **Python 3.8+** -- **Obsidian** -- **Obsidian 插件**: `Local REST API` - -### 2. 安装 Local REST API 插件 - -1. 在 Obsidian 中,进入 `设置` > `第三方插件`。 -2. 关闭 `安全模式`。 -3. 点击 `浏览社区插件`,搜索 `Local REST API` 并安装。 -4. 启用插件,并在插件设置页面生成一个 API 密钥。请妥善保管此密钥。 - -### 3. 安装 Agent - -1. 克隆或下载本项目到您的本地计算机。 - - ```bash - git clone journal_organizer - cd journal_organizer - ``` - -2. 安装 Python 依赖。 - - ```bash - pip install -r requirements.txt - ``` - -## 配置指南 - -1. **复制配置文件** - - 将 `config.example.yaml` 复制为 `config.yaml`。 - - ```bash - cp config.example.yaml config.yaml - ``` - -2. **编辑配置文件** - - 打开 `config.yaml` 并根据您的环境填写以下关键信息: - - - `obsidian.vault_path`: 您的 Obsidian vault 在计算机上的绝对路径。 - - `obsidian.rest_api.api_key`: 您在 `Local REST API` 插件中生成的 API 密钥。 - - `claude.api_key`: 您的 Anthropic API 密钥。建议使用环境变量 `ANTHROPIC_API_KEY` 来设置。 - - `journal` 和 `output` 部分的文件夹路径,确保它们在您的 vault 中存在。 - - **安全提示**: 请勿将包含敏感密钥的 `config.yaml` 文件提交到公共代码仓库。 - -## 使用方法 - -您可以通过命令行在项目根目录下运行 Agent。 - -### 整理今天的日记 - -```bash -python -m journal_organizer organize -``` - -### 整理指定日期的日记 - -```bash -python -m journal_organizer organize --date 2025-12-31 -``` - -### 在 Obsidian 中调用 - -您可以使用 `Templater` 或 `QuickAdd` 等插件,通过执行 Shell 命令来调用 Agent。 - -例如,在 `Templater` 中可以这样设置: - -```javascript -<%* -const command = `cd /path/to/journal_organizer && python -m journal_organizer organize --date ` + tp.file.title; -const result = await tp.user.exec(command); -tp.obsidian.Notice.now(result, 10000); -%> -``` - -### 查看帮助 - -```bash -# 查看所有命令 -python -m journal_organizer --help - -# 查看特定命令的帮助 -python -m journal_organizer organize --help -``` - -## 项目结构 - -``` -journal_organizer/ -├── commands/ # Command 模块 -│ ├── __init__.py -│ └── organize_command.py # 日记整理命令 -├── skills/ # Skill 模块 -│ ├── __init__.py -│ ├── claude_skill.py # Claude AI 相关 Skill -│ └── obsidian_skill.py # Obsidian API 相关 Skill -├── __init__.py -├── agent_core.py # Agent 核心框架 -├── main.py # 命令行入口和 Agent 初始化 -├── config.example.yaml # 配置文件示例 -├── requirements.txt # Python 依赖 -└── README.md # 本文档 -``` - -## 如何扩展 - -本系统基于 Command + Skill 架构,您可以轻松地添加新的功能。 - -### 添加一个新的 Skill - -1. 在 `skills/` 目录下创建一个新的 Python 文件,例如 `my_new_skill.py`。 -2. 在该文件中,创建一个继承自 `Skill` 的类。 -3. 实现 `execute` 方法,该方法是 Skill 的核心逻辑。 - - ```python - from ..agent_core import Skill, SkillType, SkillResult, CommandContext - - class MyNewSkill(Skill): - def __init__(self): - super().__init__(name="my_new_skill", skill_type=SkillType.TRANSFORM, description="我的新技能") - - async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - # 在这里实现您的逻辑 - return SkillResult(success=True, data="新技能执行成功") - ``` - -### 添加一个新的 Command - -1. 在 `commands/` 目录下创建一个新的 Python 文件。 -2. 创建一个继承自 `Command` 的类。 -3. 在 `__init__` 方法中注册所需的 Skills。 -4. 实现 `execute` 方法,编排 Skills 来完成任务。 -5. 在 `main.py` 的 `_register_commands` 方法中注册您的新命令。 - -## 许可证 - -本项目采用 MIT 许可证。 diff --git a/./README_V2.md b/./README_V2.md deleted file mode 100644 index 189f6c0..0000000 --- a/./README_V2.md +++ /dev/null @@ -1,185 +0,0 @@ -# Obsidian 智能日记整理 Agent v2.0 - 对话式 - -**版本**: 2.0.0 - -这是一个基于 **对话式 Agent 架构** 的智能日记整理系统,专为 Obsidian 用户设计。它能够通过自然语言对话,理解您的需求,自动分析每日日记,提取关键信息,并将其智能地整理到您的知识库中。 - -## v2.0 新特性:对话式交互 - -- **自然语言理解**:您可以直接用自然语言与 Agent 对话,例如“帮我整理一下昨天的日记”。 -- **多轮对话**:支持上下文感知,可以进行多轮对话来澄清和确认您的需求。 -- **智能建议**:根据您的使用习惯,主动推荐可能的操作。 -- **交互式界面**:提供一个命令行聊天界面,支持实时交互和反馈。 - -## 系统架构 - -系统采用分层架构,将对话逻辑与业务执行分离: - -1. **对话层** (`conversation/`) - - **意图理解** (`intent_understanding.py`):使用 Claude API 理解用户意图。 - - **对话状态管理** (`conversation_state.py`):管理对话历史和上下文。 - - **响应生成** (`response_generator.py`):生成自然语言响应。 - - **对话式 Agent** (`conversational_agent.py`):协调对话层的所有模块。 - -2. **执行层** (Command + Skill) - - 沿用 v1.0 的 Command + Skill 架构,负责执行具体的业务逻辑。 - -```mermaid -graph TD - subgraph User Interface - ChatCLI[命令行聊天界面] - ObsidianPlugin[Obsidian 插件] - end - - subgraph Conversational Layer - ConvAgent[对话式 Agent] - ChatCLI --> ConvAgent - ObsidianPlugin --> ConvAgent - - ConvAgent -- uses --> IntentUnderstanding[意图理解] - ConvAgent -- uses --> ConversationState[对话状态管理] - ConvAgent -- uses --> ResponseGenerator[响应生成] - end - - subgraph Execution Layer - CommandAgent[Command Agent] - ConvAgent -- dispatches to --> CommandAgent - - CommandAgent -- orchestrates --> Skills[Skills] - end - - subgraph External Services - ClaudeAPI[Claude API] - ObsidianAPI[Obsidian Local REST API] - end - - IntentUnderstanding -- HTTP --> ClaudeAPI - ResponseGenerator -- HTTP --> ClaudeAPI - Skills -- HTTP --> ObsidianAPI -``` - -## 安装与配置 - -安装和配置过程与 v1.0 相同。请参考 [README.md](README.md)。 - -## 使用方法 - -### 启动交互式对话 - -```bash -python -m journal_organizer.chat_main -``` - -启动后,您将进入一个交互式的命令行聊天界面: - -``` -============================================================ -Obsidian 智能日记整理 Agent - 对话模式 -============================================================ - -🤖 助手: 👋 欢迎使用 Obsidian 日记整理助手!我可以帮您整理日记、分析内容、导出总结等。请告诉我您想要做什么? - -👤 您: -``` - -### 示例对话 - -``` -👤 您: 帮我整理一下昨天的日记 - -⏳ 处理中... - -🤖 助手: ✓ 已成功整理您昨天的日记。提取了 5 条经验、3 条待办事项和 2 个问题。 - -💡 您可以尝试: - 1. 分析本周的主题 - 2. 导出月度总结 - -👤 您: 分析一下这周的主题 - -⏳ 处理中... - -🤖 助手: ✓ 分析完成!本周的主题主要集中在项目管理和技术学习两个方面。 -``` - -### 系统命令 - -在对话界面中,您可以使用以下系统命令: - -- `help` / `帮助`:显示帮助信息 -- `history` / `历史`:显示对话历史 -- `status` / `状态`:显示当前状态 -- `clear` / `清除`:清除对话历史 -- `exit` / `quit` / `退出`:退出程序 - -### 处理单个查询 - -```bash -python -m journal_organizer.chat_main --query "整理今天的日记" -``` - -此命令将直接输出 JSON 格式的结果,方便脚本调用。 - -## 项目结构 - -``` -journal_organizer/ -├── conversation/ # 对话层模块 -│ ├── __init__.py -│ ├── conversational_agent.py -│ ├── conversation_state.py -│ ├── intent_understanding.py -│ └── response_generator.py -├── commands/ -├── skills/ -├── agent_core.py -├── main.py -├── chat_main.py # 对话式 Agent 入口 -├── ... (其他文件) -``` - -## 开发者指南 - -### 扩展对话能力 - -1. **添加新的意图**:在 `intent_understanding.py` 的 `command_keywords` 中添加新的命令和关键词。 -2. **添加新的参数提取**:在 `parameter_patterns` 中添加新的正则表达式。 -3. **自定义响应**:修改 `response_generator.py` 中的提示来改变响应风格。 - -### 集成到 Obsidian 插件 - -您可以使用 Obsidian 插件的 `Modal` 来创建一个对话窗口,并通过 `child_process` 调用 `chat_main.py` 来与 Agent 交互。 - -```typescript -// Obsidian 插件示例 -import { App, Modal, Plugin } from 'obsidian'; -import { spawn } from 'child_process'; - -class ChatModal extends Modal { - constructor(app: App) { - super(app); - } - - onOpen() { - // 创建对话界面 - // ... - } - - async sendMessage(message: string) { - const agentProcess = spawn('python', ['-m', 'journal_organizer.chat_main', '--query', message]); - - agentProcess.stdout.on('data', (data) => { - const response = JSON.parse(data.toString()); - // 显示响应 - }); - - agentProcess.stderr.on('data', (data) => { - // 处理错误 - }); - } -} -``` - -## 许可证 - -本项目采用 MIT 许可证。 diff --git a/./TROUBLESHOOTING.md b/./TROUBLESHOOTING.md deleted file mode 100644 index f95bbc1..0000000 --- a/./TROUBLESHOOTING.md +++ /dev/null @@ -1,427 +0,0 @@ -# 故障排除指南 - -本指南提供 Obsidian 智能日记整理 Agent 常见问题的解决方案和调试技巧。 - -## 快速诊断 - -运行以下命令进行快速系统检查: - -```bash -# 检查依赖项状态 -python -m journal_organizer check-deps - -# 测试基本功能 -python -m journal_organizer --help - -# 验证配置文件 -python -c " -import json -with open('config.yaml', 'r') as f: - print('配置文件存在且可读') -" -``` - -## 常见问题分类 - -### 🚀 启动问题 - -#### 问题 1: 模块导入错误 -``` -ImportError: attempted relative import with no known parent package -``` - -**解决方案**: -```bash -# ✅ 正确方式:作为模块运行 -python -m journal_organizer --help - -# ❌ 错误方式:直接运行脚本 -python main.py -``` - -**原因**: 项目使用相对导入,必须作为 Python 包运行。 - -#### 问题 2: 找不到模块规范 -``` -ValueError: __main__.__spec__ is None -``` - -**解决方案**: -确保在项目根目录运行命令,并且 `__init__.py` 文件存在: -```bash -ls -la __init__.py -pwd # 确认在正确目录 -``` - -### ⚙️ 配置问题 - -#### 问题 3: 配置文件不存在 -``` -ConfigurationError: Configuration file not found -``` - -**解决方案**: -1. 复制示例配置文件: -```bash -cp config.example.yaml config.yaml -``` - -2. 编辑配置文件,填入正确的值: -```yaml -obsidian: - vault_path: "/path/to/your/vault" - rest_api: - api_key: "your-obsidian-api-key" - -claude: - api_key: "${ANTHROPIC_API_KEY}" -``` - -#### 问题 4: 环境变量未设置 -``` -ConfigurationError: Environment variable ANTHROPIC_API_KEY not found -``` - -**解决方案**: -```bash -# 设置环境变量 -export ANTHROPIC_API_KEY="your-claude-api-key" -export OBSIDIAN_API_KEY="your-obsidian-api-key" - -# 验证设置 -echo $ANTHROPIC_API_KEY -``` - -#### 问题 5: 配置格式错误 -``` -yaml.scanner.ScannerError: mapping values are not allowed here -``` - -**解决方案**: -1. 检查 YAML 语法: -```bash -python -c " -import yaml -with open('config.yaml', 'r') as f: - yaml.safe_load(f) -print('YAML 格式正确') -" -``` - -2. 常见 YAML 错误: -```yaml -# ❌ 错误:缩进不一致 -obsidian: - vault_path: "/path" - rest_api: # 缩进错误 - api_key: "key" - -# ✅ 正确:一致的缩进 -obsidian: - vault_path: "/path" - rest_api: - api_key: "key" -``` - -### 🌐 API 连接问题 - -#### 问题 6: Claude API 连接失败 -``` -APIError: Failed to connect to Claude API -``` - -**诊断步骤**: -1. 测试 API 密钥: -```bash -curl -H "Authorization: Bearer $ANTHROPIC_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{"model":"claude-3-5-sonnet-20241022","max_tokens":10,"messages":[{"role":"user","content":"Hi"}]}' \ - https://api.anthropic.com/v1/messages -``` - -2. 检查网络连接: -```bash -ping api.anthropic.com -``` - -3. 验证 API 密钥格式: -```bash -echo $ANTHROPIC_API_KEY | grep -E "^sk-ant-" -``` - -#### 问题 7: Obsidian API 连接失败 -``` -APIError: Failed to connect to Obsidian Local REST API -``` - -**解决方案**: -1. 确认 Obsidian Local REST API 插件已安装并启用 -2. 检查 API 服务状态: -```bash -curl -k -H "Authorization: Bearer $OBSIDIAN_API_KEY" \ - https://localhost:27123/ -``` - -3. 验证配置: -```yaml -obsidian: - rest_api: - url: "https://localhost:27123" # 确认端口正确 - verify_ssl: false # 本地开发时禁用 SSL 验证 -``` - -### 📦 依赖项问题 - -#### 问题 8: 缺少依赖项 -``` -ModuleNotFoundError: No module named 'aiohttp' -``` - -**解决方案**: -```bash -# 安装所有依赖项 -pip install -r requirements.txt - -# 或单独安装缺失的包 -pip install aiohttp pyyaml anthropic - -# 检查安装状态 -python -m journal_organizer check-deps -``` - -#### 问题 9: 版本冲突 -``` -ImportError: cannot import name 'xxx' from 'yyy' -``` - -**解决方案**: -```bash -# 升级到兼容版本 -pip install --upgrade aiohttp anthropic - -# 或使用虚拟环境 -python -m venv venv -source venv/bin/activate # Linux/Mac -# 或 venv\Scripts\activate # Windows -pip install -r requirements.txt -``` - -### 🔐 权限问题 - -#### 问题 10: 文件权限错误 -``` -PermissionError: [Errno 13] Permission denied: 'config.yaml' -``` - -**解决方案**: -```bash -# 检查文件权限 -ls -la config.yaml - -# 修复权限 -chmod 644 config.yaml -chmod 755 . # 目录权限 -``` - -#### 问题 11: Vault 访问权限 -``` -PermissionError: Cannot access Obsidian vault -``` - -**解决方案**: -```bash -# 检查 vault 目录权限 -ls -la /path/to/obsidian/vault - -# 修复权限(谨慎操作) -chmod -R 755 /path/to/obsidian/vault -``` - -## 调试技巧 - -### 1. 启用详细日志 - -```bash -# 设置调试级别 -python -m journal_organizer --log-level DEBUG organize - -# 查看日志文件 -tail -f logs/journal_organizer.log -``` - -### 2. 分步调试 - -```python -# 在代码中添加调试点 -import logging -logger = logging.getLogger(__name__) - -logger.debug(f"配置内容: {config}") -logger.debug(f"API 响应: {response}") -``` - -### 3. 测试单个组件 - -```python -# 测试配置加载 -from journal_organizer.main import JournalOrganizerAgent -agent = JournalOrganizerAgent("config.yaml") -print(f"配置加载成功: {bool(agent.config)}") - -# 测试 API 连接 -import asyncio -from journal_organizer.skills.claude_skill import ClaudeAnalyzeSkill - -async def test_claude(): - skill = ClaudeAnalyzeSkill() - # 测试逻辑 - -asyncio.run(test_claude()) -``` - -### 4. 网络诊断 - -```bash -# 检查网络连接 -ping api.anthropic.com -ping localhost - -# 检查端口占用 -netstat -an | grep 27123 - -# 测试 SSL 连接 -openssl s_client -connect api.anthropic.com:443 -``` - -## 性能问题 - -### 内存使用过高 - -**诊断**: -```python -import psutil -import os - -process = psutil.Process(os.getpid()) -memory_mb = process.memory_info().rss / 1024 / 1024 -print(f"内存使用: {memory_mb:.2f} MB") -``` - -**解决方案**: -- 检查是否有内存泄漏 -- 限制并发操作数量 -- 使用 `gc.collect()` 强制垃圾回收 - -### API 调用缓慢 - -**诊断**: -```python -import time -import asyncio - -async def time_api_call(): - start = time.time() - result = await api_call() - duration = time.time() - start - print(f"API 调用耗时: {duration:.2f} 秒") - return result -``` - -**解决方案**: -- 检查网络延迟 -- 增加超时设置 -- 实现重试机制 -- 使用连接池 - -## 错误代码参考 - -| 错误代码 | 描述 | 常见原因 | 解决方案 | -|---------|------|----------|----------| -| CONFIG_001 | 配置文件不存在 | 未创建配置文件 | 复制 config.example.yaml | -| CONFIG_002 | 配置格式错误 | YAML 语法错误 | 检查缩进和语法 | -| CONFIG_003 | 缺少必需配置项 | 配置不完整 | 添加缺失的配置项 | -| CONFIG_004 | 环境变量未设置 | 环境变量缺失 | 设置相应的环境变量 | -| API_001 | API 密钥无效 | 密钥错误或过期 | 检查并更新 API 密钥 | -| API_002 | API 连接超时 | 网络问题 | 检查网络连接 | -| API_003 | API 限流 | 请求过于频繁 | 减少请求频率 | -| API_004 | SSL 证书错误 | 证书验证失败 | 禁用 SSL 验证(仅本地) | -| SKILL_001 | Skill 执行失败 | 输入参数错误 | 检查参数格式 | -| SKILL_002 | Skill 超时 | 操作耗时过长 | 增加超时设置 | -| IMPORT_001 | 模块导入错误 | 相对导入问题 | 使用模块方式运行 | -| IMPORT_002 | 依赖项缺失 | 包未安装 | 安装缺失的依赖项 | - -## 日志分析 - -### 常见日志模式 - -```bash -# 查找错误 -grep -i error logs/journal_organizer.log - -# 查找 API 调用 -grep -i "api" logs/journal_organizer.log - -# 查找配置问题 -grep -i "config" logs/journal_organizer.log - -# 实时监控 -tail -f logs/journal_organizer.log | grep -i error -``` - -### 日志级别说明 - -- **DEBUG**: 详细的调试信息 -- **INFO**: 一般信息,正常操作 -- **WARNING**: 警告信息,可能的问题 -- **ERROR**: 错误信息,操作失败 -- **CRITICAL**: 严重错误,系统无法继续 - -## 获取帮助 - -### 自助诊断清单 - -在寻求帮助前,请完成以下检查: - -- [ ] 运行 `python -m journal_organizer check-deps` -- [ ] 检查配置文件格式和内容 -- [ ] 验证环境变量设置 -- [ ] 查看日志文件中的错误信息 -- [ ] 测试网络连接和 API 访问 -- [ ] 确认文件和目录权限 - -### 报告问题时请提供 - -1. **错误信息**: 完整的错误堆栈跟踪 -2. **配置文件**: 脱敏后的配置内容 -3. **环境信息**: Python 版本、操作系统 -4. **日志文件**: 相关的日志片段 -5. **重现步骤**: 导致问题的具体操作 - -### 联系方式 - -- 查看项目文档 -- 检查 GitHub Issues -- 运行内置诊断工具 - -## 预防措施 - -### 定期维护 - -```bash -# 定期更新依赖项 -pip list --outdated -pip install --upgrade package_name - -# 清理日志文件 -find logs/ -name "*.log" -mtime +30 -delete - -# 备份配置文件 -cp config.yaml config.yaml.backup -``` - -### 监控建议 - -- 设置日志轮转 -- 监控内存和 CPU 使用 -- 定期测试 API 连接 -- 备份重要配置和数据 \ No newline at end of file diff --git a/./__init__.py b/./__init__.py deleted file mode 100644 index 74ee156..0000000 --- a/./__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Obsidian 智能日记整理 Agent -""" - -__version__ = "0.1.0" -__author__ = "Journal Organizer Team" - -from .agent_core import Agent, Command, Skill, SkillChain, SkillResult, CommandContext -from .main import JournalOrganizerAgent, run_command_sync - -__all__ = [ - "Agent", - "Command", - "Skill", - "SkillChain", - "SkillResult", - "CommandContext", - "JournalOrganizerAgent", - "run_command_sync", -] diff --git a/./__main__.py b/./__main__.py deleted file mode 100644 index 0811ab9..0000000 --- a/./__main__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -包的主入口点 -允许通过 python -m journal_organizer 运行 -""" - -import asyncio -import sys - -# Handle imports with both relative and absolute paths -try: - from .main import main -except ImportError: - # Fallback to absolute imports when running as script - from main import main - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code or 0) diff --git a/./__pycache__/__init__.cpython-310.pyc b/./__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index ceac0a44aa05affeec11750b384a379848ff9018..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 559 zcmaKoy>AmS7{+}U-$yP<=~CvCm~P3H9r2+or~^f`EM8uj*h(GzfqZwAWJT(Xgb-q+ z5<{1YfeFDs;uR^Be}M@*2_F-d{Ig&A)${m$%xF{~zg~Yi7Vjg3zJ=o775n0@-_3mw zh6rL2iC9cx)?--q6ed_DBvC0zRYo$ElU(&lKSKUJk%bzNff|xwgl34P=MfpP>PDq@|aWg!+#l7sY}+~E}IyosAeuXdMc!J#rl|>P@&ymeSgH8PPT4m+c+`0&%uE5?0N^>djWF65 z1pw{Z6Jr5LLBpKh!t(zMJFNT|f8~QVl@j0r*=xZw;OCwCapQgJQ=!?SerU~{Pur$8 t+o0>Yt>>F0!Te6L1`t}b0OMy1m)}x`b&}r>$u>g|j4;Mg8Sh5J_!nTwoumK& diff --git a/./__pycache__/__init__.cpython-311.pyc b/./__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 4a6fa8353e3a6632531ea3efad5cadddf8dba5b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 649 zcmb7=ziX5+7{`kS9SF`Y zQXCbebP)$9!T+H+l}v(g*6oFEolNeBE+UwOPo9t`-*2Ahq|?cO8(-`F@+}15yFaW( zunaDDTyP91K!8F7X@COSq9JdI5QQQ_k%&<&5|oG(rJ{}65V-q@XCgX+UniyZA~_J zl+lv#?yepYSx%Vl?lU5Ox(|aQw$VJ>CIck|k<>a#*Cg{TgU=z?fRf1U9 zSVt`K2Y$d>>XJ=wWg*T61i=gUoO`*4!?|*9&H<)AU-$T%;rDX+Ti7fcruCKq3Vc8z zMG^Gjl#!1prdEVw>|+R+MJvZhEiu@yFnk3D(62Joud!-tk-`ClfTG=LH>$~|F?8~7 zXWYB~7{274hy3<_eEIC;or~gWI63bXqZ_xCwvVK6mt(gn6D5L(NOc$pe#VqrjP8m- z*O?d}uswkW8!)rtfQ!r~q+~)Gc2*;V%8-z(7U)!jTxM9OT9uELIuXRo9I;A-Kf_LW zn}m|G%qqu6pk|G13SaBqzGg~$CtT3f+flN~!nXG0f##G4!fPt;UqB?A2J( YBw}l2R6TFN0(QlQb!4MDT!7a14*|=2IRF3v diff --git a/./__pycache__/__main__.cpython-311.pyc b/./__pycache__/__main__.cpython-311.pyc deleted file mode 100644 index d346add1fddfc0997c9b66d7b0a258782d29ca1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmZ3^%ge<81oovNnYV%TV-N=hSfGs0T|mZkh7^V|v#E{CE#R}EW0OT<*19Dcw1Q?bv0(n3T0a0u~6--&|Fn%gq z0ni3iHH-`?Ou=BR$yVwDRLS+Ur}g=)mM1%QKW$z5w14sQrkz|*TN_`j+wrnt){Fh^ z3I&xV8Tok%y15Ej`K3j9i8=B4Md^uonN_Jp3NQ9gc(JgD>m|rWzn36~Xfobn$xY16 zyTuV752EAaH92mv$H%ASC&$O%Voxlt%uCM9zr|c!SzH7P7?8TW#N1S%By&+|-Yw45 zip-MuTPz^vN`}uM!+&Y%XXNLm>X#;!=9QG{W4J^gW}99?6Ym2uM$0pTa%EWroX4w>fSX1(dG{C^vZCVBu+SZt>`d?1-D8c#%cs3X9AI82Z2p M)CbmF#0PW(0G&#kx&QzG diff --git a/./__pycache__/agent_core.cpython-310.pyc b/./__pycache__/agent_core.cpython-310.pyc deleted file mode 100644 index 9023d17919d590f1e3bcf490d5fe3d067a96d7a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11266 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 SSk>0yQ=d#`^ diff --git a/./__pycache__/agent_core.cpython-311.pyc b/./__pycache__/agent_core.cpython-311.pyc deleted file mode 100644 index db783106501f9ccd99a3f7f92c0483059ad52ec3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16847 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 diff --git a/./__pycache__/api_response_validation.cpython-310.pyc b/./__pycache__/api_response_validation.cpython-310.pyc deleted file mode 100644 index 4a8ab4fa17dc3510701c1f796b52929a65973ca0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14590 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 diff --git a/./__pycache__/chat_main.cpython-310.pyc b/./__pycache__/chat_main.cpython-310.pyc deleted file mode 100644 index 11ae2c5d3a87cb269933290fe25a6df0bd0e5aef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6242 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 diff --git a/./__pycache__/claude_api_client.cpython-310.pyc b/./__pycache__/claude_api_client.cpython-310.pyc deleted file mode 100644 index 94f53c3644fd3c9390e5f2b18ea61caa0ef7fae5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11109 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 E0rum;XaE2J diff --git a/./__pycache__/config.cpython-310.pyc b/./__pycache__/config.cpython-310.pyc deleted file mode 100644 index 33a845ba54d09871951f4e4cea1541615028a31e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9948 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 diff --git a/./__pycache__/config_validation.cpython-310.pyc b/./__pycache__/config_validation.cpython-310.pyc deleted file mode 100644 index 593554a4fb4e13b8597dcb44a3dcea2a5180e506..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40820 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 diff --git a/./__pycache__/configuration_migrator.cpython-310.pyc b/./__pycache__/configuration_migrator.cpython-310.pyc deleted file mode 100644 index c7157a31aaf215a5757ccd6c92ec00cada79e074..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8628 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 diff --git a/./__pycache__/date_validation.cpython-310.pyc b/./__pycache__/date_validation.cpython-310.pyc deleted file mode 100644 index 3cd644f559c90a51e2cc0508224c8ee41205fe06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15072 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 diff --git a/./__pycache__/dependency_manager.cpython-310.pyc b/./__pycache__/dependency_manager.cpython-310.pyc deleted file mode 100644 index 64787105e33d1d19f357b599125197419ee11053..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11303 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 q%8S9%0A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 diff --git a/./__pycache__/input_validation.cpython-310.pyc b/./__pycache__/input_validation.cpython-310.pyc deleted file mode 100644 index 7ea192291e5b2baa52949f38bacb0f8268022049..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25762 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 WB2_D4ml9|O$gxC2Ib0YI*8c)rY9Q?Z diff --git a/./__pycache__/path_security.cpython-310.pyc b/./__pycache__/path_security.cpython-310.pyc deleted file mode 100644 index 1ca056129dbe5c84dd17e68dae716ca209cfc2f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14254 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 P;|KJw`djupW&M8v#drd8 diff --git a/./__pycache__/server.cpython-310.pyc b/./__pycache__/server.cpython-310.pyc deleted file mode 100644 index 6a11d38ca2396b25e4515d14b1ea2545d5c1ec6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6328 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 KYb{YCfAv58O0?Ag diff --git a/./__pycache__/test_application_startup.cpython-310-pytest-7.3.1.pyc b/./__pycache__/test_application_startup.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index 645bccb0831429aee986c3434a3c14f227bd6c0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8742 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 It+g-z2Mw%Zod5s; diff --git a/./__pycache__/test_basic_functionality.cpython-310-pytest-7.3.1.pyc b/./__pycache__/test_basic_functionality.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index f4fda41afe041add5c5d063f3a581900650be6f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14412 zcmeHOYm6Mnah{o-ea${@?|nxdrIBRHTs@8=De4uG5JizP9f?wOXDM1(S?}%49hcnO zJ@w2=yzwk0i?VFla>5`%fW#7VR1hN!0wf0VBZ(2fNDv?h@_-W~K*s-K#OeS+HR?2DEK?_oyAsARg~}2L-J3@!-Ke- zzd&LsHN{dbt*zEHzNc1oO;MHAbSg&!Czb8t6K6tV?5MwT1|7g z*J-+~Zl}?1x$EZhxLFN)HeK6s=Z!_Aj@j%A8_DWgw_|tk?4EA8X0y>Tmu$0zQsLT` z+2~kNd)t~f-o}V2r>bh8KhbbcSM@-DxYcxn)X~m*pg-Dj+#tQ!TWi~1bA%i!TbTn+|K zv6Y&N?}+bLOIezg!f5H1Zl~>xowYN|saa)NqfvfE#~0jOp0#o)&u=NuTL#LFE#(EP zi1Nag@{(0Xd2vhmh*d#(X-oO2HHPx?mhy3H0_7tpuO#1q-(k|4Lft6pGI*nDYX(na z*6e~(-4#r29X0yS-ffhcOWVwg?(*h2(8OTIf0Wm79mD02My)8mQisAUD3?{514Wum zbzwWbw(HQGaP0Q-Wkr-gd*$(cpK@&B?0ccrvDWuJA-ZSmrt9qME;%i$)#&UyBlex? z_5`MAy(?B4ot9^deaw(Wtq)D%`L*>RSFg7^Ew^5uBY8+LL-lUh{-x(5+8`rr7rhL< z*eJEd#V?jg0Rz;&dQtV3XzsL?zUFHi>LsP0vQmBBPx*RJ?Wg^;bJEo|QkL$gmsKlG z<;&V7-O9N7M*5QSyy8?*pYc^b;v(}v4iqCyokx$AH*`pAgwh(3wEetW z_H!51zHVv#g0K69vwFYi7oB&|GG&eWMU2@PmGhX5$Nl{&_g6-Lf6o0OhyKPBGJl&W zSmu8qib!V8V^sN0V==*!mGGRiCh1~S!-8<>KOB? zFx4lR7xJLm#VxKn-o9#GXU$>MM=q#eRjl1_rj}DrLp6299lfZozpN_#imG@O&|Poc z;FkOf(Y>h=RYrM6!Q8H#ufTHf{RQ;}rD}S67%MD$&F)xsr@3yfHaY-lF>Fx0>4ygt zkA)P}QV?XEx~yKwv>db3buX*l)SkU->vzHi$PyTWwe^ElCn%tkddU5<7NkH#$w6;V zf_K!rbGqAWTf~uU(<#`iD;48N!vVm!%<&L7qV*xqTyPt1&oPBf_R9<#qEW+yc&*OL zm3xL7-c@ys&yq~Yn1`7cgxyOe`>W3FTyOw3%IF71%V~8Sx6x_ZK?!$s1MH)2f)sdP z*2NUwp2XNs$pK34s;XDiBURPg7rwXb3LG)XR-l+qUjEt7E|!IEPa!`uM@ritSR5o6C&{*Vfq{G(<33 zZ#LR(8g4-gmWHr{BC6|4y;j@BR|zuhE-0K}rY=7@lh#+--6hx;T6F~Zy3<&;i37OF zg+^V@pjBrjC?Dm?geB5(A-W?JM}R9K+5lwux8KJ1qd@;&ZKKHM$HJ|<}o zCaVu6Yla?%xZIO$bDy|{SgJN#cA1o^WST|Z`t4}KI@cr7HC@Z8xzx+J-ctVn>F=pC zT17L}+)LUIz4;xi_YQ1#HsUi_4v-P|zLzxN7R*CG1!IoIA96Z!DJz3q8abVo+Wm}N zXp=Gb(|%@Ie^`ML%erYli+j$`IX^?oZ#T1%WfOm|o7>2{;3f_uXHX7sU7$RfZoZRg zFTRn2nasOXQ}hAkvDwM?b1Y-2Z}`RrmVc0<0C`4y7_olQD)mcl#V`6~?8|=Hd5g_+ z)c-l~%eycVqECHM4*g?^8B6#YQTVh7_<5qB94IQBhcV|n%%C;O=5i#I5XLE%u+%SG zW1FP-ry(i6L4B|k-z0w9Nb#3M!BTvSd;CyRz;t0TJY?1i3A2X9use1QW^LNry+})7 zn>5=zjU7#!FmPEw)d3`gLWv{Ci81w{y)>hEhp(nrEL}YshWQ61avqtFd3m=dEbCPaWM~&tK;~+S zjh`?ffw&P#P~i^jI!36@JzhDK;0k*!Eg>>nEul2KFp^|;v!@$Q!*xYaN(gR8=5}Bk z#VmC@9d zp>FfAFpnZ{i=fCXcmmr(e3EEs>aaNq?TfgZ3Qi%Z>6l9UIX%GXK~C=y_Y%cVQStyK z3rNVYQV-&%spK9?j!<$xB}XZt&0fMJjMHGrF^}gM@pV3iMAtN!10!Xq^zUjp&b_L= zsul-E;nr)PyhA#cMF;V&P8sa#>XAvX^nS`!ea%k+T6JG{9wDCqR@y0s_T98DcU1uA zO~_>^2jI+D*_dLUD0~eR2Z@4mpvZC_dp*9>J}<}6ns%us=fgzA(2D(}mER=AmxiSH z4BAL3Y~r_#6weU_OYuBWd`KybO;Y^TkQBcWOYvJfNb&oz6o2>;q$mtafqm2HkR1OK z%kjM(Q0VEC|Wj}2cQ`?(ik z_~4J^{T!Kl_zkw~=Q8Iryr09J_j9A6;gdEi#G%NWhn1(ddkCS-0U{+0U-|LSWh05j zNm6)%m|#>gt`ERgJWA!oW9>%IvL$^`Tv&HpdsRNwW1kB01lkIhx0eg*VbH-UfwO z9~gyL8$KioZN|dahNP&+Qdm1ku|^auMUN;xoD{xxCXbDPL`3-SLhJBtaKzF-^Z*U) z#BWodJT|{eea2%$o+oKZzCjc|HX7oaL_s;&3G&micaZPzYbSsK@XN&*P+0!~5Fi^O zK*bt`zY1HFO&*=>`79#zqZC=L3Lye!tgS+@X7O)k3cLN$xAr5@Pf z-$8lW#Tk|OIw>>d$PVLdDYDdmOcdm}6MsS!lmi8wheZ^B%{;IQ5P!!!kOKvsm+_fY zcpjDy_j<9T4{{AdKnZJjSns}(0D-b^Yykn|yul!Vdk6w!3<7I7ZZ{pf*%LU?_r~b- z-CPJClK?Og>YZJu7VPtO*Aoa(lEq<5*8f_`!SSn$16Xe3mG-%-YkU+t@1AqLJ=|A9 zorm>*soQPajgBC%mULQ?mIXSM3-SRkdgII?5_vFz|kC2M%Kl4nre&5uJvsdGc+L+126Icq_S@uU-uvhPdqv z4pR|0{M%2!VH>B4(Ne6!8`_Q#@y}4$q6IIsRf6>%001rFB}9-zBZ7z|0MHU%Ld2gD zMFe&(O} zRuf)#cu9Y>zEya8XbotMe1*(K=8VzL;?Pz15u?>Zq?Mn8gkJ-A7ig1*E#oNF@Ro5b zf_zYXfk231$Jn*cxr^c$;1j2LMAy$Rd0KgH7IA&0_0K~0(%3=XLVZ|>M27aI zqdg?hO!fj)Qt2H=tdframyjbo+{{sz91#In?x)YEpPQ9;+HF#M#1fBqH$K_xm^jxr zm$nHTrqzhkyA|W~AHV)#EC1@j34$nY+N~zJ(}0OJTVE|nWw51@e~RI zz12Z%)I-RMrz!a~B}mkJ8tlyAyQ?b4MxEh)R4N6LZX) zcmx422&l06mkeQ;=g_;Q@H$wa%1|5HAaJOSf&TF(P*zPK%q9%CQ4z_m3G(ip;*WDV zB_75@pw=by4Sz6k0(IZV^`?*PH(lEXJ-r)h6ItbzE)gg8KNht*7Auw&@_7T@Re;5 zn;}-O;ZGBcY#Q&b7d+cJ*E`~JD_rkuI99v)XeP6+|1<6#LPI-$+?-a8Ttckd(-eUY+jkzn(}BN}bL{WTdSj0@TJncV zan$(-Q*dWU!NqHMJvn+`igSY@B%PjxH4?8Q<4v!k?`^b|tq}u-xB$9S)dG*{;PV9O zHAI)Y;uok3UMG=)o_M%;8-tN)#aGe3$X&$^7c9W9;n&1>;NHct)zjCKX8zHKq~1GjFsY0e4Vb|l*UlS|3!5M aK3iSY)XaN^UQQ`5=AO?@WH5mGd;bSTb}jAz diff --git a/./__pycache__/test_config_loader_integration.cpython-310-pytest-7.3.1.pyc b/./__pycache__/test_config_loader_integration.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index f57492a1b3fea6c90071afeb5a1eb9af044474ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6804 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 BKXm{A diff --git a/./__pycache__/test_performance_reliability.cpython-310-pytest-7.3.1.pyc b/./__pycache__/test_performance_reliability.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index 969ef5eef4671ef48ef4b6bdf8b60f91a88b44d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14108 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 LGdku#d;h-xf7Q`O diff --git a/./__pycache__/test_startup_validation.cpython-310-pytest-7.3.1.pyc b/./__pycache__/test_startup_validation.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index 2957ac381cd898d5e8df9fe39a364541ea92f3a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18057 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 nC None: - """Validate SkillResult consistency after initialization""" - if not self.success and not self.error: - raise ValueError("Failed results must include error message") - if self.success and self.error: - raise ValueError("Successful results should not include error message") - - def to_dict(self) -> Dict[str, Any]: - """转换为字典""" - return asdict(self) - - def to_json(self) -> str: - """转换为 JSON 字符串""" - return json.dumps(self.to_dict(), ensure_ascii=False, indent=2) - - -@dataclass -class CommandContext: - """命令执行上下文""" - - command_name: str - args: Dict[str, Any] = field(default_factory=dict) - options: Dict[str, Any] = field(default_factory=dict) - config: Optional[Dict[str, Any]] = None - metadata: Dict[str, Any] = field(default_factory=dict) - - def to_dict(self) -> Dict[str, Any]: - """转换为字典""" - return asdict(self) - - -class Skill(ABC): - """Skill 基础类""" - - def __init__(self, name: str, skill_type: SkillType, description: str = "") -> None: - """ - 初始化 Skill - - Args: - name: Skill 名称 - skill_type: Skill 类型 - description: Skill 描述 - """ - self.name = name - self.skill_type = skill_type - self.description = description - self.logger = logging.getLogger(f"Skill.{name}") - - @abstractmethod - async def execute(self, context: CommandContext, **kwargs: Any) -> SkillResult: - """ - 执行 Skill - - Args: - context: 命令执行上下文 - **kwargs: 额外参数 - - Returns: - SkillResult: 执行结果 - """ - pass - - def get_info(self) -> Dict[str, Any]: - """获取 Skill 信息""" - return { - "name": self.name, - "type": self.skill_type.value, - "description": self.description, - } - - -class SkillChain: - """Skill 链 - 用于按顺序执行多个 Skill""" - - def __init__(self, name: str, description: str = "") -> None: - """ - 初始化 Skill 链 - - Args: - name: 链名称 - description: 链描述 - """ - self.name = name - self.description = description - self.skills: List[Tuple[Skill, Dict[str, Any]]] = [] - self.logger = logging.getLogger(f"SkillChain.{name}") - - def add_skill( - self, skill: Skill, params: Optional[Dict[str, Any]] = None - ) -> "SkillChain": - """ - 添加 Skill 到链中 - - Args: - skill: 要添加的 Skill - params: Skill 参数 - - Returns: - self 用于链式调用 - """ - self.skills.append((skill, params or {})) - return self - - async def execute(self, context: CommandContext) -> SkillResult: - """ - 执行 Skill 链 - - Args: - context: 命令执行上下文 - - Returns: - SkillResult: 最后一个 Skill 的结果 - """ - result: Optional[SkillResult] = None - - for skill, params in self.skills: - try: - self.logger.info(f"执行 Skill: {skill.name}") - result = await skill.execute(context, **params) - - if not result.success: - self.logger.error(f"Skill {skill.name} 执行失败: {result.error}") - return result - - # 将结果传递给下一个 Skill - if result.data: - context.metadata[f"{skill.name}_result"] = result.data - - except Exception as e: - self.logger.error(f"执行 Skill {skill.name} 时出错: {str(e)}") - return SkillResult( - success=False, error=str(e), message=f"Skill {skill.name} 执行异常" - ) - - return result or SkillResult(success=True, message="Skill 链执行完成") - - def get_info(self) -> Dict[str, Any]: - """获取 Skill 链信息""" - return { - "name": self.name, - "description": self.description, - "skills": [skill.get_info() for skill, _ in self.skills], - } - - -class Command(ABC): - """Command 基础类""" - - def __init__( - self, name: str, description: str = "", aliases: Optional[List[str]] = None - ) -> None: - """ - 初始化 Command - - Args: - name: 命令名称 - description: 命令描述 - aliases: 命令别名 - """ - self.name = name - self.description = description - self.aliases = aliases or [] - self.skills: Dict[str, Skill] = {} - self.skill_chains: Dict[str, SkillChain] = {} - self.logger = logging.getLogger(f"Command.{name}") - - def register_skill(self, skill: Skill) -> "Command": - """ - 注册 Skill - - Args: - skill: 要注册的 Skill - - Returns: - self 用于链式调用 - """ - self.skills[skill.name] = skill - return self - - def register_skill_chain(self, chain: SkillChain) -> "Command": - """ - 注册 Skill 链 - - Args: - chain: 要注册的 Skill 链 - - Returns: - self 用于链式调用 - """ - self.skill_chains[chain.name] = chain - return self - - @abstractmethod - async def execute(self, context: CommandContext) -> SkillResult: - """ - 执行命令 - - Args: - context: 命令执行上下文 - - Returns: - SkillResult: 执行结果 - """ - pass - - def get_info(self) -> Dict[str, Any]: - """获取命令信息""" - return { - "name": self.name, - "description": self.description, - "aliases": self.aliases, - "skills": {name: skill.get_info() for name, skill in self.skills.items()}, - "skill_chains": { - name: chain.get_info() for name, chain in self.skill_chains.items() - }, - } - - -class Agent: - """Agent 核心类""" - - def __init__(self, name: str, config: Optional[Dict[str, Any]] = None) -> None: - """ - 初始化 Agent - - Args: - name: Agent 名称 - config: 配置字典 - """ - self.name = name - self.config = config or {} - self.commands: Dict[str, Command] = {} - self.command_aliases: Dict[str, str] = {} - self.logger = logging.getLogger(f"Agent.{name}") - self._setup_logging() - - def _setup_logging(self) -> None: - """设置日志""" - log_level = self.config.get("log_level", "INFO") - logging.basicConfig( - level=getattr(logging, log_level), - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - ) - - def register_command(self, command: Command) -> "Agent": - """ - 注册命令 - - Args: - command: 要注册的命令 - - Returns: - self 用于链式调用 - """ - self.commands[command.name] = command - - # 注册别名 - for alias in command.aliases: - self.command_aliases[alias] = command.name - - self.logger.info(f"注册命令: {command.name}") - return self - - async def execute_command( - self, - command_name: str, - args: Optional[Dict[str, Any]] = None, - options: Optional[Dict[str, Any]] = None, - ) -> SkillResult: - """ - 执行命令 - - Args: - command_name: 命令名称或别名 - args: 命令参数 - options: 命令选项 - - Returns: - SkillResult: 执行结果 - """ - # 解析命令名称(处理别名) - actual_command_name = self.command_aliases.get(command_name, command_name) - - if actual_command_name not in self.commands: - return SkillResult( - success=False, - error=f"未知命令: {command_name}", - message=f"命令 '{command_name}' 不存在", - ) - - command = self.commands[actual_command_name] - context = CommandContext( - command_name=actual_command_name, - args=args or {}, - options=options or {}, - config=self.config, - ) - - self.logger.info(f"执行命令: {actual_command_name}, 参数: {args}, 选项: {options}") - - try: - result = await command.execute(context) - self.logger.info(f"命令 {actual_command_name} 执行完成: {result.success}") - return result - except Exception as e: - self.logger.error(f"执行命令 {actual_command_name} 时出错: {str(e)}") - return SkillResult(success=False, error=str(e), message=f"命令执行异常: {str(e)}") - - def get_commands_info(self) -> Dict[str, Any]: - """获取所有命令信息""" - return { - "agent_name": self.name, - "commands": {name: cmd.get_info() for name, cmd in self.commands.items()}, - "aliases": self.command_aliases, - } - - def list_commands(self) -> List[str]: - """列出所有可用命令""" - return list(self.commands.keys()) diff --git a/./api_response_validation.py b/./api_response_validation.py deleted file mode 100644 index d888dc6..0000000 --- a/./api_response_validation.py +++ /dev/null @@ -1,657 +0,0 @@ -""" -API response validation and sanitization utilities -Provides comprehensive validation for API responses to handle malformed data gracefully -""" - -import json -import logging -import re -from typing import Any, Dict, List, Optional, Union, Tuple, Callable - -try: - from .error_handling import ValidationError, APIError -except ImportError: - from error_handling import ValidationError, APIError - - -class APIResponseValidator: - """Comprehensive API response validation and sanitization""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - - # Response size limits - self.max_response_size = 10 * 1024 * 1024 # 10MB - self.max_json_depth = 32 - self.max_array_length = 10000 - self.max_string_length = 1000000 # 1MB for individual strings - - # Content type patterns - self.json_content_types = [ - 'application/json', - 'application/vnd.api+json', - 'text/json' - ] - - self.text_content_types = [ - 'text/plain', - 'text/html', - 'text/markdown', - 'text/xml' - ] - - def validate_http_response( - self, - response: Any, - expected_status_codes: Optional[List[int]] = None, - expected_content_type: Optional[str] = None, - max_size: Optional[int] = None - ) -> Dict[str, Any]: - """ - Validate HTTP response object (aiohttp.ClientResponse or similar) - - Args: - response: HTTP response object - expected_status_codes: List of acceptable status codes - expected_content_type: Expected content type - max_size: Maximum response size in bytes - - Returns: - Dictionary with validation results - - Raises: - APIError: If response validation fails - """ - validation_result = { - 'valid': True, - 'status_code': None, - 'content_type': None, - 'content_length': None, - 'warnings': [] - } - - try: - # Check if response object has expected attributes - if not hasattr(response, 'status'): - raise APIError( - message="Response object missing 'status' attribute", - api_name="unknown" - ) - - validation_result['status_code'] = response.status - - # Validate status code - if expected_status_codes and response.status not in expected_status_codes: - raise APIError( - message=f"Unexpected status code: {response.status} (expected: {expected_status_codes})", - api_name="unknown", - status_code=response.status - ) - - # Check content type if available - if hasattr(response, 'headers') and 'content-type' in response.headers: - content_type = response.headers['content-type'].split(';')[0].strip().lower() - validation_result['content_type'] = content_type - - if expected_content_type and not content_type.startswith(expected_content_type.lower()): - validation_result['warnings'].append( - f"Unexpected content type: {content_type} (expected: {expected_content_type})" - ) - - # Check content length if available - if hasattr(response, 'headers') and 'content-length' in response.headers: - try: - content_length = int(response.headers['content-length']) - validation_result['content_length'] = content_length - - max_allowed = max_size or self.max_response_size - if content_length > max_allowed: - raise APIError( - message=f"Response too large: {content_length} bytes (max: {max_allowed})", - api_name="unknown" - ) - except ValueError: - validation_result['warnings'].append("Invalid content-length header") - - return validation_result - - except Exception as e: - if isinstance(e, APIError): - raise - else: - raise APIError( - message=f"Response validation error: {str(e)}", - api_name="unknown", - cause=e - ) - - def validate_json_response( - self, - json_data: Any, - schema: Optional[Dict[str, Any]] = None, - api_name: str = "unknown" - ) -> Dict[str, Any]: - """ - Validate JSON response data with optional schema validation - - Args: - json_data: Parsed JSON data to validate - schema: Optional schema definition for validation - api_name: Name of the API for error context - - Returns: - Validated and sanitized JSON data - - Raises: - APIError: If validation fails - """ - try: - # Basic structure validation - self._validate_json_structure(json_data, api_name) - - # Schema validation if provided - if schema: - self._validate_json_schema(json_data, schema, api_name) - - # Sanitize the data - sanitized_data = self._sanitize_json_data(json_data) - - return sanitized_data - - except Exception as e: - if isinstance(e, (APIError, ValidationError)): - raise - else: - raise APIError( - message=f"JSON validation error: {str(e)}", - api_name=api_name, - cause=e - ) - - def validate_text_response( - self, - text_data: str, - max_length: Optional[int] = None, - allowed_patterns: Optional[List[str]] = None, - forbidden_patterns: Optional[List[str]] = None, - api_name: str = "unknown" - ) -> str: - """ - Validate text response with content checks - - Args: - text_data: Text response to validate - max_length: Maximum allowed text length - allowed_patterns: List of regex patterns that must be present - forbidden_patterns: List of regex patterns that must not be present - api_name: Name of the API for error context - - Returns: - Validated and sanitized text - - Raises: - APIError: If validation fails - """ - if not isinstance(text_data, str): - raise APIError( - message="Response data must be a string", - api_name=api_name - ) - - # Length validation - max_len = max_length or self.max_string_length - if len(text_data) > max_len: - raise APIError( - message=f"Response text too long: {len(text_data)} characters (max: {max_len})", - api_name=api_name - ) - - # Pattern validation - if allowed_patterns: - for pattern in allowed_patterns: - if not re.search(pattern, text_data, re.IGNORECASE | re.DOTALL): - raise APIError( - message=f"Response missing required pattern: {pattern}", - api_name=api_name - ) - - if forbidden_patterns: - for pattern in forbidden_patterns: - if re.search(pattern, text_data, re.IGNORECASE | re.DOTALL): - raise APIError( - message=f"Response contains forbidden pattern: {pattern}", - api_name=api_name - ) - - # Sanitize the text - sanitized_text = self._sanitize_text_data(text_data) - - return sanitized_text - - def parse_and_validate_json( - self, - response_text: str, - schema: Optional[Dict[str, Any]] = None, - api_name: str = "unknown" - ) -> Dict[str, Any]: - """ - Parse JSON response text and validate the result - - Args: - response_text: Raw response text to parse - schema: Optional schema for validation - api_name: Name of the API for error context - - Returns: - Parsed and validated JSON data - - Raises: - APIError: If parsing or validation fails - """ - # Basic text validation first - if not isinstance(response_text, str): - raise APIError( - message="Response must be a string", - api_name=api_name - ) - - if len(response_text) > self.max_response_size: - raise APIError( - message=f"Response too large: {len(response_text)} bytes", - api_name=api_name - ) - - # Try to parse JSON - try: - json_data = json.loads(response_text) - except json.JSONDecodeError as e: - # Try to extract JSON from response if it's embedded - json_data = self._extract_json_from_text(response_text, api_name) - if json_data is None: - raise APIError( - message=f"Invalid JSON response: {str(e)}", - api_name=api_name, - response_data=response_text[:500], # First 500 chars for debugging - cause=e - ) - - # Validate the parsed JSON - return self.validate_json_response(json_data, schema, api_name) - - def validate_obsidian_api_response( - self, - response_data: Any, - operation: str = "unknown" - ) -> Dict[str, Any]: - """ - Validate Obsidian API response with operation-specific checks - - Args: - response_data: Response data to validate - operation: Type of operation (read, write, list, etc.) - - Returns: - Validated response data - - Raises: - APIError: If validation fails - """ - api_name = "obsidian" - - if operation == "read": - # For read operations, expect text content - if not isinstance(response_data, str): - raise APIError( - message="Obsidian read response must be text", - api_name=api_name - ) - - # Validate as text with reasonable limits - return { - 'content': self.validate_text_response( - response_data, - max_length=10 * 1024 * 1024, # 10MB for note content - api_name=api_name - ), - 'length': len(response_data) - } - - elif operation == "write": - # Write operations might return status info - if isinstance(response_data, str): - # Simple text response - return {'message': response_data} - elif isinstance(response_data, dict): - # Structured response - return self.validate_json_response(response_data, api_name=api_name) - else: - # Assume success if no specific response - return {'success': True} - - elif operation == "list": - # List operations should return array or object with files - if isinstance(response_data, list): - # Validate as array of file info - validated_files = [] - for item in response_data: - if isinstance(item, str): - # Simple filename - validated_files.append(self._sanitize_filename(item)) - elif isinstance(item, dict): - # File info object - validated_files.append(self._validate_file_info(item, api_name)) - else: - self.logger.warning(f"Unexpected file list item type: {type(item)}") - - return {'files': validated_files, 'count': len(validated_files)} - - elif isinstance(response_data, dict): - # Object with file list - return self.validate_json_response(response_data, api_name=api_name) - - else: - raise APIError( - message="Obsidian list response must be array or object", - api_name=api_name - ) - - else: - # Generic validation for unknown operations - if isinstance(response_data, str): - return {'content': self.validate_text_response(response_data, api_name=api_name)} - elif isinstance(response_data, (dict, list)): - return self.validate_json_response(response_data, api_name=api_name) - else: - return {'data': str(response_data)} - - def validate_claude_api_response( - self, - response_data: Any, - operation: str = "unknown" - ) -> Dict[str, Any]: - """ - Validate Claude API response with operation-specific checks - - Args: - response_data: Response data to validate - operation: Type of operation (analyze, transform, etc.) - - Returns: - Validated response data - - Raises: - APIError: If validation fails - """ - api_name = "claude" - - # Claude API typically returns structured objects - if not isinstance(response_data, dict): - raise APIError( - message="Claude API response must be an object", - api_name=api_name - ) - - # Validate basic structure - validated_response = self.validate_json_response(response_data, api_name=api_name) - - # Check for required fields based on operation - if operation in ["analyze", "transform"]: - # Expect content field - if 'content' not in validated_response: - raise APIError( - message="Claude response missing 'content' field", - api_name=api_name - ) - - # Validate content structure - content = validated_response['content'] - if isinstance(content, list) and len(content) > 0: - # Check first content item - first_item = content[0] - if isinstance(first_item, dict) and 'text' in first_item: - # Validate the text content - text_content = first_item['text'] - if isinstance(text_content, str): - validated_response['content'][0]['text'] = self.validate_text_response( - text_content, - max_length=1000000, # 1MB for AI responses - api_name=api_name - ) - - return validated_response - - def _validate_json_structure(self, data: Any, api_name: str, depth: int = 0) -> None: - """Recursively validate JSON structure""" - if depth > self.max_json_depth: - raise APIError( - message=f"JSON structure too deep (max depth: {self.max_json_depth})", - api_name=api_name - ) - - if isinstance(data, dict): - if len(data) > 1000: # Reasonable limit for object keys - raise APIError( - message=f"JSON object has too many keys: {len(data)}", - api_name=api_name - ) - - for key, value in data.items(): - if not isinstance(key, str): - raise APIError( - message=f"JSON object key must be string, got {type(key)}", - api_name=api_name - ) - - if len(key) > 1000: # Reasonable key length limit - raise APIError( - message=f"JSON object key too long: {len(key)} characters", - api_name=api_name - ) - - self._validate_json_structure(value, api_name, depth + 1) - - elif isinstance(data, list): - if len(data) > self.max_array_length: - raise APIError( - message=f"JSON array too long: {len(data)} items (max: {self.max_array_length})", - api_name=api_name - ) - - for item in data: - self._validate_json_structure(item, api_name, depth + 1) - - elif isinstance(data, str): - if len(data) > self.max_string_length: - raise APIError( - message=f"JSON string too long: {len(data)} characters (max: {self.max_string_length})", - api_name=api_name - ) - - def _validate_json_schema(self, data: Any, schema: Dict[str, Any], api_name: str) -> None: - """Basic JSON schema validation""" - # This is a simplified schema validator - # For production use, consider using jsonschema library - - if 'type' in schema: - expected_type = schema['type'] - - type_mapping = { - 'object': dict, - 'array': list, - 'string': str, - 'number': (int, float), - 'integer': int, - 'boolean': bool, - 'null': type(None) - } - - if expected_type in type_mapping: - expected_python_type = type_mapping[expected_type] - if not isinstance(data, expected_python_type): - raise APIError( - message=f"Expected {expected_type}, got {type(data).__name__}", - api_name=api_name - ) - - if isinstance(data, dict) and 'properties' in schema: - # Validate object properties - for prop_name, prop_schema in schema['properties'].items(): - if prop_name in data: - self._validate_json_schema(data[prop_name], prop_schema, api_name) - - # Check required properties - if 'required' in schema: - for required_prop in schema['required']: - if required_prop not in data: - raise APIError( - message=f"Missing required property: {required_prop}", - api_name=api_name - ) - - elif isinstance(data, list) and 'items' in schema: - # Validate array items - item_schema = schema['items'] - for item in data: - self._validate_json_schema(item, item_schema, api_name) - - def _sanitize_json_data(self, data: Any) -> Any: - """Sanitize JSON data by removing/replacing problematic content""" - if isinstance(data, dict): - sanitized = {} - for key, value in data.items(): - # Sanitize key - clean_key = self._sanitize_string(str(key)) - # Recursively sanitize value - sanitized[clean_key] = self._sanitize_json_data(value) - return sanitized - - elif isinstance(data, list): - return [self._sanitize_json_data(item) for item in data] - - elif isinstance(data, str): - return self._sanitize_string(data) - - else: - # Numbers, booleans, null - return as-is - return data - - def _sanitize_text_data(self, text: str) -> str: - """Sanitize text data""" - return self._sanitize_string(text) - - def _sanitize_string(self, text: str) -> str: - """Sanitize string content""" - if not isinstance(text, str): - return str(text) - - # Remove null bytes - sanitized = text.replace('\x00', '') - - # Remove other control characters except common whitespace - sanitized = ''.join(char for char in sanitized if ord(char) >= 32 or char in '\t\n\r') - - # Limit length - if len(sanitized) > self.max_string_length: - sanitized = sanitized[:self.max_string_length] + '...[truncated]' - - return sanitized - - def _sanitize_filename(self, filename: str) -> str: - """Sanitize filename from API response""" - if not isinstance(filename, str): - filename = str(filename) - - # Remove dangerous characters - sanitized = re.sub(r'[<>:"|?*\x00-\x1f]', '', filename) - - # Remove path separators - sanitized = sanitized.replace('/', '').replace('\\', '') - - # Limit length - if len(sanitized) > 255: - sanitized = sanitized[:255] - - return sanitized - - def _validate_file_info(self, file_info: Dict[str, Any], api_name: str) -> Dict[str, Any]: - """Validate file information object""" - validated = {} - - # Common file info fields - if 'name' in file_info: - validated['name'] = self._sanitize_filename(str(file_info['name'])) - - if 'path' in file_info: - validated['path'] = self._sanitize_string(str(file_info['path'])) - - if 'size' in file_info: - try: - validated['size'] = int(file_info['size']) - except (ValueError, TypeError): - self.logger.warning(f"Invalid file size: {file_info['size']}") - - if 'modified' in file_info: - validated['modified'] = self._sanitize_string(str(file_info['modified'])) - - if 'type' in file_info: - validated['type'] = self._sanitize_string(str(file_info['type'])) - - return validated - - def _extract_json_from_text(self, text: str, api_name: str) -> Optional[Dict[str, Any]]: - """Try to extract JSON from text response (e.g., if wrapped in markdown)""" - # Look for JSON blocks in markdown - json_patterns = [ - r'```json\s*\n(.*?)\n```', # Markdown JSON block - r'```\s*\n(\{.*?\})\n```', # Generic code block with JSON - r'(\{.*\})', # Any JSON-like structure - ] - - for pattern in json_patterns: - matches = re.findall(pattern, text, re.DOTALL | re.IGNORECASE) - for match in matches: - try: - return json.loads(match.strip()) - except json.JSONDecodeError: - continue - - return None - - -# Global validator instance -api_response_validator = APIResponseValidator() - - -def validate_api_response( - response_data: Any, - api_name: str, - operation: str = "unknown", - schema: Optional[Dict[str, Any]] = None -) -> Dict[str, Any]: - """ - Convenience function for validating API responses - - Args: - response_data: Response data to validate - api_name: Name of the API - operation: Type of operation - schema: Optional schema for validation - - Returns: - Validated response data - - Raises: - APIError: If validation fails - """ - if api_name.lower() == "obsidian": - return api_response_validator.validate_obsidian_api_response(response_data, operation) - elif api_name.lower() == "claude": - return api_response_validator.validate_claude_api_response(response_data, operation) - else: - # Generic validation - if isinstance(response_data, str): - return {'content': api_response_validator.validate_text_response(response_data, api_name=api_name)} - elif isinstance(response_data, (dict, list)): - return api_response_validator.validate_json_response(response_data, schema, api_name) - else: - return {'data': str(response_data)} \ No newline at end of file diff --git a/./chat_main.py b/./chat_main.py deleted file mode 100644 index 757fdb8..0000000 --- a/./chat_main.py +++ /dev/null @@ -1,254 +0,0 @@ -""" -对话式 Agent 的命令行入口 -支持交互式对话 -""" - -import argparse -import asyncio -import logging -import sys -from typing import Dict, Any, Optional, List - -from typing import Dict, Any, Optional, List - -# Handle imports with both relative and absolute paths -try: - from .main import JournalOrganizerAgent - from .conversation import ConversationalAgent -except ImportError: - # Fallback to absolute imports when running as script - from main import JournalOrganizerAgent - from conversation import ConversationalAgent - - -class ChatInterface: - """对话式 Agent 的命令行界面""" - - def __init__(self, config_file: Optional[str] = None) -> None: - """ - 初始化聊天界面 - - Args: - config_file: 配置文件路径 - """ - self.logger: logging.Logger = logging.getLogger("ChatInterface") - - # 初始化 Agent - self.journal_agent: JournalOrganizerAgent = JournalOrganizerAgent(config_file) - self.conversational_agent: ConversationalAgent = ConversationalAgent( - self.journal_agent.agent, self.journal_agent.config - ) - - async def run_interactive(self) -> None: - """ - 运行交互式对话 - """ - print(f"\n{'='*60}") - print("Obsidian 智能日记整理 Agent - 对话模式") - print(f"{'='*60}\n") - - # 显示欢迎消息 - welcome_msg = await self.conversational_agent.initialize() - print(f"\n🤖 助手: {welcome_msg}\n") - - # 交互循环 - while True: - try: - # 获取用户输入 - user_input: str = input("👤 您: ").strip() - - if not user_input: - continue - - # 处理特殊命令 - if user_input.lower() in ["exit", "quit", "退出"]: - print("\n👋 再见!\n") - break - - if user_input.lower() in ["help", "帮助"]: - self._show_help() - continue - - if user_input.lower() in ["history", "历史"]: - self._show_history() - continue - - if user_input.lower() in ["status", "状态"]: - self._show_status() - continue - - if user_input.lower() in ["clear", "清除"]: - self.conversational_agent.clear_history() - print("\n✓ 对话历史已清除\n") - continue - - # 处理用户消息 - print("\n⏳ 处理中...\n") - response = await self.conversational_agent.chat(user_input) - - # 显示响应 - print(f"🤖 助手: {response.message}") - - # 显示建议 - if response.suggestions: - print("\n💡 您可以尝试:") - for i, suggestion in enumerate(response.suggestions, 1): - print(f" {i}. {suggestion}") - - print() - - except KeyboardInterrupt: - print("\n\n👋 再见!\n") - break - - except Exception as e: - self.logger.error(f"错误: {str(e)}", exc_info=True) - print(f"\n❌ 发生错误: {str(e)}\n") - - async def run_single_query(self, query: str) -> None: - """ - 运行单个查询 - - Args: - query: 用户查询 - """ - # 初始化 - await self.conversational_agent.initialize() - - # 处理查询 - response = await self.conversational_agent.chat(query) - - # 输出结果 - output_data: Dict[str, Any] = { - "message": response.message, - "status": response.status, - "suggestions": response.suggestions, - "metadata": response.metadata, - } - print(json.dumps(output_data, ensure_ascii=False, indent=2)) - - def _show_help(self) -> None: - """ - 显示帮助信息 - """ - help_text = """ -🆘 可用命令: - -对话命令: - - 直接输入您的需求,例如: "整理今天的日记" - - "分析本周的主题" - - "导出月度总结" - -系统命令: - - help / 帮助 显示此帮助信息 - - history / 历史 显示对话历史 - - status / 状态 显示当前状态 - - clear / 清除 清除对话历史 - - exit / quit / 退出 退出程序 - -示例对话: - 👤 您: 帮我整理一下昨天的日记 - 🤖 助手: 好的,我来帮您整理昨天的日记... - - 👤 您: 分析一下这周的主题 - 🤖 助手: 这周的主题主要集中在... -""" - print(help_text) - - def _show_history(self) -> None: - """ - 显示对话历史 - """ - history: List[ - Dict[str, str] - ] = self.conversational_agent.get_conversation_history() - - if not history: - print("\n📭 对话历史为空\n") - return - - print("\n📜 对话历史:\n") - for msg in history: - role: str = "👤 您" if msg["role"] == "user" else "🤖 助手" - content: str = msg["content"] - if len(content) > 100: - content = f"{content[:100]}..." - print(f"{role}: {content}") - print() - - def _show_status(self) -> None: - """ - 显示当前状态 - """ - summary: Dict[str, Any] = self.conversational_agent.get_state_summary() - - print("\n📊 当前状态:\n") - print(f"总消息数: {summary['total_messages']}") - print(f"总任务数: {summary['stats']['total_tasks']}") - print(f"成功任务: {summary['stats']['successful_tasks']}") - print(f"失败任务: {summary['stats']['failed_tasks']}") - - if summary["current_task"]: - print(f"\n当前任务: {summary['current_task']['command']}") - print(f"状态: {summary['current_task']['status']}") - - print() - - -async def main() -> Optional[int]: - """ - 主函数 - """ - parser: argparse.ArgumentParser = argparse.ArgumentParser( - description="Obsidian 智能日记整理 Agent - 对话模式", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -示例: - # 启动交互式对话 - python -m journal_organizer.chat_main - - # 处理单个查询 - python -m journal_organizer.chat_main --query "整理今天的日记" - - # 使用指定配置文件 - python -m journal_organizer.chat_main --config /path/to/config.yaml - """, - ) - - parser.add_argument("--config", type=str, help="配置文件路径") - - parser.add_argument("--query", type=str, help="单个查询(不进入交互模式)") - - parser.add_argument( - "--log-level", - type=str, - default="INFO", - choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - help="日志级别", - ) - - args: argparse.Namespace = parser.parse_args() - - # 设置日志 - logging.basicConfig( - level=getattr(logging, args.log_level), - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - ) - - # 初始化聊天界面 - chat: ChatInterface = ChatInterface(args.config) - - # 运行 - if args.query: - # 单个查询模式 - await chat.run_single_query(args.query) - else: - # 交互模式 - await chat.run_interactive() - - return None - - -if __name__ == "__main__": - exit_code: Optional[int] = asyncio.run(main()) - sys.exit(exit_code or 0) diff --git a/./claude_api_client.py b/./claude_api_client.py deleted file mode 100644 index 1d2eb08..0000000 --- a/./claude_api_client.py +++ /dev/null @@ -1,473 +0,0 @@ -""" -Enhanced Claude API Client with configurable URL support -Provides a centralized client for all Claude API interactions with custom endpoint support -""" - -import ssl -import logging -from datetime import datetime -from typing import Optional, Dict, Any, AsyncContextManager, TYPE_CHECKING -from contextlib import asynccontextmanager - -if TYPE_CHECKING: - import aiohttp - -try: - from .dependency_manager import get_dependency_manager - from .config_validation import ClaudeAPIConfig - from .error_handling import APIError, ConfigurationError - - # Try to import anthropic with graceful degradation - dependency_manager = get_dependency_manager() - AsyncAnthropic = dependency_manager.get_class_from_module('anthropic', 'AsyncAnthropic') - aiohttp = dependency_manager.get_module('aiohttp') -except ImportError: - # Fallback for backward compatibility - try: - from anthropic import AsyncAnthropic - import aiohttp - from config_validation import ClaudeAPIConfig - from error_handling import APIError, ConfigurationError - except ImportError: - AsyncAnthropic = None - aiohttp = None - ClaudeAPIConfig = None - APIError = Exception - ConfigurationError = Exception - - -class ClaudeAPIClient: - """Enhanced Claude API client with configurable endpoint support""" - - def __init__(self, config: ClaudeAPIConfig): - """ - Initialize Claude API client with enhanced configuration - - Args: - config: ClaudeAPIConfig instance with api_url, api_key, model, etc. - """ - self.config = config - self.base_url = config.api_url - self.api_key = config.api_key - self.model = config.model - self.max_tokens = config.max_tokens - self.temperature = config.temperature - self.logger = logging.getLogger(__name__) - - # Validate dependencies - if not AsyncAnthropic: - raise ConfigurationError( - message="Anthropic library is not installed. Please install it with: pip install anthropic", - config_key="anthropic_dependency", - ) - - if not aiohttp: - raise ConfigurationError( - message="aiohttp library is not installed. Please install it with: pip install aiohttp", - config_key="aiohttp_dependency", - ) - - # Initialize the Anthropic client with custom base URL - self._anthropic_client = self._create_anthropic_client() - - def _create_anthropic_client(self) -> AsyncAnthropic: - """Create Anthropic client with custom configuration""" - client_kwargs = { - 'api_key': self.api_key, - } - - # Set custom base URL if different from default - if self.base_url != "https://api.anthropic.com": - client_kwargs['base_url'] = self.base_url - self.logger.info(f"Using custom Claude API URL: {self.base_url}") - - return AsyncAnthropic(**client_kwargs) - - @asynccontextmanager - async def create_http_session(self) -> AsyncContextManager[Any]: - """ - Create HTTP session with proper SSL configuration for custom endpoints - - This is used for direct HTTP calls when needed (e.g., connection validation) - """ - if not aiohttp: - raise ConfigurationError( - message="aiohttp library is not installed. Please install it with: pip install aiohttp", - config_key="aiohttp_dependency", - ) - - headers = { - 'Authorization': f'Bearer {self.api_key}', - 'Content-Type': 'application/json', - 'User-Agent': 'journal-organizer/1.0', - 'anthropic-version': '2023-06-01' - } - - # Configure SSL context for custom endpoints - ssl_context = ssl.create_default_context() - - # Handle localhost and custom endpoints with SSL considerations - if ('localhost' in self.base_url or - self.base_url.startswith('https://127.0.0.1') or - self.base_url.startswith('http://localhost') or - self.base_url.startswith('http://127.0.0.1')): - # For localhost, disable SSL verification - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - self.logger.warning(f"SSL verification disabled for localhost endpoint: {self.base_url}") - - connector = aiohttp.TCPConnector(ssl=ssl_context) - timeout = aiohttp.ClientTimeout(total=60) - - async with aiohttp.ClientSession( - connector=connector, - headers=headers, - timeout=timeout - ) as session: - yield session - - async def validate_connection(self) -> bool: - """ - Validate API connection and credentials - - Returns: - True if connection is valid - - Raises: - APIError: If connection validation fails - """ - try: - self.logger.info(f"Validating connection to Claude API at {self.base_url}") - - # Try a minimal API call to validate connection - message = await self._anthropic_client.messages.create( - model=self.model, - max_tokens=10, - messages=[{"role": "user", "content": "test"}] - ) - - if message and hasattr(message, 'content') and message.content: - self.logger.info("Claude API connection validated successfully") - return True - else: - raise APIError( - message="Invalid response from Claude API", - api_name="claude" - ) - - except Exception as e: - error_msg = str(e) - - # Provide specific error messages based on common issues - if "401" in error_msg or "unauthorized" in error_msg.lower(): - raise APIError( - message=f"Invalid API key or unauthorized access to {self.base_url}. Please check your Claude API key.", - api_name="claude", - cause=e - ) - elif "404" in error_msg or "not found" in error_msg.lower(): - raise APIError( - message=f"API endpoint not found. Please verify that {self.base_url} is the correct Claude API URL.", - api_name="claude", - cause=e - ) - elif "connection" in error_msg.lower() or "timeout" in error_msg.lower(): - raise APIError( - message=f"Connection error to {self.base_url}. Please check your network connection and API URL.", - api_name="claude", - cause=e - ) - elif "ssl" in error_msg.lower() or "certificate" in error_msg.lower(): - raise APIError( - message=f"SSL/Certificate error connecting to {self.base_url}. For localhost endpoints, this is expected.", - api_name="claude", - cause=e - ) - else: - raise APIError( - message=f"Claude API validation failed: {error_msg}", - api_name="claude", - cause=e - ) - - async def validate_model_availability(self) -> bool: - """ - Validate that the configured model is available - - Returns: - True if model is available - - Raises: - APIError: If model validation fails - """ - try: - self.logger.info(f"Validating model availability: {self.model}") - - # Try a minimal API call with the specific model - message = await self._anthropic_client.messages.create( - model=self.model, - max_tokens=5, - messages=[{"role": "user", "content": "hi"}] - ) - - if message and hasattr(message, 'content') and message.content: - self.logger.info(f"Model {self.model} is available and working") - return True - else: - raise APIError( - message=f"Invalid response when testing model {self.model}", - api_name="claude" - ) - - except Exception as e: - error_msg = str(e) - - if "model" in error_msg.lower() and ("not found" in error_msg.lower() or "invalid" in error_msg.lower()): - raise APIError( - message=f"Model '{self.model}' is not available or invalid. Please check your model configuration.", - api_name="claude", - cause=e - ) - else: - # Re-raise as general API error - raise APIError( - message=f"Model validation failed: {error_msg}", - api_name="claude", - cause=e - ) - - async def test_api_connectivity(self) -> Dict[str, Any]: - """ - Comprehensive API connectivity test with detailed results - - Returns: - Dictionary with test results and diagnostics - """ - results = { - 'connection_test': {'status': 'unknown', 'message': '', 'error': None}, - 'model_test': {'status': 'unknown', 'message': '', 'error': None}, - 'authentication_test': {'status': 'unknown', 'message': '', 'error': None}, - 'overall_status': 'unknown', - 'api_url': self.base_url, - 'model': self.model, - 'timestamp': datetime.now().isoformat() - } - - # Test 1: Basic connection - try: - await self.validate_connection() - results['connection_test'] = { - 'status': 'success', - 'message': 'API connection successful', - 'error': None - } - results['authentication_test'] = { - 'status': 'success', - 'message': 'API key authentication successful', - 'error': None - } - except APIError as e: - results['connection_test'] = { - 'status': 'failed', - 'message': str(e), - 'error': type(e).__name__ - } - - # Determine if it's an auth issue - if "401" in str(e) or "unauthorized" in str(e).lower(): - results['authentication_test'] = { - 'status': 'failed', - 'message': 'API key authentication failed', - 'error': 'AuthenticationError' - } - else: - results['authentication_test'] = { - 'status': 'unknown', - 'message': 'Could not test authentication due to connection failure', - 'error': None - } - - # Test 2: Model availability (only if connection succeeded) - if results['connection_test']['status'] == 'success': - try: - await self.validate_model_availability() - results['model_test'] = { - 'status': 'success', - 'message': f'Model {self.model} is available', - 'error': None - } - except APIError as e: - results['model_test'] = { - 'status': 'failed', - 'message': str(e), - 'error': type(e).__name__ - } - else: - results['model_test'] = { - 'status': 'skipped', - 'message': 'Model test skipped due to connection failure', - 'error': None - } - - # Determine overall status - if (results['connection_test']['status'] == 'success' and - results['model_test']['status'] == 'success' and - results['authentication_test']['status'] == 'success'): - results['overall_status'] = 'success' - elif results['connection_test']['status'] == 'failed': - results['overall_status'] = 'connection_failed' - elif results['authentication_test']['status'] == 'failed': - results['overall_status'] = 'authentication_failed' - elif results['model_test']['status'] == 'failed': - results['overall_status'] = 'model_unavailable' - else: - results['overall_status'] = 'partial_failure' - - return results - - async def create_message(self, messages: list, **kwargs) -> Any: - """ - Create a message using the Claude API - - Args: - messages: List of message dictionaries - **kwargs: Additional parameters (max_tokens, temperature, etc.) - - Returns: - Claude API response - - Raises: - APIError: If API call fails - """ - try: - # Use configured defaults, allow override via kwargs - api_params = { - 'model': kwargs.get('model', self.model), - 'max_tokens': kwargs.get('max_tokens', self.max_tokens), - 'messages': messages - } - - # Add temperature if specified - temperature = kwargs.get('temperature', self.temperature) - if temperature is not None: - api_params['temperature'] = temperature - - # Add any other parameters passed in kwargs - for key, value in kwargs.items(): - if key not in ['model', 'max_tokens', 'temperature'] and value is not None: - api_params[key] = value - - self.logger.debug(f"Making Claude API call with model: {api_params['model']}") - - response = await self._anthropic_client.messages.create(**api_params) - - self.logger.debug("Claude API call completed successfully") - return response - - except Exception as e: - error_msg = str(e) - - # Provide specific error handling - if "rate_limit" in error_msg.lower(): - raise APIError( - message="Claude API rate limit exceeded. Please wait before making more requests.", - api_name="claude", - cause=e - ) - elif "invalid_request" in error_msg.lower(): - raise APIError( - message=f"Invalid request to Claude API. Please check your parameters: {error_msg}", - api_name="claude", - cause=e - ) - elif "model" in error_msg.lower() and "not found" in error_msg.lower(): - raise APIError( - message=f"Model '{api_params.get('model', self.model)}' not found. Please check your model configuration.", - api_name="claude", - cause=e - ) - else: - raise APIError( - message=f"Claude API call failed: {error_msg}", - api_name="claude", - cause=e - ) - - def get_client_info(self) -> Dict[str, Any]: - """ - Get information about the client configuration - - Returns: - Dictionary with client configuration details - """ - return { - 'api_url': self.base_url, - 'model': self.model, - 'max_tokens': self.max_tokens, - 'temperature': self.temperature, - 'is_custom_endpoint': self.base_url != "https://api.anthropic.com" - } - - @classmethod - async def validate_configuration(cls, config: ClaudeAPIConfig, quick_test: bool = True) -> Dict[str, Any]: - """ - Class method to validate Claude API configuration without creating a persistent client - - Args: - config: ClaudeAPIConfig to validate - quick_test: If True, perform only basic validation. If False, run comprehensive tests. - - Returns: - Dictionary with validation results - """ - validation_results = { - 'config_valid': False, - 'connection_valid': False, - 'model_valid': False, - 'errors': [], - 'warnings': [], - 'config_info': { - 'api_url': config.api_url, - 'model': config.model, - 'is_custom_endpoint': config.api_url != "https://api.anthropic.com" - } - } - - try: - # Create temporary client for validation - client = cls(config) - validation_results['config_valid'] = True - - if quick_test: - # Quick validation - just test connection - try: - await client.validate_connection() - validation_results['connection_valid'] = True - validation_results['model_valid'] = True # Assume model is valid if connection works - except APIError as e: - validation_results['errors'].append(str(e)) - else: - # Comprehensive validation - test_results = await client.test_api_connectivity() - validation_results['connection_valid'] = test_results['connection_test']['status'] == 'success' - validation_results['model_valid'] = test_results['model_test']['status'] == 'success' - - # Collect errors from detailed tests - for test_name, test_result in test_results.items(): - if isinstance(test_result, dict) and test_result.get('status') == 'failed': - validation_results['errors'].append(f"{test_name}: {test_result['message']}") - - # Add test results to validation results - validation_results['detailed_tests'] = test_results - - # Add warnings for custom endpoints - if config.api_url != "https://api.anthropic.com": - validation_results['warnings'].append( - f"Using custom API endpoint: {config.api_url}. " - "Ensure this is a valid Claude API-compatible endpoint." - ) - - except Exception as e: - validation_results['errors'].append(f"Configuration validation failed: {str(e)}") - - return validation_results \ No newline at end of file diff --git a/./commands/__init__.py b/./commands/__init__.py deleted file mode 100644 index e2b1133..0000000 --- a/./commands/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Commands 模块 -""" - -# Handle imports with both relative and absolute paths -try: - from .organize_command import OrganizeCommand -except ImportError: - # Fallback to absolute imports when running as script - from commands.organize_command import OrganizeCommand - -__all__ = [ - "OrganizeCommand", -] diff --git a/./commands/__pycache__/__init__.cpython-310.pyc b/./commands/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 00449ff03053c280c520d16e89966203c2e8b81d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 371 zcmZ8c!AiqG5S`s7HAG9}u{W=|bWh%-3L@x51r;%uC1yj}knEP(gp&SNpO-I){(n@c5Xc8xkqlhQE#Xrz#_Vi~ebSE%BgZw--cGZ^Pm$&&N&tZuxcLW?R- WC+$-vu;2i71F}Z~O24V={rm#>8D)|H diff --git a/./commands/__pycache__/__init__.cpython-311.pyc b/./commands/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 826a81bde48f220a0da379d90cdb729dfc0922e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 307 zcmZ3^%ge<81UE}UGIN3SV-N=hn4pZ$GC;<3h7^V#wg|# zmS6@=)>1*B5-#Wb+}y;xlwyTvD;7STJDuw#BT!V6@fN>-QF>xtW>qRwK@l@h+)tC` z7JGbrN`7*D{4IfesEYVxh#6odAo;|cocQ>a44;8I7=9V+XXNLm>X#;!=9QG{XXTd` zlIY~;;_lhPbtkwwJQR7738F1V<7Q?nURt40}~S? j%LfJ!!NI_&cY#3{8GT@5U={no%*4&qzz%{%96)^lwE|P} diff --git a/./commands/__pycache__/organize_command.cpython-310.pyc b/./commands/__pycache__/organize_command.cpython-310.pyc deleted file mode 100644 index a8639fe68e6d8431c44e84baa3834e88eabe16c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9834 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 I=hnpk0bUe!iU0rr diff --git a/./commands/__pycache__/organize_command.cpython-311.pyc b/./commands/__pycache__/organize_command.cpython-311.pyc deleted file mode 100644 index 5b5b36529a4b633f56457b48daaefcd016215fd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17525 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 None: - super().__init__( - name="organize", - description="分析和整理日记内容,提取经验和要点", - aliases=["org", "organize-journal"], - ) - - # 注册 Skills - self.register_skill(ObsidianReadSkill()) - self.register_skill(ObsidianWriteSkill()) - self.register_skill(ObsidianAppendSkill()) - self.register_skill(ClaudeAnalyzeSkill()) - self.register_skill(ClaudeTransformSkill()) - - async def execute(self, context: CommandContext) -> SkillResult: - """ - 执行日记整理命令 - - Args: - context: 命令执行上下文 - - args: - - date: 日期(格式: YYYY-MM-DD,默认今天) - - vault_path: Obsidian vault 路径 - - daily_folder: 日记文件夹(默认: Daily) - - config: 系统配置 - - Returns: - SkillResult: 执行结果 - """ - try: - # Validate and sanitize input arguments - validated_args = command_input_validator.validate_organize_command_input( - context.args - ) - - # 获取参数 - date_str: Optional[str] = validated_args.get("date") - vault_path: Optional[str] = validated_args.get("vault_path") - daily_folder: str = validated_args.get("daily_folder", "Daily") - - # 从配置中获取信息 - config: Dict[str, Any] = context.config or {} - obsidian_config: Dict[str, Any] = config.get("obsidian", {}) - claude_config: Dict[str, Any] = config.get("claude", {}) - output_config: Dict[str, Any] = config.get("output", {}) - analysis_config: Dict[str, Any] = config.get("analysis", {}) - - # 如果没有提供日期,使用今天 - if not date_str: - date_str = datetime.now().strftime("%Y-%m-%d") - else: - # Validate the provided date - validated_date = validate_date_input( - date_str, - field_name="date", - required=True, - format_hint="iso_date" - ) - date_str = validated_date.strftime("%Y-%m-%d") - - # 获取必要的配置 - api_url: str = obsidian_config.get("rest_api", {}).get( - "url", "https://localhost:27123" - ) - api_key: Optional[str] = obsidian_config.get("rest_api", {}).get("api_key") - - # Create enhanced Claude API configuration - try: - claude_api_config = ClaudeAPIConfig(**claude_config) - except Exception as e: - return SkillResult( - success=False, - error=f"Claude API 配置无效: {str(e)}", - message="请检查 Claude API 配置", - ) - - if not api_key or not claude_api_config.api_key: - return SkillResult( - success=False, - error="缺少必要的配置", - message="请配置 Obsidian API 密钥和 Claude API 密钥", - ) - - self.logger.info(f"开始整理日期 {date_str} 的日记") - - # 步骤 1: 读取日记 - daily_note_path = str(Path(daily_folder) / f"{date_str}.md") - - self.logger.info(f"步骤 1: 读取日记 {daily_note_path}") - read_skill = self.skills["obsidian_read"] - read_result: SkillResult = await read_skill.execute( - context, - file_path=daily_note_path, - vault_path=vault_path, - api_url=api_url, - api_key=api_key, - ) - - if not read_result.success: - return read_result - - journal_content: str = read_result.data["content"] - - # 步骤 2: 使用 Claude 分析日记 - self.logger.info("步骤 2: 使用 Claude 分析日记内容") - analyze_skill = self.skills["claude_analyze"] - categories: List[str] = analysis_config.get("categories", []) - - analyze_result: SkillResult = await analyze_skill.execute( - context, - journal_content=journal_content, - claude_config=claude_api_config, - categories=categories, - ) - - if not analyze_result.success: - return analyze_result - - analysis_data: Dict[str, Any] = analyze_result.data["analysis"] - - # 步骤 3: 整理分析结果到各个位置 - self.logger.info("步骤 3: 整理分析结果") - - write_results: Dict[str, SkillResult] = {} - write_skill = self.skills["obsidian_write"] - - # 处理经验 - if "experiences" in analysis_data and analysis_data["experiences"]: - experiences_content: str = self._format_experiences( - analysis_data["experiences"], date_str - ) - experiences_path = str( - Path( - output_config.get("experiences_folder", "Knowledge/Experiences") - ) - / f"{date_str}.md" - ) - - exp_result: SkillResult = await write_skill.execute( - context, - file_path=experiences_path, - content=experiences_content, - api_url=api_url, - api_key=api_key, - overwrite=True, - ) - write_results["experiences"] = exp_result - - # 处理经验教训 - if "lessons_learned" in analysis_data and analysis_data["lessons_learned"]: - lessons_content: str = self._format_lessons( - analysis_data["lessons_learned"], date_str - ) - lessons_path = str( - Path(output_config.get("lessons_folder", "Knowledge/Lessons")) - / f"{date_str}.md" - ) - - lessons_result: SkillResult = await write_skill.execute( - context, - file_path=lessons_path, - content=lessons_content, - api_url=api_url, - api_key=api_key, - overwrite=True, - ) - write_results["lessons"] = lessons_result - - # 处理待办事项 - if "action_items" in analysis_data and analysis_data["action_items"]: - tasks_content: str = self._format_tasks( - analysis_data["action_items"], date_str - ) - tasks_path = str( - Path(output_config.get("tasks_folder", "Tasks/Daily")) - / f"{date_str}.md" - ) - - tasks_result: SkillResult = await write_skill.execute( - context, - file_path=tasks_path, - content=tasks_content, - api_url=api_url, - api_key=api_key, - overwrite=True, - ) - write_results["tasks"] = tasks_result - - # 处理问题 - if "problems" in analysis_data and analysis_data["problems"]: - problems_content: str = self._format_problems( - analysis_data["problems"], date_str - ) - problems_path = str( - Path(output_config.get("problems_folder", "Knowledge/Problems")) - / f"{date_str}.md" - ) - - problems_result: SkillResult = await write_skill.execute( - context, - file_path=problems_path, - content=problems_content, - api_url=api_url, - api_key=api_key, - overwrite=True, - ) - write_results["problems"] = problems_result - - # 处理成就 - if "achievements" in analysis_data and analysis_data["achievements"]: - achievements_content: str = self._format_achievements( - analysis_data["achievements"], date_str - ) - achievements_path = str( - Path( - output_config.get( - "achievements_folder", "Knowledge/Achievements" - ) - ) - / f"{date_str}.md" - ) - - achievements_result: SkillResult = await write_skill.execute( - context, - file_path=achievements_path, - content=achievements_content, - api_url=api_url, - api_key=api_key, - overwrite=True, - ) - write_results["achievements"] = achievements_result - - # 处理改进建议 - if "improvements" in analysis_data and analysis_data["improvements"]: - improvements_content: str = self._format_improvements( - analysis_data["improvements"], date_str - ) - improvements_path = str( - Path( - output_config.get( - "improvements_folder", "Knowledge/Improvements" - ) - ) - / f"{date_str}.md" - ) - - improvements_result: SkillResult = await write_skill.execute( - context, - file_path=improvements_path, - content=improvements_content, - api_url=api_url, - api_key=api_key, - overwrite=True, - ) - write_results["improvements"] = improvements_result - - # 统计结果 - successful_writes: int = sum(1 for r in write_results.values() if r.success) - - return SkillResult( - success=True, - data={ - "date": date_str, - "journal_file": daily_note_path, - "analysis": analysis_data, - "write_results": {k: v.success for k, v in write_results.items()}, - "successful_writes": successful_writes, - "total_writes": len(write_results), - "organized_at": datetime.now().isoformat(), - }, - message=f"成功整理日记 {date_str},已生成 {successful_writes} 个文件", - ) - - except Exception as e: - self.logger.error(f"执行日记整理命令时出错: {str(e)}") - return SkillResult(success=False, error=str(e), message="日记整理异常") - - def _format_experiences( - self, experiences: List[Dict[str, Any]], date_str: str - ) -> str: - """格式化经验内容""" - content: str = f"# 经验总结 - {date_str}\n\n" - content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n" - - for i, exp in enumerate(experiences, 1): - content += f"## {i}. {exp.get('title', '经验')}\n\n" - content += f"**分类**: {exp.get('category', '未分类')}\n" - content += f"**优先级**: {exp.get('priority', 'medium')}\n\n" - content += f"{exp.get('content', '')}\n\n" - - content += f"\n---\n*来源: [[{date_str}]]*\n" - return content - - def _format_lessons(self, lessons: List[Dict[str, Any]], date_str: str) -> str: - """格式化经验教训内容""" - content: str = f"# 经验教训 - {date_str}\n\n" - content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n" - - for i, lesson in enumerate(lessons, 1): - content += f"## {i}. {lesson.get('lesson', '教训')}\n\n" - content += f"**背景**: {lesson.get('context', '')}\n\n" - content += f"**应用**: {lesson.get('application', '')}\n\n" - - content += f"\n---\n*来源: [[{date_str}]]*\n" - return content - - def _format_tasks(self, tasks: List[Dict[str, Any]], date_str: str) -> str: - """格式化待办事项内容""" - content: str = f"# 待办事项 - {date_str}\n\n" - content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n" - - # 按优先级分组 - by_priority: Dict[str, List[Dict[str, Any]]] = { - "high": [], - "medium": [], - "low": [], - } - for task in tasks: - priority: str = task.get("priority", "medium") - by_priority[priority].append(task) - - for priority in ["high", "medium", "low"]: - if by_priority[priority]: - priority_text: Dict[str, str] = { - "high": "🔴 高", - "medium": "� 中", - r"low": "🟢 低", - } - content += f"## {priority_text[priority]} 优先级\n\n" - - for task in by_priority[priority]: - content += f"- [ ] {task.get('task', '任务')}\n" - if task.get("deadline"): - content += f" - 截止: {task.get('deadline')}\n" - content += "\n" - - content += f"\n---\n*来源: [[{date_str}]]*\n" - return content - - def _format_problems(self, problems: List[Dict[str, Any]], date_str: str) -> str: - """格式化问题内容""" - content: str = f"# 问题记录 - {date_str}\n\n" - content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n" - - for i, problem in enumerate(problems, 1): - content += f"## {i}. {problem.get('problem', '问题')}\n\n" - content += f"**影响**: {problem.get('impact', '')}\n\n" - content += f"**建议方案**: {problem.get('proposed_solution', '')}\n\n" - - content += f"\n---\n*来源: [[{date_str}]]*\n" - return content - - def _format_achievements( - self, achievements: List[Dict[str, Any]], date_str: str - ) -> str: - """格式化成就内容""" - content: str = f"# 成就记录 - {date_str}\n\n" - content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n" - - for i, achievement in enumerate(achievements, 1): - content += f"## {i}. {achievement.get('achievement', '成就')}\n\n" - content += f"**重要性**: {achievement.get('significance', '')}\n\n" - content += f"**证据**: {achievement.get('evidence', '')}\n\n" - - content += f"\n---\n*来源: [[{date_str}]]*\n" - return content - - def _format_improvements( - self, improvements: List[Dict[str, Any]], date_str: str - ) -> str: - """格式化改进建议内容""" - content: str = f"# 改进建议 - {date_str}\n\n" - content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n" - - for i, improvement in enumerate(improvements, 1): - content += f"## {i}. {improvement.get('area', '改进领域')}\n\n" - content += f"**当前状态**: {improvement.get('current_state', '')}\n\n" - content += f"**建议改进**: {improvement.get('suggested_change', '')}\n\n" - content += f"**预期收益**: {improvement.get('expected_benefit', '')}\n\n" - - content += f"\n---\n*来源: [[{date_str}]]*\n" - return content diff --git a/./config.example.yaml b/./config.example.yaml deleted file mode 100644 index 87caf1f..0000000 --- a/./config.example.yaml +++ /dev/null @@ -1,101 +0,0 @@ -# Obsidian 智能日记整理 Agent 配置文件示例 -# 将此文件复制为 config.yaml 并根据您的环境进行配置 - -# Obsidian 配置 -obsidian: - # Obsidian vault 的路径 - vault_path: "/path/to/your/obsidian/vault" - - # Local REST API 插件配置 - rest_api: - # API 服务器地址 - url: "https://localhost:27123" - # API 密钥(从 Obsidian 插件设置中获取) - api_key: "your-api-key-here" - # 是否验证 SSL 证书(通常设为 false) - verify_ssl: false - -# Claude API 配置 -claude: - # Claude API 密钥(从环境变量或直接配置) - api_key: "${ANTHROPIC_API_KEY}" - # Claude API 基础 URL(可选,默认为官方 API) - # 可用于代理服务器、区域端点或自定义部署 - # 示例: - # - 官方 API: "https://api.anthropic.com" - # - 代理服务器: "https://your-proxy.example.com" - # - 区域端点: "https://api-eu.anthropic.com" - api_url: "https://api.anthropic.com" - # 使用的模型(支持的模型包括) - # - claude-3-5-sonnet-20241022 (推荐) - # - claude-3-5-haiku-20241022 - # - claude-3-opus-20240229 - # - claude-3-sonnet-20240229 - # - claude-3-haiku-20240307 - model: "claude-3-5-sonnet-20241022" - # 最大 token 数 - max_tokens: 4096 - # 温度参数(0-1,越低越确定) - temperature: 0.7 - -# 日记配置 -journal: - # 日记文件夹名称 - daily_notes_folder: "Daily" - # 日期格式 - date_format: "YYYY-MM-DD" - # 文件扩展名 - file_extension: ".md" - -# 输出配置(整理后的内容放置位置) -output: - # 经验总结文件夹 - experiences_folder: "Knowledge/Experiences" - # 经验教训文件夹 - lessons_folder: "Knowledge/Lessons" - # 待办事项文件夹 - tasks_folder: "Tasks/Daily" - # 问题记录文件夹 - problems_folder: "Knowledge/Problems" - # 成就记录文件夹 - achievements_folder: "Knowledge/Achievements" - # 改进建议文件夹 - improvements_folder: "Knowledge/Improvements" - -# 分析配置 -analysis: - # 分类类别 - categories: - - "技术学习" - - "项目管理" - - "个人成长" - - "团队协作" - - "问题解决" - - # 提取规则(定义哪些内容需要提取) - extraction_rules: - experiences: - enabled: true - description: "提取日记中的重要经验和见解" - lessons: - enabled: true - description: "提取学到的知识点和最佳实践" - action_items: - enabled: true - description: "提取需要采取行动的任务" - problems: - enabled: true - description: "提取遇到的问题和挑战" - achievements: - enabled: true - description: "提取完成的成就和进展" - improvements: - enabled: true - description: "提取改进建议和优化方向" - -# 日志配置 -logging: - # 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL) - level: "INFO" - # 日志文件路径 - file: "logs/journal_organizer.log" diff --git a/./config.py b/./config.py deleted file mode 100644 index 478f1ae..0000000 --- a/./config.py +++ /dev/null @@ -1,333 +0,0 @@ -""" -配置管理模块 -负责加载和管理系统的所有配置参数 -Enhanced with Pydantic validation and comprehensive error handling -""" - -import json -import logging -import os -from dataclasses import dataclass, field -from pathlib import Path -from typing import Dict, Any, Optional, List - -from .dependency_manager import get_dependency_manager - -# Import the new validation framework -from .config_validation import ( - SystemConfig, ConfigurationValidator, - ObsidianConfig as PydanticObsidianConfig, - ClaudeAPIConfig as PydanticClaudeAPIConfig, - JournalConfig as PydanticJournalConfig, - OutputConfig as PydanticOutputConfig, - AnalysisConfig as PydanticAnalysisConfig, - LoggingConfig as PydanticLoggingConfig -) - -# Try to import yaml with graceful degradation -dependency_manager = get_dependency_manager() -yaml = dependency_manager.get_module('yaml') - - -@dataclass -class ObsidianConfig: - """Obsidian 配置""" - - vault_path: str - rest_api_url: str - rest_api_key: str - verify_ssl: bool = False - - def __post_init__(self) -> None: - """Validate configuration after initialization""" - if not self.vault_path or not self.vault_path.strip(): - raise ValueError("vault_path cannot be empty") - if not self.rest_api_key or not self.rest_api_key.strip(): - raise ValueError("rest_api_key cannot be empty") - if not self.rest_api_url or not self.rest_api_url.strip(): - raise ValueError("rest_api_url cannot be empty") - if not self.rest_api_url.startswith(("http://", "https://")): - raise ValueError("rest_api_url must start with http:// or https://") - - -@dataclass -class ClaudeConfig: - """Claude API 配置""" - - api_key: str - model: str = "claude-3-5-sonnet-20241022" - api_url: str = "https://api.anthropic.com" - max_tokens: int = 4096 - temperature: float = 0.7 - - def __post_init__(self) -> None: - """Validate configuration after initialization""" - if not self.api_key or not self.api_key.strip(): - raise ValueError("api_key cannot be empty") - if self.max_tokens <= 0: - raise ValueError("max_tokens must be positive") - if not 0.0 <= self.temperature <= 2.0: - raise ValueError("temperature must be between 0.0 and 2.0") - if not self.api_url or not self.api_url.strip(): - raise ValueError("api_url cannot be empty") - if not self.api_url.startswith(("http://", "https://")): - raise ValueError("api_url must start with http:// or https://") - - -@dataclass -class JournalConfig: - """日记配置""" - - daily_notes_folder: str - date_format: str = "YYYY-MM-DD" - file_extension: str = ".md" - - def __post_init__(self) -> None: - """Validate configuration after initialization""" - if not self.daily_notes_folder or not self.daily_notes_folder.strip(): - raise ValueError("daily_notes_folder cannot be empty") - if not self.file_extension.startswith("."): - raise ValueError("file_extension must start with a dot") - - -@dataclass -class OutputConfig: - """输出配置""" - - experiences_folder: str - lessons_folder: str - tasks_folder: str - problems_folder: str - achievements_folder: str - improvements_folder: str - - def __post_init__(self) -> None: - """Validate configuration after initialization""" - folders = [ - self.experiences_folder, - self.lessons_folder, - self.tasks_folder, - self.problems_folder, - self.achievements_folder, - self.improvements_folder, - ] - for folder in folders: - if not folder or not folder.strip(): - raise ValueError("All folder paths must be non-empty") - - -@dataclass -class AnalysisConfig: - """分析配置""" - - categories: List[str] = field(default_factory=list) - extraction_rules: Dict[str, Dict[str, Any]] = field(default_factory=dict) - - def __post_init__(self) -> None: - """Validate configuration after initialization""" - if not isinstance(self.categories, list): - raise ValueError("categories must be a list") - if not isinstance(self.extraction_rules, dict): - raise ValueError("extraction_rules must be a dictionary") - - -@dataclass -class LoggingConfig: - """日志配置""" - - level: str = "INFO" - file: str = "logs/journal_organizer.log" - - def __post_init__(self) -> None: - """Validate configuration after initialization""" - valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] - if self.level not in valid_levels: - raise ValueError(f"level must be one of {valid_levels}") - if not self.file or not self.file.strip(): - raise ValueError("file path cannot be empty") - - -class Config: - """系统配置管理器 - Enhanced with Pydantic validation""" - - def __init__(self, config_file: Optional[str] = None): - """ - 初始化配置 - - Args: - config_file: 配置文件路径,如果为 None 则使用默认位置 - """ - self.config_file = config_file or self._get_default_config_path() - self.validator = ConfigurationValidator() - self.logger = logging.getLogger(__name__) - - # Load and validate configuration using Pydantic - try: - self.system_config = self.validator.load_and_validate_config(self.config_file) - except (FileNotFoundError, ValueError) as e: - self.logger.error(f"Configuration error: {e}") - raise - - # Create legacy-compatible attributes - self._create_legacy_attributes() - - # Perform additional validations - self._perform_additional_validations() - - def _create_legacy_attributes(self) -> None: - """Create legacy-compatible attributes from Pydantic models""" - # Convert Pydantic models to legacy dataclass format for backward compatibility - self.obsidian = ObsidianConfig( - vault_path=self.system_config.obsidian.vault_path, - rest_api_url=self.system_config.obsidian.rest_api.url, - rest_api_key=self.system_config.obsidian.rest_api.api_key, - verify_ssl=self.system_config.obsidian.rest_api.verify_ssl - ) - - self.claude = ClaudeConfig( - api_key=self.system_config.claude.api_key, - model=self.system_config.claude.model, - api_url=self.system_config.claude.api_url, - max_tokens=self.system_config.claude.max_tokens, - temperature=self.system_config.claude.temperature - ) - - self.journal = JournalConfig( - daily_notes_folder=self.system_config.journal.daily_notes_folder, - date_format=self.system_config.journal.date_format, - file_extension=self.system_config.journal.file_extension - ) - - self.output = OutputConfig( - experiences_folder=self.system_config.output.experiences_folder, - lessons_folder=self.system_config.output.lessons_folder, - tasks_folder=self.system_config.output.tasks_folder, - problems_folder=self.system_config.output.problems_folder, - achievements_folder=self.system_config.output.achievements_folder, - improvements_folder=self.system_config.output.improvements_folder - ) - - self.analysis = AnalysisConfig( - categories=self.system_config.analysis.categories, - extraction_rules=self.system_config.analysis.extraction_rules - ) - - self.logging = LoggingConfig( - level=self.system_config.logging.level, - file=self.system_config.logging.file - ) - - def _perform_additional_validations(self) -> None: - """Perform additional validations and provide helpful guidance""" - # Validate API keys and provide setup instructions - api_key_issues = self.validator.validate_api_keys(self.system_config) - if api_key_issues: - error_msg = "API Key Configuration Issues:\n" + "\n\n".join(api_key_issues) - self.logger.error(error_msg) - raise ValueError(error_msg) - - # Validate file paths - path_issues = self.validator.validate_file_paths(self.system_config) - if path_issues: - error_msg = "File Path Issues:\n" + "\n".join(path_issues) - self.logger.warning(error_msg) - # Don't raise error for path issues, just warn - - def get_environment_variable_help(self) -> str: - """Get help for setting up environment variables""" - return self.validator.get_environment_variable_help() - - def get_path_setup_help(self) -> str: - """Get help for setting up file paths""" - return self.validator.get_path_setup_help() - - def _get_default_config_path(self) -> str: - """获取默认配置文件路径""" - # First check current directory - current_dir_config = Path("config.yaml") - if current_dir_config.exists(): - return str(current_dir_config) - - # Then check user home directory - config_dir = Path.home() / ".journal_organizer" - config_dir.mkdir(exist_ok=True) - return str(config_dir / "config.yaml") - - def get_validation_errors(self) -> List[str]: - """Get any validation errors or warnings""" - errors = [] - - try: - # Re-validate to get current status - api_key_issues = self.validator.validate_api_keys(self.system_config) - errors.extend(api_key_issues) - - path_issues = self.validator.validate_file_paths(self.system_config) - errors.extend(path_issues) - - except Exception as e: - errors.append(f"Validation error: {e}") - - return errors - - def reload_config(self) -> None: - """Reload configuration from file""" - try: - self.system_config = self.validator.load_and_validate_config(self.config_file) - self._create_legacy_attributes() - self._perform_additional_validations() - self.logger.info("Configuration reloaded successfully") - except Exception as e: - self.logger.error(f"Failed to reload configuration: {e}") - raise - - def get_daily_note_path(self, date_str: str) -> str: - """ - 获取指定日期的日记文件路径 - - Args: - date_str: 日期字符串,格式应与配置中的 date_format 一致 - - Returns: - 日记文件的相对路径 - """ - daily_path = ( - Path(self.journal.daily_notes_folder) - / f"{date_str}{self.journal.file_extension}" - ) - return str(daily_path) - - def to_dict(self) -> Dict[str, Any]: - """转换为字典(不包含敏感信息)""" - return { - "obsidian": { - "vault_path": self.obsidian.vault_path, - "rest_api_url": self.obsidian.rest_api_url, - }, - "claude": { - "model": self.claude.model, - "api_url": self.claude.api_url, - "max_tokens": self.claude.max_tokens, - "temperature": self.claude.temperature, - }, - "journal": { - "daily_notes_folder": self.journal.daily_notes_folder, - "date_format": self.journal.date_format, - "file_extension": self.journal.file_extension, - }, - "output": { - "experiences_folder": self.output.experiences_folder, - "lessons_folder": self.output.lessons_folder, - "tasks_folder": self.output.tasks_folder, - "problems_folder": self.output.problems_folder, - "achievements_folder": self.output.achievements_folder, - "improvements_folder": self.output.improvements_folder, - }, - "analysis": { - "categories": self.analysis.categories, - }, - "logging": { - "level": self.logging.level, - "file": self.logging.file, - }, - } diff --git a/./config_validation.py b/./config_validation.py deleted file mode 100644 index 16c9e92..0000000 --- a/./config_validation.py +++ /dev/null @@ -1,1316 +0,0 @@ -""" -Enhanced configuration validation using Pydantic models -Provides comprehensive validation, environment variable expansion, and error handling -""" - -import json -import os -import re -import logging -from pathlib import Path -from typing import Dict, Any, List, Optional, Union - -try: - from .dependency_manager import get_dependency_manager - from .configuration_loader import ConfigurationLoader, ConfigurationError, EnvironmentVariableError - from .configuration_migrator import ConfigurationMigrator - from .error_handling import ( - ClaudeConfigurationError, ClaudeAPIURLError, ClaudeModelValidationError, - ClaudeAPIKeyError, transform_pydantic_validation_error, get_error_handler - ) -except ImportError: - from dependency_manager import get_dependency_manager - from configuration_loader import ConfigurationLoader, ConfigurationError, EnvironmentVariableError - from configuration_migrator import ConfigurationMigrator - try: - from error_handling import ( - ClaudeConfigurationError, ClaudeAPIURLError, ClaudeModelValidationError, - ClaudeAPIKeyError, transform_pydantic_validation_error, get_error_handler - ) - except ImportError: - # Fallback if error handling not available - ClaudeConfigurationError = Exception - ClaudeAPIURLError = Exception - ClaudeModelValidationError = Exception - ClaudeAPIKeyError = Exception - def transform_pydantic_validation_error(e, section=""): return e - def get_error_handler(): return None - -# Try to import dependencies with graceful degradation -dependency_manager = get_dependency_manager() -yaml = dependency_manager.get_module('yaml') - -# Try to import pydantic with graceful degradation -pydantic_module = dependency_manager.get_module('pydantic') -if pydantic_module is not None: - from pydantic import BaseModel, Field, validator, root_validator - from pydantic.error_wrappers import ValidationError -else: - # Create fallback classes if pydantic is not available - class BaseModel: - def __init__(self, **kwargs): - for key, value in kwargs.items(): - setattr(self, key, value) - - def Field(*args, **kwargs): - return None - - def validator(*args, **kwargs): - def decorator(func): - return func - return decorator - - def root_validator(*args, **kwargs): - def decorator(func): - return func - return decorator - - class ValidationError(Exception): - def __init__(self, message): - super().__init__(message) - self.errors = lambda: [{'loc': ['unknown'], 'msg': message}] - - -class ObsidianRestAPIConfig(BaseModel): - """Obsidian REST API configuration with validation""" - - url: str = Field(..., description="REST API server URL") - api_key: str = Field(..., min_length=1, description="API key for authentication") - verify_ssl: bool = Field(default=False, description="Whether to verify SSL certificates") - - @validator('url') - def validate_url(cls, v: str) -> str: - """Validate URL format""" - if not v.startswith(('http://', 'https://')): - raise ValueError('URL must start with http:// or https://') - return v - - @validator('api_key') - def validate_api_key(cls, v: str) -> str: - """Validate API key format""" - if not v or not v.strip(): - raise ValueError('API key cannot be empty') - # Basic format validation - should be alphanumeric with possible dashes/underscores - if not re.match(r'^[a-zA-Z0-9_-]+$', v.strip()): - raise ValueError('API key contains invalid characters') - return v.strip() - - -class ObsidianConfig(BaseModel): - """Obsidian configuration with path validation""" - - vault_path: str = Field(..., description="Path to Obsidian vault") - rest_api: ObsidianRestAPIConfig = Field(..., description="REST API configuration") - - @validator('vault_path') - def validate_vault_path(cls, v: str) -> str: - """Validate vault path exists and is accessible""" - if not v or not v.strip(): - raise ValueError('Vault path cannot be empty') - - # Expand environment variables and user home - expanded_path = os.path.expandvars(os.path.expanduser(v.strip())) - path = Path(expanded_path) - - if not path.exists(): - raise ValueError(f'Vault path does not exist: {expanded_path}') - - if not path.is_dir(): - raise ValueError(f'Vault path is not a directory: {expanded_path}') - - # Check if it's readable - if not os.access(path, os.R_OK): - raise ValueError(f'Vault path is not readable: {expanded_path}') - - return str(path.resolve()) - - -class ClaudeAPIConfig(BaseModel): - """Enhanced Claude API configuration with URL and model validation""" - - api_key: str = Field(..., min_length=1, description="Claude API key") - api_url: Optional[str] = Field( - default="https://api.anthropic.com", - description="Claude API base URL" - ) - model: str = Field( - default="claude-3-5-sonnet-20241022", - description="Claude model name" - ) - max_tokens: int = Field(default=4096, ge=1, le=200000, description="Maximum tokens") - temperature: float = Field(default=0.7, ge=0.0, le=1.0, description="Temperature parameter") - - @validator('api_url') - def validate_api_url(cls, v: Optional[str]) -> str: - """Validate API URL format and accessibility""" - if v is None: - return "https://api.anthropic.com" - - # Parse URL to validate format - from urllib.parse import urlparse - parsed = urlparse(v) - if not parsed.scheme or not parsed.netloc: - raise ValueError(f"Invalid URL format: {v}") - - if parsed.scheme not in ['http', 'https']: - raise ValueError(f"URL must use http or https protocol: {v}") - - # Remove trailing slash for consistency - return v.rstrip('/') - - @validator('model') - def validate_model_name(cls, v: str) -> str: - """Validate Claude model name and provide suggestions""" - valid_models = [ - # Claude 3 series - "claude-3-opus-20240229", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307", - # Claude 3.5 series - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - # Latest aliases - "claude-3-opus-latest", - "claude-3-sonnet-latest", - "claude-3-haiku-latest", - "claude-3-5-sonnet-latest", - "claude-3-5-haiku-latest" - ] - - if v not in valid_models: - # Check if it follows Claude naming pattern - claude_pattern = r'^claude-\d+(\.\d+)?-(opus|sonnet|haiku)(-\d{8}|-latest)?$' - if not re.match(claude_pattern, v, re.IGNORECASE): - suggestions = ", ".join(valid_models[:5]) - raise ValueError( - f"Invalid model name: {v}. " - f"Valid models include: {suggestions}. " - f"Model names should follow pattern: claude-X-Y-YYYYMMDD or claude-X-Y-latest" - ) - - return v - - @validator('api_key') - def validate_api_key(cls, v: str) -> str: - """Validate Claude API key format""" - if not v or not v.strip(): - raise ValueError('Claude API key cannot be empty') - - # Claude API keys typically start with 'sk-ant-' - stripped = v.strip() - if not stripped.startswith('sk-ant-'): - raise ValueError('Claude API key should start with "sk-ant-"') - - # Should be at least 50 characters long - if len(stripped) < 50: - raise ValueError('Claude API key appears to be too short') - - return stripped - - -# Keep the old ClaudeConfig for backward compatibility -class ClaudeConfig(ClaudeAPIConfig): - """Legacy Claude API configuration - use ClaudeAPIConfig for new implementations""" - pass - - -class JournalConfig(BaseModel): - """Journal configuration with validation""" - - daily_notes_folder: str = Field(default="Daily", description="Folder for daily notes") - date_format: str = Field(default="YYYY-MM-DD", description="Date format for files") - file_extension: str = Field(default=".md", description="File extension for notes") - - @validator('daily_notes_folder') - def validate_folder(cls, v: str) -> str: - """Validate folder name""" - if not v or not v.strip(): - raise ValueError('Daily notes folder cannot be empty') - return v.strip() - - @validator('file_extension') - def validate_extension(cls, v: str) -> str: - """Validate file extension""" - if not v.startswith('.'): - raise ValueError('File extension must start with a dot') - return v - - -class OutputConfig(BaseModel): - """Output configuration with folder validation""" - - experiences_folder: str = Field(default="Knowledge/Experiences", description="Experiences folder") - lessons_folder: str = Field(default="Knowledge/Lessons", description="Lessons folder") - tasks_folder: str = Field(default="Tasks/Daily", description="Tasks folder") - problems_folder: str = Field(default="Knowledge/Problems", description="Problems folder") - achievements_folder: str = Field(default="Knowledge/Achievements", description="Achievements folder") - improvements_folder: str = Field(default="Knowledge/Improvements", description="Improvements folder") - - @validator('*') - def validate_folder_paths(cls, v: str) -> str: - """Validate all folder paths are non-empty""" - if not v or not v.strip(): - raise ValueError('Folder path cannot be empty') - return v.strip() - - -class AnalysisConfig(BaseModel): - """Analysis configuration with validation""" - - categories: List[str] = Field(default_factory=list, description="Analysis categories") - extraction_rules: Dict[str, Dict[str, Any]] = Field(default_factory=dict, description="Extraction rules") - - @validator('categories') - def validate_categories(cls, v: List[str]) -> List[str]: - """Validate categories list""" - if not isinstance(v, list): - raise ValueError('Categories must be a list') - - # Remove empty categories - valid_categories = [cat.strip() for cat in v if cat and cat.strip()] - return valid_categories - - -class LoggingConfig(BaseModel): - """Logging configuration with validation""" - - level: str = Field(default="INFO", description="Logging level") - file: str = Field(default="logs/journal_organizer.log", description="Log file path") - - @validator('level') - def validate_level(cls, v: str) -> str: - """Validate logging level""" - valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] - if v.upper() not in valid_levels: - raise ValueError(f'Logging level must be one of: {", ".join(valid_levels)}') - return v.upper() - - @validator('file') - def validate_file_path(cls, v: str) -> str: - """Validate log file path""" - if not v or not v.strip(): - raise ValueError('Log file path cannot be empty') - - # Ensure parent directory exists or can be created - log_path = Path(v.strip()) - try: - log_path.parent.mkdir(parents=True, exist_ok=True) - except (OSError, PermissionError) as e: - raise ValueError(f'Cannot create log directory: {e}') - - return str(log_path) - - -class SystemConfig(BaseModel): - """Complete system configuration with validation""" - - obsidian: ObsidianConfig = Field(..., description="Obsidian configuration") - claude: ClaudeAPIConfig = Field(..., description="Claude API configuration") - journal: JournalConfig = Field(default_factory=JournalConfig, description="Journal configuration") - output: OutputConfig = Field(default_factory=OutputConfig, description="Output configuration") - analysis: AnalysisConfig = Field(default_factory=AnalysisConfig, description="Analysis configuration") - logging: LoggingConfig = Field(default_factory=LoggingConfig, description="Logging configuration") - - class Config: - """Pydantic configuration""" - extra = 'forbid' # Don't allow extra fields - validate_assignment = True # Validate on assignment - - @root_validator - def validate_paths_relative_to_vault(cls, values: Dict[str, Any]) -> Dict[str, Any]: - """Validate that output paths make sense relative to vault""" - obsidian_config = values.get('obsidian') - output_config = values.get('output') - - if obsidian_config and output_config: - vault_path = Path(obsidian_config.vault_path) - - # Check if output folders would be accessible within vault - for folder_name in ['experiences_folder', 'lessons_folder', 'tasks_folder', - 'problems_folder', 'achievements_folder', 'improvements_folder']: - folder_path = getattr(output_config, folder_name) - if folder_path: - # Ensure the folder path doesn't try to escape the vault - full_path = vault_path / folder_path - try: - full_path.resolve().relative_to(vault_path.resolve()) - except ValueError: - raise ValueError(f'Output folder {folder_name} ({folder_path}) would be outside vault') - - return values - - -class ConfigurationValidator: - """Configuration validation and loading utility""" - - def __init__(self, logger: Optional[logging.Logger] = None): - self.env_var_pattern = re.compile(r'\$\{([^}]+)\}') - self.config_loader = ConfigurationLoader() - self.migrator = ConfigurationMigrator() - self.logger = logger or logging.getLogger(__name__) - self.error_handler = get_error_handler() - - def expand_environment_variables(self, data: Any) -> Any: - """ - Recursively expand environment variables in configuration data - - DEPRECATED: Use ConfigurationLoader.expand_environment_variables instead - This method is kept for backward compatibility - """ - return self.config_loader.expand_environment_variables(data) if isinstance(data, dict) else self.config_loader._expand_value(data) - - def load_and_validate_config(self, config_file: Union[str, Path]) -> SystemConfig: - """Load and validate configuration from file""" - self.logger.info(f"Loading configuration from: {config_file}") - - try: - # Use the new ConfigurationLoader for loading and environment variable expansion - self.logger.debug("Expanding environment variables in configuration") - expanded_config = self.config_loader.load_config(config_file) - self.logger.debug("Environment variable expansion completed successfully") - - except (ConfigurationError, EnvironmentVariableError) as e: - self.logger.error(f"Configuration loading failed: {str(e)}") - if self.error_handler: - error_response = self.error_handler.handle_configuration_error(e, "config_file") - self.logger.error(f"Configuration error details: {error_response}") - raise ValueError(str(e)) - - # Apply migration before validation to ensure backward compatibility - if self.migrator.check_migration_needed(expanded_config): - self.logger.info("Configuration migration needed, applying migration") - migration_preview = self.migrator.get_migration_preview(expanded_config) - for action in migration_preview: - self.logger.info(f"Migration action: {action}") - - expanded_config = self.migrator.migrate_configuration(expanded_config) - self.logger.info("Configuration migration completed successfully") - else: - self.logger.debug("No configuration migration needed") - - # Apply default values for missing sections and fields - self.logger.debug("Applying default values to configuration") - expanded_config = self._apply_default_values(expanded_config) - self.logger.debug("Default values applied successfully") - - # Validate configuration - try: - self.logger.debug("Starting configuration validation") - if pydantic_module is None: - self.logger.warning("Pydantic not available, using fallback validation") - # Fallback validation without pydantic - config = self._validate_config_without_pydantic(expanded_config) - self.logger.info("Configuration validation completed (fallback mode)") - return config - - config = SystemConfig(**expanded_config) - self.logger.info("Configuration validation completed successfully") - - # Log configuration summary (without sensitive data) - self._log_configuration_summary(config) - - return config - - except ValidationError as e: - self.logger.error("Configuration validation failed") - - # Transform pydantic errors into user-friendly Claude errors - claude_error = transform_pydantic_validation_error(e, "claude") - - # Log detailed validation errors - self._log_validation_errors(e) - - # Use error handler if available - if self.error_handler: - error_response = self.error_handler.handle_claude_configuration_error( - claude_error, operation="validate_config" - ) - self.logger.error(f"Validation error details: {error_response}") - - # Format validation errors nicely - error_messages = [] - for error in e.errors(): - field_path = ' -> '.join(str(loc) for loc in error['loc']) - error_messages.append(f"{field_path}: {error['msg']}") - - formatted_error = f'Configuration validation failed:\n' + '\n'.join(error_messages) - self.logger.error(f"Formatted validation error: {formatted_error}") - - raise ValueError(formatted_error) - - def _log_configuration_summary(self, config: SystemConfig) -> None: - """Log a summary of the loaded configuration (without sensitive data)""" - try: - summary = { - "obsidian": { - "vault_path": config.obsidian.vault_path, - "rest_api_url": config.obsidian.rest_api.url, - "verify_ssl": config.obsidian.rest_api.verify_ssl - }, - "claude": { - "api_url": config.claude.api_url, - "model": config.claude.model, - "max_tokens": config.claude.max_tokens, - "temperature": config.claude.temperature, - "is_custom_endpoint": config.claude.api_url != "https://api.anthropic.com" - }, - "journal": { - "daily_notes_folder": config.journal.daily_notes_folder, - "date_format": config.journal.date_format, - "file_extension": config.journal.file_extension - }, - "logging": { - "level": config.logging.level, - "file": config.logging.file - } - } - - self.logger.info(f"Configuration summary: {json.dumps(summary, indent=2)}") - - # Log specific warnings for custom configurations - if config.claude.api_url != "https://api.anthropic.com": - self.logger.warning(f"Using custom Claude API endpoint: {config.claude.api_url}") - - if not config.obsidian.rest_api.verify_ssl: - self.logger.warning("SSL verification disabled for Obsidian REST API") - - except Exception as e: - self.logger.debug(f"Could not log configuration summary: {e}") - - def _log_validation_errors(self, validation_error) -> None: - """Log detailed validation errors for debugging""" - try: - errors = validation_error.errors() - self.logger.debug(f"Total validation errors: {len(errors)}") - - for i, error in enumerate(errors): - field_path = ' -> '.join(str(loc) for loc in error.get('loc', [])) - error_type = error.get('type', 'unknown') - error_msg = error.get('msg', 'Unknown error') - input_value = error.get('input', 'N/A') - - # Sanitize input value for logging - if any(sensitive in field_path.lower() for sensitive in ['key', 'password', 'token']): - input_value = '[REDACTED]' - elif isinstance(input_value, str) and len(input_value) > 100: - input_value = input_value[:100] + '...' - - self.logger.debug( - f"Validation error {i+1}: " - f"field='{field_path}', type='{error_type}', " - f"message='{error_msg}', input='{input_value}'" - ) - - except Exception as e: - self.logger.debug(f"Could not log validation error details: {e}") - - def _apply_default_values(self, config_dict: Dict[str, Any]) -> Dict[str, Any]: - """ - Apply default values for missing configuration sections and fields - - Note: Most default value application is now handled by ConfigurationMigrator. - This method provides additional validation and ensures consistency. - - Args: - config_dict: Configuration dictionary loaded from file - - Returns: - Configuration dictionary with default values applied - """ - # Create a copy to avoid modifying the original - config_with_defaults = config_dict.copy() - - # The migrator should have already applied most defaults, - # but we'll ensure critical defaults are present as a safety measure - - # Ensure Claude API defaults are present (critical for functionality) - if 'claude' not in config_with_defaults: - config_with_defaults['claude'] = {} - - claude_config = config_with_defaults['claude'] - - # These are critical defaults that must be present - if 'api_url' not in claude_config: - claude_config['api_url'] = "https://api.anthropic.com" - - if 'model' not in claude_config: - claude_config['model'] = "claude-3-5-sonnet-20241022" - - if 'max_tokens' not in claude_config: - claude_config['max_tokens'] = 4096 - - if 'temperature' not in claude_config: - claude_config['temperature'] = 0.7 - - # Ensure other critical sections exist (migrator should have handled this) - self._ensure_section_exists(config_with_defaults, 'journal', { - 'daily_notes_folder': "Daily", - 'date_format': "YYYY-MM-DD", - 'file_extension': ".md" - }) - - self._ensure_section_exists(config_with_defaults, 'output', { - 'experiences_folder': "Knowledge/Experiences", - 'lessons_folder': "Knowledge/Lessons", - 'tasks_folder': "Tasks/Daily", - 'problems_folder': "Knowledge/Problems", - 'achievements_folder': "Knowledge/Achievements", - 'improvements_folder': "Knowledge/Improvements" - }) - - self._ensure_section_exists(config_with_defaults, 'analysis', { - 'categories': [], - 'extraction_rules': {} - }) - - self._ensure_section_exists(config_with_defaults, 'logging', { - 'level': "INFO", - 'file': "logs/journal_organizer.log" - }) - - return config_with_defaults - - def _ensure_section_exists(self, config_dict: Dict[str, Any], section_name: str, defaults: Dict[str, Any]) -> None: - """Ensure a configuration section exists with default values""" - if section_name not in config_dict: - config_dict[section_name] = defaults.copy() - else: - section_config = config_dict[section_name] - for field, default_value in defaults.items(): - if field not in section_config: - section_config[field] = default_value - - def _validate_config_without_pydantic(self, config_data: Dict[str, Any]) -> 'SystemConfig': - """Fallback validation when pydantic is not available""" - # Basic validation without pydantic - required_sections = ['obsidian', 'claude'] - for section in required_sections: - if section not in config_data: - raise ValueError(f'Missing required configuration section: {section}') - - # Basic obsidian validation - obsidian = config_data.get('obsidian', {}) - if not obsidian.get('vault_path'): - raise ValueError('obsidian.vault_path is required') - if not obsidian.get('rest_api', {}).get('api_key'): - raise ValueError('obsidian.rest_api.api_key is required') - - # Basic claude validation - now with defaults applied - claude = config_data.get('claude', {}) - if not claude.get('api_key'): - raise ValueError('claude.api_key is required') - - # Validate that defaults were applied correctly - if not claude.get('api_url'): - raise ValueError('claude.api_url should have default value') - if not claude.get('model'): - raise ValueError('claude.model should have default value') - - # Create a simple config object (fallback) - class SimpleConfig: - def __init__(self, data): - for key, value in data.items(): - if isinstance(value, dict): - setattr(self, key, SimpleConfig(value)) - else: - setattr(self, key, value) - - return SimpleConfig(config_data) - - def validate_api_keys(self, config: SystemConfig) -> List[str]: - """Validate API keys and return setup instructions if needed""" - self.logger.debug("Starting API key validation") - issues = [] - - # Check Claude API key - self.logger.debug("Validating Claude API key") - claude_key = config.claude.api_key - if not claude_key or claude_key.startswith('${'): - self.logger.warning("Claude API key is not set or uses environment variable placeholder") - issues.append(self._get_claude_api_key_setup_instructions()) - else: - # Validate Claude API key format - claude_issues = self._validate_claude_api_key_format(claude_key) - if claude_issues: - self.logger.warning(f"Claude API key format issues found: {len(claude_issues)} issues") - for issue in claude_issues: - self.logger.debug(f"Claude API key issue: {issue}") - issues.extend(claude_issues) - else: - self.logger.debug("Claude API key format validation passed") - - # Check Obsidian API key - self.logger.debug("Validating Obsidian API key") - obsidian_key = config.obsidian.rest_api.api_key - if not obsidian_key or obsidian_key.startswith('${'): - self.logger.warning("Obsidian API key is not set or uses environment variable placeholder") - issues.append(self._get_obsidian_api_key_setup_instructions()) - else: - # Validate Obsidian API key format - obsidian_issues = self._validate_obsidian_api_key_format(obsidian_key) - if obsidian_issues: - self.logger.warning(f"Obsidian API key format issues found: {len(obsidian_issues)} issues") - for issue in obsidian_issues: - self.logger.debug(f"Obsidian API key issue: {issue}") - issues.extend(obsidian_issues) - else: - self.logger.debug("Obsidian API key format validation passed") - - if issues: - self.logger.warning(f"API key validation completed with {len(issues)} issues") - else: - self.logger.info("API key validation completed successfully") - - return issues - - def _get_claude_api_key_setup_instructions(self) -> str: - """Get detailed setup instructions for Claude API key""" - return """Claude API Key Setup Required: - -1. Visit https://console.anthropic.com/ -2. Sign in or create an account -3. Navigate to 'API Keys' section -4. Click 'Create Key' and give it a name -5. Copy the generated key (starts with 'sk-ant-') -6. Set the environment variable: - export ANTHROPIC_API_KEY="sk-ant-your-key-here" - - Or add it to your shell profile (~/.bashrc, ~/.zshrc): - echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.zshrc - -7. Restart your terminal or run: source ~/.zshrc - -Note: Keep your API key secure and never commit it to version control.""" - - def _get_obsidian_api_key_setup_instructions(self) -> str: - """Get detailed setup instructions for Obsidian API key""" - return """Obsidian Local REST API Setup Required: - -1. Install the 'Local REST API' plugin in Obsidian: - - Open Obsidian - - Go to Settings → Community Plugins - - Browse and search for 'Local REST API' - - Install and enable the plugin - -2. Configure the plugin: - - Go to Settings → Local REST API - - Enable the API server - - Set a secure API key (recommended: generate a random string) - - Note the server URL (usually https://localhost:27123) - -3. Update your configuration: - - Set the API key in your config.yaml file - - Or use environment variable: export OBSIDIAN_API_KEY="your-key-here" - -4. Security note: - - The plugin uses HTTPS with a self-signed certificate - - Set verify_ssl: false in your config for local development""" - - def _validate_claude_api_key_format(self, api_key: str) -> List[str]: - """Validate Claude API key format and provide specific feedback""" - issues = [] - - if not api_key.startswith('sk-ant-'): - issues.append( - "Claude API key format issue: Key should start with 'sk-ant-'\n" - "Please verify you copied the complete key from https://console.anthropic.com/" - ) - - if len(api_key) < 50: - issues.append( - "Claude API key appears too short. Please verify you copied the complete key.\n" - "Claude API keys are typically 100+ characters long." - ) - - # Check for common copy-paste issues - if ' ' in api_key: - issues.append( - "Claude API key contains spaces. Please remove any whitespace.\n" - "API keys should be a single continuous string." - ) - - if api_key.endswith('...') or '...' in api_key: - issues.append( - "Claude API key appears to be truncated (contains '...').\n" - "Please copy the complete key from the Anthropic console." - ) - - return issues - - def _validate_obsidian_api_key_format(self, api_key: str) -> List[str]: - """Validate Obsidian API key format and provide specific feedback""" - issues = [] - - # Obsidian API keys are typically user-generated, so less strict validation - if len(api_key) < 8: - issues.append( - "Obsidian API key appears very short. For security, consider using a longer key.\n" - "Recommended: Generate a random string of at least 16 characters." - ) - - # Check for obviously insecure keys - insecure_keys = ['password', '123456', 'admin', 'test', 'key', 'secret'] - if api_key.lower() in insecure_keys: - issues.append( - f"Obsidian API key '{api_key}' is not secure.\n" - "Please generate a random, unique API key for better security." - ) - - # Check for common copy-paste issues - if ' ' in api_key: - issues.append( - "Obsidian API key contains spaces. Please remove any whitespace.\n" - "API keys should be a single continuous string." - ) - - return issues - - async def test_api_key_connectivity(self, config: SystemConfig) -> Dict[str, Any]: - """Test API key connectivity (optional, for advanced validation)""" - self.logger.debug("Starting API key connectivity testing") - - results = { - 'claude': {'status': 'unknown', 'message': ''}, - 'obsidian': {'status': 'unknown', 'message': ''} - } - - # Note: This is a placeholder for actual connectivity testing - # In a real implementation, you might want to make test API calls - # but that requires additional dependencies and network access - - # For now, just validate the format - self.logger.debug("Testing Claude API key format") - claude_issues = self._validate_claude_api_key_format(config.claude.api_key) - if not claude_issues: - results['claude'] = {'status': 'format_valid', 'message': 'API key format appears valid'} - self.logger.debug("Claude API key format validation passed") - else: - results['claude'] = {'status': 'format_invalid', 'message': '; '.join(claude_issues)} - self.logger.warning(f"Claude API key format validation failed: {results['claude']['message']}") - - self.logger.debug("Testing Obsidian API key format") - obsidian_issues = self._validate_obsidian_api_key_format(config.obsidian.rest_api.api_key) - if not obsidian_issues: - results['obsidian'] = {'status': 'format_valid', 'message': 'API key format appears valid'} - self.logger.debug("Obsidian API key format validation passed") - else: - results['obsidian'] = {'status': 'format_invalid', 'message': '; '.join(obsidian_issues)} - self.logger.warning(f"Obsidian API key format validation failed: {results['obsidian']['message']}") - - self.logger.info(f"API key connectivity testing completed: Claude={results['claude']['status']}, Obsidian={results['obsidian']['status']}") - - return results - - def test_environment_variables(self, config_data: Dict[str, Any]) -> List[str]: - """Test environment variable expansion and return any issues""" - self.logger.debug("Testing environment variable expansion") - - try: - issues = self.config_loader.validate_environment_variables(config_data) - - if issues: - self.logger.warning(f"Environment variable validation found {len(issues)} issues") - for issue in issues: - self.logger.debug(f"Environment variable issue: {issue}") - else: - self.logger.debug("Environment variable validation passed") - - return issues - - except Exception as e: - self.logger.error(f"Environment variable testing failed: {e}") - return [f"Environment variable testing failed: {str(e)}"] - - def get_environment_variable_help(self) -> str: - """Get help text for setting up environment variables""" - return """ -Environment Variable Setup: - -1. Claude API Key: - export ANTHROPIC_API_KEY="sk-ant-your-key-here" - Get your key from: https://console.anthropic.com/ - -2. For other API keys, set them as: - export YOUR_API_KEY="your-key-value" - -3. You can also use default values in your config: - api_key: "${API_KEY:-default-value}" - -4. Or make variables required with custom error messages: - api_key: "${API_KEY:?Please set your API key}" - -5. Check current environment variables: - env | grep API_KEY -""" - - def validate_file_paths(self, config: SystemConfig) -> List[str]: - """Validate file paths and return issues if any""" - self.logger.debug("Starting file path validation") - issues = [] - - # Validate vault path (already validated by pydantic, but let's check write access) - self.logger.debug(f"Validating vault path: {config.obsidian.vault_path}") - vault_issues = self._validate_vault_path(config.obsidian.vault_path) - if vault_issues: - self.logger.warning(f"Vault path validation issues: {len(vault_issues)} issues") - for issue in vault_issues: - self.logger.debug(f"Vault path issue: {issue}") - else: - self.logger.debug("Vault path validation passed") - issues.extend(vault_issues) - - if not vault_issues: # Only check other paths if vault path is valid - vault_path = Path(config.obsidian.vault_path) - - # Validate daily notes folder - self.logger.debug(f"Validating daily notes folder: {config.journal.daily_notes_folder}") - daily_notes_issues = self._validate_daily_notes_folder(vault_path, config.journal.daily_notes_folder) - if daily_notes_issues: - self.logger.info(f"Daily notes folder validation: {len(daily_notes_issues)} messages") - for issue in daily_notes_issues: - self.logger.debug(f"Daily notes folder: {issue}") - else: - self.logger.debug("Daily notes folder validation passed") - issues.extend(daily_notes_issues) - - # Validate output folders - self.logger.debug("Validating output folders") - output_issues = self._validate_output_folders(vault_path, config.output) - if output_issues: - self.logger.warning(f"Output folders validation issues: {len(output_issues)} issues") - for issue in output_issues: - self.logger.debug(f"Output folder issue: {issue}") - else: - self.logger.debug("Output folders validation passed") - issues.extend(output_issues) - - # Validate log file path - self.logger.debug(f"Validating log file path: {config.logging.file}") - log_issues = self._validate_log_file_path(config.logging.file) - if log_issues: - self.logger.warning(f"Log file path validation issues: {len(log_issues)} issues") - for issue in log_issues: - self.logger.debug(f"Log file path issue: {issue}") - else: - self.logger.debug("Log file path validation passed") - issues.extend(log_issues) - - if issues: - self.logger.warning(f"File path validation completed with {len(issues)} issues") - else: - self.logger.info("File path validation completed successfully") - - return issues - - def _validate_vault_path(self, vault_path: str) -> List[str]: - """Validate Obsidian vault path with detailed checks""" - issues = [] - path = Path(vault_path) - - # Check if path exists (already validated by pydantic) - if not path.exists(): - issues.append(f"Vault path does not exist: {vault_path}") - return issues - - # Check if it's a directory - if not path.is_dir(): - issues.append(f"Vault path is not a directory: {vault_path}") - return issues - - # Check read access - if not os.access(path, os.R_OK): - issues.append(f"Vault path is not readable: {vault_path}") - - # Check write access - if not os.access(path, os.W_OK): - issues.append( - f"Vault path is not writable: {vault_path}\n" - "The application needs write access to create and update notes.\n" - "Please check file permissions or run with appropriate privileges." - ) - - # Check if it looks like an Obsidian vault - obsidian_config_path = path / ".obsidian" - if not obsidian_config_path.exists(): - issues.append( - f"Path does not appear to be an Obsidian vault: {vault_path}\n" - "Expected to find .obsidian folder. Please verify the vault path is correct." - ) - - # Check available disk space (basic check) - try: - stat = os.statvfs(path) - free_space_mb = (stat.f_bavail * stat.f_frsize) / (1024 * 1024) - if free_space_mb < 100: # Less than 100MB - issues.append( - f"Low disk space in vault directory: {free_space_mb:.1f}MB available\n" - "Consider freeing up disk space to avoid issues when creating notes." - ) - except (OSError, AttributeError): - # statvfs not available on all systems (e.g., Windows) - pass - - return issues - - def _validate_daily_notes_folder(self, vault_path: Path, daily_notes_folder: str) -> List[str]: - """Validate daily notes folder""" - issues = [] - daily_notes_path = vault_path / daily_notes_folder - - if not daily_notes_path.exists(): - try: - daily_notes_path.mkdir(parents=True, exist_ok=True) - issues.append(f"Created daily notes folder: {daily_notes_path}") - except (OSError, PermissionError) as e: - issues.append( - f"Cannot create daily notes folder: {daily_notes_path}\n" - f"Error: {e}\n" - "Please create this folder manually or check permissions." - ) - elif not daily_notes_path.is_dir(): - issues.append( - f"Daily notes path exists but is not a directory: {daily_notes_path}\n" - "Please remove this file or choose a different folder name." - ) - elif not os.access(daily_notes_path, os.W_OK): - issues.append( - f"Daily notes folder is not writable: {daily_notes_path}\n" - "Please check folder permissions." - ) - - return issues - - def _validate_output_folders(self, vault_path: Path, output_config: OutputConfig) -> List[str]: - """Validate all output folders""" - issues = [] - - output_folders = { - 'experiences': output_config.experiences_folder, - 'lessons': output_config.lessons_folder, - 'tasks': output_config.tasks_folder, - 'problems': output_config.problems_folder, - 'achievements': output_config.achievements_folder, - 'improvements': output_config.improvements_folder - } - - for folder_type, folder_path in output_folders.items(): - full_path = vault_path / folder_path - - # Check if path would be outside vault (security check) - try: - full_path.resolve().relative_to(vault_path.resolve()) - except ValueError: - issues.append( - f"Output folder '{folder_type}' ({folder_path}) would be outside vault\n" - "This is not allowed for security reasons. Please use a relative path within the vault." - ) - continue - - if not full_path.exists(): - try: - full_path.mkdir(parents=True, exist_ok=True) - # This is informational, not an error - pass - except (OSError, PermissionError) as e: - issues.append( - f"Cannot create {folder_type} folder: {full_path}\n" - f"Error: {e}\n" - "Please create this folder manually or check permissions." - ) - elif not full_path.is_dir(): - issues.append( - f"Output path for {folder_type} exists but is not a directory: {full_path}\n" - "Please remove this file or choose a different folder name." - ) - elif not os.access(full_path, os.W_OK): - issues.append( - f"Output folder for {folder_type} is not writable: {full_path}\n" - "Please check folder permissions." - ) - - return issues - - def _validate_log_file_path(self, log_file_path: str) -> List[str]: - """Validate log file path""" - issues = [] - log_path = Path(log_file_path) - - # Check if parent directory exists or can be created - try: - log_path.parent.mkdir(parents=True, exist_ok=True) - except (OSError, PermissionError) as e: - issues.append( - f"Cannot create log directory: {log_path.parent}\n" - f"Error: {e}\n" - "Please create the directory manually or choose a different log file path." - ) - return issues - - # Check if log file is writable (if it exists) - if log_path.exists(): - if not os.access(log_path, os.W_OK): - issues.append( - f"Log file is not writable: {log_path}\n" - "Please check file permissions or choose a different log file path." - ) - else: - # Check if we can create the log file - try: - log_path.touch() - log_path.unlink() # Remove the test file - except (OSError, PermissionError) as e: - issues.append( - f"Cannot create log file: {log_path}\n" - f"Error: {e}\n" - "Please check directory permissions or choose a different log file path." - ) - - return issues - - def get_path_setup_help(self) -> str: - """Get help text for setting up file paths""" - return """ -File Path Setup Guide: - -1. Obsidian Vault Path: - - Must point to an existing Obsidian vault directory - - Should contain a .obsidian folder - - Must have read and write permissions - - Example: "/Users/username/Documents/MyVault" - -2. Daily Notes Folder: - - Relative path within your vault - - Will be created if it doesn't exist - - Example: "Daily" or "Journal/Daily" - -3. Output Folders: - - All paths are relative to your vault - - Will be created automatically if they don't exist - - Must be within the vault for security - - Examples: "Knowledge/Experiences", "Tasks/Daily" - -4. Log File: - - Can be absolute or relative path - - Parent directory will be created if needed - - Must have write permissions - - Example: "logs/journal_organizer.log" - -5. Common Issues: - - Permission denied: Check folder/file permissions - - Path not found: Verify the path exists and is correct - - Outside vault: Use relative paths within your vault - - Disk space: Ensure sufficient free space - -6. Fixing Permissions (Unix/Linux/macOS): - chmod 755 /path/to/vault # For directories - chmod 644 /path/to/file # For files -""" - - def check_migration_needed(self, config_dict: Dict[str, Any]) -> bool: - """ - Check if configuration needs migration - - Args: - config_dict: Configuration dictionary to check - - Returns: - True if migration is needed, False otherwise - """ - return self.migrator.check_migration_needed(config_dict) - - def get_migration_preview(self, config_dict: Dict[str, Any]) -> List[str]: - """ - Get a preview of what migration actions would be taken - - Args: - config_dict: Configuration dictionary to analyze - - Returns: - List of migration actions that would be taken - """ - return self.migrator.get_migration_preview(config_dict) - - def get_supported_model_names(self) -> List[str]: - """ - Get list of all supported model names (both current and legacy) - - Returns: - List of supported model names - """ - return self.migrator.get_supported_model_names() - - def validate_configuration_comprehensive(self, config: SystemConfig) -> Dict[str, Any]: - """ - Perform comprehensive configuration validation with detailed logging and reporting - - Args: - config: SystemConfig instance to validate - - Returns: - Dictionary with comprehensive validation results - """ - self.logger.info("Starting comprehensive configuration validation") - - validation_results = { - 'overall_status': 'unknown', - 'validation_timestamp': None, - 'api_keys': {'status': 'unknown', 'issues': []}, - 'file_paths': {'status': 'unknown', 'issues': []}, - 'environment_variables': {'status': 'unknown', 'issues': []}, - 'configuration_summary': {}, - 'warnings': [], - 'errors': [], - 'recommendations': [] - } - - try: - from datetime import datetime - validation_results['validation_timestamp'] = datetime.now().isoformat() - - # 1. Validate API keys - self.logger.info("Phase 1: API key validation") - api_key_issues = self.validate_api_keys(config) - validation_results['api_keys'] = { - 'status': 'failed' if api_key_issues else 'passed', - 'issues': api_key_issues - } - - if api_key_issues: - validation_results['errors'].extend(api_key_issues) - self.logger.warning(f"API key validation failed with {len(api_key_issues)} issues") - else: - self.logger.info("API key validation passed") - - # 2. Validate file paths - self.logger.info("Phase 2: File path validation") - file_path_issues = self.validate_file_paths(config) - validation_results['file_paths'] = { - 'status': 'failed' if file_path_issues else 'passed', - 'issues': file_path_issues - } - - if file_path_issues: - validation_results['errors'].extend(file_path_issues) - self.logger.warning(f"File path validation failed with {len(file_path_issues)} issues") - else: - self.logger.info("File path validation passed") - - # 3. Test environment variables (if we have the raw config data) - # Note: This would require the original config dict, which we don't have here - # So we'll mark it as skipped for now - validation_results['environment_variables'] = { - 'status': 'skipped', - 'issues': ['Environment variable testing requires raw configuration data'] - } - self.logger.debug("Environment variable validation skipped (requires raw config data)") - - # 4. Generate configuration summary - try: - validation_results['configuration_summary'] = { - 'claude_api_url': config.claude.api_url, - 'claude_model': config.claude.model, - 'is_custom_claude_endpoint': config.claude.api_url != "https://api.anthropic.com", - 'obsidian_vault_path': config.obsidian.vault_path, - 'obsidian_api_url': config.obsidian.rest_api.url, - 'ssl_verification_disabled': not config.obsidian.rest_api.verify_ssl, - 'log_level': config.logging.level, - 'daily_notes_folder': config.journal.daily_notes_folder - } - self.logger.debug("Configuration summary generated") - except Exception as e: - self.logger.warning(f"Could not generate configuration summary: {e}") - - # 5. Generate warnings and recommendations - if config.claude.api_url != "https://api.anthropic.com": - warning = f"Using custom Claude API endpoint: {config.claude.api_url}" - validation_results['warnings'].append(warning) - self.logger.warning(warning) - - if not config.obsidian.rest_api.verify_ssl: - warning = "SSL verification is disabled for Obsidian REST API" - validation_results['warnings'].append(warning) - self.logger.warning(warning) - - if config.logging.level == "DEBUG": - recommendation = "Debug logging is enabled. Consider using INFO level for production." - validation_results['recommendations'].append(recommendation) - self.logger.info(recommendation) - - # 6. Determine overall status - has_errors = bool(validation_results['errors']) - has_warnings = bool(validation_results['warnings']) - - if has_errors: - validation_results['overall_status'] = 'failed' - self.logger.error(f"Configuration validation failed with {len(validation_results['errors'])} errors") - elif has_warnings: - validation_results['overall_status'] = 'passed_with_warnings' - self.logger.warning(f"Configuration validation passed with {len(validation_results['warnings'])} warnings") - else: - validation_results['overall_status'] = 'passed' - self.logger.info("Configuration validation passed successfully") - - return validation_results - - except Exception as e: - error_msg = f"Comprehensive validation failed: {str(e)}" - self.logger.error(error_msg) - validation_results['overall_status'] = 'error' - validation_results['errors'].append(error_msg) - return validation_results - - -def create_example_config() -> Dict[str, Any]: - """Create an example configuration dictionary""" - return { - "obsidian": { - "vault_path": "/path/to/your/obsidian/vault", - "rest_api": { - "url": "https://localhost:27123", - "api_key": "your-api-key-here", - "verify_ssl": False - } - }, - "claude": { - "api_key": "${ANTHROPIC_API_KEY}", - "model": "claude-3-5-sonnet-20241022", - "max_tokens": 4096, - "temperature": 0.7 - }, - "journal": { - "daily_notes_folder": "Daily", - "date_format": "YYYY-MM-DD", - "file_extension": ".md" - }, - "output": { - "experiences_folder": "Knowledge/Experiences", - "lessons_folder": "Knowledge/Lessons", - "tasks_folder": "Tasks/Daily", - "problems_folder": "Knowledge/Problems", - "achievements_folder": "Knowledge/Achievements", - "improvements_folder": "Knowledge/Improvements" - }, - "analysis": { - "categories": [ - "技术学习", - "项目管理", - "个人成长", - "团队协作", - "问题解决" - ], - "extraction_rules": { - "experiences": { - "enabled": True, - "description": "提取日记中的重要经验和见解" - }, - "lessons": { - "enabled": True, - "description": "提取学到的知识点和最佳实践" - } - } - }, - "logging": { - "level": "INFO", - "file": "logs/journal_organizer.log" - } - } \ No newline at end of file diff --git a/./configuration_loader.py b/./configuration_loader.py deleted file mode 100644 index e6bbc0c..0000000 --- a/./configuration_loader.py +++ /dev/null @@ -1,268 +0,0 @@ -""" -Enhanced configuration loader with comprehensive environment variable expansion -Implements ${VAR} and ${VAR:-default} pattern support with recursive expansion -""" - -import os -import re -from typing import Any, Dict, List, Union -from pathlib import Path - -try: - from .dependency_manager import get_dependency_manager -except ImportError: - from dependency_manager import get_dependency_manager - -# Try to import dependencies with graceful degradation -dependency_manager = get_dependency_manager() -yaml = dependency_manager.get_module('yaml') - - -class ConfigurationError(Exception): - """Base exception for configuration-related errors""" - pass - - -class EnvironmentVariableError(ConfigurationError): - """Exception for environment variable expansion errors""" - pass - - -class ConfigurationLoader: - """Enhanced configuration loader with environment variable expansion""" - - def __init__(self): - # Pattern to match ${VAR} and ${VAR:-default} syntax - self.env_var_pattern = re.compile(r'\$\{([^}]+)\}') - - def expand_environment_variables(self, config_dict: Dict[str, Any]) -> Dict[str, Any]: - """ - Recursively expand environment variables in configuration - - Supports patterns: - - ${VAR}: Required variable, raises error if not set - - ${VAR:-default}: Variable with default value - - ${VAR:?error_message}: Required variable with custom error message - - Args: - config_dict: Configuration dictionary to expand - - Returns: - Configuration dictionary with expanded environment variables - - Raises: - EnvironmentVariableError: If required environment variable is missing - """ - return self._expand_value(config_dict) - - def _expand_value(self, value: Any) -> Any: - """Recursively expand environment variables in any value type""" - if isinstance(value, str): - return self._expand_string_env_vars(value) - elif isinstance(value, dict): - return {k: self._expand_value(v) for k, v in value.items()} - elif isinstance(value, list): - return [self._expand_value(item) for item in value] - else: - return value - - def _expand_string_env_vars(self, text: str) -> str: - """ - Expand environment variables in a string with enhanced support - - Supports multiple expansion patterns: - - ${VAR}: Required variable - - ${VAR:-default}: Variable with default value - - ${VAR:?error_message}: Required variable with custom error message - - Args: - text: String potentially containing environment variable references - - Returns: - String with environment variables expanded - - Raises: - EnvironmentVariableError: If required environment variable is missing - """ - def replace_env_var(match): - var_expr = match.group(1) - - # Handle ${VAR:-default} pattern - if ':-' in var_expr: - var_name, default_value = var_expr.split(':-', 1) - var_name = var_name.strip() - env_value = os.getenv(var_name) - return env_value if env_value is not None else default_value - - # Handle ${VAR:?error_message} pattern - elif ':?' in var_expr: - var_name, error_msg = var_expr.split(':?', 1) - var_name = var_name.strip() - env_value = os.getenv(var_name) - if env_value is None: - raise EnvironmentVariableError( - f"Environment variable '{var_name}' is required: {error_msg}" - ) - return env_value - - # Handle simple ${VAR} pattern - else: - var_name = var_expr.strip() - env_value = os.getenv(var_name) - if env_value is None: - raise EnvironmentVariableError( - f"Environment variable '{var_name}' is not set. " - f"Please set it or provide a default value using ${{{var_name}:-default}}" - ) - return env_value - - return self.env_var_pattern.sub(replace_env_var, text) - - @classmethod - def load_config(cls, config_path: Union[str, Path]) -> Dict[str, Any]: - """ - Load and validate configuration with environment variable expansion - - Args: - config_path: Path to configuration file (YAML or JSON) - - Returns: - Configuration dictionary with expanded environment variables - - Raises: - ConfigurationError: If configuration file cannot be loaded or parsed - EnvironmentVariableError: If required environment variables are missing - """ - loader = cls() - config_path = Path(config_path) - - if not config_path.exists(): - raise ConfigurationError(f"Configuration file not found: {config_path}") - - # Load raw configuration - try: - with config_path.open('r', encoding='utf-8') as f: - if config_path.suffix.lower() in ['.yaml', '.yml']: - if yaml is None: - raise ConfigurationError( - 'YAML configuration files require PyYAML library.\n' - 'Please install it with: pip install pyyaml>=6.0' - ) - config_dict = yaml.safe_load(f) or {} - elif config_path.suffix.lower() == '.json': - import json - config_dict = json.load(f) - else: - raise ConfigurationError( - 'Configuration file must be YAML (.yaml/.yml) or JSON (.json)' - ) - except Exception as e: - if isinstance(e, ConfigurationError): - raise - elif yaml is None and 'yaml' in str(e).lower(): - raise ConfigurationError( - 'YAML parsing failed - PyYAML library not available.\n' - 'Please install it with: pip install pyyaml>=6.0\n' - f'Original error: {e}' - ) - else: - raise ConfigurationError(f"Failed to parse configuration file: {e}") - - # Expand environment variables - try: - expanded_config = loader.expand_environment_variables(config_dict) - return expanded_config - except EnvironmentVariableError as e: - raise EnvironmentVariableError(f"Environment variable expansion failed: {e}") - - def validate_environment_variables(self, config_dict: Dict[str, Any]) -> List[str]: - """ - Validate that all required environment variables are available - - Args: - config_dict: Configuration dictionary to validate - - Returns: - List of missing environment variable error messages - """ - missing_vars = [] - - def check_env_vars_in_value(value: Any, path: str = "") -> None: - if isinstance(value, str): - # Find all environment variable references - matches = self.env_var_pattern.findall(value) - for match in matches: - var_expr = match - - # Skip variables with default values - if ':-' in var_expr: - continue - - # Handle custom error message pattern - if ':?' in var_expr: - var_name = var_expr.split(':?')[0].strip() - else: - var_name = var_expr.strip() - - if not os.getenv(var_name): - location = f" at {path}" if path else "" - missing_vars.append(f"Environment variable '{var_name}'{location} is not set") - - elif isinstance(value, dict): - for key, val in value.items(): - new_path = f"{path}.{key}" if path else key - check_env_vars_in_value(val, new_path) - - elif isinstance(value, list): - for i, item in enumerate(value): - new_path = f"{path}[{i}]" if path else f"[{i}]" - check_env_vars_in_value(item, new_path) - - check_env_vars_in_value(config_dict) - return missing_vars - - def get_environment_variable_references(self, config_dict: Dict[str, Any]) -> Dict[str, List[str]]: - """ - Get all environment variable references in the configuration - - Args: - config_dict: Configuration dictionary to analyze - - Returns: - Dictionary mapping variable names to list of locations where they're used - """ - env_vars = {} - - def collect_env_vars_in_value(value: Any, path: str = "") -> None: - if isinstance(value, str): - matches = self.env_var_pattern.findall(value) - for match in matches: - var_expr = match - - # Extract variable name (handle default value and error message patterns) - if ':-' in var_expr: - var_name = var_expr.split(':-')[0].strip() - elif ':?' in var_expr: - var_name = var_expr.split(':?')[0].strip() - else: - var_name = var_expr.strip() - - if var_name not in env_vars: - env_vars[var_name] = [] - - location = path if path else "root" - if location not in env_vars[var_name]: - env_vars[var_name].append(location) - - elif isinstance(value, dict): - for key, val in value.items(): - new_path = f"{path}.{key}" if path else key - collect_env_vars_in_value(val, new_path) - - elif isinstance(value, list): - for i, item in enumerate(value): - new_path = f"{path}[{i}]" if path else f"[{i}]" - collect_env_vars_in_value(item, new_path) - - collect_env_vars_in_value(config_dict) - return env_vars \ No newline at end of file diff --git a/./configuration_migrator.py b/./configuration_migrator.py deleted file mode 100644 index 16dd449..0000000 --- a/./configuration_migrator.py +++ /dev/null @@ -1,349 +0,0 @@ -""" -Configuration migration utility for backward compatibility -Handles migration from legacy configuration formats to new enhanced format -""" - -import logging -from typing import Dict, Any, List, Optional -from pathlib import Path - - -class ConfigurationMigrator: - """Handle migration from legacy configuration formats""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - - # Model name migrations mapping old names to new names - self.model_migrations = { - # Legacy model names to current format - "claude-3-sonnet": "claude-3-sonnet-20240229", - "claude-3-opus": "claude-3-opus-20240229", - "claude-3-haiku": "claude-3-haiku-20240307", - "claude-3.5-sonnet": "claude-3-5-sonnet-20241022", - "claude-3.5-haiku": "claude-3-5-haiku-20241022", - # Handle common variations - "claude-sonnet": "claude-3-sonnet-20240229", - "claude-opus": "claude-3-opus-20240229", - "claude-haiku": "claude-3-haiku-20240307", - "sonnet": "claude-3-5-sonnet-20241022", - "opus": "claude-3-opus-20240229", - "haiku": "claude-3-haiku-20240307", - } - - # Default values for new fields - self.default_values = { - 'claude': { - 'api_url': "https://api.anthropic.com", - 'model': "claude-3-5-sonnet-20241022", - 'max_tokens': 4096, - 'temperature': 0.7 - }, - 'journal': { - 'daily_notes_folder': "Daily", - 'date_format': "YYYY-MM-DD", - 'file_extension': ".md" - }, - 'output': { - 'experiences_folder': "Knowledge/Experiences", - 'lessons_folder': "Knowledge/Lessons", - 'tasks_folder': "Tasks/Daily", - 'problems_folder': "Knowledge/Problems", - 'achievements_folder': "Knowledge/Achievements", - 'improvements_folder': "Knowledge/Improvements" - }, - 'analysis': { - 'categories': [], - 'extraction_rules': {} - }, - 'logging': { - 'level': "INFO", - 'file': "logs/journal_organizer.log" - } - } - - def migrate_claude_config(self, config_dict: Dict[str, Any]) -> Dict[str, Any]: - """ - Migrate legacy Claude configuration to new format - - Args: - config_dict: Configuration dictionary to migrate - - Returns: - Migrated configuration dictionary - """ - # Create a copy to avoid modifying the original - migrated_config = config_dict.copy() - - # Ensure claude section exists - if 'claude' not in migrated_config: - migrated_config['claude'] = {} - self.logger.info("Created missing 'claude' configuration section") - - claude_config = migrated_config['claude'] - migration_actions = [] - - # Add default api_url if not present - if 'api_url' not in claude_config: - claude_config['api_url'] = self.default_values['claude']['api_url'] - migration_actions.append(f"Added default api_url: {claude_config['api_url']}") - - # Ensure model has a default value - if 'model' not in claude_config: - claude_config['model'] = self.default_values['claude']['model'] - migration_actions.append(f"Added default model: {claude_config['model']}") - else: - # Migrate old model names to new format if needed - old_model = claude_config['model'] - if old_model in self.model_migrations: - new_model = self.model_migrations[old_model] - claude_config['model'] = new_model - migration_actions.append(f"Migrated model '{old_model}' to '{new_model}'") - - # Add other default values if missing - if 'max_tokens' not in claude_config: - claude_config['max_tokens'] = self.default_values['claude']['max_tokens'] - migration_actions.append(f"Added default max_tokens: {claude_config['max_tokens']}") - - if 'temperature' not in claude_config: - claude_config['temperature'] = self.default_values['claude']['temperature'] - migration_actions.append(f"Added default temperature: {claude_config['temperature']}") - - # Log migration actions - if migration_actions: - self.logger.info("Claude configuration migration completed:") - for action in migration_actions: - self.logger.info(f" - {action}") - - return migrated_config - - def migrate_configuration(self, config_dict: Dict[str, Any]) -> Dict[str, Any]: - """ - Migrate complete configuration from legacy format to new format - - Args: - config_dict: Configuration dictionary to migrate - - Returns: - Migrated configuration dictionary - """ - migrated_config = config_dict.copy() - all_migration_actions = [] - - # Migrate Claude configuration - migrated_config = self.migrate_claude_config(migrated_config) - - # Migrate other sections if needed - migrated_config, journal_actions = self._migrate_journal_config(migrated_config) - all_migration_actions.extend(journal_actions) - - migrated_config, output_actions = self._migrate_output_config(migrated_config) - all_migration_actions.extend(output_actions) - - migrated_config, analysis_actions = self._migrate_analysis_config(migrated_config) - all_migration_actions.extend(analysis_actions) - - migrated_config, logging_actions = self._migrate_logging_config(migrated_config) - all_migration_actions.extend(logging_actions) - - # Log overall migration summary - if all_migration_actions: - self.logger.info(f"Configuration migration completed with {len(all_migration_actions)} changes") - self._log_migration_summary(all_migration_actions) - else: - self.logger.debug("No configuration migration needed - all fields are up to date") - - return migrated_config - - def _migrate_journal_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]: - """Migrate journal configuration section""" - migrated_config = config_dict.copy() - actions = [] - - if 'journal' not in migrated_config: - migrated_config['journal'] = self.default_values['journal'].copy() - actions.append("Created missing 'journal' configuration section with defaults") - else: - journal_config = migrated_config['journal'] - - # Add missing fields with defaults - for field, default_value in self.default_values['journal'].items(): - if field not in journal_config: - journal_config[field] = default_value - actions.append(f"Added default journal.{field}: {default_value}") - - return migrated_config, actions - - def _migrate_output_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]: - """Migrate output configuration section""" - migrated_config = config_dict.copy() - actions = [] - - if 'output' not in migrated_config: - migrated_config['output'] = self.default_values['output'].copy() - actions.append("Created missing 'output' configuration section with defaults") - else: - output_config = migrated_config['output'] - - # Add missing fields with defaults - for field, default_value in self.default_values['output'].items(): - if field not in output_config: - output_config[field] = default_value - actions.append(f"Added default output.{field}: {default_value}") - - return migrated_config, actions - - def _migrate_analysis_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]: - """Migrate analysis configuration section""" - migrated_config = config_dict.copy() - actions = [] - - if 'analysis' not in migrated_config: - migrated_config['analysis'] = self.default_values['analysis'].copy() - actions.append("Created missing 'analysis' configuration section with defaults") - else: - analysis_config = migrated_config['analysis'] - - # Add missing fields with defaults - for field, default_value in self.default_values['analysis'].items(): - if field not in analysis_config: - analysis_config[field] = default_value - actions.append(f"Added default analysis.{field}: {default_value}") - - return migrated_config, actions - - def _migrate_logging_config(self, config_dict: Dict[str, Any]) -> tuple[Dict[str, Any], List[str]]: - """Migrate logging configuration section""" - migrated_config = config_dict.copy() - actions = [] - - if 'logging' not in migrated_config: - migrated_config['logging'] = self.default_values['logging'].copy() - actions.append("Created missing 'logging' configuration section with defaults") - else: - logging_config = migrated_config['logging'] - - # Add missing fields with defaults - for field, default_value in self.default_values['logging'].items(): - if field not in logging_config: - logging_config[field] = default_value - actions.append(f"Added default logging.{field}: {default_value}") - - return migrated_config, actions - - def _log_migration_summary(self, actions: List[str]) -> None: - """Log a summary of migration actions taken""" - self.logger.info("Migration summary:") - for action in actions: - self.logger.info(f" - {action}") - - # Provide helpful information about new features - self.logger.info("") - self.logger.info("New configuration options are now available:") - self.logger.info(" - claude.api_url: Configure custom Claude API endpoints") - self.logger.info(" - claude.model: Enhanced model validation with suggestions") - self.logger.info(" - Environment variable support: Use ${VAR} and ${VAR:-default} patterns") - self.logger.info(" - See config.example.yaml for complete configuration examples") - - def check_migration_needed(self, config_dict: Dict[str, Any]) -> bool: - """ - Check if configuration needs migration - - Args: - config_dict: Configuration dictionary to check - - Returns: - True if migration is needed, False otherwise - """ - # Check if Claude section needs migration - claude_config = config_dict.get('claude', {}) - - # Check for missing new fields - if 'api_url' not in claude_config: - return True - - # Check for old model names that need migration - model = claude_config.get('model', '') - if model in self.model_migrations: - return True - - # Check for missing other sections - required_sections = ['journal', 'output', 'analysis', 'logging'] - for section in required_sections: - if section not in config_dict: - return True - - # Check for missing fields in existing sections - section_config = config_dict[section] - default_fields = self.default_values.get(section, {}) - for field in default_fields: - if field not in section_config: - return True - - return False - - def get_migration_preview(self, config_dict: Dict[str, Any]) -> List[str]: - """ - Get a preview of what migration actions would be taken - - Args: - config_dict: Configuration dictionary to analyze - - Returns: - List of migration actions that would be taken - """ - preview_actions = [] - - # Check Claude configuration - claude_config = config_dict.get('claude', {}) - - if 'api_url' not in claude_config: - preview_actions.append(f"Would add default api_url: {self.default_values['claude']['api_url']}") - - if 'model' not in claude_config: - preview_actions.append(f"Would add default model: {self.default_values['claude']['model']}") - elif claude_config['model'] in self.model_migrations: - old_model = claude_config['model'] - new_model = self.model_migrations[old_model] - preview_actions.append(f"Would migrate model '{old_model}' to '{new_model}'") - - # Check other sections - for section_name, section_defaults in self.default_values.items(): - if section_name == 'claude': - continue # Already handled above - - if section_name not in config_dict: - preview_actions.append(f"Would create missing '{section_name}' section with defaults") - else: - section_config = config_dict[section_name] - for field, default_value in section_defaults.items(): - if field not in section_config: - preview_actions.append(f"Would add default {section_name}.{field}: {default_value}") - - return preview_actions - - def get_supported_model_names(self) -> List[str]: - """ - Get list of all supported model names (both old and new) - - Returns: - List of supported model names - """ - # Current valid models - current_models = [ - "claude-3-opus-20240229", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307", - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "claude-3-opus-latest", - "claude-3-sonnet-latest", - "claude-3-haiku-latest", - "claude-3-5-sonnet-latest", - "claude-3-5-haiku-latest" - ] - - # Legacy models that will be migrated - legacy_models = list(self.model_migrations.keys()) - - return current_models + legacy_models \ No newline at end of file diff --git a/./conversation/__init__.py b/./conversation/__init__.py deleted file mode 100644 index 4cddb39..0000000 --- a/./conversation/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -对话模块 -提供对话式 Agent 的核心功能 -""" - -from .intent_understanding import IntentUnderstanding, Intent -from .conversation_state import ConversationState, Message, Task, TaskStatus -from .response_generator import ResponseGenerator -from .conversational_agent import ConversationalAgent, ChatResponse - -__all__ = [ - "IntentUnderstanding", - "Intent", - "ConversationState", - "Message", - "Task", - "TaskStatus", - "ResponseGenerator", - "ConversationalAgent", - "ChatResponse", -] diff --git a/./conversation/__pycache__/__init__.cpython-310.pyc b/./conversation/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 170c32952a4f16ef57897101e8eb6b6ccd12e1cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 649 zcmZvZJ#W=86o&15+~kH_`auWAu35;!h!Cg)LMjN++r^7zuC3rpVpp~UDr=d#lnn_~ z@iD3=d+XJ#rwD2@r!J6^!&%ySIba$dbD}7V!~|xeEDqg=2Q3WpgVkh zcJL+p1>KKDU@zSk#^rYfWl9?&C>NC-ErUg5*S4g1>@vh~DRvAo%qDl3)}&%KniBoU zW}ZIN>H^xe`%Ldiq1i1am?B0hUt=gj>fVsew;mGn?{+a(epo8>h&*DCkw@Z@dW=2% z$nO>_Ho=(68OC;l_Z{GN*uuFSTAvMCz^+Hb{#U}TtH0K<8`#o&Ulwf)1xSru@4}p$ z@lHrxWBdWA74OjF_jb9f-Q3D2T+sdUo|1DmGrE)yG^d=1a<0m`Y?W|&QdLCor%aW@ kLM6sraD(xcy?vWrYb0%JcHO_4&K)AK8efJvT!ne?2SnnHYo6t|OHlFQwJ`oZ2c3lS0$3sgckR7k);3xgNSG*%jjoyu`4!BnYC2&rYN z1giL0fT=4;eEbVHp1eYivPJ6F2|LHBK~&=T=@-BE;@|r{``us=0Bw8E*HYU8;JXQC zrsKwr@9EDzhyemIjI9hZ3t7xYHZ-xFIjn;^%tbEqkjH%FH+d)PvH%6mzLWJ>ANAP) z4IoHB?7oHQTgk+5%)gNvUIr}^83R~+Dj)9H= z*MMihH_$a;?ppLT+z!tx592sr$5<_<%>=k?k8nz-=GR*zU_GOxXSGoC;*WH!FKnv4 zX{#371SG?Hb}moF7$qdjF#Z6JwKi~aCECatjmrBamu2)IF9p}9a*+_8KBgjS(?uAk zJe3$<+~#PD diff --git a/./conversation/__pycache__/conversation_state.cpython-310.pyc b/./conversation/__pycache__/conversation_state.cpython-310.pyc deleted file mode 100644 index 2bc423010c5222498c9014034f67cb2e0c16e94a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8769 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 Z8i{Ecye&#fyKw0y8yQK?WTer}{|y-oACmw8 diff --git a/./conversation/__pycache__/conversation_state.cpython-311.pyc b/./conversation/__pycache__/conversation_state.cpython-311.pyc deleted file mode 100644 index 431d07b8a5a912df494600ac77fc2a142ecd9ca8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12686 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 L`p<8IWqbQSSw9EX diff --git a/./conversation/__pycache__/conversational_agent.cpython-310.pyc b/./conversation/__pycache__/conversational_agent.cpython-310.pyc deleted file mode 100644 index b44ce22ec42f907463d61610a5df77824f0937ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5990 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 diff --git a/./conversation/__pycache__/response_generator.cpython-310.pyc b/./conversation/__pycache__/response_generator.cpython-310.pyc deleted file mode 100644 index d34185431a4a607ecb7892a847ce8c14afd09550..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7195 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 PhSu_RLRhTh8^r$r7U>t_ diff --git a/./conversation/__pycache__/response_generator.cpython-311.pyc b/./conversation/__pycache__/response_generator.cpython-311.pyc deleted file mode 100644 index 5c5d9cdeee38a2207554ae020abc6e121be3f2c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9139 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 NB6WY72SuLo{{en}WHbN( diff --git a/./conversation/conversation_state.py b/./conversation/conversation_state.py deleted file mode 100644 index 61a1768..0000000 --- a/./conversation/conversation_state.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -对话状态管理模块 -管理对话历史、上下文和当前任务状态 -""" - -import logging -from dataclasses import dataclass, field -from datetime import datetime -from enum import Enum -from typing import Dict, Any, List, Optional, Union - - -class TaskStatus(Enum): - """任务状态""" - - IDLE = "idle" # 空闲 - PROCESSING = "processing" # 处理中 - COMPLETED = "completed" # 已完成 - FAILED = "failed" # 失败 - WAITING_INPUT = "waiting_input" # 等待用户输入 - - -@dataclass -class Message: - """对话消息""" - - role: str # "user" 或 "assistant" - content: str - timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) - metadata: Dict[str, Any] = field(default_factory=dict) - - def __post_init__(self) -> None: - """Validate message after initialization""" - if self.role not in ["user", "assistant"]: - raise ValueError("role must be 'user' or 'assistant'") - if not self.content or not self.content.strip(): - raise ValueError("content cannot be empty") - - -@dataclass -class Task: - """当前任务""" - - command: str - parameters: Dict[str, Any] - status: TaskStatus = TaskStatus.IDLE - result: Optional[Any] = None - error: Optional[str] = None - started_at: Optional[str] = None - completed_at: Optional[str] = None - - def __post_init__(self) -> None: - """Validate task after initialization""" - if not self.command or not self.command.strip(): - raise ValueError("command cannot be empty") - if not isinstance(self.parameters, dict): - raise ValueError("parameters must be a dictionary") - - -class ConversationState: - """对话状态管理器""" - - def __init__(self, max_history: int = 20) -> None: - """ - 初始化对话状态 - - Args: - max_history: 保留的最大历史消息数 - """ - self.logger: logging.Logger = logging.getLogger("ConversationState") - self.max_history: int = max_history - - # 对话历史 - self.messages: List[Message] = [] - - # 当前任务 - self.current_task: Optional[Task] = None - - # 用户偏好和上下文 - self.user_preferences: Dict[str, Any] = {} - self.context: Dict[str, Any] = {} - - # 统计信息 - self.stats: Dict[str, Union[int, str]] = { - "total_messages": 0, - "total_tasks": 0, - "successful_tasks": 0, - "failed_tasks": 0, - "session_start": datetime.now().isoformat(), - } - - def add_message( - self, role: str, content: str, metadata: Optional[Dict[str, Any]] = None - ) -> Message: - """ - 添加消息到历史 - - Args: - role: 消息角色("user" 或 "assistant") - content: 消息内容 - metadata: 消息元数据 - - Returns: - Message: 添加的消息 - """ - message: Message = Message(role=role, content=content, metadata=metadata or {}) - - self.messages.append(message) - self.stats["total_messages"] += 1 - - # 保持历史长度在限制内 - if len(self.messages) > self.max_history: - self.messages.pop(0) - - self.logger.debug(f"添加消息: {role} - {content[:50]}...") - - return message - - def get_recent_messages(self, count: int = 5) -> List[Message]: - """ - 获取最近的 N 条消息 - - Args: - count: 消息数量 - - Returns: - 最近的消息列表 - """ - return self.messages[-count:] - - def get_conversation_history(self) -> List[Dict[str, str]]: - """ - 获取对话历史(用于 Claude API) - - Returns: - 对话历史列表 - """ - return [{"role": msg.role, "content": msg.content} for msg in self.messages] - - def start_task(self, command: str, parameters: Dict[str, Any]) -> Task: - """ - 开始一个新任务 - - Args: - command: 命令名称 - parameters: 命令参数 - - Returns: - Task: 创建的任务 - """ - self.current_task = Task( - command=command, - parameters=parameters, - status=TaskStatus.PROCESSING, - started_at=datetime.now().isoformat(), - ) - - self.stats["total_tasks"] += 1 - self.logger.info(f"开始任务: {command} - {parameters}") - - return self.current_task - - def complete_task(self, result: Any) -> Optional[Task]: - """ - 完成当前任务 - - Args: - result: 任务结果 - - Returns: - Task: 完成的任务 - """ - if not self.current_task: - self.logger.warning("没有正在进行的任务") - return None - - self.current_task.status = TaskStatus.COMPLETED - self.current_task.result = result - self.current_task.completed_at = datetime.now().isoformat() - self.stats["successful_tasks"] += 1 - - self.logger.info(f"任务完成: {self.current_task.command}") - - return self.current_task - - def fail_task(self, error: str) -> Optional[Task]: - """ - 标记任务失败 - - Args: - error: 错误信息 - - Returns: - Task: 失败的任务 - """ - if not self.current_task: - self.logger.warning("没有正在进行的任务") - return None - - self.current_task.status = TaskStatus.FAILED - self.current_task.error = error - self.current_task.completed_at = datetime.now().isoformat() - self.stats["failed_tasks"] += 1 - - self.logger.error(f"任务失败: {self.current_task.command} - {error}") - - return self.current_task - - def set_context(self, key: str, value: Any) -> None: - """ - 设置上下文信息 - - Args: - key: 上下文键 - value: 上下文值 - """ - self.context[key] = value - self.logger.debug(f"设置上下文: {key} = {value}") - - def get_context(self, key: str, default: Any = None) -> Any: - """ - 获取上下文信息 - - Args: - key: 上下文键 - default: 默认值 - - Returns: - 上下文值 - """ - return self.context.get(key, default) - - def set_preference(self, key: str, value: Any) -> None: - """ - 设置用户偏好 - - Args: - key: 偏好键 - value: 偏好值 - """ - self.user_preferences[key] = value - self.logger.debug(f"设置偏好: {key} = {value}") - - def get_preference(self, key: str, default: Any = None) -> Any: - """ - 获取用户偏好 - - Args: - key: 偏好键 - default: 默认值 - - Returns: - 偏好值 - """ - return self.user_preferences.get(key, default) - - def get_summary(self) -> Dict[str, Any]: - """ - 获取对话状态摘要 - - Returns: - 状态摘要字典 - """ - return { - "total_messages": len(self.messages), - "recent_messages": [ - { - "role": msg.role, - "content": msg.content[:100], - "timestamp": msg.timestamp, - } - for msg in self.get_recent_messages(3) - ], - "current_task": { - "command": self.current_task.command, - "status": self.current_task.status.value, - "started_at": self.current_task.started_at, - } - if self.current_task - else None, - "stats": self.stats, - "preferences": self.user_preferences, - } - - def clear_history(self) -> None: - """清除对话历史""" - self.messages.clear() - self.logger.info("对话历史已清除") - - def reset(self) -> None: - """重置对话状态""" - self.messages.clear() - self.current_task = None - self.context.clear() - self.logger.info("对话状态已重置") diff --git a/./conversation/conversational_agent.py b/./conversation/conversational_agent.py deleted file mode 100644 index 5759fca..0000000 --- a/./conversation/conversational_agent.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -对话式 Agent 核心模块 -融合对话能力的智能 Agent -""" - -import logging -from dataclasses import dataclass -from typing import Dict, Any, Optional, List - -from .conversation_state import ConversationState -from .intent_understanding import IntentUnderstanding, Intent -from .response_generator import ResponseGenerator -from ..agent_core import Agent, SkillResult - - -@dataclass -class ChatResponse: - """对话响应""" - - message: str # 响应消息 - suggestions: Optional[List[str]] = None # 建议的后续操作 - status: str = "success" # 状态:success, error, waiting_input - metadata: Optional[Dict[str, Any]] = None # 元数据 - - def __post_init__(self) -> None: - """Validate response after initialization""" - if not self.message or not self.message.strip(): - raise ValueError("message cannot be empty") - valid_statuses = ["success", "error", "waiting_input"] - if self.status not in valid_statuses: - raise ValueError(f"status must be one of {valid_statuses}") - if self.suggestions is not None and not isinstance(self.suggestions, list): - raise ValueError("suggestions must be a list or None") - - -class ConversationalAgent: - """对话式 Agent,融合对话能力的智能 Agent""" - - def __init__( - self, command_agent: Agent, config: Optional[Dict[str, Any]] = None - ) -> None: - """ - 初始化对话式 Agent - - Args: - command_agent: 底层的 Command Agent - config: 配置字典 - """ - self.command_agent: Agent = command_agent - self.config: Dict[str, Any] = config or {} - self.logger: logging.Logger = logging.getLogger("ConversationalAgent") - - # 初始化各个模块 - self.intent_understanding: IntentUnderstanding = IntentUnderstanding(config) - self.conversation_state: ConversationState = ConversationState() - self.response_generator: ResponseGenerator = ResponseGenerator(config) - - async def initialize(self) -> str: - """ - 初始化 Agent 并返回欢迎消息 - - Returns: - 欢迎消息 - """ - welcome_msg = await self.response_generator.generate_welcome_message() - self.conversation_state.add_message("assistant", welcome_msg) - self.logger.info("对话式 Agent 已初始化") - return welcome_msg - - async def chat(self, user_message: str) -> ChatResponse: - """ - 处理用户消息并返回响应 - - Args: - user_message: 用户的输入消息 - - Returns: - ChatResponse: 对话响应 - """ - self.logger.info(f"处理用户消息: {user_message}") - - try: - # 1. 记录用户消息 - self.conversation_state.add_message("user", user_message) - - # 2. 理解用户意图 - context: Dict[str, Any] = { - "conversation_history": self.conversation_state.get_conversation_history() - } - intent: Intent = await self.intent_understanding.understand( - user_message, context - ) - - self.logger.debug(f"识别的意图: {intent.command}") - - # 3. 如果需要澄清,返回澄清问题 - if intent.clarification_needed: - response: ChatResponse = ChatResponse( - message=intent.clarification_question or "抱歉,我没有理解您的意思。能否请您重新表述?", - status="waiting_input", - ) - self.conversation_state.add_message("assistant", response.message) - return response - - # 4. 映射意图到命令并执行 - command_result: SkillResult = await self._execute_command(intent) - - # 5. 生成响应 - if command_result.success: - response_msg: str = ( - await self.response_generator.generate_success_response( - intent.command, command_result.data, user_message - ) - ) - else: - response_msg = await self.response_generator.generate_error_response( - intent.command, command_result.error or "未知错误", user_message - ) - - # 6. 生成建议 - suggestions: List[str] = await self.response_generator.generate_suggestions( - self.conversation_state.context - ) - - # 7. 构建响应 - response = ChatResponse( - message=response_msg, - suggestions=suggestions, - status="success" if command_result.success else "error", - metadata={ - "command": intent.command, - "confidence": intent.confidence, - "reasoning": intent.raw_understanding, - }, - ) - - # 8. 记录助手响应 - self.conversation_state.add_message("assistant", response_msg) - - self.logger.info(f"响应生成完成: {response_msg[:50]}...") - - return response - - except Exception as e: - self.logger.error(f"处理消息失败: {str(e)}", exc_info=True) - - error_response: ChatResponse = ChatResponse( - message="抱歉,处理您的请求时出现了问题。请稍后重试。", status="error" - ) - - self.conversation_state.add_message("assistant", error_response.message) - - return error_response - - async def _execute_command(self, intent: Intent) -> SkillResult: - """ - 执行命令 - - Args: - intent: 识别的意图 - - Returns: - SkillResult: 命令执行结果 - """ - try: - # 开始任务 - task = self.conversation_state.start_task(intent.command, intent.parameters) - - # 执行命令 - result: SkillResult = await self.command_agent.execute_command( - intent.command, intent.parameters - ) - - # 更新任务状态 - if result.success: - self.conversation_state.complete_task(result.data) - else: - self.conversation_state.fail_task(result.error or "未知错误") - - return result - - except Exception as e: - self.logger.error(f"命令执行失败: {str(e)}") - self.conversation_state.fail_task(str(e)) - - return SkillResult(success=False, error=str(e), message="命令执行失败") - - def get_conversation_history(self) -> List[Dict[str, str]]: - """ - 获取对话历史 - - Returns: - 对话历史列表 - """ - return self.conversation_state.get_conversation_history() - - def get_state_summary(self) -> Dict[str, Any]: - """ - 获取对话状态摘要 - - Returns: - 状态摘要 - """ - return self.conversation_state.get_summary() - - def clear_history(self) -> None: - """ - 清除对话历史 - """ - self.conversation_state.clear_history() - self.logger.info("对话历史已清除") - - def reset(self) -> None: - """ - 重置对话状态 - """ - self.conversation_state.reset() - self.logger.info("对话状态已重置") diff --git a/./conversation/intent_understanding.py b/./conversation/intent_understanding.py deleted file mode 100644 index 93f8a60..0000000 --- a/./conversation/intent_understanding.py +++ /dev/null @@ -1,402 +0,0 @@ -""" -意图理解模块 -使用 Claude 理解用户的自然语言输入,提取意图和参数 -""" - -import json -import logging -import re -from dataclasses import dataclass, field -from datetime import datetime, timedelta -from typing import Dict, Any, Optional, List - -from ..dependency_manager import get_dependency_manager, graceful_import - -# Try to import anthropic with graceful degradation -dependency_manager = get_dependency_manager() -Anthropic = dependency_manager.get_class_from_module('anthropic', 'Anthropic') - - -@dataclass -class Intent: - """用户意图""" - - command: str # 对应的命令名称 - parameters: Dict[str, Any] = field(default_factory=dict) - confidence: float = 1.0 - clarification_needed: bool = False - clarification_question: Optional[str] = None - raw_understanding: str = "" # Claude 的原始理解 - - def __post_init__(self) -> None: - """Validate intent after initialization""" - if not self.command or not self.command.strip(): - raise ValueError("command cannot be empty") - if not 0.0 <= self.confidence <= 1.0: - raise ValueError("confidence must be between 0.0 and 1.0") - if not isinstance(self.parameters, dict): - raise ValueError("parameters must be a dictionary") - - -class IntentUnderstanding: - """意图理解器,使用 Claude 理解用户意图""" - - def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: - """ - 初始化意图理解器 - - Args: - config: 配置字典 - """ - self.config: Dict[str, Any] = config or {} - self.logger: logging.Logger = logging.getLogger("IntentUnderstanding") - - # Initialize Anthropic client with dependency checking - if Anthropic is not None: - try: - self.client: Optional[Anthropic] = Anthropic() - except Exception as e: - self.logger.error(f"Failed to initialize Anthropic client: {e}") - self.client = None - else: - self.client = None - self.logger.warning("Anthropic library not available - Claude-based understanding disabled") - - # 定义支持的命令和它们的关键词 - self.command_keywords: Dict[str, List[str]] = { - "organize": [ - "整理", - "组织", - "分类", - "归纳", - "整理日记", - "organize", - "arrange", - "categorize", - ], - "analyze": [ - "分析", - "总结", - "统计", - "分类", - "分析日记", - "analyze", - "summarize", - "statistics", - ], - "export": [ - "导出", - "保存", - "生成", - "输出", - "导出为", - "export", - "save", - "generate", - "output", - ], - "review": [ - "回顾", - "查看", - "查询", - "搜索", - "浏览", - "review", - "view", - "search", - "browse", - ], - } - - # 定义参数提取规则 - self.parameter_patterns: Dict[str, List[str]] = { - "date": [ - r"(\d{4}[-/]\d{1,2}[-/]\d{1,2})", # YYYY-MM-DD 或 YYYY/M/D - r"(今天|明天|昨天|前天)", # 相对日期 - r"(这周|本周|上周|下周)", # 周 - r"(这个月|本月|上个月|下个月)", # 月 - ], - "category": [ - r"(经验|教训|待办|问题|成就|改进)", - r"(experience|lesson|task|problem|achievement|improvement)", - ], - "format": [r"(PDF|Excel|Word|Markdown|JSON)", r"(pdf|xlsx|docx|md|json)"], - } - - async def understand( - self, user_message: str, context: Optional[Dict[str, Any]] = None - ) -> Intent: - """ - 理解用户的自然语言输入 - - Args: - user_message: 用户的输入消息 - context: 上下文信息(如对话历史) - - Returns: - Intent: 提取的意图 - """ - self.logger.debug(f"理解用户消息: {user_message}") - - try: - # 首先尝试本地模式匹配(快速路径) - intent = self._match_intent_locally(user_message) - if intent and intent.confidence > 0.8: - self.logger.debug(f"本地匹配成功: {intent.command}") - return intent - - # 使用 Claude 进行更深入的理解 - intent = await self._understand_with_claude(user_message, context) - - return intent - - except Exception as e: - self.logger.error(f"意图理解失败: {str(e)}") - return Intent( - command="unknown", - clarification_needed=True, - clarification_question="抱歉,我没有理解您的意思。能否请您重新表述?", - ) - - def _match_intent_locally(self, user_message: str) -> Optional[Intent]: - """ - 本地模式匹配,快速识别常见意图 - - Args: - user_message: 用户消息 - - Returns: - Intent 或 None - """ - message_lower: str = user_message.lower() - - # 逐个检查命令关键词 - for command, keywords in self.command_keywords.items(): - for keyword in keywords: - if keyword in message_lower: - # 提取参数 - parameters: Dict[str, Any] = self._extract_parameters_locally( - user_message - ) - - return Intent( - command=command, parameters=parameters, confidence=0.9 - ) - - return None - - def _extract_parameters_locally(self, user_message: str) -> Dict[str, Any]: - """ - 本地提取参数 - - Args: - user_message: 用户消息 - - Returns: - 提取的参数字典 - """ - parameters: Dict[str, Any] = {} - - # 提取日期 - for pattern in self.parameter_patterns["date"]: - match: Optional[re.Match[str]] = re.search(pattern, user_message) - if match: - date_str: str = match.group(1) - parameters["date"] = self._normalize_date(date_str) - break - - # 提取分类 - for pattern in self.parameter_patterns["category"]: - match = re.search(pattern, user_message) - if match: - parameters["category"] = match.group(1) - break - - # 提取格式 - for pattern in self.parameter_patterns["format"]: - match = re.search(pattern, user_message) - if match: - parameters["format"] = match.group(1).lower() - break - - return parameters - - def _normalize_date(self, date_str: str) -> str: - """ - 规范化日期字符串为 YYYY-MM-DD 格式 - - Args: - date_str: 日期字符串 - - Returns: - 规范化的日期字符串 - """ - today: datetime = datetime.now() - - # 处理相对日期 - if date_str == "今天": - return today.strftime("%Y-%m-%d") - elif date_str == "明天": - return (today + timedelta(days=1)).strftime("%Y-%m-%d") - elif date_str == "昨天": - return (today - timedelta(days=1)).strftime("%Y-%m-%d") - elif date_str == "前天": - return (today - timedelta(days=2)).strftime("%Y-%m-%d") - - # 处理标准日期格式 - try: - # 尝试 YYYY-MM-DD 或 YYYY/M/D 格式 - for fmt in ["%Y-%m-%d", "%Y/%m/%d", "%Y-%m-%d"]: - try: - parsed: datetime = datetime.strptime( - date_str.replace("/", "-"), fmt - ) - return parsed.strftime("%Y-%m-%d") - except ValueError: - continue - except: - pass - - return date_str - - async def _understand_with_claude( - self, user_message: str, context: Optional[Dict[str, Any]] = None - ) -> Intent: - """ - 使用 Claude 理解用户意图 - - Args: - user_message: 用户消息 - context: 上下文信息 - - Returns: - Intent: 提取的意图 - """ - # Check if Claude is available - if self.client is None: - self.logger.warning("Claude client not available, falling back to local matching") - return Intent( - command="unknown", - clarification_needed=True, - clarification_question="抱歉,AI 理解功能暂时不可用。请使用更具体的命令,如 '整理今天的日记' 或 '分析本周内容'。", - ) - - # Construct prompt - prompt = self._build_understanding_prompt(user_message, context) - - try: - # Call Claude - response = self.client.messages.create( - model="claude-3-5-sonnet-20241022", - max_tokens=500, - messages=[{"role": "user", "content": prompt}], - ) - - # Parse response - response_text = response.content[0].text - self.logger.debug(f"Claude 响应: {response_text}") - - return self._parse_claude_response(response_text, user_message) - - except Exception as e: - self.logger.error(f"Claude API call failed: {str(e)}") - # Fall back to local matching - local_intent = self._match_intent_locally(user_message) - if local_intent: - return local_intent - - return Intent( - command="unknown", - clarification_needed=True, - clarification_question="抱歉,我在理解您的意图时遇到了问题。请尝试使用更具体的命令。", - ) - - def _build_understanding_prompt( - self, user_message: str, context: Optional[Dict[str, Any]] = None - ) -> str: - """ - 构建用于 Claude 的提示 - - Args: - user_message: 用户消息 - context: 上下文信息 - - Returns: - 提示文本 - """ - available_commands: str = ", ".join(self.command_keywords.keys()) - - prompt: str = f"""你是一个 Obsidian 日记整理助手的意图识别器。 - -用户消息: "{user_message}" - -可用的命令有: {available_commands} - -请分析用户的意图,并返回一个 JSON 对象,包含以下字段: - -{{ - "command": "识别出的命令名称(必须是可用命令之一)", - "parameters": {{ - "date": "如果用户指定了日期,转换为 YYYY-MM-DD 格式;否则为 null", - "category": "如果用户指定了分类,提取分类名称;否则为 null", - "format": "如果用户指定了导出格式,提取格式;否则为 null", - "other_params": "其他相关参数" - }}, - "confidence": 0.0 到 1.0 之间的置信度, - "clarification_needed": 是否需要澄清(布尔值), - "clarification_question": "如果需要澄清,提出的问题;否则为 null", - "reasoning": "简短的推理说明" -}} - -请确保返回有效的 JSON 格式。""" - - if context and "conversation_history" in context: - prompt += f"\n\n对话历史(最近的消息):\n" - for msg in context["conversation_history"][-3:]: - prompt += f"- {msg['role']}: {msg['content']}\n" - - return prompt - - def _parse_claude_response(self, response_text: str, user_message: str) -> Intent: - """ - 解析 Claude 的响应 - - Args: - response_text: Claude 的响应文本 - user_message: 原始用户消息 - - Returns: - Intent: 提取的意图 - """ - try: - # 尝试从响应中提取 JSON - json_match: Optional[re.Match[str]] = re.search( - r"\{.*\}", response_text, re.DOTALL - ) - if not json_match: - raise ValueError("未找到 JSON 响应") - - json_str: str = json_match.group(0) - data: Dict[str, Any] = json.loads(json_str) - - # 构建 Intent 对象 - intent: Intent = Intent( - command=data.get("command", "unknown"), - parameters={ - k: v for k, v in data.get("parameters", {}).items() if v is not None - }, - confidence=data.get("confidence", 0.7), - clarification_needed=data.get("clarification_needed", False), - clarification_question=data.get("clarification_question"), - raw_understanding=data.get("reasoning", ""), - ) - - return intent - - except Exception as e: - self.logger.error(f"解析 Claude 响应失败: {str(e)}") - return Intent( - command="unknown", - clarification_needed=True, - clarification_question="抱歉,我在处理您的请求时遇到了问题。能否请您重新表述?", - ) diff --git a/./conversation/response_generator.py b/./conversation/response_generator.py deleted file mode 100644 index 5f8be83..0000000 --- a/./conversation/response_generator.py +++ /dev/null @@ -1,234 +0,0 @@ -""" -响应生成模块 -使用 Claude 生成自然语言响应 -""" - -import logging -from typing import Dict, Any, Optional, List - -from ..dependency_manager import get_dependency_manager - -# Try to import anthropic with graceful degradation -dependency_manager = get_dependency_manager() -Anthropic = dependency_manager.get_class_from_module('anthropic', 'Anthropic') - - -class ResponseGenerator: - """响应生成器,使用 Claude 生成自然语言响应""" - - def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: - """ - 初始化响应生成器 - - Args: - config: 配置字典 - """ - self.config: Dict[str, Any] = config or {} - self.logger: logging.Logger = logging.getLogger("ResponseGenerator") - - # Initialize Anthropic client with dependency checking - if Anthropic is not None: - try: - self.client: Optional[Anthropic] = Anthropic() - except Exception as e: - self.logger.error(f"Failed to initialize Anthropic client: {e}") - self.client = None - else: - self.client = None - self.logger.warning("Anthropic library not available - AI response generation disabled") - - async def generate_success_response( - self, command: str, result: Any, user_message: str - ) -> str: - """ - 生成成功响应 - - Args: - command: 执行的命令 - result: 命令执行结果 - user_message: 原始用户消息 - - Returns: - 生成的响应文本 - """ - prompt: str = self._build_success_prompt(command, result, user_message) - return await self._generate_response(prompt) - - async def generate_error_response( - self, command: str, error: str, user_message: str - ) -> str: - """ - 生成错误响应 - - Args: - command: 执行的命令 - error: 错误信息 - user_message: 原始用户消息 - - Returns: - 生成的响应文本 - """ - prompt: str = self._build_error_prompt(command, error, user_message) - return await self._generate_response(prompt) - - async def generate_clarification_response(self, question: str) -> str: - """ - 生成澄清问题的响应 - - Args: - question: 澄清问题 - - Returns: - 生成的响应文本 - """ - return question - - async def generate_welcome_message(self) -> str: - """ - 生成欢迎消息 - - Returns: - 欢迎消息 - """ - return "👋 欢迎使用 Obsidian 日记整理助手!我可以帮您整理日记、分析内容、导出总结等。请告诉我您想要做什么?" - - async def generate_suggestions( - self, context: Optional[Dict[str, Any]] = None - ) -> List[str]: - """ - 生成智能建议 - - Args: - context: 上下文信息 - - Returns: - 建议列表 - """ - suggestions: List[str] = ["整理今天的日记", "分析本周的主题", "导出月度总结", "查看最近的经验"] - - # 可以根据上下文生成更个性化的建议 - if context: - # 例如,如果是周五,建议生成周总结 - from datetime import datetime - - if datetime.now().weekday() == 4: # 周五 - suggestions.insert(0, "生成本周总结") - - return suggestions - - def _build_success_prompt( - self, command: str, result: Any, user_message: str - ) -> str: - """ - 构建成功响应的提示 - - Args: - command: 执行的命令 - result: 命令执行结果 - user_message: 原始用户消息 - - Returns: - 提示文本 - """ - result_str: str = self._format_result(result) - - prompt: str = f"""你是一个友好的 Obsidian 日记整理助手。 - -用户问: "{user_message}" - -你已经成功执行了 "{command}" 命令。 - -执行结果: -{result_str} - -请用友好、自然的语言总结结果。保持回复简洁(1-3 句话)。 -如果有重要的数据或统计信息,请突出显示。 - -示例回复: -- "✓ 已成功整理您今天的日记。提取了 5 条经验、3 条待办事项和 2 个问题。" -- "✓ 分析完成!本周的主题主要集中在项目管理和技术学习两个方面。" -""" - - return prompt - - def _build_error_prompt(self, command: str, error: str, user_message: str) -> str: - """ - 构建错误响应的提示 - - Args: - command: 执行的命令 - error: 错误信息 - user_message: 原始用户消息 - - Returns: - 提示文本 - """ - prompt: str = f"""你是一个友好的 Obsidian 日记整理助手。 - -用户问: "{user_message}" - -执行 "{command}" 命令时出现了错误: -{error} - -请用友好、有帮助的语言解释错误,并建议可能的解决方案。保持回复简洁(1-2 句话)。 - -示例回复: -- "✗ 抱歉,找不到该日期的日记。请检查日期格式是否正确(YYYY-MM-DD)。" -- "✗ 执行过程中出现了问题。请稍后重试,或检查您的配置设置。" -""" - - return prompt - - def _format_result(self, result: Any) -> str: - """ - 格式化结果 - - Args: - result: 结果对象 - - Returns: - 格式化的结果字符串 - """ - if isinstance(result, dict): - lines: List[str] = [] - for key, value in result.items(): - if isinstance(value, (list, dict)): - lines.append(f"- {key}: {len(value)} 项") - else: - lines.append(f"- {key}: {value}") - return "\n".join(lines) - elif isinstance(result, list): - return "\n".join([f"- {item}" for item in result]) - else: - return str(result) - - async def _generate_response(self, prompt: str) -> str: - """ - 使用 Claude 生成响应 - - Args: - prompt: 提示文本 - - Returns: - 生成的响应 - """ - # Check if Claude is available - if self.client is None: - self.logger.warning("Claude client not available, using fallback response") - return "✓ 操作已完成。(注意:AI 响应生成功能暂时不可用)" - - try: - response = self.client.messages.create( - model="claude-3-5-sonnet-20241022", - max_tokens=300, - messages=[{"role": "user", "content": prompt}], - ) - - response_text: str = response.content[0].text.strip() - self.logger.debug(f"生成响应: {response_text[:100]}...") - - return response_text - - except Exception as e: - self.logger.error(f"生成响应失败: {str(e)}") - return "✓ 操作已完成。(注意:AI 响应生成遇到问题,请检查网络连接和 API 配置)" diff --git a/./date_validation.py b/./date_validation.py deleted file mode 100644 index bf397e8..0000000 --- a/./date_validation.py +++ /dev/null @@ -1,675 +0,0 @@ -""" -Comprehensive date validation and parsing utilities -Provides robust date format validation and constraint checking -""" - -import logging -import re -from datetime import datetime, date, timedelta -from typing import Optional, List, Tuple, Union, Dict, Any - -try: - from .error_handling import ValidationError -except ImportError: - from error_handling import ValidationError - - -class DateValidator: - """Comprehensive date validation with multiple format support""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - - # Supported date formats with their regex patterns and strptime formats - self.date_formats = { - 'iso_date': { - 'pattern': r'^\d{4}-\d{2}-\d{2}$', - 'strptime': '%Y-%m-%d', - 'description': 'ISO date format (YYYY-MM-DD)' - }, - 'iso_datetime': { - 'pattern': r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$', - 'strptime': '%Y-%m-%dT%H:%M:%S', - 'description': 'ISO datetime format (YYYY-MM-DDTHH:MM:SS)' - }, - 'iso_datetime_ms': { - 'pattern': r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}$', - 'strptime': '%Y-%m-%dT%H:%M:%S.%f', - 'description': 'ISO datetime with milliseconds' - }, - 'iso_datetime_tz': { - 'pattern': r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$', - 'strptime': '%Y-%m-%dT%H:%M:%S%z', - 'description': 'ISO datetime with timezone' - }, - 'us_date': { - 'pattern': r'^\d{1,2}/\d{1,2}/\d{4}$', - 'strptime': '%m/%d/%Y', - 'description': 'US date format (MM/DD/YYYY)' - }, - 'eu_date': { - 'pattern': r'^\d{1,2}/\d{1,2}/\d{4}$', - 'strptime': '%d/%m/%Y', - 'description': 'European date format (DD/MM/YYYY)' - }, - 'dot_date': { - 'pattern': r'^\d{1,2}\.\d{1,2}\.\d{4}$', - 'strptime': '%d.%m.%Y', - 'description': 'Dot-separated date (DD.MM.YYYY)' - }, - 'compact_date': { - 'pattern': r'^\d{8}$', - 'strptime': '%Y%m%d', - 'description': 'Compact date format (YYYYMMDD)' - } - } - - # Default constraints - self.default_constraints = { - 'min_year': 1900, - 'max_year': 2100, - 'allow_future': True, - 'max_future_days': 365 * 10, # 10 years - 'allow_past': True, - 'max_past_days': 365 * 50, # 50 years - } - - def validate_date_string( - self, - date_string: str, - field_name: str = "date", - allowed_formats: Optional[List[str]] = None, - constraints: Optional[Dict[str, Any]] = None, - auto_detect_format: bool = True - ) -> Tuple[datetime, str]: - """ - Validate a date string with comprehensive format and constraint checking - - Args: - date_string: Date string to validate - field_name: Name of the field for error messages - allowed_formats: List of allowed format names (None = all formats) - constraints: Date constraints (None = use defaults) - auto_detect_format: Whether to auto-detect format - - Returns: - Tuple of (parsed_datetime, detected_format) - - Raises: - ValidationError: If validation fails - """ - if not date_string or not isinstance(date_string, str): - raise ValidationError( - message=f"{field_name} must be a non-empty string", - field_name=field_name, - validation_rule="non_empty_string" - ) - - date_string = date_string.strip() - if not date_string: - raise ValidationError( - message=f"{field_name} cannot be empty", - field_name=field_name, - validation_rule="non_empty" - ) - - # Determine which formats to try - formats_to_try = allowed_formats or list(self.date_formats.keys()) - - parsed_date = None - detected_format = None - parsing_errors = [] - - # Try each format - for format_name in formats_to_try: - if format_name not in self.date_formats: - self.logger.warning(f"Unknown date format: {format_name}") - continue - - format_info = self.date_formats[format_name] - - # Check regex pattern first (faster than strptime) - if not re.match(format_info['pattern'], date_string): - continue - - # Try to parse with strptime - try: - parsed_date = datetime.strptime(date_string, format_info['strptime']) - detected_format = format_name - break - except ValueError as e: - parsing_errors.append(f"{format_name}: {str(e)}") - continue - - # If no format worked, provide helpful error - if parsed_date is None: - if auto_detect_format: - error_msg = f"{field_name} format not recognized. Tried formats: {', '.join(formats_to_try)}" - if parsing_errors: - error_msg += f". Errors: {'; '.join(parsing_errors[:3])}" # Limit error details - else: - format_descriptions = [f'{fmt} ({self.date_formats[fmt]["description"]})' for fmt in formats_to_try if fmt in self.date_formats] - error_msg = f"{field_name} must match one of these formats: {', '.join(format_descriptions)}" - - raise ValidationError( - message=error_msg, - field_name=field_name, - validation_rule="date_format" - ) - - # Apply constraints - self._validate_date_constraints(parsed_date, field_name, constraints or self.default_constraints) - - return parsed_date, detected_format - - def validate_date_range( - self, - start_date: Union[str, datetime, date], - end_date: Union[str, datetime, date], - field_name_start: str = "start_date", - field_name_end: str = "end_date", - allow_same_date: bool = True, - max_range_days: Optional[int] = None - ) -> Tuple[datetime, datetime]: - """ - Validate a date range with logical constraints - - Args: - start_date: Start date (string or datetime) - end_date: End date (string or datetime) - field_name_start: Name of start date field - field_name_end: Name of end date field - allow_same_date: Whether start and end can be the same - max_range_days: Maximum allowed range in days - - Returns: - Tuple of (start_datetime, end_datetime) - - Raises: - ValidationError: If validation fails - """ - # Parse start date - if isinstance(start_date, str): - start_dt, _ = self.validate_date_string(start_date, field_name_start) - elif isinstance(start_date, datetime): - start_dt = start_date - elif isinstance(start_date, date): - start_dt = datetime.combine(start_date, datetime.min.time()) - else: - raise ValidationError( - message=f"{field_name_start} must be a string, date, or datetime", - field_name=field_name_start, - validation_rule="date_type" - ) - - # Parse end date - if isinstance(end_date, str): - end_dt, _ = self.validate_date_string(end_date, field_name_end) - elif isinstance(end_date, datetime): - end_dt = end_date - elif isinstance(end_date, date): - end_dt = datetime.combine(end_date, datetime.min.time()) - else: - raise ValidationError( - message=f"{field_name_end} must be a string, date, or datetime", - field_name=field_name_end, - validation_rule="date_type" - ) - - # Validate range logic - if start_dt > end_dt: - raise ValidationError( - message=f"{field_name_start} cannot be after {field_name_end}", - field_name=field_name_start, - validation_rule="date_range_order" - ) - - if not allow_same_date and start_dt.date() == end_dt.date(): - raise ValidationError( - message=f"{field_name_start} and {field_name_end} cannot be the same date", - field_name=field_name_start, - validation_rule="date_range_same" - ) - - # Check maximum range - if max_range_days is not None: - range_days = (end_dt - start_dt).days - if range_days > max_range_days: - raise ValidationError( - message=f"Date range cannot exceed {max_range_days} days (current: {range_days} days)", - field_name=field_name_start, - validation_rule="date_range_too_large" - ) - - return start_dt, end_dt - - def validate_journal_date( - self, - date_string: str, - field_name: str = "journal_date" - ) -> datetime: - """ - Validate a date for journal entries with specific constraints - - Args: - date_string: Date string to validate - field_name: Name of the field for error messages - - Returns: - Parsed datetime - - Raises: - ValidationError: If validation fails - """ - # Journal dates should typically be in ISO format and not too far in the future - journal_constraints = { - 'min_year': 2000, # Journals unlikely before 2000 - 'max_year': datetime.now().year + 1, # Allow up to next year - 'allow_future': True, - 'max_future_days': 30, # Allow up to 30 days in future - 'allow_past': True, - 'max_past_days': 365 * 20, # Allow up to 20 years in past - } - - # Prefer ISO date format for journals - preferred_formats = ['iso_date', 'compact_date', 'us_date', 'eu_date'] - - parsed_date, detected_format = self.validate_date_string( - date_string, - field_name, - allowed_formats=preferred_formats, - constraints=journal_constraints - ) - - # Additional journal-specific validations - today = datetime.now().date() - date_only = parsed_date.date() - - # Warn about future dates (but don't reject) - if date_only > today: - days_future = (date_only - today).days - if days_future > 7: # More than a week in future - self.logger.warning(f"Journal date is {days_future} days in the future: {date_string}") - - # Check for weekend dates (informational) - if date_only.weekday() >= 5: # Saturday = 5, Sunday = 6 - self.logger.debug(f"Journal date is on weekend: {date_string}") - - return parsed_date - - def validate_obsidian_date_format( - self, - date_string: str, - obsidian_format: str = "YYYY-MM-DD", - field_name: str = "date" - ) -> datetime: - """ - Validate date against Obsidian's date format configuration - - Args: - date_string: Date string to validate - obsidian_format: Obsidian date format (e.g., "YYYY-MM-DD", "DD-MM-YYYY") - field_name: Name of the field for error messages - - Returns: - Parsed datetime - - Raises: - ValidationError: If validation fails - """ - # Convert Obsidian format to Python strptime format - python_format = self._obsidian_to_python_format(obsidian_format) - - if not date_string or not isinstance(date_string, str): - raise ValidationError( - message=f"{field_name} must be a non-empty string", - field_name=field_name, - validation_rule="non_empty_string" - ) - - date_string = date_string.strip() - - try: - parsed_date = datetime.strptime(date_string, python_format) - except ValueError as e: - raise ValidationError( - message=f"{field_name} must match Obsidian format '{obsidian_format}': {str(e)}", - field_name=field_name, - validation_rule="obsidian_date_format" - ) - - # Apply basic constraints - self._validate_date_constraints(parsed_date, field_name, self.default_constraints) - - return parsed_date - - def get_supported_formats(self) -> Dict[str, str]: - """ - Get list of supported date formats - - Returns: - Dictionary of format_name -> description - """ - return {name: info['description'] for name, info in self.date_formats.items()} - - def suggest_date_format(self, date_string: str) -> List[Tuple[str, str]]: - """ - Suggest possible date formats for a given string - - Args: - date_string: Date string to analyze - - Returns: - List of (format_name, description) tuples for matching patterns - """ - suggestions = [] - - for format_name, format_info in self.date_formats.items(): - if re.match(format_info['pattern'], date_string.strip()): - suggestions.append((format_name, format_info['description'])) - - return suggestions - - def _validate_date_constraints( - self, - date_obj: datetime, - field_name: str, - constraints: Dict[str, Any] - ) -> None: - """Validate date against constraints""" - # Year constraints - if date_obj.year < constraints.get('min_year', 1900): - raise ValidationError( - message=f"{field_name} year cannot be before {constraints['min_year']}", - field_name=field_name, - validation_rule="min_year" - ) - - if date_obj.year > constraints.get('max_year', 2100): - raise ValidationError( - message=f"{field_name} year cannot be after {constraints['max_year']}", - field_name=field_name, - validation_rule="max_year" - ) - - # Future date constraints - today = datetime.now() - if date_obj > today: - if not constraints.get('allow_future', True): - raise ValidationError( - message=f"{field_name} cannot be in the future", - field_name=field_name, - validation_rule="no_future_dates" - ) - - days_future = (date_obj - today).days - max_future = constraints.get('max_future_days', 365 * 10) - if days_future > max_future: - raise ValidationError( - message=f"{field_name} cannot be more than {max_future} days in the future", - field_name=field_name, - validation_rule="max_future_days" - ) - - # Past date constraints - if date_obj < today: - if not constraints.get('allow_past', True): - raise ValidationError( - message=f"{field_name} cannot be in the past", - field_name=field_name, - validation_rule="no_past_dates" - ) - - days_past = (today - date_obj).days - max_past = constraints.get('max_past_days', 365 * 50) - if days_past > max_past: - raise ValidationError( - message=f"{field_name} cannot be more than {max_past} days in the past", - field_name=field_name, - validation_rule="max_past_days" - ) - - def _obsidian_to_python_format(self, obsidian_format: str) -> str: - """Convert Obsidian date format to Python strptime format""" - # Common Obsidian format mappings - mappings = { - 'YYYY': '%Y', # 4-digit year - 'YY': '%y', # 2-digit year - 'MM': '%m', # Month with zero padding - 'M': '%m', # Month without zero padding (Python doesn't distinguish) - 'DD': '%d', # Day with zero padding - 'D': '%d', # Day without zero padding (Python doesn't distinguish) - 'HH': '%H', # Hour (24-hour) - 'hh': '%I', # Hour (12-hour) - 'mm': '%M', # Minute - 'ss': '%S', # Second - 'A': '%p', # AM/PM - } - - python_format = obsidian_format - - # Replace in order of length (longest first to avoid partial replacements) - for obsidian_token in sorted(mappings.keys(), key=len, reverse=True): - python_format = python_format.replace(obsidian_token, mappings[obsidian_token]) - - return python_format - - -class DateRangeValidator: - """Specialized validator for date ranges and periods""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - self.date_validator = DateValidator() - - def validate_journal_date_range( - self, - start_date: Optional[str] = None, - end_date: Optional[str] = None, - period: Optional[str] = None - ) -> Tuple[datetime, datetime]: - """ - Validate a date range for journal operations - - Args: - start_date: Start date string (optional) - end_date: End date string (optional) - period: Period specification (e.g., "week", "month", "year") - - Returns: - Tuple of (start_datetime, end_datetime) - - Raises: - ValidationError: If validation fails - """ - today = datetime.now() - - # Handle period-based ranges - if period: - return self._get_period_range(period, today) - - # Handle explicit date ranges - if start_date and end_date: - return self.date_validator.validate_date_range( - start_date, end_date, - max_range_days=365 * 2 # Max 2 years for journal ranges - ) - - # Handle single date (default to that day) - if start_date: - start_dt = self.date_validator.validate_journal_date(start_date) - end_dt = start_dt.replace(hour=23, minute=59, second=59) - return start_dt, end_dt - - if end_date: - end_dt = self.date_validator.validate_journal_date(end_date) - start_dt = end_dt.replace(hour=0, minute=0, second=0) - return start_dt, end_dt - - # Default to today - start_dt = today.replace(hour=0, minute=0, second=0, microsecond=0) - end_dt = today.replace(hour=23, minute=59, second=59, microsecond=999999) - return start_dt, end_dt - - def _get_period_range(self, period: str, reference_date: datetime) -> Tuple[datetime, datetime]: - """Get date range for a period specification""" - period = period.lower().strip() - - if period in ['today', 'day']: - start = reference_date.replace(hour=0, minute=0, second=0, microsecond=0) - end = reference_date.replace(hour=23, minute=59, second=59, microsecond=999999) - - elif period in ['yesterday']: - yesterday = reference_date - timedelta(days=1) - start = yesterday.replace(hour=0, minute=0, second=0, microsecond=0) - end = yesterday.replace(hour=23, minute=59, second=59, microsecond=999999) - - elif period in ['week', 'this_week']: - # Start of week (Monday) - days_since_monday = reference_date.weekday() - start = (reference_date - timedelta(days=days_since_monday)).replace( - hour=0, minute=0, second=0, microsecond=0 - ) - end = (start + timedelta(days=6)).replace( - hour=23, minute=59, second=59, microsecond=999999 - ) - - elif period in ['last_week']: - # Previous week - days_since_monday = reference_date.weekday() - this_week_start = reference_date - timedelta(days=days_since_monday) - start = (this_week_start - timedelta(days=7)).replace( - hour=0, minute=0, second=0, microsecond=0 - ) - end = (start + timedelta(days=6)).replace( - hour=23, minute=59, second=59, microsecond=999999 - ) - - elif period in ['month', 'this_month']: - # Start of month - start = reference_date.replace(day=1, hour=0, minute=0, second=0, microsecond=0) - # End of month - if reference_date.month == 12: - next_month = reference_date.replace(year=reference_date.year + 1, month=1, day=1) - else: - next_month = reference_date.replace(month=reference_date.month + 1, day=1) - end = (next_month - timedelta(days=1)).replace( - hour=23, minute=59, second=59, microsecond=999999 - ) - - elif period in ['last_month']: - # Previous month - if reference_date.month == 1: - last_month = reference_date.replace(year=reference_date.year - 1, month=12, day=1) - else: - last_month = reference_date.replace(month=reference_date.month - 1, day=1) - - start = last_month.replace(hour=0, minute=0, second=0, microsecond=0) - - # End of last month - this_month_start = reference_date.replace(day=1) - end = (this_month_start - timedelta(days=1)).replace( - hour=23, minute=59, second=59, microsecond=999999 - ) - - elif period in ['year', 'this_year']: - start = reference_date.replace( - month=1, day=1, hour=0, minute=0, second=0, microsecond=0 - ) - end = reference_date.replace( - month=12, day=31, hour=23, minute=59, second=59, microsecond=999999 - ) - - elif period in ['last_year']: - last_year = reference_date.year - 1 - start = reference_date.replace( - year=last_year, month=1, day=1, hour=0, minute=0, second=0, microsecond=0 - ) - end = reference_date.replace( - year=last_year, month=12, day=31, hour=23, minute=59, second=59, microsecond=999999 - ) - - else: - # Try to parse as number of days - try: - if period.endswith('d') or period.endswith('days'): - days = int(period.rstrip('days').rstrip('d')) - start = (reference_date - timedelta(days=days)).replace( - hour=0, minute=0, second=0, microsecond=0 - ) - end = reference_date.replace(hour=23, minute=59, second=59, microsecond=999999) - else: - raise ValueError("Invalid period format") - except ValueError: - raise ValidationError( - message=f"Unknown period specification: {period}. " - f"Supported: today, yesterday, week, month, year, last_week, last_month, last_year, or Nd (N days)", - field_name="period", - validation_rule="unknown_period" - ) - - return start, end - - -# Global validator instances -date_validator = DateValidator() -date_range_validator = DateRangeValidator() - - -def validate_date_input( - date_input: Union[str, datetime, date, None], - field_name: str = "date", - required: bool = True, - format_hint: Optional[str] = None -) -> Optional[datetime]: - """ - Convenience function for validating date inputs - - Args: - date_input: Date input to validate - field_name: Name of the field for error messages - required: Whether the date is required - format_hint: Hint about expected format - - Returns: - Validated datetime or None if not required and empty - - Raises: - ValidationError: If validation fails - """ - if date_input is None or (isinstance(date_input, str) and not date_input.strip()): - if required: - raise ValidationError( - message=f"{field_name} is required", - field_name=field_name, - validation_rule="required" - ) - return None - - if isinstance(date_input, datetime): - return date_input - elif isinstance(date_input, date): - return datetime.combine(date_input, datetime.min.time()) - elif isinstance(date_input, str): - # Use format hint if provided - allowed_formats = None - if format_hint: - if format_hint in date_validator.date_formats: - allowed_formats = [format_hint] - else: - # Try to match format hint to known formats - for fmt_name, fmt_info in date_validator.date_formats.items(): - if format_hint.lower() in fmt_info['description'].lower(): - allowed_formats = [fmt_name] - break - - parsed_date, _ = date_validator.validate_date_string( - date_input, field_name, allowed_formats=allowed_formats - ) - return parsed_date - else: - raise ValidationError( - message=f"{field_name} must be a string, date, or datetime", - field_name=field_name, - validation_rule="date_type" - ) \ No newline at end of file diff --git a/./dependency_manager.py b/./dependency_manager.py deleted file mode 100644 index 26b1618..0000000 --- a/./dependency_manager.py +++ /dev/null @@ -1,341 +0,0 @@ -""" -Dependency Management Module -Handles optional dependencies with graceful degradation and helpful error messages -""" - -import logging -import sys -from dataclasses import dataclass -from pathlib import Path -from typing import Dict, Any, Optional, List, Tuple, Callable - - -@dataclass -class DependencyInfo: - """Information about a dependency""" - name: str - import_name: str - install_command: str - description: str - required_for: List[str] - minimum_version: Optional[str] = None - alternative_packages: Optional[List[str]] = None - setup_instructions: Optional[str] = None - - -class DependencyManager: - """Manages optional dependencies with graceful degradation""" - - def __init__(self): - self.logger = logging.getLogger("DependencyManager") - self._dependency_cache: Dict[str, Any] = {} - self._availability_cache: Dict[str, bool] = {} - - # Define known dependencies - self.dependencies = { - 'anthropic': DependencyInfo( - name='anthropic', - import_name='anthropic', - install_command='pip install anthropic>=0.25.0', - description='Anthropic Claude API client for AI-powered content analysis', - required_for=['Claude AI analysis', 'Natural language understanding', 'Content transformation'], - minimum_version='0.25.0', - setup_instructions=""" -Claude API Setup: -1. Install: pip install anthropic>=0.25.0 -2. Get API key from: https://console.anthropic.com/ -3. Set environment variable: export ANTHROPIC_API_KEY="sk-ant-your-key-here" -4. Or add to config.yaml: claude.api_key: "${ANTHROPIC_API_KEY}" - """ - ), - 'aiohttp': DependencyInfo( - name='aiohttp', - import_name='aiohttp', - install_command='pip install aiohttp>=3.9.0', - description='Async HTTP client for Obsidian REST API integration', - required_for=['Obsidian API communication', 'Reading/writing notes', 'File operations'], - minimum_version='3.9.0', - setup_instructions=""" -HTTP Client Setup: -1. Install: pip install aiohttp>=3.9.0 -2. Used for communicating with Obsidian Local REST API -3. No additional configuration required - """ - ), - 'yaml': DependencyInfo( - name='PyYAML', - import_name='yaml', - install_command='pip install pyyaml>=6.0', - description='YAML parser for configuration files', - required_for=['Configuration file parsing', 'Settings management'], - minimum_version='6.0', - alternative_packages=['ruamel.yaml'], - setup_instructions=""" -YAML Parser Setup: -1. Install: pip install pyyaml>=6.0 -2. Alternative: pip install ruamel.yaml -3. Used for parsing config.yaml files - """ - ), - 'pydantic': DependencyInfo( - name='pydantic', - import_name='pydantic', - install_command='pip install pydantic>=2.0.0', - description='Data validation and settings management', - required_for=['Configuration validation', 'Data model validation', 'Type checking'], - minimum_version='2.0.0', - setup_instructions=""" -Data Validation Setup: -1. Install: pip install pydantic>=2.0.0 -2. Used for validating configuration files and data models -3. Provides enhanced error messages and type checking - """ - ) - } - - def is_available(self, dependency_name: str) -> bool: - """Check if a dependency is available""" - if dependency_name in self._availability_cache: - return self._availability_cache[dependency_name] - - if dependency_name not in self.dependencies: - self.logger.warning(f"Unknown dependency: {dependency_name}") - return False - - dep_info = self.dependencies[dependency_name] - - try: - # Try to import the module - __import__(dep_info.import_name) - self._availability_cache[dependency_name] = True - return True - except ImportError: - self._availability_cache[dependency_name] = False - return False - - def get_module(self, dependency_name: str, raise_on_missing: bool = False) -> Optional[Any]: - """Get a module if available, with optional error raising""" - if dependency_name in self._dependency_cache: - return self._dependency_cache[dependency_name] - - if not self.is_available(dependency_name): - if raise_on_missing: - raise ImportError(self._get_missing_dependency_message(dependency_name)) - return None - - dep_info = self.dependencies[dependency_name] - try: - module = __import__(dep_info.import_name) - self._dependency_cache[dependency_name] = module - return module - except ImportError as e: - if raise_on_missing: - raise ImportError(self._get_missing_dependency_message(dependency_name)) from e - return None - - def get_class_from_module(self, dependency_name: str, class_name: str, raise_on_missing: bool = False) -> Optional[Any]: - """Get a specific class from a module""" - module = self.get_module(dependency_name, raise_on_missing=False) - if module is None: - if raise_on_missing: - raise ImportError(self._get_missing_dependency_message(dependency_name)) - return None - - try: - return getattr(module, class_name) - except AttributeError as e: - if raise_on_missing: - raise ImportError(f"Class {class_name} not found in {dependency_name}") from e - return None - - def require_dependency(self, dependency_name: str) -> Any: - """Require a dependency, raising detailed error if not available""" - module = self.get_module(dependency_name, raise_on_missing=True) - return module - - def check_all_dependencies(self) -> Dict[str, Dict[str, Any]]: - """Check status of all known dependencies""" - results = {} - - for dep_name, dep_info in self.dependencies.items(): - is_avail = self.is_available(dep_name) - results[dep_name] = { - 'available': is_avail, - 'name': dep_info.name, - 'description': dep_info.description, - 'required_for': dep_info.required_for, - 'install_command': dep_info.install_command, - 'setup_instructions': dep_info.setup_instructions - } - - if is_avail: - # Try to get version info - try: - module = self.get_module(dep_name) - if hasattr(module, '__version__'): - results[dep_name]['version'] = module.__version__ - elif hasattr(module, 'version'): - results[dep_name]['version'] = module.version - except: - pass - - return results - - def get_missing_dependencies(self) -> List[str]: - """Get list of missing dependencies""" - missing = [] - for dep_name in self.dependencies: - if not self.is_available(dep_name): - missing.append(dep_name) - return missing - - def get_installation_instructions(self, missing_only: bool = True) -> str: - """Get installation instructions for dependencies""" - deps_to_show = self.get_missing_dependencies() if missing_only else list(self.dependencies.keys()) - - if not deps_to_show: - return "✓ All dependencies are available!" - - instructions = [] - instructions.append("Missing Dependencies Installation Guide:") - instructions.append("=" * 50) - - for dep_name in deps_to_show: - dep_info = self.dependencies[dep_name] - instructions.append(f"\n📦 {dep_info.name}") - instructions.append(f" Description: {dep_info.description}") - instructions.append(f" Required for: {', '.join(dep_info.required_for)}") - instructions.append(f" Install: {dep_info.install_command}") - - if dep_info.alternative_packages: - instructions.append(f" Alternatives: {', '.join(dep_info.alternative_packages)}") - - if dep_info.setup_instructions: - instructions.append(f" Setup:{dep_info.setup_instructions}") - - instructions.append("\n" + "=" * 50) - instructions.append("Quick install all missing dependencies:") - install_commands = [self.dependencies[dep].install_command for dep in deps_to_show] - instructions.append(" && ".join(install_commands)) - - return "\n".join(instructions) - - def _get_missing_dependency_message(self, dependency_name: str) -> str: - """Get detailed error message for missing dependency""" - if dependency_name not in self.dependencies: - return f"Unknown dependency: {dependency_name}" - - dep_info = self.dependencies[dependency_name] - - message_parts = [ - f"Missing required dependency: {dep_info.name}", - f"Description: {dep_info.description}", - f"Required for: {', '.join(dep_info.required_for)}", - "", - f"To install: {dep_info.install_command}", - ] - - if dep_info.alternative_packages: - message_parts.append(f"Alternatives: {', '.join(dep_info.alternative_packages)}") - - if dep_info.setup_instructions: - message_parts.append("") - message_parts.append("Setup Instructions:") - message_parts.append(dep_info.setup_instructions) - - return "\n".join(message_parts) - - def create_graceful_import_wrapper(self, dependency_name: str, fallback_message: Optional[str] = None): - """Create a wrapper that provides graceful degradation for missing dependencies""" - def wrapper(func: Callable) -> Callable: - def inner(*args, **kwargs): - if not self.is_available(dependency_name): - error_msg = fallback_message or f"Feature unavailable: {dependency_name} is not installed" - detailed_msg = self._get_missing_dependency_message(dependency_name) - - # Log the detailed message - self.logger.error(f"Dependency missing: {detailed_msg}") - - # Return a user-friendly error - try: - from agent_core import SkillResult - except ImportError: - # Fallback if agent_core is not available - class SkillResult: - def __init__(self, success, error=None, message=""): - self.success = success - self.error = error - self.message = message - - return SkillResult( - success=False, - error=error_msg, - message=f"Please install {dependency_name} to use this feature" - ) - - return func(*args, **kwargs) - return inner - return wrapper - - def get_dependency_status_report(self) -> str: - """Get a comprehensive dependency status report""" - all_deps = self.check_all_dependencies() - - available = [name for name, info in all_deps.items() if info['available']] - missing = [name for name, info in all_deps.items() if not info['available']] - - report = [] - report.append("Dependency Status Report") - report.append("=" * 30) - - if available: - report.append(f"\n✓ Available ({len(available)}):") - for dep_name in available: - info = all_deps[dep_name] - version = info.get('version', 'unknown version') - report.append(f" • {info['name']} ({version})") - - if missing: - report.append(f"\n✗ Missing ({len(missing)}):") - for dep_name in missing: - info = all_deps[dep_name] - report.append(f" • {info['name']} - {info['description']}") - report.append(f" Install: {info['install_command']}") - - if missing: - report.append(f"\nTo install all missing dependencies:") - install_commands = [all_deps[dep]['install_command'] for dep in missing] - report.append(" && ".join(install_commands)) - else: - report.append(f"\n🎉 All dependencies are available!") - - return "\n".join(report) - - -# Global dependency manager instance -dependency_manager = DependencyManager() - - -def get_dependency_manager() -> DependencyManager: - """Get the global dependency manager instance""" - return dependency_manager - - -def check_dependency(dependency_name: str) -> bool: - """Quick check if a dependency is available""" - return dependency_manager.is_available(dependency_name) - - -def require_dependency(dependency_name: str) -> Any: - """Require a dependency, raising detailed error if not available""" - return dependency_manager.require_dependency(dependency_name) - - -def get_optional_module(dependency_name: str) -> Optional[Any]: - """Get a module if available, None otherwise""" - return dependency_manager.get_module(dependency_name, raise_on_missing=False) - - -def graceful_import(dependency_name: str, fallback_message: Optional[str] = None): - """Decorator for graceful dependency handling""" - return dependency_manager.create_graceful_import_wrapper(dependency_name, fallback_message) \ No newline at end of file diff --git a/./error_handling.py b/./error_handling.py deleted file mode 100644 index 319d3be..0000000 --- a/./error_handling.py +++ /dev/null @@ -1,1118 +0,0 @@ -""" -Centralized error handling framework for the journal organizer application. -Provides consistent error types and handling patterns across all components. -""" - -import logging -import traceback -from dataclasses import dataclass, field -from datetime import datetime -from typing import Dict, Any, Optional, Union, List - - -class JournalOrganizerError(Exception): - """Base exception for journal organizer errors""" - - def __init__( - self, - message: str, - context: Optional[Dict[str, Any]] = None, - cause: Optional[Exception] = None, - ) -> None: - """ - Initialize base error - - Args: - message: Error message - context: Additional context information - cause: Original exception that caused this error - """ - super().__init__(message) - self.message = message - self.context = context or {} - self.cause = cause - self.timestamp = datetime.now().isoformat() - - def to_dict(self) -> Dict[str, Any]: - """Convert error to dictionary for logging/serialization""" - return { - "error_type": self.__class__.__name__, - "message": self.message, - "context": self.context, - "timestamp": self.timestamp, - "cause": str(self.cause) if self.cause else None, - } - - -class ConfigurationError(JournalOrganizerError): - """Raised when configuration is invalid or missing""" - - def __init__( - self, - message: str, - config_key: Optional[str] = None, - config_value: Optional[Any] = None, - cause: Optional[Exception] = None, - ) -> None: - context = {} - if config_key: - context["config_key"] = config_key - if config_value is not None: - # Sanitize sensitive values - if ( - "key" in str(config_key).lower() - or "password" in str(config_key).lower() - ): - context["config_value"] = "[REDACTED]" - else: - context["config_value"] = str(config_value) - - super().__init__(message, context, cause) - - -class ClaudeConfigurationError(ConfigurationError): - """Specific error for Claude API configuration issues""" - - def __init__( - self, - message: str, - config_key: Optional[str] = None, - config_value: Optional[Any] = None, - suggestions: Optional[list] = None, - corrective_action: Optional[str] = None, - cause: Optional[Exception] = None, - ) -> None: - # Add Claude-specific context - context = {} - if config_key: - context["config_key"] = config_key - if config_value is not None: - # Sanitize sensitive values for Claude config - if any(sensitive in str(config_key).lower() for sensitive in ["key", "token", "secret"]): - context["config_value"] = "[REDACTED]" - else: - context["config_value"] = str(config_value) - if suggestions: - context["suggestions"] = suggestions - if corrective_action: - context["corrective_action"] = corrective_action - - super().__init__(message, config_key, config_value, cause) - self.suggestions = suggestions or [] - self.corrective_action = corrective_action - - def get_user_friendly_message(self) -> str: - """Get a user-friendly error message with suggestions""" - message_parts = [self.message] - - if self.suggestions: - message_parts.append("\nSuggestions:") - for suggestion in self.suggestions: - message_parts.append(f" • {suggestion}") - - if self.corrective_action: - message_parts.append(f"\nTo fix this: {self.corrective_action}") - - return "\n".join(message_parts) - - -class ClaudeAPIURLError(ClaudeConfigurationError): - """Error for invalid Claude API URL configuration""" - - def __init__( - self, - message: str, - invalid_url: Optional[str] = None, - cause: Optional[Exception] = None, - ) -> None: - suggestions = [ - "Use a valid HTTP or HTTPS URL (e.g., https://api.anthropic.com)", - "Ensure the URL doesn't have trailing slashes", - "For custom endpoints, verify the server is running and accessible", - "For localhost endpoints, use http://localhost:PORT or https://localhost:PORT" - ] - - corrective_action = ( - "Update your configuration with a valid API URL. " - "The default Anthropic API URL is 'https://api.anthropic.com'" - ) - - super().__init__( - message=message, - config_key="api_url", - config_value=invalid_url, - suggestions=suggestions, - corrective_action=corrective_action, - cause=cause - ) - - -class ClaudeModelValidationError(ClaudeConfigurationError): - """Error for invalid Claude model names with suggestions""" - - def __init__( - self, - message: str, - invalid_model: Optional[str] = None, - valid_models: Optional[list] = None, - cause: Optional[Exception] = None, - ) -> None: - # Default valid models if not provided - if not valid_models: - valid_models = [ - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "claude-3-opus-20240229", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307" - ] - - suggestions = [ - f"Use one of these supported models: {', '.join(valid_models[:3])}", - "Model names should follow pattern: claude-X-Y-YYYYMMDD or claude-X-Y-latest", - "Check the Anthropic documentation for the latest available models", - "Ensure the model name is spelled correctly and includes the date suffix" - ] - - corrective_action = ( - f"Update your model configuration to use a valid model name. " - f"Recommended: '{valid_models[0]}'" - ) - - super().__init__( - message=message, - config_key="model", - config_value=invalid_model, - suggestions=suggestions, - corrective_action=corrective_action, - cause=cause - ) - - -class ClaudeAPIKeyError(ClaudeConfigurationError): - """Error for invalid Claude API key configuration""" - - def __init__( - self, - message: str, - cause: Optional[Exception] = None, - ) -> None: - suggestions = [ - "Obtain a valid API key from https://console.anthropic.com/", - "Ensure the API key starts with 'sk-ant-'", - "Set the API key as an environment variable: export ANTHROPIC_API_KEY='sk-ant-...'", - "Verify the API key is not truncated or contains extra whitespace", - "Check that your API key has the necessary permissions" - ] - - corrective_action = ( - "Get a valid Claude API key from the Anthropic console and set it in your configuration " - "or as the ANTHROPIC_API_KEY environment variable" - ) - - super().__init__( - message=message, - config_key="api_key", - config_value="[REDACTED]", - suggestions=suggestions, - corrective_action=corrective_action, - cause=cause - ) - - -class ClaudeConnectionError(ClaudeConfigurationError): - """Error for Claude API connection issues""" - - def __init__( - self, - message: str, - api_url: Optional[str] = None, - status_code: Optional[int] = None, - cause: Optional[Exception] = None, - ) -> None: - suggestions = [] - corrective_action = "" - - # Provide specific suggestions based on the error context - if status_code == 401: - suggestions = [ - "Verify your API key is correct and active", - "Check that the API key has not expired", - "Ensure you're using the correct API key for the endpoint" - ] - corrective_action = "Update your API key with a valid, active key from the Anthropic console" - elif status_code == 404: - suggestions = [ - "Verify the API URL is correct", - "Check if the API endpoint is available", - "For custom endpoints, ensure the service is running" - ] - corrective_action = "Verify and correct your API URL configuration" - elif status_code == 429: - suggestions = [ - "You've exceeded the API rate limit", - "Wait before making more requests", - "Consider implementing request throttling" - ] - corrective_action = "Wait for the rate limit to reset before retrying" - elif "connection" in message.lower() or "timeout" in message.lower(): - suggestions = [ - "Check your internet connection", - "Verify the API URL is accessible", - "For custom endpoints, ensure the server is running", - "Check firewall settings that might block the connection" - ] - corrective_action = "Verify network connectivity and API endpoint availability" - elif "ssl" in message.lower() or "certificate" in message.lower(): - suggestions = [ - "For localhost endpoints, SSL errors are expected", - "For custom HTTPS endpoints, ensure valid SSL certificates", - "Consider using HTTP for local development endpoints" - ] - corrective_action = "Check SSL certificate configuration or use HTTP for local endpoints" - else: - suggestions = [ - "Check the API endpoint status", - "Verify your configuration is correct", - "Try again after a short delay" - ] - corrective_action = "Review your Claude API configuration and try again" - - context = {} - if api_url: - context["api_url"] = api_url - if status_code: - context["status_code"] = status_code - - super().__init__( - message=message, - config_key="connection", - suggestions=suggestions, - corrective_action=corrective_action, - cause=cause - ) - - # Add additional context - self.context.update(context) - - -class APIError(JournalOrganizerError): - """Raised when external API calls fail""" - - def __init__( - self, - message: str, - api_name: Optional[str] = None, - status_code: Optional[int] = None, - response_data: Optional[str] = None, - cause: Optional[Exception] = None, - ) -> None: - context = {} - if api_name: - context["api_name"] = api_name - if status_code: - context["status_code"] = status_code - if response_data: - # Limit response data length to prevent log spam - context["response_data"] = ( - f"{response_data[:500]}..." - if len(response_data) > 500 - else response_data - ) - - super().__init__(message, context, cause) - - -class ValidationError(JournalOrganizerError): - """Raised when input validation fails""" - - def __init__( - self, - message: str, - field_name: Optional[str] = None, - field_value: Optional[Any] = None, - validation_rule: Optional[str] = None, - cause: Optional[Exception] = None, - ) -> None: - context = {} - if field_name: - context["field_name"] = field_name - if field_value is not None: - # Sanitize potentially sensitive field values - if any( - sensitive in str(field_name).lower() - for sensitive in ["password", "key", "token", "secret"] - ): - context["field_value"] = "[REDACTED]" - else: - context["field_value"] = str(field_value)[:100] # Limit length - if validation_rule: - context["validation_rule"] = validation_rule - - super().__init__(message, context, cause) - - -class FileSystemError(JournalOrganizerError): - """Raised when file system operations fail""" - - def __init__( - self, - message: str, - file_path: Optional[str] = None, - operation: Optional[str] = None, - cause: Optional[Exception] = None, - ) -> None: - context = {} - if file_path: - context["file_path"] = file_path - if operation: - context["operation"] = operation - - super().__init__(message, context, cause) - - -class SecurityError(JournalOrganizerError): - """Raised when security violations are detected""" - - def __init__( - self, - message: str, - security_issue: Optional[str] = None, - attempted_path: Optional[str] = None, - risk_level: str = "high", - cause: Optional[Exception] = None, - ) -> None: - context = {} - if security_issue: - context["security_issue"] = security_issue - if attempted_path: - # Sanitize the attempted path to prevent log injection - context["attempted_path"] = str(attempted_path)[:500] # Limit length - context["risk_level"] = risk_level - - super().__init__(message, context, cause) - - -@dataclass -class ErrorContext: - """Context information for error handling""" - - component: str - operation: str - user_message: str = "" - technical_details: Dict[str, Any] = field(default_factory=dict) - severity: str = "error" # error, warning, info - - def __post_init__(self) -> None: - """Validate error context after initialization""" - if not self.component or not self.component.strip(): - raise ValueError("component cannot be empty") - if not self.operation or not self.operation.strip(): - raise ValueError("operation cannot be empty") - valid_severities = ["error", "warning", "info"] - if self.severity not in valid_severities: - raise ValueError(f"severity must be one of {valid_severities}") - if not isinstance(self.technical_details, dict): - raise ValueError("technical_details must be a dictionary") - - def to_dict(self) -> Dict[str, Any]: - """Convert to dictionary""" - return { - "component": self.component, - "operation": self.operation, - "user_message": self.user_message, - "technical_details": self.technical_details, - "severity": self.severity, - } - - -class ErrorHandler: - """Centralized error handling and logging""" - - def __init__(self, logger: Optional[logging.Logger] = None) -> None: - """ - Initialize error handler - - Args: - logger: Logger instance to use (creates default if None) - """ - self.logger = logger or logging.getLogger(__name__) - - def handle_error(self, error: Exception, context: ErrorContext) -> Dict[str, Any]: - """ - Handle any error with consistent logging and formatting - - Args: - error: The exception that occurred - context: Error context information - - Returns: - Standardized error response dictionary - """ - # Log the error with appropriate level - log_level = getattr(logging, context.severity.upper(), logging.ERROR) - - if isinstance(error, JournalOrganizerError): - # Use our custom error information - error_dict = error.to_dict() - # Sanitize the error dictionary - sanitized_error_dict = self._sanitize_context_data(error_dict) - self.logger.log( - log_level, - f"{context.component}.{context.operation}: {error.message}", - extra={"error_context": sanitized_error_dict}, - ) - else: - # Handle unexpected errors - sanitized_context = self._sanitize_context_data(context.to_dict()) - self.logger.log( - log_level, - f"{context.component}.{context.operation}: {str(error)}", - extra={ - "error_context": sanitized_context, - "traceback": traceback.format_exc(), - }, - ) - - # Return standardized error response with sanitized messages - return { - "success": False, - "error": self._sanitize_error_message(str(error)), - "message": context.user_message or f"Error in {context.operation}", - "component": context.component, - "operation": context.operation, - "timestamp": datetime.now().isoformat(), - "error_type": error.__class__.__name__, - } - - def handle_api_error( - self, error: Exception, api_name: str, operation: str - ) -> Dict[str, Any]: - """ - Handle API-related errors consistently - - Args: - error: The exception that occurred - api_name: Name of the API (e.g., 'obsidian', 'claude') - operation: Operation being performed - - Returns: - Standardized error response - """ - if isinstance(error, APIError): - api_error = error - else: - # Wrap generic exceptions in APIError - api_error = APIError( - message=f"{api_name} API error: {str(error)}", - api_name=api_name, - cause=error, - ) - - context = ErrorContext( - component=f"{api_name}_api", - operation=operation, - user_message=f"Failed to communicate with {api_name}. Please check your configuration and try again.", - severity="error", - ) - - return self.handle_error(api_error, context) - - def handle_validation_error( - self, error: Exception, field_name: str, operation: str - ) -> Dict[str, Any]: - """ - Handle validation errors with user-friendly messages - - Args: - error: The validation exception - field_name: Name of the field that failed validation - operation: Operation being performed - - Returns: - Standardized error response - """ - if isinstance(error, ValidationError): - validation_error = error - else: - validation_error = ValidationError( - message=f"Invalid {field_name}: {str(error)}", - field_name=field_name, - cause=error, - ) - - context = ErrorContext( - component="validation", - operation=operation, - user_message=f"Please check your {field_name} and try again.", - severity="warning", - ) - - return self.handle_error(validation_error, context) - - def handle_configuration_error( - self, error: Exception, config_key: str - ) -> Dict[str, Any]: - """ - Handle configuration errors with helpful guidance - - Args: - error: The configuration exception - config_key: Configuration key that caused the error - - Returns: - Standardized error response - """ - if isinstance(error, ConfigurationError): - config_error = error - else: - config_error = ConfigurationError( - message=f"Configuration error for {config_key}: {str(error)}", - config_key=config_key, - cause=error, - ) - - # Provide helpful guidance based on config key - guidance_messages = { - "api_key": "Please check your API key configuration. Ensure the key is valid and properly set.", - "vault_path": "Please verify that the Obsidian vault path exists and is accessible.", - "url": "Please check the API URL configuration. Ensure the service is running and accessible.", - "model": "Please verify the AI model name is correct and available.", - } - - user_message = guidance_messages.get( - config_key, f"Please check your {config_key} configuration." - ) - - context = ErrorContext( - component="configuration", - operation="load_config", - user_message=user_message, - severity="error", - ) - - return self.handle_error(config_error, context) - - def handle_claude_configuration_error( - self, error: Exception, config_key: str = None, operation: str = "validate_config" - ) -> Dict[str, Any]: - """ - Handle Claude-specific configuration errors with detailed guidance - - Args: - error: The configuration exception - config_key: Specific Claude configuration key that caused the error - operation: Operation being performed when error occurred - - Returns: - Standardized error response with Claude-specific guidance - """ - # Convert to Claude-specific error if not already - if isinstance(error, ClaudeConfigurationError): - claude_error = error - else: - # Create appropriate Claude error based on config key - if config_key == "api_url": - claude_error = ClaudeAPIURLError( - message=str(error), - cause=error - ) - elif config_key == "model": - claude_error = ClaudeModelValidationError( - message=str(error), - cause=error - ) - elif config_key == "api_key": - claude_error = ClaudeAPIKeyError( - message=str(error), - cause=error - ) - else: - claude_error = ClaudeConfigurationError( - message=f"Claude configuration error for {config_key}: {str(error)}", - config_key=config_key, - cause=error - ) - - # Get user-friendly message with suggestions - user_message = claude_error.get_user_friendly_message() - - context = ErrorContext( - component="claude_configuration", - operation=operation, - user_message=user_message, - severity="error", - ) - - return self.handle_error(claude_error, context) - - def handle_claude_connection_error( - self, error: Exception, api_url: str = None, operation: str = "test_connection" - ) -> Dict[str, Any]: - """ - Handle Claude API connection errors with specific guidance - - Args: - error: The connection exception - api_url: API URL that failed to connect - operation: Operation being performed when error occurred - - Returns: - Standardized error response with connection-specific guidance - """ - # Extract status code if available - status_code = None - error_str = str(error) - - # Try to extract HTTP status codes from common error messages - import re - status_match = re.search(r'\b(4\d{2}|5\d{2})\b', error_str) - if status_match: - status_code = int(status_match.group(1)) - - # Create Claude connection error - if isinstance(error, ClaudeConnectionError): - connection_error = error - else: - connection_error = ClaudeConnectionError( - message=str(error), - api_url=api_url, - status_code=status_code, - cause=error - ) - - # Get user-friendly message with suggestions - user_message = connection_error.get_user_friendly_message() - - context = ErrorContext( - component="claude_api", - operation=operation, - user_message=user_message, - severity="error", - ) - - return self.handle_error(connection_error, context) - - def _sanitize_error_message(self, message: str) -> str: - """ - Sanitize error messages to prevent sensitive information exposure - - Args: - message: Original error message - - Returns: - Sanitized error message - """ - # List of patterns that might contain sensitive information - sensitive_patterns = [ - "api_key", - "password", - "token", - "secret", - "auth", - "bearer", - "key", - "pwd", - "pass", - "credential", - "authorization", - ] - - sanitized = message - - # Replace potential sensitive values with placeholder - import re - - # Pattern 1: key=value, key:value patterns - for pattern in sensitive_patterns: - patterns_to_replace = [ - # Match key=value patterns (with optional quotes) - rf'{pattern}["\']?\s*[:=]\s*["\']?[^\s"\'&,;]+["\']?', - # Match Bearer token patterns - rf"Bearer\s+[^\s]+", - # Match quoted key-value pairs - rf'["\']?{pattern}["\']?\s*:\s*["\'][^"\']+["\']', - # Match URL parameters - rf"{pattern}=[^&\s]+", - # Match JSON-like patterns - rf'["\']?{pattern}["\']?\s*:\s*["\']?[^"\'&,;\s]+["\']?', - ] - - for regex_pattern in patterns_to_replace: - sanitized = re.sub( - regex_pattern, - f"{pattern}=[REDACTED]", - sanitized, - flags=re.IGNORECASE, - ) - - # Additional patterns for common sensitive data - additional_patterns = [ - # Email addresses - ( - r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", - "[EMAIL_REDACTED]", - ), - # IP addresses (be conservative, only redact private ranges) - ( - r"\b(?:10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|192\.168\.)\d{1,3}\.\d{1,3}\b", - "[IP_REDACTED]", - ), - # File paths that might contain usernames - (r"/Users/[^/\s]+", "/Users/[USER_REDACTED]"), - (r"C:\\\\Users\\\\[^\\\\s]+", "C:\\\\Users\\\\[USER_REDACTED]"), - # UUIDs (might be sensitive identifiers) - ( - r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b", - "[UUID_REDACTED]", - ), - # Long hex strings that might be tokens/keys - (r"\b[0-9a-fA-F]{32,}\b", "[HEX_TOKEN_REDACTED]"), - ] - - for pattern, replacement in additional_patterns: - sanitized = re.sub(pattern, replacement, sanitized) - - return sanitized - - def _sanitize_context_data(self, data: Dict[str, Any]) -> Dict[str, Any]: - """ - Sanitize context data to prevent sensitive information exposure - - Args: - data: Original context data - - Returns: - Sanitized context data - """ - if not isinstance(data, dict): - return data - - sanitized = {} - sensitive_keys = { - "api_key", - "password", - "token", - "secret", - "auth", - "bearer", - "key", - "pwd", - "pass", - "credential", - "authorization", - "private_key", - "client_secret", - "access_token", - "refresh_token", - } - - for key, value in data.items(): - key_lower = str(key).lower() - - # Check if key contains sensitive information - if any(sensitive in key_lower for sensitive in sensitive_keys): - sanitized[key] = "[REDACTED]" - elif isinstance(value, dict): - # Recursively sanitize nested dictionaries - sanitized[key] = self._sanitize_context_data(value) - elif isinstance(value, str): - # Sanitize string values - sanitized[key] = self._sanitize_error_message(value) - else: - sanitized[key] = value - - return sanitized - - -# Global error handler instance -_global_error_handler: Optional[ErrorHandler] = None - - -def get_error_handler() -> ErrorHandler: - """Get the global error handler instance""" - global _global_error_handler - if _global_error_handler is None: - _global_error_handler = ErrorHandler() - return _global_error_handler - - -def set_error_handler(handler: ErrorHandler) -> None: - """Set the global error handler instance""" - global _global_error_handler - _global_error_handler = handler - - -def audit_error_message_security(message: str) -> Dict[str, Any]: - """ - Audit an error message for potential security issues - - Args: - message: Error message to audit - - Returns: - Dictionary containing audit results - """ - issues = [] - - # Check for potential sensitive patterns - sensitive_patterns = [ - (r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", "email_address"), - (r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "ip_address"), - (r"/Users/[^/\s]+", "user_path"), - (r"C:\\Users\\[^\\s]+", "windows_user_path"), - (r"\b[0-9a-fA-F]{32,}\b", "potential_token"), - ( - r"(?i)(api_key|password|token|secret|auth|bearer)\s*[:=]\s*[^\s]+", - "credential_pattern", - ), - (r"Bearer\s+[^\s]+", "bearer_token"), - ] - - for pattern, issue_type in sensitive_patterns: - import re - - if re.search(pattern, message): - issues.append( - { - "type": issue_type, - "pattern": pattern, - "severity": "high" - if "token" in issue_type or "credential" in issue_type - else "medium", - } - ) - - return { - "message": message, - "has_issues": len(issues) > 0, - "issues": issues, - "risk_level": "high" - if any(issue["severity"] == "high" for issue in issues) - else "medium" - if issues - else "low", - } - - -def transform_pydantic_validation_error(validation_error, config_section: str = "configuration") -> ClaudeConfigurationError: - """ - Transform Pydantic validation errors into user-friendly Claude configuration errors - - Args: - validation_error: Pydantic ValidationError instance - config_section: Configuration section being validated - - Returns: - ClaudeConfigurationError with user-friendly message and suggestions - """ - try: - # Import pydantic ValidationError if available - from pydantic import ValidationError as PydanticValidationError - - if not isinstance(validation_error, PydanticValidationError): - # Not a pydantic error, create generic error - return ClaudeConfigurationError( - message=f"Configuration validation failed: {str(validation_error)}", - cause=validation_error - ) - - errors = validation_error.errors() - if not errors: - return ClaudeConfigurationError( - message="Unknown validation error occurred", - cause=validation_error - ) - - # Process the first error (most relevant) - first_error = errors[0] - field_path = " -> ".join(str(loc) for loc in first_error.get('loc', [])) - error_msg = first_error.get('msg', 'Unknown validation error') - error_type = first_error.get('type', 'unknown') - - # Determine the specific field that failed - field_name = first_error.get('loc', [])[-1] if first_error.get('loc') else 'unknown' - - # Create specific error based on field - if field_name == 'api_url': - return ClaudeAPIURLError( - message=f"Invalid API URL: {error_msg}", - invalid_url=first_error.get('input'), - cause=validation_error - ) - elif field_name == 'model': - return ClaudeModelValidationError( - message=f"Invalid model name: {error_msg}", - invalid_model=first_error.get('input'), - cause=validation_error - ) - elif field_name == 'api_key': - return ClaudeAPIKeyError( - message=f"Invalid API key: {error_msg}", - cause=validation_error - ) - else: - # Generic Claude configuration error - suggestions = [] - corrective_action = "" - - # Add specific suggestions based on error type - if error_type == 'value_error': - suggestions.append("Check that the value meets the required format") - corrective_action = f"Update the {field_name} field with a valid value" - elif error_type == 'missing': - suggestions.append(f"The {field_name} field is required") - corrective_action = f"Add the missing {field_name} field to your configuration" - elif error_type == 'type_error': - suggestions.append(f"The {field_name} field has the wrong data type") - corrective_action = f"Ensure {field_name} is the correct data type" - - return ClaudeConfigurationError( - message=f"Configuration error in {field_path}: {error_msg}", - config_key=field_name, - suggestions=suggestions, - corrective_action=corrective_action, - cause=validation_error - ) - - except ImportError: - # Pydantic not available, create generic error - return ClaudeConfigurationError( - message=f"Configuration validation failed: {str(validation_error)}", - cause=validation_error - ) - - -def create_claude_configuration_help_message() -> str: - """ - Create a comprehensive help message for Claude configuration - - Returns: - Formatted help message with configuration guidance - """ - return """ -Claude API Configuration Help: - -1. API Key (api_key): - - Required: Yes - - Format: Must start with 'sk-ant-' - - Source: Get from https://console.anthropic.com/ - - Environment Variable: ANTHROPIC_API_KEY - - Example: sk-ant-api03-abc123... - -2. API URL (api_url): - - Required: No (defaults to https://api.anthropic.com) - - Format: Valid HTTP/HTTPS URL - - Custom Endpoints: Supported for proxy servers or regional endpoints - - Example: https://api.anthropic.com - -3. Model (model): - - Required: No (defaults to claude-3-5-sonnet-20241022) - - Format: claude-X-Y-YYYYMMDD or claude-X-Y-latest - - Supported Models: - • claude-3-5-sonnet-20241022 (recommended) - • claude-3-5-haiku-20241022 - • claude-3-opus-20240229 - • claude-3-sonnet-20240229 - • claude-3-haiku-20240307 - -4. Additional Parameters: - - max_tokens: Maximum response length (1-200000, default: 4096) - - temperature: Response randomness (0.0-1.0, default: 0.7) - -5. Environment Variables: - - Use ${VARIABLE_NAME} syntax in configuration - - Set required variables: export ANTHROPIC_API_KEY="your-key" - - Default values: ${VARIABLE_NAME:-default_value} - -6. Common Issues: - - API Key: Ensure it's complete and starts with 'sk-ant-' - - URL: Use valid HTTP/HTTPS format without trailing slashes - - Model: Check spelling and include date suffix - - Connection: Verify network access and endpoint availability - -7. Testing Configuration: - - The system will validate your configuration on startup - - Connection tests are performed automatically - - Check logs for detailed error information - -For more help, visit: https://docs.anthropic.com/claude/reference/ -""" - - -def get_claude_error_recovery_suggestions(error_type: str, context: Dict[str, Any] = None) -> List[str]: - """ - Get specific recovery suggestions based on error type and context - - Args: - error_type: Type of error (e.g., 'connection', 'authentication', 'model') - context: Additional context information - - Returns: - List of recovery suggestions - """ - context = context or {} - suggestions = [] - - if error_type == 'connection': - suggestions = [ - "Check your internet connection", - "Verify the API URL is correct and accessible", - "For custom endpoints, ensure the service is running", - "Check firewall settings that might block API access", - "Try again after a short delay" - ] - - if context.get('api_url') and 'localhost' in context['api_url']: - suggestions.insert(2, "For localhost endpoints, ensure the local service is running") - - elif error_type == 'authentication': - suggestions = [ - "Verify your API key is correct and complete", - "Check that the API key starts with 'sk-ant-'", - "Ensure the API key hasn't expired", - "Verify the API key has necessary permissions", - "Get a new API key from https://console.anthropic.com/" - ] - - elif error_type == 'model': - suggestions = [ - "Check the model name spelling and format", - "Ensure the model name includes the date suffix (YYYYMMDD)", - "Use a supported model like 'claude-3-5-sonnet-20241022'", - "Verify the model is available in your region", - "Check the Anthropic documentation for available models" - ] - - if context.get('invalid_model'): - suggestions.insert(0, f"Replace '{context['invalid_model']}' with a valid model name") - - elif error_type == 'rate_limit': - suggestions = [ - "Wait before making more API requests", - "Implement request throttling in your application", - "Consider upgrading your API plan for higher limits", - "Reduce the frequency of API calls", - "Use exponential backoff for retries" - ] - - elif error_type == 'validation': - suggestions = [ - "Check your configuration file syntax", - "Ensure all required fields are present", - "Verify data types match the expected format", - "Remove any extra or invalid configuration fields", - "Use the configuration example as a reference" - ] - - else: - # Generic suggestions - suggestions = [ - "Check your Claude API configuration", - "Verify all required fields are present and correct", - "Review the error message for specific details", - "Consult the configuration documentation", - "Try restarting the application after fixing the configuration" - ] - - return suggestions diff --git a/./input_validation.py b/./input_validation.py deleted file mode 100644 index 81bfcc4..0000000 --- a/./input_validation.py +++ /dev/null @@ -1,1297 +0,0 @@ -""" -Comprehensive input validation and sanitization utilities -Provides validation for user input formats, constraints, and security -""" - -import json -import logging -import re -from datetime import datetime, date -from pathlib import Path -from typing import Any, Dict, List, Optional, Union, Tuple, Callable -from urllib.parse import urlparse - -try: - from .error_handling import ValidationError -except ImportError: - from error_handling import ValidationError - - -class InputValidator: - """Comprehensive input validation utility class""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - - # Common regex patterns - self.patterns = { - 'email': re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'), - 'url': re.compile(r'^https?://[^\s/$.?#].[^\s]*$'), - 'api_key_claude': re.compile(r'^sk-ant-[a-zA-Z0-9_-]{50,}$'), - 'api_key_generic': re.compile(r'^[a-zA-Z0-9_-]{8,}$'), - 'date_iso': re.compile(r'^\d{4}-\d{2}-\d{2}$'), - 'datetime_iso': re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'), - 'file_extension': re.compile(r'^\.[a-zA-Z0-9]+$'), - 'folder_name': re.compile(r'^[a-zA-Z0-9_\-\s/]+$'), - 'safe_filename': re.compile(r'^[a-zA-Z0-9_\-\.\s]+$'), - } - - # Validation constraints - self.constraints = { - 'max_string_length': 10000, - 'max_content_length': 1000000, # 1MB - 'min_password_length': 8, - 'max_api_key_length': 200, - 'max_path_length': 4096, - } - - def validate_string( - self, - value: Any, - field_name: str, - min_length: int = 0, - max_length: Optional[int] = None, - pattern: Optional[str] = None, - allow_empty: bool = False, - strip_whitespace: bool = True - ) -> str: - """ - Validate string input with comprehensive checks - - Args: - value: Input value to validate - field_name: Name of the field for error messages - min_length: Minimum string length - max_length: Maximum string length - pattern: Regex pattern name or custom pattern - allow_empty: Whether to allow empty strings - strip_whitespace: Whether to strip whitespace - - Returns: - Validated and sanitized string - - Raises: - ValidationError: If validation fails - """ - # Type check - if not isinstance(value, str): - if value is None: - if allow_empty: - return "" - raise ValidationError( - message=f"{field_name} cannot be None", - field_name=field_name, - validation_rule="not_none" - ) - # Try to convert to string - try: - value = str(value) - except Exception: - raise ValidationError( - message=f"{field_name} must be a string", - field_name=field_name, - validation_rule="string_type" - ) - - # Strip whitespace if requested - if strip_whitespace: - value = value.strip() - - # Check empty string - if not value and not allow_empty: - raise ValidationError( - message=f"{field_name} cannot be empty", - field_name=field_name, - validation_rule="non_empty" - ) - - # Length validation - if len(value) < min_length: - raise ValidationError( - message=f"{field_name} must be at least {min_length} characters long", - field_name=field_name, - validation_rule="min_length" - ) - - max_len = max_length or self.constraints['max_string_length'] - if len(value) > max_len: - raise ValidationError( - message=f"{field_name} cannot exceed {max_len} characters", - field_name=field_name, - validation_rule="max_length" - ) - - # Pattern validation - if pattern and value: - if pattern in self.patterns: - regex = self.patterns[pattern] - else: - try: - regex = re.compile(pattern) - except re.error as e: - raise ValidationError( - message=f"Invalid regex pattern for {field_name}: {e}", - field_name=field_name, - validation_rule="pattern_error" - ) - - if not regex.match(value): - raise ValidationError( - message=f"{field_name} format is invalid", - field_name=field_name, - validation_rule="pattern_match" - ) - - return value - - def validate_integer( - self, - value: Any, - field_name: str, - min_value: Optional[int] = None, - max_value: Optional[int] = None, - allow_none: bool = False - ) -> Optional[int]: - """ - Validate integer input - - Args: - value: Input value to validate - field_name: Name of the field for error messages - min_value: Minimum allowed value - max_value: Maximum allowed value - allow_none: Whether to allow None values - - Returns: - Validated integer or None - - Raises: - ValidationError: If validation fails - """ - if value is None: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be None", - field_name=field_name, - validation_rule="not_none" - ) - - # Try to convert to integer - if isinstance(value, str): - value = value.strip() - if not value: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be empty", - field_name=field_name, - validation_rule="non_empty" - ) - - try: - int_value = int(value) - except (ValueError, TypeError): - raise ValidationError( - message=f"{field_name} must be a valid integer", - field_name=field_name, - validation_rule="integer_type" - ) - - # Range validation - if min_value is not None and int_value < min_value: - raise ValidationError( - message=f"{field_name} must be at least {min_value}", - field_name=field_name, - validation_rule="min_value" - ) - - if max_value is not None and int_value > max_value: - raise ValidationError( - message=f"{field_name} cannot exceed {max_value}", - field_name=field_name, - validation_rule="max_value" - ) - - return int_value - - def validate_float( - self, - value: Any, - field_name: str, - min_value: Optional[float] = None, - max_value: Optional[float] = None, - allow_none: bool = False - ) -> Optional[float]: - """ - Validate float input - - Args: - value: Input value to validate - field_name: Name of the field for error messages - min_value: Minimum allowed value - max_value: Maximum allowed value - allow_none: Whether to allow None values - - Returns: - Validated float or None - - Raises: - ValidationError: If validation fails - """ - if value is None: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be None", - field_name=field_name, - validation_rule="not_none" - ) - - # Try to convert to float - if isinstance(value, str): - value = value.strip() - if not value: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be empty", - field_name=field_name, - validation_rule="non_empty" - ) - - try: - float_value = float(value) - except (ValueError, TypeError): - raise ValidationError( - message=f"{field_name} must be a valid number", - field_name=field_name, - validation_rule="float_type" - ) - - # Range validation - if min_value is not None and float_value < min_value: - raise ValidationError( - message=f"{field_name} must be at least {min_value}", - field_name=field_name, - validation_rule="min_value" - ) - - if max_value is not None and float_value > max_value: - raise ValidationError( - message=f"{field_name} cannot exceed {max_value}", - field_name=field_name, - validation_rule="max_value" - ) - - return float_value - - def validate_boolean( - self, - value: Any, - field_name: str, - allow_none: bool = False - ) -> Optional[bool]: - """ - Validate boolean input with flexible conversion - - Args: - value: Input value to validate - field_name: Name of the field for error messages - allow_none: Whether to allow None values - - Returns: - Validated boolean or None - - Raises: - ValidationError: If validation fails - """ - if value is None: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be None", - field_name=field_name, - validation_rule="not_none" - ) - - # Handle boolean type - if isinstance(value, bool): - return value - - # Handle string conversion - if isinstance(value, str): - value = value.strip().lower() - if value in ('true', '1', 'yes', 'on', 'enabled'): - return True - elif value in ('false', '0', 'no', 'off', 'disabled'): - return False - else: - raise ValidationError( - message=f"{field_name} must be a valid boolean value (true/false, yes/no, 1/0)", - field_name=field_name, - validation_rule="boolean_format" - ) - - # Handle numeric conversion - if isinstance(value, (int, float)): - return bool(value) - - raise ValidationError( - message=f"{field_name} must be a valid boolean value", - field_name=field_name, - validation_rule="boolean_type" - ) - - def validate_list( - self, - value: Any, - field_name: str, - min_length: int = 0, - max_length: Optional[int] = None, - item_validator: Optional[Callable] = None, - allow_none: bool = False - ) -> Optional[List[Any]]: - """ - Validate list input with optional item validation - - Args: - value: Input value to validate - field_name: Name of the field for error messages - min_length: Minimum list length - max_length: Maximum list length - item_validator: Function to validate each item - allow_none: Whether to allow None values - - Returns: - Validated list or None - - Raises: - ValidationError: If validation fails - """ - if value is None: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be None", - field_name=field_name, - validation_rule="not_none" - ) - - # Convert to list if needed - if not isinstance(value, list): - if isinstance(value, (tuple, set)): - value = list(value) - else: - raise ValidationError( - message=f"{field_name} must be a list", - field_name=field_name, - validation_rule="list_type" - ) - - # Length validation - if len(value) < min_length: - raise ValidationError( - message=f"{field_name} must contain at least {min_length} items", - field_name=field_name, - validation_rule="min_length" - ) - - if max_length is not None and len(value) > max_length: - raise ValidationError( - message=f"{field_name} cannot contain more than {max_length} items", - field_name=field_name, - validation_rule="max_length" - ) - - # Validate each item if validator provided - if item_validator: - validated_items = [] - for i, item in enumerate(value): - try: - validated_item = item_validator(item) - validated_items.append(validated_item) - except ValidationError as e: - raise ValidationError( - message=f"{field_name}[{i}]: {e.message}", - field_name=f"{field_name}[{i}]", - validation_rule=e.validation_rule - ) - return validated_items - - return value - - def validate_dict( - self, - value: Any, - field_name: str, - required_keys: Optional[List[str]] = None, - optional_keys: Optional[List[str]] = None, - allow_extra_keys: bool = True, - allow_none: bool = False - ) -> Optional[Dict[str, Any]]: - """ - Validate dictionary input with key validation - - Args: - value: Input value to validate - field_name: Name of the field for error messages - required_keys: List of required keys - optional_keys: List of optional keys - allow_extra_keys: Whether to allow keys not in required/optional - allow_none: Whether to allow None values - - Returns: - Validated dictionary or None - - Raises: - ValidationError: If validation fails - """ - if value is None: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be None", - field_name=field_name, - validation_rule="not_none" - ) - - if not isinstance(value, dict): - raise ValidationError( - message=f"{field_name} must be a dictionary", - field_name=field_name, - validation_rule="dict_type" - ) - - # Check required keys - if required_keys: - missing_keys = [key for key in required_keys if key not in value] - if missing_keys: - raise ValidationError( - message=f"{field_name} is missing required keys: {', '.join(missing_keys)}", - field_name=field_name, - validation_rule="missing_keys" - ) - - # Check for unexpected keys - if not allow_extra_keys and (required_keys or optional_keys): - allowed_keys = set(required_keys or []) | set(optional_keys or []) - extra_keys = [key for key in value.keys() if key not in allowed_keys] - if extra_keys: - raise ValidationError( - message=f"{field_name} contains unexpected keys: {', '.join(extra_keys)}", - field_name=field_name, - validation_rule="extra_keys" - ) - - return value - - def validate_json( - self, - value: Any, - field_name: str, - allow_none: bool = False - ) -> Optional[Union[Dict[str, Any], List[Any]]]: - """ - Validate JSON input (string or already parsed) - - Args: - value: Input value to validate - field_name: Name of the field for error messages - allow_none: Whether to allow None values - - Returns: - Parsed JSON data or None - - Raises: - ValidationError: If validation fails - """ - if value is None: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be None", - field_name=field_name, - validation_rule="not_none" - ) - - # If already parsed, validate it's JSON-serializable - if isinstance(value, (dict, list)): - try: - json.dumps(value) - return value - except (TypeError, ValueError) as e: - raise ValidationError( - message=f"{field_name} contains non-JSON-serializable data: {e}", - field_name=field_name, - validation_rule="json_serializable" - ) - - # If string, try to parse - if isinstance(value, str): - value = value.strip() - if not value: - if allow_none: - return None - raise ValidationError( - message=f"{field_name} cannot be empty", - field_name=field_name, - validation_rule="non_empty" - ) - - try: - return json.loads(value) - except json.JSONDecodeError as e: - raise ValidationError( - message=f"{field_name} is not valid JSON: {e}", - field_name=field_name, - validation_rule="json_format" - ) - - raise ValidationError( - message=f"{field_name} must be a JSON string or parsed JSON data", - field_name=field_name, - validation_rule="json_type" - ) - - -class ContentValidator: - """Validator for content-specific inputs""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - self.base_validator = InputValidator() - - def validate_journal_content( - self, - content: Any, - field_name: str = "journal_content", - max_length: Optional[int] = None - ) -> str: - """ - Validate journal content with content-specific rules - - Args: - content: Journal content to validate - field_name: Name of the field for error messages - max_length: Maximum content length - - Returns: - Validated journal content - - Raises: - ValidationError: If validation fails - """ - max_len = max_length or self.base_validator.constraints['max_content_length'] - - validated_content = self.base_validator.validate_string( - content, - field_name, - min_length=1, - max_length=max_len, - allow_empty=False - ) - - # Additional content validation - if len(validated_content.strip()) < 10: - raise ValidationError( - message=f"{field_name} appears to be too short for meaningful analysis", - field_name=field_name, - validation_rule="content_too_short" - ) - - # Check for suspicious content patterns - suspicious_patterns = [ - r']*>.*?', # Script tags - r'javascript:', # JavaScript URLs - r'data:text/html', # Data URLs - ] - - for pattern in suspicious_patterns: - if re.search(pattern, validated_content, re.IGNORECASE | re.DOTALL): - self.logger.warning(f"Suspicious content pattern detected in {field_name}") - # Don't reject, but log for security monitoring - - return validated_content - - def validate_api_key( - self, - api_key: Any, - api_type: str = "generic", - field_name: str = "api_key" - ) -> str: - """ - Validate API key with type-specific rules - - Args: - api_key: API key to validate - api_type: Type of API key (claude, generic, obsidian) - field_name: Name of the field for error messages - - Returns: - Validated API key - - Raises: - ValidationError: If validation fails - """ - validated_key = self.base_validator.validate_string( - api_key, - field_name, - min_length=8, - max_length=self.base_validator.constraints['max_api_key_length'], - allow_empty=False - ) - - # Type-specific validation - if api_type == "claude": - if not validated_key.startswith('sk-ant-'): - raise ValidationError( - message=f"{field_name} for Claude API should start with 'sk-ant-'", - field_name=field_name, - validation_rule="claude_api_key_format" - ) - - if len(validated_key) < 50: - raise ValidationError( - message=f"{field_name} for Claude API appears to be too short", - field_name=field_name, - validation_rule="claude_api_key_length" - ) - - elif api_type == "obsidian": - # Obsidian API keys are user-generated, so less strict - if len(validated_key) < 8: - raise ValidationError( - message=f"{field_name} for Obsidian API should be at least 8 characters", - field_name=field_name, - validation_rule="obsidian_api_key_length" - ) - - # Check for obviously insecure keys - insecure_keys = ['password', '123456', 'admin', 'test', 'key', 'secret'] - if validated_key.lower() in insecure_keys: - raise ValidationError( - message=f"{field_name} appears to be insecure. Please use a stronger API key", - field_name=field_name, - validation_rule="insecure_api_key" - ) - - # Check for common issues - if ' ' in validated_key: - raise ValidationError( - message=f"{field_name} should not contain spaces", - field_name=field_name, - validation_rule="api_key_whitespace" - ) - - if validated_key.endswith('...') or '...' in validated_key: - raise ValidationError( - message=f"{field_name} appears to be truncated", - field_name=field_name, - validation_rule="api_key_truncated" - ) - - return validated_key - - def validate_url( - self, - url: Any, - field_name: str = "url", - allowed_schemes: Optional[List[str]] = None, - require_https: bool = False - ) -> str: - """ - Validate URL with security checks - - Args: - url: URL to validate - field_name: Name of the field for error messages - allowed_schemes: List of allowed URL schemes - require_https: Whether to require HTTPS - - Returns: - Validated URL - - Raises: - ValidationError: If validation fails - """ - validated_url = self.base_validator.validate_string( - url, - field_name, - min_length=1, - allow_empty=False - ) - - # Parse URL - try: - parsed = urlparse(validated_url) - except Exception as e: - raise ValidationError( - message=f"{field_name} is not a valid URL: {e}", - field_name=field_name, - validation_rule="url_parse_error" - ) - - # Check scheme - if not parsed.scheme: - raise ValidationError( - message=f"{field_name} must include a scheme (http:// or https://)", - field_name=field_name, - validation_rule="url_missing_scheme" - ) - - allowed_schemes = allowed_schemes or ['http', 'https'] - if parsed.scheme not in allowed_schemes: - raise ValidationError( - message=f"{field_name} scheme must be one of: {', '.join(allowed_schemes)}", - field_name=field_name, - validation_rule="url_invalid_scheme" - ) - - if require_https and parsed.scheme != 'https': - raise ValidationError( - message=f"{field_name} must use HTTPS", - field_name=field_name, - validation_rule="url_https_required" - ) - - # Check hostname - if not parsed.netloc: - raise ValidationError( - message=f"{field_name} must include a hostname", - field_name=field_name, - validation_rule="url_missing_hostname" - ) - - # Security checks for local/private URLs - hostname = parsed.hostname - if hostname: - # Check for localhost/private IPs (warn but don't reject for development) - if hostname in ['localhost', '127.0.0.1', '::1']: - self.logger.info(f"Local URL detected in {field_name}: {validated_url}") - elif hostname.startswith('192.168.') or hostname.startswith('10.') or hostname.startswith('172.'): - self.logger.info(f"Private network URL detected in {field_name}: {validated_url}") - - return validated_url - - -# Global validator instances -input_validator = InputValidator() -content_validator = ContentValidator() - - -def validate_user_input( - data: Dict[str, Any], - validation_rules: Dict[str, Dict[str, Any]] -) -> Dict[str, Any]: - """ - Validate user input data against validation rules - - Args: - data: Input data to validate - validation_rules: Dictionary of field validation rules - - Returns: - Dictionary of validated data - - Raises: - ValidationError: If any validation fails - """ - validated_data = {} - - for field_name, rules in validation_rules.items(): - value = data.get(field_name) - - # Get validation type and parameters - validation_type = rules.get('type', 'string') - required = rules.get('required', True) - - # Handle missing values - if value is None or (isinstance(value, str) and not value.strip()): - if required: - raise ValidationError( - message=f"{field_name} is required", - field_name=field_name, - validation_rule="required" - ) - else: - validated_data[field_name] = None - continue - - # Apply validation based on type - try: - if validation_type == 'string': - validated_data[field_name] = input_validator.validate_string( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'integer': - validated_data[field_name] = input_validator.validate_integer( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'float': - validated_data[field_name] = input_validator.validate_float( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'boolean': - validated_data[field_name] = input_validator.validate_boolean( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'list': - validated_data[field_name] = input_validator.validate_list( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'dict': - validated_data[field_name] = input_validator.validate_dict( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'json': - validated_data[field_name] = input_validator.validate_json( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'url': - validated_data[field_name] = content_validator.validate_url( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - elif validation_type == 'api_key': - api_type = rules.get('api_type', 'generic') - validated_data[field_name] = content_validator.validate_api_key( - value, api_type, field_name - ) - elif validation_type == 'journal_content': - validated_data[field_name] = content_validator.validate_journal_content( - value, field_name, **{k: v for k, v in rules.items() if k not in ['type', 'required']} - ) - else: - raise ValidationError( - message=f"Unknown validation type: {validation_type}", - field_name=field_name, - validation_rule="unknown_validation_type" - ) - - except ValidationError: - # Re-raise validation errors as-is - raise - except Exception as e: - # Wrap unexpected errors - raise ValidationError( - message=f"Validation error for {field_name}: {str(e)}", - field_name=field_name, - validation_rule="validation_error" - ) - - return validated_data - - -class CommandInputValidator: - """Specialized validator for command inputs""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - self.base_validator = InputValidator() - self.content_validator = ContentValidator() - - def validate_organize_command_input(self, args: Dict[str, Any]) -> Dict[str, Any]: - """ - Validate input for the organize command - - Args: - args: Command arguments to validate - - Returns: - Validated arguments - - Raises: - ValidationError: If validation fails - """ - validation_rules = { - 'date': { - 'type': 'string', - 'required': False, - 'pattern': 'date_iso', - 'allow_empty': True - }, - 'vault_path': { - 'type': 'string', - 'required': False, - 'min_length': 1, - 'max_length': 4096, - 'allow_empty': True - }, - 'daily_folder': { - 'type': 'string', - 'required': False, - 'pattern': 'folder_name', - 'min_length': 1, - 'max_length': 255, - 'allow_empty': True - } - } - - validated_args = validate_user_input(args, validation_rules) - - # Additional date validation - if validated_args.get('date'): - try: - datetime.strptime(validated_args['date'], '%Y-%m-%d') - except ValueError: - raise ValidationError( - message="Date must be in YYYY-MM-DD format", - field_name="date", - validation_rule="date_format" - ) - - return validated_args - - def validate_skill_input(self, skill_name: str, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """ - Validate input for specific skills - - Args: - skill_name: Name of the skill - kwargs: Skill arguments to validate - - Returns: - Validated arguments - - Raises: - ValidationError: If validation fails - """ - if skill_name == 'obsidian_read': - return self._validate_obsidian_read_input(kwargs) - elif skill_name == 'obsidian_write': - return self._validate_obsidian_write_input(kwargs) - elif skill_name == 'obsidian_append': - return self._validate_obsidian_append_input(kwargs) - elif skill_name == 'obsidian_list_files': - return self._validate_obsidian_list_input(kwargs) - elif skill_name == 'claude_analyze': - return self._validate_claude_analyze_input(kwargs) - elif skill_name == 'claude_transform': - return self._validate_claude_transform_input(kwargs) - else: - # Generic validation for unknown skills - return self._validate_generic_skill_input(kwargs) - - def _validate_obsidian_read_input(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """Validate Obsidian read skill input""" - validation_rules = { - 'file_path': { - 'type': 'string', - 'required': True, - 'min_length': 1, - 'max_length': 4096 - }, - 'api_url': { - 'type': 'url', - 'required': False, - 'allowed_schemes': ['http', 'https'] - }, - 'api_key': { - 'type': 'api_key', - 'required': True, - 'api_type': 'obsidian' - } - } - - validated = validate_user_input(kwargs, validation_rules) - - # Additional file path validation - if validated.get('file_path'): - validated['file_path'] = self._sanitize_file_path(validated['file_path']) - - return validated - - def _validate_obsidian_write_input(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """Validate Obsidian write skill input""" - validation_rules = { - 'file_path': { - 'type': 'string', - 'required': True, - 'min_length': 1, - 'max_length': 4096 - }, - 'content': { - 'type': 'string', - 'required': True, - 'min_length': 0, # Allow empty content for creating empty files - 'max_length': 1000000 # 1MB limit - }, - 'overwrite': { - 'type': 'boolean', - 'required': False - }, - 'api_url': { - 'type': 'url', - 'required': False, - 'allowed_schemes': ['http', 'https'] - }, - 'api_key': { - 'type': 'api_key', - 'required': True, - 'api_type': 'obsidian' - } - } - - validated = validate_user_input(kwargs, validation_rules) - - # Additional file path validation - if validated.get('file_path'): - validated['file_path'] = self._sanitize_file_path(validated['file_path']) - - return validated - - def _validate_obsidian_append_input(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """Validate Obsidian append skill input""" - validation_rules = { - 'file_path': { - 'type': 'string', - 'required': True, - 'min_length': 1, - 'max_length': 4096 - }, - 'content': { - 'type': 'string', - 'required': True, - 'min_length': 1, - 'max_length': 1000000 # 1MB limit - }, - 'api_url': { - 'type': 'url', - 'required': False, - 'allowed_schemes': ['http', 'https'] - }, - 'api_key': { - 'type': 'api_key', - 'required': True, - 'api_type': 'obsidian' - } - } - - validated = validate_user_input(kwargs, validation_rules) - - # Additional file path validation - if validated.get('file_path'): - validated['file_path'] = self._sanitize_file_path(validated['file_path']) - - return validated - - def _validate_obsidian_list_input(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """Validate Obsidian list files skill input""" - validation_rules = { - 'folder_path': { - 'type': 'string', - 'required': False, - 'min_length': 0, - 'max_length': 4096, - 'allow_empty': True - }, - 'api_url': { - 'type': 'url', - 'required': False, - 'allowed_schemes': ['http', 'https'] - }, - 'api_key': { - 'type': 'api_key', - 'required': True, - 'api_type': 'obsidian' - } - } - - validated = validate_user_input(kwargs, validation_rules) - - # Additional folder path validation - if validated.get('folder_path'): - validated['folder_path'] = self._sanitize_file_path(validated['folder_path']) - - return validated - - def _validate_claude_analyze_input(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """Validate Claude analyze skill input""" - validation_rules = { - 'journal_content': { - 'type': 'journal_content', - 'required': True - }, - 'api_key': { - 'type': 'api_key', - 'required': True, - 'api_type': 'claude' - }, - 'model': { - 'type': 'string', - 'required': False, - 'min_length': 1, - 'max_length': 100 - }, - 'categories': { - 'type': 'list', - 'required': False, - 'min_length': 0, - 'max_length': 50 - } - } - - validated = validate_user_input(kwargs, validation_rules) - - # Validate model name if provided - if validated.get('model'): - valid_models = [ - 'claude-3-5-sonnet-20241022', - 'claude-3-5-haiku-20241022', - 'claude-3-opus-20240229', - 'claude-3-sonnet-20240229', - 'claude-3-haiku-20240307' - ] - if validated['model'] not in valid_models: - self.logger.warning(f"Unknown Claude model: {validated['model']}") - - # Validate categories if provided - if validated.get('categories'): - category_validator = lambda cat: self.base_validator.validate_string( - cat, 'category', min_length=1, max_length=100 - ) - validated['categories'] = self.base_validator.validate_list( - validated['categories'], - 'categories', - item_validator=category_validator - ) - - return validated - - def _validate_claude_transform_input(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """Validate Claude transform skill input""" - validation_rules = { - 'content': { - 'type': 'string', - 'required': True, - 'min_length': 1, - 'max_length': 1000000 # 1MB limit - }, - 'transform_type': { - 'type': 'string', - 'required': False, - 'min_length': 1, - 'max_length': 50 - }, - 'api_key': { - 'type': 'api_key', - 'required': True, - 'api_type': 'claude' - }, - 'model': { - 'type': 'string', - 'required': False, - 'min_length': 1, - 'max_length': 100 - } - } - - validated = validate_user_input(kwargs, validation_rules) - - # Validate transform type - if validated.get('transform_type'): - valid_types = ['markdown', 'html', 'summary', 'outline', 'checklist'] - if validated['transform_type'] not in valid_types: - self.logger.warning(f"Unknown transform type: {validated['transform_type']}") - - return validated - - def _validate_generic_skill_input(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: - """Generic validation for unknown skills""" - validated = {} - - for key, value in kwargs.items(): - if isinstance(value, str): - # Basic string validation - validated[key] = self.base_validator.validate_string( - value, key, max_length=10000, allow_empty=True - ) - elif isinstance(value, (int, float)): - # Keep numeric values as-is but validate range - if isinstance(value, int): - validated[key] = self.base_validator.validate_integer( - value, key, min_value=-2147483648, max_value=2147483647 - ) - else: - validated[key] = self.base_validator.validate_float( - value, key, min_value=-1e10, max_value=1e10 - ) - elif isinstance(value, bool): - validated[key] = value - elif isinstance(value, (list, dict)): - # Basic structure validation - try: - json.dumps(value) # Ensure JSON serializable - validated[key] = value - except (TypeError, ValueError): - raise ValidationError( - message=f"{key} contains non-serializable data", - field_name=key, - validation_rule="json_serializable" - ) - else: - # Convert other types to string - validated[key] = str(value) - - return validated - - def _sanitize_file_path(self, file_path: str) -> str: - """ - Sanitize file path to prevent directory traversal - - Args: - file_path: File path to sanitize - - Returns: - Sanitized file path - - Raises: - ValidationError: If path is unsafe - """ - # Remove any null bytes - file_path = file_path.replace('\x00', '') - - # Normalize path separators - file_path = file_path.replace('\\', '/') - - # Check for directory traversal attempts - if '..' in file_path: - raise ValidationError( - message="File path cannot contain '..' (directory traversal)", - field_name="file_path", - validation_rule="path_traversal" - ) - - # Check for absolute paths (should be relative to vault) - if file_path.startswith('/'): - raise ValidationError( - message="File path must be relative to vault root", - field_name="file_path", - validation_rule="absolute_path" - ) - - # Check for dangerous characters - dangerous_chars = ['<', '>', ':', '"', '|', '?', '*'] - for char in dangerous_chars: - if char in file_path: - raise ValidationError( - message=f"File path cannot contain '{char}'", - field_name="file_path", - validation_rule="dangerous_character" - ) - - # Ensure path doesn't start with special directories - path_parts = file_path.split('/') - if path_parts and path_parts[0].startswith('.'): - if path_parts[0] not in ['.obsidian']: # Allow .obsidian folder - raise ValidationError( - message="File path cannot start with hidden directory", - field_name="file_path", - validation_rule="hidden_directory" - ) - - return file_path - - -# Global command input validator instance -command_input_validator = CommandInputValidator() \ No newline at end of file diff --git a/./main.py b/./main.py deleted file mode 100644 index 2eb5e2c..0000000 --- a/./main.py +++ /dev/null @@ -1,284 +0,0 @@ -""" -日记整理 Agent 主入口 -支持命令行调用和外部集成 -""" - -import argparse -import asyncio -import json -import logging -import sys -from pathlib import Path -from typing import Dict, Any, Optional - -import sys -from pathlib import Path -from typing import Dict, Any, Optional - -# Handle imports with both relative and absolute paths -try: - from .dependency_manager import get_dependency_manager - from .agent_core import Agent, SkillResult - from .commands.organize_command import OrganizeCommand -except ImportError: - # Fallback to absolute imports when running as script - from dependency_manager import get_dependency_manager - from agent_core import Agent, SkillResult - from commands.organize_command import OrganizeCommand - -# Try to import yaml with graceful degradation -dependency_manager = get_dependency_manager() -yaml = dependency_manager.get_module('yaml') - - -class JournalOrganizerAgent: - """日记整理 Agent""" - - def __init__(self, config_file: Optional[str] = None): - """ - 初始化 Agent - - Args: - config_file: 配置文件路径 - """ - self.logger = logging.getLogger("JournalOrganizerAgent") - self.config = self._load_config(config_file) - self.agent = Agent("JournalOrganizer", self.config) - self._register_commands() - - def _load_config(self, config_file: Optional[str] = None) -> Dict[str, Any]: - """ - 加载配置文件 - - Args: - config_file: 配置文件路径 - - Returns: - 配置字典 - """ - if config_file and Path(config_file).exists(): - config_path = Path(config_file) - with config_path.open("r", encoding="utf-8") as f: - if config_path.suffix in [".yaml", ".yml"]: - return yaml.safe_load(f) or {} - elif config_path.suffix == ".json": - return json.load(f) - - # 尝试从默认位置加载 - default_paths = [ - Path.home() / ".journal_organizer" / "config.yaml", - Path.home() / ".journal_organizer" / "config.json", - Path.cwd() / "config.yaml", - Path.cwd() / "config.json", - ] - - for path in default_paths: - if path.exists(): - self.logger.info(f"从 {path} 加载配置") - with path.open("r", encoding="utf-8") as f: - if path.suffix in [".yaml", ".yml"]: - return yaml.safe_load(f) or {} - else: - return json.load(f) - - self.logger.warning("未找到配置文件,使用默认配置") - return {} - - def _register_commands(self) -> None: - """注册所有命令""" - self.agent.register_command(OrganizeCommand()) - - async def run_command( - self, - command: str, - args: Optional[Dict[str, Any]] = None, - options: Optional[Dict[str, Any]] = None, - ) -> SkillResult: - """ - 运行命令 - - Args: - command: 命令名称 - args: 命令参数 - options: 命令选项 - - Returns: - SkillResult: 执行结果 - """ - return await self.agent.execute_command(command, args, options) - - def list_commands(self) -> list: - """列出所有可用命令""" - return self.agent.list_commands() - - def get_command_info(self, command: str) -> Dict[str, Any]: - """获取命令信息""" - if command in self.agent.commands: - return self.agent.commands[command].get_info() - return {} - - def get_all_commands_info(self) -> Dict[str, Any]: - """获取所有命令信息""" - return self.agent.get_commands_info() - - -async def main(): - """命令行主函数""" - parser = argparse.ArgumentParser( - description="Obsidian 智能日记整理 Agent", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -示例: - # 整理今天的日记 - python -m journal_organizer organize - - # 整理指定日期的日记 - python -m journal_organizer organize --date 2025-12-31 - - # 列出所有可用命令 - python -m journal_organizer list - - # 显示命令帮助 - python -m journal_organizer help organize - - # 使用指定配置文件 - python -m journal_organizer --config /path/to/config.yaml organize - """, - ) - - parser.add_argument("--config", type=str, help="配置文件路径") - - parser.add_argument( - "--log-level", - type=str, - default="INFO", - choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - help="日志级别", - ) - - subparsers = parser.add_subparsers(dest="command", help="命令") - - # organize 命令 - organize_parser = subparsers.add_parser("organize", help="整理日记") - organize_parser.add_argument("--date", type=str, help="日期 (YYYY-MM-DD)") - organize_parser.add_argument("--vault-path", type=str, help="Obsidian vault 路径") - organize_parser.add_argument( - "--daily-folder", type=str, default="Daily", help="日记文件夹" - ) - - # list 命令 - subparsers.add_parser("list", help="列出所有可用命令") - - # help 命令 - help_parser = subparsers.add_parser("help", help="显示命令帮助") - help_parser.add_argument("help_command", nargs="?", help="要查看帮助的命令") - - # info 命令 - subparsers.add_parser("info", help="显示 Agent 信息") - - # check-deps 命令 - subparsers.add_parser("check-deps", help="检查依赖项状态") - - args = parser.parse_args() - - # 设置日志 - logging.basicConfig( - level=getattr(logging, args.log_level), - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - ) - - # 初始化 Agent - agent = JournalOrganizerAgent(args.config) - - # 处理命令 - if not args.command: - parser.print_help() - return - - if args.command == "organize": - # 构建参数 - organize_args = {} - if args.date: - organize_args["date"] = args.date - if args.vault_path: - organize_args["vault_path"] = args.vault_path - if args.daily_folder: - organize_args["daily_folder"] = args.daily_folder - - result = await agent.run_command("organize", organize_args) - - print(f"\n{'='*50}") - print("执行结果") - print("=" * 50) - print(result.to_json()) - - return 0 if result.success else 1 - - elif args.command == "list": - commands = agent.list_commands() - print("\n可用命令:") - for cmd in commands: - print(f" - {cmd}") - return 0 - - elif args.command == "help": - if args.help_command: - info = agent.get_command_info(args.help_command) - if info: - print(f"\n命令: {info['name']}") - print(f"描述: {info['description']}") - if info.get("aliases"): - print(f"别名: {', '.join(info['aliases'])}") - print("\nSkills:") - for skill_name, skill_info in info.get("skills", {}).items(): - print(f" - {skill_name}: {skill_info['description']}") - else: - print(f"未找到命令: {args.help_command}") - return 1 - else: - parser.print_help() - return 0 - - elif args.command == "info": - info = agent.get_all_commands_info() - print(f"\n{json.dumps(info, ensure_ascii=False, indent=2)}") - return 0 - - elif args.command == "check-deps": - print("\n🔍 检查依赖项状态...") - print(dependency_manager.get_dependency_status_report()) - - missing_deps = dependency_manager.get_missing_dependencies() - if missing_deps: - print(f"\n📋 安装说明:") - print(dependency_manager.get_installation_instructions(missing_only=True)) - return 1 - else: - print(f"\n✅ 所有依赖项都已正确安装!") - return 0 - - -def run_command_sync( - command: str, - args: Optional[Dict[str, Any]] = None, - config_file: Optional[str] = None, -) -> Dict[str, Any]: - """ - 同步运行命令(用于外部调用) - - Args: - command: 命令名称 - args: 命令参数 - config_file: 配置文件路径 - - Returns: - 执行结果字典 - """ - agent = JournalOrganizerAgent(config_file) - result = asyncio.run(agent.run_command(command, args)) - return result.to_dict() - - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code or 0) diff --git a/./path_security.py b/./path_security.py deleted file mode 100644 index 8ac2814..0000000 --- a/./path_security.py +++ /dev/null @@ -1,583 +0,0 @@ -""" -Path sanitization and security utilities -Provides comprehensive path validation and sanitization to prevent security issues -""" - -import logging -import os -import re -from pathlib import Path, PurePath -from typing import Optional, List, Tuple, Union - -try: - from .error_handling import ValidationError, SecurityError -except ImportError: - from error_handling import ValidationError, SecurityError - - -class PathSanitizer: - """Comprehensive path sanitization and security validation""" - - def __init__(self): - self.logger = logging.getLogger(__name__) - - # Dangerous path patterns - self.dangerous_patterns = [ - r'\.\./', # Directory traversal - r'\.\.\.', # Multiple dots - r'~/', # Home directory reference - r'\$\{.*\}', # Environment variable expansion - r'%[A-Za-z0-9_]+%', # Windows environment variables - ] - - # Dangerous characters in file paths - self.dangerous_chars = { - '<': 'less_than', - '>': 'greater_than', - ':': 'colon', - '"': 'quote', - '|': 'pipe', - '?': 'question', - '*': 'asterisk', - '\x00': 'null_byte', - '\n': 'newline', - '\r': 'carriage_return', - '\t': 'tab' - } - - # Reserved names (Windows) - self.reserved_names = { - 'CON', 'PRN', 'AUX', 'NUL', - 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', - 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9' - } - - # Maximum path lengths - self.max_path_length = 4096 # Unix/Linux limit - self.max_filename_length = 255 # Most filesystems - self.max_path_depth = 32 # Reasonable depth limit - - def sanitize_vault_relative_path( - self, - path: Union[str, Path], - vault_root: Optional[Union[str, Path]] = None, - allow_creation: bool = True - ) -> str: - """ - Sanitize a path that should be relative to an Obsidian vault - - Args: - path: Path to sanitize (should be relative to vault) - vault_root: Optional vault root path for additional validation - allow_creation: Whether to allow paths that don't exist yet - - Returns: - Sanitized relative path - - Raises: - ValidationError: If path is invalid or unsafe - SecurityError: If path poses security risks - """ - if not path: - raise ValidationError( - message="Path cannot be empty", - field_name="path", - validation_rule="non_empty" - ) - - # Convert to string and normalize - path_str = str(path).strip() - - # Basic security checks - self._check_dangerous_patterns(path_str) - self._check_dangerous_characters(path_str) - - # Normalize path separators - normalized_path = path_str.replace('\\', '/') - - # Remove leading/trailing slashes for relative paths - normalized_path = normalized_path.strip('/') - - # Check for absolute path attempts - if os.path.isabs(path_str) or path_str.startswith('/'): - raise SecurityError( - message="Absolute paths are not allowed - path must be relative to vault", - security_issue="absolute_path_attempt", - attempted_path=path_str - ) - - # Check for directory traversal - if '..' in normalized_path: - raise SecurityError( - message="Directory traversal is not allowed", - security_issue="directory_traversal", - attempted_path=path_str - ) - - # Validate path components - path_parts = normalized_path.split('/') - self._validate_path_components(path_parts) - - # Check path length and depth - self._validate_path_constraints(normalized_path, path_parts) - - # Additional validation if vault root is provided - if vault_root: - self._validate_against_vault_root(normalized_path, vault_root, allow_creation) - - return normalized_path - - def sanitize_absolute_path( - self, - path: Union[str, Path], - allowed_roots: Optional[List[Union[str, Path]]] = None, - must_exist: bool = True - ) -> str: - """ - Sanitize an absolute path with security checks - - Args: - path: Absolute path to sanitize - allowed_roots: List of allowed root directories - must_exist: Whether the path must exist - - Returns: - Sanitized absolute path - - Raises: - ValidationError: If path is invalid - SecurityError: If path poses security risks - """ - if not path: - raise ValidationError( - message="Path cannot be empty", - field_name="path", - validation_rule="non_empty" - ) - - # Convert to Path object for better handling - try: - path_obj = Path(path).resolve() - except (OSError, ValueError) as e: - raise ValidationError( - message=f"Invalid path format: {e}", - field_name="path", - validation_rule="path_format" - ) - - path_str = str(path_obj) - - # Basic security checks - self._check_dangerous_patterns(path_str) - self._check_dangerous_characters(path_str) - - # Check if path exists (if required) - if must_exist and not path_obj.exists(): - raise ValidationError( - message=f"Path does not exist: {path_str}", - field_name="path", - validation_rule="path_exists" - ) - - # Check against allowed roots - if allowed_roots: - self._validate_against_allowed_roots(path_obj, allowed_roots) - - # Check for symbolic link attacks - self._check_symbolic_links(path_obj) - - # Validate path components - path_parts = path_obj.parts - self._validate_path_components(path_parts) - - return path_str - - def sanitize_filename( - self, - filename: str, - allow_extensions: Optional[List[str]] = None, - max_length: Optional[int] = None - ) -> str: - """ - Sanitize a filename with security checks - - Args: - filename: Filename to sanitize - allow_extensions: List of allowed file extensions - max_length: Maximum filename length - - Returns: - Sanitized filename - - Raises: - ValidationError: If filename is invalid - SecurityError: If filename poses security risks - """ - if not filename: - raise ValidationError( - message="Filename cannot be empty", - field_name="filename", - validation_rule="non_empty" - ) - - # Remove path separators (filename only) - clean_filename = filename.replace('/', '').replace('\\', '') - - # Basic security checks - self._check_dangerous_characters(clean_filename) - - # Check for reserved names - name_without_ext = clean_filename.split('.')[0].upper() - if name_without_ext in self.reserved_names: - raise SecurityError( - message=f"Filename uses reserved name: {name_without_ext}", - security_issue="reserved_filename", - attempted_path=filename - ) - - # Check filename length - max_len = max_length or self.max_filename_length - if len(clean_filename) > max_len: - raise ValidationError( - message=f"Filename too long (max {max_len} characters)", - field_name="filename", - validation_rule="max_length" - ) - - # Check file extension if restrictions apply - if allow_extensions: - file_ext = Path(clean_filename).suffix.lower() - if file_ext not in allow_extensions: - raise ValidationError( - message=f"File extension not allowed. Allowed: {', '.join(allow_extensions)}", - field_name="filename", - validation_rule="extension_not_allowed" - ) - - # Check for hidden files (starting with dot) - if clean_filename.startswith('.') and clean_filename != '.obsidian': - self.logger.warning(f"Hidden file detected: {clean_filename}") - - return clean_filename - - def validate_vault_structure(self, vault_path: Union[str, Path]) -> Tuple[bool, List[str]]: - """ - Validate that a directory is a proper Obsidian vault - - Args: - vault_path: Path to validate as vault - - Returns: - Tuple of (is_valid, list_of_issues) - """ - issues = [] - vault_path_obj = Path(vault_path) - - # Check if path exists and is directory - if not vault_path_obj.exists(): - issues.append(f"Vault path does not exist: {vault_path}") - return False, issues - - if not vault_path_obj.is_dir(): - issues.append(f"Vault path is not a directory: {vault_path}") - return False, issues - - # Check for .obsidian directory - obsidian_dir = vault_path_obj / '.obsidian' - if not obsidian_dir.exists(): - issues.append("No .obsidian directory found - this may not be an Obsidian vault") - - # Check permissions - if not os.access(vault_path_obj, os.R_OK): - issues.append(f"Vault directory is not readable: {vault_path}") - - if not os.access(vault_path_obj, os.W_OK): - issues.append(f"Vault directory is not writable: {vault_path}") - - # Check for suspicious files/directories - try: - for item in vault_path_obj.iterdir(): - if item.name.startswith('..'): - issues.append(f"Suspicious directory name found: {item.name}") - - # Check for executable files in vault (potential security risk) - if item.is_file() and item.suffix.lower() in ['.exe', '.bat', '.sh', '.cmd']: - issues.append(f"Executable file found in vault: {item.name}") - except PermissionError: - issues.append("Cannot read vault directory contents - permission denied") - - return len(issues) == 0, issues - - def create_safe_path( - self, - base_path: Union[str, Path], - relative_path: str, - create_dirs: bool = False - ) -> Path: - """ - Safely create a path by joining base and relative paths - - Args: - base_path: Base directory path - relative_path: Relative path to join - create_dirs: Whether to create intermediate directories - - Returns: - Safe combined path - - Raises: - SecurityError: If the resulting path would be unsafe - """ - # Sanitize the relative path first - safe_relative = self.sanitize_vault_relative_path(relative_path, allow_creation=True) - - # Create the combined path - base_path_obj = Path(base_path).resolve() - combined_path = base_path_obj / safe_relative - - # Ensure the result is still within the base path - try: - combined_path.resolve().relative_to(base_path_obj.resolve()) - except ValueError: - raise SecurityError( - message="Resulting path would be outside base directory", - security_issue="path_escape", - attempted_path=str(combined_path) - ) - - # Create directories if requested - if create_dirs and not combined_path.parent.exists(): - try: - combined_path.parent.mkdir(parents=True, exist_ok=True) - self.logger.info(f"Created directory: {combined_path.parent}") - except (OSError, PermissionError) as e: - raise ValidationError( - message=f"Cannot create directory: {e}", - field_name="path", - validation_rule="directory_creation" - ) - - return combined_path - - def _check_dangerous_patterns(self, path: str) -> None: - """Check for dangerous patterns in path""" - for pattern in self.dangerous_patterns: - if re.search(pattern, path, re.IGNORECASE): - raise SecurityError( - message=f"Dangerous pattern detected in path: {pattern}", - security_issue="dangerous_pattern", - attempted_path=path - ) - - def _check_dangerous_characters(self, path: str) -> None: - """Check for dangerous characters in path""" - for char, char_name in self.dangerous_chars.items(): - if char in path: - raise SecurityError( - message=f"Dangerous character '{char}' ({char_name}) found in path", - security_issue="dangerous_character", - attempted_path=path - ) - - def _validate_path_components(self, path_parts: Union[List[str], Tuple[str, ...]]) -> None: - """Validate individual path components""" - for part in path_parts: - if not part: # Empty component - continue - - # Check for reserved names - if part.upper() in self.reserved_names: - raise SecurityError( - message=f"Path component uses reserved name: {part}", - security_issue="reserved_name", - attempted_path=str(path_parts) - ) - - # Check component length - if len(part) > self.max_filename_length: - raise ValidationError( - message=f"Path component too long: {part} (max {self.max_filename_length})", - field_name="path_component", - validation_rule="max_length" - ) - - # Check for control characters - if any(ord(c) < 32 for c in part): - raise SecurityError( - message=f"Path component contains control characters: {part}", - security_issue="control_characters", - attempted_path=part - ) - - def _validate_path_constraints(self, path: str, path_parts: List[str]) -> None: - """Validate path length and depth constraints""" - # Check total path length - if len(path) > self.max_path_length: - raise ValidationError( - message=f"Path too long (max {self.max_path_length} characters)", - field_name="path", - validation_rule="max_path_length" - ) - - # Check path depth - if len(path_parts) > self.max_path_depth: - raise ValidationError( - message=f"Path too deep (max {self.max_path_depth} levels)", - field_name="path", - validation_rule="max_path_depth" - ) - - def _validate_against_vault_root( - self, - relative_path: str, - vault_root: Union[str, Path], - allow_creation: bool - ) -> None: - """Validate path against vault root""" - vault_root_obj = Path(vault_root) - full_path = vault_root_obj / relative_path - - # Ensure path stays within vault - try: - full_path.resolve().relative_to(vault_root_obj.resolve()) - except ValueError: - raise SecurityError( - message="Path would escape vault directory", - security_issue="vault_escape", - attempted_path=relative_path - ) - - # Check if parent directory exists (for file creation) - if not allow_creation and not full_path.parent.exists(): - raise ValidationError( - message=f"Parent directory does not exist: {full_path.parent}", - field_name="path", - validation_rule="parent_directory_exists" - ) - - def _validate_against_allowed_roots( - self, - path_obj: Path, - allowed_roots: List[Union[str, Path]] - ) -> None: - """Validate path is within allowed root directories""" - path_resolved = path_obj.resolve() - - for allowed_root in allowed_roots: - try: - allowed_root_obj = Path(allowed_root).resolve() - path_resolved.relative_to(allowed_root_obj) - return # Path is within this allowed root - except ValueError: - continue # Try next allowed root - - # Path is not within any allowed root - raise SecurityError( - message=f"Path is not within any allowed root directory", - security_issue="unauthorized_path", - attempted_path=str(path_obj) - ) - - def _check_symbolic_links(self, path_obj: Path) -> None: - """Check for symbolic link attacks""" - # Check if any part of the path is a symbolic link - current_path = path_obj - while current_path != current_path.parent: - if current_path.is_symlink(): - self.logger.warning(f"Symbolic link detected in path: {current_path}") - # Don't reject, but log for security monitoring - break - current_path = current_path.parent - - -class PathSecurityManager: - """High-level path security management""" - - def __init__(self, vault_root: Optional[Union[str, Path]] = None): - self.vault_root = Path(vault_root) if vault_root else None - self.sanitizer = PathSanitizer() - self.logger = logging.getLogger(__name__) - - def set_vault_root(self, vault_root: Union[str, Path]) -> None: - """Set the vault root directory""" - self.vault_root = Path(vault_root) - - # Validate vault structure - is_valid, issues = self.sanitizer.validate_vault_structure(self.vault_root) - if not is_valid: - self.logger.warning(f"Vault validation issues: {'; '.join(issues)}") - - def get_safe_vault_path(self, relative_path: str, create_dirs: bool = False) -> Path: - """ - Get a safe path within the vault - - Args: - relative_path: Relative path within vault - create_dirs: Whether to create intermediate directories - - Returns: - Safe absolute path within vault - - Raises: - ValidationError: If vault root not set or path invalid - """ - if not self.vault_root: - raise ValidationError( - message="Vault root not configured", - field_name="vault_root", - validation_rule="not_configured" - ) - - return self.sanitizer.create_safe_path( - self.vault_root, - relative_path, - create_dirs=create_dirs - ) - - def validate_file_operation( - self, - file_path: str, - operation: str = "read", - content_length: Optional[int] = None - ) -> Tuple[bool, Optional[str]]: - """ - Validate a file operation for security - - Args: - file_path: Path to file - operation: Type of operation (read, write, append, delete) - content_length: Length of content for write operations - - Returns: - Tuple of (is_allowed, reason_if_not_allowed) - """ - try: - # Sanitize the path - safe_path = self.sanitizer.sanitize_vault_relative_path( - file_path, - self.vault_root, - allow_creation=(operation in ['write', 'append']) - ) - - # Additional checks based on operation - if operation == 'write' and content_length: - # Check for reasonable content size limits - max_content_size = 10 * 1024 * 1024 # 10MB - if content_length > max_content_size: - return False, f"Content too large ({content_length} bytes, max {max_content_size})" - - # Check file extension for security - file_ext = Path(safe_path).suffix.lower() - dangerous_extensions = ['.exe', '.bat', '.sh', '.cmd', '.scr', '.vbs', '.js'] - if file_ext in dangerous_extensions: - return False, f"Dangerous file extension: {file_ext}" - - return True, None - - except (ValidationError, SecurityError) as e: - return False, str(e) - - -# Global instances -path_sanitizer = PathSanitizer() -path_security_manager = PathSecurityManager() \ No newline at end of file diff --git a/./pytest.ini b/./pytest.ini deleted file mode 100644 index 86c83b9..0000000 --- a/./pytest.ini +++ /dev/null @@ -1,20 +0,0 @@ -[tool:pytest] -testpaths = tests -python_files = test_*.py *_test.py -python_classes = Test* -python_functions = test_* -addopts = - --verbose - --tb=short - --cov=. - --cov-report=term-missing - --cov-report=html:htmlcov - --cov-exclude=tests/* - --cov-exclude=__pycache__/* - --cov-exclude=.kiro/* -asyncio_mode = auto -markers = - unit: Unit tests - integration: Integration tests - property: Property-based tests - slow: Slow running tests \ No newline at end of file diff --git a/./requirements.txt b/./requirements.txt deleted file mode 100644 index 382460d..0000000 --- a/./requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Obsidian 智能日记整理 Agent 依赖 - -# 核心依赖 -anthropic>=0.25.0 -aiohttp>=3.9.0 -pyyaml>=6.0 -pydantic>=2.0.0 - -# 测试依赖 -pytest>=7.0.0 -pytest-asyncio>=0.21.0 -pytest-cov>=4.0.0 -hypothesis>=6.0.0 -aioresponses>=0.7.0 diff --git a/./run_tests.py b/./run_tests.py deleted file mode 100644 index 36bc3e5..0000000 --- a/./run_tests.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -""" -Test runner script for the journal organizer project. -Provides easy access to run different types of tests. -""" - -import sys -import subprocess -from pathlib import Path - - -def run_command(cmd, description): - """Run a command and handle the result""" - print(f"\n{'='*60}") - print(f"Running: {description}") - print(f"Command: {' '.join(cmd)}") - print('='*60) - - try: - result = subprocess.run(cmd, check=True, capture_output=False) - print(f"\n✅ {description} - PASSED") - return True - except subprocess.CalledProcessError as e: - print(f"\n❌ {description} - FAILED (exit code: {e.returncode})") - return False - - -def main(): - """Main test runner""" - if len(sys.argv) < 2: - print("Usage: python run_tests.py [unit|integration|all|coverage]") - print("\nOptions:") - print(" unit - Run unit tests only") - print(" integration - Run integration tests only (may have import issues)") - print(" all - Run all tests") - print(" coverage - Run tests with coverage report") - print(" help - Show this help message") - return - - test_type = sys.argv[1].lower() - - if test_type == "help": - main() - return - - # Base pytest command - base_cmd = ["python", "-m", "pytest", "-v"] - - success = True - - if test_type == "unit": - cmd = base_cmd + ["tests/unit/"] - success = run_command(cmd, "Unit Tests") - - elif test_type == "integration": - cmd = base_cmd + ["tests/integration/"] - success = run_command(cmd, "Integration Tests") - - elif test_type == "all": - # Run unit tests first - cmd = base_cmd + ["tests/unit/"] - success = run_command(cmd, "Unit Tests") - - if success: - # Run integration tests - cmd = base_cmd + ["tests/integration/"] - success = run_command(cmd, "Integration Tests") and success - - elif test_type == "coverage": - cmd = base_cmd + ["--cov=.", "--cov-report=term-missing", "--cov-report=html:htmlcov", "tests/unit/"] - success = run_command(cmd, "Unit Tests with Coverage") - - if success: - print(f"\n📊 Coverage report generated in htmlcov/index.html") - - else: - print(f"Unknown test type: {test_type}") - print("Use 'python run_tests.py help' for usage information") - return - - # Summary - print(f"\n{'='*60}") - if success: - print("🎉 All tests completed successfully!") - else: - print("💥 Some tests failed. Check the output above for details.") - print('='*60) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/./server.py b/./server.py deleted file mode 100644 index b42f35c..0000000 --- a/./server.py +++ /dev/null @@ -1,260 +0,0 @@ -""" -Journal Organizer Agent HTTP 服务器 - -提供 REST API 接口,供 Obsidian 插件调用 -""" - -import json -import logging -from datetime import datetime -from typing import Dict, List, Any -from flask import Flask, request, jsonify -from flask_cors import CORS -from .conversation.conversational_agent import ConversationalAgent -from .config import Config - -# 配置日志 -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - -# 创建 Flask 应用 -app = Flask(__name__) -CORS(app) # 启用 CORS 支持 - -# 初始化 Agent -config = Config() -agent = ConversationalAgent(config) - -# 存储对话历史 -conversations: Dict[str, List[Dict[str, Any]]] = {} - - -@app.route("/health", methods=["GET"]) -def health_check(): - """ - 健康检查端点 - - Returns: - JSON: { "status": "ok", "timestamp": "ISO 8601 时间戳" } - """ - return jsonify( - {"status": "ok", "timestamp": datetime.now().isoformat(), "agent": "ready"} - ) - - -@app.route("/api/chat", methods=["POST"]) -def chat(): - """ - 对话端点 - - Request JSON: - { - "message": "用户消息", - "conversation_id": "对话 ID", - "timestamp": "ISO 8601 时间戳" - } - - Response JSON: - { - "message": "Agent 响应", - "suggestions": ["建议1", "建议2"], - "status": "success|error|waiting_input", - "conversation_id": "对话 ID", - "timestamp": "ISO 8601 时间戳" - } - """ - try: - data = request.get_json() - - if not data or "message" not in data: - return jsonify({"message": "错误:缺少 'message' 字段", "status": "error"}), 400 - - user_message = data["message"] - conversation_id = data.get("conversation_id", "default") - - logger.info(f"[{conversation_id}] 用户消息: {user_message}") - - # 初始化对话历史 - if conversation_id not in conversations: - conversations[conversation_id] = [] - - # 添加用户消息到历史 - conversations[conversation_id].append( - { - "role": "user", - "content": user_message, - "timestamp": datetime.now().isoformat(), - } - ) - - # 调用 Agent 处理消息 - response = agent.process_message( - user_message, conversation_id, conversations[conversation_id] - ) - - # 添加 Agent 响应到历史 - conversations[conversation_id].append( - { - "role": "assistant", - "content": response.get("message", ""), - "timestamp": datetime.now().isoformat(), - } - ) - - logger.info(f"[{conversation_id}] Agent 响应: {response['message'][:100]}...") - - return jsonify( - { - "message": response.get("message", ""), - "suggestions": response.get("suggestions", []), - "status": response.get("status", "success"), - "conversation_id": conversation_id, - "timestamp": datetime.now().isoformat(), - } - ) - - except Exception as e: - logger.error(f"Chat 端点错误: {e}", exc_info=True) - return jsonify({"message": f"服务器错误: {str(e)}", "status": "error"}), 500 - - -@app.route("/api/history/", methods=["GET"]) -def get_history(conversation_id: str): - """ - 获取对话历史 - - Args: - conversation_id: 对话 ID - - Response JSON: - { - "messages": [ - { "role": "user", "content": "...", "timestamp": "..." }, - { "role": "assistant", "content": "...", "timestamp": "..." } - ], - "conversation_id": "对话 ID" - } - """ - try: - messages = conversations.get(conversation_id, []) - - return jsonify( - { - "messages": messages, - "conversation_id": conversation_id, - "count": len(messages), - } - ) - - except Exception as e: - logger.error(f"获取历史错误: {e}") - return jsonify({"message": f"错误: {str(e)}", "status": "error"}), 500 - - -@app.route("/api/history/", methods=["DELETE"]) -def clear_history(conversation_id: str): - """ - 清除对话历史 - - Args: - conversation_id: 对话 ID - - Response JSON: - { "status": "success", "conversation_id": "对话 ID" } - """ - try: - if conversation_id in conversations: - del conversations[conversation_id] - - logger.info(f"已清除对话历史: {conversation_id}") - - return jsonify({"status": "success", "conversation_id": conversation_id}) - - except Exception as e: - logger.error(f"清除历史错误: {e}") - return jsonify({"message": f"错误: {str(e)}", "status": "error"}), 500 - - -@app.route("/api/suggestions/", methods=["GET"]) -def get_suggestions(conversation_id: str): - """ - 获取建议 - - Args: - conversation_id: 对话 ID - - Response JSON: - { - "suggestions": ["建议1", "建议2"], - "conversation_id": "对话 ID" - } - """ - try: - history = conversations.get(conversation_id, []) - - # 调用 Agent 生成建议 - suggestions = agent.generate_suggestions(history) - - return jsonify({"suggestions": suggestions, "conversation_id": conversation_id}) - - except Exception as e: - logger.error(f"获取建议错误: {e}") - return jsonify({"suggestions": [], "status": "error"}), 500 - - -@app.route("/api/status", methods=["GET"]) -def get_status(): - """ - 获取 Agent 状态 - - Response JSON: - { - "status": "ready", - "conversations": 对话数量, - "uptime": "运行时间", - "version": "版本号" - } - """ - return jsonify( - { - "status": "ready", - "conversations": len(conversations), - "timestamp": datetime.now().isoformat(), - "version": "1.0.0", - } - ) - - -@app.errorhandler(404) -def not_found(error): - """处理 404 错误""" - return jsonify({"message": "端点不存在", "status": "error"}), 404 - - -@app.errorhandler(500) -def internal_error(error): - """处理 500 错误""" - logger.error(f"内部服务器错误: {error}") - return jsonify({"message": "内部服务器错误", "status": "error"}), 500 - - -def run_server(host: str = "0.0.0.0", port: int = 5000, debug: bool = False): - """ - 启动 HTTP 服务器 - - Args: - host: 绑定的主机地址 - port: 绑定的端口 - debug: 是否启用调试模式 - """ - logger.info(f"启动 Journal Organizer Agent 服务器...") - logger.info(f"监听地址: {host}:{port}") - logger.info(f"调试模式: {debug}") - - app.run(host=host, port=port, debug=debug, threaded=True) - - -if __name__ == "__main__": - run_server(debug=True) diff --git a/./skills/__init__.py b/./skills/__init__.py deleted file mode 100644 index eed008e..0000000 --- a/./skills/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -Skills 模块 -""" - -from .obsidian_skill import ( - ObsidianReadSkill, - ObsidianWriteSkill, - ObsidianAppendSkill, - ObsidianListFilesSkill, -) -from .claude_skill import ( - ClaudeAnalyzeSkill, - ClaudeTransformSkill, -) - -__all__ = [ - "ObsidianReadSkill", - "ObsidianWriteSkill", - "ObsidianAppendSkill", - "ObsidianListFilesSkill", - "ClaudeAnalyzeSkill", - "ClaudeTransformSkill", -] diff --git a/./skills/__pycache__/__init__.cpython-310.pyc b/./skills/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 7d84cd2d20604e2c7a462468e7e37b1c5d9d2538..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 483 zcmZvYy-EZz6ooVY(PYzll!2qOTxnkOr!42bFZ+|{NemvYIUuGUi{c7#> zN;AT;{G4W_wp6`Vx>T&73vIpC4yRL^H^$9YJZ8G+Gfs7FPTi_^#Iusn!#v}&c@wd* z(1t3@^|e%D;SH5tQh64>MPLzHLBq=%Fx1XwG10QVpt6?Yj0pHVz8UO$Q diff --git a/./skills/__pycache__/__init__.cpython-311.pyc b/./skills/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 034f72ed25667cb3deffdcc6d465f9848509138e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 593 zcmbV|KT88K7{-(P(>u?$Y87YKTRdDvgd#YJf-MMH!nKJtn%tG#-Ox^seuFML=%Rih zhg(I#?VwvHlk1(6Aeg|9Sa`i`Mx^L009UPNDK~z zfec|HQ&`9nHnO4CP2La=a)gUq2u6TdPY|sTy9fM6wx$BS-VNhC+4+2V{CJpoU#c7M zt$gEbkTAl+=$wY6wB(&yx{xfTOKr2(y5lj8D&ux7p0Ff6W}GIa**Ege5f3v$yHUs| z(<)+Xsr6+TC6}=jOK(G(Y7l8@*cuHDN5fS(^}XD|n8ZVj^LAX#iWB{o-zxLwuncJ2Uvj5Vm=MCkFrZZc4cCT70se=kscZj+=d3DUjax@=*;d1R1N&W_SO5S3 diff --git a/./skills/__pycache__/claude_skill.cpython-310.pyc b/./skills/__pycache__/claude_skill.cpython-310.pyc deleted file mode 100644 index 2458c27830928d35ed4f7d9729be4f03bb36e543..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10818 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 N*R?J3k7X_L{{eSKEGGZ} diff --git a/./skills/__pycache__/obsidian_skill.cpython-310.pyc b/./skills/__pycache__/obsidian_skill.cpython-310.pyc deleted file mode 100644 index c8da781411176db48cf36d5b98a95595c9cff9de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11935 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 Nt#_=~?R{#%{U0y4-6jA4 diff --git a/./skills/__pycache__/obsidian_skill.cpython-311.pyc b/./skills/__pycache__/obsidian_skill.cpython-311.pyc deleted file mode 100644 index a523b07b1031f7a7dbae559a82e58746a42a2f8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19125 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 Ih9nL0|54_!5&!@I diff --git a/./skills/claude_skill.py b/./skills/claude_skill.py deleted file mode 100644 index 8edde00..0000000 --- a/./skills/claude_skill.py +++ /dev/null @@ -1,467 +0,0 @@ -""" -Claude AI 集成 Skill -负责与 Claude API 的交互和内容分析 -Enhanced with configurable API URL support -""" - -import json -import logging -from datetime import datetime -from typing import Dict, Any, Optional, List, Union - -try: - from ..dependency_manager import get_dependency_manager - from ..claude_api_client import ClaudeAPIClient - from ..config_validation import ClaudeAPIConfig - - # Try to import anthropic with graceful degradation - dependency_manager = get_dependency_manager() - Anthropic = dependency_manager.get_class_from_module('anthropic', 'Anthropic') - AsyncAnthropic = dependency_manager.get_class_from_module('anthropic', 'AsyncAnthropic') -except ImportError: - # Fallback for backward compatibility - try: - from anthropic import Anthropic, AsyncAnthropic - from claude_api_client import ClaudeAPIClient - from config_validation import ClaudeAPIConfig - except ImportError: - AsyncAnthropic = None - Anthropic = None - ClaudeAPIClient = None - ClaudeAPIConfig = None - -from ..agent_core import Skill, SkillType, SkillResult, CommandContext -from ..api_response_validation import validate_api_response -from ..error_handling import ( - ErrorHandler, - APIError, - ConfigurationError, - ValidationError, - ErrorContext, - get_error_handler, -) -from ..input_validation import command_input_validator - - -class ClaudeAnalyzeSkill(Skill): - """Claude 日记分析 Skill""" - - def __init__(self) -> None: - super().__init__( - name="claude_analyze", - skill_type=SkillType.ANALYZE, - description="使用 Claude 分析日记内容并提取关键信息", - ) - self.client: Optional[ClaudeAPIClient] = None - self.error_handler = get_error_handler() - - def _get_analysis_prompt(self, categories: List[str]) -> str: - """ - 获取分析 prompt - - Args: - categories: 分类列表 - - Returns: - prompt 字符串 - """ - categories_str: str = "、".join(categories) if categories else "技术学习、项目管理、个人成长" - - return f"""你是一个专业的日记分析助手。请分析以下日记内容,并按照指定的格式提取关键信息。 - -分析要求: -1. 提取经验和见解(Experiences):日记中提到的重要经验、发现或见解 -2. 提取学到的知识(Lessons Learned):具体学到的知识点、最佳实践或原则 -3. 提取待办事项(Action Items):需要采取行动的任务或改进项 -4. 提取问题和挑战(Problems):遇到的问题、挑战或障碍 -5. 提取成就和进展(Achievements):完成的工作、达成的目标或进展 -6. 提取改进建议(Improvements):可以改进的方向或优化建议 - -分类类别:{categories_str} - -请以 JSON 格式返回结果,结构如下: -{{ - "experiences": [ - {{ - "title": "标题", - "content": "详细内容", - "category": "分类", - "priority": "high/medium/low" - }} - ], - "lessons_learned": [ - {{ - "lesson": "学到的内容", - "context": "背景信息", - "application": "如何应用" - }} - ], - "action_items": [ - {{ - "task": "任务描述", - "priority": "high/medium/low", - "deadline": "建议截止日期", - "status": "new" - }} - ], - "problems": [ - {{ - "problem": "问题描述", - "impact": "影响程度", - "proposed_solution": "建议方案" - }} - ], - "achievements": [ - {{ - "achievement": "成就描述", - "significance": "重要性", - "evidence": "证据或细节" - }} - ], - "improvements": [ - {{ - "area": "改进领域", - "current_state": "当前状态", - "suggested_change": "建议改进", - "expected_benefit": "预期收益" - }} - ], - "summary": "日记的总体总结" -}} - -日记内容: -""" - - async def execute(self, context: CommandContext, **kwargs: Any) -> SkillResult: - """ - 分析日记内容 - - Args: - context: 命令执行上下文 - **kwargs: 包含以下参数 - - journal_content: 日记内容 - - api_key: Claude API 密钥 - - model: 模型名称(默认 claude-3-5-sonnet-20241022) - - categories: 分类列表(可选) - - Returns: - SkillResult: 包含分析结果的结果 - """ - try: - return await self._execute_analyze(context, **kwargs) - except (ValidationError, ConfigurationError, APIError) as e: - error_context = ErrorContext( - component="claude_analyze", - operation="analyze_journal", - user_message="Failed to analyze journal content with Claude AI", - technical_details=kwargs, - ) - error_response = self.error_handler.handle_error(e, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - except Exception as e: - api_error = APIError( - message=f"Unexpected error analyzing journal: {str(e)}", - api_name="claude", - cause=e, - ) - error_context = ErrorContext( - component="claude_analyze", - operation="analyze_journal", - user_message="An unexpected error occurred while analyzing the journal", - ) - error_response = self.error_handler.handle_error(api_error, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - - async def _execute_analyze( - self, context: CommandContext, **kwargs: Any - ) -> SkillResult: - """Internal method that performs the actual analysis""" - # Validate input parameters - validated_kwargs = command_input_validator.validate_skill_input( - 'claude_analyze', kwargs - ) - - journal_content: str = validated_kwargs["journal_content"] - - # Handle both legacy and new configuration formats - if 'claude_config' in validated_kwargs: - # New format: ClaudeAPIConfig object - claude_config = validated_kwargs['claude_config'] - if not isinstance(claude_config, ClaudeAPIConfig): - raise ConfigurationError( - message="claude_config must be a ClaudeAPIConfig instance", - config_key="claude_config" - ) - else: - # Legacy format: individual parameters - api_key: str = validated_kwargs["api_key"] - model: str = validated_kwargs.get("model", "claude-3-5-sonnet-20241022") - api_url: str = validated_kwargs.get("api_url", "https://api.anthropic.com") - max_tokens: int = validated_kwargs.get("max_tokens", 4096) - temperature: float = validated_kwargs.get("temperature", 0.7) - - # Create ClaudeAPIConfig from legacy parameters - claude_config = ClaudeAPIConfig( - api_key=api_key, - model=model, - api_url=api_url, - max_tokens=max_tokens, - temperature=temperature - ) - - categories: List[str] = validated_kwargs.get("categories", []) - - if not ClaudeAPIClient: - raise ConfigurationError( - message="ClaudeAPIClient is not available. Please check your installation.", - config_key="claude_api_client", - ) - - # Initialize enhanced client - try: - client = ClaudeAPIClient(claude_config) - except Exception as e: - raise ConfigurationError( - message=f"Failed to initialize Claude API client: {str(e)}", - config_key="claude_client_init", - cause=e - ) - - # Construct prompt - system_prompt: str = self._get_analysis_prompt(categories) - user_message: str = journal_content - - self.logger.info(f"开始分析日记,模型: {claude_config.model}, API URL: {claude_config.api_url}") - - try: - # Call Claude API using enhanced client - message = await client.create_message( - messages=[ - {"role": "user", "content": f"{system_prompt}{user_message}"} - ] - ) - except APIError: - # Re-raise APIError as-is (already properly formatted) - raise - except Exception as e: - raise APIError( - message=f"Claude API call failed: {str(e)}", - api_name="claude", - cause=e - ) - - # Validate API response - validated_response = validate_api_response( - message.model_dump() if hasattr(message, 'model_dump') else message.__dict__, - "claude", - "analyze" - ) - - # Parse response - response_text: str = message.content[0].text - - # Try to extract JSON - try: - # Find JSON block - json_start: int = response_text.find("{") - json_end: int = response_text.rfind("}") + 1 - - if json_start >= 0 and json_end > json_start: - json_str: str = response_text[json_start:json_end] - analysis_result: Dict[str, Any] = json.loads(json_str) - else: - # If no JSON found, return raw text - analysis_result = { - "raw_response": response_text, - "parse_error": "无法解析 JSON 格式", - } - except json.JSONDecodeError as e: - self.logger.warning(f"JSON 解析失败: {str(e)}") - analysis_result = {"raw_response": response_text, "parse_error": str(e)} - - return SkillResult( - success=True, - data={ - "analysis": analysis_result, - "model": claude_config.model, - "api_url": claude_config.api_url, - "analyzed_at": datetime.now().isoformat(), - "journal_length": len(journal_content), - "api_response": validated_response, - "client_info": client.get_client_info(), - }, - message="成功分析日记内容", - ) - - -class ClaudeTransformSkill(Skill): - """Claude 内容转换 Skill""" - - def __init__(self) -> None: - super().__init__( - name="claude_transform", - skill_type=SkillType.TRANSFORM, - description="使用 Claude 转换和格式化内容", - ) - self.error_handler = get_error_handler() - - async def execute(self, context: CommandContext, **kwargs: Any) -> SkillResult: - """ - 转换内容格式 - - Args: - context: 命令执行上下文 - **kwargs: 包含以下参数 - - content: 要转换的内容 - - transform_type: 转换类型(markdown, html, summary 等) - - api_key: Claude API 密钥 - - model: 模型名称(可选) - - Returns: - SkillResult: 包含转换结果的结果 - """ - try: - return await self._execute_transform(context, **kwargs) - except (ValidationError, ConfigurationError, APIError) as e: - error_context = ErrorContext( - component="claude_transform", - operation="transform_content", - user_message="Failed to transform content with Claude AI", - technical_details=kwargs, - ) - error_response = self.error_handler.handle_error(e, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - except Exception as e: - api_error = APIError( - message=f"Unexpected error transforming content: {str(e)}", - api_name="claude", - cause=e, - ) - error_context = ErrorContext( - component="claude_transform", - operation="transform_content", - user_message="An unexpected error occurred while transforming content", - ) - error_response = self.error_handler.handle_error(api_error, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - - async def _execute_transform( - self, context: CommandContext, **kwargs: Any - ) -> SkillResult: - """Internal method that performs the actual transformation""" - content: Optional[str] = kwargs.get("content") - transform_type: str = kwargs.get("transform_type", "markdown") - - if not content: - raise ValidationError( - message="Content is required for transformation", - field_name="content", - validation_rule="non_empty", - ) - - # Handle both legacy and new configuration formats - if 'claude_config' in kwargs: - # New format: ClaudeAPIConfig object - claude_config = kwargs['claude_config'] - if not isinstance(claude_config, ClaudeAPIConfig): - raise ConfigurationError( - message="claude_config must be a ClaudeAPIConfig instance", - config_key="claude_config" - ) - else: - # Legacy format: individual parameters - api_key: Optional[str] = kwargs.get("api_key") - model: str = kwargs.get("model", "claude-3-5-sonnet-20241022") - api_url: str = kwargs.get("api_url", "https://api.anthropic.com") - max_tokens: int = kwargs.get("max_tokens", 4096) - temperature: float = kwargs.get("temperature", 0.7) - - if not api_key: - raise ConfigurationError( - message="Claude API key is required", config_key="api_key" - ) - - # Create ClaudeAPIConfig from legacy parameters - claude_config = ClaudeAPIConfig( - api_key=api_key, - model=model, - api_url=api_url, - max_tokens=max_tokens, - temperature=temperature - ) - - if not ClaudeAPIClient: - raise ConfigurationError( - message="ClaudeAPIClient is not available. Please check your installation.", - config_key="claude_api_client", - ) - - # Initialize enhanced client - try: - client = ClaudeAPIClient(claude_config) - except Exception as e: - raise ConfigurationError( - message=f"Failed to initialize Claude API client: {str(e)}", - config_key="claude_client_init", - cause=e - ) - - # Build prompt based on transformation type - prompts: Dict[str, str] = { - "markdown": "请将以下内容转换为格式良好的 Markdown 格式:", - "html": "请将以下内容转换为 HTML 格式:", - "summary": "请为以下内容生成一个简洁的总结:", - "outline": "请为以下内容生成一个结构化的大纲:", - "checklist": "请将以下内容转换为检查清单格式:", - } - - system_prompt: str = prompts.get(transform_type, "请转换以下内容:") - - self.logger.info(f"转换内容,类型: {transform_type}, 模型: {claude_config.model}") - - try: - message = await client.create_message( - messages=[{"role": "user", "content": f"{system_prompt}\n\n{content}"}] - ) - except APIError: - # Re-raise APIError as-is (already properly formatted) - raise - except Exception as e: - raise APIError( - message=f"Claude API call failed: {str(e)}", - api_name="claude", - cause=e - ) - - transformed_content: str = message.content[0].text - - return SkillResult( - success=True, - data={ - "original_length": len(content), - "transformed_length": len(transformed_content), - "transform_type": transform_type, - "transformed_content": transformed_content, - "transformed_at": datetime.now().isoformat(), - "model": claude_config.model, - "api_url": claude_config.api_url, - "client_info": client.get_client_info(), - }, - message=f"成功转换内容为 {transform_type} 格式", - ) diff --git a/./skills/obsidian_skill.py b/./skills/obsidian_skill.py deleted file mode 100644 index 743bbd5..0000000 --- a/./skills/obsidian_skill.py +++ /dev/null @@ -1,517 +0,0 @@ -""" -Obsidian 集成 Skill -负责与 Obsidian Local REST API 的交互 -""" - -import ssl -from contextlib import asynccontextmanager -from datetime import datetime -from typing import Dict, Any, Optional, List, Union, AsyncGenerator - -from ..agent_core import Skill, SkillType, SkillResult, CommandContext -from ..api_response_validation import validate_api_response -from ..dependency_manager import get_dependency_manager -from ..error_handling import ( - APIError, - ConfigurationError, - ValidationError, - ErrorContext, - get_error_handler, -) -from ..input_validation import command_input_validator - -# Try to import aiohttp with graceful degradation -dependency_manager = get_dependency_manager() -aiohttp = dependency_manager.get_module('aiohttp') - - -@asynccontextmanager -async def obsidian_api_client( - api_url: str, api_key: str -) -> AsyncGenerator[Any, None]: - """ - Async context manager for Obsidian API client - - Args: - api_url: Obsidian API URL - api_key: API key for authentication - - Yields: - Configured aiohttp ClientSession - - Raises: - ConfigurationError: If aiohttp is not available - """ - if aiohttp is None: - raise ConfigurationError( - message="aiohttp library is not installed. Please install it with: pip install aiohttp>=3.9.0", - config_key="aiohttp_dependency", - ) - - # Create SSL context (skip certificate verification for local development) - ssl_context = ssl.create_default_context() - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - - # Configure headers - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - - # Create session with proper configuration - connector = aiohttp.TCPConnector(ssl=ssl_context) - async with aiohttp.ClientSession(connector=connector, headers=headers) as session: - try: - yield session - except Exception as e: - # Log error but let it propagate - import logging - - logger = logging.getLogger("obsidian_api_client") - logger.error(f"Error in Obsidian API client: {str(e)}") - raise - - -class ObsidianReadSkill(Skill): - """读取 Obsidian 笔记 Skill""" - - def __init__(self) -> None: - super().__init__( - name="obsidian_read", - skill_type=SkillType.READ, - description="从 Obsidian 读取笔记内容", - ) - self.error_handler = get_error_handler() - - async def execute(self, context: CommandContext, **kwargs: Any) -> SkillResult: - """ - 读取 Obsidian 笔记 - - Args: - context: 命令执行上下文 - **kwargs: 包含以下参数 - - file_path: 笔记文件路径(相对于 vault) - - vault_path: vault 路径 - - api_url: API URL - - api_key: API 密钥 - - Returns: - SkillResult: 包含笔记内容的结果 - """ - try: - return await self._execute_read(context, **kwargs) - except (ValidationError, ConfigurationError, APIError) as e: - error_context = ErrorContext( - component="obsidian_read", - operation="read_note", - user_message="Failed to read note from Obsidian", - technical_details=kwargs, - ) - error_response = self.error_handler.handle_error(e, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - except Exception as e: - # Handle any unexpected errors - api_error = APIError( - message=f"Unexpected error reading note: {str(e)}", - api_name="obsidian", - cause=e, - ) - error_context = ErrorContext( - component="obsidian_read", - operation="read_note", - user_message="An unexpected error occurred while reading the note", - ) - error_response = self.error_handler.handle_error(api_error, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - - async def _execute_read( - self, context: CommandContext, **kwargs: Any - ) -> SkillResult: - """Internal method that performs the actual read operation""" - # Validate input parameters - validated_kwargs = command_input_validator.validate_skill_input( - 'obsidian_read', kwargs - ) - - file_path: str = validated_kwargs["file_path"] - api_url: str = validated_kwargs.get("api_url", "https://localhost:27123") - api_key: str = validated_kwargs["api_key"] - - # Use async context manager for API client - async with obsidian_api_client(api_url, api_key) as session: - # Build API URL - api_endpoint: str = f"{api_url}/vault/{file_path}" - - self.logger.info(f"读取笔记: {file_path}") - - async with session.get(api_endpoint) as response: - if response.status == 200: - content: str = await response.text() - - # Validate API response - validated_response = validate_api_response( - content, "obsidian", "read" - ) - - return SkillResult( - success=True, - data={ - "file_path": file_path, - "content": validated_response["content"], - "size": validated_response["length"], - "read_at": datetime.now().isoformat(), - }, - message=f"成功读取笔记: {file_path}", - ) - elif response.status == 404: - raise APIError( - message=f"Note file not found: {file_path}", - api_name="obsidian", - status_code=response.status, - ) - else: - error_text: str = await response.text() - raise APIError( - message=f"Failed to read note: {error_text}", - api_name="obsidian", - status_code=response.status, - response_data=error_text, - ) - - -class ObsidianWriteSkill(Skill): - """写入 Obsidian 笔记 Skill""" - - def __init__(self) -> None: - super().__init__( - name="obsidian_write", - skill_type=SkillType.WRITE, - description="向 Obsidian 写入或更新笔记", - ) - self.error_handler = get_error_handler() - - async def execute(self, context: CommandContext, **kwargs: Any) -> SkillResult: - """ - 写入或创建 Obsidian 笔记 - - Args: - context: 命令执行上下文 - **kwargs: 包含以下参数 - - file_path: 笔记文件路径(相对于 vault) - - content: 要写入的内容 - - overwrite: 是否覆盖现有内容(默认 False) - - api_url: API URL - - api_key: API 密钥 - - Returns: - SkillResult: 执行结果 - """ - try: - return await self._execute_write(context, **kwargs) - except (ValidationError, ConfigurationError, APIError) as e: - error_context = ErrorContext( - component="obsidian_write", - operation="write_note", - user_message="Failed to write note to Obsidian", - technical_details=kwargs, - ) - error_response = self.error_handler.handle_error(e, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - except Exception as e: - api_error = APIError( - message=f"Unexpected error writing note: {str(e)}", - api_name="obsidian", - cause=e, - ) - error_context = ErrorContext( - component="obsidian_write", - operation="write_note", - user_message="An unexpected error occurred while writing the note", - ) - error_response = self.error_handler.handle_error(api_error, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - - async def _execute_write( - self, context: CommandContext, **kwargs: Any - ) -> SkillResult: - """Internal method that performs the actual write operation""" - # Validate input parameters - validated_kwargs = command_input_validator.validate_skill_input( - 'obsidian_write', kwargs - ) - - file_path: str = validated_kwargs["file_path"] - content: str = validated_kwargs["content"] - overwrite: bool = validated_kwargs.get("overwrite", False) - api_url: str = validated_kwargs.get("api_url", "https://localhost:27123") - api_key: str = validated_kwargs["api_key"] - - async with obsidian_api_client(api_url, api_key) as session: - api_endpoint: str = f"{api_url}/vault/{file_path}" - - payload: Dict[str, Union[str, bool]] = { - "content": content, - "overwrite": overwrite, - } - - self.logger.info(f"写入笔记: {file_path}") - - async with session.post(api_endpoint, json=payload) as response: - if response.status in [200, 201]: - # Validate API response - response_text = await response.text() - validated_response = validate_api_response( - response_text, "obsidian", "write" - ) - - return SkillResult( - success=True, - data={ - "file_path": file_path, - "size": len(content) if content else 0, - "written_at": datetime.now().isoformat(), - "response": validated_response, - }, - message=f"成功写入笔记: {file_path}", - ) - else: - error_text: str = await response.text() - raise APIError( - message=f"Failed to write note: {error_text}", - api_name="obsidian", - status_code=response.status, - response_data=error_text, - ) - - -class ObsidianAppendSkill(Skill): - """追加内容到 Obsidian 笔记 Skill""" - - def __init__(self) -> None: - super().__init__( - name="obsidian_append", - skill_type=SkillType.WRITE, - description="向 Obsidian 笔记追加内容", - ) - self.error_handler = get_error_handler() - - async def execute(self, context: CommandContext, **kwargs: Any) -> SkillResult: - """ - 向笔记追加内容 - - Args: - context: 命令执行上下文 - **kwargs: 包含以下参数 - - file_path: 笔记文件路径 - - content: 要追加的内容 - - api_url: API URL - - api_key: API 密钥 - - Returns: - SkillResult: 执行结果 - """ - try: - return await self._execute_append(context, **kwargs) - except (ValidationError, ConfigurationError, APIError) as e: - error_context = ErrorContext( - component="obsidian_append", - operation="append_note", - user_message="Failed to append content to Obsidian note", - technical_details=kwargs, - ) - error_response = self.error_handler.handle_error(e, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - except Exception as e: - api_error = APIError( - message=f"Unexpected error appending to note: {str(e)}", - api_name="obsidian", - cause=e, - ) - error_context = ErrorContext( - component="obsidian_append", - operation="append_note", - user_message="An unexpected error occurred while appending to the note", - ) - error_response = self.error_handler.handle_error(api_error, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - - async def _execute_append( - self, context: CommandContext, **kwargs: Any - ) -> SkillResult: - """Internal method that performs the actual append operation""" - file_path: Optional[str] = kwargs.get("file_path") - content: Optional[str] = kwargs.get("content") - api_url: str = kwargs.get("api_url", "https://localhost:27123") - api_key: Optional[str] = kwargs.get("api_key") - - if not file_path: - raise ValidationError( - message="File path is required for appending to notes", - field_name="file_path", - validation_rule="non_empty", - ) - - if content is None: - raise ValidationError( - message="Content is required for appending to notes", - field_name="content", - validation_rule="not_none", - ) - - if not api_key: - raise ConfigurationError( - message="Obsidian API key is required", config_key="api_key" - ) - - async with obsidian_api_client(api_url, api_key) as session: - api_endpoint: str = f"{api_url}/vault/{file_path}" - - payload: Dict[str, Union[str, bool]] = {"content": content, "append": True} - - self.logger.info(f"追加内容到笔记: {file_path}") - - async with session.post(api_endpoint, json=payload) as response: - if response.status in [200, 201]: - return SkillResult( - success=True, - data={ - "file_path": file_path, - "appended_size": len(content) if content else 0, - "appended_at": datetime.now().isoformat(), - }, - message=f"成功追加内容到笔记: {file_path}", - ) - else: - error_text: str = await response.text() - raise APIError( - message=f"Failed to append to note: {error_text}", - api_name="obsidian", - status_code=response.status, - response_data=error_text, - ) - - -class ObsidianListFilesSkill(Skill): - """列出 Obsidian 文件 Skill""" - - def __init__(self) -> None: - super().__init__( - name="obsidian_list_files", - skill_type=SkillType.READ, - description="列出 Obsidian vault 中的文件", - ) - self.error_handler = get_error_handler() - - async def execute(self, context: CommandContext, **kwargs: Any) -> SkillResult: - """ - 列出指定文件夹中的文件 - - Args: - context: 命令执行上下文 - **kwargs: 包含以下参数 - - folder_path: 文件夹路径(可选) - - api_url: API URL - - api_key: API 密钥 - - Returns: - SkillResult: 包含文件列表的结果 - """ - try: - return await self._execute_list(context, **kwargs) - except (ValidationError, ConfigurationError, APIError) as e: - error_context = ErrorContext( - component="obsidian_list_files", - operation="list_files", - user_message="Failed to list files from Obsidian vault", - technical_details=kwargs, - ) - error_response = self.error_handler.handle_error(e, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - except Exception as e: - api_error = APIError( - message=f"Unexpected error listing files: {str(e)}", - api_name="obsidian", - cause=e, - ) - error_context = ErrorContext( - component="obsidian_list_files", - operation="list_files", - user_message="An unexpected error occurred while listing files", - ) - error_response = self.error_handler.handle_error(api_error, error_context) - return SkillResult( - success=error_response["success"], - error=error_response["error"], - message=error_response["message"], - ) - - async def _execute_list( - self, context: CommandContext, **kwargs: Any - ) -> SkillResult: - """Internal method that performs the actual list operation""" - folder_path: str = kwargs.get("folder_path", "") - api_url: str = kwargs.get("api_url", "https://localhost:27123") - api_key: Optional[str] = kwargs.get("api_key") - - if not api_key: - raise ConfigurationError( - message="Obsidian API key is required", config_key="api_key" - ) - - async with obsidian_api_client(api_url, api_key) as session: - api_endpoint: str = f"{api_url}/vault/list" - params: Dict[str, str] = {} - if folder_path: - params["path"] = folder_path - - self.logger.info(f"列出文件: {folder_path or 'root'}") - - async with session.get(api_endpoint, params=params) as response: - if response.status == 200: - files: Union[List[Any], Dict[str, Any]] = await response.json() - return SkillResult( - success=True, - data={ - "folder_path": folder_path, - "files": files, - "count": len(files) if isinstance(files, list) else 0, - }, - message=f"成功列出文件", - ) - else: - error_text: str = await response.text() - raise APIError( - message=f"Failed to list files: {error_text}", - api_name="obsidian", - status_code=response.status, - response_data=error_text, - ) diff --git a/./test_application_startup.py b/./test_application_startup.py deleted file mode 100644 index e8b91fb..0000000 --- a/./test_application_startup.py +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple application startup validation test. -Tests the application by running it as a module to validate basic functionality. -""" - -import asyncio -import json -import logging -import os -import subprocess -import sys -import tempfile -from pathlib import Path -from typing import Dict, Any, List, Tuple - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger("ApplicationStartup") - - -class ApplicationStartupTest: - """Test application startup and basic functionality""" - - def __init__(self): - self.test_results: List[Tuple[str, bool, str]] = [] - self.temp_config_path: Path = None - - def create_test_config(self) -> Path: - """Create a temporary test configuration file""" - config_content = { - 'obsidian': { - 'vault_path': '/tmp/test_vault', - 'rest_api': { - 'url': 'https://localhost:27123', - 'api_key': 'test-api-key', - 'verify_ssl': False - } - }, - 'claude': { - 'api_key': 'test-api-key-placeholder', - 'model': 'claude-3-5-sonnet-20241022', - 'max_tokens': 4096, - 'temperature': 0.7 - }, - 'journal': { - 'daily_notes_folder': 'Daily', - 'date_format': 'YYYY-MM-DD', - 'file_extension': '.md' - }, - 'output': { - 'experiences_folder': 'Knowledge/Experiences', - 'lessons_folder': 'Knowledge/Lessons', - 'tasks_folder': 'Tasks/Daily', - 'problems_folder': 'Knowledge/Problems', - 'achievements_folder': 'Knowledge/Achievements', - 'improvements_folder': 'Knowledge/Improvements' - }, - 'logging': { - 'level': 'INFO', - 'file': 'logs/journal_organizer.log' - } - } - - # Create temporary config file - temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) - json.dump(config_content, temp_file, indent=2) - temp_file.close() - - self.temp_config_path = Path(temp_file.name) - return self.temp_config_path - - def cleanup(self): - """Clean up temporary files""" - if self.temp_config_path and self.temp_config_path.exists(): - self.temp_config_path.unlink() - - def run_command(self, cmd: List[str], timeout: int = 30) -> Tuple[bool, str, str]: - """Run a command and return success, stdout, stderr""" - try: - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=timeout, - cwd=Path.cwd() - ) - return result.returncode == 0, result.stdout, result.stderr - except subprocess.TimeoutExpired: - return False, "", "Command timed out" - except Exception as e: - return False, "", str(e) - - def test_module_import(self) -> Tuple[str, bool, str]: - """Test if the module can be imported""" - test_name = "Module Import Test" - - cmd = [sys.executable, "-c", "import sys; sys.path.insert(0, '.'); import main; print('SUCCESS: Module imported')"] - success, stdout, stderr = self.run_command(cmd, timeout=10) - - if success and "SUCCESS" in stdout: - return test_name, True, "Module imported successfully" - else: - return test_name, False, f"Import failed: {stderr}" - - def test_help_command(self) -> Tuple[str, bool, str]: - """Test the help command""" - test_name = "Help Command Test" - - cmd = [sys.executable, "-m", "__main__", "--help"] - success, stdout, stderr = self.run_command(cmd, timeout=10) - - if success and ("usage:" in stdout.lower() or "help" in stdout.lower()): - return test_name, True, "Help command works" - else: - return test_name, False, f"Help command failed: {stderr}" - - def test_list_command(self) -> Tuple[str, bool, str]: - """Test the list command""" - test_name = "List Command Test" - - config_path = self.create_test_config() - cmd = [sys.executable, "-m", "__main__", "--config", str(config_path), "list"] - success, stdout, stderr = self.run_command(cmd, timeout=15) - - if success and ("organize" in stdout.lower() or "可用命令" in stdout): - return test_name, True, "List command works" - else: - return test_name, False, f"List command failed: {stderr}" - - def test_check_deps_command(self) -> Tuple[str, bool, str]: - """Test the check-deps command""" - test_name = "Check Dependencies Command Test" - - cmd = [sys.executable, "-m", "__main__", "check-deps"] - success, stdout, stderr = self.run_command(cmd, timeout=15) - - # This command should run regardless of missing dependencies - if "检查依赖项状态" in stdout or "dependency" in stdout.lower() or success: - return test_name, True, "Check-deps command works" - else: - return test_name, False, f"Check-deps command failed: {stderr}" - - def test_info_command(self) -> Tuple[str, bool, str]: - """Test the info command""" - test_name = "Info Command Test" - - config_path = self.create_test_config() - cmd = [sys.executable, "-m", "__main__", "--config", str(config_path), "info"] - success, stdout, stderr = self.run_command(cmd, timeout=15) - - if success and ("{" in stdout or "info" in stdout.lower()): - return test_name, True, "Info command works" - else: - return test_name, False, f"Info command failed: {stderr}" - - def test_chat_help(self) -> Tuple[str, bool, str]: - """Test the chat interface help""" - test_name = "Chat Interface Help Test" - - cmd = [sys.executable, "-c", "import sys; sys.path.insert(0, '.'); import chat_main; print('SUCCESS: Chat module imported')"] - success, stdout, stderr = self.run_command(cmd, timeout=10) - - if success and "SUCCESS" in stdout: - return test_name, True, "Chat module can be imported" - else: - return test_name, False, f"Chat module import failed: {stderr}" - - def test_configuration_validation(self) -> Tuple[str, bool, str]: - """Test configuration validation""" - test_name = "Configuration Validation Test" - - # Test with valid config - config_path = self.create_test_config() - cmd = [sys.executable, "-c", f""" -import sys -sys.path.insert(0, '.') -import json -from pathlib import Path - -# Test config loading -config_path = Path('{config_path}') -with config_path.open('r') as f: - config = json.load(f) - -print(f'SUCCESS: Config loaded with {{len(config)}} sections') -"""] - - success, stdout, stderr = self.run_command(cmd, timeout=10) - - if success and "SUCCESS" in stdout: - return test_name, True, "Configuration validation works" - else: - return test_name, False, f"Configuration validation failed: {stderr}" - - def run_all_tests(self) -> List[Tuple[str, bool, str]]: - """Run all startup tests""" - logger.info("🚀 Starting application startup tests...") - - test_methods = [ - self.test_module_import, - self.test_help_command, - self.test_list_command, - self.test_check_deps_command, - self.test_info_command, - self.test_chat_help, - self.test_configuration_validation - ] - - for test_method in test_methods: - try: - logger.info(f"Running {test_method.__name__}...") - result = test_method() - self.test_results.append(result) - - status = "✅ PASS" if result[1] else "❌ FAIL" - logger.info(f"{status} {result[0]}: {result[2]}") - - except Exception as e: - error_result = (test_method.__name__, False, f"Test execution failed: {str(e)}") - self.test_results.append(error_result) - logger.error(f"❌ FAIL {error_result[0]}: {error_result[2]}") - - return self.test_results - - def generate_report(self) -> str: - """Generate test report""" - total_tests = len(self.test_results) - passed_tests = sum(1 for _, success, _ in self.test_results if success) - failed_tests = total_tests - passed_tests - - report = f""" -{'='*60} -APPLICATION STARTUP TEST REPORT -{'='*60} - -Summary: - Total Tests: {total_tests} - Passed: {passed_tests} - Failed: {failed_tests} - Success Rate: {(passed_tests/total_tests*100):.1f}% - -Test Results: -""" - - for test_name, success, message in self.test_results: - status = "✅ PASS" if success else "❌ FAIL" - report += f"\n{status} {test_name}: {message}" - - report += f"\n\n{'='*60}\n" - - return report - - -def main(): - """Main test function""" - tester = ApplicationStartupTest() - - try: - results = tester.run_all_tests() - report = tester.generate_report() - - print(report) - - # Return appropriate exit code - failed_count = sum(1 for _, success, _ in results if not success) - return 0 if failed_count == 0 else 1 - - except Exception as e: - logger.error(f"Test execution failed: {str(e)}") - return 1 - - finally: - tester.cleanup() - - -if __name__ == "__main__": - exit_code = main() - sys.exit(exit_code) \ No newline at end of file diff --git a/./test_basic_functionality.py b/./test_basic_functionality.py deleted file mode 100644 index 6d5a23d..0000000 --- a/./test_basic_functionality.py +++ /dev/null @@ -1,292 +0,0 @@ -#!/usr/bin/env python3 -""" -Basic functionality validation test. -Tests core components that can be imported and validated. -""" - -import asyncio -import json -import logging -import os -import sys -import tempfile -import traceback -from pathlib import Path -from typing import Dict, Any, List, Tuple - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger("BasicFunctionality") - - -class BasicFunctionalityTest: - """Test basic functionality of core components""" - - def __init__(self): - self.test_results: List[Tuple[str, bool, str]] = [] - - def test_dependency_manager(self) -> Tuple[str, bool, str]: - """Test dependency manager functionality""" - test_name = "Dependency Manager Test" - - try: - import dependency_manager - - # Test getting dependency manager - dep_manager = dependency_manager.get_dependency_manager() - assert dep_manager is not None, "Dependency manager should not be None" - - # Test status report - status_report = dep_manager.get_dependency_status_report() - assert isinstance(status_report, str), "Status report should be a string" - assert len(status_report) > 0, "Status report should not be empty" - - # Test missing dependencies - missing_deps = dep_manager.get_missing_dependencies() - assert isinstance(missing_deps, list), "Missing deps should be a list" - - return test_name, True, f"Dependency manager works (missing: {len(missing_deps)} deps)" - - except Exception as e: - return test_name, False, f"Dependency manager failed: {str(e)}" - - def test_error_handling(self) -> Tuple[str, bool, str]: - """Test error handling framework""" - test_name = "Error Handling Framework Test" - - try: - import error_handling - - # Test custom exceptions - exc1 = error_handling.JournalOrganizerError("test") - exc2 = error_handling.ConfigurationError("config error") - exc3 = error_handling.APIError("api error") - exc4 = error_handling.ValidationError("validation error") - - assert all(isinstance(exc, Exception) for exc in [exc1, exc2, exc3, exc4]) - - # Test ErrorHandler - import logging - logger = logging.getLogger("test") - error_handler = error_handling.ErrorHandler(logger) - - assert hasattr(error_handler, 'handle_api_error') - assert hasattr(error_handler, 'handle_validation_error') - - return test_name, True, "Error handling framework works correctly" - - except Exception as e: - return test_name, False, f"Error handling test failed: {str(e)}" - - def test_configuration_validation(self) -> Tuple[str, bool, str]: - """Test configuration validation""" - test_name = "Configuration Validation Test" - - try: - import config_validation - - # Test configuration models exist - assert hasattr(config_validation, 'ObsidianConfig') - assert hasattr(config_validation, 'ClaudeConfig') - assert hasattr(config_validation, 'SystemConfig') - - # Test validation functions - assert hasattr(config_validation, 'validate_obsidian_config') - assert hasattr(config_validation, 'validate_claude_config') - - return test_name, True, "Configuration validation works" - - except Exception as e: - return test_name, False, f"Configuration validation failed: {str(e)}" - - def test_input_validation(self) -> Tuple[str, bool, str]: - """Test input validation""" - test_name = "Input Validation Test" - - try: - import input_validation - - # Test validation functions exist - assert hasattr(input_validation, 'command_input_validator') - assert hasattr(input_validation, 'validate_user_input') - - # Test basic validation - validator = input_validation.command_input_validator - assert hasattr(validator, 'validate_organize_command_input'), "Validator should have organize command validation method" - - return test_name, True, "Input validation works" - - except Exception as e: - return test_name, False, f"Input validation failed: {str(e)}" - - def test_date_validation(self) -> Tuple[str, bool, str]: - """Test date validation""" - test_name = "Date Validation Test" - - try: - import date_validation - - # Test validation function exists - assert hasattr(date_validation, 'validate_date_input') - - # Test basic date validation - result = date_validation.validate_date_input("2025-12-31") - assert result is not None, "Valid date should return result" - - return test_name, True, "Date validation works" - - except Exception as e: - return test_name, False, f"Date validation failed: {str(e)}" - - def test_path_security(self) -> Tuple[str, bool, str]: - """Test path security""" - test_name = "Path Security Test" - - try: - import path_security - - # Test security functions exist - assert hasattr(path_security, 'validate_path_safety') - assert hasattr(path_security, 'sanitize_path') - - # Test basic path validation - safe_path = "/tmp/test.txt" - result = path_security.validate_path_safety(safe_path) - assert isinstance(result, bool), "Path validation should return boolean" - - return test_name, True, "Path security works" - - except Exception as e: - return test_name, False, f"Path security failed: {str(e)}" - - def test_api_response_validation(self) -> Tuple[str, bool, str]: - """Test API response validation""" - test_name = "API Response Validation Test" - - try: - import api_response_validation - - # Test validation functions exist - assert hasattr(api_response_validation, 'validate_claude_response') - assert hasattr(api_response_validation, 'validate_obsidian_response') - - # Test basic response validation - test_response = {"status": "success", "data": {}} - result = api_response_validation.validate_obsidian_response(test_response) - assert isinstance(result, bool), "Response validation should return boolean" - - return test_name, True, "API response validation works" - - except Exception as e: - return test_name, False, f"API response validation failed: {str(e)}" - - def test_agent_core_classes(self) -> Tuple[str, bool, str]: - """Test agent core classes can be imported""" - test_name = "Agent Core Classes Test" - - try: - import agent_core - - # Test core classes exist - assert hasattr(agent_core, 'Agent') - assert hasattr(agent_core, 'Command') - assert hasattr(agent_core, 'Skill') - assert hasattr(agent_core, 'SkillResult') - assert hasattr(agent_core, 'CommandContext') - - # Test SkillResult can be instantiated - result = agent_core.SkillResult(success=True, message="test") - assert result.success is True - assert result.message == "test" - - return test_name, True, "Agent core classes work" - - except Exception as e: - return test_name, False, f"Agent core classes failed: {str(e)}" - - def run_all_tests(self) -> List[Tuple[str, bool, str]]: - """Run all basic functionality tests""" - logger.info("🚀 Starting basic functionality tests...") - - test_methods = [ - self.test_dependency_manager, - self.test_error_handling, - self.test_configuration_validation, - self.test_input_validation, - self.test_date_validation, - self.test_path_security, - self.test_api_response_validation, - self.test_agent_core_classes - ] - - for test_method in test_methods: - try: - logger.info(f"Running {test_method.__name__}...") - result = test_method() - self.test_results.append(result) - - status = "✅ PASS" if result[1] else "❌ FAIL" - logger.info(f"{status} {result[0]}: {result[2]}") - - except Exception as e: - error_result = (test_method.__name__, False, f"Test execution failed: {str(e)}") - self.test_results.append(error_result) - logger.error(f"❌ FAIL {error_result[0]}: {error_result[2]}") - - return self.test_results - - def generate_report(self) -> str: - """Generate test report""" - total_tests = len(self.test_results) - passed_tests = sum(1 for _, success, _ in self.test_results if success) - failed_tests = total_tests - passed_tests - - report = f""" -{'='*60} -BASIC FUNCTIONALITY TEST REPORT -{'='*60} - -Summary: - Total Tests: {total_tests} - Passed: {passed_tests} - Failed: {failed_tests} - Success Rate: {(passed_tests/total_tests*100):.1f}% - -Test Results: -""" - - for test_name, success, message in self.test_results: - status = "✅ PASS" if success else "❌ FAIL" - report += f"\n{status} {test_name}: {message}" - - report += f"\n\n{'='*60}\n" - - return report - - -def main(): - """Main test function""" - tester = BasicFunctionalityTest() - - try: - results = tester.run_all_tests() - report = tester.generate_report() - - print(report) - - # Return appropriate exit code - failed_count = sum(1 for _, success, _ in results if not success) - return 0 if failed_count == 0 else 1 - - except Exception as e: - logger.error(f"Test execution failed: {str(e)}") - traceback.print_exc() - return 1 - - -if __name__ == "__main__": - exit_code = main() - sys.exit(exit_code) \ No newline at end of file diff --git a/./test_config_loader_integration.py b/./test_config_loader_integration.py deleted file mode 100644 index a094bff..0000000 --- a/./test_config_loader_integration.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test to verify ConfigurationLoader integration works correctly -""" - -import os -import tempfile -from pathlib import Path -from configuration_loader import ConfigurationLoader, ConfigurationError, EnvironmentVariableError - -def test_basic_environment_variable_expansion(): - """Test basic environment variable expansion functionality""" - - # Set up test environment variables - os.environ['TEST_API_KEY'] = 'test-key-123' - os.environ['TEST_URL'] = 'https://test.example.com' - - # Create test configuration - test_config = { - 'api': { - 'key': '${TEST_API_KEY}', - 'url': '${TEST_URL}', - 'timeout': '${TEST_TIMEOUT:-30}', # With default value - 'retries': 3 # No environment variable - }, - 'nested': { - 'values': ['${TEST_API_KEY}', 'static-value', '${TEST_URL}'] - } - } - - # Test expansion - loader = ConfigurationLoader() - expanded = loader.expand_environment_variables(test_config) - - # Verify results - assert expanded['api']['key'] == 'test-key-123' - assert expanded['api']['url'] == 'https://test.example.com' - assert expanded['api']['timeout'] == '30' # Default value used - assert expanded['api']['retries'] == 3 # Unchanged - assert expanded['nested']['values'][0] == 'test-key-123' - assert expanded['nested']['values'][1] == 'static-value' - assert expanded['nested']['values'][2] == 'https://test.example.com' - - print("✓ Basic environment variable expansion test passed") - -def test_missing_required_variable(): - """Test error handling for missing required environment variables""" - - # Ensure variable is not set - if 'MISSING_VAR' in os.environ: - del os.environ['MISSING_VAR'] - - test_config = { - 'api': { - 'key': '${MISSING_VAR}' - } - } - - loader = ConfigurationLoader() - - try: - loader.expand_environment_variables(test_config) - assert False, "Should have raised EnvironmentVariableError" - except EnvironmentVariableError as e: - assert 'MISSING_VAR' in str(e) - print("✓ Missing required variable error test passed") - -def test_config_file_loading(): - """Test loading configuration from YAML file""" - - # Set up test environment variable - os.environ['TEST_CLAUDE_KEY'] = 'sk-ant-test-key' - - # Create temporary YAML config file - yaml_content = """ -obsidian: - vault_path: "/tmp/test-vault" - rest_api: - url: "https://localhost:27123" - api_key: "test-obsidian-key" - verify_ssl: false - -claude: - api_key: "${TEST_CLAUDE_KEY}" - api_url: "${CLAUDE_URL:-https://api.anthropic.com}" - model: "claude-3-5-sonnet-20241022" - max_tokens: 4096 - temperature: 0.7 -""" - - with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: - f.write(yaml_content) - temp_file = f.name - - try: - # Load and expand configuration - expanded_config = ConfigurationLoader.load_config(temp_file) - - # Verify expansion worked - assert expanded_config['claude']['api_key'] == 'sk-ant-test-key' - assert expanded_config['claude']['api_url'] == 'https://api.anthropic.com' # Default value - assert expanded_config['obsidian']['rest_api']['api_key'] == 'test-obsidian-key' - - print("✓ Config file loading test passed") - - finally: - # Clean up - Path(temp_file).unlink() - -def test_validation_methods(): - """Test validation helper methods""" - - # Set up test environment - os.environ['PRESENT_VAR'] = 'present' - if 'MISSING_VAR' in os.environ: - del os.environ['MISSING_VAR'] - - test_config = { - 'present': '${PRESENT_VAR}', - 'missing': '${MISSING_VAR}', - 'with_default': '${MISSING_VAR:-default_value}' - } - - loader = ConfigurationLoader() - - # Test validation - missing_vars = loader.validate_environment_variables(test_config) - assert len(missing_vars) == 1 - assert 'MISSING_VAR' in missing_vars[0] - - # Test environment variable references - env_refs = loader.get_environment_variable_references(test_config) - assert 'PRESENT_VAR' in env_refs - assert 'MISSING_VAR' in env_refs - assert len(env_refs['MISSING_VAR']) == 2 # Used in 'missing' and 'with_default' - - print("✓ Validation methods test passed") - -if __name__ == '__main__': - print("Testing ConfigurationLoader integration...") - - try: - test_basic_environment_variable_expansion() - test_missing_required_variable() - test_config_file_loading() - test_validation_methods() - - print("\n✅ All tests passed! ConfigurationLoader integration is working correctly.") - - except Exception as e: - print(f"\n❌ Test failed: {e}") - import traceback - traceback.print_exc() - exit(1) - - finally: - # Clean up test environment variables - for var in ['TEST_API_KEY', 'TEST_URL', 'TEST_CLAUDE_KEY', 'PRESENT_VAR']: - if var in os.environ: - del os.environ[var] \ No newline at end of file diff --git a/./test_performance_reliability.py b/./test_performance_reliability.py deleted file mode 100644 index 52cd13e..0000000 --- a/./test_performance_reliability.py +++ /dev/null @@ -1,496 +0,0 @@ -#!/usr/bin/env python3 -""" -Performance and reliability testing script. -Tests various input sizes, edge cases, memory usage, and error recovery. -""" - -import asyncio -import gc -import json -import logging -import os -import sys -import tempfile -import time -import traceback -from pathlib import Path -from typing import Dict, Any, List, Tuple, Optional - -# Try to import psutil, fallback to basic memory tracking -try: - import psutil - HAS_PSUTIL = True -except ImportError: - HAS_PSUTIL = False - psutil = None - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger("PerformanceReliability") - - -class PerformanceReliabilityTest: - """Test performance and reliability of core components""" - - def __init__(self): - self.test_results: List[Tuple[str, bool, str, Dict[str, Any]]] = [] - if HAS_PSUTIL: - self.process = psutil.Process() - else: - self.process = None - - def get_memory_usage(self) -> float: - """Get current memory usage in MB""" - if HAS_PSUTIL and self.process: - return self.process.memory_info().rss / 1024 / 1024 - else: - # Fallback to basic memory tracking - return 0.0 # Can't measure without psutil - - def test_dependency_manager_performance(self) -> Tuple[str, bool, str, Dict[str, Any]]: - """Test dependency manager performance with multiple calls""" - test_name = "Dependency Manager Performance Test" - metrics = {} - - try: - import dependency_manager - - # Measure initial memory - initial_memory = self.get_memory_usage() - - # Test multiple instantiations - start_time = time.time() - managers = [] - for i in range(100): - manager = dependency_manager.get_dependency_manager() - managers.append(manager) - - creation_time = time.time() - start_time - - # Test status report generation performance - start_time = time.time() - for manager in managers[:10]: # Test first 10 - status = manager.get_dependency_status_report() - report_time = time.time() - start_time - - # Measure final memory - final_memory = self.get_memory_usage() - memory_increase = final_memory - initial_memory if HAS_PSUTIL else 0.0 - - # Cleanup - del managers - gc.collect() - - metrics = { - "creation_time_100_instances": f"{creation_time:.3f}s", - "report_generation_time_10_calls": f"{report_time:.3f}s", - "memory_increase": f"{memory_increase:.2f}MB" if HAS_PSUTIL else "N/A (psutil not available)", - "avg_creation_time": f"{creation_time/100*1000:.2f}ms" - } - - # Performance thresholds - if creation_time > 5.0: # 5 seconds for 100 instances - return test_name, False, "Dependency manager creation too slow", metrics - - if HAS_PSUTIL and memory_increase > 50: # 50MB increase - return test_name, False, "Excessive memory usage", metrics - - return test_name, True, "Dependency manager performance acceptable", metrics - - except Exception as e: - return test_name, False, f"Performance test failed: {str(e)}", metrics - - def test_error_handling_reliability(self) -> Tuple[str, bool, str, Dict[str, Any]]: - """Test error handling reliability with various error conditions""" - test_name = "Error Handling Reliability Test" - metrics = {} - - try: - import error_handling - import logging - - # Create error handler - logger = logging.getLogger("test_reliability") - error_handler = error_handling.ErrorHandler(logger) - - # Test various error scenarios - test_errors = [ - Exception("Generic error"), - ValueError("Value error"), - TypeError("Type error"), - RuntimeError("Runtime error"), - ConnectionError("Connection error") - ] - - successful_handles = 0 - start_time = time.time() - - for i, error in enumerate(test_errors * 20): # 100 total errors - try: - result = error_handler.handle_api_error(error, "test_api", f"test_operation_{i}") - # ErrorHandler returns a dict with success, message, etc. - if isinstance(result, dict) and 'success' in result: - successful_handles += 1 - elif result is not None: # Any non-None result counts as handled - successful_handles += 1 - except Exception: - pass # Error in error handling - not good but continue - - handling_time = time.time() - start_time - - metrics = { - "total_errors_processed": len(test_errors) * 20, - "successful_handles": successful_handles, - "handling_time": f"{handling_time:.3f}s", - "avg_handling_time": f"{handling_time/(len(test_errors)*20)*1000:.2f}ms", - "success_rate": f"{successful_handles/(len(test_errors)*20)*100:.1f}%" - } - - # Reliability thresholds - success_rate = successful_handles / (len(test_errors) * 20) - if success_rate < 0.95: # 95% success rate - return test_name, False, "Error handling success rate too low", metrics - - if handling_time > 2.0: # 2 seconds for 100 errors - return test_name, False, "Error handling too slow", metrics - - return test_name, True, "Error handling reliability acceptable", metrics - - except Exception as e: - return test_name, False, f"Reliability test failed: {str(e)}", metrics - - def test_configuration_edge_cases(self) -> Tuple[str, bool, str, Dict[str, Any]]: - """Test configuration handling with edge cases""" - test_name = "Configuration Edge Cases Test" - metrics = {} - - try: - # Test various configuration scenarios - test_configs = [ - {}, # Empty config - {"invalid": "structure"}, # Invalid structure - {"obsidian": {}}, # Partial config - {"obsidian": {"vault_path": ""}}, # Empty values - {"obsidian": {"vault_path": "/nonexistent/path"}}, # Invalid paths - {"claude": {"api_key": ""}}, # Empty API key - {"claude": {"api_key": "x" * 1000}}, # Very long API key - ] - - successful_loads = 0 - start_time = time.time() - - for i, config in enumerate(test_configs): - try: - # Create temporary config file - with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: - json.dump(config, f) - temp_path = f.name - - # Try to load config (this would normally be done by main.py) - with open(temp_path, 'r') as f: - loaded_config = json.load(f) - - # Basic validation that it's a dict - if isinstance(loaded_config, dict): - successful_loads += 1 - - # Cleanup - os.unlink(temp_path) - - except Exception: - # Expected for some edge cases - pass - - processing_time = time.time() - start_time - - metrics = { - "total_configs_tested": len(test_configs), - "successful_loads": successful_loads, - "processing_time": f"{processing_time:.3f}s", - "avg_processing_time": f"{processing_time/len(test_configs)*1000:.2f}ms" - } - - # Should handle at least basic configs - if successful_loads < 3: - return test_name, False, "Too many configuration failures", metrics - - return test_name, True, "Configuration edge cases handled", metrics - - except Exception as e: - return test_name, False, f"Edge case test failed: {str(e)}", metrics - - def test_memory_usage_patterns(self) -> Tuple[str, bool, str, Dict[str, Any]]: - """Test memory usage patterns and potential leaks""" - test_name = "Memory Usage Patterns Test" - metrics = {} - - try: - # Measure baseline memory - gc.collect() - baseline_memory = self.get_memory_usage() - - # Test repeated operations that might cause memory leaks - operations_memory = [] - - for iteration in range(10): - # Simulate typical operations - temp_data = [] - - # Create temporary objects - for i in range(1000): - temp_data.append({ - "id": i, - "data": "x" * 100, # 100 chars - "nested": {"value": i * 2} - }) - - # Process data - processed = [item for item in temp_data if item["id"] % 2 == 0] - - # Measure memory after each iteration - current_memory = self.get_memory_usage() - operations_memory.append(current_memory) - - # Cleanup - del temp_data, processed - gc.collect() - - # Final memory measurement - final_memory = self.get_memory_usage() - memory_increase = final_memory - baseline_memory - - # Calculate memory growth trend - if len(operations_memory) > 1: - memory_growth = operations_memory[-1] - operations_memory[0] - else: - memory_growth = 0 - - metrics = { - "baseline_memory": f"{baseline_memory:.2f}MB" if HAS_PSUTIL else "N/A", - "final_memory": f"{final_memory:.2f}MB" if HAS_PSUTIL else "N/A", - "total_memory_increase": f"{memory_increase:.2f}MB" if HAS_PSUTIL else "N/A", - "memory_growth_trend": f"{memory_growth:.2f}MB" if HAS_PSUTIL else "N/A", - "max_memory_during_test": f"{max(operations_memory):.2f}MB" if HAS_PSUTIL and operations_memory else "N/A", - "iterations_tested": 10 - } - - # Memory usage thresholds (only check if psutil available) - if HAS_PSUTIL: - if memory_increase > 20: # 20MB increase after cleanup - return test_name, False, "Potential memory leak detected", metrics - - if memory_growth > 15: # 15MB growth trend - return test_name, False, "Memory growth trend concerning", metrics - - return test_name, True, "Memory usage patterns acceptable", metrics - - except Exception as e: - return test_name, False, f"Memory test failed: {str(e)}", metrics - - def test_graceful_degradation(self) -> Tuple[str, bool, str, Dict[str, Any]]: - """Test graceful degradation when dependencies are missing""" - test_name = "Graceful Degradation Test" - metrics = {} - - try: - import dependency_manager - - # Test dependency manager with missing modules - dep_manager = dependency_manager.get_dependency_manager() - - # Test getting non-existent modules - test_modules = [ - 'nonexistent_module', - 'fake_dependency', - 'missing_package', - 'invalid.module.name' - ] - - successful_degradations = 0 - start_time = time.time() - - for module_name in test_modules: - try: - result = dep_manager.get_module(module_name) - # Should return None for missing modules, not crash - if result is None: - successful_degradations += 1 - except Exception: - # Should not raise exceptions for missing modules - pass - - degradation_time = time.time() - start_time - - # Test status report with missing dependencies - try: - status_report = dep_manager.get_dependency_status_report() - status_report_works = isinstance(status_report, str) and len(status_report) > 0 - except Exception: - status_report_works = False - - metrics = { - "modules_tested": len(test_modules), - "successful_degradations": successful_degradations, - "degradation_time": f"{degradation_time:.3f}s", - "status_report_works": status_report_works, - "degradation_rate": f"{successful_degradations/len(test_modules)*100:.1f}%" - } - - # Should gracefully handle all missing modules - if successful_degradations < len(test_modules): - return test_name, False, "Not all missing modules handled gracefully", metrics - - if not status_report_works: - return test_name, False, "Status report fails with missing dependencies", metrics - - return test_name, True, "Graceful degradation working correctly", metrics - - except Exception as e: - return test_name, False, f"Degradation test failed: {str(e)}", metrics - - def test_concurrent_operations(self) -> Tuple[str, bool, str, Dict[str, Any]]: - """Test concurrent operations and thread safety""" - test_name = "Concurrent Operations Test" - metrics = {} - - try: - import dependency_manager - import asyncio - import concurrent.futures - - async def async_operation(operation_id: int) -> bool: - """Simulate async operation""" - try: - dep_manager = dependency_manager.get_dependency_manager() - status = dep_manager.get_dependency_status_report() - missing = dep_manager.get_missing_dependencies() - - # Simulate some processing time - await asyncio.sleep(0.01) - - return len(status) > 0 and isinstance(missing, list) - except Exception: - return False - - # Test concurrent async operations - start_time = time.time() - - async def run_concurrent_test(): - tasks = [async_operation(i) for i in range(20)] - results = await asyncio.gather(*tasks, return_exceptions=True) - return results - - results = asyncio.run(run_concurrent_test()) - concurrent_time = time.time() - start_time - - successful_operations = sum(1 for r in results if r is True) - - metrics = { - "concurrent_operations": 20, - "successful_operations": successful_operations, - "concurrent_time": f"{concurrent_time:.3f}s", - "avg_operation_time": f"{concurrent_time/20*1000:.2f}ms", - "success_rate": f"{successful_operations/20*100:.1f}%" - } - - # Concurrency thresholds - if successful_operations < 18: # 90% success rate - return test_name, False, "Concurrent operations success rate too low", metrics - - if concurrent_time > 5.0: # 5 seconds for 20 operations - return test_name, False, "Concurrent operations too slow", metrics - - return test_name, True, "Concurrent operations working correctly", metrics - - except Exception as e: - return test_name, False, f"Concurrency test failed: {str(e)}", metrics - - def run_all_tests(self) -> List[Tuple[str, bool, str, Dict[str, Any]]]: - """Run all performance and reliability tests""" - logger.info("🚀 Starting performance and reliability tests...") - - test_methods = [ - self.test_dependency_manager_performance, - self.test_error_handling_reliability, - self.test_configuration_edge_cases, - self.test_memory_usage_patterns, - self.test_graceful_degradation, - self.test_concurrent_operations - ] - - for test_method in test_methods: - try: - logger.info(f"Running {test_method.__name__}...") - result = test_method() - self.test_results.append(result) - - status = "✅ PASS" if result[1] else "❌ FAIL" - logger.info(f"{status} {result[0]}: {result[2]}") - - except Exception as e: - error_result = (test_method.__name__, False, f"Test execution failed: {str(e)}", {}) - self.test_results.append(error_result) - logger.error(f"❌ FAIL {error_result[0]}: {error_result[2]}") - - return self.test_results - - def generate_report(self) -> str: - """Generate performance and reliability test report""" - total_tests = len(self.test_results) - passed_tests = sum(1 for _, success, _, _ in self.test_results if success) - failed_tests = total_tests - passed_tests - - report = f""" -{'='*60} -PERFORMANCE AND RELIABILITY TEST REPORT -{'='*60} - -Summary: - Total Tests: {total_tests} - Passed: {passed_tests} - Failed: {failed_tests} - Success Rate: {(passed_tests/total_tests*100):.1f}% - -Test Results: -""" - - for test_name, success, message, metrics in self.test_results: - status = "✅ PASS" if success else "❌ FAIL" - report += f"\n{status} {test_name}: {message}" - - if metrics: - report += "\n Metrics:" - for key, value in metrics.items(): - report += f"\n - {key}: {value}" - - report += f"\n\n{'='*60}\n" - - return report - - -def main(): - """Main test function""" - tester = PerformanceReliabilityTest() - - try: - results = tester.run_all_tests() - report = tester.generate_report() - - print(report) - - # Return appropriate exit code - failed_count = sum(1 for _, success, _, _ in results if not success) - return 0 if failed_count == 0 else 1 - - except Exception as e: - logger.error(f"Test execution failed: {str(e)}") - traceback.print_exc() - return 1 - - -if __name__ == "__main__": - exit_code = main() - sys.exit(exit_code) \ No newline at end of file diff --git a/./test_startup_validation.py b/./test_startup_validation.py deleted file mode 100644 index b5d87cb..0000000 --- a/./test_startup_validation.py +++ /dev/null @@ -1,484 +0,0 @@ -#!/usr/bin/env python3 -""" -Application startup and basic functionality validation script. -Tests both v1.0 CLI and v2.0 conversational interfaces. -""" - -import asyncio -import json -import logging -import os -import sys -import tempfile -import traceback -from pathlib import Path -from typing import Dict, Any, Optional, List, Tuple - -# Add the current directory to Python path for imports -sys.path.insert(0, str(Path(__file__).parent)) - -# Configure logging for validation -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger("StartupValidation") - - -class ValidationResult: - """Container for validation test results""" - - def __init__(self, test_name: str): - self.test_name = test_name - self.success = False - self.message = "" - self.details: Dict[str, Any] = {} - self.error: Optional[Exception] = None - - def set_success(self, message: str, details: Optional[Dict[str, Any]] = None): - self.success = True - self.message = message - self.details = details or {} - - def set_failure(self, message: str, error: Optional[Exception] = None, details: Optional[Dict[str, Any]] = None): - self.success = False - self.message = message - self.error = error - self.details = details or {} - - def __str__(self) -> str: - status = "✅ PASS" if self.success else "❌ FAIL" - return f"{status} {self.test_name}: {self.message}" - - -class StartupValidator: - """Validates application startup and basic functionality""" - - def __init__(self): - self.results: List[ValidationResult] = [] - self.temp_config_path: Optional[Path] = None - - def create_test_config(self) -> Path: - """Create a temporary test configuration file""" - config_content = { - 'obsidian': { - 'vault_path': '/tmp/test_vault', - 'rest_api': { - 'url': 'https://localhost:27123', - 'api_key': 'test-api-key', - 'verify_ssl': False - } - }, - 'claude': { - 'api_key': '${ANTHROPIC_API_KEY}', - 'model': 'claude-3-5-sonnet-20241022', - 'max_tokens': 4096, - 'temperature': 0.7 - }, - 'journal': { - 'daily_notes_folder': 'Daily', - 'date_format': 'YYYY-MM-DD', - 'file_extension': '.md' - }, - 'output': { - 'experiences_folder': 'Knowledge/Experiences', - 'lessons_folder': 'Knowledge/Lessons', - 'tasks_folder': 'Tasks/Daily', - 'problems_folder': 'Knowledge/Problems', - 'achievements_folder': 'Knowledge/Achievements', - 'improvements_folder': 'Knowledge/Improvements' - }, - 'logging': { - 'level': 'INFO', - 'file': 'logs/journal_organizer.log' - } - } - - # Create temporary config file - temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) - json.dump(config_content, temp_file, indent=2) - temp_file.close() - - self.temp_config_path = Path(temp_file.name) - return self.temp_config_path - - def cleanup_test_config(self): - """Clean up temporary test configuration""" - if self.temp_config_path and self.temp_config_path.exists(): - self.temp_config_path.unlink() - - async def test_import_main_modules(self) -> ValidationResult: - """Test importing main application modules""" - result = ValidationResult("Import Main Modules") - - try: - # Test importing core modules - modules_tested = [] - - # Test core modules - try: - import main - modules_tested.append("main") - except ImportError as e: - logger.warning(f"Could not import main: {e}") - - try: - import chat_main - modules_tested.append("chat_main") - except ImportError as e: - logger.warning(f"Could not import chat_main: {e}") - - try: - import agent_core - modules_tested.append("agent_core") - except ImportError as e: - logger.warning(f"Could not import agent_core: {e}") - - try: - import config - modules_tested.append("config") - except ImportError as e: - logger.warning(f"Could not import config: {e}") - - # Test command modules - try: - from commands import organize_command - modules_tested.append("commands.organize_command") - except ImportError as e: - logger.warning(f"Could not import commands.organize_command: {e}") - - # Test skill modules - try: - from skills import obsidian_skill - modules_tested.append("skills.obsidian_skill") - except ImportError as e: - logger.warning(f"Could not import skills.obsidian_skill: {e}") - - try: - from skills import claude_skill - modules_tested.append("skills.claude_skill") - except ImportError as e: - logger.warning(f"Could not import skills.claude_skill: {e}") - - # Test conversation modules - try: - from conversation import conversational_agent - modules_tested.append("conversation.conversational_agent") - except ImportError as e: - logger.warning(f"Could not import conversation.conversational_agent: {e}") - - try: - from conversation import conversation_state - modules_tested.append("conversation.conversation_state") - except ImportError as e: - logger.warning(f"Could not import conversation.conversation_state: {e}") - - if len(modules_tested) >= 4: # At least core modules should work - result.set_success(f"Successfully imported {len(modules_tested)} modules", { - "modules_tested": modules_tested, - "total_attempted": 9 - }) - else: - result.set_failure(f"Only imported {len(modules_tested)} out of 9 modules", details={ - "modules_tested": modules_tested - }) - - except Exception as e: - result.set_failure(f"Failed to import modules: {str(e)}", e) - - return result - - async def test_agent_initialization(self) -> ValidationResult: - """Test basic agent initialization""" - result = ValidationResult("Agent Initialization") - - try: - # Try to import and test agent initialization - try: - from main import JournalOrganizerAgent - except ImportError: - # If relative import fails, skip this test - result.set_failure("Could not import JournalOrganizerAgent - likely due to package structure") - return result - - # Create test config - config_path = self.create_test_config() - - # Initialize agent - agent = JournalOrganizerAgent(str(config_path)) - - # Test basic properties - assert hasattr(agent, 'config'), "Agent should have config attribute" - assert hasattr(agent, 'agent'), "Agent should have agent attribute" - assert hasattr(agent, 'logger'), "Agent should have logger attribute" - - # Test command listing - commands = agent.list_commands() - assert isinstance(commands, list), "Commands should be a list" - assert len(commands) > 0, "Should have at least one command" - - result.set_success("Agent initialized successfully", { - "available_commands": commands, - "config_loaded": bool(agent.config) - }) - - except Exception as e: - result.set_failure(f"Agent initialization failed: {str(e)}", e) - - return result - - async def test_configuration_loading(self) -> ValidationResult: - """Test configuration loading with various scenarios""" - result = ValidationResult("Configuration Loading") - - try: - from main import JournalOrganizerAgent - - test_results = {} - - # Test 1: Load from specific config file - config_path = self.create_test_config() - agent1 = JournalOrganizerAgent(str(config_path)) - test_results["specific_config"] = bool(agent1.config) - - # Test 2: Load with no config file (should use defaults) - agent2 = JournalOrganizerAgent(None) - test_results["default_config"] = isinstance(agent2.config, dict) - - # Test 3: Load with non-existent config file (should use defaults) - agent3 = JournalOrganizerAgent("/non/existent/config.yaml") - test_results["fallback_config"] = isinstance(agent3.config, dict) - - result.set_success("Configuration loading works correctly", test_results) - - except Exception as e: - result.set_failure(f"Configuration loading failed: {str(e)}", e) - - return result - - async def test_command_registration(self) -> ValidationResult: - """Test command registration and basic info retrieval""" - result = ValidationResult("Command Registration") - - try: - from main import JournalOrganizerAgent - - config_path = self.create_test_config() - agent = JournalOrganizerAgent(str(config_path)) - - # Test command listing - commands = agent.list_commands() - assert "organize" in commands, "Should have 'organize' command" - - # Test command info retrieval - organize_info = agent.get_command_info("organize") - assert isinstance(organize_info, dict), "Command info should be a dict" - assert "name" in organize_info, "Command info should have name" - - # Test all commands info - all_info = agent.get_all_commands_info() - assert isinstance(all_info, dict), "All commands info should be a dict" - - result.set_success("Command registration working correctly", { - "registered_commands": commands, - "organize_command_info": bool(organize_info), - "all_commands_info": bool(all_info) - }) - - except Exception as e: - result.set_failure(f"Command registration failed: {str(e)}", e) - - return result - - async def test_conversational_agent_init(self) -> ValidationResult: - """Test conversational agent initialization""" - result = ValidationResult("Conversational Agent Initialization") - - try: - from main import JournalOrganizerAgent - from conversation import ConversationalAgent - - config_path = self.create_test_config() - journal_agent = JournalOrganizerAgent(str(config_path)) - - # Initialize conversational agent - conv_agent = ConversationalAgent(journal_agent.agent, journal_agent.config) - - # Test basic properties - assert hasattr(conv_agent, 'agent'), "Should have agent attribute" - assert hasattr(conv_agent, 'config'), "Should have config attribute" - - # Test initialization - welcome_msg = await conv_agent.initialize() - assert isinstance(welcome_msg, str), "Welcome message should be a string" - assert len(welcome_msg) > 0, "Welcome message should not be empty" - - result.set_success("Conversational agent initialized successfully", { - "welcome_message_length": len(welcome_msg), - "has_required_attributes": True - }) - - except Exception as e: - result.set_failure(f"Conversational agent initialization failed: {str(e)}", e) - - return result - - async def test_dependency_management(self) -> ValidationResult: - """Test dependency management and graceful degradation""" - result = ValidationResult("Dependency Management") - - try: - from dependency_manager import get_dependency_manager - - dep_manager = get_dependency_manager() - - # Test dependency status - status_report = dep_manager.get_dependency_status_report() - assert isinstance(status_report, str), "Status report should be a string" - - # Test missing dependencies check - missing_deps = dep_manager.get_missing_dependencies() - assert isinstance(missing_deps, list), "Missing deps should be a list" - - # Test module retrieval (should work even if module is missing) - yaml_module = dep_manager.get_module('yaml') - # yaml_module could be None if not installed, which is fine - - result.set_success("Dependency management working correctly", { - "status_report_generated": bool(status_report), - "missing_dependencies_count": len(missing_deps), - "yaml_module_available": yaml_module is not None - }) - - except Exception as e: - result.set_failure(f"Dependency management failed: {str(e)}", e) - - return result - - async def test_error_handling_framework(self) -> ValidationResult: - """Test error handling framework""" - result = ValidationResult("Error Handling Framework") - - try: - from error_handling import ( - JournalOrganizerError, ConfigurationError, - APIError, ValidationError, ErrorHandler - ) - - # Test custom exceptions - test_exceptions = [ - JournalOrganizerError("test"), - ConfigurationError("test config error"), - APIError("test api error"), - ValidationError("test validation error") - ] - - for exc in test_exceptions: - assert isinstance(exc, Exception), f"{type(exc).__name__} should be an Exception" - assert str(exc), f"{type(exc).__name__} should have string representation" - - # Test ErrorHandler - import logging - logger = logging.getLogger("test") - error_handler = ErrorHandler(logger) - - assert hasattr(error_handler, 'handle_api_error'), "Should have handle_api_error method" - assert hasattr(error_handler, 'handle_validation_error'), "Should have handle_validation_error method" - - result.set_success("Error handling framework working correctly", { - "custom_exceptions_count": len(test_exceptions), - "error_handler_methods": ["handle_api_error", "handle_validation_error"] - }) - - except Exception as e: - result.set_failure(f"Error handling framework test failed: {str(e)}", e) - - return result - - async def run_all_tests(self) -> List[ValidationResult]: - """Run all validation tests""" - logger.info("🚀 Starting application startup validation...") - - test_methods = [ - self.test_import_main_modules, - self.test_agent_initialization, - self.test_configuration_loading, - self.test_command_registration, - self.test_conversational_agent_init, - self.test_dependency_management, - self.test_error_handling_framework - ] - - for test_method in test_methods: - try: - logger.info(f"Running {test_method.__name__}...") - result = await test_method() - self.results.append(result) - logger.info(str(result)) - except Exception as e: - error_result = ValidationResult(test_method.__name__) - error_result.set_failure(f"Test execution failed: {str(e)}", e) - self.results.append(error_result) - logger.error(str(error_result)) - - # Cleanup - self.cleanup_test_config() - - return self.results - - def generate_report(self) -> str: - """Generate a comprehensive validation report""" - total_tests = len(self.results) - passed_tests = sum(1 for r in self.results if r.success) - failed_tests = total_tests - passed_tests - - report = f""" -{'='*60} -APPLICATION STARTUP VALIDATION REPORT -{'='*60} - -Summary: - Total Tests: {total_tests} - Passed: {passed_tests} - Failed: {failed_tests} - Success Rate: {(passed_tests/total_tests*100):.1f}% - -Test Results: -""" - - for result in self.results: - report += f"\n{str(result)}" - if result.details: - for key, value in result.details.items(): - report += f"\n - {key}: {value}" - if not result.success and result.error: - report += f"\n - Error: {str(result.error)}" - - report += f"\n\n{'='*60}\n" - - return report - - -async def main(): - """Main validation function""" - validator = StartupValidator() - - try: - results = await validator.run_all_tests() - report = validator.generate_report() - - print(report) - - # Return appropriate exit code - failed_count = sum(1 for r in results if not r.success) - return 0 if failed_count == 0 else 1 - - except Exception as e: - logger.error(f"Validation failed with unexpected error: {str(e)}") - traceback.print_exc() - return 1 - - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) \ No newline at end of file diff --git a/./tests/README.md b/./tests/README.md deleted file mode 100644 index 4f0c23c..0000000 --- a/./tests/README.md +++ /dev/null @@ -1,247 +0,0 @@ -# Testing Framework - -This directory contains the comprehensive test suite for the Obsidian journal organizer project. - -## Overview - -The test suite is organized into three main categories: - -- **Unit Tests** (`tests/unit/`) - Test individual components in isolation -- **Integration Tests** (`tests/integration/`) - Test component interactions and workflows -- **Property Tests** (`tests/property/`) - Property-based tests for comprehensive validation - -## Test Structure - -``` -tests/ -├── README.md # This file -├── conftest.py # Shared pytest fixtures and configuration -├── unit/ # Unit tests -│ ├── test_agent_core.py # Tests for Agent, Command, Skill classes -│ └── test_error_handling.py # Tests for error handling framework -├── integration/ # Integration tests -│ ├── test_obsidian_skills.py # Obsidian API integration tests -│ ├── test_claude_skills.py # Claude AI integration tests -│ ├── test_organize_command.py # Full command workflow tests -│ └── test_conversational_agent.py # Conversational interface tests -└── property/ # Property-based tests (placeholder) -``` - -## Running Tests - -### Using the Test Runner - -The easiest way to run tests is using the provided test runner: - -```bash -# Run unit tests only -python run_tests.py unit - -# Run integration tests only (may have import issues) -python run_tests.py integration - -# Run all tests -python run_tests.py all - -# Run tests with coverage report -python run_tests.py coverage - -# Show help -python run_tests.py help -``` - -### Using pytest Directly - -You can also run tests directly with pytest: - -```bash -# Run all unit tests -python -m pytest tests/unit/ -v - -# Run specific test file -python -m pytest tests/unit/test_agent_core.py -v - -# Run with coverage -python -m pytest tests/unit/ --cov=. --cov-report=html - -# Run tests matching a pattern -python -m pytest -k "test_skill" -v -``` - -## Test Configuration - -The test suite is configured through: - -- `pytest.ini` - Main pytest configuration -- `tests/conftest.py` - Shared fixtures and test utilities - -### Key Configuration Options - -- **Async Support**: Tests use `pytest-asyncio` for async/await testing -- **Coverage**: Configured to exclude test files and generate HTML reports -- **Markers**: Custom markers for different test types (unit, integration, property) -- **Fixtures**: Shared fixtures for common test data and mocks - -## Test Categories - -### Unit Tests - -Unit tests focus on testing individual components in isolation: - -- **Agent Core Tests** (`test_agent_core.py`) - - SkillResult validation and serialization - - CommandContext creation and validation - - Skill base class functionality - - SkillChain execution logic - - Command registration and execution - - Agent command orchestration - -- **Error Handling Tests** (`test_error_handling.py`) - - Custom exception classes - - Error context management - - Error message sanitization - - Security audit functionality - - Global error handler patterns - -### Integration Tests - -Integration tests verify component interactions and end-to-end workflows: - -- **Obsidian Skills** (`test_obsidian_skills.py`) - - API client integration with mocked responses - - File read/write/append operations - - Error handling for API failures - - Multi-skill workflows - -- **Claude Skills** (`test_claude_skills.py`) - - AI analysis integration with mocked responses - - Content transformation workflows - - Batch processing scenarios - - Error recovery patterns - -- **Organize Command** (`test_organize_command.py`) - - Full journal organization workflow - - Skill chain coordination - - Partial success handling - - Agent integration - -- **Conversational Agent** (`test_conversational_agent.py`) - - Natural language intent understanding - - Multi-turn conversation flows - - Response generation - - Error recovery in conversations - -## Test Data and Fixtures - -### Shared Fixtures (conftest.py) - -- `temp_dir` - Temporary directory for file operations -- `sample_config` - Complete system configuration for testing -- `config_file` - Temporary configuration file -- `mock_skill_result` - Standard SkillResult for mocking -- `sample_journal_content` - Realistic journal content for testing -- `sample_obsidian_response` - Mock Obsidian API responses - -### Mock Strategies - -The test suite uses several mocking strategies: - -1. **API Mocking**: Using `aioresponses` for HTTP API calls -2. **Service Mocking**: Using `unittest.mock` for external services -3. **Dependency Injection**: Providing test doubles through fixtures -4. **Response Simulation**: Creating realistic API responses for testing - -## Writing New Tests - -### Unit Test Guidelines - -1. **Isolation**: Test one component at a time -2. **Mocking**: Mock all external dependencies -3. **Coverage**: Test both success and failure paths -4. **Validation**: Verify inputs, outputs, and side effects -5. **Naming**: Use descriptive test names that explain the scenario - -Example unit test: -```python -def test_skill_result_validation_success(self): - """Test SkillResult validation with valid data""" - result = SkillResult(success=True, data={"key": "value"}) - - assert result.success is True - assert result.data == {"key": "value"} - assert result.error is None -``` - -### Integration Test Guidelines - -1. **Realistic Scenarios**: Test real-world usage patterns -2. **Mock External APIs**: Use aioresponses for HTTP calls -3. **End-to-End Flows**: Test complete workflows -4. **Error Scenarios**: Test failure modes and recovery -5. **Data Validation**: Verify data flows between components - -Example integration test: -```python -@pytest.mark.asyncio -async def test_organize_workflow_success(self, command, context): - """Test complete organize command workflow""" - with aioresponses() as m: - # Mock API responses - m.get("https://localhost:27123/vault/Daily/2024-01-15.md", - payload={"content": "journal content"}) - - result = await command.execute(context) - - assert result.success is True - assert "created_notes" in result.data -``` - -## Test Maintenance - -### Regular Tasks - -1. **Update Fixtures**: Keep test data current with schema changes -2. **Review Coverage**: Ensure new code has adequate test coverage -3. **Mock Updates**: Update mocks when external APIs change -4. **Performance**: Monitor test execution time and optimize slow tests - -### Debugging Tests - -1. **Verbose Output**: Use `-v` flag for detailed test output -2. **Specific Tests**: Run individual tests with `-k` pattern matching -3. **Debug Mode**: Use `--pdb` to drop into debugger on failures -4. **Logging**: Enable debug logging in tests when needed - -## Dependencies - -The test suite requires these additional packages: - -- `pytest>=7.0.0` - Test framework -- `pytest-asyncio>=0.21.0` - Async test support -- `pytest-cov>=4.0.0` - Coverage reporting -- `hypothesis>=6.0.0` - Property-based testing -- `aioresponses>=0.7.0` - HTTP mocking for aiohttp - -Install with: -```bash -pip install -r requirements.txt -``` - -## Continuous Integration - -The test suite is designed to run in CI environments: - -- All tests should pass on clean installations -- No external network dependencies (all APIs mocked) -- Deterministic results (no random failures) -- Fast execution (unit tests < 2 minutes) - -## Contributing - -When adding new features: - -1. Write unit tests for new components -2. Add integration tests for new workflows -3. Update fixtures if data models change -4. Maintain test coverage above 80% -5. Follow existing test patterns and naming conventions \ No newline at end of file diff --git a/./tests/__init__.py b/./tests/__init__.py deleted file mode 100644 index 93a2d4b..0000000 --- a/./tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Test package initialization \ No newline at end of file diff --git a/./tests/__pycache__/__init__.cpython-310.pyc b/./tests/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index d784485e21548da0347e3af1f858cbe2bb122d41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmd1j<>g`kf>W15GG&4EV-N=!FakLaKwQiMBvKfH88jLFRx%WUgb~CqSN+i9)S_bj z^31%HO8tPM{H)aEl4AY*q~gq!%)~tXtRnra{L-Sl#GLs2qV&YP%&OEP{gTvTpqlvj f%)HE!_;|g7%3B;Zx%nxjIjMFa%0Uaju65SXPq$fxzk~4ZwaFoXCTN4*6L8vn!P{QgE=x2$VUi5# zxGWQcbe@xD*I~>ock#^82hLL)}uSVSmc-1eyNqq{R30O{vw8KI1WYi(>xCK<|z!>$z zsPouK$8~>BdBqKZTh}4R0`?Zj24z~rueKiW9!Y_LNN&P(Rtlv# zd0v`R=A|>zl=MsRqbX@lorjgkU(-RSsOI2o~_^YO$u&C6}Oz1FfDIDuCVn9Zst zaTv%Oz)rXfI!DQIDF+Lsq72d@7^S_F1ObM373qAu5;|_AGcRNmO(G~QU?z+bPwRk` zZGveVj6|V~YRKKPz{1-UoYgO)3$z|oN{8_V4E`2OQ*h~9q%-oAJSU+Ggs_`|R6|sv zk<_e8^jU?ZF_4J?P$z-#1xz=FDXe|7+8JdE!>7Ntv#mb2y|PQ638R+&jz2b1Fz{Zi zQ|L>z4dY>0=KJz_Hz?Zfc)-Jbqu<#*x~JBKM8kI389qs?KZNj6E6fGADmxY+BI6Wg zPN`~f?n0;-f|*r@K#MXEqM-$JwI0Eu=-L%RkxOlJvfnejcbEshPs8ESU8CRHy=!#T zk$NR5r?RbJmMG-~gu0R8*{7{AI7xjTr4HJTTdf&y!!vyN@bJNdIi=7~5NY`Ve8?&V zE`f;bBoI9SFwYKagFo?uNtco_nm=^93=&faCtmAYLH`tDCh$4v&`(z=TFkn^S@1Z4 zA+9nl-;eZInL;{=fJhH2VYw1w<^YFsSfuihflBBl>;=?Ik8|5IZWLwXE3@L$mw=ML z3R6bS$hxK|imb@m*V_Q!3VyUr9vOBu$!8As>N5?UwkKNr)}{dmDyuGOhN_wv(;ISS+-N zt9F#3m(_ZOm&L~;RoinTdGZLRVaSqxqzQ~hDqcCK#U?^EqR#k**p=kcv!-etW85AB zdfp9FM(u*QGybC~y-ziT?EzE~#+oMukT3`4wNR98qB=6h*<}?XNUy!zR?dU`Z?4Mv zYxFuEX{cDFfuGs6jmKecf<{TXDh3#QAtj(_npcY8ZJh0ePePJ`tvv?$gmnPCN->m( zVLpla@XHp*dSgKpL3|zoG{OhV+#aY1=ytzN6k z*BZqz_j4#<=F$DNH~3=Nr!Pu{J6{OQuob4yRI)~}p5 zhRyneOO1=~Eq-ynIsf_6tp{+_yl{E>?%C#rOH22DzjXT#K(qMSmHMsQ%^M#se|2kl z{t^&3W`Eas{~{1K{`_Hc`ljGrzkj!W@e$w_Kbu{?^A+;Ge|K@=CX!!AJoIGafnQ(v zZR7r(rG@LjzVYsK{jZ+@<K|W9h zxHjfLGFIKah`W{GTc2d%Rzs8AmjgE{Tw7Dwe%L>E_iHi>w)=7YhI*Zv#oDaba)~GW zENPoie-q-n43&kh@rT-uAyZ_OBT&)|}^aw8Q_e_{K!>R%OG~BSf19?z{^orCo*er|eDq=FTW> zStKZ0R+KqWaoiAk)u?NKxoWkcO;%Yn^dmNh9uk}R5k@B&^yX1V8x^HnI0IfrJuRC> zdc4KoW)j1Wk!^qp>S>inX>2lF=S0%1h1D`t8-_Mv7<>$`Bg$5MCq!-|7}{|@!^FVy zc&dp$MF){VF18a<8Oy?w0_x~x_^|AzVn9@Y(winJDu;=zJy+iuEQ2KF5=QZNh{>$~Ou0`Yce82|tP diff --git a/./tests/conftest.py b/./tests/conftest.py deleted file mode 100644 index 4c76c67..0000000 --- a/./tests/conftest.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Pytest configuration and shared fixtures for the journal organizer test suite. -""" -import pytest -import asyncio -import tempfile -import shutil -import sys -from pathlib import Path -from typing import Dict, Any, AsyncGenerator -from unittest.mock import Mock, AsyncMock -import yaml - -# Add the project root to Python path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from agent_core import Agent, SkillResult -from error_handling import ErrorHandler - - -@pytest.fixture(scope="session") -def event_loop(): - """Create an instance of the default event loop for the test session.""" - loop = asyncio.get_event_loop_policy().new_event_loop() - yield loop - loop.close() - - -@pytest.fixture -def temp_dir(): - """Create a temporary directory for test files.""" - temp_dir = tempfile.mkdtemp() - yield Path(temp_dir) - shutil.rmtree(temp_dir) - - -@pytest.fixture -def sample_config(temp_dir: Path) -> Dict[str, Any]: - """Create a sample configuration for testing.""" - vault_path = temp_dir / "test_vault" - vault_path.mkdir() - - config = { - "obsidian": { - "vault_path": str(vault_path), - "rest_api": { - "url": "https://localhost:27123", - "api_key": "test-api-key", - "verify_ssl": False - } - }, - "claude": { - "api_key": "test-claude-key", - "model": "claude-3-5-sonnet-20241022", - "max_tokens": 4096 - }, - "journal": { - "daily_notes_folder": "Daily", - "date_format": "YYYY-MM-DD" - }, - "output": { - "experiences_folder": "Knowledge/Experiences", - "lessons_folder": "Knowledge/Lessons" - } - } - return config - - -@pytest.fixture -def config_file(temp_dir: Path, sample_config: Dict[str, Any]) -> Path: - """Create a temporary config file for testing.""" - config_path = temp_dir / "test_config.yaml" - with open(config_path, 'w') as f: - yaml.dump(sample_config, f) - return config_path - - -@pytest.fixture -def mock_skill_result(): - """Create a mock SkillResult for testing.""" - return SkillResult( - success=True, - data={"test": "data"}, - message="Test operation completed" - ) - - -@pytest.fixture -def mock_error_handler(): - """Create a mock ErrorHandler for testing.""" - return Mock(spec=ErrorHandler) - - -@pytest.fixture -async def mock_aiohttp_session(): - """Create a mock aiohttp session for testing.""" - session = AsyncMock() - session.get = AsyncMock() - session.post = AsyncMock() - session.put = AsyncMock() - session.delete = AsyncMock() - return session - - -@pytest.fixture -def sample_journal_content(): - """Sample journal content for testing.""" - return """# 2024-01-15 Daily Journal - -## 今天的经历 -- 完成了项目的重要里程碑 -- 与团队进行了有效的沟通 - -## 学到的东西 -- 学会了新的Python异步编程技巧 -- 理解了更好的错误处理模式 - -## 遇到的问题 -- API调用偶尔超时 -- 配置文件格式需要改进 - -## 明天的计划 -- 优化API调用的重试机制 -- 更新文档 -""" - - -@pytest.fixture -def sample_obsidian_response(): - """Sample Obsidian API response for testing.""" - return { - "content": "# Test Note\n\nThis is test content.", - "stat": { - "ctime": 1642204800000, - "mtime": 1642204800000, - "size": 35 - } - } \ No newline at end of file diff --git a/./tests/integration/__init__.py b/./tests/integration/__init__.py deleted file mode 100644 index e27cd7a..0000000 --- a/./tests/integration/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Integration tests package \ No newline at end of file diff --git a/./tests/integration/__pycache__/__init__.cpython-310.pyc b/./tests/integration/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 68392f3e5de584ee540df2e55a149d1e843ebf79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmd1j<>g`kf-9FoGNpm^V-N=!FakLaKwQiMBvKfH88jLFRx%WUgb~EAK>g6-)S_bj z^31%HO8tPM{H)aEl4AY*q~gq!%)~tXtRnra{L-Sl#GLs2qV&YP%&OEP{gTvTpqk9Q rlGOB~#FEVXJpK6i%)HE!_;|g7%3B;Zx%nxjIjMFatBRR`1PcQI(J(C` diff --git a/./tests/integration/__pycache__/test_claude_api_integration.cpython-310-pytest-7.3.1.pyc b/./tests/integration/__pycache__/test_claude_api_integration.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index 3f7b4780ae0ba64adfd193a60ca265954d33e709..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8868 zcmcIq>u(!ZcApo?Aw^2EB+JjljP31)-RS9O+&Hl#$*DKC62#6fLZBUtc}LPn!x`q@ zp>0jbVo|3>TOhmtfEJMYO+bqRi$x2x|3eEDMN!PhF0jR7fqv>Iwd?eE?hL7+EIBSV zr7(ByJ@+~1o_o&koU5QxDJl5<+pizGe}7g{enExoFOR}4JmTMxFr}_Am1&+@*W_Do z8k(Xi&0IaNCN+h6LDracQ@)G!qI{R?CA@WSs9CO;Wn06mG>7ZM&5`;@bF@AxX*us$ zbF4nr9Iub3<4x2joQd`0+mlRnPB>-fW(H`g|v3RvUr8;;y#SCSp^BPP6n-wtTSRdY-60al^GLb5~Xz z?)c%6eZ^Z<$A9edz;9Bs$2NEEN1g++?X&75yRq@a<_z;STUgE`*K@` zPARP56juypvf`HpWTM1|&;onHDtMRKFzrEno|w6`&zT>jjLGawbeY;5%D$>-I6is= zqU!pCo7GUU)ZUNsI-UhQ;yjX23Ds?FM|q}p6iW|{XA0iAFfZSb>j%oN7EL^G!fLxE zC8N6HJ`LO45z94`YZxImAquS(*K^`RbAwSKAz94jyhttbo@6tpa~GSu8J09}IQ%jvngk#$$z_p?62WseC>vPo)t} zE_jaZzrc4+d$^11f0sNw z>dHZ;o@mIqt;_@#HT@p?ieV%rFn3rjeQju?&kZfQ@d~UxV#onOCQcUcNN)ZS#Eq$; zvJNi31D?s8fgDsEE09v#(p?oo4gn|b?nvQgIjFtTDW$73wF|NC7#mtQ*U5>yp|PD~ zL!I1;%F0Av(RK{b^4JwszKtD~RkpNArJHBNY^0NSRpbj{0kvkQu%kXx|5#%J#!4 z5|^nBXLD+9{?g?uSFc@vd-hdx`~68)GmW+ggXR>p=(O|HZnivU8g`kV zM58#LsMu)o;ul%%lIo%EVRWdX?%wI7-_)h4D^nuyeJ7lnpPj!vH#*jRg# zX>%{mH3Q~&d_MJCdn{zc$y?L`r*~>MttHnBKlw95$wgoInDC@4) zps`w;SC{!ThTtr$lygY7&f#dcbsdHvZRZ{G$5^Wpm z2C9VKq)JMuDgoZ=tCAdQ^LH6+XOq;WD;wSqik{5sXWP00;PevcA@N&F@y?@~fIFoP=2 zA1zueWd#{~F^1%T`05berQb?;dowK8DzRxzD=t|U6jIxxeA%)-Z`)p4Q?M)+ zG%Slx(nq{b2_ef^7a`|lP9<9A-=*9lCA9R+7t3!aFvW+EkRp~g-c$_L)Xe62?TF81_S+8SO%ID#pSt6ItQ&)tp zzgm^pSPYzYLaVes2`0+Ogo^YnRPrr25o(%r4EQnRIc;TzDUu(=&^jJLqwCzs&sBh?3dQSw#dO0sqlAY8 z6eMytLmIc(F;nDD=2lCE-oQKG&Ee~S^w}so#>TdCY@AK7<7{$EXD8T6c8Z;5XV_Wx z+E#v5aTWIZmd4JpDtiO(^IN%Yk$k-jKZ=qc7Y6w8pI8A7@R9uZThu4~_y_qVNAcrg zpCA7udlbQse~~?q13xax{BO?>0tVkn!#&Ii5eG?+=79exs^OX)R(;1|`v*9K4J!SE z7_{1g--k#&M5s%1MNN&qAmDthCQ7|bwU4C}5c;kb<_v@aj9uPwZdS-@kKmD(J2R^Jp{1g+@kGz zn^EaA{HE^TpITgu^ywx;WMzy|jpYYW20-v0LWJ=Uf)7p#=i^~=!-1ni42qqJj(_L} zPdtaMIx|a8F@fv&4M#-B_R04g0evZ=p@+8E5HrctWBc_8cOeSe6w%~9?RI0$bsjqu zCmo>QZMJxj(!szn;Q8K^z10pQqDu4i%H@!z?UhE*U(VJejN6&1`b0M~PfA39wbTG3 zRs(#ah)2N!ZcFL3I6x70=kD^IkD}9Fuqs}R)=h(q5po{B%8LzRBKC{GY+Oh~dvQK! zhpl!Pn{b!BP2nOQgf4_5NLhVt;G>Tq(5xBKYlAWR=Yp0?d^mV<|E-ycH>c%$G*I`nBGTv7 z&Ul{xkAFQEExx+xI(jk7Fxwx{NBlk|cPaTkB@zmMOu46&?0@1u*u95}AL0>Y^-{2F zlt($yj|RF`)V>_(LW36KGxTu_*{h2m*q)1%Oo#TnR5=(c$8|)ndOxE6f^8-MDQ)v# z!8V6|`1qAhppiQ8%>UVTreQ9aP7&HX6m)r(no9(%U@j3~OKz-k{1aNt14_t1O7EHs5fxT(MmMy_{*;hVU>IFY@-Nc-iPhJM4cpN!Uj zmE3&{_Pl&Z&ywsplJ*3iKiHGy9~k|(UmotCULPMz}u8P1<^-% zfr7^}lC|G{-?@z{y@4f7!sMC}=aZN(uA6LE0N9jZIogg4>n37;NTec8g0AU!sRX5$ zMOKd+e@$XU;h|Bzte*XPRGM#1HJ>A2NKCc){QTF^d*3KjFP~?*mOs=cKO<)R86^{g z2^|BziR7nv1PNhcj>{EEtzD2dJvMtlN}lWZl$9`QIys9ltkc|i!rc&o@jfXoq_ji> z$msi@&~z0_E+B~uxXWMj+(*fsa;5FNA)UzSCa(3lPR;0&kLRduKHO|Uu4H^ohSm^d z+a%8bL&SMtJOuywhtv{*I|=stC+D;^7mq*UoK{hL ztP|V)eczqgodp4svFL<6^Wn~&JNJHbAK(3bkGq9fO!45~vj4g(_pRUdcz#MZ?tfm~ zY{#Xa1W`S`9#vB1ywof6U+=I__DG&#e{Vn%PlCNceiG^p;jcFz9#(pl;Ye>}INBTK zGQNClINlo{Zs={`dw)JL+}PVV+|=77c|PM&18VRgj~YBE_cr4$q=s=9#$6J3iW;!C`ixv*BxPiELr)D9wA7A#Us27b?zuOY&+F@yUHm}Y zr5?x`ds4%N{(G}(>dr6Sozk-UXkkRp>M5g;x<9Ms2KJ@yygQY1RXk7}>8BR@@;PJQ zI%N(W;@O$@TH!nOeIxx}KwXx9w9n|@WBKSNEnD#&h2i185p_oa)$BE_2si%atX|9; zc-$lyyc0Ff-`=0w4_fh*ks>b;Jn0+5t8~8r1CeZthRx9=E z)n8ItbFD#fe{;X&`ZP+@wFB3?0T9D;(1UiQ(#joLw$I4gqw1~zqof;sBmLR5qy_QH zq-6Q@Y<^(Uqs4Ih{_n5yD~_?TUxqshpgj*{Zz>LM`=GU z`dB>)V(=nTy{d0aHYL@6%!^MCl-Apy-Y=$8s$a|}F^T(V`sg`5HQJ~34QCB}sTJhR zQnomZzFd{++0e78YeRQe_vX~6`f~Ywsn3beRg_h$Qdce?seE^G=IMuKE+4Nv_K4Dz zs!Sc7K78WJrAOu_UY|KVg-3ITj?F%IVD8Y-nHT=m%$aYZ%$19eRZgFoJN4}B+oxwI zj-v4N*rU^rA4cKn?>{@Y|7otfa_+gx;S0#Sa&c_-?AuiLx#zCD@iY~Gg7Uw#&)8EK zsl4&v^trP$Z#;qOr;qHfy!Jdw&YXB;_WAEn$cpFHBV4 zIL?jC9XKQ!nR{|-?#wB?iUmD;;o!{47b*u{tz0-c`|88fPri!$xrdLo+ply`vB#SxifPQnrFTW+R3bbH*!U>>JHadabBEs+kdt zp5^P)26Zcd(gV4{NlByWF)3?N{IOzs-|%QYn-LFrZVM8pr=o?uMhk9q%8|XYB+GJ> z?{DRnz+7qhBGb~IO#LKNoPShD2-lBr>3;Pf zcaqwmCh(+DZQ{G;F>l$6X`MVMosf@TF8d7Wpr`CN{ibh7&UpDQU`Ph!mg!Yn)YfCN z+ICDDl*~XTUClA4f5Q`!x@^qb z>M2Lm+hg+ zRK3NFgOin%Pn>Z5G1On<=w;*yj$Y-bZjOkPRX4;*44lNl$?B^)IU(syH^|HXtMc*> zH-VQAEyzpnjqw7mKjCn5Lef6(2^)@rg zGpW&@NsaSo62>qAj)>3Ib$oW!@hR(`>+<YFCFxT?hVS>UzkIn5keMVC9~W^`~sUY3**HC+@XNT$&hR zwR=p)$Tq7z>PEAfM>c7XEapU2Mu?TE*zIlN_BPLJ?{P@gc7qFYpK6>8X%>}ranxKvT;G2~rr{AMp&c+;^ZTa@(2KP+* zEWeR6@>wgQX7zq8H%chn3iSgG4i>b1y-IF)w4gBpEH5VZzJ0chAMx2RFo1J z&E2{c(zB#i8T)#h^y1(k=3Q3J=!JZdH~|cbxbTt{%ws-69kW8CT48rSJFLII%!b@< z0KTqo#Mq-1Msxk22Ih}uNA#kW&GhO0xg6;Lq;u`ug_NArC#BN*53L#9*OS(jKe7|4 zZ7E7^NbBqDC7_g30MeAXISmvBr4X7LnDe3(ksczO-;ej{P$csmLr^L8zbj^nmQ zpsd_Rb#$k7s3uNzqFK~yH@gwds(nVEQciON-Bis+Z7ZtL))M&$$Q-E%lhXV0wr%)= z4OHtU=L?XieZd-fgpW((6Sj%(V$Sl}kx8Gnh3dNkGMm8#2s^_dRhMA zf||{jIz?A^ZS30Ig~1rf8eODFbZ_YC0WVF?7uv0X8{pJ!)X-h?tKTB3-(o4leS0%T z;a-eX&I6q(-KG)j)O^%z_)cdut*DkYppa$m@5>i~iHFd<C>#nOn+ZKpV6|T8qhWrrK}ajk@k9m8SrWG`+IYXLyA4C2; zBqxg}AC0ZHgSc%H(}j7!q%S}vu)@p`f&Fv~qkD}OqqdC7$H*=L4unO*it}xCs#_7h z@6SWGFsvxw*xjLRqSqtzWunP$_su5v%|>w}7~kT)+3GH~!F|)?zOmmC?Q9m^DQfH9 zp!HSx5Luhu4ctCob6F~eNti)0jw)`CV-Kp*e-}jFF9)E|;J@!5|4LSB z>A+lcWk3o^0dKpcND29fd_+15==K8tk9rkpRMMo?bES=oC`Q-fs!m%X<=@ek-rbEQ zbU><8_E2oh+`L|Q?1ep>6M^hP4IUbM|b2q3UrWC5dn47U(+8Ar(3 zVjOZ(h;bxr*$XKvvXHQ|AmbMj_E{1ZN_kzr@IV%S*FaQH`h~>&IJM4_pG33v8IboI z#M0vJqZ?a^<-?BBJsD$@fc6zC_d7(sN`w}O_IpHniTpm1uMznJB7X>C`MG_|&&^LJ zTta6ll_AL^`eT4ngGBZa$q^YMaxal#5VsuIs^uxWK-nx)Z6VMzf0cLEttTESvSb*L zVxxFjr(wbxhtVR0H6HjyB5*|hzEtYIk=0pOTix1Rf?C74$dd8yL)cBl+JrSiI^GS- z1X&}n5=}2#BYdiSP{K;^4+Szotk;ljbMRul!e%iP##-@Vod(Px)@#TN>0dLH@rdd- zLs%JPuL$yb4I0t$7_ZlDcpf*gGPO@4)j%n%*C3~9MNl5Jel^7FH4MuMRw%Co#Yo_3 zBdsuA30`{zgv!A|tOYfKb?P5#!a9w@`hZo6FNvEGGmbS2NkY1$eprn{yQNZ%W+O^L z^FgU*GhsHPRMJeMR0A~IHnbFhq#&uob@2Ko-mqH1t@AbCMLF>`lgzpMYp|X<4;Bvo z8^@asyuMpt5o$DB_^VpYzRWM(GwapterPIS=GdhuWsLn;l%L)9&?7SFblX zvIl>IxT3gNv%}v6t|YETTutR>XkHj6Tl0eP7Mho&f2>aPB9m@MX(dhT)E+no^0|@0 zRAIpJRsfHuc%|yvZJ4vAHp+l&Bb&m!*4 z@aaHPk}X19QOXu!H!SGZEiBa*5NO(7e9rqJmZ)f+#o0#4*g?(#M06PHsUloT*$nm6 zfICu7CZM}uy$2~fvwJIm^@3b+lUtZ>CEBxrX%Ev)g0&vPs`PA&_F3ZBErojsCeyP! zCR=N`Rr?~!XCg!0GUh7u#BiD*7X;C;-(hJEB@~ z2X5{z6!L}9$t5mP&QiReUQvng{;iYVpY?Rt z-EDN=b>FUiy;{4yH*69G#9dDvc1>BRsBPUkO7xLZ)MvA&Yj1EJnP$+=b^c!h721 zkcv|ZyGw}E*j4y1?2>AjcNc{$^`^pdE@A*_!IlO2&bx~b@{43!xE#Xn62!g{1xJmv zuVD9pWeod9gBdj&uy0`B!>$t2PpC2MD^v>m3Q9Gb%_x;L8_gt2wU{loWpxEwQn0Vk z9?5mEj3<5x1J=IgC6p6i^D=Yp{uD{Fkh&36}A#RroLLduozfZYO_*+0HF@aLXOM zyM(dB1=Titcj;8y)ef_hOD(Z`eTl2rX$Nt374j)KI}7bPw5zn>YQoiAPGWb#IN7_4 z&)!{j)a@?FO?Q@huG(F4BhaODYU*?Md~s*h*_|5D3d8osBX*mXyB8{_t;f8--iD+7 z3lt&`xCYw@;b-kZA_s{)MC4&2j}SRT)PwjuSZn0)5y^X|f5_WUGMXRjK+x z1gUnCYJQxWTDrLC%u=C$jQrG|q&FBNpQ6-LM4l#cn#dRt5=`1NM7~YrSt8>^CWw%; zS`c>Vt*jGn_5fzc7E-khP#@~;Ada95c;mLR%fjyOOLyFeuv@V1I>ATkQ&sT2#47F} zeAQz&lFQ0#n8jrn5FGA33R-~Rl%To-^uYnBKwQGz2X!?95F7>MjY3?Kn)(d_ zs%n(g)EMQnxC|Ka@dg%`{}RvHd=K+dR6uKp%K)cIEu}mXNpTjJ2y}q5>!_toc-m|_ zYALCt5nM4+lZTROBTV~nBbh$95dgLIAEI;&VyTe@5As>?Q2hjJ8><{mq8y$=fKPGj zNP(k92pDoNG_inbV$l&d1P2g+EU<}#M7a?n0VyF8Mnc^l{l{)`a9>bWAQ0M0%UB?& zIiL^?3Rl&>pCTu;4nr+zU%d^E@7gxM_9mPR#|+yW){6*f(I`ZM)a%m9d)OykbsX3l z9mn6$`~|qJ>E67aO36E&hy`I^<+3b(gq+#sQqbq=<}GMa+YYjT0TP(fF+hGCCF<;( z(8yW6hHQZ|+q0m6A@QYsnFt|=8i4yXL`?=2q?=u`g>XY}+_6=#=U>jT6}Yp)e- zRbO_|a4U@>0n<`xbzPuU{mbr9D>8X%5hAs&=3Z_<-VZ510pgB3sjq91fT#do+JQ@7 z4pOtDBG$w!;!ehZoz*ahMk%Gu*9wg?oG3aG8R!a-3RfR#)XaOgg501pQ(J+ulivA` zwN~MXaE1M3MhfspglM{|g$Ll{626OgwP{|5Rt#=}!K`5`b!?;5I?;qirmOi~OXA|+ z;?l{K$pJnT*B#ioj>^9L6Z@il1l5 zEy7}iT0>zL1ji{nMyNFrR%@I<9ii4>7q6-{cAyS8X;h=+f`?PzE8PA1^UO(%19jq* zUsVt&P^Y1)Xq{k=V7%8J=hl(JJ`H@p`*Z`VAPu}vFQiz-pg>XU%^U!bbO!+F|J}tw zOEuhpzR*#5$i&{t?IhSxmEd+7ZB@bvBq0UP2{)j&I=HP)cUw@1nr?n;>Js#*)3+Q- z0p*WEDJ1=FZfz;|?y{TnZR+wxTigE|Xl=!!t-W^hTU$`Q`*Sy*Q%Su`+3KAuY{Nla zRnZ~@9zvNN=~H|UpnX+p!B7qkd_?r;+>3~RI6nLK1x2|*S%~O`*|(pXerZC)j^0R+ z2Vw-=p%gRUe{1IKw|~0-K@l(d@GEmCrx1}j^Wf_W1#G%vL=X%?VIprmiNFUDrhx+P zszl(#^r^RIzxgD>J}OT5G+zUzr|1G;{7PFypQ`wW`W;YL)x*&>vl0%G%1|)0NAY@F{ao{@|mo$lkTe zT5vgi>BL7<(@&nAow_jn_zw_SvsPIIS+iimJ5+MZJkx3%Z6J7^43pQqFd zM79#SMC3IhpCj_yL_SaC3q&pxxd>8h16gGHIfm(KM1k#WgYlOmR1l-=o}5KO8+Bcl zZdwq~-y9g<5*cUtX6(iO*LY1|2?8kYjdlo# z?x_40^tb#dV~ZWv!PvS1bR2;VAzc9W28H0JR?|O;$tj zd;8(XW)lD#02CMiUR=xo0G~|6R{i~~`Ui%Btony+)jw*h{$Z&8q|F6TR6M?0V zJk;=7*{Xkn)Noe)Gk6{+Uz(<}uLvn@;N)Rr9~b{6)xXhJ{oNjE;fHfnfAap+RR1P5 zIZyR}8+AqCbt2Wjl}rK*DX1M%{gYLWUO>4xtN!P>b)otv)izu8C##{O`U?&m)t@*J zsy|XL)&DQuTLo1ADBA*8(KJX^6|z)y-0eNWHUQ8X?rpL`tkC}%!1gxVdA12aWXA1G zVGY=){Qz%UK5D5H<@Rjpk(OOJnFN9MIuSyzC9LaR4nW5#oE^d!tSENkT4sR-gqM&+ z+o^UIR`qfU+W4Z1|ILvTSE>6A)sD9HeHN8BM}*819Lwh|=;~mLmT@-Aqi;aZu%^$U zGeX}dwIT3xMR9a}N08XM{=22yX)G61C9d6Ko%kRv(9tk79XANxSi_T~+CyS0iZfX` zT_=yIv;EM_5*rLnAIvB2M&4Lx7G2jW&7=DC8&=)BAH>eDeG?UGPY{{c_wDm0SJfByTj+J| zJ9xt#6;hQU_t2TF`FmUS-JR*U_C0FsB_ijDOc6OxdjW8-v$$ngQ|DbjYqY|ys`9`Io02$UF zq7!{VoWX;dP5Uh0hd350Xf2^xA=WAPg6=X0mC~*o`*b9NAb00RXp)GRQX;sv?r<8} zMV#5E|1}#1=qz4Y=9n3;f%ArjmpJ32YCi1r;7V9%pqTs0ew2X8UYreLn+=sgtlOY8 z=z;S9Mt1la8*n1SrzY6caK0Qi{AdBjeKSlEID8i|{03YA@TkGhhtomLj1VFjM4h2* z)IjWwofpk0wDS+jtbiayWqJNm9nON05Sw&d8yi)8vh$Jn}|%Fmh;AJ8|}?kyZ32qZLov z%qH6yL2)8+-n$V=H`J~!1#*ZR9q_G6<%GIS#O1&+fw&y~@9<`$x}4);Ryc7nYO2cB zJ9wTn;BVA^LL5*EcqT<@_L0(G;0?Z~c$i!GBubOIQe9=EiWZx@migR)!z6g|qVM1b zwfnBsY`wbgpyJ<&7Ow8S)efI1u@WUX;s>W@Ze@ND{R3WCbNbirL70@}-Hj}l43py-L0 zm=A$MyEyIigfnXJGjl$C#p*gcj%qzlq=-1GW%lSTTWIva^V0GXm=o{?0w1bL+gp6a zjS(p~-7Nwlu;J3-Lc|%do1&EMqD`W!x{0J7C}{T%Ab61dKJ$Vq*@&wB2_yzvN3|5C zZXr^?eGBxr1`mp-Ko(I5ueL|rpgBV5^@kssdHJ&9)~PvPoMFWY)5@jil)3MnT3qiI zD)XncF8YA=AUjjE zgPt)`p`B#DD*XxM!8o6%Xeq=fiLfV%ni9eO)tD()$dps67BEFe9h}g4M4}21PYH^S z?=KWbjN0)51UqpMQQEI+M@q_$x$yhsaln{4SAehPrCxe-okiO6|+i%Y`OSJG6ZY7lX(fpXI{pc80wO z1m4?gkZaUA&YPjgeyg!Ie*9@T-k)$@s)p`2)e1RzH9wz~v;+J3_cZ8nYww1BkM+j~m+#L^&cN@dF}OpWTZ>-S9=}N#Ga0Ks4^e=Tq1MVzj(R z5K)lup1}6-s(1BtY5O%`jR1PrVX+R-tiqQ?as3FF{wl&t4j@dw259Vf21FxJ5HJoa zZwG|&;yH!&`iFcOKLth^0n>L-Ld3(iugT>Q4&CBNGysUd%|lTRCjY#`n0HV@yn}); ze)|bbAmP*S)IZ}dN4U;IA9`A?YiYHvUr`A%xnKkKaSg)gvXs*RLOA*zK-@(e zAN{t&h7J0LB_;xg*`q|ESOg4O@uhL}{NtDh34(4z2?P?S@ZB9|E1+ek*@^GpgLhkS z5WExLzmlHt_pfp|LW#RkqMe``c-qN)AdN3v#c9Ag{tL*Yu4aI_ME#KZVRH!s%%wJO zOI^HmfVWO?(Yzo(7f{>P{M_o|=bI?ePQh;A=Lv3GjLEH>{Fr;Q;D z?+h(1FSF;&a&=94g}K~Zk@S>P<}x#d`GfF599sP-^V`k=d}(^Z{kPV|?;P_BpbCC} z#ypDPy|s1x0@Cq4;8JbIpne<&7jx!>ti`Cr)w2erXts%YBWo$H$31VB+jXx%-5(hX zVgB5r-lle$x9}LPw8v=W{4v5fEyG3OeamsJD5n6}FuQEP)?^>s-BkzJkegmt+CXwK zwVQrxCIzz`9o{8f4ngF@KqNW9ILCaF8qH<#IPK*Vo8qTF=ABG+|LjI_HDn`xb))jg zD>wst<>G$$L@M7sHvP)J~n5KKxqsH$ZHR<+w^_r{ER2BpeWg zi%OBZs=H{4K~gb*Y;nLw?GYGQ2Y#UfgC_GZOxUdgFgo zyQIEzG=iI-r!Ob$Nv6R7V$K3zhAEJq6#q9K{WJYS6Wr&=AFLc1r{7o-zcg_8kt>(J zhu>$KothH5^l$q2oAB#Nk0Iz_f>_`mgmSCx@R?7)^0-jmZ`N--IUxh$*CF7=ubevt z=lzwpC#DaazjEA5ux|7;z-V*mAH-sjwaIs zFA*O#rQo>yhyi^T!IUfkY3J>27KYz-{jQ%ibIDuUdf8&lb5;9ybX%L=H31O%EfLW9 haoT-#ieB@1R{BCx5=%G%&Cc_UcsH(u=NqBb{~s;70~r7S diff --git a/./tests/integration/__pycache__/test_conversational_agent.cpython-310-pytest-7.3.1.pyc b/./tests/integration/__pycache__/test_conversational_agent.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index e6a33a9dbea7d256da5d758dd0c2987cd55fb5e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30756 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 diff --git a/./tests/integration/__pycache__/test_obsidian_skills.cpython-310-pytest-7.3.1.pyc b/./tests/integration/__pycache__/test_obsidian_skills.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index f31a08505be5571fe31d90f2df416a1dbe7aa95c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19763 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 dmk^x$ diff --git a/./tests/integration/__pycache__/test_organize_command.cpython-310-pytest-7.3.1.pyc b/./tests/integration/__pycache__/test_organize_command.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index 8cb04aac4b020eb5667c964ae0e0e0ea219acf2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20109 zcmc(H3vgW3nP%VH4|U&usP(kuXJeU%43;cm8!(Rm1`?8t!vHgxQ9_!wPFrr#Qj2?U zgVnh`v5~>X@r%LOfQ2m)Fjf+50|^)#!^~{$&g^4mE4!PW-8);gvo-3LG_~2Snj$qD z7bN@r|GnM1-Ik32SB+iw@AJCn|IfMS{O5liWx`>_g@2oW`EcsfZ@OInKpE#hH!^qP z(e?odSC30bLQYFPGXJ^9zTBF>A--}qYMOlf&-zR5V!H9 z!vo=-FsHrg$UtpR?Lf3AI#AbB$7Q~B{Xj!cgXH?EOVkMe4wvwc$UTk71%!f}f?Si3 zA9A$^bE%fW2E1sX;BKXJStz)zs$USaMLT(le{@gJy%h)O{kI(uX^g8O(!gW4qf7 z+@%c<_WoX`_c7Bul+b&(m>$ZsOJ*pM%BV?gC^M)fHMES_csFD~>{i_jK14_#OlsM* zZhG(POAhK2l380tbT1)t{wsfq-6&1EdL(=fe1;x(($nu1vT*NkjYvH{&Uu8Fb2Xgv zi5kxNkqh)I!Y=}x588P}1UVnF^C1!DeAv!ML@nndc0MZVIA1I3@fjM-2AbX~@^e?u z>(lf~jv-Q-o(*_3AAs%}aRGyrTed+>CiG50MKq#8hLOrl3irZ-Qx)CjT{ zzSsE&T2j?IpG*yk;m!xtOn;Rl}MMCEdWMI>@x`YRhSNKNV z$k*(3V-Ebe>+LzPdyDm2%ZV3!iGCFFiZ}hGk_$@r>h3kGR(GxHT8mP9?56TW0;?~M zg}cS{-jhhBhp|vS0&}c3f*3O-s4TuYlNL!8RUy0M_+VlnX?hZBpJw_%!RAz7yQI>$ zYM0e8y&2XL14HQ~CWg63%=k^!&J)^9O6;AFc?m94e_L-US_ot4WMAgjBz0=3H&+ObYb>fFpuRiyGKiE?^ z^6Jcs#|nG*Po5v0dF5!~%$b?-vxQv~czyEBzQV~@i@V;N{^-c`_#X5#dG?LM^V^Fb z9GrUXEZ&{o_g>+{!ziD6<5cnJ_QL+XGskxk-u+J%c8{UFxO-c1^i*LNUi``A2QN>b zJV_YeJYLv!nraW721&)E?-q~lD4rNEe6XL#Vb!B%^33S;>mN~nZyv|nruOYb4^s!8nSSF(C@*|? zvN*c8uzTD>OZZ2(O`dm9+utpmJ~;jE)5XK@ z()iE6Rd{|c$}zK(=bxYX@g6jud3x{E`IE&%J15V*TO9iUb1^fz4WFub@O{iazx90K z*}YcP^?R#a$@TI+x9F!yQ?_*QyS30WBBM(`%V>J9YIS>V&R#0XdaK8 zExM_o`CZ5uvQT@c=Jayue>^aYRBZc_xM{EU)*P{+3Azx zgx(fx2kkuX{&a z`G65H{QbUo4d)cytNZ%{hDUUY)q7>pwO8ts3?)wGUc+nUMfYB5&b+UxzA7(ntgIJn zX7hc%e9%yUE4aXA1p0&hp?KJk#v+D1F5T}EYrp2ohm25vZ9Xj4e^0-IbqSuc#jJuw2X`iCL(Fl!82P9QSx{1<*5?W9?JTCEX zWLy&KN8Qb?d{o>jZZo1O3F$h$8MO=a8SVZD zaMg*gU<8u5lX{_%Fm?5yt~?^N>_cg3OlfZ698-fmrbhd>32)yu>cw~#i@U`JV=<3y zi4h)KXe=3*unLLFzqGM00`_}|wlekwRoIU^u>a&D>@7CdR{M7$-oBTyFBSKRuNq4k z`!dG9jIg7JMMhYBZCp;dKx0hYFTQTXxYbqm{9omm|7QCe)Z;gTrvXnRo+dmC@GQd9 zf~PgVP<#V38|q(-Z-Fm?xvn?r56Id&d!+Rk={KpSJksWhk;>Yl@R(eUN~6_APGf!Z zZyRRxp-~T3!3y!P_yc1FuY#-XRdBUq74X;3w~0R#-`?Remfr&fCVx$zOZ?HRQvO=w z+Iw7&-0`eyrwd>Bk44W8zi|z>2c$*cB38G zx1-lO5jWaF~1FtbORBJil?u`3ALW zPv&l-bDQ)F2=~y$O8P}fFnL8v4o{B zBU)!k5V1va7GO-zmQ>%C36JU3Q+hgS21QcqRZ~Nx#hd>*Jr4E*%tJ5DN#H76~h1J#B#=HYY6D+{juz7>U969 z$#bt4&K@Mec^8Ci5_`+~OIa?o#6(ED?H)CT*VL;BEC(=yiJ_r%3bTgU%PvAmmCWxH z__NodexAyKMK5bB(JCLQ(LS>oSciJf(Lp!m$wUia-)R=xPNv0xwxTjvr_VrZMk6^q` zy?W~H52s!~iheBp_UU(K4xWT=H}%x}q|Z3s#%`bvK0J(-Z_OndID4`b+Tx2JPCs`T zs$t>K4%Uclb-uXgS*Vy6YqC&T+i3J`@ugD=27#q%O-|J=D~|rS_{!^~YdMI)=7Qci z_3ZnFT|X(lcnUft)-k>(t+MxyO_x@AnMW26y$|KCG|H(XXTV2_V>_qb{7`9g_8V&} zzY=S64o*>4ty-n5EbKZ`ICmBUnK}Hk^$wHAN@XR8ES^2EKK7mOu=%5N)*kZ6BP*5c z^RUIW#V*COtQlcWQ+Vd-!pZlQFUi14L&Z4pL5s(qDtts1gbKriW!8X-&CB27T9&l} zEuBUR`*V|mj*UI@($f|TRI0wzXY#|c5yaYpur#2fnQg}kXOEZYt^71j4jgN2+3r%Z>r@zhUljxmMJz`;B+P zZF*tPh7r#@z&{=hhVlv7w#*Icu%YS+ede$50+kLWhSQmZFnv^-)v|CBxGsUZpqs$j3z38dM}p1`Z$d*9-jr)DNx&jz_eKt- zVKYz?kp)vS2=7HNp}nzPgS&$J=;|s_MJb!CTS{d}*hRo)5s!pj1UfqgKzX7sspLB# zZYNbGe_gwhpYRe{H>zL5gsQYCD0>Tb$8cBI1wRqc1qwKSBS=bd$+}sZy4RGDQOd^F zJv)6!l?pL|_M9SR7k1rz8N!H&b&UFEg59ebb~9XK5!Ve;IKOU&JU|y}9aG+o8R5EH z%o;5V|Asn@5!}Edm>mFS#-a)dnF83wMYBUwWQKd$qa@;k88`)0 zLOLN`knTXwym0WY)g67e^1`{!Mv4iK-NE0I=ys=*gOCpIqs|{CKxhczx}0lgX9;sM;;KB`# z)mq+1v)c&UgclC>5EhQ#RgguCV$BSFCaggDj@z*W)3na8w5%ma+tgZu zb96qtMa!*-!`Vy%EUv>^>6Cz04!>M>P^aZ<^+p#s#^#M01PLqtMqkMnshkIOQ(SuQjgYeZRe8*S2J`X%VNU@xF9s6Fk7|*9^q9#O5Rwn~}Si z=R@heS5-5r*#KXtI*`!gSW@Z4AgjY>jZLrVWln7RperM0z>Jg{Z^~rSnIU$pCRCkf zLz(>u?box%R3$g4N}E7kLM@``Jw2RDf;=mRpr>|rk1@OI30fPCGp00PM)(7kPhB-f z{WRKzR%bS)JuO_StBFC422@6(srS%JL7FjZHFr5PYn+)It&BAx>ztXJoz1$P8M~R) z_086ovGJ^Swp(3MwYDO6i!;~VRgt^Vnd`dA%2^b4bywupICI@L2CHRv#e3a0nO5D6 ztJOr+46Stz{U>mL8?dq_mz$1&_jhqe-@UwY#upq5^x@YKxf-0jbIy?fTEbt?$NL z+P?vix5<8Z*vY^CiCj}o{YH+K({q>pPFDD>PvzhF{?60sX;PXz9+b!t$@5#;)x0SU02I zZ+ONf^>(D-o?|nx!f7wezEtLe**C~$X+JqH4L_TG13I+|k`b95SU#9~y?B5h4lJ*T zx*3l zl2!*yUJ(WLlBlCT*sVgeDWGk3G{?}+8qI4=xpOo@do*xoaUG2AqMnV}jpTea8hKPr zh8M0uxV7NA@@b#gcpHGXiKr^$B~$$@-oGp3{rijXMs0j>^l=?I+?pA0GdX&VX2!dK z@xmPjru=Nt~O@|=^SisoIxL*F2XCxnNciTEWaRuoTN zz8+?Fw6M(Z!uOC1W_3whTVYm5Y5U^bI$GFsSkt#82V)$AQ;uU1nWQ#&tsYBmh4`hC zqLw-cvDNe@>43^b?n|YUF>oCOx?~3hgr%F&M7;@M<9+H~l-@vq*t~i#f%^%Ns*odz z%6bh)T1wLpBBPR`KOyB3B#Dp=d}VYOrnORYQ8yAD4-z0D7&!Y_$|kADMtUWblesWI zOh|hOe22hy2|PmJdjJzrhhQ^8r8@{bP2d>=V5Ev!!3W1{pju99mFizlA0zV?~8iCgd94GJwffEEK z2>h79PY6(GoI^~$cJ4!^6u^Onfs+dB=@Y!Bkq<-Orr;c)qrnIE9`)^(e=Ci;R{T15 z>-<^tMZH%Us_fGcwD6?L$XGCMjLbjtZ+=#~TAl3o_bHuE5x2QpMgtUKk z-m81SpDs9Fp^A}tU-0Hz)Q<`%Ri%+0{uQjUfo%*32$G5<5#S(rcJ-kq2!5`x~K zNC^6+NQhR(jL01@>{iCSkTD}p0V^Op20FxIo2@SrOAx=ah+8eTXT9V>rCG1DXP($N z<%!LcN0nGPd8o!NEX|Ss7#G{ze>hQm5irqH-WVT$X}`629R6ocnRUr$yhoykfO7rH<=Vk@mVJV zbkv~~lrYB3M}hit>bsIvtM5|TWtnJutAmLOYVIcBAz%>51DJt(K^OaH)ooO`oxoEB zt_a_4UsBC?iN&bQd|#jx=}Z5zZ1|eF*|2R@#m9*A`Gv4~m$nkrmbq|Q01qi7UPq*; zOdR>BEB5~)AIqT?ALrK3pD$OVt6<9wKwFlYA7Hjzl9MR_uTScO?FzvgJ_@6=<>_D^ z0cVh>YrwGm!f!L|02sCchK&F{mae@Bp9^!C9tG-AZQX1wg5Byzr2T)5kUR=?1Dgf| zFNL}x{LKz^gItMFwVeNyh3P4%r8vWLZtBg8O zwY!z{uHL2&OuCXfP*|6b zg3W>63O49>GBsztSio$s+@A8x2Cs6yL8a~2=Dv9eHc0n1VA0W230&E*Y(KHL*7dXQ~JXE5tp#8s$00l5yp1MbB@hMDJjB99pSed_I$q_~mXHEpq~mlWmUl7S~FN&)a2AHKX6z~&80 z2HuKpVTD{xa`+r3$4Yx<$&v^s1>657Y)d$qHZ2R{*O97lqQL0L7R8HChwsx%a~u)s zpHRgQ2$0SA%O4ZZp_lfCInRaX2|?B2a0q2)bCvDPwyAx_$?*zx_frCI5vY>Cuh!=@ z!P|?yWPHDt_&%vfFz3vgYkp^$c6`59A}o=Y&jQOdqu7QTg--@Dy!hM^DFVySZue_a z?EggmQEp!GkGWfD@qDZ5#?-v1y0lSchf`|rt}v=t@!D=26Ea0?SD9Bx5-*uolnV38 zccH9%A&HZDB~&)Agv;g?GOAbxiMx?1nODM`cA8f#N!-tp_}u0dk^!COmAZMDS9U=X zpO1NE0gs}A%_}%Ks?001qamY;HJXWwMl+Xr#gfDuSrVVayaGvFwlR}|rR5?6%Pi*q zRmNPr2=iP97E2OuW=Z@~29}Z}-ogjq+y<7Ch?im7AjH7MtEy`%) zwt`JQH+HYhii;(t5Di#j;);K=B0J~D04jRl*d8_garT_VJ^s@X$jlxfhxs}nqGdv@#XlV{Q_0r zr#0$BLUD&AkCa<}xf$xZq)itaqL};nKNdz*fZ8A{qB?s;osY_rqQ!{J3Eg zz=>3{A$f4mSla(zpvaIS0VM{eb0{59S?GU=?hD!K4sI2dR1;en@sTP6WhBbVNR6$G zKvlsVB&!Sx3e*@slo4-98R?H$HgL+cicIstHW| z+6h(yTA`AVYJ&1A)#O=H0?78H?qk6psj_MUHKn8s*s4jGIdhnW|A;O8!{WojKi$J1 zlQ%gS0=OrmKuNMFk(iQSF(u2GlGq%S{7spX|9s^rfrhItBX%JyxsHs5Dx3b%eyO@Y*dC6`tYP_33V z0rf*{0B0aDhi$VW<5UtFEhPb(0S&Np)Yai#wV6OYfLVi>=1mzb`7pc(tz3{{tBkhs z@~T6-bitR>$oWvx8ZHr)U`69l&=q+cNWDO`&Xv*vO!&z;KA_BocF@S?Yp`dHpw{A9 z#F*F{U#$nQZa~U_`WFQ5A#h34{hTK@b1*~^Sd^CJk`l>J5jP}xVfMs!`dckes=}Vs z5P4(C4F8F*2BJNL|A_3tZEBe1rO>CleS4&(laXJ^4J&??yYsRSUWi{MVyd$ztYLpbI#l1&f`Y{=0mw%kQ>kIG565a}dk3xuQ z@QmQm1|j-w=lH>KR~~;M1ltng5A2B^!)Qi3ssD=@uwRD_A6FbD^=_oylm`23qBM!1 z{EPL(zr;py`5x;k#8DX~HfpUa*rm(sirA>fVx>B)_eAGdaGWlQ;^MYw#U)X=MIDz! z;S9tj(a1g2BA|z?@faRZcuFi5OB@s*0wrj{6dxg~C=H62Foh0^#ZqSYK6}+;T(i{W z(evX^2tU~JvRnsk=omtCS!_fABK_eZ#4lK3UI-(sV#g$T;WDDtPGc7vWT$$Le3%V< zfbkWg*rRC6meh)HV#G&qZ01IU0YlM-3?|=Bv7_ZaZZF4-@^!u}8BX!XLyAWa&;_y= zenx-m;qdG9r#JZij(yc-{~`NM6YeGPr7HDriPRcqc1bN4Er~3vY$F4%hS1+LvSO&! zO=t`vIEH#2rsGmfJzI{|Kf}xFpA&e1y78Lf(jTlroFXovM9SGGarZ81)|T_7z{`>c z%L<`1qjtU=?|A7bWQuLrZpU6%M*Lyq9NPE zcA53%nOO3inBnYTN~c-uAaO{AnZ)U4@lq4TQUQALx6AksI;kX3uz{ziwe;~j%3s$m uVM`Ai(wO$ShHd(9vkZfGVCdEz2ajvH#}A<>BsIz*gnPPv;QB#*`u_u|?A}5E diff --git a/./tests/integration/test_claude_api_integration.py b/./tests/integration/test_claude_api_integration.py deleted file mode 100644 index 25a8d2c..0000000 --- a/./tests/integration/test_claude_api_integration.py +++ /dev/null @@ -1,191 +0,0 @@ -""" -Integration tests for Claude API configuration system -Tests Skills with different Claude API configurations, environment variables, and backward compatibility -""" - -import os -import pytest -import tempfile -import yaml -from pathlib import Path -from unittest.mock import Mock, patch, AsyncMock -from typing import Dict, Any - -# Import the modules we're testing -import sys -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from config_validation import ClaudeAPIConfig -from configuration_loader import ConfigurationLoader -from configuration_migrator import ConfigurationMigrator - - -class TestEnvironmentVariableIntegration: - """Test environment variable scenarios in configuration""" - - def setup_method(self): - """Set up test fixtures""" - self.temp_dir = Path(tempfile.mkdtemp()) - self.loader = ConfigurationLoader() - self.migrator = ConfigurationMigrator() - - def teardown_method(self): - """Clean up test fixtures""" - import shutil - shutil.rmtree(self.temp_dir) - - # Clean up any test environment variables - test_vars = ['TEST_CLAUDE_API_KEY', 'TEST_CLAUDE_API_URL', 'TEST_CLAUDE_MODEL'] - for var in test_vars: - if var in os.environ: - del os.environ[var] - - def create_test_config_file(self, config_data: Dict[str, Any]) -> Path: - """Create a test configuration file""" - config_file = self.temp_dir / 'test_config.yaml' - with config_file.open('w') as f: - yaml.dump(config_data, f) - return config_file - - def test_environment_variable_expansion(self): - """Test environment variable expansion""" - # Set up environment variables - os.environ['TEST_CLAUDE_API_KEY'] = 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - os.environ['TEST_CLAUDE_API_URL'] = 'https://custom-api.example.com' - - config_data = { - 'claude': { - 'api_key': '${TEST_CLAUDE_API_KEY}', - 'api_url': '${TEST_CLAUDE_API_URL}', - 'model': 'claude-3-5-sonnet-20241022' - } - } - - expanded = self.loader.expand_environment_variables(config_data) - - assert expanded['claude']['api_key'] == 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - assert expanded['claude']['api_url'] == 'https://custom-api.example.com' - assert expanded['claude']['model'] == 'claude-3-5-sonnet-20241022' - - def test_environment_variable_defaults(self): - """Test environment variable expansion with defaults""" - config_data = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'api_url': '${CLAUDE_API_URL:-https://api.anthropic.com}', - 'model': '${CLAUDE_MODEL:-claude-3-5-sonnet-20241022}' - } - } - - expanded = self.loader.expand_environment_variables(config_data) - - # Should use defaults since env vars are not set - assert expanded['claude']['api_url'] == 'https://api.anthropic.com' - assert expanded['claude']['model'] == 'claude-3-5-sonnet-20241022' - - -class TestBackwardCompatibilityIntegration: - """Test backward compatibility with existing setups""" - - def setup_method(self): - """Set up test fixtures""" - self.migrator = ConfigurationMigrator() - - def test_legacy_model_migration(self): - """Test migration of legacy model names""" - legacy_config = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': 'claude-3-sonnet' # Legacy model name - } - } - - migrated = self.migrator.migrate_claude_config(legacy_config) - - # Should migrate to new model name - assert migrated['claude']['model'] == 'claude-3-sonnet-20240229' - # Should add default API URL - assert migrated['claude']['api_url'] == 'https://api.anthropic.com' - - def test_migration_needed_detection(self): - """Test detection of configurations that need migration""" - # Config that needs migration - legacy_config = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': 'claude-3-opus' # Legacy model name - } - } - - assert self.migrator.check_migration_needed(legacy_config) is True - - # Config that doesn't need migration - modern_config = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'api_url': 'https://api.anthropic.com', - 'model': 'claude-3-5-sonnet-20241022', - 'max_tokens': 4096, - 'temperature': 0.7 - }, - 'journal': { - 'daily_notes_folder': 'Daily', - 'date_format': 'YYYY-MM-DD', - 'file_extension': '.md' - }, - 'output': { - 'experiences_folder': 'Knowledge/Experiences', - 'lessons_folder': 'Knowledge/Lessons', - 'tasks_folder': 'Tasks/Daily', - 'problems_folder': 'Knowledge/Problems', - 'achievements_folder': 'Knowledge/Achievements', - 'improvements_folder': 'Knowledge/Improvements' - }, - 'analysis': { - 'categories': [], - 'extraction_rules': {} - }, - 'logging': { - 'level': 'INFO', - 'file': 'logs/journal_organizer.log' - } - } - - assert self.migrator.check_migration_needed(modern_config) is False - - -class TestConfigurationValidation: - """Test configuration validation scenarios""" - - def test_claude_api_config_validation(self): - """Test ClaudeAPIConfig validation""" - # Valid configuration - config = ClaudeAPIConfig( - api_key='sk-ant-test-key-12345678901234567890123456789012345678901234567890', - api_url='https://custom-api.example.com', - model='claude-3-5-sonnet-20241022' - ) - - assert config.api_key.startswith('sk-ant-') - assert config.api_url == 'https://custom-api.example.com' - assert config.model == 'claude-3-5-sonnet-20241022' - - def test_invalid_configuration_handling(self): - """Test handling of invalid configurations""" - # Invalid API key format - with pytest.raises(ValueError, match="Claude API key should start with"): - ClaudeAPIConfig(api_key='invalid-key') - - # Invalid URL format - with pytest.raises(ValueError, match="Invalid URL format"): - ClaudeAPIConfig( - api_key='sk-ant-test-key-12345678901234567890123456789012345678901234567890', - api_url='not-a-url' - ) - - # Invalid model name - with pytest.raises(ValueError, match="Invalid model name"): - ClaudeAPIConfig( - api_key='sk-ant-test-key-12345678901234567890123456789012345678901234567890', - model='invalid-model' - ) \ No newline at end of file diff --git a/./tests/integration/test_claude_skills.py b/./tests/integration/test_claude_skills.py deleted file mode 100644 index cadcb7a..0000000 --- a/./tests/integration/test_claude_skills.py +++ /dev/null @@ -1,516 +0,0 @@ -""" -Integration tests for Claude Skills. -Tests Skills with mocked API responses to verify AI integration functionality. -""" -import pytest -import json -from unittest.mock import AsyncMock, patch, Mock - -from agent_core import CommandContext, SkillResult -from skills.claude_skill import ClaudeAnalyzeSkill, ClaudeTransformSkill - - -class TestClaudeAnalyzeSkill: - """Integration tests for ClaudeAnalyzeSkill""" - - @pytest.fixture - def skill(self): - """Create ClaudeAnalyzeSkill instance""" - return ClaudeAnalyzeSkill() - - @pytest.fixture - def context(self, sample_config): - """Create command context with analysis parameters""" - return CommandContext( - command_name="analyze", - args={ - "content": """# 2024-01-15 Daily Journal - -## 今天的经历 -- 完成了项目的重要里程碑 -- 与团队进行了有效的沟通 - -## 学到的东西 -- 学会了新的Python异步编程技巧 -- 理解了更好的错误处理模式 - -## 遇到的问题 -- API调用偶尔超时 -- 配置文件格式需要改进 - -## 明天的计划 -- 优化API调用的重试机制 -- 更新文档 -""", - "analysis_type": "extract_experiences" - }, - config=sample_config - ) - - @pytest.mark.asyncio - async def test_analyze_journal_success(self, skill, context): - """Test successful journal analysis""" - mock_response = { - "experiences": [ - { - "title": "项目里程碑完成", - "description": "成功完成了项目的重要里程碑,展现了良好的项目管理能力", - "category": "项目管理", - "importance": "high" - }, - { - "title": "团队沟通改进", - "description": "与团队进行了有效的沟通,提升了协作效率", - "category": "团队协作", - "importance": "medium" - } - ], - "lessons": [ - { - "title": "Python异步编程", - "description": "学会了新的Python异步编程技巧,提升了代码效率", - "category": "技术学习", - "application": "可以应用到当前项目的API调用优化中" - } - ], - "problems": [ - { - "title": "API调用超时", - "description": "API调用偶尔出现超时问题", - "severity": "medium", - "suggested_solution": "实现重试机制和超时处理" - } - ] - } - - # Mock the Claude API client - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock the messages.create method - mock_message = Mock() - mock_message.content = [Mock(text=json.dumps(mock_response, ensure_ascii=False))] - mock_client.messages.create.return_value = mock_message - - result = await skill.execute(context) - - assert result.success is True - assert "experiences" in result.data - assert "lessons" in result.data - assert "problems" in result.data - assert len(result.data["experiences"]) == 2 - assert len(result.data["lessons"]) == 1 - assert len(result.data["problems"]) == 1 - - # Verify API was called with correct parameters - mock_client.messages.create.assert_called_once() - call_args = mock_client.messages.create.call_args - assert call_args[1]["model"] == "claude-3-5-sonnet-20241022" - assert call_args[1]["max_tokens"] == 4096 - - @pytest.mark.asyncio - async def test_analyze_empty_content(self, skill, sample_config): - """Test analysis with empty content""" - context = CommandContext( - command_name="analyze", - args={"content": "", "analysis_type": "extract_experiences"}, - config=sample_config - ) - - result = await skill.execute(context) - - assert result.success is False - assert "content" in result.error.lower() - - @pytest.mark.asyncio - async def test_analyze_api_error(self, skill, context): - """Test handling of Claude API errors""" - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock API error - mock_client.messages.create.side_effect = Exception("API rate limit exceeded") - - result = await skill.execute(context) - - assert result.success is False - assert "api" in result.error.lower() or "rate limit" in result.error.lower() - - @pytest.mark.asyncio - async def test_analyze_invalid_json_response(self, skill, context): - """Test handling of invalid JSON response from Claude""" - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock invalid JSON response - mock_message = Mock() - mock_message.content = [Mock(text="Invalid JSON response")] - mock_client.messages.create.return_value = mock_message - - result = await skill.execute(context) - - assert result.success is False - assert "json" in result.error.lower() or "parse" in result.error.lower() - - @pytest.mark.asyncio - async def test_analyze_different_types(self, skill, sample_config): - """Test different analysis types""" - analysis_types = ["extract_experiences", "extract_lessons", "extract_problems", "summarize"] - - for analysis_type in analysis_types: - context = CommandContext( - command_name="analyze", - args={ - "content": "Sample journal content for testing", - "analysis_type": analysis_type - }, - config=sample_config - ) - - mock_response = {"result": f"Analysis result for {analysis_type}"} - - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - mock_message = Mock() - mock_message.content = [Mock(text=json.dumps(mock_response))] - mock_client.messages.create.return_value = mock_message - - result = await skill.execute(context) - - assert result.success is True - assert result.data["analysis_type"] == analysis_type - - -class TestClaudeTransformSkill: - """Integration tests for ClaudeTransformSkill""" - - @pytest.fixture - def skill(self): - """Create ClaudeTransformSkill instance""" - return ClaudeTransformSkill() - - @pytest.fixture - def context(self, sample_config): - """Create command context with transformation parameters""" - return CommandContext( - command_name="transform", - args={ - "content": { - "title": "项目里程碑完成", - "description": "成功完成了项目的重要里程碑", - "category": "项目管理" - }, - "transform_type": "create_experience_note", - "target_format": "markdown" - }, - config=sample_config - ) - - @pytest.mark.asyncio - async def test_transform_to_markdown_success(self, skill, context): - """Test successful content transformation to markdown""" - mock_response = """# 项目里程碑完成 - -## 经验描述 - -成功完成了项目的重要里程碑,这次经历展现了良好的项目管理能力和团队协作精神。 - -## 关键要点 - -- 项目管理技能得到提升 -- 团队协作效率显著改善 -- 里程碑按时完成 - -## 应用场景 - -这个经验可以应用到未来的项目管理中,特别是在设定和跟踪项目里程碑方面。 - -## 相关标签 - -#项目管理 #里程碑 #团队协作 - ---- -*创建时间: 2024-01-15* -*来源: 日记整理* -""" - - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - mock_message = Mock() - mock_message.content = [Mock(text=mock_response)] - mock_client.messages.create.return_value = mock_message - - result = await skill.execute(context) - - assert result.success is True - assert result.data["transformed_content"] == mock_response - assert result.data["transform_type"] == "create_experience_note" - assert result.data["target_format"] == "markdown" - - # Verify the content contains expected markdown elements - assert "# 项目里程碑完成" in result.data["transformed_content"] - assert "## 经验描述" in result.data["transformed_content"] - assert "#项目管理" in result.data["transformed_content"] - - @pytest.mark.asyncio - async def test_transform_different_types(self, skill, sample_config): - """Test different transformation types""" - transform_types = [ - "create_experience_note", - "create_lesson_note", - "create_problem_note", - "create_summary" - ] - - for transform_type in transform_types: - context = CommandContext( - command_name="transform", - args={ - "content": {"title": "Test", "description": "Test content"}, - "transform_type": transform_type, - "target_format": "markdown" - }, - config=sample_config - ) - - mock_response = f"# Transformed Content\n\nContent for {transform_type}" - - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - mock_message = Mock() - mock_message.content = [Mock(text=mock_response)] - mock_client.messages.create.return_value = mock_message - - result = await skill.execute(context) - - assert result.success is True - assert result.data["transform_type"] == transform_type - - @pytest.mark.asyncio - async def test_transform_missing_content(self, skill, sample_config): - """Test transformation with missing content""" - context = CommandContext( - command_name="transform", - args={ - "transform_type": "create_experience_note", - "target_format": "markdown" - # Missing content - }, - config=sample_config - ) - - result = await skill.execute(context) - - assert result.success is False - assert "content" in result.error.lower() - - @pytest.mark.asyncio - async def test_transform_api_error(self, skill, context): - """Test handling of Claude API errors during transformation""" - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock API error - mock_client.messages.create.side_effect = Exception("API authentication failed") - - result = await skill.execute(context) - - assert result.success is False - assert "api" in result.error.lower() or "authentication" in result.error.lower() - - -class TestClaudeSkillsIntegration: - """Integration tests combining Claude skills""" - - @pytest.mark.asyncio - async def test_analyze_then_transform_workflow(self, sample_config): - """Test complete analyze-then-transform workflow""" - analyze_skill = ClaudeAnalyzeSkill() - transform_skill = ClaudeTransformSkill() - - # Step 1: Analyze journal content - analyze_context = CommandContext( - command_name="analyze", - args={ - "content": sample_journal_content, - "analysis_type": "extract_experiences" - }, - config=sample_config - ) - - # Step 2: Transform extracted experience to note - experience_data = { - "title": "项目里程碑完成", - "description": "成功完成了项目的重要里程碑", - "category": "项目管理", - "importance": "high" - } - - transform_context = CommandContext( - command_name="transform", - args={ - "content": experience_data, - "transform_type": "create_experience_note", - "target_format": "markdown" - }, - config=sample_config - ) - - # Mock responses - analyze_response = { - "experiences": [experience_data], - "lessons": [], - "problems": [] - } - - transform_response = """# 项目里程碑完成 - -## 经验描述 -成功完成了项目的重要里程碑 - -## 分类 -项目管理 - -## 重要程度 -高 - -#项目管理 #里程碑 -""" - - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock analyze response - mock_analyze_message = Mock() - mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))] - - # Mock transform response - mock_transform_message = Mock() - mock_transform_message.content = [Mock(text=transform_response)] - - # Set up side_effect to return different responses for different calls - mock_client.messages.create.side_effect = [mock_analyze_message, mock_transform_message] - - # Execute analyze - analyze_result = await analyze_skill.execute(analyze_context) - assert analyze_result.success is True - assert len(analyze_result.data["experiences"]) == 1 - - # Execute transform using analyze result - transform_result = await transform_skill.execute(transform_context) - assert transform_result.success is True - assert "项目里程碑完成" in transform_result.data["transformed_content"] - - # Verify both API calls were made - assert mock_client.messages.create.call_count == 2 - - @pytest.mark.asyncio - async def test_batch_analysis_and_transformation(self, sample_config): - """Test batch processing of multiple content pieces""" - analyze_skill = ClaudeAnalyzeSkill() - transform_skill = ClaudeTransformSkill() - - # Multiple journal entries to process - journal_entries = [ - "今天学会了新的编程技巧", - "解决了一个复杂的技术问题", - "与客户进行了重要的项目讨论" - ] - - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock responses for each entry - mock_responses = [] - for i, entry in enumerate(journal_entries): - analyze_response = { - "experiences": [{ - "title": f"Experience {i+1}", - "description": entry, - "category": "学习" - }] - } - - transform_response = f"# Experience {i+1}\n\n{entry}\n\n#学习" - - mock_analyze_message = Mock() - mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))] - - mock_transform_message = Mock() - mock_transform_message.content = [Mock(text=transform_response)] - - mock_responses.extend([mock_analyze_message, mock_transform_message]) - - mock_client.messages.create.side_effect = mock_responses - - # Process each entry - results = [] - for entry in journal_entries: - # Analyze - analyze_context = CommandContext( - command_name="analyze", - args={"content": entry, "analysis_type": "extract_experiences"}, - config=sample_config - ) - analyze_result = await analyze_skill.execute(analyze_context) - - # Transform - if analyze_result.success and analyze_result.data["experiences"]: - experience = analyze_result.data["experiences"][0] - transform_context = CommandContext( - command_name="transform", - args={ - "content": experience, - "transform_type": "create_experience_note", - "target_format": "markdown" - }, - config=sample_config - ) - transform_result = await transform_skill.execute(transform_context) - results.append((analyze_result, transform_result)) - - # Verify all entries were processed successfully - assert len(results) == len(journal_entries) - for analyze_result, transform_result in results: - assert analyze_result.success is True - assert transform_result.success is True - - # Verify correct number of API calls (2 per entry: analyze + transform) - assert mock_client.messages.create.call_count == len(journal_entries) * 2 - - -# Sample journal content for testing -sample_journal_content = """# 2024-01-15 Daily Journal - -## 今天的经历 -- 完成了项目的重要里程碑 -- 与团队进行了有效的沟通 -- 参加了技术分享会议 - -## 学到的东西 -- 学会了新的Python异步编程技巧 -- 理解了更好的错误处理模式 -- 掌握了新的项目管理方法 - -## 遇到的问题 -- API调用偶尔超时 -- 配置文件格式需要改进 -- 团队沟通中存在信息不对称 - -## 明天的计划 -- 优化API调用的重试机制 -- 更新文档 -- 组织团队同步会议 -""" \ No newline at end of file diff --git a/./tests/integration/test_conversational_agent.py b/./tests/integration/test_conversational_agent.py deleted file mode 100644 index 2d8a7d8..0000000 --- a/./tests/integration/test_conversational_agent.py +++ /dev/null @@ -1,431 +0,0 @@ -""" -Integration tests for conversational agent flow. -Tests the v2.0 conversational interface with natural language processing. -""" -import pytest -import json -from unittest.mock import AsyncMock, patch, Mock -from aioresponses import aioresponses - -from agent_core import CommandContext, SkillResult -from conversation.conversational_agent import ConversationalAgent -from conversation.conversation_state import ConversationState -from conversation.intent_understanding import IntentUnderstanding -from conversation.response_generator import ResponseGenerator - - -class TestConversationalAgent: - """Integration tests for ConversationalAgent""" - - @pytest.fixture - def agent(self, sample_config): - """Create ConversationalAgent instance""" - return ConversationalAgent(sample_config) - - @pytest.fixture - def sample_user_inputs(self): - """Sample user inputs for testing""" - return [ - "整理今天的日记", - "organize today's journal", - "分析2024年1月15日的日记", - "help me organize my notes from yesterday", - "今天学到了什么?", - "what did I learn today?", - "整理昨天的经验和教训" - ] - - @pytest.mark.asyncio - async def test_conversational_agent_basic_flow(self, agent): - """Test basic conversational flow""" - user_input = "整理今天的日记" - - # Mock the underlying organize command execution - with patch.object(agent.agent, 'execute_command') as mock_execute: - mock_result = SkillResult( - success=True, - data={ - "summary": "Successfully organized journal", - "created_notes": [ - {"type": "experience", "title": "Test Experience", "path": "Knowledge/Experiences/test.md"} - ] - }, - message="Journal organized successfully" - ) - mock_execute.return_value = mock_result - - response = await agent.process_message(user_input) - - assert response is not None - assert "成功" in response or "successfully" in response.lower() - mock_execute.assert_called_once() - - # Verify the command was called with correct parameters - call_args = mock_execute.call_args - assert call_args[0][0] == "organize" # Command name - - @pytest.mark.asyncio - async def test_conversational_agent_with_date_extraction(self, agent): - """Test conversational agent with date parameter extraction""" - user_input = "分析2024年1月15日的日记" - - with patch.object(agent.agent, 'execute_command') as mock_execute: - mock_result = SkillResult(success=True, data={}, message="Analysis completed") - mock_execute.return_value = mock_result - - response = await agent.process_message(user_input) - - assert response is not None - mock_execute.assert_called_once() - - # Verify date was extracted and passed - call_args = mock_execute.call_args - assert "date" in call_args[1]["args"] # Should have extracted date - - @pytest.mark.asyncio - async def test_conversational_agent_error_handling(self, agent): - """Test conversational agent error handling""" - user_input = "整理今天的日记" - - with patch.object(agent.agent, 'execute_command') as mock_execute: - mock_result = SkillResult( - success=False, - error="Journal file not found", - message="Failed to organize journal" - ) - mock_execute.return_value = mock_result - - response = await agent.process_message(user_input) - - assert response is not None - assert "错误" in response or "error" in response.lower() or "failed" in response.lower() - - @pytest.mark.asyncio - async def test_conversational_agent_unknown_intent(self, agent): - """Test conversational agent with unknown intent""" - user_input = "今天天气怎么样?" # Weather question, not related to journal organization - - response = await agent.process_message(user_input) - - assert response is not None - assert "不理解" in response or "不明白" in response or "help" in response.lower() - - @pytest.mark.asyncio - async def test_conversational_agent_help_request(self, agent): - """Test conversational agent help functionality""" - help_inputs = ["help", "帮助", "你能做什么?", "what can you do?"] - - for user_input in help_inputs: - response = await agent.process_message(user_input) - - assert response is not None - assert "整理" in response or "organize" in response.lower() - assert "日记" in response or "journal" in response.lower() - - @pytest.mark.asyncio - async def test_conversational_agent_multiple_turns(self, agent): - """Test multi-turn conversation""" - conversation_turns = [ - ("你好", "greeting"), - ("整理今天的日记", "organize"), - ("谢谢", "thanks") - ] - - for user_input, expected_intent in conversation_turns: - if expected_intent == "organize": - with patch.object(agent.agent, 'execute_command') as mock_execute: - mock_result = SkillResult(success=True, data={}, message="Success") - mock_execute.return_value = mock_result - - response = await agent.process_message(user_input) - else: - response = await agent.process_message(user_input) - - assert response is not None - assert len(response) > 0 - - -class TestIntentUnderstanding: - """Integration tests for IntentUnderstanding""" - - @pytest.fixture - def intent_processor(self): - """Create IntentUnderstanding instance""" - return IntentUnderstanding() - - def test_organize_intent_detection(self, intent_processor): - """Test detection of organize intents""" - organize_inputs = [ - "整理今天的日记", - "organize today's journal", - "分析我的日记", - "help me organize my notes", - "整理昨天的笔记" - ] - - for user_input in organize_inputs: - intent = intent_processor.understand_intent(user_input) - - assert intent["action"] == "organize" - assert "command" in intent - assert intent["command"] == "organize" - - def test_date_parameter_extraction(self, intent_processor): - """Test extraction of date parameters""" - date_inputs = [ - ("整理2024年1月15日的日记", "2024-01-15"), - ("analyze journal from yesterday", "yesterday"), - ("organize today's notes", "today"), - ("分析昨天的日记", "yesterday") - ] - - for user_input, expected_date in date_inputs: - intent = intent_processor.understand_intent(user_input) - - if expected_date in ["today", "yesterday"]: - # These should be converted to actual dates - assert "date" in intent["parameters"] - else: - assert intent["parameters"].get("date") == expected_date - - def test_help_intent_detection(self, intent_processor): - """Test detection of help intents""" - help_inputs = [ - "help", - "帮助", - "你能做什么?", - "what can you do?", - "how to use this?" - ] - - for user_input in help_inputs: - intent = intent_processor.understand_intent(user_input) - - assert intent["action"] == "help" - - def test_unknown_intent_handling(self, intent_processor): - """Test handling of unknown intents""" - unknown_inputs = [ - "今天天气怎么样?", - "what's the weather like?", - "计算1+1等于多少", - "play music" - ] - - for user_input in unknown_inputs: - intent = intent_processor.understand_intent(user_input) - - assert intent["action"] == "unknown" - assert "confidence" in intent - assert intent["confidence"] < 0.5 # Low confidence for unknown intents - - -class TestResponseGenerator: - """Integration tests for ResponseGenerator""" - - @pytest.fixture - def response_generator(self): - """Create ResponseGenerator instance""" - return ResponseGenerator() - - def test_success_response_generation(self, response_generator): - """Test generation of success responses""" - result = SkillResult( - success=True, - data={ - "summary": "Successfully organized journal", - "created_notes": [ - {"type": "experience", "title": "Project Milestone", "path": "Knowledge/Experiences/milestone.md"}, - {"type": "lesson", "title": "Python Tips", "path": "Knowledge/Lessons/python.md"} - ] - }, - message="Journal organized successfully" - ) - - response = response_generator.generate_response(result, "organize") - - assert response is not None - assert "成功" in response or "successfully" in response.lower() - assert "2" in response # Should mention number of notes created - assert "经验" in response or "experience" in response.lower() - assert "教训" in response or "lesson" in response.lower() - - def test_error_response_generation(self, response_generator): - """Test generation of error responses""" - result = SkillResult( - success=False, - error="Journal file not found for date 2024-01-15", - message="Failed to organize journal" - ) - - response = response_generator.generate_response(result, "organize") - - assert response is not None - assert "错误" in response or "error" in response.lower() or "失败" in response - assert "2024-01-15" in response # Should include the problematic date - - def test_help_response_generation(self, response_generator): - """Test generation of help responses""" - response = response_generator.generate_help_response() - - assert response is not None - assert "整理" in response or "organize" in response.lower() - assert "日记" in response or "journal" in response.lower() - assert "命令" in response or "command" in response.lower() - - def test_unknown_intent_response(self, response_generator): - """Test generation of unknown intent responses""" - response = response_generator.generate_unknown_response("今天天气怎么样?") - - assert response is not None - assert "不理解" in response or "不明白" in response or "understand" in response.lower() - assert "帮助" in response or "help" in response.lower() - - -class TestConversationState: - """Integration tests for ConversationState""" - - @pytest.fixture - def conversation_state(self): - """Create ConversationState instance""" - return ConversationState() - - def test_conversation_history_tracking(self, conversation_state): - """Test conversation history tracking""" - # Add some conversation turns - conversation_state.add_turn("user", "整理今天的日记") - conversation_state.add_turn("assistant", "好的,我来帮您整理今天的日记。") - conversation_state.add_turn("user", "谢谢") - conversation_state.add_turn("assistant", "不客气!还有其他需要帮助的吗?") - - history = conversation_state.get_history() - - assert len(history) == 4 - assert history[0]["role"] == "user" - assert history[0]["content"] == "整理今天的日记" - assert history[1]["role"] == "assistant" - assert history[-1]["role"] == "assistant" - - def test_context_management(self, conversation_state): - """Test conversation context management""" - # Set some context - conversation_state.set_context("last_command", "organize") - conversation_state.set_context("last_date", "2024-01-15") - conversation_state.set_context("user_preference", "detailed_summary") - - # Retrieve context - assert conversation_state.get_context("last_command") == "organize" - assert conversation_state.get_context("last_date") == "2024-01-15" - assert conversation_state.get_context("user_preference") == "detailed_summary" - assert conversation_state.get_context("nonexistent") is None - - def test_conversation_reset(self, conversation_state): - """Test conversation reset functionality""" - # Add some data - conversation_state.add_turn("user", "test message") - conversation_state.set_context("test_key", "test_value") - - # Verify data exists - assert len(conversation_state.get_history()) == 1 - assert conversation_state.get_context("test_key") == "test_value" - - # Reset conversation - conversation_state.reset() - - # Verify data is cleared - assert len(conversation_state.get_history()) == 0 - assert conversation_state.get_context("test_key") is None - - -class TestFullConversationalFlow: - """End-to-end integration tests for the complete conversational flow""" - - @pytest.mark.asyncio - async def test_complete_organize_conversation(self, sample_config, sample_journal_content): - """Test complete conversation flow for journal organization""" - agent = ConversationalAgent(sample_config) - - # Mock all external dependencies - with aioresponses() as m: - # Mock Obsidian API - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": sample_journal_content, - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": len(sample_journal_content)} - }, - status=200 - ) - - # Mock note creation - m.put( - "https://localhost:27123/vault/Knowledge/Experiences/test.md", - payload={"path": "Knowledge/Experiences/test.md", "stat": {}}, - status=200 - ) - - # Mock Claude API - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock analysis response - analyze_response = { - "experiences": [{"title": "Test Experience", "description": "Test", "category": "Test"}], - "lessons": [], - "problems": [], - "achievements": [] - } - - mock_analyze_message = Mock() - mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))] - - mock_transform_message = Mock() - mock_transform_message.content = [Mock(text="# Test Experience\n\nTest content")] - - mock_client.messages.create.side_effect = [mock_analyze_message, mock_transform_message] - - # Simulate conversation - conversation_turns = [ - "你好", - "整理今天的日记", - "谢谢你的帮助" - ] - - responses = [] - for user_input in conversation_turns: - response = await agent.process_message(user_input) - responses.append(response) - assert response is not None - assert len(response) > 0 - - # Verify conversation flow - assert "你好" in responses[0] or "hello" in responses[0].lower() # Greeting response - assert "成功" in responses[1] or "successfully" in responses[1].lower() # Success response - assert "不客气" in responses[2] or "welcome" in responses[2].lower() # Thanks response - - @pytest.mark.asyncio - async def test_error_recovery_conversation(self, sample_config): - """Test conversation flow with error recovery""" - agent = ConversationalAgent(sample_config) - - with aioresponses() as m: - # Mock journal file not found - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - status=404, - payload={"error": "File not found"} - ) - - # Simulate error scenario - user_input = "整理今天的日记" - response = await agent.process_message(user_input) - - assert response is not None - assert "找不到" in response or "not found" in response.lower() or "错误" in response - - # Follow up with help request - help_response = await agent.process_message("我应该怎么办?") - - assert help_response is not None - assert "建议" in help_response or "suggest" in help_response.lower() or "帮助" in help_response \ No newline at end of file diff --git a/./tests/integration/test_obsidian_skills.py b/./tests/integration/test_obsidian_skills.py deleted file mode 100644 index 9bdbc86..0000000 --- a/./tests/integration/test_obsidian_skills.py +++ /dev/null @@ -1,458 +0,0 @@ -""" -Integration tests for Obsidian Skills. -Tests Skills with mocked API responses to verify end-to-end functionality. -""" -import pytest -import json -from unittest.mock import AsyncMock, patch, Mock -from aioresponses import aioresponses - -from agent_core import CommandContext, SkillResult -from skills.obsidian_skill import ( - ObsidianReadSkill, ObsidianWriteSkill, ObsidianAppendSkill, - ObsidianListFilesSkill -) - - -class TestObsidianReadSkill: - """Integration tests for ObsidianReadSkill""" - - @pytest.fixture - def skill(self): - """Create ObsidianReadSkill instance""" - return ObsidianReadSkill() - - @pytest.fixture - def context(self, sample_config): - """Create command context with Obsidian configuration""" - return CommandContext( - command_name="test_read", - args={"file_path": "Daily/2024-01-15.md"}, - config=sample_config - ) - - @pytest.mark.asyncio - async def test_read_note_success(self, skill, context): - """Test successful note reading""" - mock_response = { - "content": "# 2024-01-15 Daily Journal\n\nTest content", - "stat": { - "ctime": 1642204800000, - "mtime": 1642204800000, - "size": 45 - } - } - - with aioresponses() as m: - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload=mock_response, - status=200 - ) - - result = await skill.execute(context) - - assert result.success is True - assert result.data["content"] == mock_response["content"] - assert result.data["file_path"] == "Daily/2024-01-15.md" - assert "stat" in result.data - - @pytest.mark.asyncio - async def test_read_note_not_found(self, skill, context): - """Test reading non-existent note""" - with aioresponses() as m: - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - status=404, - payload={"error": "File not found"} - ) - - result = await skill.execute(context) - - assert result.success is False - assert "not found" in result.error.lower() - - @pytest.mark.asyncio - async def test_read_note_api_error(self, skill, context): - """Test API connection error""" - with aioresponses() as m: - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - exception=Exception("Connection failed") - ) - - result = await skill.execute(context) - - assert result.success is False - assert "connection" in result.error.lower() or "api" in result.error.lower() - - @pytest.mark.asyncio - async def test_read_note_invalid_response(self, skill, context): - """Test handling of invalid API response""" - with aioresponses() as m: - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload="invalid json response", - status=200 - ) - - result = await skill.execute(context) - - assert result.success is False - assert "response" in result.error.lower() - - -class TestObsidianWriteSkill: - """Integration tests for ObsidianWriteSkill""" - - @pytest.fixture - def skill(self): - """Create ObsidianWriteSkill instance""" - return ObsidianWriteSkill() - - @pytest.fixture - def context(self, sample_config): - """Create command context with write parameters""" - return CommandContext( - command_name="test_write", - args={ - "file_path": "Knowledge/Experiences/test-experience.md", - "content": "# Test Experience\n\nThis is a test experience note." - }, - config=sample_config - ) - - @pytest.mark.asyncio - async def test_write_note_success(self, skill, context): - """Test successful note writing""" - mock_response = { - "path": "Knowledge/Experiences/test-experience.md", - "stat": { - "ctime": 1642204800000, - "mtime": 1642204800000, - "size": 45 - } - } - - with aioresponses() as m: - m.put( - "https://localhost:27123/vault/Knowledge/Experiences/test-experience.md", - payload=mock_response, - status=200 - ) - - result = await skill.execute(context) - - assert result.success is True - assert result.data["file_path"] == "Knowledge/Experiences/test-experience.md" - assert "created" in result.message.lower() or "written" in result.message.lower() - - @pytest.mark.asyncio - async def test_write_note_permission_error(self, skill, context): - """Test write permission error""" - with aioresponses() as m: - m.put( - "https://localhost:27123/vault/Knowledge/Experiences/test-experience.md", - status=403, - payload={"error": "Permission denied"} - ) - - result = await skill.execute(context) - - assert result.success is False - assert "permission" in result.error.lower() - - @pytest.mark.asyncio - async def test_write_note_missing_content(self, skill, sample_config): - """Test writing note without content""" - context = CommandContext( - command_name="test_write", - args={"file_path": "test.md"}, # Missing content - config=sample_config - ) - - result = await skill.execute(context) - - assert result.success is False - assert "content" in result.error.lower() - - -class TestObsidianAppendSkill: - """Integration tests for ObsidianAppendSkill""" - - @pytest.fixture - def skill(self): - """Create ObsidianAppendSkill instance""" - return ObsidianAppendSkill() - - @pytest.fixture - def context(self, sample_config): - """Create command context with append parameters""" - return CommandContext( - command_name="test_append", - args={ - "file_path": "Daily/2024-01-15.md", - "content": "\n\n## Additional Notes\n\nAppended content." - }, - config=sample_config - ) - - @pytest.mark.asyncio - async def test_append_to_existing_note_success(self, skill, context): - """Test successful content appending to existing note""" - # Mock reading existing content - existing_content = "# 2024-01-15 Daily Journal\n\nExisting content" - read_response = { - "content": existing_content, - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": 45} - } - - # Mock writing updated content - write_response = { - "path": "Daily/2024-01-15.md", - "stat": {"ctime": 1642204800000, "mtime": 1642204900000, "size": 90} - } - - with aioresponses() as m: - # Mock GET request for reading existing content - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload=read_response, - status=200 - ) - - # Mock PUT request for writing updated content - m.put( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload=write_response, - status=200 - ) - - result = await skill.execute(context) - - assert result.success is True - assert result.data["file_path"] == "Daily/2024-01-15.md" - assert "appended" in result.message.lower() - - @pytest.mark.asyncio - async def test_append_to_nonexistent_note(self, skill, context): - """Test appending to non-existent note (should create new note)""" - write_response = { - "path": "Daily/2024-01-15.md", - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": 45} - } - - with aioresponses() as m: - # Mock GET request returning 404 (file doesn't exist) - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - status=404 - ) - - # Mock PUT request for creating new file - m.put( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload=write_response, - status=200 - ) - - result = await skill.execute(context) - - assert result.success is True - assert "created" in result.message.lower() - - -class TestObsidianListFilesSkill: - """Integration tests for ObsidianListFilesSkill""" - - @pytest.fixture - def skill(self): - """Create ObsidianListFilesSkill instance""" - return ObsidianListFilesSkill() - - @pytest.fixture - def context(self, sample_config): - """Create command context with list parameters""" - return CommandContext( - command_name="test_list", - args={"folder_path": "Daily"}, - config=sample_config - ) - - @pytest.mark.asyncio - async def test_list_files_success(self, skill, context): - """Test successful file listing""" - mock_response = { - "files": [ - { - "path": "Daily/2024-01-15.md", - "name": "2024-01-15.md", - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": 45} - }, - { - "path": "Daily/2024-01-14.md", - "name": "2024-01-14.md", - "stat": {"ctime": 1642118400000, "mtime": 1642118400000, "size": 38} - } - ] - } - - with aioresponses() as m: - m.get( - "https://localhost:27123/vault/Daily/", - payload=mock_response, - status=200 - ) - - result = await skill.execute(context) - - assert result.success is True - assert len(result.data["files"]) == 2 - assert result.data["folder_path"] == "Daily" - assert any(file["name"] == "2024-01-15.md" for file in result.data["files"]) - - @pytest.mark.asyncio - async def test_list_files_empty_folder(self, skill, context): - """Test listing files in empty folder""" - mock_response = {"files": []} - - with aioresponses() as m: - m.get( - "https://localhost:27123/vault/Daily/", - payload=mock_response, - status=200 - ) - - result = await skill.execute(context) - - assert result.success is True - assert len(result.data["files"]) == 0 - - @pytest.mark.asyncio - async def test_list_files_folder_not_found(self, skill, context): - """Test listing files in non-existent folder""" - with aioresponses() as m: - m.get( - "https://localhost:27123/vault/Daily/", - status=404, - payload={"error": "Folder not found"} - ) - - result = await skill.execute(context) - - assert result.success is False - assert "not found" in result.error.lower() - - -class TestObsidianSkillsIntegration: - """Integration tests combining multiple Obsidian skills""" - - @pytest.mark.asyncio - async def test_read_write_workflow(self, sample_config): - """Test complete read-modify-write workflow""" - read_skill = ObsidianReadSkill() - write_skill = ObsidianWriteSkill() - - # Read existing content - read_context = CommandContext( - command_name="read", - args={"file_path": "Daily/2024-01-15.md"}, - config=sample_config - ) - - # Write modified content - write_context = CommandContext( - command_name="write", - args={ - "file_path": "Knowledge/Processed/2024-01-15-summary.md", - "content": "# Summary\n\nProcessed content from daily journal." - }, - config=sample_config - ) - - with aioresponses() as m: - # Mock read response - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": "# 2024-01-15 Daily Journal\n\nOriginal content", - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": 45} - }, - status=200 - ) - - # Mock write response - m.put( - "https://localhost:27123/vault/Knowledge/Processed/2024-01-15-summary.md", - payload={ - "path": "Knowledge/Processed/2024-01-15-summary.md", - "stat": {"ctime": 1642204900000, "mtime": 1642204900000, "size": 60} - }, - status=200 - ) - - # Execute read - read_result = await read_skill.execute(read_context) - assert read_result.success is True - - # Execute write (in real scenario, content would be processed) - write_result = await write_skill.execute(write_context) - assert write_result.success is True - - # Verify workflow completed successfully - assert read_result.data["content"] is not None - assert write_result.data["file_path"] == "Knowledge/Processed/2024-01-15-summary.md" - - @pytest.mark.asyncio - async def test_list_and_read_multiple_files(self, sample_config): - """Test listing files and reading multiple files""" - list_skill = ObsidianListFilesSkill() - read_skill = ObsidianReadSkill() - - list_context = CommandContext( - command_name="list", - args={"folder_path": "Daily"}, - config=sample_config - ) - - with aioresponses() as m: - # Mock list response - m.get( - "https://localhost:27123/vault/Daily/", - payload={ - "files": [ - {"path": "Daily/2024-01-15.md", "name": "2024-01-15.md"}, - {"path": "Daily/2024-01-14.md", "name": "2024-01-14.md"} - ] - }, - status=200 - ) - - # Mock read responses for each file - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={"content": "Content 1", "stat": {}}, - status=200 - ) - m.get( - "https://localhost:27123/vault/Daily/2024-01-14.md", - payload={"content": "Content 2", "stat": {}}, - status=200 - ) - - # List files - list_result = await list_skill.execute(list_context) - assert list_result.success is True - assert len(list_result.data["files"]) == 2 - - # Read each file - for file_info in list_result.data["files"]: - read_context = CommandContext( - command_name="read", - args={"file_path": file_info["path"]}, - config=sample_config - ) - - read_result = await read_skill.execute(read_context) - assert read_result.success is True - assert read_result.data["content"] is not None \ No newline at end of file diff --git a/./tests/integration/test_organize_command.py b/./tests/integration/test_organize_command.py deleted file mode 100644 index 394c150..0000000 --- a/./tests/integration/test_organize_command.py +++ /dev/null @@ -1,488 +0,0 @@ -""" -Integration tests for OrganizeCommand. -Tests Commands with full skill chains to verify end-to-end functionality. -""" -import pytest -import json -from unittest.mock import AsyncMock, patch, Mock -from aioresponses import aioresponses - -from agent_core import CommandContext, SkillResult, Agent -from commands.organize_command import OrganizeCommand - - -class TestOrganizeCommand: - """Integration tests for OrganizeCommand""" - - @pytest.fixture - def command(self): - """Create OrganizeCommand instance""" - return OrganizeCommand() - - @pytest.fixture - def context(self, sample_config): - """Create command context for organize command""" - return CommandContext( - command_name="organize", - args={ - "date": "2024-01-15", - "vault_path": sample_config["obsidian"]["vault_path"], - "daily_folder": "Daily" - }, - config=sample_config - ) - - @pytest.fixture - def sample_journal_content(self): - """Sample journal content for testing""" - return """# 2024-01-15 Daily Journal - -## 今天的经历 -- 完成了项目的重要里程碑,团队协作非常顺利 -- 与客户进行了产品演示,获得了积极反馈 -- 参加了技术分享会议,学到了新的架构模式 - -## 学到的东西 -- 学会了新的Python异步编程技巧,提升了代码效率 -- 理解了微服务架构的最佳实践 -- 掌握了更好的错误处理和日志记录模式 - -## 遇到的问题 -- API调用偶尔超时,影响用户体验 -- 配置文件格式需要改进,当前格式不够灵活 -- 团队沟通中存在信息不对称问题 - -## 今天的成就 -- 成功部署了新版本到生产环境 -- 解决了困扰团队一周的性能问题 -- 获得了客户的正面评价 - -## 明天的计划 -- 优化API调用的重试机制 -- 重构配置管理模块 -- 组织团队同步会议 -""" - - @pytest.mark.asyncio - async def test_organize_command_full_workflow_success(self, command, context, sample_journal_content): - """Test complete organize command workflow with all skills""" - - # Mock Claude API responses - analyze_response = { - "experiences": [ - { - "title": "项目里程碑完成", - "description": "成功完成了项目的重要里程碑,团队协作非常顺利", - "category": "项目管理", - "importance": "high" - }, - { - "title": "客户产品演示", - "description": "与客户进行了产品演示,获得了积极反馈", - "category": "客户关系", - "importance": "high" - } - ], - "lessons": [ - { - "title": "Python异步编程技巧", - "description": "学会了新的Python异步编程技巧,提升了代码效率", - "category": "技术学习", - "application": "可以应用到当前项目的API调用优化中" - }, - { - "title": "微服务架构最佳实践", - "description": "理解了微服务架构的最佳实践", - "category": "架构设计", - "application": "用于指导下一个项目的架构设计" - } - ], - "problems": [ - { - "title": "API调用超时", - "description": "API调用偶尔超时,影响用户体验", - "severity": "medium", - "suggested_solution": "实现重试机制和超时处理" - } - ], - "achievements": [ - { - "title": "生产环境部署", - "description": "成功部署了新版本到生产环境", - "impact": "提升了系统稳定性和性能" - } - ] - } - - # Mock transformation responses - experience_note = """# 项目里程碑完成 - -## 经验描述 -成功完成了项目的重要里程碑,团队协作非常顺利。这次经历展现了良好的项目管理能力和团队协作精神。 - -## 关键要点 -- 项目管理技能得到提升 -- 团队协作效率显著改善 -- 里程碑按时完成 - -## 应用场景 -这个经验可以应用到未来的项目管理中,特别是在设定和跟踪项目里程碑方面。 - -## 相关标签 -#项目管理 #里程碑 #团队协作 - ---- -*创建时间: 2024-01-15* -*来源: [[Daily/2024-01-15]]* -""" - - lesson_note = """# Python异步编程技巧 - -## 学习内容 -学会了新的Python异步编程技巧,提升了代码效率。 - -## 关键概念 -- 异步编程模式 -- 性能优化技巧 -- 代码效率提升 - -## 实际应用 -可以应用到当前项目的API调用优化中,提升系统响应速度。 - -## 相关标签 -#技术学习 #Python #异步编程 - ---- -*创建时间: 2024-01-15* -*来源: [[Daily/2024-01-15]]* -""" - - with aioresponses() as m: - # Mock Obsidian API calls - - # 1. Read daily journal - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": sample_journal_content, - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": len(sample_journal_content)} - }, - status=200 - ) - - # 2. Write experience note - m.put( - "https://localhost:27123/vault/Knowledge/Experiences/项目里程碑完成.md", - payload={ - "path": "Knowledge/Experiences/项目里程碑完成.md", - "stat": {"ctime": 1642204900000, "mtime": 1642204900000, "size": len(experience_note)} - }, - status=200 - ) - - # 3. Write lesson note - m.put( - "https://localhost:27123/vault/Knowledge/Lessons/Python异步编程技巧.md", - payload={ - "path": "Knowledge/Lessons/Python异步编程技巧.md", - "stat": {"ctime": 1642204900000, "mtime": 1642204900000, "size": len(lesson_note)} - }, - status=200 - ) - - # 4. Additional notes for other categories (problems, achievements) - m.put( - "https://localhost:27123/vault/Knowledge/Problems/API调用超时.md", - payload={"path": "Knowledge/Problems/API调用超时.md", "stat": {}}, - status=200 - ) - - m.put( - "https://localhost:27123/vault/Knowledge/Achievements/生产环境部署.md", - payload={"path": "Knowledge/Achievements/生产环境部署.md", "stat": {}}, - status=200 - ) - - # Mock Claude API - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock analyze response - mock_analyze_message = Mock() - mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))] - - # Mock transform responses (one for each item to be transformed) - mock_transform_responses = [ - Mock(content=[Mock(text=experience_note)]), - Mock(content=[Mock(text=experience_note)]), # Second experience - Mock(content=[Mock(text=lesson_note)]), - Mock(content=[Mock(text=lesson_note)]), # Second lesson - Mock(content=[Mock(text="# API调用超时\n\n问题描述...")]), # Problem note - Mock(content=[Mock(text="# 生产环境部署\n\n成就描述...")]) # Achievement note - ] - - # Set up responses: first analyze, then multiple transforms - mock_client.messages.create.side_effect = [mock_analyze_message] + mock_transform_responses - - # Execute the organize command - result = await command.execute(context) - - # Verify overall success - assert result.success is True - assert "organized successfully" in result.message.lower() or "completed" in result.message.lower() - - # Verify data structure - assert "summary" in result.data - assert "created_notes" in result.data - - # Verify created notes - created_notes = result.data["created_notes"] - assert len(created_notes) > 0 - - # Should have created notes for experiences, lessons, problems, achievements - note_types = [note.get("type") for note in created_notes] - expected_types = ["experience", "lesson", "problem", "achievement"] - for expected_type in expected_types: - assert any(expected_type in note_type for note_type in note_types if note_type) - - # Verify API calls were made - assert mock_client.messages.create.call_count >= 2 # At least analyze + some transforms - - @pytest.mark.asyncio - async def test_organize_command_journal_not_found(self, command, context): - """Test organize command when daily journal doesn't exist""" - - with aioresponses() as m: - # Mock journal file not found - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - status=404, - payload={"error": "File not found"} - ) - - result = await command.execute(context) - - assert result.success is False - assert "not found" in result.error.lower() or "missing" in result.error.lower() - - @pytest.mark.asyncio - async def test_organize_command_claude_api_error(self, command, context, sample_journal_content): - """Test organize command when Claude API fails""" - - with aioresponses() as m: - # Mock successful journal read - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": sample_journal_content, - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": len(sample_journal_content)} - }, - status=200 - ) - - # Mock Claude API error - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - mock_client.messages.create.side_effect = Exception("Claude API rate limit exceeded") - - result = await command.execute(context) - - assert result.success is False - assert "api" in result.error.lower() or "claude" in result.error.lower() - - @pytest.mark.asyncio - async def test_organize_command_partial_success(self, command, context, sample_journal_content): - """Test organize command with partial success (some notes created, some failed)""" - - analyze_response = { - "experiences": [ - { - "title": "Test Experience", - "description": "Test description", - "category": "Test", - "importance": "medium" - } - ], - "lessons": [], - "problems": [], - "achievements": [] - } - - with aioresponses() as m: - # Mock successful journal read - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": sample_journal_content, - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": len(sample_journal_content)} - }, - status=200 - ) - - # Mock successful experience note creation - m.put( - "https://localhost:27123/vault/Knowledge/Experiences/Test Experience.md", - payload={ - "path": "Knowledge/Experiences/Test Experience.md", - "stat": {"ctime": 1642204900000, "mtime": 1642204900000, "size": 100} - }, - status=200 - ) - - # Mock Claude API - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Mock analyze response - mock_analyze_message = Mock() - mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))] - - # Mock transform response - mock_transform_message = Mock() - mock_transform_message.content = [Mock(text="# Test Experience\n\nTransformed content")] - - mock_client.messages.create.side_effect = [mock_analyze_message, mock_transform_message] - - result = await command.execute(context) - - # Should succeed even with minimal content - assert result.success is True - assert len(result.data["created_notes"]) >= 1 - - @pytest.mark.asyncio - async def test_organize_command_empty_journal(self, command, context): - """Test organize command with empty journal content""" - - with aioresponses() as m: - # Mock journal with empty content - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": "", - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": 0} - }, - status=200 - ) - - result = await command.execute(context) - - assert result.success is False - assert "empty" in result.error.lower() or "content" in result.error.lower() - - -class TestOrganizeCommandWithAgent: - """Integration tests for OrganizeCommand within Agent context""" - - @pytest.fixture - def agent(self, sample_config): - """Create Agent with OrganizeCommand registered""" - agent = Agent("test_agent", sample_config) - agent.register_command(OrganizeCommand()) - return agent - - @pytest.mark.asyncio - async def test_agent_execute_organize_command(self, agent, sample_journal_content): - """Test executing organize command through Agent""" - - with aioresponses() as m: - # Mock Obsidian API - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": sample_journal_content, - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": len(sample_journal_content)} - }, - status=200 - ) - - # Mock note creation (simplified - just one note) - m.put( - "https://localhost:27123/vault/Knowledge/Experiences/Test.md", - payload={"path": "Knowledge/Experiences/Test.md", "stat": {}}, - status=200 - ) - - # Mock Claude API - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - # Minimal response for testing - analyze_response = { - "experiences": [{"title": "Test", "description": "Test", "category": "Test"}], - "lessons": [], - "problems": [], - "achievements": [] - } - - mock_analyze_message = Mock() - mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))] - - mock_transform_message = Mock() - mock_transform_message.content = [Mock(text="# Test\n\nTest content")] - - mock_client.messages.create.side_effect = [mock_analyze_message, mock_transform_message] - - # Execute command through agent - result = await agent.execute_command( - "organize", - args={"date": "2024-01-15"}, - options={"verbose": True} - ) - - assert result.success is True - assert result.data is not None - - @pytest.mark.asyncio - async def test_agent_execute_organize_by_alias(self, agent, sample_journal_content): - """Test executing organize command by alias through Agent""" - - with aioresponses() as m: - # Mock minimal successful workflow - m.get( - "https://localhost:27123/vault/Daily/2024-01-15.md", - payload={ - "content": sample_journal_content, - "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": len(sample_journal_content)} - }, - status=200 - ) - - # Mock Claude API with minimal response - with patch('skills.claude_skill.anthropic') as mock_anthropic: - mock_client = Mock() - mock_anthropic.Anthropic.return_value = mock_client - - analyze_response = {"experiences": [], "lessons": [], "problems": [], "achievements": []} - mock_message = Mock() - mock_message.content = [Mock(text=json.dumps(analyze_response))] - mock_client.messages.create.return_value = mock_message - - # Execute by alias - result = await agent.execute_command("org", args={"date": "2024-01-15"}) - - assert result.success is True - - @pytest.mark.asyncio - async def test_agent_command_info(self, agent): - """Test getting command information through Agent""" - - commands_info = agent.get_commands_info() - - assert "organize" in commands_info["commands"] - - organize_info = commands_info["commands"]["organize"] - assert organize_info["name"] == "organize" - assert organize_info["description"] == "分析和整理日记内容,提取经验和要点" - assert "org" in organize_info["aliases"] - assert "organize-journal" in organize_info["aliases"] - - # Verify skills are registered - assert len(organize_info["skills"]) > 0 - skill_names = list(organize_info["skills"].keys()) - expected_skills = ["obsidian_read", "obsidian_write", "obsidian_append", "claude_analyze", "claude_transform"] - for expected_skill in expected_skills: - assert any(expected_skill in skill_name for skill_name in skill_names) \ No newline at end of file diff --git a/./tests/property/__init__.py b/./tests/property/__init__.py deleted file mode 100644 index 08ba291..0000000 --- a/./tests/property/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Property-based tests package \ No newline at end of file diff --git a/./tests/unit/__init__.py b/./tests/unit/__init__.py deleted file mode 100644 index 07c9273..0000000 --- a/./tests/unit/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Unit tests package \ No newline at end of file diff --git a/./tests/unit/__pycache__/__init__.cpython-310.pyc b/./tests/unit/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 6de824b46d41385095c3ddb4cab9a8be4feb7843..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmd1j<>g`kg7cR`GDU#&V-N=!FakLaKwQiMBvKfH88jLFRx%WUgb~CqFa6Ns)S_bj z^31%HO8tPM{H)aEl4AY*q~gq!%)~tXtRnra{L-Sl#GLs2qV&YP%&OEP{gTvTpqkRW j%o6?h_)H)bAFo$Xd5gm)H$SB`C)EyQM==wSU||3Na=0og diff --git a/./tests/unit/__pycache__/test_agent_core.cpython-310-pytest-7.3.1.pyc b/./tests/unit/__pycache__/test_agent_core.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index e1f4b83ab8e4590c165b84062197c9f12f808155..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35674 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 diff --git a/./tests/unit/__pycache__/test_claude_api_configuration.cpython-310-pytest-7.3.1.pyc b/./tests/unit/__pycache__/test_claude_api_configuration.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index 755936e16e892c359d31c332f5fadf0eb00d5e8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33463 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 diff --git a/./tests/unit/__pycache__/test_config_validation.cpython-310-pytest-7.3.1.pyc b/./tests/unit/__pycache__/test_config_validation.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index d21afda8534bab95dd8c58b3303fda9ac52f6749..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28214 zcmeHwd5|2}dEa!;xihn~CvJcog5WVCb^$EFQzQ%s1VoU)f&oBE97`S!wg-z@%+4&k zdqH40E6E0BxnR+fY^6*munUl4MUX8OtwdIwD2i=Wq8!fcL~)lYsW?@MREZLm;aD;p zQ~CYA*WJ@S+k4<(mH*Ig%{Q;RU%&6`*YADzd(-Og&&Kd~;tx;O|Id43vH!+{;9mv@ z`|z=fS}bP7$}vMT^oCZ}PottmMfWZM!x&Xee#_xXXQIr&dGPa zoX2;((LY-#7v$bVV_Xg0Fh|5ud#l1LwUpO#`4D5P329qo6DPLx0JWc-c`P9 z_U`iCv&C|8_MY-Rvs=qswb=V&hMtHeVi)6DteM#rv+P$j4bP;-Ucv{svPKTLa=>*j zj`Bu7j{0%54MzoI07nBjy3f#0#7cwh4`p9y*6pHQvuvw)x@8upTFuk->B_lkqi$5~ zdaGHSZ5i{8+IaS))KS;xO?9nXZF{lSJXbec&DmPhE}pBJ_3EictynuhS8ZBoW_z*P zG>SFT1dcN(HtNmk@$7f`?j}o`lX$*rpDF21a;|DmopBOJTT^GJcro~=<6s{?)+j(N zR@U%><;A`pE60sE9w_0g;hj&{=4wr&)||Rixx0;PC-`tZe`t&Ct&3 zX3CD8U|C0b(1;tchPI%0&`$==mlBgs;zD({;bi767}cg-pE5DTVkO*%FEh<@+(}rq z#%U+ftj^Zt)U}w&0ej>9J6^DArnTeMdegYD<9V|+Q=77_9j#MV-KbZaJ7&xsGp%{E zS#4BW=5)1LZ`aHn@>X`t;{{2vvgqB6&s}h`(>1%I-q8p!S$Tkrme4ZzPvE~@jO>x| z?roFkwoIi$nkyA2TX9=MIbW%~GGA?|D<)f(rwM=@&m{qd@v#yBnOI(vGwlZ#(@Ug= z|8gp`>h0kD=gpdp$LF}4ua&aS+5<8U-EsD!8jUT}$)C7j*|k}9)hQfv`_DbjA81tP zjhcJZ|E%g!@8Yp}dv4zLZrzW*zF^fYukw-B^fbn#d$`7H`8Y=X{^t*Sr>D$X)vi@) z=c}`Gjhg#LmL8~YqC>g^6@yea;ux$6BVi=*4ZdKc@tra+oGL)*Bn}Jz#7!HsO2F*lcXU_mFXyaW}q)jiPZ6 zzDJC$#=ZC+HMU_y-{)-PoK}z4+tclPc&!+Hu{hPJTGkZDzxT(oKdCrU?AXOvCsx7K z0xG>ddZK0*=jVje6i?UB<89ZhNz9%h)OGq?M$$5`X(oAi&k%F+7RJY1WwvIYX&Dco zf<uD-f(Q&J+GPPuiFlGS2VC^^}bB z2T@A9<2)&4j`Nf|&eKN5ALkid?eoWZpBm?O_Dasq&-9c31?C^=$_rh&(EPiEHkPBS zh3kqgTWC@9?-uC7`~!vV{OgnX_bZ|KhjXR=_9J{rD@?rNt9AQK(WsrSV){Tqf}CsL zv+cFXW0MEpfB3}7gOf5TpBQs%sCl{I^oNekBF2Wfm4IW@r1oNx9hwgUOqTP~*5S$b za)Ouh)hQd|+nwTfZF?hT=U^#U=YmG;^KebWks7TYuC(dRyepE-UEDscsRapk##FSYM~ zzEP`MHIB?8j7jP63(&obN*ynvUh!=0g6i4PZLckAVY!~%w{lOy8ardQ=IT=wV5zu7 zS3E28kl&j>d0V}?JeE+8lAU-g#aa(dHK<=2@yj|i%pbt^hZSI?rgiFrC}%F~3!2h8 zDM@L28hsm}0d9aUXlM)2H#-Svpvg|sIs$DmZR9#hXrOtXrv{31nGV>Ld?z4(`$hhy zXhn2VqGzV_D>!=`OLxNGD|%b9~TxVHv-od%6ZF%<~!xJ0hPLTuGW}v z5{W(yR+ z-z6}gV_UgMts+V{2`eL|j416eII4D4)T5xbzB72R+k6C=f-3vN!GlM81>@MtJV?Es z3SSSO!defoR@dVxE%COVNc}}V)6`AP)Ee5hKWRU6TZ|}xD&*Iq`-j@r0pkC*a2a7!tdnxqZ8PV7!&WlZU(Vo33&sXxW2YcIf53g}0q?27?`e0X z@#)8hzJ?s0G&M6SJ+THop&OeZ2R4IO4&pfn{T?uY*Xw~Tyb|sK^wEBy7f=34%{(Mj zF3}5eZ4eaSCGTd)xZ5aphJ@l_m*U|liWdfr0yyVA?Q!9pjaJow{w(fg*Neo%m-Zh$ z;ySZk*Ou?^fcSNF)4qb~o@p-f1 zq;Z1Eg_Av3GwY|JWLOQbx9m9@a~1fk&o~)4M(hge-FT06b_~jbFb%dccK)XSZft~y z4W7)g-D3}rS*>QXW{*wmoY=E#=fuP{-TWYCI4ppcQ6m)@&x`D&)Ll3F#=TSRL1A4} z;!PbF|F!n&}z!Ipioqf9{7A7Nv1(;^NmjRr#+po%Z(Cvm1C7LsE-OoWrnodrQ$co5nxz#&!y56Wc+G#bu zPO{|}2p(ng0ax5($_J?Wf7i~?QOHgAu)3t&bi4M1jvfiN=pLwwZu9P4J9`y^tum{Y z>b(l*8DGCeXG%7q>$;|EiNAy<(1-80Q$NTIX0*JPfoG$jZT?X{@uvQ^mXCc&Yd=Mv zww$|fS)3ttU=X-{bztJh>cEfHftykXM96MhUdTRs;@D(Z$eQoRs54(8C<7caf0D%t z!6||%0)yZ*K^-70qGllN%o&`XG|#ezFe34F3OAbNxgtS}fGkcpkNFBq=LlW}D2=L1 z=Xv4+fRm7h+dLLi;x(3Tjmw!G-b?RjX;8%p7I@-A1TPbOnBXG>LU@>onLo`7KSOYl z;B|tJ68tPdkK=R&M#G09HtSsgt7SBUvD;uX#2E5*&~Wp79iCfmJGofV)6AN!=2-1K z>_}z?D8BFI50KfnH;4Egf`bTR(b0LjHQzLp(`XQFcuj~FGg(TS``KUb0dR&6;hxEs zeTa7iJe~5Ax3F4Iw{Bmaf0Kj?NI+-^4PIdb;*NXoEpz8^iy=N8NAq3>TE}tV8f0+) zTtTu2z0S5aquU607zy7ajSvh&iDH5!Jrm4Gibr~D&RwgAU&n(kK5<%cEPKo&b9B3czd((s}@hyLpFkQIR;zP`PK@6~fQGs5O&Fo{Fj4On^=>Iw?eg=4)2ao-*$z2{b@L4z$OV1MRWN5GRI;7b7#F`hT7Nr=Lb__k(Ff zbukjp#a_gydj(-F^p)P;azN}$>8KyG4n_N$l6%@#7dLPw=`S>$MQl+6=nL2KdGYmd&hkYH?{}NtYyqq*Gi37 zn-{Ldd#t!?!=@+$qD72=TBRUtV2SBu!h^~~i>Nc-OAGAI8m@l%T*f+}Ncw8(Vv#UGwFv9ydSi5H15}o-) zw0gY=kkM&y&=5(%6cKae?Zn%ebn1us+?Vt@t-bGd-`kR`(EJqeV|uC}xf=^wG(A13 z;*LwqLI#@<!mr1?igVxM`Og5f%QxS-o~Tii!p%{Mm>cG2 ziG}0Nq^vVPNAR}^!W215|^d;r%&W&)|F$Ba3aS>Av06>WERrKDERXl zH=|2bVR{!AO&96)A}?9XPY`8x`4eN2z)0d%oWMsCVW%lD3ZB5Q3WnCvsIHugnFten zN`Hk(!+JXa)BB6-d7F#(>z#mkiH_Kxx zUKf}Fe#;U-GK**$?zWw)6TNOm8)UEW24`Rj%4r>0WECZZ%^p5c8d`<;a7H5Nz9|8M z;1^8+Qw++NS^7MIaIP=2F{HZ)*^X*Lp>g6<3b7bXt!VWImXH$EH*w^P)V5e4saBg6@bf}gR| zS29LcqTo3%3U1^v165?{FzCJC-sd={yI0 z*eJ*xPBCufN77lV;78JjFn`@hy2{e+=%V{(Y{Z*DJ7P@M73S^wxIc#bb2g$f=EsSL zCE#8oIH9G>H#BQwt?0g4TjBF~2JvMU`ivn=$#rhR(bD|F6R+Y_7rxOR+7U3ScW{m= zzfD-c_V5oi*Y1R1Ef}quN@x5&|efKXpu|v=JeKcZi1W>-`NR@B@ZTDnoil>0h z6YAH`QK(B|>D3tIi6pbo2$A&ZrzCw-m$Xutz#WaE7v~UG#&A#A2pWs&dcT*vX+9)Z zRT?M5*gA|sBwQQmCDKYWm>e*l!7kuM@$p_*CEim$-sxM!n+@Up$|~{xnveIhw}>|b zW{_*EiJ@QfcRhLDL^oH1VO5?14UOx{_Xl1YI zvh6K7SJ=)lde>Z)>Nfp-o;Z-5IjlnG{GT)_Y%C1;1a>d34Vd#lLWs(@Fjx3 zOYjwfe@O6;2)d6YB`PxJCV*}DSRDJA7#3UUnM5X)$z_uAKb+Z`xd)|8W-b2LWrp(^ zxn~O1Lx0qV_u*rakiS|Bg}_Pfb%yzdo_E)~9c8|O)!r(@)H~VZ(3hNq@X8>{1bzY#5{{WfS|b(_@VppjX@B9t1Gugd{I`2N z6RJ~M%8()Q4fPEvm2?{>jXo-gVtOG730M1Qpo!^~qolwm0sPtw_%@dM1S$D>)Q)43 z4=I6qxQLPlt}9Aj5v)o^-99B)@Lr$bEQ~|IKy*kI((|%t#*kOz1_Fy112EDaqBL0|0|ea_3RZgxsOXj4(}{#6nW#dR?OFMJW|%9`>tV2W(F)LsK{D2c*?45r(`suav_N z0Czx8^~flZTfU%D9^;?F9oN#z))SRbls+-(PO>o(SwF{8m;I3QIQQCg`+3m3 zjzUXA*J6%YgQ)H4eyP8_l)9w-f~H(>-pp0gS*^@izQK^>LgRk8>iRw$^e&fBfhyW_ z5}blx!_9(CMJ$)Vxl-CySfU4(IPX34<%Q!%+Uo&|5@A9n5>}l_1?n6sROmYdF_5~> z!by15_k&o87pkC>==EE8uBVSn%lEOTXQO?r$BtdkqlrPu@X)o)+gJl3ixO_Cmjg{M z+1KVbfd_pxCR1^lzCwzPeZdQZwAXnK*QsigK~+TMq=IyoOjbv^Qzy-T%D#x|kcXD< z5NVH@Z_JQ;7rMm!=V+H2nDu!(YDP*h9K(5|V_{BN>n$Or50k zpy5xKgeO6eKq-z!E*8CnJ8}rnaj_^Rh;lS`DLRYmilRBerX)z1BDxX#MV4hGncoY) zDQdj%n@11@T|9^sfM5=a7LYe`?po@;c51196WkZ>mV*C55mu zZQRI=itQ=y^aPlnY);`xUWD8J-_NEmX_t@j%_Cs>k6B;T6*Dk7>o=_>OW!A;6{8iHAi|pV}14KyMQC^D@E=t;l zyTFr&u1s;GU;8L+>17(k;^^h%>}ncYvV+Zk2MkD#B2e zwW?_q?H0wWUqR_3d6yf=J*s!if4@{`z$F8PE=<4N=!w^Z6tDjQw=3g$<x3QoE{g%_~NfEE;i@xZX9d>q28nt$9^8!r~6|bhj>hP(EdjYV7|Y43*P#a_MeS z!R~Okcw?m7?6Vvn)W-5bZS1k7)R2{$Te7pvZ;|q(8>3tLfdiaPo_HF!;V-k#dusBf zWu98OdZWnjp&q?M0G+*12LgtA(lylS(v3FM#ibiE)cZn)`o{v4WT8+fmqC}3Gq|qQtXaYC zI+Jgk5*q5c5G8*Qprq?E=u+}UpOP=%IZ6z=e*4?^3)c#5e4`Ac(8dFnlYF{bZ(Q)S zf+v=d&ju?p8M0=957U1b64OechV_KWcJ?LwjU7EY_Vm*$h<{mhpu|k5-FAF@*1)}w zEJK<^9Q{pKHDq|wa z)*dPSk}lS`ja+2QVXIYUcSeIB|hkPSbN(crrP?)i;U`O(P-Hs z#T=b4!~D-ciru1mEot_2q-VQA-0R;{zv4iT1UX3sLh z$Y#yRMuxi-VdFpSia1M>9$lubIk#G5b8EZJ?VmbR$2xs(xyUBpy-btUK-HC+J&g5% zt<^P)Eyw(-QO%YH%(ZyY&hX-QxoSCF2)`jGAM9#ppr^B(k%&iiMj{A}l;N91hPPy^ zK+mi0iSTKP@B$u3iSSF>$6?lgT2pJJq6B$#Ga+FWCC8QNxp-oUf7A`)svMq8heRWj zgW^<_hxcE|G&BDgUlEdUhVL85^N}P$SQjglyKwO-VysDa@hX`d}>`&6o*~n&d^Xr zD~s$z<^4Sp-0)Vt49`w<8!pO&wl}*q)uFNy;pfDP%wp@bG7r;-CtuuukV^M3gh<&(csad<+?V*(ils0}K(M;Ttl?U+|1RSxy4ZK>| z--aPalPeJX-C!S;-7OKqGoM6BcDg~bN)CC--hQ#qu!ijMa+Eb#LXEtXnSG3P2=30Z zA7o7%vdlcit1O{rztm)yvfzd(c}7+tNRrEd8=j2M47ijgT}r=+)qGt_KQ674(oae2 zDD_bKtE?fEeugy`Q99&P`ekVgG?`zOwopP_Lvq(@C>?Yu9a5CSy1E;A)m>WuSlTD8 z|3lheOzU;l5L$mISbKYQ#G@5$XQef)K7{2qAnmD@h$C|6YUmwy=^gRueGS>(KE1%f zx1ZzgM!>OOa4e!3wT`hCsYb1ng1wjSH37O$OPeE7`>eEyGWvOq+_@UMM_szt_;h~@ z`ws=_ep7HLy7~SiMlk694c2n$eoL_T(!DM~_wP%aYlQA^N}DKy?sanKYUp0;(!I{5 zTeo&&7wjP2e=0Z>-G44P7WcPqrCCep)=f3zdgF>D{dt#)0vVNb(!SKcPU%CPHcm&Vj>z6ekI>}i>*^KEv8D)E5x2m`UnY>j_XtZ2 z)T`xAJ=t2yV7KIF0X!Ybn^~8jIj9`nC6~W`A@>FCE$#2&|7*(_^3lytVw;hE(nX(u z0IFWq9>)`Wlb~LR+fxVllvI%$YS?$?z4)$`rV}}Y=wGRmm;QaDT?L=hoU*o z9Tu53Ms3euH>Q%dAd3`ErJEW3A-vyMCEnlk@&48=;w^;m{%DnWzvJWm-Yw#VCfe<1 z=ns2WhZ!~fw+{^|{Qg(p+t4mU*cVof{j~Swz|PdY?&w=R_Msl^Ut2ZyUk_ma!YyKl zriz_O(%9P(ar=&b4hiy|B>MIH0V+m#MV4i`V_k4z78#>5qT}4fqZ5~u`iwh^yj?Z9 zk{Jmj*i%?H) zO?df5V9P9#oF(vFa~5FI6e0dq9{&zOc#)9Wu<$xB{3*fzCHOOfmjU1>>0Jp11<^dh z>&FNr&9KUnB=dZm_vFltJRvHikg*EgVVpPi1@iPxvWXW6P7sJ9@EA*yn)f10UD+u4 z@f&SPpo;pHp3hNQQsGJIZ(=dgepq@|9P0O zuF(D=E_{|`FpG^sJFlBOSGwQ)U3@ti?A75P=4z#7_7Qb4_1lt5Aa zMB`KICuF2>O_k^<(j$TQ>UW=$c@inAa4qje7b?9j5!;&J4_&GMyf&z9ju+yC*$M2b QSjfaaT$qb}czEdl0}Pw1EdT%j diff --git a/./tests/unit/__pycache__/test_error_handling.cpython-310-pytest-7.3.1.pyc b/./tests/unit/__pycache__/test_error_handling.cpython-310-pytest-7.3.1.pyc deleted file mode 100644 index 07bdcc9321511a09acd06ce940d5ebcb7779f7d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36345 zcmeHw3y>VgdEUO?xA(x~01zaXq)B2);tnqoBt_C7LGYo7M~Wb+aA|q9xETO@xZ6GT z?BOxmi)E2=6eJWCSxGr6#sU;e5$srTWIJ+HaiS#USgE8OCsw7(qd2bEjw3piM+J@( z!J?G!`+J^yvv&vZP*SM`a{GE_db)dh{`dd)Ki!d$QUd=@e*J}ZVJ4CILmq_xrEzcw zKdYKdB#cBOVI+;ze6o>}zv+ccDv?YqWE;7pzLIa`GEtDH&xt^J? zEL0j5sb}X$7OIWv!f0c3VXQH>Fy0tnm}pEaY-(&;*xcB>u%)qOVQXXS!enD|VOwKc zGVx@>$Qk+95=Q>jRAW1i3Puq}MI7zGQOPLdsEnfHBv_t^&R%{ z(ue>ERW(zou47QA!R(UN>n_yHv$N)6uifof z_tcKC{Znjis(Whqt9z(fE%e*ATWmH}dW&ylrk-*#&va)`IoZWlZ}wz8=@g7s&+N4q z%zD|`^6u`E>a^ycRVQ$-Z7S*7nK<0-9B-dkQqq+^S9tWfr*&=grPh2KonRFA#1rj# z^XNINXD;XyBS+2ICDrbo(}xxLgFbua*`A}1-03^xC(K^c3>Lst&bYPW)XvtD(e~Oc zn3mN#VKyyykXb%x_+JVKhw!uZpfD4SBtQ_L*GL;FBmG*Ukufqx7JsuEG#WV#6ac~! zUQpTD&Xtc{8T)o#svT=tW-YcsZFat8S+f9p|6dukJbs7qv;GI>)&tPLnp{ryl70Tg z%$C#rR6l(xwVdf^tQUK!3z%s?a~!C^^T$&cG2>q5LiS?fbi%qF*K@t(#iY6mwLEK` zB%jD{aUWas+2UPn!5Ug*rJlN&l)veVNh9}WW@}VzTatgl;W|*8SEdxz4$9zA7xfc2b>(gk>xfU?j63@$AgPD8?*Y!+3ETe{Jl6Pm4 z!A#0;rZIYv2-q*mOiJ!dN}-wLF_QwuPtKK>^KHy3G?U*C&7>MLldt+S`Hc;oNd;?C z)H7Ma?D~aIB&|DTE~Bz4V=L!^7F=anmro?s$cD~mBs8C-JD;x1XI$np5j&sfLh~W~ zMPcTe-;_}VK5eoEpK7zp1kC__1NO9MHOaCk1fK-|Yc5`2N?u7l{BS*K@40=~;<^2G ztMnnaQhT_@+Mc?#i+}glt$Jy>_OL#CU)^%jR!=!u>8+k|(rC(JFN^&w?z>V2cABau z{oKzx7SG*3b17+0v%`JfSf~VJO}X<5hPtv&dE z#AwG!C)KuaQ$A+ zBdJY%uF|2!b3HU{wm^@Yv#t60rZN}RYZ9@oeCDyK2^P4}0&zdPIN$0BTXFL4nmc)S zjhw7Jvs3iv4?3N(%=zPLGv{08g!H@J7e2T*d~iS?=du+7J!`sq%xW9$R%d!nP0wjI+w6LLcAAT8O)r6A zOEt^_fg@9k=bY`b{(>y-RgFR24xiYnpeUrim^z<4pZrAfQ%QS2Q7M)wO%3fS!l=lk zd`9&TaX^{ONWYl^t4bIkyGHi))N-nynj^ykLx?b}oG`3B&kGYM`0S`I#088;O$lj1 z4eW>{5UoM{i+;;Tq$L^GVY#Z=MbRh;d&nAPqcVh1jes?zmovr)2u{_maVY|Vqshye zcJE}(2dC4m`6ctMyYQ5dkkY5jb55?av~bK+S48g8zkPDD5wGCHh>LrbWGU)9b`3@F$ve?C<&VrLxceA($MLn%Hn;o+iNR;vqJe6v)ux7uTohuFp-lXys6pQkCR|l)ZNi*jm}caLD=WMw|TJSRJ-l~nj$F5l_dyK-8 zdV=TTU}(z<7~)!RM1i1!hM?)IfFRx%_{79uJCLi`2INkJqn z2+b*oNLuU>w(|X=E0U7tWbw^^BZ{G=EwV{DeEOh=ybPeS8w(0UqM z{1m?SCjIHv{Ch?4-XvkCGof+G_XGF`*I9uvNseov`U0R?Hy0C5i&naAF zyM3F+_HO4iBt{CZnve9lHKRRC?YE`QIR$}!@{f|#gs(IT9wAVV0d(srO`VB~PD#_s z-nm6nBPf~6rKCOKinN|Q3!J}{Qd@CRJzO}iuVAiD8p%7=xpRe64?L*XkREl$4IZStxL$V-$r4REe{CB&)%8uJn+ z5*E4k2=h|=I8&eMgn8Kk^#HF8@Un`mzdI#zMr#5^Ayr6LlZDhAq!uWSP-^Wv_+D2L zl-w?6>IJ9J6#2E;bV|*pw#%SiX*NH&)S9Pci=%wAX>@0sP4zG*^(2dTvv`F?Fh{A8 z>8ddleCb+*OJp#+LaC6iRoW4ue?{8+h8Q0>mTPx`7}eymlvRH?ydrm{BIHGc2BE;S+#*Sy438 zWFSDjRi#8tF3KAJ0?vMb{ZLfY81*1)FSCduUkHpvY`jTOO{*r6r`KZ1Bj`bRe#00CiR+ChN;Pd9Pl5$7_L z7UeIYtsoGPHNtWr&1JcW92h_wph?CWiHxp? zO7I%EQ_{fl9D7+rVLx?E;1Sd13{>(TW4G^T@#8F-EKacKvQR97sRyD#3m14~l~5G4 zXppaz70(Y94Acj36%4<^2k^W!_9h+???qr+gSlVsg_wIlQdh9|CxpaN{Dz|K`zG=} zd#B}F+xzc9&9frsr5byR#vC9~sKtt1f#VHO7Xpca9bK z7D~prh!QX-g+z%jZTwh5+$ZeK@}jgTK{74+fj!D9?XI=sEqK=qCkM~cl3~8}r+8DB zQci&)h1gchFz5D`ESPQ#b1jkLEFdM0n5%OHp|T>2y?VJq9p`NUJPRsr?g9RqT1Mbp zwc3M*Rsls7lEHiuMERC5(1s-f|DFiNjaUM1!cxE?MJqtqiUeB#USPTh8VK6}9AnTW zGyUwTB(&C?^`3wM+R+-Loi8 zG`J3paz8KHbD^hgNCj7Wt^mB!_+{|RVJ^@_WQN6l5ss{qF|u6lm-=PQ6BKJS+Tx9`ptAAarTG*ez*cZT!A0>6}VFLNdvUg5sxM`NfXpED0Tb)g38w6 zlYPkd$-*?HeX^(oep2zt!gETW>_gfoD}K;86J_Nm zcxj!Qv{Ud#am`tc5mHn5JT)~Pu$zYsTlcB`9Ee)B_MmD{9R{3o^0s40dX!P zPQJhy<4cBWuUrkBF*LYGmGG4^A)S2M8FMF}P!wAcEi_|;0~+3a?_CIIv&%>2q_k2W z!sDnu3j(*6m#70cRk<~Ijql~<=ULEXO4nQfF9E2Ca(gFR(K@I7b1PhPf0>s8g;#Xl z2G7NrF>j4~+0nUChYdGHP)D?JrGDGZcBAGgECf68EXrS6h>ET~l2TrLs1&8Iu);DTN<`Eixs3kOP@Jf+_(4iCM%W%X z>MAUVI2@v|aICPxG7+P&{PJ}gYmJZTwgwy>7N|5a`g#5sW)-*r97=9r3JN z<^psi1pe@kJ<`@TuibLksK_5T$fTZjO2^wKLR7Tsr0Zfeaz=yxn+gG#Yw1$26CYx1 z6zb!=@CFJfRMd4gNTIqx2!mFrLQ@s8M$ElT3gel)VD$R-`TTrRbS$9{FZrt%A`|hw z4P^%EB%i8kKPYb{m_)oWQWkfGNpj44X^UX-XS&L83VyAqt&-XxDeEh1DYfBtNNF?A zndS9*PoW-S=R%`>q8w{Cx=YqJc%XReqCAk0Mll#Y!c~Y})^&9hS>+<8?6cfo!-2%iiI=hVdbvlzx2L*cQw+}r1K0ej;jgtj1r zEs29Y&D688_W7UXgTw*Jr*Yvk9G-4hTIDM##dAXeP zT>TwPI~J~fju$@1hbk-jK{g1u!tu2JQ;78RB@Xh}QP_8{cp+gxirzJxf9u%Ub=##i z_x_&SrA6W%v3CmqdXE~G?1Xm{U_!Ry*2{>-oBlyz+&_Gd@2?fGCysiA>~y<3FYF3Y zv_c@HI8DqI&ERKP8zK`45xHiWs3?L4;r<##B5lp_uqmuo_`QA=Gxx1IpHJBbhR~o@ zJDI8HVtLx(kO=v}Q(i@?fjK<7l z(VxlEhR&pnnM9*Rz8;#%Sj0`>;V?{vk(u%gRBMgUv%|t0xtqdE?06P>0hI< zD7e0!#R$4TKVRTRvrlrPS$&XXYZ*DFuZV~oA=zIH8Sq2F#N1~bgs$meV$Y-G25(|p zk7vA(4<_D#Xrtv6b{B_da$X34o=L?g0KrU7dFZc5vOSjZ&SyeG9St)5x1pI-V`lP8 z{!B=+H)tl}aU0cv^HI8^y>5niytOpn)3#nqT_U{wJPL%B3UCV*aRs%1z>5N^1IVsK z4cpAizl;LjRlyE{_4Q(hj}z)w*v|mWYYV!leBMSkkT99L^$oNatCR-&+;`4r&nG{g zwC{-r_V`XB@D2!vu|RuKFCRjsmzYDyF~gQ$PrWgkO!U$WU*@LDWa52DQ;|tz60fF_ z<|21Au{U9DKa)%%O-3^DGJZT)&)fSrn}IhVcqgKB9&mk<+R6SX&*U8(o%Q zW=$C{U{8OHjxN)hGM1_)Z~d+K|c@t6Ddor`X&5>fpMmub@{A z<+5rX-rHtm(lXCCj0x=so+2IeiP=s^3JRBdf0hyN7le zSy$3lUj=+{6&3J85}QHcJ~Ijo3zZBeX~|&1a7%7EPjizIl))0BS@gz2H);jfu!$7z z|FXE`bE4|yB*+u-EV!EMd%>4f^%&%tg=0KraEm7LqM#m(WL=k$#$+VMDhDHd;kt~( zsA%C=P}ahu>C7t|y@CvBK&V7`B&b6Q~z7AEqcJ`*x@X z>g|{>^F9&(CDiB%9>(n4mK}lD*{HOe~CSEHg+d^JXNa<4{Jr}b)#8as>|&~D7wY21jv&bqineJu!i8;iyqn?#qVrhHxQL|TDT9jmBwd3ux zy(PrH)ssk#vS`lICd&ON+1r^iFmSq*hB<8QfV-jfEk^eYmM`+X&evWcy_MJ?N#Ecr`I-saqn6$zZ2G ziSzJOShwN2w$w3>%TsnTQV(0|vTpPitb#)Ct?b`bdXL!;~~u&+wX1X_MfTB3%X>Sd{8_kdB6 z2#lgJf@rb$v{=EskvR{bH|qL|{bWn>T5IlWol&=8^~di zuo2bUJd@9}>*+Gc)ubbOk@>`p=tbzdw!c=>y7 zB<@gr$sv1BDfOkWmid%Mz_1`Yc>8d4x(~#|GlbKs!n7EMWVw?7qQo4~iD@z}N}?5! zhcd`sCP75VUdHV)hY6gJgcw9r_JW9T4k47-fWp7r^imOdC@+BNg$B_Yg8XOhW|UDN zl(*p}ZBR2Y^0qOF zyBM<2w(Bz1RvBwD{8SigX5+^)wvoj5(@60FxWgE4$QauN9(M4Gq%mt6-{=u;x|+Z9mw#ZNJE`82(L4tcW_AVI7EkZ zJF`eQ8ru6!xmA6G#ZYmz!K)9j7$B~We@Kqh+&wH#|8En8UcgNPVgMV2fZlg74wSxfi zw45+I@RrTio;wFgx)Y@KwMU+GuZYyigaZCX=9g@#6P66*h)aFs>d41`<^Ts+2n~6VwE;r% zBSQ#@`-U_nc^4@O1QThk%JFm;2Le$rPCQGNPHW~_vLxl5 zwqzL~apq2n|jS5n( z&Y!c*Ce24{5f*z)Cm{%S-RY1hN*58SMJExX)Iy1*QPQ_Hu`5kSYt7)SSibCAo{;ng zG_b)>wSa`E*o1CsPnNz*!arf{dLQo*(PYeJ&i*F1p_Jy5Ya|nbxVo@0KpAva!@+@` z1t}1RK_N9JDaB|bgM~~|iVa17f1X3}-TDAJ_B1y)|3-lISr_XmFr*Qeuk0gwElYR3G z^q+?DLta`9S&VxvNT_y+DC1__D0mrs+n>VQQD*>uPLYxHO*m4_wQxma#yEjipj{B5 z;&n+>08EqKYliv9tPNwBuC3Q8`jE7SOB@S)fq(@_NB$z^=aB7-5gxfEJO;sO#UUgQ@@bmHyQY3+771P27< zV+oKZLZ(Dj_A)tSd)|gpCU(~exS7~pQYu1KhP_#kf35F|Fq1!ju8PnaP^LfGQ z41}+&GA_e$^|*7_I4-|SF`{F;yszdER81yO@VvQ}7D8PRvFy8L{U7w8@!=Io6zMQ= zLl;yVOU`dHFyulK>~5tQk1Dr5<0siIx*zQ^jRA|?j6LDP{lh_u#lVhtsibA$NqkvB zzWQToc=W<_2W5#nF`>Q9l~e+ND2&9N&9z&iR^a{=YM5o;}=h&;8HVZeHfa6o%kptd2QA`;v)^#YYR zhF2dl&*B3k*n0|%&K?^)v&JggW3L=}=Fz9$)AaL-u4hSgikuO3i4Y~q^eRxs?rP#nOR)M5g){*^E~Gy&$B0!MuWJ<0+|#eY1K3u#KnC$ zY36|0(2;boQ}mgB{<@5$Wl~q7ca3a1fNP_lX)`SGbX=yvf^`g)D{pjF2KaN<9hL`WOy0(J?3;Cm}6E zeCekEz>rqmh*9oF;rID)Uc!HZ@AIt@lw@Q@I)d6B zlv>me6$UaURt~;ELEXiNIrTMy0^d1;e8h>2kEMBD9wf~Zvr#>zv++%`Z6LlN!x>+> z$`b~H_d6gsMm;$QFx$&}zQJN23yGOouizMdA26x`Iq)@!FY{$HvaGTmp)t0L891bV zjI((^iyvpvWbyA={0A1l&*BeRe3Qk0XTfN2^`|V}X7ODX@GmA5_F*Jckwu9`Ak9gQ z;Xdl$pxA6t%;(Me_()#L-?uBpg?j6`F_Kb z@)5N#NV}gU(O*|{H5RR&LbgB(S6cX^AE1l?_ovahj<}Oj02`Ja0`}8AxS5Qk0JfYL zz)sVOAfC}%a9zF{qwYXW;_jgCAtcyuu}@ksckV%Hp>yXV1StmO@!?8@3;7@qx~;)o z=O5^uZ1vzL_2SOG2Lw6A_e!lA4GZ&TuiJ?POc2(ggSmldPQkrvuV34T4M|(L#5bad zq@Sd#;&GguK_;9Lwzx_J7$IQrYqx>`;g6N<*WWM+fQInmNWf-u#RE3|2m304iEl2K z$|XOZ(w_iW2@N_#S~*gw*fkMMZk!xdHCeZv9; z3SS6LIO6~jY;rw~kQ4W!Q+2~ayvEpig^#eX&~IFa3o4pjuIb%xW>=e72=Np#NIab_rCvJT;)-dS)p7}}^K;pC6zc9e1^L0&a}nhEaXGCrBoQA;Z#nJ|fr6R>!lGX){@;3(|{>K(Lb?ZBzM=8q@qn{^u7&Ms`XkTb52G8fLb4eM%GXqMqgnA9SSpErmPQiXm6@ANwVYXH+CnNZVuV!rYNP=(M z>|H_weSC9MFK8svrZ(3UXHO3p;$U8GA7=~&13hBwacUB9^@3)Y4zXcSE#$_IxA0pd zua069M5y62cGx>Y>tEBIl6H3se&!ZTqrJ2MTlK0as`K_l6^%=7u-7`!R=DnKobi4Z z53=|uivj+7qwrsigzk!J7r0FS_wYdSMK+Dp7m^BKYrl&xVqIvxvd0K+7cn6Zw=chd zvA#Dvil3=?EddOIv-R-=u-nIS_+Pq==lAjWB|x{v<5!5Q!|)jR%Wt>i6K`lGpU(8Z z9M5mH$H#SjRxoEUu8S1mnz^rF&Suv&8IL)xE62Wv_J0^xJv7nr9&w%>I0Du}n8;hE z4wuHy!te4<6h8?|!uyNd-ee>|ukgvvm!ML95FR&(LTR-VfZ$BPN@Q?4$fyFtFhWZ@ zGXlGpQch}WkCPSVHwewehZstDc2Pa@Kheo$D$J=5??~9sT^q!@UYn_h^tZepnew|= zqzG5;2_@u*vb1{se<$7QoYNnt6*`bP**g)ZYu_l}D}( zmj-&~S61Te_B%M+TUbo9*vH}ki-RnJ$B>%XhSC2LcfK= z8A1Fy1URcFdzoePy`ns*D4sc0F=}bMBy3pv^WU`b>NsGUv2-?V3L-iMM1?hy7Hyss zHEN89Efy3rB~`hkqgEGq_yrbpM~E>`9yM--oWeu;d)AL&EfUtPC^Cs#Glk?>vXVTI cs^+VARd1=@P~BA>t8TA8llX~hePYx92b$UCKmY&$ diff --git a/./tests/unit/test_agent_core.py b/./tests/unit/test_agent_core.py deleted file mode 100644 index 58b0a4e..0000000 --- a/./tests/unit/test_agent_core.py +++ /dev/null @@ -1,457 +0,0 @@ -""" -Unit tests for agent_core module. -Tests Agent, Command, Skill, SkillResult, and related classes. -""" -import pytest -from unittest.mock import Mock, AsyncMock -from datetime import datetime - -from agent_core import ( - Agent, Command, Skill, SkillResult, SkillChain, - CommandContext, SkillType -) - - -class TestSkillResult: - """Test SkillResult class""" - - def test_successful_result_creation(self): - """Test creating a successful SkillResult""" - result = SkillResult(success=True, data={"key": "value"}, message="Success") - - assert result.success is True - assert result.data == {"key": "value"} - assert result.message == "Success" - assert result.error is None - assert result.timestamp is not None - - def test_failed_result_creation(self): - """Test creating a failed SkillResult""" - result = SkillResult(success=False, error="Something went wrong", message="Failed") - - assert result.success is False - assert result.error == "Something went wrong" - assert result.message == "Failed" - assert result.data is None - - def test_failed_result_without_error_raises_exception(self): - """Test that failed result without error message raises ValueError""" - with pytest.raises(ValueError, match="Failed results must include error message"): - SkillResult(success=False) - - def test_successful_result_with_error_raises_exception(self): - """Test that successful result with error message raises ValueError""" - with pytest.raises(ValueError, match="Successful results should not include error message"): - SkillResult(success=True, error="This shouldn't be here") - - def test_to_dict(self): - """Test converting SkillResult to dictionary""" - result = SkillResult(success=True, data={"test": "data"}, message="Test") - result_dict = result.to_dict() - - assert isinstance(result_dict, dict) - assert result_dict["success"] is True - assert result_dict["data"] == {"test": "data"} - assert result_dict["message"] == "Test" - assert "timestamp" in result_dict - - def test_to_json(self): - """Test converting SkillResult to JSON string""" - result = SkillResult(success=True, message="Test") - json_str = result.to_json() - - assert isinstance(json_str, str) - assert '"success": true' in json_str - assert '"message": "Test"' in json_str - - -class TestCommandContext: - """Test CommandContext class""" - - def test_context_creation(self): - """Test creating CommandContext""" - context = CommandContext( - command_name="test_command", - args={"arg1": "value1"}, - options={"option1": True}, - config={"config_key": "config_value"} - ) - - assert context.command_name == "test_command" - assert context.args == {"arg1": "value1"} - assert context.options == {"option1": True} - assert context.config == {"config_key": "config_value"} - assert isinstance(context.metadata, dict) - - def test_context_defaults(self): - """Test CommandContext with default values""" - context = CommandContext(command_name="test") - - assert context.command_name == "test" - assert context.args == {} - assert context.options == {} - assert context.config is None - assert context.metadata == {} - - def test_to_dict(self): - """Test converting CommandContext to dictionary""" - context = CommandContext(command_name="test", args={"key": "value"}) - context_dict = context.to_dict() - - assert isinstance(context_dict, dict) - assert context_dict["command_name"] == "test" - assert context_dict["args"] == {"key": "value"} - - -class MockSkill(Skill): - """Mock Skill implementation for testing""" - - def __init__(self, name: str, should_succeed: bool = True): - super().__init__(name, SkillType.READ, f"Mock skill {name}") - self.should_succeed = should_succeed - self.execute_called = False - self.execute_context = None - self.execute_kwargs = None - - async def execute(self, context: CommandContext, **kwargs) -> SkillResult: - self.execute_called = True - self.execute_context = context - self.execute_kwargs = kwargs - - if self.should_succeed: - return SkillResult(success=True, data={"skill": self.name}, message=f"{self.name} executed") - else: - return SkillResult(success=False, error=f"{self.name} failed", message="Execution failed") - - -class TestSkill: - """Test Skill base class""" - - def test_skill_creation(self): - """Test creating a Skill""" - skill = MockSkill("test_skill") - - assert skill.name == "test_skill" - assert skill.skill_type == SkillType.READ - assert skill.description == "Mock skill test_skill" - assert skill.logger is not None - - def test_get_info(self): - """Test getting skill information""" - skill = MockSkill("test_skill") - info = skill.get_info() - - assert info["name"] == "test_skill" - assert info["type"] == "read" - assert info["description"] == "Mock skill test_skill" - - @pytest.mark.asyncio - async def test_skill_execute_success(self): - """Test successful skill execution""" - skill = MockSkill("test_skill", should_succeed=True) - context = CommandContext(command_name="test") - - result = await skill.execute(context, param1="value1") - - assert skill.execute_called is True - assert skill.execute_context == context - assert skill.execute_kwargs == {"param1": "value1"} - assert result.success is True - assert result.data == {"skill": "test_skill"} - - @pytest.mark.asyncio - async def test_skill_execute_failure(self): - """Test failed skill execution""" - skill = MockSkill("test_skill", should_succeed=False) - context = CommandContext(command_name="test") - - result = await skill.execute(context) - - assert result.success is False - assert result.error == "test_skill failed" - - -class TestSkillChain: - """Test SkillChain class""" - - def test_skill_chain_creation(self): - """Test creating a SkillChain""" - chain = SkillChain("test_chain", "Test chain description") - - assert chain.name == "test_chain" - assert chain.description == "Test chain description" - assert chain.skills == [] - assert chain.logger is not None - - def test_add_skill(self): - """Test adding skills to chain""" - chain = SkillChain("test_chain") - skill1 = MockSkill("skill1") - skill2 = MockSkill("skill2") - - result = chain.add_skill(skill1, {"param1": "value1"}) - chain.add_skill(skill2) - - assert result == chain # Test fluent interface - assert len(chain.skills) == 2 - assert chain.skills[0] == (skill1, {"param1": "value1"}) - assert chain.skills[1] == (skill2, {}) - - @pytest.mark.asyncio - async def test_skill_chain_execute_success(self): - """Test successful skill chain execution""" - chain = SkillChain("test_chain") - skill1 = MockSkill("skill1", should_succeed=True) - skill2 = MockSkill("skill2", should_succeed=True) - - chain.add_skill(skill1).add_skill(skill2) - context = CommandContext(command_name="test") - - result = await chain.execute(context) - - assert result.success is True - assert skill1.execute_called is True - assert skill2.execute_called is True - # Check that skill1 result was passed to context metadata - assert "skill1_result" in context.metadata - - @pytest.mark.asyncio - async def test_skill_chain_execute_failure(self): - """Test skill chain execution with failure""" - chain = SkillChain("test_chain") - skill1 = MockSkill("skill1", should_succeed=True) - skill2 = MockSkill("skill2", should_succeed=False) - skill3 = MockSkill("skill3", should_succeed=True) - - chain.add_skill(skill1).add_skill(skill2).add_skill(skill3) - context = CommandContext(command_name="test") - - result = await chain.execute(context) - - assert result.success is False - assert skill1.execute_called is True - assert skill2.execute_called is True - assert skill3.execute_called is False # Should not execute after failure - - def test_get_info(self): - """Test getting skill chain information""" - chain = SkillChain("test_chain", "Test description") - skill1 = MockSkill("skill1") - skill2 = MockSkill("skill2") - - chain.add_skill(skill1).add_skill(skill2) - info = chain.get_info() - - assert info["name"] == "test_chain" - assert info["description"] == "Test description" - assert len(info["skills"]) == 2 - assert info["skills"][0]["name"] == "skill1" - assert info["skills"][1]["name"] == "skill2" - - -class MockCommand(Command): - """Mock Command implementation for testing""" - - def __init__(self, name: str, should_succeed: bool = True): - super().__init__(name, f"Mock command {name}", ["mock_alias"]) - self.should_succeed = should_succeed - self.execute_called = False - self.execute_context = None - - async def execute(self, context: CommandContext) -> SkillResult: - self.execute_called = True - self.execute_context = context - - if self.should_succeed: - return SkillResult(success=True, data={"command": self.name}, message=f"{self.name} executed") - else: - return SkillResult(success=False, error=f"{self.name} failed", message="Command failed") - - -class TestCommand: - """Test Command base class""" - - def test_command_creation(self): - """Test creating a Command""" - command = MockCommand("test_command") - - assert command.name == "test_command" - assert command.description == "Mock command test_command" - assert command.aliases == ["mock_alias"] - assert command.skills == {} - assert command.skill_chains == {} - assert command.logger is not None - - def test_register_skill(self): - """Test registering skills with command""" - command = MockCommand("test_command") - skill = MockSkill("test_skill") - - result = command.register_skill(skill) - - assert result == command # Test fluent interface - assert command.skills["test_skill"] == skill - - def test_register_skill_chain(self): - """Test registering skill chains with command""" - command = MockCommand("test_command") - chain = SkillChain("test_chain") - - result = command.register_skill_chain(chain) - - assert result == command # Test fluent interface - assert command.skill_chains["test_chain"] == chain - - @pytest.mark.asyncio - async def test_command_execute_success(self): - """Test successful command execution""" - command = MockCommand("test_command", should_succeed=True) - context = CommandContext(command_name="test_command") - - result = await command.execute(context) - - assert command.execute_called is True - assert command.execute_context == context - assert result.success is True - assert result.data == {"command": "test_command"} - - @pytest.mark.asyncio - async def test_command_execute_failure(self): - """Test failed command execution""" - command = MockCommand("test_command", should_succeed=False) - context = CommandContext(command_name="test_command") - - result = await command.execute(context) - - assert result.success is False - assert result.error == "test_command failed" - - def test_get_info(self): - """Test getting command information""" - command = MockCommand("test_command") - skill = MockSkill("test_skill") - chain = SkillChain("test_chain") - - command.register_skill(skill).register_skill_chain(chain) - info = command.get_info() - - assert info["name"] == "test_command" - assert info["description"] == "Mock command test_command" - assert info["aliases"] == ["mock_alias"] - assert "test_skill" in info["skills"] - assert "test_chain" in info["skill_chains"] - - -class TestAgent: - """Test Agent class""" - - def test_agent_creation(self): - """Test creating an Agent""" - config = {"log_level": "DEBUG", "test_key": "test_value"} - agent = Agent("test_agent", config) - - assert agent.name == "test_agent" - assert agent.config == config - assert agent.commands == {} - assert agent.command_aliases == {} - assert agent.logger is not None - - def test_agent_creation_without_config(self): - """Test creating an Agent without config""" - agent = Agent("test_agent") - - assert agent.name == "test_agent" - assert agent.config == {} - - def test_register_command(self): - """Test registering commands with agent""" - agent = Agent("test_agent") - command = MockCommand("test_command") - - result = agent.register_command(command) - - assert result == agent # Test fluent interface - assert agent.commands["test_command"] == command - assert agent.command_aliases["mock_alias"] == "test_command" - - @pytest.mark.asyncio - async def test_execute_command_success(self): - """Test successful command execution through agent""" - agent = Agent("test_agent") - command = MockCommand("test_command", should_succeed=True) - agent.register_command(command) - - result = await agent.execute_command("test_command", {"arg1": "value1"}, {"opt1": True}) - - assert result.success is True - assert command.execute_called is True - assert command.execute_context.command_name == "test_command" - assert command.execute_context.args == {"arg1": "value1"} - assert command.execute_context.options == {"opt1": True} - - @pytest.mark.asyncio - async def test_execute_command_by_alias(self): - """Test executing command by alias""" - agent = Agent("test_agent") - command = MockCommand("test_command") - agent.register_command(command) - - result = await agent.execute_command("mock_alias") - - assert result.success is True - assert command.execute_called is True - - @pytest.mark.asyncio - async def test_execute_unknown_command(self): - """Test executing unknown command""" - agent = Agent("test_agent") - - result = await agent.execute_command("unknown_command") - - assert result.success is False - assert "未知命令: unknown_command" in result.error - - @pytest.mark.asyncio - async def test_execute_command_with_exception(self): - """Test command execution with exception""" - agent = Agent("test_agent") - command = MockCommand("test_command") - - # Mock the execute method to raise an exception - async def mock_execute_with_exception(context): - raise RuntimeError("Test exception") - - command.execute = mock_execute_with_exception - agent.register_command(command) - - result = await agent.execute_command("test_command") - - assert result.success is False - assert "Test exception" in result.error - - def test_get_commands_info(self): - """Test getting all commands information""" - agent = Agent("test_agent") - command1 = MockCommand("command1") - command2 = MockCommand("command2") - - agent.register_command(command1).register_command(command2) - info = agent.get_commands_info() - - assert info["agent_name"] == "test_agent" - assert "command1" in info["commands"] - assert "command2" in info["commands"] - assert info["aliases"]["mock_alias"] == "command2" # Last registered wins - - def test_list_commands(self): - """Test listing all available commands""" - agent = Agent("test_agent") - command1 = MockCommand("command1") - command2 = MockCommand("command2") - - agent.register_command(command1).register_command(command2) - commands = agent.list_commands() - - assert "command1" in commands - assert "command2" in commands - assert len(commands) == 2 \ No newline at end of file diff --git a/./tests/unit/test_claude_api_configuration.py b/./tests/unit/test_claude_api_configuration.py deleted file mode 100644 index 1a75f16..0000000 --- a/./tests/unit/test_claude_api_configuration.py +++ /dev/null @@ -1,710 +0,0 @@ -""" -Unit tests for Claude API configuration system -Tests configuration loading, validation, environment variable expansion, and migration -""" - -import os -import pytest -import tempfile -import yaml -from pathlib import Path -from unittest.mock import Mock, patch, AsyncMock -from typing import Dict, Any - -# Import the modules we're testing -from config_validation import ( - ClaudeAPIConfig, ConfigurationValidator, SystemConfig, - ObsidianConfig, ObsidianRestAPIConfig, JournalConfig, - OutputConfig, AnalysisConfig, LoggingConfig -) -from configuration_loader import ConfigurationLoader, ConfigurationError, EnvironmentVariableError -from configuration_migrator import ConfigurationMigrator -from claude_api_client import ClaudeAPIClient -from error_handling import ClaudeConfigurationError, ClaudeAPIURLError, ClaudeModelValidationError - - -class TestClaudeAPIConfig: - """Test ClaudeAPIConfig validation""" - - def test_valid_claude_config(self): - """Test creating valid Claude API configuration""" - config = ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - api_url="https://api.anthropic.com", - model="claude-3-5-sonnet-20241022", - max_tokens=4096, - temperature=0.7 - ) - - assert config.api_key.startswith("sk-ant-") - assert config.api_url == "https://api.anthropic.com" - assert config.model == "claude-3-5-sonnet-20241022" - assert config.max_tokens == 4096 - assert config.temperature == 0.7 - - def test_default_values(self): - """Test default values are applied correctly""" - config = ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890" - ) - - assert config.api_url == "https://api.anthropic.com" - assert config.model == "claude-3-5-sonnet-20241022" - assert config.max_tokens == 4096 - assert config.temperature == 0.7 - - def test_custom_api_url(self): - """Test custom API URL validation""" - config = ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - api_url="https://custom-claude-api.example.com" - ) - - assert config.api_url == "https://custom-claude-api.example.com" - - def test_api_url_trailing_slash_removal(self): - """Test that trailing slashes are removed from API URLs""" - config = ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - api_url="https://api.anthropic.com/" - ) - - assert config.api_url == "https://api.anthropic.com" - - def test_invalid_api_url_format(self): - """Test validation of invalid API URL formats""" - with pytest.raises(ValueError, match="Invalid URL format"): - ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - api_url="not-a-url" - ) - - def test_invalid_api_url_protocol(self): - """Test validation of invalid URL protocols""" - with pytest.raises(ValueError, match="URL must use http or https protocol"): - ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - api_url="ftp://api.anthropic.com" - ) - - def test_invalid_api_key_format(self): - """Test validation of invalid API key formats""" - with pytest.raises(ValueError, match="Claude API key should start with"): - ClaudeAPIConfig(api_key="invalid-key") - - def test_api_key_too_short(self): - """Test validation of API keys that are too short""" - with pytest.raises(ValueError, match="Claude API key appears to be too short"): - ClaudeAPIConfig(api_key="sk-ant-short") - - def test_valid_model_names(self): - """Test validation of valid model names""" - valid_models = [ - "claude-3-opus-20240229", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307", - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "claude-3-opus-latest", - "claude-3-sonnet-latest", - "claude-3-haiku-latest", - "claude-3-5-sonnet-latest", - "claude-3-5-haiku-latest" - ] - - for model in valid_models: - config = ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - model=model - ) - assert config.model == model - - def test_invalid_model_name(self): - """Test validation of invalid model names""" - with pytest.raises(ValueError, match="Invalid model name"): - ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - model="invalid-model" - ) - - def test_invalid_max_tokens(self): - """Test validation of invalid max_tokens values""" - with pytest.raises(ValueError): - ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - max_tokens=0 - ) - - with pytest.raises(ValueError): - ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - max_tokens=300000 # Too high - ) - - def test_invalid_temperature(self): - """Test validation of invalid temperature values""" - with pytest.raises(ValueError): - ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - temperature=-0.1 - ) - - with pytest.raises(ValueError): - ClaudeAPIConfig( - api_key="sk-ant-test-key-12345678901234567890123456789012345678901234567890", - temperature=1.1 - ) - - -class TestConfigurationLoader: - """Test ConfigurationLoader environment variable expansion""" - - def setup_method(self): - """Set up test fixtures""" - self.loader = ConfigurationLoader() - self.temp_dir = Path(tempfile.mkdtemp()) - - def teardown_method(self): - """Clean up test fixtures""" - import shutil - shutil.rmtree(self.temp_dir) - - def test_simple_env_var_expansion(self): - """Test simple environment variable expansion""" - os.environ['TEST_API_KEY'] = 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - - config_data = { - 'claude': { - 'api_key': '${TEST_API_KEY}' - } - } - - expanded = self.loader.expand_environment_variables(config_data) - - assert expanded['claude']['api_key'] == 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - - # Clean up - del os.environ['TEST_API_KEY'] - - def test_env_var_with_default(self): - """Test environment variable expansion with default values""" - config_data = { - 'claude': { - 'api_url': '${CLAUDE_API_URL:-https://api.anthropic.com}', - 'model': '${CLAUDE_MODEL:-claude-3-5-sonnet-20241022}' - } - } - - expanded = self.loader.expand_environment_variables(config_data) - - assert expanded['claude']['api_url'] == 'https://api.anthropic.com' - assert expanded['claude']['model'] == 'claude-3-5-sonnet-20241022' - - def test_env_var_override_default(self): - """Test environment variable overriding default values""" - os.environ['CLAUDE_API_URL'] = 'https://custom-api.example.com' - - config_data = { - 'claude': { - 'api_url': '${CLAUDE_API_URL:-https://api.anthropic.com}' - } - } - - expanded = self.loader.expand_environment_variables(config_data) - - assert expanded['claude']['api_url'] == 'https://custom-api.example.com' - - # Clean up - del os.environ['CLAUDE_API_URL'] - - def test_missing_required_env_var(self): - """Test error handling for missing required environment variables""" - config_data = { - 'claude': { - 'api_key': '${MISSING_API_KEY}' - } - } - - with pytest.raises(EnvironmentVariableError, match="Environment variable 'MISSING_API_KEY' is not set"): - self.loader.expand_environment_variables(config_data) - - def test_nested_env_var_expansion(self): - """Test environment variable expansion in nested structures""" - os.environ['VAULT_PATH'] = '/test/vault' - os.environ['API_KEY'] = 'test-key' - - config_data = { - 'obsidian': { - 'vault_path': '${VAULT_PATH}', - 'rest_api': { - 'api_key': '${API_KEY}' - } - } - } - - expanded = self.loader.expand_environment_variables(config_data) - - assert expanded['obsidian']['vault_path'] == '/test/vault' - assert expanded['obsidian']['rest_api']['api_key'] == 'test-key' - - # Clean up - del os.environ['VAULT_PATH'] - del os.environ['API_KEY'] - - def test_load_yaml_config(self): - """Test loading YAML configuration file""" - config_data = { - 'claude': { - 'api_key': '${TEST_API_KEY:-default-key}', - 'api_url': 'https://api.anthropic.com', - 'model': 'claude-3-5-sonnet-20241022' - } - } - - config_file = self.temp_dir / 'test_config.yaml' - with config_file.open('w') as f: - yaml.dump(config_data, f) - - loaded_config = self.loader.load_config(config_file) - - assert loaded_config['claude']['api_key'] == 'default-key' - assert loaded_config['claude']['api_url'] == 'https://api.anthropic.com' - - def test_load_nonexistent_config(self): - """Test error handling for nonexistent configuration files""" - nonexistent_file = self.temp_dir / 'nonexistent.yaml' - - with pytest.raises(ConfigurationError, match="Configuration file not found"): - self.loader.load_config(nonexistent_file) - - def test_validate_environment_variables(self): - """Test validation of environment variables in configuration""" - config_data = { - 'claude': { - 'api_key': '${EXISTING_VAR}', - 'api_url': '${MISSING_VAR}', - 'model': '${VAR_WITH_DEFAULT:-default-model}' - } - } - - os.environ['EXISTING_VAR'] = 'test-value' - - missing_vars = self.loader.validate_environment_variables(config_data) - - assert len(missing_vars) == 1 - assert 'MISSING_VAR' in missing_vars[0] - - # Clean up - del os.environ['EXISTING_VAR'] - - def test_get_environment_variable_references(self): - """Test getting all environment variable references""" - config_data = { - 'claude': { - 'api_key': '${API_KEY}', - 'api_url': '${API_URL:-default}', - 'model': 'claude-3-5-sonnet-20241022' - }, - 'obsidian': { - 'vault_path': '${VAULT_PATH}' - } - } - - env_vars = self.loader.get_environment_variable_references(config_data) - - assert 'API_KEY' in env_vars - assert 'API_URL' in env_vars - assert 'VAULT_PATH' in env_vars - assert 'claude.api_key' in env_vars['API_KEY'] - assert 'claude.api_url' in env_vars['API_URL'] - - -class TestConfigurationMigrator: - """Test ConfigurationMigrator backward compatibility""" - - def setup_method(self): - """Set up test fixtures""" - self.migrator = ConfigurationMigrator() - - def test_migrate_claude_config_missing_api_url(self): - """Test migration of Claude config missing api_url""" - config_dict = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': 'claude-3-5-sonnet-20241022' - } - } - - migrated = self.migrator.migrate_claude_config(config_dict) - - assert migrated['claude']['api_url'] == 'https://api.anthropic.com' - assert migrated['claude']['api_key'] == 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - assert migrated['claude']['model'] == 'claude-3-5-sonnet-20241022' - - def test_migrate_legacy_model_names(self): - """Test migration of legacy model names""" - legacy_models = { - 'claude-3-sonnet': 'claude-3-sonnet-20240229', - 'claude-3-opus': 'claude-3-opus-20240229', - 'claude-3-haiku': 'claude-3-haiku-20240307', - 'sonnet': 'claude-3-5-sonnet-20241022' - } - - for old_model, expected_new_model in legacy_models.items(): - config_dict = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': old_model - } - } - - migrated = self.migrator.migrate_claude_config(config_dict) - - assert migrated['claude']['model'] == expected_new_model - - def test_migrate_missing_claude_section(self): - """Test migration when Claude section is completely missing""" - config_dict = { - 'obsidian': { - 'vault_path': '/test/vault' - } - } - - migrated = self.migrator.migrate_claude_config(config_dict) - - assert 'claude' in migrated - assert migrated['claude']['api_url'] == 'https://api.anthropic.com' - assert migrated['claude']['model'] == 'claude-3-5-sonnet-20241022' - - def test_migrate_complete_configuration(self): - """Test migration of complete configuration""" - config_dict = { - 'obsidian': { - 'vault_path': '/test/vault', - 'rest_api': { - 'url': 'https://localhost:27123', - 'api_key': 'test-key' - } - }, - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': 'claude-3-sonnet' # Legacy model name - } - } - - migrated = self.migrator.migrate_configuration(config_dict) - - # Check Claude migration - assert migrated['claude']['api_url'] == 'https://api.anthropic.com' - assert migrated['claude']['model'] == 'claude-3-sonnet-20240229' - - # Check that other sections are added with defaults - assert 'journal' in migrated - assert 'output' in migrated - assert 'analysis' in migrated - assert 'logging' in migrated - - def test_check_migration_needed(self): - """Test checking if migration is needed""" - # Config that needs migration - config_needing_migration = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': 'claude-3-sonnet' # Legacy model name - } - } - - assert self.migrator.check_migration_needed(config_needing_migration) is True - - # Config that doesn't need migration - need all required fields - config_up_to_date = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'api_url': 'https://api.anthropic.com', - 'model': 'claude-3-5-sonnet-20241022', - 'max_tokens': 4096, - 'temperature': 0.7 - }, - 'journal': { - 'daily_notes_folder': 'Daily', - 'date_format': 'YYYY-MM-DD', - 'file_extension': '.md' - }, - 'output': { - 'experiences_folder': 'Knowledge/Experiences', - 'lessons_folder': 'Knowledge/Lessons', - 'tasks_folder': 'Tasks/Daily', - 'problems_folder': 'Knowledge/Problems', - 'achievements_folder': 'Knowledge/Achievements', - 'improvements_folder': 'Knowledge/Improvements' - }, - 'analysis': { - 'categories': [], - 'extraction_rules': {} - }, - 'logging': { - 'level': 'INFO', - 'file': 'logs/journal_organizer.log' - } - } - - assert self.migrator.check_migration_needed(config_up_to_date) is False - - def test_get_migration_preview(self): - """Test getting migration preview""" - config_dict = { - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': 'claude-3-sonnet' - } - } - - preview = self.migrator.get_migration_preview(config_dict) - - assert len(preview) > 0 - assert any('api_url' in action for action in preview) - assert any('claude-3-sonnet' in action and 'claude-3-sonnet-20240229' in action for action in preview) - - def test_get_supported_model_names(self): - """Test getting supported model names""" - supported_models = self.migrator.get_supported_model_names() - - # Should include current models - assert 'claude-3-5-sonnet-20241022' in supported_models - assert 'claude-3-opus-20240229' in supported_models - - # Should include legacy models - assert 'claude-3-sonnet' in supported_models - assert 'sonnet' in supported_models - - -class TestConfigurationValidator: - """Test ConfigurationValidator comprehensive validation""" - - def setup_method(self): - """Set up test fixtures""" - self.validator = ConfigurationValidator() - self.temp_dir = Path(tempfile.mkdtemp()) - - # Create a test vault directory - self.test_vault = self.temp_dir / 'test_vault' - self.test_vault.mkdir() - (self.test_vault / '.obsidian').mkdir() - - def teardown_method(self): - """Clean up test fixtures""" - import shutil - shutil.rmtree(self.temp_dir) - - def create_test_config_file(self, config_data: Dict[str, Any]) -> Path: - """Create a test configuration file""" - config_file = self.temp_dir / 'test_config.yaml' - with config_file.open('w') as f: - yaml.dump(config_data, f) - return config_file - - def test_load_and_validate_valid_config(self): - """Test loading and validating a valid configuration""" - config_data = { - 'obsidian': { - 'vault_path': str(self.test_vault), - 'rest_api': { - 'url': 'https://localhost:27123', - 'api_key': 'test-api-key', - 'verify_ssl': False - } - }, - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'api_url': 'https://api.anthropic.com', - 'model': 'claude-3-5-sonnet-20241022' - } - } - - config_file = self.create_test_config_file(config_data) - - system_config = self.validator.load_and_validate_config(config_file) - - assert isinstance(system_config, SystemConfig) - assert system_config.claude.api_url == 'https://api.anthropic.com' - assert system_config.claude.model == 'claude-3-5-sonnet-20241022' - # Path resolution may add /private prefix on macOS, so check if paths resolve to same location - assert Path(system_config.obsidian.vault_path).resolve() == self.test_vault.resolve() - - def test_load_and_validate_with_migration(self): - """Test loading configuration that needs migration""" - config_data = { - 'obsidian': { - 'vault_path': str(self.test_vault), - 'rest_api': { - 'url': 'https://localhost:27123', - 'api_key': 'test-api-key' - } - }, - 'claude': { - 'api_key': 'sk-ant-test-key-12345678901234567890123456789012345678901234567890', - 'model': 'claude-3-sonnet' # Legacy model name - } - } - - config_file = self.create_test_config_file(config_data) - - system_config = self.validator.load_and_validate_config(config_file) - - # Should have migrated the model name - assert system_config.claude.model == 'claude-3-sonnet-20240229' - # Should have added default api_url - assert system_config.claude.api_url == 'https://api.anthropic.com' - - def test_load_and_validate_with_env_vars(self): - """Test loading configuration with environment variables""" - os.environ['TEST_CLAUDE_KEY'] = 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - os.environ['TEST_VAULT_PATH'] = str(self.test_vault) - - config_data = { - 'obsidian': { - 'vault_path': '${TEST_VAULT_PATH}', - 'rest_api': { - 'url': 'https://localhost:27123', - 'api_key': 'test-api-key' - } - }, - 'claude': { - 'api_key': '${TEST_CLAUDE_KEY}', - 'api_url': '${CLAUDE_API_URL:-https://api.anthropic.com}' - } - } - - config_file = self.create_test_config_file(config_data) - - system_config = self.validator.load_and_validate_config(config_file) - - assert system_config.claude.api_key == 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - # Path resolution may add /private prefix on macOS, so check if paths resolve to same location - assert Path(system_config.obsidian.vault_path).resolve() == self.test_vault.resolve() - assert system_config.claude.api_url == 'https://api.anthropic.com' - - # Clean up - del os.environ['TEST_CLAUDE_KEY'] - del os.environ['TEST_VAULT_PATH'] - - def test_validation_error_handling(self): - """Test validation error handling""" - config_data = { - 'obsidian': { - 'vault_path': '/nonexistent/path', - 'rest_api': { - 'url': 'invalid-url', - 'api_key': 'test-key' - } - }, - 'claude': { - 'api_key': 'invalid-key', - 'model': 'invalid-model' - } - } - - config_file = self.create_test_config_file(config_data) - - with pytest.raises(ValueError, match="Configuration validation failed"): - self.validator.load_and_validate_config(config_file) - - def test_validate_api_keys(self): - """Test API key validation""" - # Create a valid system config for testing - system_config = SystemConfig( - obsidian=ObsidianConfig( - vault_path=str(self.test_vault), - rest_api=ObsidianRestAPIConfig( - url='https://localhost:27123', - api_key='test-api-key' - ) - ), - claude=ClaudeAPIConfig( - api_key='sk-ant-test-key-12345678901234567890123456789012345678901234567890' - ) - ) - - issues = self.validator.validate_api_keys(system_config) - - # Should have no issues with valid keys - assert len(issues) == 0 - - def test_validate_api_keys_with_env_vars(self): - """Test API key validation with environment variable placeholders""" - # Skip this test as it requires complex mocking of pydantic validation - pytest.skip("Environment variable validation requires complex setup") - - -@pytest.mark.asyncio -class TestClaudeAPIClient: - """Test ClaudeAPIClient functionality""" - - def setup_method(self): - """Set up test fixtures""" - self.config = ClaudeAPIConfig( - api_key='sk-ant-test-key-12345678901234567890123456789012345678901234567890', - api_url='https://api.anthropic.com', - model='claude-3-5-sonnet-20241022' - ) - - @patch('claude_api_client.AsyncAnthropic') - @patch('claude_api_client.aiohttp') - def test_client_initialization(self, mock_aiohttp, mock_anthropic): - """Test Claude API client initialization""" - client = ClaudeAPIClient(self.config) - - assert client.base_url == 'https://api.anthropic.com' - assert client.api_key == 'sk-ant-test-key-12345678901234567890123456789012345678901234567890' - assert client.model == 'claude-3-5-sonnet-20241022' - - # Should have called AsyncAnthropic constructor - mock_anthropic.assert_called_once() - - @patch('claude_api_client.AsyncAnthropic') - @patch('claude_api_client.aiohttp') - def test_client_with_custom_url(self, mock_aiohttp, mock_anthropic): - """Test client initialization with custom API URL""" - custom_config = ClaudeAPIConfig( - api_key='sk-ant-test-key-12345678901234567890123456789012345678901234567890', - api_url='https://custom-api.example.com', - model='claude-3-5-sonnet-20241022' - ) - - client = ClaudeAPIClient(custom_config) - - assert client.base_url == 'https://custom-api.example.com' - - # Should have called AsyncAnthropic with custom base_url - mock_anthropic.assert_called_once() - call_args = mock_anthropic.call_args - assert call_args[1]['base_url'] == 'https://custom-api.example.com' - - @patch('claude_api_client.AsyncAnthropic') - @patch('claude_api_client.aiohttp') - def test_get_client_info(self, mock_aiohttp, mock_anthropic): - """Test getting client configuration information""" - client = ClaudeAPIClient(self.config) - - info = client.get_client_info() - - assert info['api_url'] == 'https://api.anthropic.com' - assert info['model'] == 'claude-3-5-sonnet-20241022' - assert info['max_tokens'] == 4096 - assert info['temperature'] == 0.7 - assert info['is_custom_endpoint'] is False - - @patch('claude_api_client.AsyncAnthropic') - @patch('claude_api_client.aiohttp') - def test_get_client_info_custom_endpoint(self, mock_aiohttp, mock_anthropic): - """Test getting client info for custom endpoint""" - custom_config = ClaudeAPIConfig( - api_key='sk-ant-test-key-12345678901234567890123456789012345678901234567890', - api_url='https://custom-api.example.com' - ) - - client = ClaudeAPIClient(custom_config) - info = client.get_client_info() - - assert info['is_custom_endpoint'] is True - assert info['api_url'] == 'https://custom-api.example.com' \ No newline at end of file diff --git a/./tests/unit/test_error_handling.py b/./tests/unit/test_error_handling.py deleted file mode 100644 index 694ad99..0000000 --- a/./tests/unit/test_error_handling.py +++ /dev/null @@ -1,526 +0,0 @@ -""" -Unit tests for error_handling module. -Tests custom exceptions, ErrorHandler, and error sanitization. -""" -import pytest -import logging -from unittest.mock import Mock, patch -from datetime import datetime - -from error_handling import ( - JournalOrganizerError, ConfigurationError, APIError, ValidationError, - FileSystemError, SecurityError, ErrorContext, ErrorHandler, - get_error_handler, set_error_handler, audit_error_message_security -) - - -class TestJournalOrganizerError: - """Test base JournalOrganizerError class""" - - def test_basic_error_creation(self): - """Test creating basic error""" - error = JournalOrganizerError("Test error message") - - assert str(error) == "Test error message" - assert error.message == "Test error message" - assert error.context == {} - assert error.cause is None - assert error.timestamp is not None - - def test_error_with_context(self): - """Test creating error with context""" - context = {"key": "value", "number": 42} - error = JournalOrganizerError("Test error", context=context) - - assert error.context == context - - def test_error_with_cause(self): - """Test creating error with cause""" - original_error = ValueError("Original error") - error = JournalOrganizerError("Wrapped error", cause=original_error) - - assert error.cause == original_error - - def test_to_dict(self): - """Test converting error to dictionary""" - context = {"test_key": "test_value"} - original_error = RuntimeError("Original") - error = JournalOrganizerError("Test error", context=context, cause=original_error) - - error_dict = error.to_dict() - - assert error_dict["error_type"] == "JournalOrganizerError" - assert error_dict["message"] == "Test error" - assert error_dict["context"] == context - assert error_dict["cause"] == "Original" - assert "timestamp" in error_dict - - -class TestConfigurationError: - """Test ConfigurationError class""" - - def test_basic_configuration_error(self): - """Test basic configuration error""" - error = ConfigurationError("Config error", config_key="api_key") - - assert error.message == "Config error" - assert error.context["config_key"] == "api_key" - - def test_configuration_error_with_sensitive_value(self): - """Test configuration error with sensitive value redaction""" - error = ConfigurationError( - "Invalid API key", - config_key="api_key", - config_value="sk-secret-key-123" - ) - - assert error.context["config_key"] == "api_key" - assert error.context["config_value"] == "[REDACTED]" - - def test_configuration_error_with_non_sensitive_value(self): - """Test configuration error with non-sensitive value""" - error = ConfigurationError( - "Invalid timeout", - config_key="timeout", - config_value="30" - ) - - assert error.context["config_key"] == "timeout" - assert error.context["config_value"] == "30" - - -class TestAPIError: - """Test APIError class""" - - def test_basic_api_error(self): - """Test basic API error""" - error = APIError("API call failed", api_name="claude", status_code=500) - - assert error.message == "API call failed" - assert error.context["api_name"] == "claude" - assert error.context["status_code"] == 500 - - def test_api_error_with_long_response(self): - """Test API error with long response data truncation""" - long_response = "x" * 1000 - error = APIError("API error", response_data=long_response) - - assert len(error.context["response_data"]) <= 503 # 500 + "..." - assert error.context["response_data"].endswith("...") - - def test_api_error_with_short_response(self): - """Test API error with short response data""" - short_response = "Short error" - error = APIError("API error", response_data=short_response) - - assert error.context["response_data"] == short_response - - -class TestValidationError: - """Test ValidationError class""" - - def test_basic_validation_error(self): - """Test basic validation error""" - error = ValidationError( - "Invalid email", - field_name="email", - field_value="invalid-email", - validation_rule="email_format" - ) - - assert error.message == "Invalid email" - assert error.context["field_name"] == "email" - assert error.context["field_value"] == "invalid-email" - assert error.context["validation_rule"] == "email_format" - - def test_validation_error_with_sensitive_field(self): - """Test validation error with sensitive field value redaction""" - error = ValidationError( - "Invalid password", - field_name="password", - field_value="secret123" - ) - - assert error.context["field_name"] == "password" - assert error.context["field_value"] == "[REDACTED]" - - def test_validation_error_with_long_value(self): - """Test validation error with long field value truncation""" - long_value = "x" * 200 - error = ValidationError( - "Invalid input", - field_name="description", - field_value=long_value - ) - - assert len(error.context["field_value"]) == 100 # Truncated to 100 chars - - -class TestFileSystemError: - """Test FileSystemError class""" - - def test_basic_filesystem_error(self): - """Test basic filesystem error""" - error = FileSystemError( - "File not found", - file_path="/path/to/file.txt", - operation="read" - ) - - assert error.message == "File not found" - assert error.context["file_path"] == "/path/to/file.txt" - assert error.context["operation"] == "read" - - -class TestSecurityError: - """Test SecurityError class""" - - def test_basic_security_error(self): - """Test basic security error""" - error = SecurityError( - "Path traversal detected", - security_issue="path_traversal", - attempted_path="../../../etc/passwd", - risk_level="critical" - ) - - assert error.message == "Path traversal detected" - assert error.context["security_issue"] == "path_traversal" - assert error.context["attempted_path"] == "../../../etc/passwd" - assert error.context["risk_level"] == "critical" - - def test_security_error_with_long_path(self): - """Test security error with long path truncation""" - long_path = "/" + "x" * 600 - error = SecurityError("Security violation", attempted_path=long_path) - - assert len(error.context["attempted_path"]) == 500 # Truncated - - -class TestErrorContext: - """Test ErrorContext class""" - - def test_valid_error_context(self): - """Test creating valid error context""" - context = ErrorContext( - component="test_component", - operation="test_operation", - user_message="User friendly message", - technical_details={"key": "value"}, - severity="warning" - ) - - assert context.component == "test_component" - assert context.operation == "test_operation" - assert context.user_message == "User friendly message" - assert context.technical_details == {"key": "value"} - assert context.severity == "warning" - - def test_error_context_defaults(self): - """Test error context with default values""" - context = ErrorContext( - component="test_component", - operation="test_operation" - ) - - assert context.user_message == "" - assert context.technical_details == {} - assert context.severity == "error" - - def test_error_context_validation_empty_component(self): - """Test error context validation with empty component""" - with pytest.raises(ValueError, match="component cannot be empty"): - ErrorContext(component="", operation="test_operation") - - def test_error_context_validation_empty_operation(self): - """Test error context validation with empty operation""" - with pytest.raises(ValueError, match="operation cannot be empty"): - ErrorContext(component="test_component", operation="") - - def test_error_context_validation_invalid_severity(self): - """Test error context validation with invalid severity""" - with pytest.raises(ValueError, match="severity must be one of"): - ErrorContext( - component="test_component", - operation="test_operation", - severity="invalid" - ) - - def test_error_context_validation_invalid_technical_details(self): - """Test error context validation with invalid technical_details""" - with pytest.raises(ValueError, match="technical_details must be a dictionary"): - ErrorContext( - component="test_component", - operation="test_operation", - technical_details="not a dict" - ) - - def test_to_dict(self): - """Test converting error context to dictionary""" - context = ErrorContext( - component="test_component", - operation="test_operation", - user_message="Test message", - technical_details={"key": "value"}, - severity="info" - ) - - context_dict = context.to_dict() - - assert context_dict["component"] == "test_component" - assert context_dict["operation"] == "test_operation" - assert context_dict["user_message"] == "Test message" - assert context_dict["technical_details"] == {"key": "value"} - assert context_dict["severity"] == "info" - - -class TestErrorHandler: - """Test ErrorHandler class""" - - def setup_method(self): - """Set up test fixtures""" - self.mock_logger = Mock(spec=logging.Logger) - self.error_handler = ErrorHandler(self.mock_logger) - - def test_error_handler_creation(self): - """Test creating error handler""" - handler = ErrorHandler() - assert handler.logger is not None - - handler_with_logger = ErrorHandler(self.mock_logger) - assert handler_with_logger.logger == self.mock_logger - - def test_handle_error_with_custom_error(self): - """Test handling custom JournalOrganizerError""" - error = ConfigurationError("Config error", config_key="api_key") - context = ErrorContext( - component="config", - operation="load", - user_message="Please check your configuration" - ) - - result = self.error_handler.handle_error(error, context) - - assert result["success"] is False - assert result["error"] == "Config error" - assert result["message"] == "Please check your configuration" - assert result["component"] == "config" - assert result["operation"] == "load" - assert result["error_type"] == "ConfigurationError" - assert "timestamp" in result - - # Check that logger was called - self.mock_logger.log.assert_called_once() - - def test_handle_error_with_generic_error(self): - """Test handling generic Python exception""" - error = ValueError("Generic error") - context = ErrorContext( - component="test", - operation="test_op", - severity="warning" - ) - - result = self.error_handler.handle_error(error, context) - - assert result["success"] is False - assert result["error"] == "Generic error" - assert result["error_type"] == "ValueError" - - # Check that logger was called with warning level - self.mock_logger.log.assert_called_once() - call_args = self.mock_logger.log.call_args - assert call_args[0][0] == logging.WARNING # Log level - - def test_handle_api_error(self): - """Test handling API errors""" - error = RuntimeError("Connection failed") - - result = self.error_handler.handle_api_error(error, "claude", "analyze_text") - - assert result["success"] is False - assert "claude API error" in result["error"] - assert result["component"] == "claude_api" - assert result["operation"] == "analyze_text" - assert "Failed to communicate with claude" in result["message"] - - def test_handle_api_error_with_api_error_instance(self): - """Test handling APIError instance""" - api_error = APIError("API failed", api_name="obsidian", status_code=404) - - result = self.error_handler.handle_api_error(api_error, "obsidian", "read_note") - - assert result["success"] is False - assert result["error"] == "API failed" - assert result["component"] == "obsidian_api" - - def test_handle_validation_error(self): - """Test handling validation errors""" - error = ValueError("Invalid format") - - result = self.error_handler.handle_validation_error(error, "email", "validate_input") - - assert result["success"] is False - assert "Invalid email" in result["error"] - assert result["component"] == "validation" - assert result["operation"] == "validate_input" - assert "Please check your email" in result["message"] - - def test_handle_configuration_error(self): - """Test handling configuration errors""" - error = ValueError("Missing key") - - result = self.error_handler.handle_configuration_error(error, "api_key") - - assert result["success"] is False - assert "Configuration error for api_key" in result["error"] - assert result["component"] == "configuration" - assert result["operation"] == "load_config" - assert "Please check your API key configuration" in result["message"] - - def test_sanitize_error_message_api_key(self): - """Test sanitizing error messages with API keys""" - message = "Error: api_key=sk-secret-key-123 is invalid" - - sanitized = self.error_handler._sanitize_error_message(message) - - assert "sk-secret-key-123" not in sanitized - assert "api_key=[REDACTED]" in sanitized - - def test_sanitize_error_message_bearer_token(self): - """Test sanitizing error messages with Bearer tokens""" - message = "Authorization failed: Bearer abc123xyz789" - - sanitized = self.error_handler._sanitize_error_message(message) - - assert "abc123xyz789" not in sanitized - assert "[REDACTED]" in sanitized - - def test_sanitize_error_message_email(self): - """Test sanitizing error messages with email addresses""" - message = "Failed to send email to user@example.com" - - sanitized = self.error_handler._sanitize_error_message(message) - - assert "user@example.com" not in sanitized - assert "[EMAIL_REDACTED]" in sanitized - - def test_sanitize_error_message_file_paths(self): - """Test sanitizing error messages with user file paths""" - message = "Cannot access /Users/john/Documents/secret.txt" - - sanitized = self.error_handler._sanitize_error_message(message) - - assert "john" not in sanitized - assert "[USER_REDACTED]" in sanitized - - def test_sanitize_context_data(self): - """Test sanitizing context data""" - data = { - "api_key": "secret-key-123", - "username": "john_doe", - "password": "secret123", - "timeout": 30, - "nested": { - "token": "bearer-token-xyz", - "safe_value": "public_info" - } - } - - sanitized = self.error_handler._sanitize_context_data(data) - - assert sanitized["api_key"] == "[REDACTED]" - assert sanitized["username"] == "john_doe" # Not sensitive - assert sanitized["password"] == "[REDACTED]" - assert sanitized["timeout"] == 30 - assert sanitized["nested"]["token"] == "[REDACTED]" - assert sanitized["nested"]["safe_value"] == "public_info" - - def test_sanitize_context_data_non_dict(self): - """Test sanitizing non-dictionary context data""" - result = self.error_handler._sanitize_context_data("not a dict") - assert result == "not a dict" - - -class TestGlobalErrorHandler: - """Test global error handler functions""" - - def test_get_error_handler_singleton(self): - """Test that get_error_handler returns singleton""" - handler1 = get_error_handler() - handler2 = get_error_handler() - - assert handler1 is handler2 - - def test_set_error_handler(self): - """Test setting custom error handler""" - custom_handler = ErrorHandler() - set_error_handler(custom_handler) - - retrieved_handler = get_error_handler() - assert retrieved_handler is custom_handler - - -class TestAuditErrorMessageSecurity: - """Test error message security auditing""" - - def test_audit_clean_message(self): - """Test auditing clean message with no issues""" - message = "Simple error message with no sensitive data" - - result = audit_error_message_security(message) - - assert result["has_issues"] is False - assert result["issues"] == [] - assert result["risk_level"] == "low" - - def test_audit_message_with_email(self): - """Test auditing message with email address""" - message = "Failed to send notification to user@example.com" - - result = audit_error_message_security(message) - - assert result["has_issues"] is True - assert len(result["issues"]) == 1 - assert result["issues"][0]["type"] == "email_address" - assert result["risk_level"] == "medium" - - def test_audit_message_with_api_key(self): - """Test auditing message with API key""" - message = "Authentication failed: api_key=sk-secret-123" - - result = audit_error_message_security(message) - - assert result["has_issues"] is True - assert any(issue["type"] == "credential_pattern" for issue in result["issues"]) - assert result["risk_level"] == "high" - - def test_audit_message_with_bearer_token(self): - """Test auditing message with Bearer token""" - message = "Authorization header: Bearer abc123xyz789" - - result = audit_error_message_security(message) - - assert result["has_issues"] is True - assert any(issue["type"] == "bearer_token" for issue in result["issues"]) - assert result["risk_level"] == "high" - - def test_audit_message_with_ip_address(self): - """Test auditing message with IP address""" - message = "Connection failed to 192.168.1.100" - - result = audit_error_message_security(message) - - assert result["has_issues"] is True - assert any(issue["type"] == "ip_address" for issue in result["issues"]) - assert result["risk_level"] == "medium" - - def test_audit_message_with_multiple_issues(self): - """Test auditing message with multiple security issues""" - message = "Failed to connect to 192.168.1.100 with api_key=secret123 for user@example.com" - - result = audit_error_message_security(message) - - assert result["has_issues"] is True - assert len(result["issues"]) >= 2 # Should find multiple issues - assert result["risk_level"] == "high" # High due to credential pattern \ No newline at end of file