From 06042ffeecdeba72fc4987d49d73f22635ebd339 Mon Sep 17 00:00:00 2001 From: rubyTanuki <161648546+rubyTanuki@users.noreply.github.com> Date: Fri, 29 May 2026 18:29:28 -0500 Subject: [PATCH 1/5] fix: mcp state management renamed mcp to server to resolve name overlap and fixed the state management issue + an old misnaming in exceptions.py --- src/tostr/cli.py | 14 +- src/tostr/commands.py | 4 +- src/tostr/core/parser.py | 27 ++-- src/tostr/exceptions.py | 16 +-- src/tostr/mcp.py | 148 --------------------- src/tostr/server.py | 183 ++++++++++++++++++++++++++ tests/testcode/MRILib/.tostr/cache.db | Bin 643072 -> 638976 bytes 7 files changed, 208 insertions(+), 184 deletions(-) delete mode 100644 src/tostr/mcp.py create mode 100644 src/tostr/server.py diff --git a/src/tostr/cli.py b/src/tostr/cli.py index bb94e32..5e9674a 100644 --- a/src/tostr/cli.py +++ b/src/tostr/cli.py @@ -5,11 +5,11 @@ from typing import Annotated from loguru import logger -from tostr.exceptions import ToasterError +from tostr.exceptions import TostrError from tostr.commands import init_async, inspect_async, skeleton_async, watch_async, clean_db -from tostr.mcp import mcp +from tostr.server import mcp from tostr.core.utils.logger import configure_cli_logging @@ -67,7 +67,7 @@ def watch( configure_cli_logging(debug) try: asyncio.run(watch_async(path)) - except ToasterError as e: + except TostrError as e: typer.secho(f"❌ Error: {e}", fg="red", err=True) raise typer.Exit(code=1) @@ -94,7 +94,7 @@ def clean( configure_cli_logging(debug) try: clean_db(path) - except ToasterError as e: + except TostrError as e: typer.secho(f"❌ Error: {e}", fg="red", err=True) raise typer.Exit(code=1) @@ -137,7 +137,7 @@ def init( start_time = time.perf_counter() try: asyncio.run(init_async(path, use_cache, ignore)) - except ToasterError as e: + except TostrError as e: typer.secho(f"❌ Error: {e}", fg="red", err=True) raise typer.Exit(code=1) @@ -190,7 +190,7 @@ def inspect( try: result = asyncio.run(inspect_async(id, path, include_body=include_body, pretty=pretty)) print(result) - except ToasterError as e: + except TostrError as e: typer.secho(f"❌ Error: {e}", fg="red", err=True) raise typer.Exit(code=1) @@ -237,7 +237,7 @@ def skeleton( try: result = asyncio.run(skeleton_async(subpath, path, pretty=pretty)) print(result) - except ToasterError as e: + except TostrError as e: typer.secho(f"❌ Error: {e}", fg="red", err=True) raise typer.Exit(code=1) end_time = time.perf_counter() diff --git a/src/tostr/commands.py b/src/tostr/commands.py index f55d499..2339e08 100644 --- a/src/tostr/commands.py +++ b/src/tostr/commands.py @@ -113,12 +113,12 @@ async def skeleton_async(subpath: str, project_path: Path, pretty: bool = True): active_tasks = {} -async def watch_async(target_path: Path): +async def watch_async(target_path: Path, stop_event: asyncio.Event = None): llm = get_llm_client() logger.info("Starting Listener") try: - async for changes in awatch(target_path): + async for changes in awatch(target_path, stop_event=stop_event): for change_type, path in changes: path = Path(path).relative_to(target_path) if ".tostr" in str(path): diff --git a/src/tostr/core/parser.py b/src/tostr/core/parser.py index 1eb2912..e3c87c4 100644 --- a/src/tostr/core/parser.py +++ b/src/tostr/core/parser.py @@ -102,8 +102,9 @@ def parse_file(self, subpath: Path, parent: BaseStruct=None) -> BaseFile: return file_obj def resolve_dependencies(self): - logger.info(f"Starting dependency resolution from root: {self.registry.root}") - self.registry.root.resolve_dependencies() + if self.registry.root: + logger.info(f"Starting dependency resolution from root: {self.registry.root}") + self.registry.root.resolve_dependencies() def load_cache(self): # print("Attempting to load cache from SQLite database...") @@ -112,20 +113,8 @@ def load_cache(self): # print(f"✅ Loaded Cache in {time.time() - t_cache:.2f} seconds") async def resolve_descriptions_async(self): - self.visited_ucids = set() - coroutine_list = [file.resolve_description_async(self.llm, self.visited_ucids) for file in self.registry.files] - if coroutine_list == []: return - result = await asyncio.gather(*coroutine_list) - - - # def write_skeleton(self): - # tost_string = tost.dump_parser(self, verbosity=Verbosity.SIMPLE) - # tostr_dir = self.path / ".tostr" - # tostr_dir.mkdir(exist_ok=True) - # with open(tostr_dir / "skeleton.tost", "w") as file: - # file.write(tost_string) - - # def write_cache(self, stale: bool = False): - # logger.debug("Writing AST to SQLite database...") - # self.registry.save_to_cache(stale=stale) - \ No newline at end of file + visited_ucids = set() + coroutine_list = [file.resolve_description_async(self.llm, visited_ucids) for file in self.registry.files] + if not coroutine_list: + return + await asyncio.gather(*coroutine_list) diff --git a/src/tostr/exceptions.py b/src/tostr/exceptions.py index 0f4dc00..5cbe391 100644 --- a/src/tostr/exceptions.py +++ b/src/tostr/exceptions.py @@ -1,22 +1,22 @@ -class ToasterError(Exception): - """Base exception for all Toaster domain errors.""" +class TostrError(Exception): + """Base exception for all Tostr domain errors.""" pass -class StructNotFoundError(ToasterError): +class StructNotFoundError(TostrError): pass -class APIKeyError(ToasterError): +class APIKeyError(TostrError): pass -class ResolveError(ToasterError): +class ResolveError(TostrError): pass -class LanguageNotSupportedError(ToasterError): +class LanguageNotSupportedError(TostrError): pass -class TargetFileNotFoundError(ToasterError): +class TargetFileNotFoundError(TostrError): pass -class DatabaseNotFoundError(ToasterError): +class DatabaseNotFoundError(TostrError): pass \ No newline at end of file diff --git a/src/tostr/mcp.py b/src/tostr/mcp.py deleted file mode 100644 index ca55bed..0000000 --- a/src/tostr/mcp.py +++ /dev/null @@ -1,148 +0,0 @@ -import threading -import asyncio -from pathlib import Path -from fastmcp import FastMCP -from loguru import logger -import os - -from tostr.exceptions import ToasterError - -from tostr.commands import ( - init_async, - inspect_async, - skeleton_async, - watch_async, - clean_db -) -from tostr.core.utils.logger import configure_mcp_logging - -_is_initialized = False -_current_project_dir = None - -mcp = FastMCP("Toaster") - -# --- THE SYNCHRONOUS BRIDGE --- -def _run_watcher_thread(target_path: Path): - """ - Sets up an isolated async environment for the background thread, - then runs your watch_async loop inside it. - """ - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - try: - # This calls your exact watch_async function! - loop.run_until_complete(watch_async(target_path)) - except Exception as e: - logger.exception(f"Fatal error in background watcher: {e}") - finally: - loop.close() - logger.info("Background watcher shut down cleanly.") - -@mcp.tool() -async def init(workspace_path: str, use_cache: bool = True, ignore: str = None) -> str: - """ - -- MUST BE RUN BEFORE ANY OTHER TOOL -- - Initializes the Toaster MCP server for a specific project workspace. - - Args: - workspace_path: The ABSOLUTE path to the project workspace. DO NOT use '.' or relative paths. If you only have a relative path, you must determine the absolute path of the current workspace first. - use_cache: Whether to use the existing AST cache. - ignore: Add a default ignore template to the project folder (e.g., 'java', 'default'). - """ - - target_path = Path(workspace_path) - - if not target_path.is_absolute(): - return (f"Error: workspace_path must be an absolute path. You provided '{workspace_path}'. " - f"Please determine the absolute path of the current workspace and try again.") - - target_path = target_path.resolve() - - try: - os.chdir(target_path) - except FileNotFoundError: - return f"Fatal Error: Workspace path does not exist: {target_path}" - - global _is_initialized, _current_project_dir - project_dir = target_path - - if _is_initialized and _current_project_dir == project_dir: - return f"Status: Already initialized for {project_dir}." - - try: - configure_mcp_logging(project_dir) - - await init_async(project_dir, use_cache, ignore) - - watcher_thread = threading.Thread( - target=_run_watcher_thread, - args=(project_dir,), - daemon=True - ) - watcher_thread.start() - - _is_initialized = True - _current_project_dir = project_dir - - return f"Success: Toaster initialized. Cache is built at {project_dir}/.tostr/cache.db. Background watcher is now actively listening on {project_dir}" - - except Exception as e: - return f"Fatal Error Initializing Toaster: {str(e)}" - -@mcp.tool() -async def inspect(id: str, include_body: bool = False) -> str: - """ - Output the AST details and code for a specific struct ID. - Use this when you need the full implementation details of a specific function or class. - - Args: - id: The unique Toaster ID of the struct to inspect. - include_body: Include the raw code body in the output. - """ - global _is_initialized, _current_project_dir - - if not _is_initialized: - return "Error: Toaster is not initialized. You must call 'init' with the absolute workspace path before querying the database." - - try: - result = await inspect_async(id, _current_project_dir, include_body, pretty=False) - return str(result) - except ToasterError as e: - return f"Error: {e}" - - -@mcp.tool() -async def clean(workspace_path: str) -> str: - """ - Clean the SQLite database for a specific workspace. - """ - try: - project_dir = Path(workspace_path).resolve() - clean_db(project_dir) - return f"Success: Database cleaned for {project_dir}." - except Exception as e: - return f"Error: {e}" - -@mcp.tool() -async def skeleton(subpath: str) -> str: - """ - Output the .tost skeleton format for all files matching a specific subpath. - Use this to understand the high-level architecture, classes, and function signatures of a file or directory without reading the full code. - - Args: - subpath: File or directory path relative to the project root to generate a skeleton for. - """ - global _is_initialized, _current_project_dir - - if not _is_initialized: - return "Error: Toaster is not initialized. You must call 'init' with the absolute workspace path before querying the database." - - try: - result = await skeleton_async(subpath, _current_project_dir, pretty=False) - return str(result) - except ToasterError as e: - return f"Error: {e}" - -if __name__ == "__main__": - mcp.run() \ No newline at end of file diff --git a/src/tostr/server.py b/src/tostr/server.py new file mode 100644 index 0000000..5c082d0 --- /dev/null +++ b/src/tostr/server.py @@ -0,0 +1,183 @@ +import threading +import asyncio +from pathlib import Path +from fastmcp import FastMCP +from loguru import logger +import os + +from tostr.exceptions import TostrError + +from tostr.commands import ( + init_async, + inspect_async, + skeleton_async, + watch_async, + clean_db +) +from tostr.core.utils.logger import configure_mcp_logging + +class MCPSession: + def __init__(self): + self.is_initialized = False + self.project_dir = None + self.watcher_thread = None + self.watcher_loop = None + self.stop_event = None + + def stop_watcher(self): + """Cleanly stops the background watcher thread if it exists.""" + if self.watcher_loop and self.stop_event: + logger.info("Signaling background watcher to stop...") + self.watcher_loop.call_soon_threadsafe(self.stop_event.set) + + if self.watcher_thread: + # We wait briefly for it to shut down + self.watcher_thread.join(timeout=2) + if self.watcher_thread.is_alive(): + logger.warning("Watcher thread did not shut down in time.") + + self.watcher_thread = None + self.watcher_loop = None + self.stop_event = None + + def start_watcher(self, target_path: Path): + """Starts the background watcher thread.""" + self.stop_watcher() + + self.watcher_thread = threading.Thread( + target=self._run_watcher_thread, + args=(target_path,), + daemon=True + ) + self.watcher_thread.start() + + def _run_watcher_thread(self, target_path: Path): + """ + Sets up an isolated async environment for the background thread, + then runs your watch_async loop inside it. + """ + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + self.watcher_loop = loop + self.stop_event = asyncio.Event() + + try: + loop.run_until_complete(watch_async(target_path, stop_event=self.stop_event)) + except Exception as e: + logger.exception(f"Fatal error in background watcher: {e}") + finally: + loop.close() + logger.info("Background watcher shut down cleanly.") + +session = MCPSession() +mcp = FastMCP("Tostr") + +@mcp.tool() +async def init(workspace_path: str, use_cache: bool = True, ignore: str = None) -> str: + """ + -- MUST BE RUN BEFORE ANY OTHER TOOL -- + Initializes the Tostr MCP server for a specific project workspace. + By default, it will attempt to sync with an existing database if one is found. + + Args: + workspace_path: The ABSOLUTE path to the project workspace. DO NOT use '.' or relative paths. If you only have a relative path, you must determine the absolute path of the current workspace first. + use_cache: Whether to use the existing AST cache. If False, forces a full re-parse. + ignore: Add a default ignore template to the project folder (e.g., 'java', 'default'). + """ + + target_path = Path(workspace_path) + + if not target_path.is_absolute(): + return (f"Error: workspace_path must be an absolute path. You provided '{workspace_path}'. " + f"Please determine the absolute path of the current workspace and try again.") + + target_path = target_path.resolve() + + if not target_path.exists(): + return f"Fatal Error: Workspace path does not exist: {target_path}" + + # Check if we are already initialized for this path + if session.is_initialized and session.project_dir == target_path and use_cache: + return f"Status: Already initialized for {target_path}. Set use_cache=False to force a re-parse." + + db_path = target_path / ".tostr" / "cache.db" + + try: + configure_mcp_logging(target_path) + + # Auto-sync logic: If DB exists and we are using cache, just latch on. + if db_path.exists() and use_cache: + session.project_dir = target_path + session.start_watcher(target_path) + session.is_initialized = True + return f"Success: Tostr synced with existing database at {target_path}. Background watcher active." + + # Otherwise, perform full initialization/parse + await init_async(target_path, use_cache, ignore) + + session.project_dir = target_path + session.start_watcher(target_path) + session.is_initialized = True + + return f"Success: Tostr initialized and parsed. Cache is built at {db_path}. Background watcher is now actively listening on {target_path}" + + except Exception as e: + return f"Fatal Error Initializing Tostr: {str(e)}" + +@mcp.tool() +async def inspect(id: str, include_body: bool = False) -> str: + """ + Output the AST details and code for a specific struct ID. + Use this when you need the full implementation details of a specific function or class. + + Args: + id: The unique Tostr ID of the struct to inspect. + include_body: Include the raw code body in the output. + """ + if not session.is_initialized: + return "Error: Tostr is not initialized. You must call 'init' or 'sync' with the absolute workspace path before querying the database." + + try: + result = await inspect_async(id, session.project_dir, include_body, pretty=False) + return str(result) + except TostrError as e: + return f"Error: {e}" + +@mcp.tool() +async def clean(workspace_path: str) -> str: + """ + Clean the SQLite database for a specific workspace and reset the server state if it matches. + """ + try: + project_dir = Path(workspace_path).resolve() + clean_db(project_dir) + + if session.project_dir == project_dir: + session.stop_watcher() + session.is_initialized = False + session.project_dir = None + + return f"Success: Database cleaned for {project_dir}." + except Exception as e: + return f"Error: {e}" + +@mcp.tool() +async def skeleton(subpath: str) -> str: + """ + Output the .tost skeleton format for all files matching a specific subpath. + Use this to understand the high-level architecture, classes, and function signatures of a file or directory without reading the full code. + + Args: + subpath: File or directory path relative to the project root to generate a skeleton for. + """ + if not session.is_initialized: + return "Error: Tostr is not initialized. You must call 'init' or 'sync' with the absolute workspace path before querying the database." + + try: + result = await skeleton_async(subpath, session.project_dir, pretty=False) + return str(result) + except TostrError as e: + return f"Error: {e}" + +if __name__ == "__main__": + mcp.run() diff --git a/tests/testcode/MRILib/.tostr/cache.db b/tests/testcode/MRILib/.tostr/cache.db index 466e2bccca7b172e8af77dfe8bd9c94c97662927..44f85dde55570f6267432fe90850a124134f0e23 100644 GIT binary patch delta 97704 zcmeFad3YPu^*^i`?Ry;WaU9Eb6x&H0&uBCnP4>juoJ3j3Ms`Lsqr}9q9or!U2trQS zDVu;>XvnKQ0!Ipd6$iyN44-x>KsID4fH|D75BBK%qSqwojecf-F8zaD-m{A~D% z@Gryng?|>lEBwRoP2uap-wIzIz9@Wd_;C18_&|7Hcu!al?+kAauM4NbOT!Dov%+Gy zBit5l4%dgP!)0NA*ctjN^pDWrLVpgu8+tqRTIhw))1k*g4~6at9Sz+Xx+V0z&^4jJ zmCAvI%AmfaTkp~G;j)f|8kb^8UDe}iERzuxRg)44C8_gqfltKZNiGmrrw^Remy3+6 zh%T*ckb`Aqjm@e2`m(ub9$7rl*VAKEh|o{75@K3PX2iIfPI8Kt;Z#jirA$Ii$~-T| zV!YpPQkxvEh;~&Q)S9EI{QG6|DYg98@ns27Nhae75RiCH=3_}EEvZsEE^@k*7Biye z^I0T9B_xK&nqfsr+3PT_Z$V@~bM_r8Fuh^n{Q|BxFTWV`)APl0rI@bBS!Ke~4FD5eyNlqwIOcmn_r|X$Co)fF8TGO?tv#)oEZZLfyH`P5# zv~5}{|C=JB`L#7wBCls;Ue_e-W>wWhF3!gUMM=w=t|lZQrd3uh>>C=M;i`<*);81z zhc|TFY^nTdHEpiFeM8-Sz2v$I_w;;qZC!ry#G3s1^)=(CN19Gw)Gl)9Ag^SS!f7+| zuTPkeKdH8ZQ!I$UR)=J28Wa(eXpYK?(Wq` z9{yq-N8ai3wC1}e)a37|YileWY17vap54ARf5L>S{JRswL|NpTKz_f-RmE@TH9g5E zlW9c|Bu!SZ_iiz_9s7s6d-B GUMrPUdA9 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 E4fQKxg}<4qoHoN!C;FKBFr-f#8CMR%Wm zI=SviPi3|kV=*-S;(4_+ebIuq^o*hD7tgAp=ojw|OVNjF9m{AEFQpAdznEzniv9@m z%TjS=Kb0(sSI^M&iwg`*AIFW)V6*Y6zzvwfNCD_E6#e2RhN9ouZpg)kSZpc!g)K@I z{VbkasA6dP#RZn8Ul7j>O@E~GwC|RpU$`SJMZbX38H#@KtXhh`0Wf3@gu&AE3k=WD z^ow_fq3IXTv!&=4ww$5p7x&##^b0tsq39RafTC|m?4kxuPtIhdjG^flZ(BptFYdgh z=@%|DL(wnpnW5+x?-4`Mr-K>Mb1g-mf_qsL-7_@(Vs>h1`o+)PU@mw8R<#uU!nrgQ z{o=kKN1GJyZ$r^9?#LQLVlVEiq3P2qRGp+O%Fy(S&!4I3<6<1S9SlXk__>CnU%cZD zMSn^Ah?Jl!mZA?0JZk|chNfRUUQ5$2NbHuTU$~GhMZW+b8H#@K5g1YQAS^ox&xrRW0}S5@kl8aEXEV$I)B^wIaMAvaTgY-#$1OWxA-3$lWt>4V6K zv?m&hesRwXMZb7`4Ml%Rd!ZwiqCYy^tYB#R#fQSs^o!YqrRn2Jh8iyjX&stARUQjk zdxJS6g{3hR{o)0;6#c@bWhwg57)GojmZo2jt_@AUnAaMbK880cw_1vRI-QC0f|f}n z3`M_qMhr#2xS(>(dW(Xk=@&SfrRf(;o`$AhOeFu|)m6AT3un>L>x;Ldq1P8rfTh^>x+pRdVR6|%Tnu2%|A<3e?zY?Ze!{71+?Jn_4?wT8G3yu^%k%sqyo>e z-GWj>NGd)M3{TbiEE|4_DvMXbSN^5&72$=@zu$pp`xT*Gq2ohcp$Q>>@HfGyf(HZF z1}+H<2WAAq{AS^uns2+$>wUp{iFeqmde?hJ&$FHfJ%gV0p2eOi?vLGT z-1Fg7xz+g*bp0PVPj>EhE_F)I7H75N_l}1hIfv}Xw%Pw~f7hP3AGQzLIrb;)W$Ylk zfDJQWF@Fu`g69Mm1>OwI@!#qve!=%|-`&2X_Z{#3UYn=P{e=4lcdhGwSF`hO#}>Pn zc@z%+55bXN39k*u!cAd2T=jn$`U%|f&kCIsl0$95FM@vx-V^*G)bwru5%J*UU`00Q z2z(NFI&fd$_P{_O9XKv9$^VuAul{HJXZv^ikM}S0H~Y(dfAKx-`4YxUK5 z|KYvEd#!iCyT}{$R(d}4yyf|k=X%ejo?(yb5j~9iZTH>oynEQa&wYZs!}YoAUDu1Q z+g(??zTrB}wa2yGCA-GE80TBgE1gTvac*+D9G}9U;V#GZj!PWVNIO_kZ##@U^|9s5%yT>;8*=T`mEy4ecX>xST_n(foup0ctn2R5eExxO?UH@1QBcpI4`l*IVMV@V9tVm>#v_>7Rfkty(j zY}nq+6!D4E$w!}hDz+Uet##U%TBnx&d`jupgJWu+T>3M)FW`+Z`^hu-C;#%zGp-1F z!%Uy?T&5RaBH7aVd&ZDEvGnKe(yzP6)ZSJ4bEfpGKBl%tzXyuuLpAy}E?wG8F-uPv z(>h=C1J3xRck7s6wv@KtJm%-)OMl)p=I4!LzHcb4zTS9d$GS1Kjw`LTcFfP@!%sbx zjjP9$t{U?_Wza5LIi`5U6(Mh=q4WXE#?)DAG-Z|;|6~`JR$erwa%bt!3&;GtV9fXV zrPb$+`FU>X&vPp53fk+8R+ zY{r=4cB3^neN6GRG2f?_cBajos@5^JTE={zGUj`LmDbM@r|X`BcWgk zgCM6ky^Uiht?RZ9@^hy*oWJ0<)ssrwj~_!m|L@!8k7-V(y1bDx-RB6-voag4Em4rKH<5l%Kc||lPlvq z=(xas4*N38Gw0dfwJo}2!Gv%wQZ^dGNW(Jk8%IMJ|2L}Bid6mIsLub5>iplR&i~&= zb*|zCn1&P{9?}W8Tf>D^6l0>S!WUdq5bB)ahZ~uyXgJsq4(8g1Yi)=XefQo;?2N7X z-boV>Wjg`!y%P{GIy3*>dne_8a$imUfqN^L#bJnqW4H#-bNKuT2~LMo6DJ~moI`Xm zCsH0g&bHSU?hG9WJ{Ne=|Dx|j?~9&ayI*uY<+neO%zyOAluI9M$bay_#wJ9k3-GlRDD`wQ4(|*FK6Y>i(qlAsK3-c( z)}GAPf=;BA&fW(NI{9%AHRQj1aAQ*@3CA*tk0I6>e!_9Y&u4@f2L~oDo`}gYp{B+n zG^?R9x_y?>p?$f{sr;dbwxL7qr1vC8b-v@_3;h$dq?}Ul+L6I-4g}L^~S|I-3wh|D8t`f(|Ay1BYxeuF(K#xaT3}otL;c96}Xc6%|bihpoZb z>nfs~M5FzlL#h0pUu~rQxb)GL@I8~@za?j=|6C#iuoWIYnYaYsRs|a{oeqVpE}Sx< zGCHTuAhA7Hp2};F&IXML`NewIg!~7Ob}oXan4X5Wwl2n#aNg1oz%OUu4kdAL84}`( z-@jony+l#Fdv`7B9?(@9k3MXxt&GmAH>fJP%2a;cV{<9h{GT72n1A!JMT;{rF#-1& zl}jr!;tAl_4j*Vv)G013%cA7-t?b*?H->8NP|YaS?YZhye%a#&Rr2OK*ZBP7kEc2{ zK8>TL!Qn^G!1r4~GzHw!;I@ru12{^?z1~G?s&A-oV8&=ybBC%+iOzgtl<0)~!%r;k zRMH$$GRWx|C&1>NOseqY6R~IEZ41xCxa{$K4biGnqSK!oB|4s5JLvG_fBNL&PQ>`b znh9?^oO)~z#Ag5!M?`!gj?@pxCE#}dC*^~cqwBSm&g`V8%=I$n;OeJVEEM8;l0$L~ zF%4%*I7QQ$mHD&^k3O8)H1Bf#C()*g(YzE5ZK=Hf7|lyDA%Ealbv%v04Y*Lii(Zi9 zJREpIB!TDz6u>7umZ-1q?j6c?m184DjocU#_{H_dRv82)K0Qt5R5*R%IBUry4gw}o>vgo*`zMuID% zju-;UFp^1&%5W=J8C9kkBkQJz`{n017KXO%g?$kL7Y?j>U^yC^!!ITQKW>FVu#%cT_~TMhz-UyHfd2URYC1#FszqMJz13f+!$Pflnv0pTN124h^0z0+w8f z;kH{auy-BJ<&c{<1DmbfAScgCF2!Kk$&mKOcoPuNuC{bcE z(s&5Tjt-DRPK?FM;K7{SUJ+eg*{}fX#|x?aDK9M~{4r)i{=JvlBo+B#5@H6KKUACp zEe`&NapIy>;&9LvxbpIKLjz!4xuw%9qEdOoG!W_9mCCoiEav-O-XV#cEJ?8EaJbnt z+|=oqj5wy4u2O$eO;0-<79U+a4Wnu?I9})CRQ{cpm*peBiI9s1dpqF0ocCKJJ+MQFiPXh%_l(1OlX{_0njfKZfVuXeceO|RC0qsgku z^EBTCuE>~xD-X#-I3%zE%a=JJ5E$uETS<@BzdG6@SN^ZBy7RBSI#ELWKmu;{F$tan z@idK;${>g)t)pu?b@=u9Mu@eR5DUFlLd=!F?6rCcVMl2-15bnmoRGB`d^TfhNh)a<>2LTdEL=tG2ml${!ye;L72xs*)2B>8`o{{!bO4^_P z8`^$ao_T#DJS^k7ihCTBs%g}OD2YfFgQ-HX0IyiL+oG{6S`ih?jcML+YAPRpJ)S@1 z^}S7qc!M9mqDwfaNdc)!a8z{#{?9S&XPr+worHPQQ8R9M`sB*dt2dWQ<;&i%?p|O1 zjyFz}I0aXQ64w*y7@Vmg-ywsIj{7)~!4Bd@uuZEU-OUA2Vs9$Xzez<2DgW@BVBL@! zkfMe|9GDbOz-gP_7H|rL2m!;jlpM*SGhESB8QnR@7|USJi9|>D?=a8f zUXKhl5*W6Yk@PrXeIyP+dwNDtshd7^y&K7*V{*?2ZK1b<_XHmEf9_xE+vh#vxf9xa zn`?vfQpeMdiT1VZdCUc{34nWE5zZ}LJj%3b!kkZjw~X*tvQ_zUe+cCJ-)WQLaXE&_ zHh8kbbvup?f>1Ba0Af324(4Vd8ePA808^Yhbx}ohbBn?Kx9?2l?|5g;C?T@D+!@KQ z|9x90n7*3C{-9BqR4j^VFyN~qLSwXy&I`%r<~7|X^$meL6s6x;)H&M4S#whPYkxo5 zMIoW;SSDn7k_GrQ;BrUokIGXXf!s$)#DGO2X5OV`3oD}Q8jar3VQ&7z@{!(E=YRW$ zNf3;99XAs^glPZ@CYN$ic>iTML~CX;`lLxF9d5xW_(7Rf)w^>?=v3t|eYYI@1<_bw z!idjQbR~mpMn;@wM$|yx;zX0q{P{SLW;QB1rk}q%e}qmTzvkb*{F?Wi`MK}a z%tVkDd>N7IC$8g2W9qOCklQH+j)7B_=4)#$abWSux%u--X!gH1hUQ=1tDlL08X*x^ z5G0h;aV;Vc0M}U>(UM?{I{d1UO34(lKyU3_tZ0M5m}h|}M&DnVU+_mRf7knsQcTej zDuP6j3JBX$#Nd$iN=5L_ZNXi7gQbaVq};?{Gpaa z$|OiVx(LVqxCX%hfqN&PJkox4N&D;mNZWJymOs@Zu~G&ZelR_VsTGo38VzwL=t%?_ zB9uksPddq>v1V39bbGVW2N^7R)1TIWMl}D%pQb`(fykLaqa;H3l5oS~v4fy`K&!!> zo{%~_sd#ka%+XHF>rUk-en2}hDSzUk*p4w#z|gR42!O<%I?9T*|_kGD|Bvdoec4y-00D~)v5d!AIt~AhWz#y-1)&j$L2zt5_u7Mz-UBk z8Y&1hGm)n;!0yPrF^G(XjheTjBZhwD&Pq#jR9vVX0+~J4_WwP0ztmpix{G-p52sEDjs%O;* zB>N-|2v`j-K$qiUSdRo^Gcrlsxk-V`-}!8PxISJPT~}*R+LJ3wGp^0A$B8kq3t2cfb-$pvdxgaI~qD5xkw9A`L&;} z%kTSaQvUr<1NpZ;odBUnq0ug~tU|yMQ?iO0UI{Z#^NSypQzEe9n$}!pLL;eZGQIO zo8Zd~yAh`h|V2eSZ0|aV6Ut!F_WuH%i(kcK+fGi>* zenkdWo02?&S=X9*ss5RVWk%0vHw{Do7M zjDv-(TuB=*SqGlpU_715e*ORG#M3MD_kZOf_aDb}%tUgrOj?ksq!0cJ$8uG|PC}kI ziAsKn*wUrz^&Wk%J~VJJ*LB=zM<}gn|DJ#NqUTt;17E-t!s#w z6A)jBRmnh5!4HcTt?3)oV~cVd*5cBdW^}A;1yUCNbM+{(bGNvw@`t`?z?tS$R5^6v{e$41Zc?O)ELJO_#8Ai_a6OhS-^ zn=V30>0%=%KR7J$) zi%fl-h6IaHsu9_%(qM0L=VxqXIJYEKvT|?yYh>lB@;mijL%np)iM(-Bq;*~!bD3l^l}M` zE054G18GuZt-ZZKYT=5C=;C^V6jcF^|7w9nN}P$6^6*2v(-=e=)7XJRWO3SQNCL=4g_k5n$iHAuBfgoN|zv}pxWbGt_M z`*sPy%iF6)qzy+t^1DF(*xR*n4N_$$odLgqXhykm8d>o&30$$ru!*b)9Ub(n%w1MO z>5EcIj{L#jHX?u(d=Vk~0%Bw-W&m9cQS@nG2yhGq?%3W=Da}}l{a9~EWYLaP{D${apzaNB&p8t6i%@Oo8SH z007Dthj?Hf0=W!r5Z4xxd@8N2pk#{9du-uZp~2uy2zz?ZAM}2O#CVPFlOme%zN@8bPpf zItkey>NUV9NSbgljRP=-6dj3JGRn3Sk=3)#Al9}$MW(rtnXW+Vr*5VLw7@~Ze<817 z95FB?Ah-hH8PLn9F|MJ(qNQ|KMEABBw8YL7`O?j78Ko5@JH3pP+~#3ikxxBxf)+iuwTuhHM7y6n8pA>e^b1)>0kvinW?@iUj;6 zi+Q;pv>!A>|>^6?trt(h|Uj4p_aa%A&O{Jvv5+8YC4g8l!_e9mFOBOe66S%4m==E)*nl zpaEBq6-2_KS(!E3<99-RDL1IjS)0oL=))yNL@{xd`Oc4`0Oke6BSVYfs5*jN z5E>9390u?PAQ60e1mDbU*a6O0W{{f&a@mg-jU^ZQc(Nqn3KtVJ^DkC{VgU47M$81V z9zc4R3&6d$y&|eN804s+(Dm_grR4r!${vA0k?gT;big9GeAC~?4A?`?IqD4LfAqIX zBm+7?BwRcsC*Yc$*n zan3*mkend{M_{CKPOGuan?c~G;~2;MDP^{P8*&Xc1pR?>{{&x?mv#Ts^_cT%$F26e z?Gssq!7?*!{ou??!ns+e!7kHafPqk4$=^33-R8x|BaviBAK2c$O-zF%0yaSo7!e{U z4UER+NA)2{$P_6Djx}Y9wTn7Vt%%O4HE4bW>C0; zQh@D71MsXt)qoKLIfbeEnLwAIm%-j7jWWr;1EY*Bg@gLV<|4-CAlWUOp`tSYb`&Pzdz=%SPGukO&hhHS`G(G9dec!NBw0q&hrf2%UA#GsyPsOOdy?F#|RUVL6hJ#j%y92T|VxDMPGK*^cKt=SBWKiAJog)9*!JPOtR40Bz zltgMb-ShF-e*#Oc6Ue0FLDL_fU9?F>?00di20 zjs{8J!F{l3NJNAD?+`%%V256s&Y4YsZ))50-Hkz#; zIyd=b57SjjyN;aQ!%SHS8dgnkfDU*_}DO7hq?LF&&*? z=MU^<)|GbIO)wonvZ{vKM6SufEDae9Uaz8EY0CP0qg1v zvQ!klqL*3vwPYiGOdX&Q7#|_N!dMp*rHm|qE6{f{&}2O{i&uO5C|P$2*>!ziOSUyh zK`?kaz~V=3`?!Fy(I^9g@&u?Z8J{-IqBu9_8eKBF@fWTcyJT+iz#1k3ku?s57FtCN z?@EB@i_U}h$dU%bB~*YI-`Yyq;*6XV_*A7aZa~H4vo#Qn#vFmYYnd8R2hv5|aG-G` zBR<>_02~72#tRop4#(iqCm_&NeEf0+S)fugBEgvYvrO1b0ye>6dXe;ts*2Ba10SIphunej9_@Dp-4? zEeoDlZ}emuw#Gl#V_+*bFaXY=)M!v7VSuCp6TG`57@**a1mHn}m)hu^yYh5QO^ZRH z6a3(t8?d=dL3tyNRO3db5uPki*K}C9fyi+**&yaG8HCTF~FftxX7q&J63db4>W zdV}#yAP;Y3CQcV(kZLm$GELwm9$F>?=PuZJ6UdkZFd6)?t<59@484|olwe!QO9M+4E#V#QY z1I%o)sh>%byY?}2h~4R!LAnR=>5L&{#ox5Vffhr^1%2ZnI{A|!5cum7M}llw>R9^I z<*qNCM>|eso^_DBGmZ#Nk8V5qYQ_=vwgF?~RX|ygy#SHL;z(Di6>!)jTi_SoW9)4<2{&U)`H{g~MNM>Zd1KqKf4FVe z0(ixd{wtWdNAJIad6bDHc}b*t7)^2t{ZxfcszA$EDTtAVb)KB_O=e!%#9VoGbVX&O ze7L@B7k?oHyqCYpEQHfdRd&rlpQ;ZI>VuJ8rb|ksySIP;P-Lhtq8?Ox^vKZefxi8_ zcH>_?GDu&yg9@5{@^t*84gk#V9g6e~MBpJ~eHl^)cIiWr{=UI(>LNBcJ)-n#XxT$u z#!k|E4n`ChY7SC*gL-7{Xg&JG-O7M=KpC(eG#ELlTZzDJV;9~?Vs>jVasaVIdSrjU zrVPQI<7mrO%s=fBButINXoECy&=EzL+H^>N34o_C34oI%jy7J+yvd9|^TgbEa~_7B z@F6*~K1F7wu`@18Gc(3T27vMm=)IVYNCqP!^EKqPelyKfIF+8BK9zi)W}?o)8F-5a zxnzf<t_auFT+GtbHcGMx=}|NsS)Yv>+8p~D?{e=VVnbf>As=JAa&O< zR%vL&JQ3O7i#MMbYreXl3ZD^B+uPlXigX6Z+fD2i!Vf~leTN>N-`29L$jgfy?PTi! z4o~|`Rw4tF*~uOpi5lZv+;^gjb2bZj#ZVf_<){M*z*@K*A_oM_X0XCz-wJ&Bsaub} zeG@aubM(!dnDbcj)$NSiwc5u1#MQC%4>S_YvQZnD>CGgRcPOgAXW>4eB8TZ3QMH zq$Z+iT@$Xr*6nxK{(-0N5~j+KRwbvpr6#; zw|~&2Xs+Nu_aN4|UDtN$kyf}M_3g*r)Y?W51l^?iN&3LSNP7R!P+u>7jx(a1+&xJ6 ztH9RgIvsA(evQL@bnwT_xxkrEbVVj%G3uyFU*yr(_>9VoEw>?HJ zjklK{smV1|!x-NHpk9DmsP zGHEyjA?GCIUl{RV8sLC27 zwdBtX>rYcI13BI}f%Ix#-5=@e*9S0h7-0G!=BLlt)g#QRaA+w*0>@E$x=*1;!8rx{D5~7K55M4AJ3+N2t0tGAO-xP|AKR zRo`B^)};f}hs+LQ0_dR}9oYXz;|e{>e6tCXfd-#z_lt~#rcdf_iL;DAM2aUbli;E-D zt>IL3XJCMj^ks}6uOb{7e} zh)e&PCz*N?kUd;jYHy@z0N4hvs7wZe5?CQX{MJ^GYtF&#*_iH1=)i!E zo_ori?e76LM6Cqg&4xq|@MTFu=!+puumE=wBt=goBTZ<}<>2AGi!N7nS=lVK|I;z3 zK1OKCMtAafdCM~#^1IL4V0!(>Q0xsj}bgZ_{^s05(e`gDP+t=@Y*#5ht zU` z&bVN3di9fa6|dBy=MdYxga@_^Mi6R`J8D-qZm^O2-KbY|6eugT1cDJNy?XTl^R$Bm z_T0dbNo2-Wa^(w5KS{rU3##l5yz6M*i!f+C^cpjrp7{kYGCaAdo(&P-&s^hAXCJ#zo3?+$>T3Eo#R_`O*K&!laU+D4m%eQ*QH3q%Q*0K zE>`^?=fXG=g`=WTj@CUyyKOKv;~=4E03HI}xDa}Gk=)&`^0Lt>?Cu2x+yaDq5@M_| zo`F~JY5t#~|J;Egko%9iT;x};Fy};&)*g^r0*;N4KH-G}9h7EsrMX?#xDcfQSZ(1kqo1|$yN>1ZQ!ZggA~0eFUY zvmqZZTriPH9`-0WrNDkhUD2s2kKFktv%wRy`G4s7l`RITV)FP)%5qbxqum|Y-Rl1A zip!Sbx_gVFs&6xGC(@1D1E(y?)duuk_yi#l_jzBhX2`cn4=$O)zNQ-#f^Z*QJY2{J zQE@Ncsi#rF6}}pUcG&Lb;2>J{VNdVZj~;rPdBqulI1i|w+BV^jqXNW43UZ)du=l}k z0>`4G8{cKFV#cR)(VA$kwhf(bFWYPmifkT#z^#-VCt72*A|Ifj==T z$FI&!hWOfur#I{ycFsAwAw^#J6Jz>Uk<}kC)5*NMT@@t%0fgSpS6r1x@B4tcj*Z~m z7>G`QvWzPLTn!*6OkTi`;s`lF#@84*;jhe%uK6~{05hNRo;Xu}){I;$?pMltyoVdh zwjPG1`ize()vAo_?i-anc&JYn02vvy+~W+%YIOIGY(gqs(XFdQ1}wikqn@>|@#Fq} z{3$dW#1S2YpCJ{?s3^6a^!(K^nS6N6?k9w0FF=~~G<+lh*uXWBgEI!4360l5;NIbo zI8|T{FsHD%o#*8`s-nBFi*rj4mF>+5DKgv6ZZ>!EeRekBgI!M_fKVOTOR}%IYAcKz zV7J~qfZjk7A4I=2^7L!KW_MCXuRGYYs4EiC5xDsRl7-(W!j6!T7@HXH%m6&2D$Gts z*}DCRFE<0zXK%suwc{PBxkG@9oor_G2>Hm>PPS>oUg)dFX^vQ`t;xj=W{sm$m}2W< znjRTC*xwC-A4+a_?@4`oz%DdXU!%;#Xx&0yudz=cZ@5_xS?yxW$lOPuq0D!&Cr99_ zL6IfAVT78T5pxGqHU3Y-0TbXb?x6p1v8`nXa_!a8Jve{4ECLzmnfS=X9&dC#yMZdl z*rW7myL$T4m^@1Yj%3g)=sJ7MUKl4Il}68xGC0_$cB8QtF_aogp??6(4CtV7vLf9X zTv*^F;Ddu`XWh?~Ee;$sHbVb?QsaV#@ZqbD5WR@T(N9lCV1S?u1^%UQm`*~X#=bxl z3-Eh5rQkIxOHp5$#aee`lPv2dm%s5?T682 zdK47eezHb314lV*2_?FbLALRVK^x4Kf z7-bdzhpt@{K@ZR!FeX^$;C=~@Uc5mCnkt+E;4Y(PNPmEBDyz&*HN^5cxz&&pb83ow zH^8Ro(l>jRk(DOXg6uSJ)1t*o7Hn9#zKQG%LScK(&(@Gjf^1I&hFrkYnxw{KKBc);Nl9}XOi zT+Bq+inAR#u{OHB0rGJ7u%6pK>xef+o*K_mpY92ytDNm%_5P!$ma`c)g6zcz!hnbl zpD_e-07HT&7-DQ;9ssff$}Y*+maJdHNGq z1DXFCY`V8y?g$X2nvET8sb&LAQw*;Nfa`*a^z%G8JKW|Fy9bvggrV?o4FwEOIwoOv zuCHOImY;QEPN*?;ma?W;SCzSQd{s0{l@`9NY+Y_`id-;>-DxaQbpyMW>}z1p>wrfd zloa>`!hr+*~#PsH8@@o{5YZ?7aYJ0(Yd4Ofl?6zFNk% zm76#G(5M=OO60aCwmvY_4bW^?q;LNa)R802>~VzK4JBk&GrN*Z*~P3SPbJuucD%=! zeAMciN|fd7Y!gufb{!*Sli4_#ThEF|cSPB{JP{smB8nknI9|g6u?#L-@K%Rq5PJ#p zA4 zY0Lz<&*h#*E^@kS@pjDB0lTF%J(plViKH?883ivCfnzugiG_z9G*DTDQcK0f40%Li zPpxRr$-sE*^jS-fq;g$K4&wBNq`9Ue534h$-MC{$t|qa4P^SV0`OxleG@m}Ju%VAm zn~AJ#kxUGcZ!!Wz#S9Pj#VK&qgVh4=B5<+AZXnBNu}$UEb4kd?<+Kk{S*~keZZJi% zv)Em9*+;ryjaQt_&N#YvHtS-dKoH=OL=kjwT^tKI+90GFuPuVV4-SZKd&+jqK~~RV z>&ts{5*|=ZAJBCu*R`LXiJRxJ-Cy&7Wvsh?;wW2=jEpjZouQlCVsx5sdUGzzMDTua z0d@ksMI#NjUHTF@s`Z0U3kYwAn+SPx9y`Trf8XYQ-}w{!`*ew4vzH%UmYY)@RZQR{ z*ETN)1-*O`yMp#;_FB5hsmvPb-n&=V=mkHbvOub%+&eN7jxllys3;9YAG+7i7O|5| zxa`eZ?ArNDakaCLZ|CGi7ic`1w+Ha`_Q1 zH@iAI*oNuaa46Td7V-Gx>BWUDv2zJq7cM%9yZd@H^1u;yLj}b+mB?Vfu2N{pkfRi` z%I}y^2Rf`eKdh`$Sx;rF}P1i7BFRzvs3u~_<_hZl-@J3Eh_*nf_aV3>eY0;j62mpS761!B{~4j z6t}EVqK$;?qYNUxdC8h=>lA&Uuf4zT0Fb*;{e;{(z@ALjT*Zo1#s0>b?A)VQ53=t$ zn-R|nX9Nu~328w05SHo2>h%r?69* zioLnHSctXtjqZv&?H@YgHzVYP$=ytRa~?ISio>iboY|;huJO? zJdDm6-F$u+b9Vlj>>Tp=9{YIm*VCLKvaH+QK*Cd;XM^=7VPc1k9nr}&hzzeJfd3JP zx24kj2f9pF9$}}C@5?Q$iEeMf>_)SPyEmN=b@03+EHFCsFm8xjBm2VWCQxS6s#oZh zDY?=)rO6Wp$(1MCCwoV?3*rA^uO@#whYdL~R9tA!4#Uo~;wpBgwMh;foqHDBZjT`9 z3(-3WeWGv96XNt>A|M-{LP_Z0z?zTlIhPffvi-RQ)zSUf;n}jBcPr(gcbo_D68pQ! zX*~4kvh!JniBRML@!)uoBRu#r;4(=>)?|#gZ{Xm^GNkJQc40Z>z4EOU=!5Y3(In@jqr1Mru4J2m`U%J}OfhN1 z&C;k`n5f`P1|us%jn(*xgT@~3B41s^Ix9T3Z`uMEc(%CbxFU|v?a#35ZQmr=G-s3_ zPUcorN7Z)hnP@HyeWpA`mR*7#8C&Lr+gLw2;TG0SUcZEW0}?r~aqtF2vLoawkw*MD zynx{UBO`1anKQ`GFJ;@xPR=c_vM5po2?RgA^lJ%@BUk0wizefhVDPe})){(RYcx3o zgg$T~EC!Hw&h~`u33A`1Y-Ph>50t#7=@Dp%gZlRE!;^Ce1=-x(C?6^-n*#_$xr{|% z9^LDOj(>0&dk`@)m>C^zpzwEx-v&Gnpsc{10P*wSaANxZ;p{!YqpG&{@zc+lo=GKy zkV(R%kWLB-J%j)OLg+07Oanp-5Q=~hW&i;d5u+?Xs#g>R1VkOhg5|1L1?*h}`?aIj zu2+BWKIf#6`M&S@KYyM_GqYx&efHUB?X_2ZHv&!1!#mx>hk}p}{jI+Bf9$Udb>AUW zqYfai7mf_kimst%I~u zHxleLCQY_b9}=qI34|L0d3H!}zzRauW)4b*SPE=43Mi2h$34S6qj!o@kq(_6qmG?Q zY@%qpD5lcfokD3J%BszozS3y#%v7+# z3jZ=6KlR$ga(aHJFiDqu$>UcptVNi8VZ$nH(`_CP!%~7^Z}o=jQYEj|`BbfYnp!2H zxwKnjj{3$qpBYnZA!pL4-9iy*>xJTWaTCDG{hIeel0(7B0h9`k9E4rMaCxc=P)~!$ zKo(XWcZxex2}4Uvph4-PN;mBhB5Cy=p>OCS-w-Ug-gu@4w?Ak6#wcu1dxSbem`~U% zjEmFdjQ$uIHPuL5W6lJYZ2+B^$?6^Z4AF7VUP!5P{h(MNW~tDkeS$kMW1mn8eGfIl zC{siBC(@L0b|pjryz!`nLT)ds?W2F}7iuE-9juW3pZ5E0QI=-)SNX7bGrxlg?rZqa z^;i2QM0u-XFv<*fbBS*};y?dDw3_Xr^wK|slrW~Cvt7r?d(dOH^uZyamZsk!%=6Ad zWdhD+gIAR2eWi?2-2hY}XUvb~G-%qAD z?-Yi*NBPD@d1rfZ&n$OyxgWU%8F#@O)cTB0dgLx4=6}0qE%iMt-0MY59tnsjMn(lD zs|?FV)=geM_!xE-#n8zZ_l7WfE|;mh1j#Xfj}tL|pLh>mii8?DKf zt#s`XVI+;YM;Ps?M)?nFmYLp;q9kNX0B8l(*-&|b9)OY*+INqT5)$qkgR&{C81!7m zdVcdBVd9lNKivZ=C`Jbx2w?}5dK92~2kw4k2;z7>P;0n%xOeDerWir}jtb!+(Y^`> zloDUG!re5I^?B7%Ve*xIc4Kp8Ge$e(m@wW0bRpCxvQv|hFaS#fW)MnCk${hqEr^uf zbn=*x8DjU1M%)ryHreei@~P$Y-7#TUThCo7hlFtY;3ly69rp?YQCNmf0G|SImtMfa zK&S#alm&n)K?N#ae=qJ0?hS~dF23>zU1*}$V%F>L_jZt)E*g?4hcnq`GrA5O7fMiP zi0W6Y8q|m4fFV7b4mqC)D&W{U+^&{;je8AUck_)xv=J;b)eW~Bx=yl z)B72Q?9}@o+hZ21!!Lw`1TG8|h7{niR&4Dx(4kO=2~05V74DV5U&jU2W0Na5`#aXv z>K?g*EyRIx*oHFSlrE^80+Y^m`{tGU5#0Re+pR0H&pX)p|0tA00z2OkR5@=f&dK1=a>DE2?Siz#B9ua+30 z7ixS@i0F!tj2>xevnFa0Z`8G98uXVG6^YCwxIUMzGzFEZep1(i!e+YE8~i)@ec_@9 zY=H6t0L1{4qNX>F)J5?WOQ~mLMS5)w6$_Er#i6W17QeK4tfFHq7Ee;vjEM5JZqFCqb34%ODGsY!B!1D za#1J^zc$e~G1@y7dbIJ}12@bpr%?y6@UD37;tzz#=>K1j9l&Eh!uNo`2G~nTy<_r@vS3+f^nJB_?)J%W8ZaHlKTG(rBkBMIk^8+v%-UK58asmY;cq^z&#I^|I z4b#G^qM!|;(4VC5gg?V>=;fQ*MVBl{nDOz$wjvyN`qfqu+~+kRxvUXRXyjipGhl8b zJns6QW3m}O>(&kYO(>dPh6Fg>W6CVL#$`)t>*3XfgSD|%4~)`HRZ?N#pRWl4(Nlv2 z4(N19WG}>GM4>rcdnj=MS`botYi1P&R=*__@FC1MCh?DQmY=kIaX0@cdk;tXu=Ue? zGxfbO%a^gj=PLI_m($wcz(#lv@$}bkg4@+t*QnR4dzw}v^0|Os_(F)0*n2VFi2rxt z9{`s^(F&Lorhh&*Y1H(vqmmJ>LEBkdnEKkF zC9XXM|Myax7~iKgS&L~`Z5eKC(HX`U@;boabC~|ss=cyRfbUI=$8+gWH0)tLJ2%!Y z=F_>ag*1AmtI*M>y}=>Y(`zjAkfi_wE>4j*^4@wJ)Qu1tLK{#Gkdu}TfE}1Y@CwyH zal@I7A8?;k(i7ha19yBOctfu%@?B-J=4`O$4aMb@_oKm@#wtJpZ3S5v{4!%_TY!(N6z2fhsvnQY4KS| zof@{FoGyopg;06-~tR>Jy)jA0%hIvF93^As@*BcyC2xX8lsh=^B1*dYfT zf$>_bYZ4>H9_}f=Mr2(kgTWAH9>!GY!blO;U^noWClOhOk3|BZvbt{WGRO$SM+2pQ zVO{kS+P)P`=Zz9KBqNOmE?bo5BNYQE4ZV=q51dRSi~|q`f%#lMmXAeJG%b!2yV46$ zVns-T4^>oSy|}H#?H&l%eQFnRlyTRSaIa8E49t;B`(=rq=mP%!7cYjbvRYV zh{Z4>fx3xBfPEi_^dT%%3#VZUE6(gBfMp?n03rb?*>#bb`z2O z(Dtssx&>mO)2wf;kySbxD`p}C9gZnhk%|l;favJ;+c-eE4ypsZ>otYEgLmL2HeZ8z z6C`4@n|vSpzue@CsaNTd?zjnxLQuQ{7%2EKAkg(P$KEK(L@pa@>52+@8*jr+9KJc` zO~606$&R?7d+aC%&|9|{gL}g1**FnUgjfpzvBZXk1B|grAX^3dL>&Wbg~c+f7ZSxR z`aI3Pa-O32|GE2--3;a#j+{$4_E8^$sw<|{VS9s917LyR7HE3;y?K>a0gt)H2e^Wg zsQ9dTaJ@8^Q<5M8IuS3x!b0~M$cLXp9zuNRMM2C*Iv=b;)E}bU3jTOxc_CJSJO`M| z@N1v~l9zZ1onGSuZUO7G!RRznG&|MwPUqLpGWt9%qE8eq!8yVbG~xTg-iy?(dgM~E zd|p&X71irBe=I% zE;&Qw0up2`!_mz~O)d8q_m>eIRD?S@+Yi>$+6d)@94Xw%l%Si1&<*o_%aH7&lS>-e{RqWs4ey^sA zk9#oT0FS5#5*N}%QP~6kFMM`LQ-}Hub+4Xo>M4#yS4*$#>Z_htbTuPQe7r|JDvEI} zs`B9Gfu@YXz*P_57gAh{a8f%z1cy$*NDil$Tw(})oe6ugB3A5Y-#X9j%4o6PcyNn>B6Yn z07;YB0O)KNF^%?TiIvReWi`G?(gyoN+N0bXFgJ)#B0z~lajTK$-3!h*t8a0nw;~Z5 z2cHk3*iEC(hxR^svbwv>AVzQ*sLmSm_qHu9=4-4SaVrMB2ChRrYGx;u)Md9YC?Kk&iI5N*qi*svsl|R*6M)XM&hZeP&=8F$FHrI793S-K`Ea>8Pdd z1(g6N!Xu9qj0;3bI9HLh&?|>u#ji8nP$y@K6`>pE_^yufu4aRacCVS|uSOIhUaY3X z1aV-b5w5RoY+M4Zl0`jr?_OYEf*8RkK@0&10hm9~!C*b2&>kO9W(aRKAPWn#`Ne#b zsNX7)-cJ;Vg~t0<7=pqlueUW;vPA#<9wL%g+xEukjj}tsZ5b>#aIg@AV?bC;@jE7F z(K4@CYGeW2`~h4v2TTi|w;t7%Ks19F4yXvVm>0Sy zNz8IGeNYq=5H6Iy6!i#IxaeL##J2_>oviq*)yv!?{qTg8%n>Is{jXc#xjAAS-v95NCGH&P>hh&4)1kQb^-qYf4Z{jhIrpKNYZ86UXicVUtMwoU%q25mP6A4sXmr&s18Y;@Bn2*!nE z^3K5X^Ta681DLKFGf|$fFpsWe7I1V-;BKy+>tSzLy^r0;i#9ov%5iJiL($ML4tl=VSPPank?S3W1T3g;wsKQR9wXt*g`RE z`*2@#7wUUJu8Pcv~U@V zsV&#bJ+*ng zp>`nt4lfx0PyR(L^VxV4Uroc-aNR1TnLRdJT!xuclv_A$zH&>zy-3`wgsq?8=aF-1 z%ZZ1x0SP~T7}o2q6&o4iz#z(w6TIZxBp&tT*B~fSk4>ruNF#t?#zKWxmjMf6r2;63 zGB%4zVIjbu_ck+5o$Raft!%s*G1*z0#riHrNO(z87D_BL=TMLBTWb>gj(2ldq`00o2rVv9(Oodxv|;L9jdgenVs=xWN|CcY{4 z=3pLz?=pn&+e-Zca@fM+%l-Y=*RGE!`3L4}%*|j19oZ>Plq@uI7a!q71sSUS8taU7 zLyB$@6Gk8egjz~wiFV*EJh1T;%p}yq(zVE=)gwvxi4l!TO_*_Qz!TLIPL4)G)y8>q4?0v;?X zS9<3TZmy(@`$bQn`#$mCymbI)JFiX^d2Rp_n=>Of4)iNX6o_J6(%^IX%6y_9d;g*X z;xhfg15pRX7m+Cg?+`1`!40r;!BT=h4k?^~?FSk-(s$^i+r`ZA34R%UC8IBYjIYS; zi`vwTQpw zHVAaeLTpL}^wpi>VoSMyV6%Jj7N`;RcZu_jZvW~d*~obqZWHfeabyO%M0E<22u6L> z0%Y16!S;o-Q5J}rDHMDx1dpP!o2Y19#MO=zZH?BZRMj7Q9E_AMBHE%F85J0jN6*rQ?})hs=v?MKCEiry;Qsj5*9 zp{I_D5h=((La{G0mggV|lhx%mumGf9{mXcvwd&Lu5|Xmm4hCj}bjN`c@b|%4L1V+k z82U2`uq$IGiO#?i$HaFLvuF4~fN;c|F^F+i_mqPJGXV052Z3EqyN`?WLymIJG4_Dv z1$B#jPFf`#Wo*Ag2wOkG?_inP2rKz{w|gebf4qF3SWCXAF`=tZiK)^eOduUOh5dBn z{qRCa_ltKFKrXG< zbH6y!cxn9}5W{1PDp{2+-VE<_J*ckwt=fRXt6~dR9ACebPCO{~bYqg2qCmH`IJ9vgee)nT zOItv^z6S!Zg(#*07=9j(c0s}>B2f$h1=u*ylk0nNC%IE}`A*RjUbUgW-x_7yEY0of zJ=PD0-1&fcw?O?vVm0rnhRdZE0=oc7d(e822bvEP43H@B7FQ!1iJpB}Obu)BTadmN z!G?{UZrN-VoUP>5^SsQoVw%eN21_j%w(+Q29`|hwwfQvw-@GJW&AS z)D!?lBcD5ja`~$D>3$VV!Fw^ZnQqhy6ahu$aWHvXkK{NbrsR22hF6RlGILDD__7IQ z6{FKV<0g)tIiaEh0Xb!iHo0uVa8M_gj!UJ+Gvf6gyif!Y0m2PS4M_R0CE3|JHHAeu z#3T<&dEoamqTQoy<&+uX9)2rh)@D9zgUufjM0Uc>-h$q!a>0h#vk}jCT+4yAZ$)Lmv_D>aiQfqKl8kiomE( zL{aecs)lnA=#~ug6{aL$!%->?{}ZGD!$l~fiqFIWp)KA0&M0ps;%n<0-OHx#MIZY= zgHuRfGq?XLc8}F_0!(=`$8fajX@1xrmMx{5zEh)VFXXtzvto%+@<4z8D)y#on-&8n z0Sn)QKfilJjtRW+xp-EEIAeapUVx~D3jvX;B9>CZ5&^-Zs1&NDgWrku);dlrmre^& zTpeWlgwXX_{_se>gqzji4`cE1(U-BI-E&!ti)3MtCYJq&B(-{06ogc=U~SB-Qr z$7L->!78axf<=8V$Z>9LS8ch0^O0-2l-vqToZm0V4k`-3n8l|mFSG~bha!U#@TU+= zb%3cy!~%)k(5}I5+1xSicqM(fR*Vj-+8E~#GsZV>eZG6xdiYU){2t>Q^Me?Z*q)RI zY2I25QonQ&;yFtV+ai-5d`QFrlRscrIr4)zbjQaSM8-K)qvQX9w{q8i#P16kQY}(f zk<`xw2tacfRs+JmwK$3c>Cpw;0q%~;8)^q;&vfcf(I)k+) z51_g|&0pfLbeG=RA2m0>!X|0?6{^hfQ(`iG`?8#rh^jUNZAed5H9YoLrl#n-LLor~ z$~O$%rr*RoJg}91{YeZDK*V5XuP@7gcuhGS5hN3eNZ(@Tic;b< zPb+t}m6S3w6cHH+R`83l$)VU_rKfHsf;FrjWD!g&)b)X5kPxXGHLZ1eY~M4ozkxnM z6>(Yw_PuyX>eV5Weu^ZGVDKWn=+G^&VO3eGq{ev769vO;@TLfVG_3I`3_EU_uPS$= z7k(328pg0>Qfby-a@ed6*^Mk8lhs1BR>h*Q>B?C)^n`YY1Phn2F;3984u$~HR;tr# zgd3chNxKyUQO0B-9}@hS$EsbE1LtD3BHn|lLV&d4C=np{p;{k!zzlR56-TuQ^1=m5 z)tcl-1-QQp(#_)I2m0dI7><&kG|7Nf8eWVvp4ny?v#mzxNWDQkZAMp~Vgc}t* zNaw`Hm&(uz=->dMui+1ITBS(^IClq0Sp^L75x^;^B7?vL)+Ul35y>g+l|Q=ydx6a# z73r-9XZTXrxJ!LVrdnl{#^`80qy9e(42$=QkxkKAU@FQmQOL|t8uYTF>Z|Lo9Gpgb zY|<1+UN~)W>KFp`2-h$?0HD7yS6@Bi485vzDrdksx?zbw68nELxFFf>p11)Pqs=bW z=|c=D0WJzkK5J0{qZdijqEBEld^%Wm5r0gK>;{Fb6^X;(&Q=CNB02T_U5=rUG)aL4 z7qB>_K0Gj{;V#IBi<%v@fqMRYgluH0!LB|(KX6rd=@1gbpXcmFmcJ3fnkaoM zF5sVMEB+LcwLJd#NN+<1-s?z~-w3#)tqIa}ebTxDa)4DuV?i_aH+dayM|c1)7HTn0 zV=vQ{c(!Q_?5f1jlPy9dxw@bxIwnT3&~=H@L=Uj5f!7Bwc@63*Sp5?Yqk|_JaU7sm z!2duO6Q!h(HGZ6F$40Vp4F&??8`;AgNX-8}kfrok59u_rdytn4Zm30$SS=#isF#3a z9$7vB#bD4zyvAFhiPrkrX?JXO@oU`0uu5+9nmqh}J!{;LawH8%k_i4f#BC5kMCLN` zQV_GnVGSrJLN-bffEf!5;7`YE$8B&FUdP*fon+qT|9q1D1zH4EEkNeW@FWxz{gN#8 zD+E$E*bA6%0H%a;>^J!1!{%a=(N12l`_py^B-vV9TKTt{(~f!xU+T z{uYU;esR^N7Scfs>u(R?Uwy-OM=7zClqNY9ma^Nhh~7JdD0GA(gwR!~QYei{l`eUp zexi;FN4EgSxDF|5I06lyX0fy{D9!b`l-*NGa2NOiVlgif%Mg~t$n{W+YkNvl+Q#Cb zlHcVpwwx@QUhlwI=l{6<=i!Qrh8@O0@aY8)Si25R7?zBTxd-|sf;qkFkk-Wx|AMMV z1Jk91&=P+P%0+l4u4>)OsyD%iZ%UVDwhbbTzRuFZY5#k&+7J5yv=VM)?D-UoggqeL-Gn9gg+1kK^X=x-hkQ74uF7O4q=58iNN96)RZN)D5E&X zH0!8lVvJy6RAcY6#q_tPxYzpoVTz%byR zn!Qlpd}#UrDOxcyL;{QZN^6leQwL;UoTv!4geL$A4*58^3I%-iz{G+CDWdQCNh{n5 z{)E=Mx^Y)%^eg&zzH8zCaj&HVq~1tV0`4@@aB(6ftYlVCiu2lXYJji{ODz}Y-|*e| zI3o%0{Y)W@`t_GmcMOsgsh$(-nc%4u!*)gaQO`a$EKGeD*p}gEGIa%~xbNQuNn@x5nb@`c96|M&|B<>RKc>m|#`)ATvFqO|YZ!qDJE_ z2Z1d$K*qJ2pLLnAT203skTd)0zz4LqRDy%|8@?~|ooCbaL#2My*aUpC9z(I`p9D4n z9bXCz<5mc>83ACH4U|17X+_Bn&`*I{3W&CPybYM;dB_5(MG0hXU0~NJX@RKR#o4zC zcd^O2O$a|V&7ai8TjGryxS_$lslg||g$$ER9nkaOObtoRM@xG&uKhv zb<@07kD0#f!cPS|WPvf2p2nKF1}vD4tCc`EO+@%aI-_RLl5f-)x^!7eHW1uinSk>0 z9T92@T~1Nbs5nK*rB{=c1b!`DUd(6E;uIy7uOiQIB_Xh7lH^us#7bV*$A(?o=m%87 zun4@{h^Fk_hSU1=#T{}>pgGo>00gj+>gRiE8Zx88#?cQX@*0dWPt;!HP zP0$7Omr=E7SwA}j-39gSTnh4o>ub?lM#3>Bt6 zK+85l7wyVFyk50f=(CxA(T+qHII&V1qOjLk&HUc5zFB-1xh#ND29^=vBnsgUs+aRC z6O4ySsDcf^1){G~-gaRdK&%yzGRWq@5m*RmA%qU#CBO?YJeKSN=^7(6E%K3^J=3z2 zk7Nv?@nJWu^`}L#vq6ge%dqIm2q_X-+ofUyU1-mCX-7z_!`G+|sBa4Ny;&Nef;Dpx zu?MdqQp|i9@UG;d4q+Cn%6Z+CHJ)y7bN&nRyeuqlxPQ{?Wy0WOO@yC63{+$D{c z^;&Ey-Ytda&T6d1Br>}nb89#%uLw-&GY$>EVUdn)#;_phyT!mu+_qav3VgI%8o&c; znIXKPR5FCbzQ-k0|EP}Sf@D+h! zj|fCM)7ywK&mC%37Xw5_;--A*N zf^SSd7 zQc%{_ay%xPa$b^NqzT_C9w%mNr2h1LPmb@&m=!U)z2p1wqiz1M-EEwOyCw=(hA&Tn0p3FB^w~>;n0)bOH<&*S0%3lSqjMS z#u*uaItThFYLFPLFTf#C3{X>#WDmN0QHs=V;Oq;nYqT4fGPPEVxRl|~j`sG)ku@78 z`(ypHd;9K(7wM^gGj8}oS{^#2VyIDwwxFI#oQ9?awCf8gDFH)kwcy!UT9&m80fWz5GCnD42sVN9c=vKZF`*Il!L<%uz_6^%MQg>oa=)3@_b%UrNQSV3sj+Y_q{$#=N(4E92}A z0CxtHF9hVxY~7}YskV3-_8eeH21#`Up>$xSR;(pN?#8p2ov9*#>D$&0-VptKRto72 zlQqMfsrisnt%`>^KS;TpKc8)ahG(aP8$WKRoMmJrOgb;+F{jO#O-?_+U~o2BfB#ry zIc9Bwz`gxzIBj$u4*Nz*vUT7bfL}=|!_AIa&728%!JZ$CRu5pBy7v1StlRFS(mAXH z@=oC3H_}w0C+tx;$WUU$Qsn@f0O6R8YBkiPLKwk~#?R+hSF+t@EEQjt66xlNQcwE$ zGW4RF%UID}2Wu(x%6Ta_koCRvij#Jy3H^2P_CT@xfU^e;}3y_^h~hrch&&cJ6O@}P{KtmqH5 z^1v8F9zOCcQ4<1DiM(5++d|Lj6~_10L2eWDj!yxnKhRvI`UF~4sYV1&50;1GIG_cp zE`%osnisI27W@R*n7tF^$iVaCw`hKlCAn@24wL}OUs*x-4(^liBse$IL zxHNER38P+`B^pwF$82$K;Kp_G0#_DPTO8Grhk!ZqNf1Lp@*UzItY8tz^0j$|DMA-h zs4Me1Wj>2l*C-Lb`&RgKx_EPOcG*U8lg*vse+$u~k{xnxEBCj@mx~#fA;lSCv?i4J zn&Q266{E@&;XWp!hE6Ckeru@h$kDBFHtcOCgr&Y-(mk!2RITN6#&T{3?hckYRKVGW z-O(X>XfS_k&VgZ~wCRe*x@(ghdIj*6@oy#_f~_3J*R|D)s%sdoOGAAb^T9J1-d4Sg z{od}O1n(OxuJ3rP0I@Z{+wf)TIYQXp0B_4``TNoB6QnFk*ePdHzsqtOz4a;7@Sn^#GRR+rXHa-!nfD6neal1l+NBFP<3;elJ-u^i7b-mZ^i|BhAX~5!rGJwBX zsB!LnvcJ4l^J$HW=t8-?dg+{5nfPgb{bWQ-*!E#oZZPK*Vi<@d12k?Cn&E|;fn8?D zJ-LWaq`{};7A9${9_r@@xXM@@kF>FB{W9OYksl#!^V2DLtRB^B#=UWno}taw&WBIC zE<+DbwlbCu-JrifY@mzSfw+21-$;96+v9>7miGjep6ScXIScO`d_w?$VlR635{PD$ zJTczYvrq8mSOdH_J#(PhyiX=`G3c8uE0tyuY#pu#17nII1Y7OA*#L<~kL`6Vt*pT= zYhtK*h>#eeRUHxqUXIqFCwQkr22Hz|Tsmi-o}$14=O*Z`&Q@@aUa9z-@_x?BCYJud zYD_UauxS$<{OIC;h`O5(u8$3g>PuNxKJ?aXihWRSYBK{V800m^xX>_>gYHTZCYc$A`2W<_g8xW`Mbat2jG+ zNo4|FgCP;G;c4E-;^@H0%jy9YMzqrnQBa5E0baZ;rBKK1q(!As+baLU>vtAt9pzA`<`A?2e%z;gw`4f##!dp z*zv`7)InS0Fd1ebhVcj_8ai##SJx~+9h*-0R@^%C6=gCxknIJR$J^-!2U52g$b zbYDMvy~CaHIZTJ_Pvo4qPG0R|CX!f10`gk03{q(4C$a~)@vHBXQ@j}e{0s)j(!M+x ztF}%8v#F6gbp8`LN@ByK%b&YRiLVTl$ZOnyq*97d? z%rtuNGx<+s?tCoj(rgAEq?&R*0LJa)^U{RCqnG6&E;`#?8^QMP8oKI9Ip%=|g+R+R zXk1`)h%(3J&Bt-$Na$yA2Y9l94uJqRQs8@`6oH+umlMmURaPPF+PK4`DAHA&auwU# zCMn@9-TVbzyhX8b$^KIJwPSs*-AF@Tl%({f$C8vqlnX~lgb`F@Dr2h{VDhWH7-|Nz zj-;0RU>U$!l0n%sq+}YBqGZ#5Qj`=4=^?tV(4&)YrYcoQ3k*wD?ozNMaV|Lg;?)Q> z)d9MG7S2M3*BnQ_)xbwsT|nPvDUpGS)%;1xaH=F&tvCw^fN?Uq3G*jsj z@IAnhfRDxwzpSi!y1Tz`q&Gh;HlIzNZv(PN zepsap43w-^uH%!lfMdk0Sr5BUCc7`Dzv0dtf1e0%Y8)oK(#^eqnBYg(D7k^;HOdq>yzhG9wKlv#MW zsu8hg#SwtGK&=7N9P;Y*^;M#;uVb8YjIFPKD4{o3`1>O^s?#D2=sVa8S9EiLI%#ZhEo@5{=j}__61fB63BqM z$d2l1XD|z0a;Xt?^rA9N4h-9XLr_=Qf2vv2vt<54+J8|sNi#$TcK9m`Ra@etJUhE_*RLzdXc|V8#9fv~hOgD0K2{#G-Jf7vw+oEEH?t zc)pI~?>i8}`O-km5qSKVnkVcyp(@HY>a0u@}6a4|}M|54fZ9zOm&F|3u$R?(?Xj^fv*|I+0u6x)hOv{NO9(_eApfeNm&xN@uKQXyjCCa$d~04t^_N}m61Wxy9QxU94ZJHJ{)%sqYK&y zivDuz^DF7(OKK|p{kN)0cXQm`#8zkM<0Jh;u!Y283-LF&xnCXSw?41sl9SgK(YlpN z0&Qp8Ft&{_!o6LAuX!!RNog+|*55h|V0~c9_wr$909sNWPix)*j_x2{>B87;LtqN8 zu%9y7Pb{cPp1%;rE^{l!;tEW0D>Ix4R(3uP>K_r7K~*G<1w`=>^69Wu7DC*k&`lqE zdf>zWZ4$4Pa+Z-?DRil#(3=JS&?s+max4z3+qj0itrX$hL0a#S;+e*Y6?z&)o2~=} zBNnngjY)L9=@yfXW_8*to3&5)z;BgFF)Gyhvh}fLCa#x zFiRKh-`eBaHtw?UU#(v2r@7U!pR13m+tpe8-)cWKRCrbSH@{MOT-m17EBT5|Op!m5 z?~^ynRYIzqCri@n(mm2DX^fO4{vp09-o^jHzlsMi|E0=aZ@fT{R%rC6}{)6Rdx|AUiyqCuk2GIDC7An;_Ls--w^qT{QaoMB$-nxQ3Vf>m6@WyJF|pxBZi zk7Ym6RZ|g~|8}<}TmV4D*>PH`I>hLV&i1sRw_;i~7Izy!H%!-31ff5TLK{uUFz;Ag zoTv$D=9iq2_!4d2@y4AN+%XJK&=W&nSjUF$pzU?IntwcnJrLj0JTrm6hR#pYdh*+8 z-2~i#dyD-Ir^XVDh`)h;o1|q5+;@~Z86$f%3zuaGE#1pgG=7r##W_7i3TqKgv6Wg^jr*5*>vfy(R=Agp zpB}5Q;HUlUr$nB%?zSYV$>vb@9l=l%&7qtahM}xAzih3*YaKyFU=EERV0POu9n8kv zZT2+vQA;A9Mh~Ki+F*{4f7Ftq)|k!l1w9TnU(x=%Syva#h6lH32@d0hrI^hbN#jGz z28SO(PZj1n?>_>sW;MMrUhAnoXwu#uhPm!%Uc2>icB|p$x6v)^Te108utd7Fd^gFa zaeteS{p_ul1huQtxTXJeJn3ReV{ei>Vz#KpvQm4P-P!kA(BxIxI0FmjJ~}cUe^%fi zTrg6Z*~#})F)_cGPl^?SC#^EO`ggn~fiIxRGqqHK&qLgu{k@fr;jj9>d4~%#*`pKt zhF5gqj3u$zVnqq?WbQRiUM}sEnxx@UckxHzY4HVdzqm{sCB{PZ-Q`;2Dt9G1e{?=0 zk96*KHabh4-5lTZeH{OC>=On!mO9G#ZH_o$j{Q6PGyEs^TkZ3NZ|sBYkwUobE87#c zTW$0B5ZfSIr1fj-Q`Vi<1;S2ii8b2tt>vGC_=hdGSr%DJEiu|I?WdW6|Q z3QZKsGjLi0Joz=V>VOf^0KUx}W(n7Xk>)(~NyJPGBdA~&R#zDfn-7)|ip=+zb0-!k zD&}hEVMPcz=FImU3~6MtFf@C1eLQ_LPs>ufo7ZJM#^hZr&8x+V0^ZNNb0G*EE#FMZ zRMX9O#N;NA_(ZK1zM~eIvq+n(Wv{7{k+2NA)2-|;_bHtLoxea|)M9|K6wag`chO|o zKTm^YnQv_^1fy>>V;V2Lw1ahU3;nhOD%GHcXfKR5pBC-Wq=;4$SYet;oUxA)H9AMQ z!&mexu~jqM{Bienj89_B6>!t^M1gyZmhVK%tu5PUKoW80tXz*-;X};{_^OVHr5DU+ zEzAV-&tmPsY6I)FM0KXw-=4BWjl0pj)tP#>*1t8sy>bH+^QiEc#ipg1s_|D{W_GfD zmX?Sqdr<$afj(q^xyQ*q8{>M7de6p;-Ahl_FzQaK##fDh+Z@ad`!Vq{eK;FW$-Qol zqj)HWd$ZYj`4)^Y-WW#9{;7DqHrg^rpPT#Vpy$8ogE?%5-yy95W3rk9b~b2Ta1bwK z_CT7B9|XP^V>rCSN9erde^gk^tn=CXkuqmB5>AEe`;D$nS>(8al!IPL~>Sv$8u z&~Y~zKh2p3X5^lr$G~a)X^I7>3j79IJRf~NL3psBxK4W>u_tF*N`$api9n!ARm@xlSeNDYb^{G?U z9Mz&+QUc1b8uE~{Kdt4%p~*thRQfB)lB6V0(ObS8g_cQ^Dg9AgJV_^Am|;m&k|*k)Pab7IPoSK= zxFmf%Xc=@6uRus1*YfFT^fs3I6kC#nfH9UZd&5do{Qy%k^H^su9vh z(df~ZbRl^pbw8-3V8<=I1EWhWYjIX2D@nuAGM>gpSrU}wVfs~><8bfvp>*LfOS+O? zs^44JVv>d!ZFgazCYR85A9@>1ZrqSBrt~CB3O|TkW%x6&<=2vAc>wFJW%I;DC8@uD zVa_q!B)y;Bp?wc~n7$-cpnad4OE4fD)^t=$kqcSd9lao;l6uq6htYWfeG+d;=JRR9 zEqK)AJW}>s@Qk^ve;Rua8*MMzd>claoTJm;dx_0tw*EPLJbq8kBIosNUNUvsh2D&o z(XxIRW-h%Yx(u_EMicQWl;oazi<_pRhon?|jwhx}Dd{QrjCZgFoNee7$;q^3H@hN9 zzaqXGbDiv^pKk^&sfT_g+v0_!MB05s!>dZ5g~RZ$@l>`2^Pl9QDlgt(QXIwI&3^7q zXYsH?axCSJU|n=Gx`<;F5ku$iLYt(n^wh2Ftw+<4TP-Pa7yZsNtJqkhjOORjJUNms z`dL@N7fM01aJ|{6NIY~>7@ZsemP!iMn_q|KO0rx3-F}`;N(c=-f{99UvEN&qP+XFo zRDov_k{xs+6MvKJ^zMGf$Tt15?Bi^%t@>w3M08=Hch2Ae(lyYKe#itzlFGUxCVG@4 zMgP3{Ha0ghZAM>0vP3f<$J~fyJFa1Lg5Kob-t1v``Who@W)hrGGY zuq25|z5)u|hROHk)5AM0$!fYU50_=o`?oDwFdALhpk-6uJ`3wJo+c$(lF^Na z-nm{&64QNgbfm(Pq4>J%!@v88J|D5<04HN`#%h>(FBFSpUyOc()exch(pCQwAA@ew zebMyLF)b5$Wp86K<2F$=6_gwy=%^l1sB7gO|0Ldm+v zf=i55!QX7@LZc69Hgq0B)@@okE^^VN?Q9C2Ej!DSF$9M`gll$Uwvv2yazirX4YBo+ z(elB#6kK7Y0Xy`^wX|G60+X(hYXf8*TNa6Q0!s*O6uJtp79V7KWIZ-#iH4=JfrzXh z;$tnWBZ0=;h4H5Qcq-qd4~C<^9%9R~nf@HYmgQR3=Z>4TL?wNVer21~qW5nUF3^=wK<6X^ZKl)2z2e_`Lf3qDGbLcA3yRh zz=Pk2vD4VyQ1!Oqf0}?-iMiriE)PnOs=4=jQubb`sXcM%%YY1lE+v9h`E|7Vs3l8H zH#NO`w_Bj;>G~Q}bf#_a6L%c5z|uHqs)=l~R{Jxp4GsDYwj|oe7}u6(pvs*<}d7Pz`T zBB;Qrd?DqsKU=5@e*}I%Z9a#01+?e;82J-=A73@@PIDA&+gII2cRNZg39!5>&qHC4 zGVj1t1e{j#&Us6!dWZR#*I&=CRigGkmr2oeNSgzHV?Ys{ba-NRnM zD0Y_}x8dfB(dY?$7P!CY&D)rF?0(ajVR|dykF{;|VniOJ?Q?s;U(Xw#?_dKE6xQU! z(`d5TAkVsmb$6HXB?e5{%lZR_5DEY+bFFVFQtMp>?1&!t%A{DNBo`!7|7arG2CQ z({QP1-PHf6FQ^CMP8qJoDL*S`mD`n-O34T%Uj9M;m%LwIE{~EEq~D~Mq(jm*(l{wa z{7ZaYyhmIoP8KsoNqAQ{A#4(62zi2y|B!!(-^SPTefd!C3po8##Uy_eox1@_qu^IcUb-l33CHcc>CX{!Nm~19VJzzJ}qAHvkf+0cLbwjJ9q>P?YGgzGWOR>mJ+6(_$_*?xna;8lKdJ?+Xt;A z*{`-dRE%BKue8iBWq)P*;10ZSzeMkjV3&*Z0PGzD945`7d4RS*>@nFr1CVw~7Ybh<(eql02|q zuytQenO;kV;9Jq+9>Fx9<#b^e`@W2ln4;}liX%`kq9#h;uImg-C?*XWMUro^-fh=V zT{T)nU2n(yru!N}&7i+0X(_nb!j^TgrF;u;AcC%w&8O|Kodn-J`f45eoJ+A=b#Q6a?7N@eOs-j1Hde75IzKzXPrQX57aJ_?RG--&gTTa!V?Z$K1 zb&`C{MvkYepqZfS$@Jgtde@Wa^-<_L**CG}p`q-i6KHfP8{+tuUq>cW|D)iS55`;K z(ZN`~18gcxT^xfiFcj}*4Ct$%Yc}b_8BMFfw4jue3kI>`8^xNngp6gn*vOV`Loq8O zTD~mDpECN&m}+J$XOOEBQ=RM^M%O&do@c0juao<9eY=!?xCiq*gtmZ36<>+o=M&SI zUOrfF_Z(~>+_adwr|R!?kWOuwr(Zpg*1?Pxd;{pYTiDyDqxUyB%3n)Bd!pBbN<75v(x8X1OYP|j+QiMv{QBbvj#yg4;&x&=} zgbe;T|Ec_*?30^E%E{72=@F?({8)TNJSNtQ#ljoH)53oH@Afb3Z`+@>-*3O&ev^Hz zlqPJoFR)LykF@u(r`o&N72A)tk6?#AZaZq*ZQE>HZmYA66Jq$+6sp3!K7ID0AQW>V?Yg4u1S^@uy=GDS9Uj0t}Ks~FTRu8K?)D6PtYIBoXqmESv zsqp)%4&@K!3*{~48Re965Rthx$~GHp<&*V3t!p#NNLw$uIq0J8 z2BjC^sX=W7y7PW5 zvH3^ihxS46|1l^XUg+1Y7q?D0|3%PM{|=&j9@NjLt^KsV0sez#vr#(}UI@DE%^=Df zK^?trP}*mOeJqD%2a=$C6pMO9wG#8X))F6)$s5Usj zi*GU-b)d`%q6{`D?K31y45Ccvd=>X+5ank^p)ViQ9L?_s{dhKrV!np

dA}wK*O{ z*&Rf=F^ICMIYjjG9p_A&9I#K}~xHQMv_DOexu+J9|(Yr89*K z5^LPMrpRjVhr5Eknb4;* zA+Iwbwll%enb7($&28Ey_oX?~cFKt$N~^BX-o`X7P$6L|&D>V=UTu_>oe5Lh3C)J4 zUC~Z42&Tf^LCxu645ou}WbI7hJ`18e7eqN@+{)~y85YX2D<~k$?Mx`@Oo(=gUQ0B!`gP-6Dw_#U|IcuJ>IpxE^xdDSRv(6SDZvUAMY6xSCuwuCcB`u1r@q zm(}@eGtv)UcRuAj;oRrE!MRH8FT8?;r~+q$b23t%a-9iIx07>x=lH;J&hd!jZU;Fw ziWQ=Y1gKiaSjRv|h9kydvHxQKRE)O2ZhzW-pZy?A;b!|ndzF2Zy|2BeJ=(6p<^P%O zb=y<66SjRwVpwIHW1D0fV(Vq|*qWWzKatPyw)I)-{YVqN$-34$ABOI5Yk{?g6-nyI zn0nvxg5@F0ot9fHexyy!u#B?wv7}fc5xx9DyM&B~NBO74YT=BqLa2wM?hWl8ZKrmv zwp6Q;dT3*{L0Xp9U9+pdtN%u5H+me^%`}qIz=6-<~6GcYKZce^0jaqf}a0U z9#rm7Zbr;wp)y@e6SgQLl|D+U5~axUf8-D4m*mIfBf@z8H+dH#qRWI>xmF%450o?b zlX5rN24CXm(i=jt^iS!ObWqwR{3NZB=1bG0P>B~Wix=^ZI{ss<(L6LG2T8qkH9$G9 z7e*M|f)gwN+JX9~T)|WvI#9}tHtou(P|7GnG75oq%gO_*vxOl+{pgzX&@?A#h?IX5NPGV^{y6@gRY8Wl%W~D0HK2;vulA6(%swy z>#I>{4lt|iA|b}O3>a$wZo?s3bwXE0xnhXXMw<>di!!fj?p*G>YV^NaGaarP1a_D_Gz0k5H2}E>?kD#ITj?Dsm7NJ;i~zPlF-#pG2QdK_cJHAX zIPf0FA?M({9bVTxhGb*|xTGFulxAlO{J2ry@;f8swhNbPTj|2v4HZx9{dV7k$9`$avBYuZ_l*VT%S%*g3By+MgK_>+6Y z=%@X%WJ-HqOK5(_?5=HT@%@4*j?NVB7lYEiNO*mR?YM0C4V~L?7Z|1E^5Z|&+4Bn0 zdqIsJ52ENs<`q}X3~CeGnZkX?rl8|h*Mz{-zk)Kka})hdcU&6$Ge*Dd>zRKdh~f^S z@SQ2#H#()cgJ9s^3TkvLh_c?m^h2}Yad4jq+uzKr`gh;k{2 zV$KI3QBh8XnxsPhTcb?}N~J-`hF`E6H5oOv{9%*Q+)lb9sL{M2%D^B>Vi2W!XA1YA z@mAY!U)n}!HV5QtqnHG1XM$>W+b$`@&JsSr8bIYuhU=paeu zAc{oCp4Sq4|JC^iUSEnGFXn#^YV!^Kgo~Ts4f^5jAj)e&lvjf&rmEIHUjELYHl~lK zLz`VhFKF)OmY}9L22o54yTirT1hrWeM42B%nHxl@)dkKK(^VDJXj%|uSP(_u@2}`b z-{`NPWCUH+)1BYxRr5(fjpBkRHfniMORW5@^AFq`L6o4k#l0BRMi;0ZN6I}wYtCwk z-kXD(ZVRGpq=RR*Ud;_`eRS+&c4xx$&V-4b3Bx)Qayk>TIunviLUWtqm)N;UWM@J| zXF_;qf-bL1GTK!lUSFkGP|g@LVYX?8-o#J7g2X7LL6p9oDcnOrlVDZEjP8`S(pyJ7s4OWn&O!Z4gD@ z>Ps>@+-!JI8(phoZJJxPaNc~>_RBb3&+FI`_ogvF?QI%66BcwP%zIHo7U>pcjxxbI%x(Hbd#NtPRa3u}@O1&&RQWriKC z-YjG~G9AqbJtOb`b9uhZNu$I^?Qh6ms^Rv3!mK_ZcEMS1H`=cetoC^*v>a|PP@MK2 z_E0-#`&RtHcENVm_K58s+b-KC+cMM%jI|B4Wr+Qh)56QP80iI@)%u(DbKxU#iE`Qc zhV^OdN$Y-LkTOL6QsFy#Gk?OmTJB+OuufJ!u$Ef$tO@c>|F5_Aj+3Iw z-p1=zhpOr@O;kYAFfc=yfzAODM1qJjWJbw!)O8UttN}yI^oS%uZ1tjI7EvU*E=Ck} z_q!$(bHw=F)itauhFwL`_qn0Fx@-3L*YEwj{4vFS`rJ^rZrywCt#h7ZUj{FM!M07V z&c<)#N8@X4spAggpmVw7Psa1=_r@dYc1<<58rP{a>_-^|;~XPtOm)0%9A^wOdK$1L z)_>6dSASK1O5dg5qOa4J>!*`X?RV>~_BPu`w#Bw>`UK}XJq!c$PFns~$7R}A+PltH z^?dDFb+|JKtDW7>TWsy-zL`Dy%FT|-b)|7l6-PBxjE@S0Z-`o&f^in~H-wm&gdc#n zG_mMCJoapsV41)tvPw;waFw&NGO;UxE>u?V#Lfybp=6f131`A$i_p>prm1WUROt~v zk;`+%5aur`oVFs!l1PR8AuxVp{(-)PAgQJACKrqQFi$|mrwgbSw^b8h zT2vN*i!w;Ia>)d_S2W0+M#)VKLCI;ve1M04GDNr)vmGu3B)U7lsHk9ANf`tz0@!nY^y7(_lj%* z1PzX1nB)@TGzQ&fHptCnsy@fN7*f?BIhUGGU*-FziA`t<0`DXGjJdXycB~+CE^W~H#s%Z5Y-m9K$#P9>LL@;j(xVN*l8T=V=^sGp*%PsVwtQzlOpem$||~u z@9EL`k#rIqKmJIVye+aJA}<)L5qv65q-s#{#=I(I5Z@h@yl`UKuz><_RN;a3&UrrHEce@)AqMuEfnO0!B6_{uRdKj%a3!b`Q9k11A5i%;5+Pb}qm>6TWT8MRbPjm}V``j3gowFD zkW;OQ^6w7F0cj#cQ+AapCc^-8ViQY#>hI?P>hgOD)QKi;90t0S2#>PhBJ9Zj0P zRcLLarI0Dp$%=>%HkC!o11+-{X*^~EQ$_PQa4a`O9ae#GV6dpCLKB_T0U1l5dRXs) z8z`ChD!++hOr)2{WGZLMA3GrT$xLNKk%0m*?JoFAS!G?kK$OBXlDsfDR{m4URkCV` zN|~}z0Fg!u<|lN+Wgm#sfV&j6R{fp*FXUN!SG#IEgiY!J+Y_++z6mDN7r^>^rfsZk z1T)Aszp(qR1y#~A`19wReIsEUez7{iH`sTy&r$PT%_lW4*X*m=QFD|1k8p6gpe73+ zxp6f?)mKvs_uV7jFTL-0pYiVT-s#=w&3n)E&ht)IL*BTzUOmOz&8xzs{E<=OfQc zp1q!Jo=u*Wp7T6u&kT@_4EOZ$xZQ`{|8T$Q{vY=PU|qU8@4gKFF>~FM+)?*HcNe$f z`r38S^}Kqk>k-#IuIugVU01lybtPR>UB|hGxq7-Z;|Ca$zoP!x*k|kjEy-%*0`ee? z$!Ej2Bxv+EYV=?9FZ6fxXY~icEO4z}(9hNv=`H%P@aXEHJGJk%PqbIGecBHDZQ4!R zYAt_(meXcyiwfd9e0!P*{Q~k_wg2V6V>+skQ+y7yIlYd8tWrCeA6fIa<5gr`z zi~?CzF7(M%GL_FH1Bg?va?7AfZQ045e^oAi_zi@889~gbtY78FU_bz2V;l-6#FzD! zrU=;d(jZk!#DrycmCnCPlnf0B(LD0GL@F+fy(?>1#|WVqqEJP0DeUjnQ35e_RIz!e z$~98v0@WlZ#tf=4fxs0p2_i^PujkMRQGr?C*dK?||^-0!8CtA)IqGDj#qn9gC*}IPr<7e5r3~O2N1`g|N@L zoH|345(($SAw=f`U#vP!K<4-oJHVEvObWv$i5*^m{3K*5kf%Bz!z2<*fU(+-VZH$;V1H_42rctMA3P#lt~7j%W%Weq*6PHz8xb+RBX*iANHM)#1nfZ@d*zwu^GWpe$v6Zv_@g z08zx!pdR(dj*^AI8X*?NK)A*k$m}`P!35bl?#KsI2Vno zM~k{r82mAq5=8?SOOZGaVi=1Bgq=TLE%0W>aWt36iVso@L4*#UXB19H9anO0@VCVz=m@O+6$Z-+@H9a0eA`>I|<+7>( z%D@3gBEt`duo&S~FM+7nI3(i^1C>Zf zzp#d?219Uwo-9LTst|&A8{)R-(rLdcl@}8smzQL~zZ}O|N`!pS0pVL#5D*|fZy<~i zE793fq`W35w~Ic^gOWPKNLN#7ZjOvhWk5bHQ+FUeWHBs$9VnG^Y4W-iQEre(1UKqO zoa}(ITO#39B!GBk$&4_NM^)iez65QSLWm`du&{WvHt|V zh{L0ZK;n-QZe!ij6vO9;AU=sCg6!in^q5!Q5v5 zJR^TdW-W2uIHp7wSu6s_;pQVckYp;2*tQ^#7q(fIDz#cKOD>aAs(J^+X+=nf zZ2@`OB7^lqjumq=L--+SW%cA{OSU9QbwH9GkY<ORV7GI->YxeH{&b%CHfM5zTOPp&aa#Y@iBd`bGvi1I#L~~_EKHy8R}ehxjNbL zsAHSsM#mb*a>p5txsJ(>k&dB`d@qL!TkkLI@7kZYKMJnp8|`brw|oZ7*(cja+K1YE z*pJHs~DHrY1PHq_S3=28!lqhrVqO+Eg zotE?tx*1~hg>Usv0>SFge4ubq{Ei< z2s-LJR8wbxYc1*imUM4Rx`!p*-IDG~H=~-)oh@)Dx*c$>+XA~RX@l;E7${-B`cCgl zdws8WCP!%R@AVP2KUPbU|5(!B(&q1x^HaJGm(B+*zrI5~Kj1MP`vV@+i?kX44?JfP zdDfEtt0nz3y@VBUABs5YAq<*V%~2r;3TM(oKY-3=s<(wph})ThcdM(vK0WI{dcUz}Ti|7u^qH3Q zX_hoX*t23%7I?8Gy~vWDV@aP%$NY%VGs^ro=i}WN*`UFdQ zEIq(V8%_U(OCWCfHDXDRu%w65vwqSS#CFetCRhzlf{#E_M>VazVyARuYfAp3O~o8dN=J?csafbCGc+TYHhie(WYo|I5>L1e)f^`dFP)% zD8CB2*L>$hrynX_H~X*nwDPKbul;WO)zIxS_9^zbeSmMXv%j;GQ*nIb_y`|oo&^2t zZH}uQmphgfm!0i&`PPAhX$L-cExztrV?lnIXOU-$=Qt?B$9PbB&ZS9i(m}z-r`$2K*)HYM;{|*0<`{L%B56AJxy)H^->|qy9;~Q@zI4 z)uyV4)Q{8y>J#vH-K?&KYWq9&RP{tPst!`SsW$R0Xxv^V`+WcMec*f1w>h&2`U9Q6 z+NdRW(F4Q?lH2GZcG*n3s>ZPX)i0mgYDv$e7j7_u^sx;_=hp8#+3M8aox3QFD5#l5 zXXTXdtiU%`;7cp;g%$W*C?@m7<^P{v)7i41fWz{T4YZM)Y{pR_a_9f=ZO8u+;te=> z`T505Z?{A0HoMV?Z4vuFDEj&3J#l$iTppztJB$(ZQHRlq^eArL>8D>h3?IUH;4+4e zbOLtKW1U7n(7W>N_lV0{aaqGJ^h+likQC4YacLEoW^rj0mm%WPi(lw>n$cZVek=yI zb|?SlfB(h)(uj`IKH$K6{O_~;@1OYJTlwD`_}{Df-?jX2f&X10y8Kdpslylj>WBLe zEAT0OR>vs%n0~=72aBFNYP-NXzB<<%G-(*)$V>D=T)I4G{rMT{b{VzBjdu>H+h_gl z2`lh8oeJSPd|Ifk<6$eXr+DLCwZ;8+)zP0^cnCX+ja!2BEVt3Q?hdOcju5L0ySW&> zJ2(p_Qq?ey(yD>$tiZKaplAV#@84Zlx4{A`>#e|5)bJR!wAo{HCM#(R{*OQa3vmft z?z7dAiuVnAbXEFdOZr0kqz8lV5A!K8F5QgJ&GR z-e>gd9I#0FX@9_EIQ8R_A7=S=s3qNCN!R-j^Rd+5mfvb?Y;{KIH_Uxxw?$x(B|Shv zz(0{gyk{wX$uDLx=}sZb72nIgExzTxY2+^7AabDQXIM~dAwQ`tHJ8-PsA+)B#H-$| z-b=hQyn{WzdS26edhYdH1?z>{(n{$fC@19xwXqp|SBlN0)h>@eVO48V! zBfyQT9*-D%VBb_n(Tj>;&Q&=XE@UTP1$J9COs5pVoU4ZDom)W;9F!!nELZ|LwYYBH zV6C^Lr9FHPs2xk01LIjR9vPXaxMwO7=d-LR_kG5nuN*FZy%3f4mrZ$p3!|WYQx^5p zU~o=eBop0tf>8Ga#2oSH*4pz~&3-4o$zqipl?_(6dx5ye`&Vm>tr9eO#PzLL&Aq#tP zJeY>3(rMd~Q5{Gx+Q_0?jQS$-G_00Md`lua61#OOPRk{662T?zD35R&3MUirfV?R2Z&lYGWrp>EW} z2!#>w<6a2s$`RS9$@ihSPO=&P{j3?POufI0_1^t*#Eh6VI8S;B2I5Z{1Mybhg}$l2 zL6~G-t+@v-T(dw9s=_2|w|Av?fj5lcb_d~oRq)L5i~yhKLH8fQmk>A zSJrhb9HTxn9tS_k?~SntYxge%b-D?5QYY$tU?25@c87M6Hd(86{^)!ODcPH)G^C3%t7pL*>~74v(K^*gTdKrI7(e18HnFg?*{|%0yP9G(2vN2WF1M9 zk))IIDSfKHF_@fNj7TfEBZ#eUJG(fw8Br73meIYF5RssbQRLRq#_5b_zilb~ z7<{~-`eiiqwe%o{7|{9dCGKQE?NZ3eAv+amh&N{BVIE#$@SX3-cL+ZNC@o4~8vHlH561p}&W9^D8s zf3bQ3{i$ew zecMcyC@!AK2$W~g2kJP1@^orkfm>~86Fj2sYDp5*O~9;=+^Y>L<|7hPD%d*3pG=^jbsPM4sq8msc`@ z-oJrYJ)Wb}dT?|c-NS=Iv{&BH-8#Av1>lCpAb|)jGE$QZ2#ZQn zOUEFt04if~Y3k@Dj2XD0ZB+5~69+rnSa1%+WEjJ-k+h}>(|v+IfhZBqHXfUX`WQ)Z zLtBgs9=IAWTti!wCl<{{uKKn}@#C@JLT(GwWE*=lA^O}9MqJz$r2WxGhzCR^xAL*% zr#AzoqZ$o^p4Mda$L}Ie&F1dB5lyrYNV2uYHWs0yiLThrn3LOv(X}T5t#4xyYieoq zWq9>Rt+U$JyLjY8T#ljF zufd>gY-4dbnu?faFdTZ)g`3f;hPEEXuO_hI3+E_vl&2M#g}z&}oAgnhu31?#ug0%^ zZ+i(OOK<6Yy*q8UcrWwL_SWNz(|%v8XS4E8?K#gm+GO~9_H=*k{tI}7&vH+2_jY~f zde(QjZ=35Da>#X_YqG1K{ZiO+zM!3l??-1F6OCiETW#n0-p~($-1iyV5&dTUq;sLL z{Gh%-CaJfn%hZW#Z}L}iz1Gj(;QZb;8OH;+InQ@CI|n#^1kcf3_!cxB#K*tdU$ft1 zpTUe<*cY184BO0(L{kq;S+)ulUqyc=Wo|B?Q)8jo*F}P1@AQ=tVD^sq6HE;fRkx{^j#4cKSjO)TACO z@8PkXSQpfYOw}MVpsEh1PqCuaq4d%iyV~jUFqY+Ma-Cw4c{D^H#lTZJO}DO;!mY7z zB0?d;Potn~sLE%>-ixqA-61nC1%G)dszI^21&jD#`dAmVh}6h?XJNF+0(MteFt29U z?sRHb7S^GMNL<7c#Gns%W%vIPt?7my{FqMc4K+!|X`rjGfCfPR%(|l+oimX|1$c@s z>y5j3STwvyPC?y&qN7o_ilB@Adg8ZRWD_pNRHIFiiMx?N0MZ|NpkuG6lY27x0kmHa zR62~_(vz*f?Xvq0^khr!=`vctI=mT(3fH$6GR~lraM4yt!TD3s&&tEJ_fhz5F}=AL z>wto+ki}|3u=Qh(K@B(3iN_!wLn(aQzDD%kLU{)W)}t!h>14D-8!z)di%2-yQBnX? zxk?b66_x19BeGS!dmDqhHVbhWsg=#~XmX=|v}PAs>64QT`c6ZIo@a8ygGCA<;hic>&7z#q@UEfvW7KT0h)sf4qS8Ou1jH_jmB-(wBR)VS6Gy z#QIU4C_DMZUU(Yn1d%F^XsyeuCkU128>aH?01ezcU%;pRm!J!ay)m!X^S=nXaJA<& zPuyd;KXN~a585er!tHbY-SwF38rSKr(FnTmrSX(OjWb~=-V1Ro|BA2D=jtcv{j?wO z75WbCLd3A_k7LIdop+X0o?jhr@BlJ%cp#~R2qd-2KF2=7?zA1W{n56{w$K)_dDKtT zht>69aywS-LOv(^$aQ218Ap05-yrCM-&S9kN*~t;{8**(&NYD6zfFVJQUMZfvvkZVd%|f{=$S} z{$#9Ih4J+A5g6=+ar6qP0LXX(^prdJ{;lUg{2)^BCkUd7^+!Swx; z5cl`BO?oXPdeD`F*i#fy;8?6z)WE`V)X@ddMj_Fiu17cy+-W!ZD21L>=*oZnXg>e7 zOYu0wJ1BIfnVqbHPV`=`c@{)Cj)f!9{Dwje&#@xHb9m`G1UW=`9=c#0dakj+<6Y5L zFu9<^Ty$CkqInfWOpXLJCWz|vHI7BGqTYY zck?Hy@;cvJ#Oow<=`cQY6ne1E7%qcybY>cPecNTl?Fi@Bc4=|%6sRGWP|BiuTwF{{ zMrt`7GYR#yT}0O*9+k80LSE|Cr?RKgb^#K#^!}M#DLo%=2GfZCKqnw7n%Z_A%|ols zyJ~hJQtp1Grrma=W{&!@?bVv$HFhU)wy9I~dG^8DaBU80u}#N*X{Wc{JJ)`h?sv?# zU4m%bcPjruCOl89WB_@|y~}-t^KJXR@K+h3+1w7_ z8(O>TUDs~sDfZu7S2%Y#$22C@R7`W4;E zub34B1L|%|=G`D0<2)WV~KxWW$mE5=G_OXjb_~njejy zuarM;K(rv0TtP3Hjv{)|1*aH8Y12+aqbp|`1Ia~n4H`iZUIj(dpQagZdVjOgU&W5P zp5NSAbS1lmC9qp&l(Rfl?N;=iC`)!~fv0uqJ zx)#5xnEw{DtJ+9EKwEG`FlRO{QL3?2h(#xcke`Nmk$!rAD=V^A*8k-Wb~oMW@*o=3 zO|Ew=z8a2aKV;o`v&2|LPE6E4qcmOlNrEiMnU@RX0PzHWj1^YqP`O#W3V&mwf9kA6c1x*|tV zaqmP(zCrc*XnLEdjz#R#mFr|;PAddnpd%JA)v7pp?hpk1dwm9a-9u;crmmzv2T}Ef zQodz8GJh@;ou(tPUnFSlRft2Ud;y(e3w>{)F-U8b4V!YUQP(8`{74;XZ3+jIFiOq_ zgY=vAdJj@VU-VfFB^?>k;_JYqlJ zp22y^b&ht&IgZ7SDUMM%ChEaIXSB`Gr0&vYvG1bx?f5dfNIStj1tx++-96o!>j&50T(7#G zbnSF))~C7FXcxLJbmd&Lv?sMS`b!8N9(470)fm6npRhe`y9HlL-!}fL`E9#xKiW<= z_84~=MZ3#bVVq?wFiu8Lzy`zH-LUD~>@VAvGoIXsI$urt2p$^PSC{R^OTI+RcTT#_ zO=D(}l?0HVG4}NUHNCqP{QzE-amU&eL*+sQ`)``OYGn)v<135%@iIf}wO^2 zNUbR~C|{;6S|%Zrfaphwl*t)NBq%0b1Cgu`CT9qd_TDCH^HA(c{&yv(67XKf;Q8eb zCQE)nm>Hx4QpwBFbjX089QS0Z$n~v2%vYx5Ls5aeEM)Q((18xwUKEHqpO$<+D_CWu zmAmN`K%^^Jg}dKhWjUtXlyuGX=gZ_gb4Y>~OZE*(tAa@nBr0~;PKO}T8o1dZ=d@|q5a^lqxCy|IIg^z<-gq%SR) zV@nfy$z_8fc`?T;vOjXkKr-mZ!;dIm$tISk-D9kPM*!J$G?9RRcSi80!r8XG29&%~ zLG%L83qROV2;Mp7_l6Y$6jP~m9uBXAOHj$zm#noSC8uX|I#qVc+RGloSqXq?JqRzL zc;@$_l!}4bAdyhXPg}{a5|HVpuG}M$vKuOyBN5YowB*uhBGUiC9CV8Gtu;L@iS$b{ zJuMeY7t>VPt&&_V>M{}Kkhm`sk@L3cL`ja4naWO-9o;IWznVEoD0i`P;Q?eq{bWsL zUq|UrYC1B?6|2H)(U5t{o{cBSwwP0f^g64UL{1YjaAeDu{1}gsP+4_Sq|;Evn{~33 zDZ3 z6X`9gG67YVTXOfyXG%_A@b~-wcK5qhW;0#+noCI8aqsI6NQEn(@Z^&|LZ;uIlK)lY zikJQLl%GYLP320`B$!jaBAtjU{OP1$n29KNiegM;mITUPb2(JaTujTKrZXk-EK?&= zu9vyWE^%Y5faHFNmi^yKUgL1>Ecw5|nVQYFrX$-zaodrxWikrE{Ro*dqP#7I$|@w0 zKE)OOWv|m0Khcjy;FXf=nDRGSSJ_jnq%W4$MbcFiT;Ufcl{(W4Ou1Xgm^_aq$Kzm` zYwQ9c0)0APqT}l#Di4&|zRs31WwI~ja0lcwfmAHTLpmS>tcbF;0?BiJJLQ%NNFXKy3oVtW>5v|l?8 zI-Ykt%=fPa>|c|PX^zo(N28;cL%09i_Lco_u)%%OzSF)L9#$9HPqVke&M0WFwRaK> zGrGZ|xHx}C{FZ|6Y~Nzv6m7O|ly9i7r+y6E(`v8j_tpHZ=2hE+HBV{d^mNT`{Xoqv zw!2tBmzvYDubo&EsTt_JRR2Rwmm1VKaPkl+q|1fEVTM`?eB=_GMvRf zWhAuDw>)p~h%UD|f3N+AQO|nL@SLh$1cl+I<~5uNg91L`axlgs5288}uVEXH*4_)BX$mi9eXq@<0Q3;BN~ z_Fz##nUYY}2!@vmDkB}3J*J3}#UibU_1`P=lohe^=CX{4$f>BQj40Q$Tv!%OwL#@e zS}T0@!`Od@q5(hRmh%1yhEgCd45eZr5w^6o30vq)Ae;#!*i2MbpM*M<$ROmZ2teA} zlnZ5ZK{$>_bE;p+a5KlqhDiKrR#tEwci04$nm+$$SKU_{b!gAi((NM{SXGgh)OF z42(=72E`ysHp!|&m~7*jls`hG+MY9!^(7h94z)9q6I(=Rz@QHnh=|w+B#;1gTomrk zY1J;X`Ga8JNq{+)Oe-VuFhG%$mw7x;nzkztSm$zeyR6GfVg*xVPPIfHrwpPdQ@bte zsbzha)QDWB%mSIoJiX{)MaV}T5V`X(4`E1Ns(}@dA+wd0Yr!GT)a_6a>yK3q{_crz zROK%Jcqwv$Xcmkl2^lm9lSVq4O$RbDe_R9&YHf;zGilHQB%*PZYpShH5s>d7@FeI% z)o!eXtxfDL0v|{?m{xJbz^Ow)ffNXUI5ZZA6(JYOMZy>f!C*Q>xZcu=!?#Q*j%^+I zYkv_kaWKgzLy1HxLcSMBAQwg9{t$R0zY~Z*8pZ(6!ik<75(xb3;}JiO2D0QUAbGSX z7D{JQ@Z?V9MAY2YrcgW$0?kAw93}q{*qpj1d@O*06N`}KS}lpBpi=}r#cc02gu_B z34)6vjaeN`!4Gf*)TtS8HO5lOP?GExLb+rHL8H;(X|i1)jFTZ40GD)~4% zh)*IR1iS(PcrHQsNgv7$##5k9P6k0|!sll|;5cGDPw^x)LZW#!n$ z<&tcmh2!O9I;rs22Jc^-F%V;3i$p{O6^4ZIdWCZ6(l${S zR=g;pgvE1NA(RNl{Qwc~o$oV&fWs^j01sAthPba-Je&a~bvhB^%MK)> ziBL2h&Zc9;BP);kqj-yP4yCLVNE9quVEc*&!-T6Wn0jJ}3LlC^0tr9ir|*D3%8F?3 zSgXkuGFuocG-h#soX9CWlEuUq1igEPOc(8mWJAGV2un&zzQtfm!8;v~MMKKxqL>I{ zT}uQ)pv&1P5fJF*1IaWNG-bbp5||t?ZDmvB$`TStM)7jR(h5Ai*oza1q@uZiKN3u} zN+6g^WHImI<(7Hy6hNemsh6B55JcunhcTgoy-mIWAXX1%(~*2E5hZe335VHcDjCIe zDMw-XD_*KbmJ>(?FheAw0p)U0c{mUYf&3P6--JTf6h^m$D-Go4A^;3##!Mg@4rT*b zgye7n4_XYNP$IENUV*3lAOk|t5atjt^Q1(%jMgX;#F`sAUQ~@K*q^{rZ6u>yBSr84 z!kHjO!hjMG493_ToIF_qm~RjXB@p)alt3y6wwp8tcSe+&4<@65L^9z=EQ5MkQ7ng! z2#2yEa#aa|7K^%KDY8@|!6-PUp?{_o9 z&z~W#5(4V76ljdX#L058gWS?N?rBT}K>4(lTw+B!9=4J-Ql@nL+9#sOLv&NgSH5!j zPE#JY^;I6~h5Z}xfH$?T^uFB7zDRB+^!S3&iEb$v1L)fYxOq*YUlicxHIDwwE@P>u z9hW#w;xZv(@eflmy;pgnsX;5?w*tfIwXDQOx~1L7*5P=wx`2UJU;u5p0`h(6;w#`7 z+LIPp>L|Jg7iz3Tt6NqYo#|Ql)5UJhtI}mFjV%2SeuTlvk1LU7=#}{Yzv!?lQP`KX zYqp+v`Xx;0uNAaHSTS@72ifr zUX5P-FP^)z=!L2=yMuorS#K@kD*DtKJkFK0&W1}H4dW8VLSEg8V0f+uF0ld^(+M_X zG!3pb8enuYcP%_Kms(};&R3W6dwK~3$?xb@?2@BT;xYjYlhsmS>Z=CurM(&ewO2K8 z3O!>TI%X=}w+{UV#i{z|3G`*)b>pl!RGMnRK4Ej_@B>tn@n;9OmA4?1x@{HkwUkAbz9{rO-$MwE+^4Jh4C!yAm+qf~1q zoX9D&>D&!QAG&G-UQ6bc&8)l%MlcUmqpAjVx-f?QQ1QJ2|DVfzvv8KCAdu7cnzoub zH6xfUzV{y3;m`68^(vk>!TH4efBe$_r{JFB9sy?MH^JppaLs{XniJejI}rM4t`RVF z{X>0^zE)4^aowl=U3*NMxlub^8?AK%7xGiiP0q96eBTGrg#QAzTe+@4Fd05Oesz=nY}Vlt(LRWdXZDBf z1L4$IwS>fi$%DqKB^K|LIw5f<e!+NNpy&JE_KbjdRwlho3TDpD{eyMNo!V?P)@I+^x_;o8!bmEEo@e7Le zQF=YxnV89DEj>HUd_&s3^iUDa^-%97cC+r{Qztbz+g)75w~(1Xwi|Tu2H0KJx9c2T zaU(M%ZP$>fE8fZsW!s(fnz8&I9klsTeg}5y9l*-7(S`>8i%MT(hPe&xq^fad9nlbKNAGTIGhs6Z|)etr_O#=V3|(W_xOh+i+JJNNQmmltoH#O#ePqT9y9 zi1$LepIMOB7cQWS)-n^}^XYD8Jlt6L1FgMBhl@$!Jo-)^hN%sOb19g*SqDj!BRv56t!gd)FFvgwBEo{%C?+$17vrDaE(3`Ji zLc&LE!|7b;3@!x!slm7h;Ucz!`l*pFTZ| z8{y95&GEw}$`CA24TZUM=H;;rlTR!QnD1!Fbe(bW)%M?sY1;lQxZHb$&;uMbLypbVq` zv-lh{sC|k0kApu4d9hS?d7n`{dvXt0sP05bI(b1;m7(yNShEH0zvLlVcg<#I>UoV! zUG^BO`vH1S6m{;VL*V*DcG2Y_mVcX&xqd60g~;791G8(_$sIDaW)Mql(iifIcC6Il zKXNramEXt)dNFJHdMSD}T&h)a6+Ly7F+y7-!JF=2WnUsv#bc);ajyJnH7atFrQV^+ zFnU=SBYm3e$tKwNYXfBB13VJ#WSN+B9Uf&*YD3473~fr_mg?yib}z~rDWWsG_JmC2 z1CT$D{u)I`D{sVttN6rNn0RX<^YuZj3vnqvjLmw>q&S&`-t4U?uv>mTWDL@k zf0R6P{$DwEb*CjCF6O(XeO%!h=TzQ<4r8Zp@RzWS{^(~%J^SnW?f%p;kd#>~>fN84m=()u87@s2u#C66J;{@i!{7`4VeWQ+*ua&MI2*-(bF-^u#;mXAFM-xHbd99Poq8II;yr$rKdq9z+yUu&$1Jlmq=rK3%z+2=I{1q z`W%~^>)TJJy_w%Z`(zr0THtJF9N{KB2g* z1^9US<#q5w8OQS+=8mcDC$I!_eZqP?mOeX_&FSsO({1qUakew}*e3eh4fp4l4C0kW=tDc%y4N126Pep-Lwkr$x}DumkS@IrB8}|;{KZ*FIiI6McRH|4$>`h+qB>^dKCf delta 93428 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�qKN`iXkNW2bpf)7!N=$s{EX+l3@qi)`?Es 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 Yx1%&6%`>aJli@%g=Vd!bV8QJ2KmVx*mjD0& From 4ae4f2d39f467091dbbcdbea8c9d0eb51cec0c61 Mon Sep 17 00:00:00 2001 From: rubyTanuki <161648546+rubyTanuki@users.noreply.github.com> Date: Fri, 29 May 2026 18:32:23 -0500 Subject: [PATCH 2/5] added distributed hashes to non-code structs hashes for non-code structs added which concatenate the children's hashes then hash the sum. adjusted diff traversal to account for new hashes --- src/tostr/core/builders.py | 1 + src/tostr/core/models.py | 44 ++++-- src/tostr/core/parser.py | 41 ++---- src/tostr/core/registry.py | 196 +++++++-------------------- src/tostr/languages/java/builders.py | 5 + 5 files changed, 93 insertions(+), 194 deletions(-) diff --git a/src/tostr/core/builders.py b/src/tostr/core/builders.py index e8640c3..58bb6d3 100644 --- a/src/tostr/core/builders.py +++ b/src/tostr/core/builders.py @@ -61,6 +61,7 @@ def from_dict(self, d: dict) -> BaseFile: description=d.get("description", ""), imports=d.get("imports", []), body=d.get("body", ""), + diff_hash=d.get("diff_hash", ""), package=d.get("package", ""), _inbound_dependency_strings=json.loads(d.get("inbound_dependency_strings", [])), _outbound_dependency_strings=json.loads(d.get("outbound_dependency_strings", [])), diff --git a/src/tostr/core/models.py b/src/tostr/core/models.py index 1828f86..60b7498 100644 --- a/src/tostr/core/models.py +++ b/src/tostr/core/models.py @@ -106,11 +106,6 @@ def edges(self): elif isinstance(self.parent, str): edges.add((self.id, self.parent, "is_child_of")) - # for child_set in self.children.values(): - # for child in child_set: - # edges.update(child.edges) - # edges.add((child.id, self.id, "contains")) - return edges @property @@ -143,6 +138,15 @@ def set_parent(self, parent: "BaseStruct"): logger.warning(f"Attempted to set parent of {self} to itself. Skipping to avoid circular reference.") return self.parent = parent + + def calculate_distributed_hash(self): + """Calculates diff_hash based on direct children's hashes.""" + if not self.all_children: + return + + child_hashes = sorted([child.diff_hash for child in self.all_children if getattr(child, "diff_hash", None)]) + if child_hashes: + self.diff_hash = hashlib.md5("".join(child_hashes).encode("utf-8")).hexdigest() def add_dependency(self, target: "BaseStruct"): self.outbound_dependencies.add(target) @@ -175,7 +179,6 @@ async def resolve_description_async(self, llm: "LLMClient", visited: set[str] = @classmethod def from_dict(cls, d: dict): data = d.copy() - # REMOVE all init=False here id = data.pop("id", None) instance = cls(**data) if id: @@ -213,6 +216,7 @@ def __str__(self): @dataclass(eq=False) class Directory(BaseStruct): _IDPREFIX: ClassVar[str] = "D" + diff_hash: str = "" def __init__(self, path, registry=None, parent=None, uid=None): uid = uid or str(path) @@ -233,11 +237,11 @@ def parse_children(self): for path in full_path.glob("*"): if self.registry.config.is_ignored(path): - logger.debug(f"Skipping '{path}' due to path ignore rules") + logger.debug(f"Skipping \'{path}\' due to path ignore rules") continue else: if path.is_dir(): - logger.debug(f"🔍 Parsing directory '{path}'") + logger.debug(f"🔍 Parsing directory \'{path}\'") relative_path = self.registry.relative_to_project(path) directory = Directory(path=relative_path, registry=self.registry, parent=self) self.registry.add_struct(directory) @@ -247,18 +251,26 @@ def parse_children(self): logger.debug(f"Attempting to resolve builder for suffix {path.parts[-1]}") try: if self.registry.config.is_ignored(path): - logger.debug(f"Skipping '{path}' due to path ignore rules") + logger.debug(f"Skipping \'{path}\' due to path ignore rules") continue builder = StructBuilderProvider.get_builder(path.suffix, self.registry) except LanguageNotSupportedError as e: continue instance = builder.build_file().from_path(path, parent=self) + + # Calculate file hash from children if any exist + instance.calculate_distributed_hash() + self.registry.add_struct(instance) self.add_child(instance) + + # Calculate directory hash from its direct children + self.calculate_distributed_hash() def to_dict(self) -> dict: data = super().to_dict() data["type"] = "Directory" + data["diff_hash"] = self.diff_hash return data @dataclass(eq=False) @@ -268,6 +280,7 @@ class BaseFile(BaseStruct): imports: List[str] = field(default_factory=list) package: str = "" body: str = "" + diff_hash: str = "" node: "Node" = None async def resolve_description_async(self, llm: "LLMClient", visited: set[str] = None): @@ -279,8 +292,9 @@ def to_dict(self) -> dict: data["type"] = "BaseFile" data["imports"] = self.imports data["body"] = self.body + data["diff_hash"] = self.diff_hash return data - + @dataclass(eq=False) class BaseCodeStruct(BaseStruct): @@ -329,7 +343,7 @@ def needs_description(self) -> bool: def imports(self) -> List[str]: return self.parent.imports - def resolve_type(self, type_name: str) -> Optional[BaseStruct]: + def resolve_type(self, type_name: str) -> Optional["BaseStruct"]: """Resolves a simple or scoped type name to a struct using package and imports.""" if not type_name: return None if type_name in self._type_cache: @@ -340,7 +354,7 @@ def resolve_type(self, type_name: str) -> Optional[BaseStruct]: # 2. Same package if not dep: - package = getattr(self.parent, 'package', None) if isinstance(self.parent, BaseFile) else None + package = getattr(self.parent, "package", None) if isinstance(self.parent, BaseFile) else None if package: dep = self.registry.get_struct_by_uid(f"{package}.{type_name}") @@ -462,7 +476,7 @@ class BaseMethod(BaseCodeStruct): # pass def resolve_dependencies(self): - logger.info(f"Resolving dependencies for method {self.uid}") + logger.debug(f"Resolving dependencies for method {self.uid}") for dep_info in self.dependency_names: # Handle both old (name, arity) and new (name, arity, receiver, is_creation) formats if len(dep_info) == 2: @@ -483,7 +497,7 @@ def resolve_dependencies(self): search_scope = self.parent.children if self.parent else self.children for child_set in list(search_scope.values()): for child in list(child_set): - if child.name == name and getattr(child, 'arity', -1) == arity: + if child.name == name and getattr(child, "arity", -1) == arity: self.add_dependency(child) resolved = True break @@ -512,7 +526,7 @@ def resolve_dependencies(self): if self.parent._potential_parents_cache is None: potential_parents = [] # Same package - package = getattr(self.parent.parent, 'package', None) if isinstance(self.parent.parent, BaseFile) else None + package = getattr(self.parent.parent, "package", None) if isinstance(self.parent.parent, BaseFile) else None if package: potential_parents.append(f"{package}.*") # All imports diff --git a/src/tostr/core/parser.py b/src/tostr/core/parser.py index e3c87c4..2518ebe 100644 --- a/src/tostr/core/parser.py +++ b/src/tostr/core/parser.py @@ -1,9 +1,10 @@ from pathlib import Path from abc import ABC import asyncio +import hashlib from loguru import logger -from tostr.core.models import BaseFile, Directory +from tostr.core.models import BaseFile, Directory, BaseStruct from tostr.core.registry import Registry from tostr.core.providers import StructBuilderProvider from tostr.exceptions import LanguageNotSupportedError @@ -13,13 +14,10 @@ def __init__(self, project_dir: str, llm=None, registry: Registry=None): self.project_dir = project_dir self.llm = llm self.registry = registry - # self.path_ignore = ["venv", ".venv", "env", ".env", "build", "dist", "__pycache__", ".tostr", ".git"] @property def files(self): - if self._files: return self._files - self._files = self.registry.uid_map.values().filter(lambda x: isinstance(x, BaseFile)) - return self._files + return [x for x in self.registry.uid_map.values() if isinstance(x, BaseFile)] async def parse(self, subpath: Path = None): if not subpath: @@ -28,9 +26,7 @@ async def parse(self, subpath: Path = None): subpath = Path(subpath) self.parse_path(subpath) - self.resolve_dependencies() - await self.resolve_descriptions_async() def parse_path(self, subpath: Path, parent: Directory = None): @@ -38,13 +34,11 @@ def parse_path(self, subpath: Path, parent: Directory = None): logger.debug(f"🔍 Parsing directory '{subpath}'") if parent is None: - # ROOT creation root_path = subpath if self.registry: root_path = self.registry.relative_to_project(subpath) root = Directory(path=root_path, registry=self.registry) self.registry.root = root - logger.debug(f"Created registry root: {root}") self.registry.add_struct(root) else: root = parent @@ -58,36 +52,27 @@ def parse_path(self, subpath: Path, parent: Directory = None): existing = self.registry.get_struct_by_uid(str(relative_path)) if path.is_dir(): - if existing and isinstance(existing, Directory): - logger.debug(f"Using cached directory '{path}'") - root.add_child(existing) - self.parse_path(path, parent=existing) - continue - directory = Directory(path=relative_path, registry=self.registry, parent=root) self.registry.add_struct(directory) root.add_child(directory) self.parse_path(path, parent=directory) else: - if existing and isinstance(existing, BaseFile): - if not self.registry.is_stale(existing): - logger.debug(f"Using cached file '{path}'") - root.add_child(existing) - continue - file = self.parse_file(path, parent=root) if file: + file.calculate_distributed_hash() self.registry.add_struct(file) root.add_child(file) + + root.calculate_distributed_hash() else: - logger.debug(f"🔍 Parsing file '{subpath}'") file = self.parse_file(subpath) - self.registry.root = file - self.registry.add_struct(file) + if file: + file.calculate_distributed_hash() + self.registry.root = file + self.registry.add_struct(file) - # @abstractmethod def parse_file(self, subpath: Path, parent: BaseStruct=None) -> BaseFile: - logger.debug(f"Attempting to resolve builder for suffix {subpath.parts[-1]}") + logger.debug(f"Attempting to resolve builder for suffix {subpath.suffix}") if self.registry.config.is_ignored(subpath): logger.debug(f"Skipping '{subpath}' due to path ignore rules") return None @@ -98,7 +83,6 @@ def parse_file(self, subpath: Path, parent: BaseStruct=None) -> BaseFile: logger.warning(str(e)) return None file_obj = builder.build_file().from_path(subpath, parent=parent) - # logger.debug(json.dumps(file_obj.to_dict(), indent=2)) return file_obj def resolve_dependencies(self): @@ -107,10 +91,7 @@ def resolve_dependencies(self): self.registry.root.resolve_dependencies() def load_cache(self): - # print("Attempting to load cache from SQLite database...") - # t_cache = time.time() self.registry.load_cache() - # print(f"✅ Loaded Cache in {time.time() - t_cache:.2f} seconds") async def resolve_descriptions_async(self): visited_ucids = set() diff --git a/src/tostr/core/registry.py b/src/tostr/core/registry.py index afc4b8d..7a8ef54 100644 --- a/src/tostr/core/registry.py +++ b/src/tostr/core/registry.py @@ -1,14 +1,15 @@ from collections import defaultdict from typing import List, Dict, Optional, TYPE_CHECKING from pathlib import Path -from tostr.core.models import BaseFile, BaseClass, BaseMethod, BaseField +import json +import hashlib +from loguru import logger + +from tostr.core.models import BaseFile, BaseClass, BaseMethod, BaseField, Directory from tostr.core.db import SQLiteCache from tostr.core.builders import BaseBuilder from tostr.core.context.config import ProjectConfig -import json -from loguru import logger - if TYPE_CHECKING: from tostr.core.models import BaseStruct, BaseCodeStruct @@ -42,7 +43,6 @@ def fields(self) -> List[BaseField]: def relative_to_project(self, path: Path) -> Path: if not self.project_path: - # logger.warning("Project path not set in registry, returning original path") return path if not path.is_absolute(): @@ -55,10 +55,9 @@ def relative_to_project(self, path: Path) -> Path: try: return path.absolute().relative_to(self.project_path.absolute()) except ValueError: - # If path is truly outside the subpath or due to macOS symlink quirks, fallback to relpath return Path(os.path.relpath(path.resolve(), self.project_path.resolve())) - def add_struct(self, struct: BaseStruct): + def add_struct(self, struct: "BaseStruct"): """ Adds a struct to the in-memory cache """ self.uid_map[struct.uid] = struct self.id_map[struct.id] = struct @@ -84,7 +83,6 @@ def _resolve_methods_recursive(self, struct: "BaseStruct", name: str, arity: int if struct.uid in visited: return [] visited.add(struct.uid) - # 1. Local methods matches = [x for x in struct.methods if x.name == name and x.arity == arity] if matches: return matches @@ -101,15 +99,12 @@ def _resolve_methods_recursive(self, struct: "BaseStruct", name: str, arity: int return [] def get_classes_in_package(self, package_name: str) -> List[BaseClass]: - """ Retrieves all classes in a specific package from memory or DB """ if package_name in self.missing_packages: return [x for x in self.classes if x.uid.startswith(package_name + ".") or x.uid.startswith(package_name + "#")] - # Ensure we have all classes from this package in memory if self.use_cache and self.db: with self.db.get_connection() as conn: cursor = conn.cursor() - # Find all classes in this package cursor.execute( "SELECT uid FROM structs WHERE type = 'BaseClass' AND (uid LIKE ? OR uid LIKE ?)", (f"{package_name}.%", f"{package_name}#%") @@ -125,52 +120,44 @@ def get_classes_in_package(self, package_name: str) -> List[BaseClass]: def load_filepath(self, path: Path): logger.debug(f"Loading subtree {str(path)}") path_str = str(self.relative_to_project(path)) - resolved_path_str = str(path.resolve()) with self.db.get_connection() as conn: cursor = conn.cursor() - # pull and hydrate structs if path_str != ".": cursor.execute("SELECT * FROM structs WHERE path = ? OR path LIKE ? || '/%'", (path_str, path_str)) else: cursor.execute("SELECT * FROM structs") node_rows = cursor.fetchall() - node_ids = [str(row['id']) for row in node_rows] + node_ids = [str(row["id"]) for row in node_rows] for row in node_rows: struct_data = dict(row) - if struct_data.get('imports', None): - struct_data['imports'] = json.loads(struct_data['imports']) - if struct_data.get('dependency_names', None): - struct_data['dependency_names'] = json.loads(struct_data['dependency_names']) - if struct_data.get('inherits', None): - struct_data['inherits'] = json.loads(struct_data['inherits']) - if struct_data.get('enum_constants', None): - struct_data['enum_constants'] = json.loads(struct_data['enum_constants']) + if struct_data.get("imports", None): + struct_data["imports"] = json.loads(struct_data["imports"]) + if struct_data.get("dependency_names", None): + struct_data["dependency_names"] = json.loads(struct_data["dependency_names"]) + if struct_data.get("inherits", None): + struct_data["inherits"] = json.loads(struct_data["inherits"]) + if struct_data.get("enum_constants", None): + struct_data["enum_constants"] = json.loads(struct_data["enum_constants"]) builder = BaseBuilder(self) - struct_type = struct_data['type'] + struct_type = struct_data["type"] instance = builder.with_type(struct_type=struct_type).from_dict(struct_data) if instance: - instance.id = str(struct_data['id']) + instance.id = str(struct_data["id"]) self.add_struct(instance) - logger.debug(f"Found {len(node_rows)} structs in subtree {path_str}") - if not node_ids: return None placeholders = ",".join(["?"] * len(node_ids)) if path_str == ".": - sql = f""" - SELECT source_id, target_id, edge_type - FROM edges - WHERE edge_type = 'is_child_of' - """ + sql = f"SELECT source_id, target_id, edge_type FROM edges WHERE edge_type = 'is_child_of'" cursor.execute(sql) else: sql = f""" @@ -185,29 +172,20 @@ def load_filepath(self, path: Path): edge_rows = cursor.fetchall() - logger.debug(f"Found {len(edge_rows)} edges in subtree {path_str}") - - for source_id, target_id, edge_type in edge_rows: source_obj = self.id_map.get(str(source_id)) target_obj = self.id_map.get(str(target_id)) if not source_obj or not target_obj: - logger.warning(f"Edge references missing struct. Source ID: {source_id}, Target ID: {target_id}, Edge Type: {edge_type}") continue target_obj.add_child(source_obj) self.root = self.get_struct_by_uid(path_str) - - logger.debug(f"Loaded subtree {path_str} with root {self.root}") - return self.root def get_struct_by_uid(self, uid: str) -> Optional["BaseStruct"]: - # Check memory cache first if uid in self.uid_map: - # logger.debug(f"Cache hit for UID {uid}, returning memory object") return self.uid_map[uid] if uid in self.missing_uids: @@ -218,13 +196,9 @@ def get_struct_by_uid(self, uid: str) -> Optional["BaseStruct"]: logger.debug(f"Attempting to retrieve struct and its children with UID {uid} from DB") from tostr.core.builders import BaseBuilder - import json with self.db.get_connection() as conn: cursor = conn.cursor() - - # Target exact match alongside delimiter-specific prefix matches - # to retrieve the target struct and all hierarchical descendants (directories or code structs) if uid != ".": cursor.execute( "SELECT * FROM structs WHERE uid = ? OR uid LIKE ? OR uid LIKE ?", @@ -235,156 +209,96 @@ def get_struct_by_uid(self, uid: str) -> Optional["BaseStruct"]: node_rows = cursor.fetchall() if not node_rows: - logger.debug(f"No structs found in DB matching UID {uid}") self.missing_uids.add(uid) return None - node_ids = [str(row['id']) for row in node_rows] + node_ids = [str(row["id"]) for row in node_rows] target_id = None for row in node_rows: struct_data = dict(row) - current_id = str(struct_data['id']) - - # Isolate the requested target struct ID for the final return - if struct_data['uid'] == uid: + current_id = str(struct_data["id"]) + if struct_data["uid"] == uid: target_id = current_id if current_id not in self.id_map: - if struct_data.get('imports', None): - struct_data['imports'] = json.loads(struct_data['imports']) - if struct_data.get('dependency_names', None): - struct_data['dependency_names'] = json.loads(struct_data['dependency_names']) - if struct_data.get('inherits', None): - struct_data['inherits'] = json.loads(struct_data['inherits']) - if struct_data.get('enum_constants', None): - struct_data['enum_constants'] = json.loads(struct_data['enum_constants']) + for field in ["imports", "dependency_names", "inherits", "enum_constants"]: + if struct_data.get(field): + struct_data[field] = json.loads(struct_data[field]) builder = BaseBuilder(self) - struct_type = struct_data['type'] - instance = builder.with_type(struct_type=struct_type).from_dict(struct_data) - + instance = builder.with_type(struct_type=struct_data["type"]).from_dict(struct_data) if instance: instance.id = current_id self.add_struct(instance) - # logger.debug(f"Created instance for struct with DB UID {struct_data['uid']} and type {struct_type}") - else: - logger.warning(f"Builder failed to create instance for struct with UID {struct_data['uid']} and type {struct_type}") if not node_ids: return None - # Fetch and connect edges for the loaded subset placeholders = ",".join(["?"] * len(node_ids)) - sql = f""" - SELECT source_id, target_id, edge_type - FROM edges - WHERE (source_id IN ({placeholders}) - OR target_id IN ({placeholders})) - AND edge_type = 'is_child_of' - """ - - params = node_ids + node_ids - cursor.execute(sql, params) + sql = f"SELECT source_id, target_id, edge_type FROM edges WHERE (source_id IN ({placeholders}) OR target_id IN ({placeholders})) AND edge_type = 'is_child_of'" + cursor.execute(sql, node_ids + node_ids) edge_rows = cursor.fetchall() for _source_id, _target_id, edge_type in edge_rows: source_obj = self.id_map.get(str(_source_id)) target_obj = self.id_map.get(str(_target_id)) - - if not source_obj or not target_obj: - continue - - target_obj.add_child(source_obj) + if source_obj and target_obj: + target_obj.add_child(source_obj) - struct = self.id_map.get(target_id) if target_id else None - - if struct is None: - logger.warning(f"Struct with DB UID {uid} was not found in memory cache after DB retrieval. Target ID: {target_id}") - return struct + return self.id_map.get(target_id) def get_struct_by_id(self, id: str) -> Optional["BaseStruct"]: id_str = str(id) - - # Check memory cache first - if hasattr(self, 'id_map') and id_str in self.id_map: + if id_str in self.id_map: return self.id_map[id_str] if not self.use_cache or not self.db: return None - # Fetch UID from db to execute localized subtree retrieval with self.db.get_connection() as conn: row = conn.execute("SELECT uid FROM structs WHERE id = ?", (id_str,)).fetchone() - logger.debug(f"Queried DB for struct with id {id_str}, got uid: {row[0]}") if not row: return None - target_uid = row[0] return self.get_struct_by_uid(target_uid) - def is_stale(self, struct: BaseCodeStruct | str) -> bool: + def is_stale(self, struct: "BaseStruct") -> bool: if not self.db: - raise RuntimeError("Cannot check for stale structs if SqLiteCache not provided.") - if isinstance(struct, str): - if struct not in self.uid_map: - raise KeyError(f"Could not find struct with uid {struct}") - struct = self.uid_map[struct] - - with self.db.get_connection() as conn: - row = conn.execute("SELECT diff_hash FROM structs WHERE uid = ?", (struct.uid,)).fetchone() - if not row or row[0] != struct.diff_hash: - return True - return False + return True - def update_cached_description(self, struct: BaseStruct | str): + return False + + def update_cached_description(self, struct: "BaseStruct"): if not self.db: - raise RuntimeError("Cannot check for stale structs if SqLiteCache not provided.") - if isinstance(struct, str): - if struct not in self.uid_map: - raise KeyError(f"Could not find struct with uid {struct}") - struct = self.uid_map[struct] - + raise RuntimeError("SqLiteCache not provided.") with self.db.get_connection() as conn: conn.execute("UPDATE structs SET description = ? WHERE uid = ?", (struct.description, struct.uid)) conn.commit() - - def save_struct_to_cache(self, struct: BaseStruct | str): - """ Saves a struct to the SQLite cache """ + def save_struct_to_cache(self, struct: "BaseStruct"): if not self.db: - raise RuntimeError("Cannot save to cache if SqLiteCache not provided.") - if isinstance(struct, str): - if struct not in self.uid_map: - raise KeyError(f"Could not find struct with uid {struct}") - struct = self.uid_map[struct] + raise RuntimeError("SqLiteCache not provided.") data = struct.to_dict() - target_uid = data.pop("uid") set_clause = ", ".join([f"{k} = ?" for k in data.keys()]) node_sql = f"UPDATE structs SET {set_clause} WHERE uid = ?" node_params = list(data.values()) + [target_uid] edges = list(struct.edges) - with self.db.get_connection() as conn: conn.execute(node_sql, node_params) - - # Clear all existing edges touching this node to prevent ghosts conn.execute("DELETE FROM edges WHERE source_id = ?", (struct.id,)) - - # Bulk insert fresh edges if edges: conn.executemany("INSERT INTO edges (source_id, target_id, edge_type) VALUES (?, ?, ?)", edges) conn.commit() - + def save_to_cache(self, stale: bool = False): - """Saves the entire AST to the SQLite cache.""" if not self.db: - raise RuntimeError("Cannot save to cache if SqLiteCache not provided.") + raise RuntimeError("SqLiteCache not provided.") parsed_ids = [(node.id,) for node in self.uid_map.values()] grouped_nodes = defaultdict(list) @@ -399,38 +313,22 @@ def serialize_for_db(value): for node in self.uid_map.values(): data_dict = node.to_dict() - if stale and data_dict.get("description", None): - data_dict["description"] = f"[STALE] {data_dict['description']}" + if stale and data_dict.get("description"): + data_dict["description"] = f"[STALE] {data_dict["description"]}" - # tuple of keys as the group identifier column_footprint = tuple(data_dict.keys()) - grouped_nodes[column_footprint].append(data_dict) all_edges.update(node.edges) with self.db.get_connection() as conn: for columns_tuple, dict_list in grouped_nodes.items(): - columns = ", ".join(columns_tuple) placeholders = ", ".join(["?"] * len(columns_tuple)) node_sql = f"INSERT OR REPLACE INTO structs ({columns}) VALUES ({placeholders})" - - # Fetching the specific value for each column from the dictionary - node_values = [ - tuple(serialize_for_db(n.get(col)) for col in columns_tuple) - for n in dict_list - ] - + node_values = [tuple(serialize_for_db(n.get(col)) for col in columns_tuple) for n in dict_list] conn.executemany(node_sql, node_values) - conn.executemany( - "DELETE FROM edges WHERE source_id = ?", - parsed_ids - ) - + conn.executemany("DELETE FROM edges WHERE source_id = ?", parsed_ids) if all_edges: - conn.executemany( - "INSERT INTO edges (source_id, target_id, edge_type) VALUES (?, ?, ?)", - list(all_edges) - ) - conn.commit() \ No newline at end of file + conn.executemany("INSERT INTO edges (source_id, target_id, edge_type) VALUES (?, ?, ?)", list(all_edges)) + conn.commit() diff --git a/src/tostr/languages/java/builders.py b/src/tostr/languages/java/builders.py index 2e42f39..c09b8cb 100644 --- a/src/tostr/languages/java/builders.py +++ b/src/tostr/languages/java/builders.py @@ -1,5 +1,6 @@ from tree_sitter import Parser, Node, Query, QueryCursor from pathlib import Path +import hashlib from tostr.core.registry import Registry from tostr.languages.java.language import JAVA_LANGUAGE @@ -29,6 +30,7 @@ def from_path(self, path: Path, parent: BaseStruct=None) -> BaseFile: with open(path, "rb") as f: body_bytes = f.read() file_obj.body = body_bytes.decode("utf-8") + file_obj.diff_hash = hashlib.md5(body_bytes).hexdigest() # Fallback until distributed hash is calculated parser = Parser(JAVA_LANGUAGE) tree = parser.parse(body_bytes) @@ -136,6 +138,7 @@ def from_node(self, node: Node, parent: BaseStruct=None) -> BaseClass: # BaseCodeStruct signature=signature, body=body, + diff_hash=hashlib.md5(node.text).hexdigest(), start_line=node.start_point[0], end_line=node.end_point[0], node=node, @@ -257,6 +260,7 @@ def from_node(self, node: Node, parent: BaseStruct=None) -> BaseMethod: # BaseCodeStruct signature=signature, body=body, + diff_hash=hashlib.md5(node.text).hexdigest(), start_line=node.start_point[0], end_line=node.end_point[0], node=node, @@ -315,6 +319,7 @@ def from_node(self, node: Node, parent: BaseStruct=None) -> BaseField: # BaseCodeStruct signature=signature, body=body, + diff_hash=hashlib.md5(node.text).hexdigest(), start_line=node.start_point[0], end_line=node.end_point[0], node=node, From b8cfd24bd89e5cd161bbd594b4aaa4ccffa8dba4 Mon Sep 17 00:00:00 2001 From: rubyTanuki <161648546+rubyTanuki@users.noreply.github.com> Date: Fri, 29 May 2026 18:32:52 -0500 Subject: [PATCH 3/5] adjusting string outputs --- src/tostr/core/serializer.py | 33 ++++++++++++++++++++++----------- src/tostr/llm/base.py | 7 +++++++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/tostr/core/serializer.py b/src/tostr/core/serializer.py index 7d183b9..ccd34b2 100644 --- a/src/tostr/core/serializer.py +++ b/src/tostr/core/serializer.py @@ -21,6 +21,8 @@ class tost: def dump_skeleton( cls, obj: "BaseStruct", + files_only: bool = True, + depth: int = 7, # indent: int = 0, pretty: bool = True ) -> str: @@ -31,18 +33,27 @@ def dump_skeleton( parts.append(header_str) if obj.files: - for f in obj.files: - parts.append(cls.dump_skeleton(f, pretty=pretty)) + if depth == 0: + parts.append(f"{indent_str}... ({len(obj.files)} files)") + else: + for f in obj.files: + parts.append(cls.dump_skeleton(f, files_only=files_only, depth=depth-1, pretty=pretty)) if obj.directories: - for d in obj.directories: - if d is obj: - logger.warning(f"Skipping dumping directory {d} as it is the same as its parent {obj}, likely to avoid circular reference.") - continue - parts.append('\n' + cls.dump_skeleton(d, pretty=pretty)) - if obj.classes: - for c in obj.classes: - parts.append(cls.dump_skeleton(c, pretty=pretty)) - + if depth == 0: + parts.append(f"{indent_str}... ({len(obj.directories)} directories)") + else: + for d in obj.directories: + if d is obj: + logger.warning(f"Skipping dumping directory {d} as it is the same as its parent {obj}, likely to avoid circular reference.") + continue + parts.append(cls.dump_skeleton(d, files_only=files_only, depth=depth-1, pretty=pretty)) + if obj.classes and not files_only: + if depth == 0: + parts.append(f"{indent_str}... ({len(obj.classes)} classes)") + else: + for c in obj.classes: + parts.append(cls.dump_skeleton(c, files_only=files_only, depth=depth-1, pretty=pretty)) + return textwrap.indent("\n".join(parts), indent_str) @classmethod diff --git a/src/tostr/llm/base.py b/src/tostr/llm/base.py index 29da770..e24804a 100644 --- a/src/tostr/llm/base.py +++ b/src/tostr/llm/base.py @@ -1,5 +1,6 @@ import asyncio import json +import time from abc import ABC, abstractmethod from typing import Any from pydantic import BaseModel, Field @@ -64,6 +65,8 @@ async def describe_class(self, class_obj: Any, imports: list[str]) -> LLMRespons input_data_string = json.dumps(input_data) logger.debug(f"Generating Description for {class_obj.uid}...") + + start_time = time.perf_counter() max_retries = 3 base_delay = 2 @@ -86,6 +89,10 @@ async def describe_class(self, class_obj: Any, imports: list[str]) -> LLMRespons except (ValueError, TypeError): continue + end_time = time.perf_counter() + elapsed_time = end_time - start_time + logger.debug(f"Finished describing {class_obj.uid} in {elapsed_time:.4f} seconds") + return result except Exception as e: error_str = str(e) From 87cb278bf7760bd7ee78d93410d2944b791dcbdf Mon Sep 17 00:00:00 2001 From: rubyTanuki <161648546+rubyTanuki@users.noreply.github.com> Date: Fri, 29 May 2026 18:33:21 -0500 Subject: [PATCH 4/5] fix: making tests align with new changes to dep resolution and mcp --- .../java/builder/test_java_class_builder.py | 9 +++---- .../java/builder/test_java_field_builder.py | 24 +++++++++-------- .../java/builder/test_java_method_builder.py | 26 ++++++++++--------- .../languages/java/test_java_dependencies.py | 6 ++--- tests/llm/test_llm_client.py | 16 +++++++----- 5 files changed, 43 insertions(+), 38 deletions(-) diff --git a/tests/languages/java/builder/test_java_class_builder.py b/tests/languages/java/builder/test_java_class_builder.py index 897c496..300cd04 100644 --- a/tests/languages/java/builder/test_java_class_builder.py +++ b/tests/languages/java/builder/test_java_class_builder.py @@ -23,9 +23,8 @@ def mock_registry(): @pytest.fixture def mock_parent_file(): """Mocks the BaseFile parent needed for UID generation.""" - mock_file = MagicMock(spec=BaseFile) - mock_file.uid = "src/main/java/com/tostr/DataProcessor.java" - mock_file.path = Path("src/main/java/com/tostr/DataProcessor.java") + mock_file = MagicMock(spec=BaseFile, uid="com/tostr/DataProcessor.java", path=Path("src/main/java/com/tostr/DataProcessor.java")) + mock_file.package = "com.tostr" # Tell isinstance(parent, BaseFile) to return True mock_file.__class__ = BaseFile return mock_file @@ -65,7 +64,7 @@ class InnerHelper {} # 1. Test BaseStruct Properties assert class_obj.name == "DataProcessor" - assert class_obj.uid == "src/main/java/com/tostr/DataProcessor.java#DataProcessor" + assert class_obj.uid == f"{mock_parent_file.package}.DataProcessor" assert class_obj.parent == mock_parent_file # 2. Test Signature Extraction @@ -90,4 +89,4 @@ class InnerHelper {} struct_names = [s.name for s in registered_structs] assert "data" in struct_names # The field assert "process" in struct_names # The method - assert "InnerHelper" in struct_names # The inner class \ No newline at end of file + assert "InnerHelper" in struct_names # The inner class diff --git a/tests/languages/java/builder/test_java_field_builder.py b/tests/languages/java/builder/test_java_field_builder.py index 7d20f57..209ff2a 100644 --- a/tests/languages/java/builder/test_java_field_builder.py +++ b/tests/languages/java/builder/test_java_field_builder.py @@ -5,7 +5,7 @@ from tree_sitter import Parser from tostr.languages.java.language import JAVA_LANGUAGE from tostr.core.registry import Registry -from tostr.core.models import BaseClass, BaseField +from tostr.core.models import BaseFile, BaseClass, BaseField from tostr.languages.java.builders import JavaFieldBuilder @pytest.fixture(scope="session") @@ -22,10 +22,12 @@ def mock_registry(): @pytest.fixture def mock_parent_class(): """Mocks the BaseClass parent needed for UID generation.""" - mock_cls = MagicMock(spec=BaseClass) - mock_cls.uid = "src/main/java/com/tostr/Constants.java#Constants" - mock_cls.path = Path("src/main/java/com/tostr/Constants.java") - mock_cls.__class__ = BaseClass + mock_cls = MagicMock( + spec=BaseClass, + uid="com.example.TestClass", + name="TestClass", + parent=MagicMock(spec=BaseFile, uid="com/tostr/Constants.java", package="com.tostr") + ) return mock_cls def test_java_field_builder_extracts_fields(java_parser, mock_registry, mock_parent_class): @@ -44,7 +46,7 @@ class Constants { # Find the field nodes inside the class body field_nodes = [] class_node = tree.root_node.children[0] - body_node = class_node.child_by_field_name('body') + body_node = class_node.child_by_field_name("body") for child in body_node.children: if child.type == "field_declaration": @@ -65,12 +67,12 @@ class Constants { # Signature Tests (Ensuring the comment was skipped and order is correct) expected_tau_sig = "@Serialized public static final double TAU" - assert tau_field.signature == expected_tau_sig, f"Expected '{expected_tau_sig}', got '{tau_field.signature}'" + assert tau_field.signature == expected_tau_sig, f"Expected \'{expected_tau_sig}\\' , got \'{tau_field.signature}\'" assert "This comment should be ignored" not in tau_field.signature # UID Test (Ensuring NO type information is appended to fields) - expected_tau_uid = "src/main/java/com/tostr/Constants.java#Constants.TAU" - assert tau_field.uid == expected_tau_uid, f"Expected '{expected_tau_uid}', got '{tau_field.uid}'" + expected_tau_uid = "com.example.TestClass.TAU" + assert tau_field.uid == expected_tau_uid, f"Expected \'{expected_tau_uid}\\' , got \'{tau_field.uid}\'" # --- TEST 2: The Generic Field --- @@ -83,5 +85,5 @@ class Constants { expected_users_sig = "private List activeUsers" assert users_field.signature == expected_users_sig - expected_users_uid = "src/main/java/com/tostr/Constants.java#Constants.activeUsers" - assert users_field.uid == expected_users_uid \ No newline at end of file + expected_users_uid = "com.example.TestClass.activeUsers" + assert users_field.uid == expected_users_uid diff --git a/tests/languages/java/builder/test_java_method_builder.py b/tests/languages/java/builder/test_java_method_builder.py index f3156fc..34e7ca5 100644 --- a/tests/languages/java/builder/test_java_method_builder.py +++ b/tests/languages/java/builder/test_java_method_builder.py @@ -5,7 +5,7 @@ from tree_sitter import Parser from tostr.languages.java.language import JAVA_LANGUAGE from tostr.core.registry import Registry -from tostr.core.models import BaseClass, BaseMethod +from tostr.core.models import BaseFile, BaseClass, BaseMethod from tostr.languages.java.builders import JavaMethodBuilder @pytest.fixture(scope="session") @@ -22,12 +22,14 @@ def mock_registry(): @pytest.fixture def mock_parent_class(): """Mocks the BaseClass parent needed for UID generation.""" - mock_cls = MagicMock(spec=BaseClass) - # The UID of a class uses # - mock_cls.uid = "src/main/java/com/tostr/Mathf.java#Mathf" - mock_cls.path = Path("src/main/java/com/tostr/Mathf.java") - # Make sure isinstance(parent, BaseFile) returns False - mock_cls.__class__ = BaseClass + mock_cls = MagicMock( + spec=BaseClass, + uid="com.example.OuterClass", + name="OuterClass", + parent=MagicMock(spec=BaseFile, package="com.example"), + imports=["java.util.*", "com.example.dep.Dependency"], + fields=[MagicMock(name="myService", field_type="MyService")] + ) return mock_cls def test_java_method_builder_extracts_complex_method(java_parser, mock_registry, mock_parent_class): @@ -48,7 +50,7 @@ class Mathf { # 1. Find the method nodes inside the class body method_nodes = [] class_node = tree.root_node.children[0] - body_node = class_node.child_by_field_name('body') + body_node = class_node.child_by_field_name("body") for child in body_node.children: if child.type == "method_declaration": @@ -63,7 +65,7 @@ class Mathf { method_obj = builder.from_node(complex_method_node, parent=mock_parent_class) # Core Properties - assert method_obj.name == "processData", f"Expected 'processData', got {method_obj.name}" + assert method_obj.name == "processData", f"Expected \'processData\', got {method_obj.name}" assert method_obj.parent == mock_parent_class # Signature Tests @@ -78,7 +80,7 @@ class Mathf { assert method_obj.arity == 2, f"Expected arity 2, got {method_obj.arity}" # UID Test (Crucial for method overloading support) - expected_uid = "src/main/java/com/tostr/Mathf.java#Mathf.processData(int, String)" + expected_uid = "com.example.OuterClass.processData(int, String)" assert method_obj.uid == expected_uid, f"Expected {expected_uid}, got {method_obj.uid}" @@ -91,5 +93,5 @@ class Mathf { assert "void ping()" in simple_obj.signature # Check empty parameter UID - expected_simple_uid = "src/main/java/com/tostr/Mathf.java#Mathf.ping()" - assert simple_obj.uid == expected_simple_uid, f"Expected {expected_simple_uid}, got {simple_obj.uid}" \ No newline at end of file + expected_simple_uid = "com.example.OuterClass.ping()" + assert simple_obj.uid == expected_simple_uid, f"Expected {expected_simple_uid}, got {simple_obj.uid}" diff --git a/tests/languages/java/test_java_dependencies.py b/tests/languages/java/test_java_dependencies.py index da1538c..d4a1a6f 100644 --- a/tests/languages/java/test_java_dependencies.py +++ b/tests/languages/java/test_java_dependencies.py @@ -29,8 +29,8 @@ def test_java_dependency_parsing(tmp_path, registry): method1 = [m for m in registry.methods if m.name == "method1"][0] # Verify dependency names and arities are parsed - assert ("method2", 0) in method1.dependency_names - assert ("method3", 2) in method1.dependency_names + assert ("method2", 0, None, False) in method1.dependency_names + assert ("method3", 2, None, False) in method1.dependency_names def test_java_dependency_resolution_local(tmp_path, registry): """Tests resolution of local method calls (same class).""" @@ -98,4 +98,4 @@ def test_java_dependency_resolution_imported(tmp_path, registry): method_a = [m for m in registry.methods if m.name == "methodA"][0] # This tests the "IMPORTED" logic in BaseMethod.resolve_dependencies - assert method_a in method_b.outbound_dependencies + assert method_a.parent in method_b.outbound_dependencies diff --git a/tests/llm/test_llm_client.py b/tests/llm/test_llm_client.py index 8c513d7..aff5e4d 100644 --- a/tests/llm/test_llm_client.py +++ b/tests/llm/test_llm_client.py @@ -22,9 +22,9 @@ async def test_llm_client_data_flow(): client = LLMClient(strategy) # Create a mock method - mock_method = MagicMock(spec=BaseMethod) + mock_method = MagicMock(spec=BaseMethod, uid="mock.method", name="method") + mock_method.parent = MagicMock(spec=BaseFile, imports=[]) # Mock the parent file mock_method.signature = "void test()" - mock_method.uid = "test_uid" mock_method.description = "" # Create a mock class @@ -46,8 +46,7 @@ async def test_models_resolve_description_data_flow(): Test the full data flow from BaseClass.resolve_description_async through LLMClient. """ registry = MagicMock(spec=Registry) - parent_file = MagicMock(spec=BaseFile) - parent_file.imports = [] + parent_file = MagicMock(spec=BaseFile, imports=[], package="com.example") # Create a real BaseClass instance cls = BaseClass(name="TestClass", uid="TestClass") @@ -93,9 +92,12 @@ async def describe_class(self, input_data_string: str, system_instruction: str) strategy = RetryStrategy(api_key="test", model_name="test") client = LLMClient(strategy) - mock_class = MagicMock(spec=BaseClass) - mock_class.methods = [] - mock_class.skeletonize.return_value = "code" + mock_class = MagicMock( + spec=BaseClass, + uid="mock.class", + methods=[], + skeletonize=MagicMock(return_value="code") + ) # We need to mock asyncio.sleep to avoid waiting during tests with MagicMock() as mock_sleep: From ef3770dffa25de0a76b2fb837ac05f89c4f1af14 Mon Sep 17 00:00:00 2001 From: rubyTanuki <161648546+rubyTanuki@users.noreply.github.com> Date: Fri, 29 May 2026 18:53:53 -0500 Subject: [PATCH 5/5] fix: files finding their children properly from db --- src/tostr/core/registry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tostr/core/registry.py b/src/tostr/core/registry.py index 7a8ef54..af6ce25 100644 --- a/src/tostr/core/registry.py +++ b/src/tostr/core/registry.py @@ -194,15 +194,15 @@ def get_struct_by_uid(self, uid: str) -> Optional["BaseStruct"]: if not self.use_cache or not self.db: return None - logger.debug(f"Attempting to retrieve struct and its children with UID {uid} from DB") + logger.debug(f"Attempting to retrieve {uid} and its children from DB") from tostr.core.builders import BaseBuilder with self.db.get_connection() as conn: cursor = conn.cursor() if uid != ".": cursor.execute( - "SELECT * FROM structs WHERE uid = ? OR uid LIKE ? OR uid LIKE ?", - (uid, f"{uid}%", f"{uid}#%") + "SELECT * FROM structs WHERE uid = ? OR uid LIKE ? OR uid LIKE ? OR path = ?", + (uid, f"{uid}%", f"{uid}#%", uid) ) else: cursor.execute("SELECT * FROM structs")