From 63c1c9c0d06f97a189dc117eec489c75111f5b28 Mon Sep 17 00:00:00 2001 From: nghialuutrung Date: Sun, 25 Jan 2026 11:55:56 +0700 Subject: [PATCH 1/3] add review bot by AI --- .github/workflows/ai-review.yml | 32 ++++ docs/PLAN-ai-code-reviewer.md | 54 ++++++ .../__pycache__/ai_reviewer.cpython-314.pyc | Bin 0 -> 7920 bytes scripts/ai_reviewer.py | 175 ++++++++++++++++++ scripts/requirements.txt | 2 + 5 files changed, 263 insertions(+) create mode 100644 .github/workflows/ai-review.yml create mode 100644 docs/PLAN-ai-code-reviewer.md create mode 100644 scripts/__pycache__/ai_reviewer.cpython-314.pyc create mode 100644 scripts/ai_reviewer.py create mode 100644 scripts/requirements.txt diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml new file mode 100644 index 00000000..fd7ce3ef --- /dev/null +++ b/.github/workflows/ai-review.yml @@ -0,0 +1,32 @@ +name: AI Code Reviewer + +on: + pull_request: + types: [opened, synchronize] + +permissions: + contents: read + pull-requests: write + +jobs: + review: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install Dependencies + run: | + pip install -r scripts/requirements.txt + + - name: Run AI Reviewer + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + run: | + python scripts/ai_reviewer.py diff --git a/docs/PLAN-ai-code-reviewer.md b/docs/PLAN-ai-code-reviewer.md new file mode 100644 index 00000000..3b38ecb5 --- /dev/null +++ b/docs/PLAN-ai-code-reviewer.md @@ -0,0 +1,54 @@ +# Plan: Custom AI Code Reviewer (GitHub Actions + Gemini) + +## Goal Description +Implement a "White Box" AI Code Review system where a custom Python script runs inside GitHub Actions. It will fetch Pull Request changes, filter them, send them to Google Gemini for analysis, and post review comments back to the PR. This approach offers maximum control over costs, context, and review quality. + +## User Review Required +> [!IMPORTANT] +> **API Key Required**: You will need a Google Gemini API Key. It must be stored in your GitHub Repository Secrets as `GEMINI_API_KEY`. + +> [!NOTE] +> **Token Usage**: This script will be optimized to only send necessary text (code diffs). However, large PRs might still consume significant tokens. We will implement basic filtering to mitigate this. + +## Proposed Changes + +### Architecture +1. **GitHub Action Workflow**: Triggers on `pull_request` types (opened, synchronized). Sets up Python, installs dependencies, and runs the script. +2. **Python Script (`scripts/ai_reviewer.py`)**: + * **Input**: `GITHUB_TOKEN`, `GEMINI_API_KEY`, and PR Context (from `GITHUB_EVENT_PATH`). + * **Logic**: + 1. Connect to GitHub API via `PyGithub`. + 2. Get the current Pull Request object. + 3. Iterate through `pr.get_files()`. + 4. **Filter**: Ignore `package-lock.json`, `dist/`, images, deleted files. + 5. **Prompting**: Construct a prompt for Gemini `1.5-flash` (balanced speed/cost) asking for review in JSON format. + 6. **Response Handling**: Parse JSON response. + 7. **Commenting**: Post comments to the exact line in the PR diff. + +### Component: Scripts +#### [NEW] [ai_reviewer.py](file:///Users/nghialuutrung/Desktop/antigravity-kit/scripts/ai_reviewer.py) +The core logic script. It will use: +* `PyGithub`: To interact with the repository and PRs. +* `google-generativeai`: To communicate with Gemini. + +#### [NEW] [requirements.txt](file:///Users/nghialuutrung/Desktop/antigravity-kit/scripts/requirements.txt) +Dependencies for the script: +```text +PyGithub +google-generativeai +``` + +### Component: Workflows +#### [NEW] [ai-review.yml](file:///Users/nghialuutrung/Desktop/antigravity-kit/.github/workflows/ai-review.yml) +The workflow file to orchestrate the process. + +## Verification Plan + +### Automated Tests +* Since this relies on external APIs (GitHub, Google), true automated unit tests are mocking-heavy. +* **Dry Run Mode**: We can implement a `--dry-run` flag in the script to print what *would* be commented instead of actually commenting. + +### Manual Verification +1. Create a dummy PR with some obvious "bad code" (e.g., `console.log` with secrets, infinite loop). +2. Watch the Action run. +3. Verify the bot comments on the correct lines with relevant feedback in Vietnamese. diff --git a/scripts/__pycache__/ai_reviewer.cpython-314.pyc b/scripts/__pycache__/ai_reviewer.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29692a7329820c1ff098dbd29bb74c043531b133 GIT binary patch literal 7920 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 literal 0 HcmV?d00001 diff --git a/scripts/ai_reviewer.py b/scripts/ai_reviewer.py new file mode 100644 index 00000000..8bc6bc21 --- /dev/null +++ b/scripts/ai_reviewer.py @@ -0,0 +1,175 @@ +import os +import json +import logging +from github import Github +from openai import OpenAI + +# Configure Logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# Constants +IGNORED_EXTENSIONS = ['.json', '.md', '.txt', '.yml', '.yaml', '.lock', '.png', '.jpg', '.jpeg', '.gif', '.svg'] +IGNORED_DIRS = ['dist', 'build', 'node_modules', '.github'] +MODEL_NAME = "mistralai/devstral-2512:free" + +def should_review(filename): + """Check if file should be reviewed based on extension and path.""" + if any(filename.endswith(ext) for ext in IGNORED_EXTENSIONS): + return False + if any(part in filename.split('/') for part in IGNORED_DIRS): + return False + return True + +def get_pr_diff(repo, pr_number): + """Fetch PR diff using PyGithub.""" + pr = repo.get_pull(pr_number) + files_data = [] + + for file in pr.get_files(): + if not should_review(file.filename): + continue + + # Only review added or modified files (not deleted) + if file.status == 'removed': + continue + + files_data.append({ + "filename": file.filename, + "patch": file.patch + }) + return pr, files_data + +def analyze_code_with_openrouter(files_data): + """Send code diff to OpenRouter for review.""" + api_key = os.getenv("OPENROUTER_API_KEY") + if not api_key: + logging.error("OPENROUTER_API_KEY not found in environment variables.") + return [] + + client = OpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=api_key, + ) + + # Construct Prompt + # We ask for a strict JSON response. + prompt = """ + Bạn là một Senior Code Reviewer. Nhiệm vụ của bạn là review các đoạn code thay đổi trong Pull Request này. + + Hãy chỉ ra các vấn đề nghiêm trọng: + 1. Lỗi Logic (Logic Errors) - Rất quan trọng. + 2. Vấn đề Bảo mật (Security Vulnerabilities) - Rất quan trọng. + 3. Hiệu năng (Performance Issues). + 4. Code xấu, khó bảo trì (Bad Practices). + + Bỏ qua: + - Các lỗi format/style (đã có linter). + - Các thay đổi không quan trọng. + + Dữ liệu input là JSON list các file kèm patch. + Hãy trả về kết quả là một JSON list thuần túy (không markdown block, không giải thích thêm), mỗi item có format sau: + [ + { + "filename": "tên_file", + "line_number": số_dòng_trong_patch_để_comment, + "comment": "Nội dung review bằng tiếng Việt, ngắn gọn, súc tích." + } + ] + + Nếu code tốt hoàn toàn, hãy trả về danh sách rỗng []. + + CODE DIFF TO REVIEW: + """ + json.dumps(files_data) + + try: + response = client.chat.completions.create( + model=MODEL_NAME, + messages=[ + {"role": "system", "content": "You are a helpful code reviewer. Always respond with valid JSON code only. No markdown formatting."}, + {"role": "user", "content": prompt} + ], + # Note: response_format={"type": "json_object"} sometimes requires 'json' in prompt or specific model support. + # We trust the prompt instruction for now. + ) + + content = response.choices[0].message.content.strip() + + # Strip markdown code blocks if present (common issue with LLMs) + if content.startswith("```json"): + content = content[7:] + if content.startswith("```"): + content = content[3:] + if content.endswith("```"): + content = content[:-3] + + content = content.strip() + + logging.info("OpenRouter response received.") + return json.loads(content) + except json.JSONDecodeError: + logging.error(f"Failed to parse JSON response: {content}") + return [] + except Exception as e: + logging.error(f"Error calling OpenRouter: {e}") + return [] + +def post_comments(pr, comments): + """Post comments to the PR.""" + commit = pr.get_commits().reversed[0] # Get latest commit + + if not comments: + logging.info("No issues found. LGTM!") + return + + logging.info(f"Posting {len(comments)} comments...") + + for note in comments: + filename = note.get('filename') + line = note.get('line_number') # AI guess of the line + body = f"🤖 **AI Review**: {note.get('comment')}" + + try: + if not line: + pr.create_issue_comment(f"File `{filename}`: {body}") + continue + + pr.create_review_comment(body, commit, filename, line=int(line), side="RIGHT") + except Exception as e: + logging.warning(f"Failed to post comment on {filename}:{line}. Error: {e}") + pr.create_issue_comment(f"Could not comment on line {line} of {filename}. \n\n{body}") + +def main(): + github_token = os.getenv("GITHUB_TOKEN") + event_path = os.getenv("GITHUB_EVENT_PATH") + + if not github_token or not event_path: + logging.error("Missing GITHUB_TOKEN or GITHUB_EVENT_PATH.") + return + + with open(event_path, 'r') as f: + event_data = json.load(f) + + if 'pull_request' not in event_data: + logging.info("Not a pull_request event. Exiting.") + return + + pr_number = event_data['pull_request']['number'] + repo_name = event_data['repository']['full_name'] + + logging.info(f"Starting review for PR #{pr_number} in {repo_name}") + + g = Github(github_token) + repo = g.get_repo(repo_name) + pr, files_data = get_pr_diff(repo, pr_number) + + if not files_data: + logging.info("No reviewable files found.") + return + + logging.info(f"Analyzing {len(files_data)} files...") + comments = analyze_code_with_openrouter(files_data) + post_comments(pr, comments) + logging.info("Review complete.") + +if __name__ == "__main__": + main() diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 00000000..881d5508 --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,2 @@ +PyGithub==2.1.1 +openai==1.12.0 From 8b910811fa10a0d13d54d5fe4fc9e170dfcca5f5 Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 25 Jan 2026 12:13:46 +0700 Subject: [PATCH 2/3] feat: implement AI code reviewer using OpenRouter (Mistral) --- docs/PLAN-ai-code-reviewer.md | 68 ++++++++++++++--------------------- scripts/ai_reviewer.py | 4 +++ scripts/requirements.txt | 4 +-- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/docs/PLAN-ai-code-reviewer.md b/docs/PLAN-ai-code-reviewer.md index 3b38ecb5..b5898ad1 100644 --- a/docs/PLAN-ai-code-reviewer.md +++ b/docs/PLAN-ai-code-reviewer.md @@ -1,54 +1,38 @@ -# Plan: Custom AI Code Reviewer (GitHub Actions + Gemini) +# Plan: Custom AI Code Reviewer (GitHub Actions + OpenRouter) ## Goal Description -Implement a "White Box" AI Code Review system where a custom Python script runs inside GitHub Actions. It will fetch Pull Request changes, filter them, send them to Google Gemini for analysis, and post review comments back to the PR. This approach offers maximum control over costs, context, and review quality. +Implement AI Code Reviewer using **OpenRouter** (Mistral) instead of Gemini. Fix dependency conflicts and ensure compliance with OpenRouter API standards. ## User Review Required > [!IMPORTANT] -> **API Key Required**: You will need a Google Gemini API Key. It must be stored in your GitHub Repository Secrets as `GEMINI_API_KEY`. - -> [!NOTE] -> **Token Usage**: This script will be optimized to only send necessary text (code diffs). However, large PRs might still consume significant tokens. We will implement basic filtering to mitigate this. +> **API Key**: Ensure `OPENROUTER_API_KEY` is set in GitHub Secrets. +> **Model**: Using `mistralai/devstral-2512:free` as requested (Note: verification of exact model ID recommended). ## Proposed Changes -### Architecture -1. **GitHub Action Workflow**: Triggers on `pull_request` types (opened, synchronized). Sets up Python, installs dependencies, and runs the script. -2. **Python Script (`scripts/ai_reviewer.py`)**: - * **Input**: `GITHUB_TOKEN`, `GEMINI_API_KEY`, and PR Context (from `GITHUB_EVENT_PATH`). - * **Logic**: - 1. Connect to GitHub API via `PyGithub`. - 2. Get the current Pull Request object. - 3. Iterate through `pr.get_files()`. - 4. **Filter**: Ignore `package-lock.json`, `dist/`, images, deleted files. - 5. **Prompting**: Construct a prompt for Gemini `1.5-flash` (balanced speed/cost) asking for review in JSON format. - 6. **Response Handling**: Parse JSON response. - 7. **Commenting**: Post comments to the exact line in the PR diff. - -### Component: Scripts -#### [NEW] [ai_reviewer.py](file:///Users/nghialuutrung/Desktop/antigravity-kit/scripts/ai_reviewer.py) -The core logic script. It will use: -* `PyGithub`: To interact with the repository and PRs. -* `google-generativeai`: To communicate with Gemini. - -#### [NEW] [requirements.txt](file:///Users/nghialuutrung/Desktop/antigravity-kit/scripts/requirements.txt) -Dependencies for the script: -```text -PyGithub -google-generativeai -``` - -### Component: Workflows -#### [NEW] [ai-review.yml](file:///Users/nghialuutrung/Desktop/antigravity-kit/.github/workflows/ai-review.yml) -The workflow file to orchestrate the process. +### Dependencies +#### [MODIFY] [requirements.txt](file:///Users/nghialuutrung/Desktop/antigravity-kit/scripts/requirements.txt) +* Update `openai>=1.55.0` to resolve `httpx` proxy argument conflict. +* Keep `PyGithub`. + +### Logic Script +#### [MODIFY] [ai_reviewer.py](file:///Users/nghialuutrung/Desktop/antigravity-kit/scripts/ai_reviewer.py) +* **Client Initialization**: + ```python + client = OpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=api_key, + default_headers={ + "HTTP-Referer": "https://github.com/hapo-nghialuu/antigravity-kit", # Attribution + "X-Title": "Antigravity AI Reviewer" + } + ) + ``` +* **Model**: Set `MODEL_NAME = "mistralai/devstral-2512:free"`. +* **JSON Handling**: Add robust try-catch for JSON parsing as free models might chatter. ## Verification Plan -### Automated Tests -* Since this relies on external APIs (GitHub, Google), true automated unit tests are mocking-heavy. -* **Dry Run Mode**: We can implement a `--dry-run` flag in the script to print what *would* be commented instead of actually commenting. - ### Manual Verification -1. Create a dummy PR with some obvious "bad code" (e.g., `console.log` with secrets, infinite loop). -2. Watch the Action run. -3. Verify the bot comments on the correct lines with relevant feedback in Vietnamese. +1. **Re-run GitHub Action**: Trigger the workflow again on the existing PR. +2. **Check Logs**: Verify "OpenRouter response received" and no 404/401 errors. diff --git a/scripts/ai_reviewer.py b/scripts/ai_reviewer.py index 8bc6bc21..e2de9925 100644 --- a/scripts/ai_reviewer.py +++ b/scripts/ai_reviewer.py @@ -49,6 +49,10 @@ def analyze_code_with_openrouter(files_data): client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=api_key, + default_headers={ + "HTTP-Referer": "https://github.com/hapo-nghialuu/antigravity-kit", + "X-Title": "Antigravity AI Reviewer" + } ) # Construct Prompt diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 881d5508..af700426 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,2 +1,2 @@ -PyGithub==2.1.1 -openai==1.12.0 +PyGithub>=2.1.1 +openai>=1.55.0 From e0232dd015db3138d2779d3f9ae9b9979ced651d Mon Sep 17 00:00:00 2001 From: hapo-nghialuu Date: Sun, 25 Jan 2026 12:14:44 +0700 Subject: [PATCH 3/3] add git ignore --- .../scripts/__pycache__/core.cpython-313.pyc | Bin 12003 -> 0 bytes .../__pycache__/design_system.cpython-313.pyc | Bin 58773 -> 0 bytes .gitignore | 1 + scripts/__pycache__/ai_reviewer.cpython-314.pyc | Bin 7920 -> 0 bytes 4 files changed, 1 insertion(+) delete mode 100644 .agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc delete mode 100644 .agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc delete mode 100644 scripts/__pycache__/ai_reviewer.cpython-314.pyc diff --git a/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc b/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc deleted file mode 100644 index fbe98ab6e62b76fb81eb43a7d4a07a3d2d7a77cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12003 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 diff --git a/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc b/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc deleted file mode 100644 index 34ee8b528d1633bc9c6da20de0ea981d21714eb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58773 zcmd_T33OZ8bsz|kAh8ex2!i0geC`Crozy~7qQqUKL{j1>Bx)&9APJEukpSz1lt`3Z zraPUO)k(T2+Z9oX6G4e5g0dBz@r;={JrgBychu+jbbl1!0t{DIluV9K*Yufg%2keS zP0q~R`~HFt3J|C&InGQ&;@{qV_q})Dd-pAOd7q@GYh>^{*t=xE@&_{6|Bf%hPtQMm z@-LEPvUg>)jFHiDI_Z?0k&}B8lSJ;xOfuY)Pbr2KjDkWK#i^9xR3?=?r<_s_s~FX= zno+}hDx;y5w5mxCS2ek6_FU5%Qkb+{S(%L1=F8~xgOE1<7%i=1(rNu=9i4Gm-=?4q z`Lfp)Oorr3Ci!A0mKDqB7CP&)o;JZ>HvHvW&ZKkUo(F#zTSw<3?CWyINVgIULTCYj z(6wpkLLMSZl0p$l!9=%_Z^Z!1!Ry;pbO{fiO$*<-yy_r~BJpzQQixG@IhW)il`bb= z^DgJp6&;f@nf2FY9DNIzg3ATtN4RoJuRxPZx~g5y6kRSFKPC;~p6M*QnyzV2qGWNx z&2%l~Ac;Oe*R`j_g{r3;Xd~U&m;~1*x|xSO{(O1|`A-aW*;ppKOpW(E4+CHOVtyTX z{_DYH?i*8axf1YI9F%kKlx!|(F3Eh*`YC+S^x0hYIR{0rxt48<)S%5_W30D<)GU{Vi%WyCLFl5*F>776I~}eDjmF}#&e=LN6#T9+&P5)6iifu^E;*TH z5%&~_jSnS}xO`E^;=t6D(N-%>&01aa(@rZhTUJMvjjT~;80YJ@ndLGw!9gzN)Oq{z zJhfzFTy~&`W5!0+j9B4`p$<@EH*5@Jht#Ph>oR^ z$|8j~Yd*$yFlv^ERZs=CW&5HHo)u@T%k$=>a4J{VVO3xEME6wR(6~Ee#a=Jga>I=4 zMmTNJx;n)-hL5pTeT+@*W8^L7l(2G%an7#HEQeEE%WDg^aLSBx!O4V`3(#z!orE>Z zYfH{K#=11WHUlxlpOSz2WE&pdm0gwrW0WCN1csQzDCp#T8IwXQE~mC7(J7>}E9q=n zd09oPE~mnGbz3T}=GlgZ*1&iCrl8XZY#RBZy{zUT)5%w0gAi9Q#?K&M(`f^NONH1R z8`SYB8F{R!bQYfyutg?gF4de9HgR2v>q%lq`nVEG6)q4B)1GjQ$SD)hWrzUSFo z4KsZ2B}76>BgsE$fXllwi)>sPN%_+? zXcSg#5H8!oH5bOD!mi$q%N7^(ip$-zi+0ClDCzZO=hEdHE3?iUj>}Nt*ppnBt&U|o z)a(sAvsQoIzI@p_M{W(!1Rcwl8(i~N#x{Ey+SAHvJw&R9c68Y_!`PP~E=L!>TQ@AN zg;i5ic87g=YAReDld=s$cn##yWrWMREL2<;EN<}?xA=?OHWc4e6?}q32&Xc(0lVpyhOKJ{ICc8xD2>3Rx6~0C8Wim^Wz^R41D~VR%KVkHyAe~ZKolHy{YUx@M zB`c9p@NZ6WEV)E>O>Rzc7oUb&j<9HoSy_MHcu4Z56npE39xX*&JcT0=$HiksoOK>@&;bHtVd7 zF()%Q7*}=8zF-4BIUkl^b0L|5w==nLXG|De8K*|0Tjc=&bq&E~UH13Jg0DM##<~qv zNT2i07vKJ3P*3^vRLE%B82T{XuvxR!>#cr2y=B|_p)u#{$@g@>sq?(y&o%pvwLeLg z=~{#7E%5)D3u;uhUDBPG{13uMoCNg zuki4$tV!lbjDI_05Xd6b^$%``C3bc%+ev5*ZlX*#(3~E@YIqTu5w=9^mB*kiN zmLxivRw!gbix`7e=}5K6bpUTk)yZ0u9m+_{n_RY}&?(qxg*m`vTQ$6m04*g@S#h8) z%VcTLjx5O)GIOfi+`np@Sy{G;tcbGCFiw|?T7P2&dEi-Q&F{r+UXm2Zjd2DR!7=KpT49X?HM~MYD>u7zQhwse?;c$qy~zR1R}E zb;jwqW}ge|gzA~%D-9+R({_h#mV0-pAU3o!@*|BcoHa#qImIEG;>WhyIIS-RY%8F2 zy#<$b*^|ty4dsW$2ZF`TzT)N&id)|v-53bzOpX+Vd$=oSr zYmWv>kFl1Eo4Qbi*>l)i>K$bdbh4#KH+3JD)p&~^Wj{{$b)WN>o!?Y`RsZ+!$+VNY z^9A3L5r5ezetN2xm9*_<$TD&^bW8yfMl}QD^_-Jgn-N(Qd`2RY=2-<_ucW|mAb-(t zu$ThFg)}}y#~(CSr~*flC1wM}rHR0PUW9!j1sgds!x1w!ZC4U9E7F|er3xheys0{p zEy)upoluHdXaf@|mSpmbxoF;JF5Y-GxfmDOKOri)VAmCC7b_fQHDb6pOhn=f?8KqC zgzSs_EGcbrIu+(1DvQD>4)p?i5ZDW;2WK<|nxH<+N_X9W9cJ;1*5#Rb;Q4#T2x7hHnsO7S9f+8s*10~dK8nkX z)g`7Yd@N2!z6NEBOm3!d*lRet;GD58xVjp|=rmw;VcI|a7`iu`+4;>HZ_ifo2gd3@ zt@+s}3JCb645Qqf8crd(V6x#$IC;*tj2vod!8!wTTNkW?U=*}E*379KA8SV_1tu9z zU9nF=Ny4h@wzZp1X4b{@LZAx=SHh!C2uvzQe#7yYR1DY?i1ok0<)^@<@=EU&-!1m% znKyL1sWMYuFsnL{RlU&{(i`7deS38)-NX3xwLyK8Pv7L%w`@DUr|%2p6nrJ^X^PBL zv#XItG3DKvygj+K=pFZ)nu4Z7KGPw;=?KU5iv9X(Z`aPb?;YsnqAM}Fc3;tRitfC1 z`z_Cmx7DB17|c2B%Q?K0yhHnQx`R1`zMMgS&d|om5A~S=tP_7`XHehi*LQNssJUb^ zGB?y5SJZJ_U09Gr4c*vZumPMwDD)JpwT<@`p%PXq}LT zQFboHmDDJ6C7sNK9%&he67Ky8yyi^O2=ol>qpl zN=8~GP>HLbQfb}bykO5yDYHVmz$x?S|9VZvO_*`4!qQ%onX}wA-LS#2wudjeDA?qP z-VAAYW;$tOoUlJIV6(y^1LoHUsNRY0ux12SFN;=~B`>TG)8FW^=h< z^TWCjPU&5+Ssm_NdS!YU=2G)c*kz!W87u6ZknIuXDC9hxI?~@aWEnB5V0F!HK5$bd z4Ov@HEjpdEVSQv>&3#A%wlihBb`6(i;dES^P4SUkVco24#*SGa%+VEAxonUy%zVST zJ|ScrR}1c>H@^@zjthG>FxP^O0^I!w>u6lwPo3eX%whR~u%;K6`coq$I}_qEpZkz@ z3bNBLWTzXm!=r-aX+C*adyHX$6|cjt5;9&ZaLtqWML!H{5y+c!*4EE3PKFuAudv4e z%SS2iWzNEP<}_YjLkPJm!f?4INH}8(cdynWBSg3=&OovU4yHCTSGJtY2KC0EzQ(7o z@#X>--BA2cZ+ueF5-M-pE@I1$hl=Y$<+Y)b_E2egsH`GXSn<@L&eHA5)akn2Tv>i0 zFz2LXwc*2}(t8(w^P*Srp6a2BZSH>5=C3*FFB;ky1xB1(cxUzYDoY*QX8buFp_W76 zuK8vS+j;8oxWDC0u;rYu~aa`id`y@{8})+^z8( z-Olvqw}nc|@2%clWoz1ZTKpwlp@NcoNA4c+jBK~~3l4=K6O(r*Jx)lcr~|W}TX;`( zSLMm~PWp2XLC!NYkaM->r=Kci8CgMnxldpIiA=6Gd|#Wfn+*4V{E5?MLT z!n7Ax*f{&e@7!7o8TgB)r!QKqITRK-V8Q8ViX|o1Dh&2|lLa>OqEsBIlLKS|3gwxc z6{n(&kYixL$Yg{`@d&@xYLq#YB=v7&>X9{?B}HJ~FHBEG%N=S$gD*jRjYab!=oiu; zZSsHmJSE4nTA|by*k-GT;l-gIIR!L`jWcC`)Rf>%<#Db6&K;4iw@7+QKJ1tH)vIzm z##DOp-8OM|1*V~YCc?%VI8`7qQPUcf09celOSHnUXi;+QPPle_4N47zjQCy2w@R^n zn$z5eY1;yDVrtsvT9(ecaZ8<=wz_Pyl+!^8{eu$vM*{;*U-pb+wv;djC!@m0xXcc# zfUROoPkDHXLjxJair)RBQ z;f^@G5%ve6JX2VnDYA}`OtJa6UkA)&RlY9slzO^;&Lkg8z~x2fZ> zF2Y>gHRFWM$FPPAo5BQA$Ty~kV7q>kUu+1QaP-K8&4WGV4cF|KGHyWW!alh! z`=LIYY+C&gH?Q)yns0Zo+M-WVWqGw5CqqS*_paQ%;xDQX7Pb0{TKz=_H%5PuU%HtZ zDk{Hs@$N;o=EzQ&zvx)7sMlB2>o4lx)P#(seq-Zy$M;&tLItH?8~H>jD=34hd|oYV z9l(x7>JPGuHU>V-E8IHwTLrI`>^=4A2pa=(p-|7e!9Pkw! z@E6o?s-74N?z!)}+1ies9)HR4U`fBPq~BjM=r<0AvhudZZ=d=~{}WTugZ%sX-r=1j ze`#m1wA)wO?Jw>1n|cxC?CoI!@;%c5@%5=mmQ%BvElW4PGxGMx*1@gD545#Ui)1;) zyOd0=e<$tjw7a@(#YUP>b?~E-N^jM+;h}lE`(fRV!C%|4abwf^_S%-=?OUvB_>uCl z{4X@{z^wt&qt*aX+aowJPlmrws^AiDbA*;R$^_01%i|<>eg7&#%V+g_TO&S;ro@dx*&d7g*it3%QL? zPoB5OuWw{kjT`~A{2u%iHuk77RRQ(p#yItB*KOJUH&z>xM*7}8>3&;o`SU>NN%V3=)Eh;Qc5~jCkqr$Y3unbFj%=EPpEd_@mPJ7Tv zU@|QRF%9T$76a^!XF80Qq+^lUV+JnuU|aMB3tmf#B_n2j^l|c#K^p}62=_P`GHGK1 zh%DNa03th5;y9_~L?GzghLkwe&7IkOUA1B;~1VNVVu=mIw4jL_Dz}8WTb^ z#c8w6ksKsQLt1~abI}sP6{jSv5eT{s%4>|1tAi1^I1q;-TrCd7Vfu(rBD%dn17moH zyHOCj6LpjY8_2L`U!aN7z#x+(6KHO+s^sD?kXf=Dw{1|Zfr5Pv8`*s!@-7B>3Zush zjw$z$&?*A*2qCRKM_L9B zxz$K6;oCs_;nwCu?u;Jm%=J0OxdKuUG3dxZ_du^(GXWDY@nds;OM7ciIAzocnouAN z0QDA92Vpf3j!O@u;LFyqW?IA;RxAUGaEb%txV0xiG>$@+0Vj|(Wyk^BXKrMc#m@D{n9xi4L^Q-ub;tQ@_;yMULudt)% z;u<)%;5~7nI&rN_X1>fKK7mAA#=MP}UxrIKWd#;-j5$jpkghAUWtB&;XT z5e#81`5>q+@hJ;?ss=H4;x=oBD3a!Zh=57DoD`n_xschHW%kx=_xQ68Z>U3gW%r8i7O~ZbcQXBXM~Uz#zc`rJ;>&B< zKIqRIf^;fcHrITrQiz^c6wIyn&-;reSyevlS_bnQeff>sx?od}uc_xz`{QYU(^-H1_=f%o z2(wIeK2x3dwLc&Evk`XqB76CY|LBz8bag}Xk@?W(Yo6x!O_e)6@YdST0zJ;YpWpbn zWkdf_{n5=Ydfs?H$NZ>lL%XYz<(1#l-PN(xM|YfxzB90}!8PjV}Qxi!9A5aaLU{dv)!6+K#G$L;?1 z*ZsNILxnZL!a84Jop*}uviJ+ng-Yv#rOm$5X0~PM@lk*2WT>nmSk~ezYhhb^AC3FV zPKJtWgT)QL;s&^dH&*4LQZaNU-0K1 z4?#9Tq(J_NA~8-0b1+Zxz_C_EmjXbn~z_Ej8aj|@NV@mGw6 zit9N&jP1HdN&ezqNVNbdR#+D*s=D{u-PgRwAz~41#8=;2yt~NOVNDl9s-^eR@1}cR z->&iJc0L`ESL4=pwOT_~(XfI?884AXn+>Rdh9e{>plPN+qB0BO5O~&3fwqZtfe+D7 zO)DKPeFP`z;hx)TUhfES`gWxyxAU<$5p!R1$E-&dUakwTtwa5ptI<$x#a zQyJWzW~8O$c*}QXaNAiXx5vvr#<$)4?37F|y(@#;_Qa=n+ijv!G+yJb3~oEK<#yZNQ56 zM~a`2JNL#F2#fJCj(6d7Zhpoq0WyW~Eov_lSMy{qV+VFL>UILN0g ztj>ut3iJw)PQ=)a5THTue1O}Pcc>j2kc*^U!ifm^(S~~1@^)w~Y2!HI-t(hjoo&(9 zr-0rjgwUCI74i(L|JZS|hz^~pHF+Fc;htY4WdRSJy)UevB`4W*P9#0C6#GjrH%^`A zMd}oE1d~2v8DqDXe1RrH{aQPqZevrqAWr`Be~$Spe2M%O#mQgcE6HDMniPMw+PH+W z9P$Gho45l7a!U}5tib-)(noRXpv006+M<+22Q5MI+0q_qvn>>KfXP;9|4C|guu7gQc6H)YZ7fv$-3SYaO>Y67(19QxR`06Qh0iO-DG@sy8U zlFGOxsftsQ%73OMsoqYH5k-+Bi@(Qm9siBO`V_(iN7B z&%se&eg8S|+1gc{($!fMG3N;NQPlaY<4Nq&H^fP^@#mOkQ=BxLe~xLk#7VRD=a^<& zoHRABB+b}u<=}I@Q{b+L;^gLFbZ&kD+%|Ek)mFq#w*8EvdhN9um7Q_D7G1 zj$3?s^nVU|DE~i3kJz*v_-qttl+lA{<)G1EfE5C4A~-VR^qUGx=4YQ-jx_$QonY69(~?B(ZroWEc!OZq8+%Vq*tK$imK5p_gO*b=)Gy3*9I2N?{1Gi) z$+uL2He|O-j#seqOu`+m4&eG$SSLW4BG5lVeAgyiPzlpWlrTdX zS$8zIHDA?kq=dkrC?%9rw4wb$R^CP*7$0pO1-s5~w0f3*uidAw^S%N0QrrDI-QOJY z>yL#p3U^ZdHC^|Ok9r|3z&#i?hXSydQ`o~8*C?aiV zegjcu!gu&Z6apX=G?%$FFkqq^($I5Iygq`f!>!^zP?S5VfpTl$jh?u0x*sB=(B7>P zMAi^Dl>;RrXt$<^R|f#d-vLMl^%y~VACc@sj7;hl6{Q~l-!O^ZvD0JYbcc{fQ0h+-z!-ms0ZJnC=V>x=;mp|J zc=wr;7a-mrV{kInNHKo`Pt1Gxnu=n42;0V&ERGUV`8@=E6N7|m5C!=i)ab_p7?Rx3 z&_L*as{aDjKQI6c4oHv(sJhRM4fRnqXf#tV*zkacOFjgh8Sk&}>pwkou7CUj)qAqP z_tfbjdZGhNJX+fVEaVawhQzSfO|`< zn5dfOgH83VO-+j~v-<$XI&Ghu2R(SO=IP*g31kf-fC5Qd!@-Vb3fxTC7grYDW&|E^ z&a8kJ8ZL<&V7G+1fG&?(^2DVJ<*?pxS0E_zc~Ex&k0qe2W1FpCbP}~1&<nhvV<@WG|kI_mJDBY10WYH1_)wzj6F)v#P+PUDmt+)zW|WS|Hq)I)wo z6xPG)p0UZP^Fw_TCkbl<7WVoIct%j$94nyW15{y}0oH6}zKa;pI|@3>h&gh79g&hj zD~M0X5|{do}0*R2nP48=i1na@y0ERqau>%$Q|>0ZXK zYXLl2rNOL9UsfeCMat?}AK6u8q?o>*yeos-H$YYiw{L+Y5pMqmB&%-+(SEvq zz5hd%VYB<~^!4s13iX#yf9dq5anl6~>^E;0d5k_oMLR#5+7c>m} z48z}T_Zuwi0!)Bdno4XgKaO9RH5mZy0_|`wV9SinAf5?#pj}>CMffK_%r=QXy6PJKDFkY>wGm z{oa9x2eua;_4sQCS@TKOG!!(A`AlPhGZ*}(i|lJxeWt4cl~q7+Jg6-9Da&~Tn{Rp! zKj^&Q>HQ+e)609=vOd<-A2glvnNEFo$ZtBwUbyTtT@I*TB2%6e_rnYZEo{m2%_nG=X zPz^jymgUq-h#!|m9b2}0_Pcg(>NW`dTiK#E)^IRr==2#n|ILu!Fz^_NdODyO2`RN- zUi;G8W`0mv=u;McKdZ>2@g97y{bBp|MbLma(8t#Hv&93CRe|wKA7oty<7j?N&bpU> zH~$+${=9~5qc5-R1MNY`n&~z9lb;vDqM)+Gr!4Ute9(Ts-8&U5Kjtex7RoO7^gkH7 zKSZ=QDo(KF-E23_PFx61yylyDjlHtSI^Xb5Ff8q2vz9@3hie0yH@56smspcIXlnGC z8o#CXn>u!ApXo$E)h%h6afw?PG?e=c9Kt%SE5z zVnA`}xd!4goM3n|mT?EaE_vpT5E_bKzS`6#`q?`a-t zwhu$`YmT$kC)ncdM@PPU;e)L6iCWL3d_OJwxToVm*Zr<-GwAD-pJ2+9Q466afT1aX5@>^eeYx7!AS>#g|VTMyZ z?gww(e{1_1kiWW{1^N{CKAQaQ+6P$|5@q-zGp^9yPg%-8g%IW=#`8 z(?y@@V&KxO-(+Lwulr2b1F8j~j0;h03;AB{dkqg8b_yRc{@UTkt&e-y+A-FAhCLG> zRfNs&=?wpnDpTo&%$ceSkTx#)M2_LcDK{-F-H&>Gron*fBqp@zo%|NmSa+g?dgSp_5(gO8 zP#-k3`V6h#g6`Dy2s(IQK+*rBBvnr8`q(F#G6S`th;WkGt#g}GK~pW{`H$NDrh_}l zK2zrhs-wUU4OILFEeVMpL7$}Q-)VolebeGkD_|7`Kl{;Ik}RXcg}NPoCC{15QT$b5 z7F=Vi`+P0^zjt*X*j`Y@FrkIGCaoPw4&R$s7k$?MvtBKpaeUFTfOA=wB~TQfR+c z&`*xjCgUl5L@Aljjr*vb#OsvYdy7T!}3vemRfCDd*vrOtbyDG%aykX@>wCx8;b66!9S9 zx8KsO$Z_F1rgCUsBFgRf1Z#i&{{V-!spQjYmq8*bqID{KUyiZ$gN^gb@7+AqEmc3?_s)nGj+qA%uit^l&Uk z3lrSU|$V5fS0)_K#xNo<+_01LgCd)~yT@F<4j#DJw%#+@MixCOGCRq7VJ*OMQ2yM?kp!94aBFDXkRV3a5+gSece3o zpe`uh4BMTDAG&Wi?O^5`Ek1K6YwD?IN${5pF8kd12zZ9E)%V#JKrVzv)`B?2-BwSb zZ0RLsX$@Sg{YulG9pd0$R-X2(o{P6%iP`}Ue19^vDh@c2RP7mG*8R%Wm=B@FnH8L5 z4HW{IA-oVlnT80;n7_pj#6TYRh$QMAU&mME6`WK|=dj&GN!Zl%$^zKh5_l5x69968ribf~L22R_E#!Kl)q%u}+mpmi>N(E^*3cMGH1XdyGXsh&aT{;5 zD4-}2LSo5`Knx>@c=pPO8n>0-PWxus&S|hRZ5m-4M_G^#sm=)C(Gnpxr_Ou+y-N=- z?UVp`{RmqZ4bB?NgUSk@vO?I#1PkN2_pU#@&UT=UX2Td;e}*-k4XDP&vXy%VADp^> ziWqfP9A{yl+t3|Q^a!PiE@weNQ6z*M^`tyd-&cc2s4_owkS#gH8V&~(NA}2h#EevX zQ{GcQRBzY9=66juTiwH&dIPGy&j=n{MkC1GYI(uZYan+!Fv!-PWKUjUr)Gmw^S-Hh z*1pQRZ~3Rbz+QQaHGT0@nM~cCBor#TxP<{lu@KS~P?X1dKI%z*pt-N{wr@}Ssdl!c zW2X#+BmqU22<)kPPwhJyjc zApyg2&!Gn$_dC1`Am}JR&X%2clmvpCfTB+TXx}=AuE>Z8uBeqQY}@V;DkDm2B5WnQ z`7E7>JeRezr5)fK-E=gdIwl~DA_HqE4JgWlpyz}ltf42M=oNyGdNdDo_jTSIVE;^Y zuqB-lSM(>Yq9|9Mc6Es#sKsA1 zz*Y~krjr5H5RZJHl8k`LBIIVT+@dC+sui+!BcLkbpSLu`#{1UxxIgy@o6{apbqY|f zfT~a^IudPMdEmAgmY01}8$UJ01=^8yNHtDd*JPuYIUpFhCn z3B~0B5QoTuK#~wpH1Jtr4TAy2N%2L}pIO6DKrt+aoCqicF(k;{0*d2e2uhB{7YPEw z`1S}6h_X~Tg(!M)X9oN#@bYlFb7gsHWf}Zkpkg6tS)j`k(85sQ@i$=&A3AGiW<-lv zh=$L^o*4C`q`uKWwT}G27B{9xvPrqH3<1BLqRVJ2j8vCIExgI)$ukEP93EI=!9h0U z{D(B@84MPsFd>JNL)3ht;72m@)tqGg&oEGFM&O7A3Y2bOUPAP8i26&kI~(3r1n7pq znM&QvoQpeBiJXdEO$%#4j%SXo_R*7)<0c(oN` z5#K?LfhQSsUkvItM4JWlZx%U?9FEcyIwr9(8XSnDYNmrKBUM*M)y&$iS;0%AnSyC| znJ_Y!ktlrabx=dsC_ERbj&fO_J4Xss54rN!a|cB=5(hYZqFC+HY{F|Kj~G~w`at$; z_8AJ*m+)LC3Xd$2<``Ow1O;C(_z3zpa0Y`|0OAyy*H@%KQ}H^wdJ)0Q8qGL4e1@+L zZn*@CNPCoD!OafD(}Yz5CCn*|0U89uIR)S;5`Fj;nZ^)>9XLee00LuQV19;|XL$L) z@bZsv39I?W0e+Le3-2+G%E%TxjqFK74z2G$;8Y1`XyAN=k{PP}8P)4Up{$}{R+TTS z%Cq9n>RcZo?C3)kNG-QJ0;>Ke%B(GoUs?8{zIf{ketkWws{b&pU~9ymR?8}Cxo)h% zn&1xanBjX0BqHgT*gHWRH*#JI_8!O#_lumAGnR$RNbVQwaEmhWhy>8mjp>Il%4-1n z|Bc~eP~ZwN?c*hfJOI}KuZVbh5a{c|6?lY_=AKj_$Wz1S27KN710Mk31%P;)RV4_A z)BXrv%_R_wazBf>RRb3Ddhsh9ghi*(T5<-F)}oE2yB^Wfrr?A?{3q}>;9BZ&R3F>( zyJQkRSTq8s67CR3^t)$W0uALaeI81JZHTTliIg?{QjU=7q}-A$*xNdElG3K{qqL&v z@iUY*T_~k+2ON3{HAKD*w%HjCaDGnwQ<_HqgmXIo0*I6RIZg)aH*o}j?E)Oq_VA78 zxZqKqC3gw@kXmseM;Za$%Gl@FMU56w9ymIviFuoNs@`6fh0aWH6kygkrv1-{<>Igt zVJ#9S&W(N?uO%ZWYPc>e*n#&glNgcFATpZn;ow+oPS14){68Tl$AJT4FVAyFBch(_ zii`876;|@Y98t~YRJwUDC;YG#PQjeI&?z$b_D7|6aEDJ$+M04+cdJLbLAQInVR4rE zJ81q)30^3;jDoiC;w)%`t`O&FaCk~8mu}b)RfSV95`l{>E~ntD23-1sD`jN1;I-`L zEVlw5M6Af)!zqi`F@m5Lx=0$$6r^53LT|zm0XCxM$~m;j;TP6~*up>x;ATCyh!+PS zCJCA_ICuj;*O(*DibHf->snZuV>Ae%Cd42hY4{FD4Pvt;84v6D7gQC-sgN1-L}GB^ zT9OSu)P+nC8-nD=`Of)&Ab`Qke}NQSHn^$ z=qL+RQjHZpV}<7m_&PEkUq2l(mIRHJK4YcdSRFJr`oQ^e@;2=^wy&Rll1q7x`*K^h zFZ*)4S%oQ-QR*4=WgJ)^dXkk5%B-O3nH^LV_!I@6zU?;9g7Lu;R+0YYu`i8n^?BRY z$9#&WCz<7*>%Pq9^;6*HE4#|G=r^^kkAA35-?+Zj>dkpy-MoD^q*A}5d|SDxgJlDp zZl-B@qA&y%xjsei*Nt13;OLitq83MEEez4PnESn>!Schta_)>3&?PMEc~s6)1HP<* z4fTgP4c;$28usUmZD{czHS-qjJ^X%p>-HNVZTdTdZx3#s4roj947Ii=DpOEZ=u;Jb zo%ZDY)|G&&4oYM!{IG~(OI!R!t)aZCr%Hu3byucHN&QJ4J2gTde7?Q~ujVP5&`vl->^xj$~T?m9qcd^?o9bePW+5BTh+T zOjl@eaCUIh4t30li&X0;HiWq2PbLK7G6iah|IymWfCy8DSP(F_I8sG$F)TLWsnreHF8$Gp`^YXI}x1gxrH3PnZgw5Mp9qa~8`!<_G5z zrgc6c#AHH<3ke}ECWN?@5aMz|h{SF2wS+jXB!rks2=OX%dNpAx)`Sq#2_a_Uw6y3M z`+i#5>?_0c|ECuDDrN$Y zLqdq(PYCfKA%r&}#KVLTe~=JD(t_wejCG_CSet@&5dNYZ*#42bms8{2ql*U~7DaU} zPD5LvQQ$yz&OY;Uc9G8vl^5zO7wao$smhZbl_MRMw7X!BrKF(Z zHvmdr?$^4&Q10~D;7~8x+nt}cIjHFs`vN(K(n^WS&!{m~2MTO(gc(KL2U1s~m8yw8 zR^uhS)uWR4YoK*@wZVP73w5)X=fR0D(S9QeYpy!V4(C)da8Q~HQWwe#=`YWN8(&@n z5#bx$wFChikO{iop!z(sys(D)+Y8Q{0BqxNOxxhS;aM`6IJ(opV3Jcd#{#+aU?IpK zoj6NA=)f)zwDIOZH5=c#gDig?st#=eslf&3G*rPjX3cFtQ$J!*309?GGq!AV7e(7u zQfOj$&dS({zsp+?9A#$7y$IJ^?sTcu$^25=CMQ zN{%rlb@Rs%#QYJ46MIhaC{z+Cnuap}6~Tx_D6!t%YY}>h`4OT>daWibH*@Bp&?=&g zLz(}ED0UIdgq2P&S!YlgR;XGRdRLurUWeXs3qCHapk=Ma1&tkukjGbg{d0GBGpSV< zV6)Y)x(HdoYv@&Q2z#}uq2(ZBTf}b@#K-BJb;$+Tj!UArNbpr~ILb#6VhllLQy2ky zk^`+8p{H^=zU4kBK>&RatH1dWkD-}QkzTgq3AnSMjXF!Z`Yrb%NwkGI5)D0^l7yQZ zgoJr3XGy$9Nxans60fy|kJrK{ciPGTrT79$^Vh=1XcLOqDrAszUP^(L!<-#_(cW^m zNYZKLqa6}5*v3agN41i?kq%7HTfx2@jK!Bg@fPdxwC#p%Ax1rRaVZe+dY<%6RP)j* z1?8cbxoK-nQyux&&~(s@C8bxUmqDd>!F~;Tnfrt^K2P~33bIH6`J>}Eljyxr15kr- zbmKDm+r8!Pmtx>60dfEiW~pXyJ{t?ih;!Du07zyT=MtLHBbqZ(G(4%As1`)i@;n-@ zD1>rwU@@o|1D^+dH9N_i%Z4XEoM}2Ibs0 zskg` zf-h;_b8!b}B4uMb!}2e!6w%+B@CFJaW`%2A9N))!|xz z@#I2HJ+u^ar#l-c5nO zEO(V3xf_2X#+Yg>5gl}WAvLy87V9{lYlQ^%7whOgK|K- z?{w!z)2fNWf31)&cXA74hm?%#j_!;G@K|kKsK-XMU|oX}mO^B4=mZzv95d60w zgFqT+wg`efxgI7zpw8YwO)so4HP8VdSjFK4T-YKcxcH6}qtXcxrIn7HxzqikN?cul zq@Y(VT374I83-NJ!6x7w&=M$^tHPlO9aMW$CBWb?xx5DKZ*T>y;*e0l6TlA!ej@|) zS8z<7z{@BOtT6F<-uVT5l)Aof!~8(vl>yPXGjKpYgF~(0+9MODcyQ>gjd4(-gAd}l zzJPdr5RadS{BdW}lkA{-59YsMPRJ}l7|F(vsSp6-w4(?TBmN)w6u=F7pOYFLn}Ffo zW@e@#{tB9d)>D7?+rLJ+OEGiZz^-^ZN0{$V+h=ECo*=|u84;mtYx}u_Dg+nnP`tle z{~BUOKkI_qb_r=a5?vCk> z9Zq6^S^PX&N=CN1+GMtO9PfI=PGoh#`1UMTdC18i7eAVNzPD8#(g z0mGJ%wS}A3H5V8&@i`+k1GN}&EWd(2*(0bTe&Qp}<9S?BzAp&XrfpONo}tj6I=T*t z=Mbc-#|D(K!TOK*3PCzr9cz?j2ue2#3o2j__G@b$?ncgiM#OOlWPg?tnu+L%A0~TX ze6QCIL zDO}twT-+$iNGWn%;%VPLhc)ML*YN4Z_6~BD5}ocdKgJ~LIB*~awiYbRJlCbBZS!^q zno+F+aln)Rw3TtWD@5pdCu4U*3|POpW*Ar_Vu2Q&)3A=@{D`PHD<(7<2`4+8H^VAe zQbOwn_xo>R0YzW^oP&{c&Nk_9G3bR3M#5^u0gZ8SiTA*74XdGj+TmCdv@OR+S301P zm>9v?5#)ew9c&yhcqvW-Bcp!@P`_w5BY&Tfdq?{f?N@cdjA~y-HCxl^&o~OEN$7n< zV%pyjP&D$!_{9ERG|XQLDBciX#sdmke35#fK*t0^bW=c)BRosLAM@mR)U2U4pg14| zANRC-4zLDk;$5ElU|FlLtaW?%k=kE2%$A;FIq&Yg`(`Q1y-7(5R3>qzfl(wuQrG?x zY^ZDx5Cw4v@3Jo-vgUdJ|J!>~Z%X^~oWnld;;%ImuXiRPC=BW~-xXhy}B`iw~~czp_2BGw84EV=MYu z!$3eWDEP#9DGwdbyWFVrUVQJ$!z(*O;QG6Mn5{bn-oM@d@vVRV7JFruy*BS3v$LbG zv#&3(i#LOdZ~7MBgpGhN$j6f4b{39W0_NwCPvG04+=4et(mA|kpHBjosGf{+)FNbg zj~fqDRmAPa=43!sEKu;`oh!Gmcus@A8q>jm>X7i+5>OQg&wGem6Hu6i%9RqC91|h5 zo#+>I;4OxXOS|OBfMQ4tiE_gsaiGE)qQLqCiUDZ`_R~@D2n#Wts252~WDRscF(HCU zj|qrs(pYLS(apVGT8v*AP*latXOwFs2?;lI1B!exdUZfiBfdymb{Frd#Kom9d7X3`Ql{98k0fQQ}5N z>rXND@_?d3crk+47Lm0JZhX|+?f#-;Y+=`ql{H9Rv_RkHjy!1JvHNRIveiS64Xoi* zKyg~Y_ev?Sx3(kgK&(T=howK zf9n~xGmA3vUx<*Yy;S?Fgyx7j96V#}Kd<+)?ii;xHYXQX-X)Q!D z5Vr+OW(Coik#e7?>_<&eMgxj5G5OwzBf1xP7n96nSVLz(aa2Sg@h%qCq<9y0l3Ly4 zckE|B>X*w*J#rTs+W%usZg;;z!TX*-vFHpgK7}8G;QJ^4GdQCJ_eHXCSO@O?ML8Tw z{#OG#)cH7?V#Zj8$x4Aj@GJ)1D9q|>+dfe5riIu7j*5nNSCK_Kl!-Wd$d(;HevZqwV<#trT>% z3Sl=F_Fa~KGfw+3S=7)bvn?8^T}33XBgLGK)|eqqJgHZL*tM9rj--AI661(Xn>-0^ z(ky9Fv`PB~(I!7a$2inWOq+t2!V#M`g%a9G^z}%)iRl3CpP=0oy;Qkkw~FGI!V#M` zB@)`CThgQ2P5LjW-B2%8uGq9GeJLEVX;ao1;Ws*qPNWU+1d9%M3b>zh=;O5Pa*IBC zoEXVVz)31zD#x)){VKS9i9qg>5PzIdvkuDgziATJ!e9E0v4(fcIBzO!2XXuO7MUIVk>rsjE%=p+a zoPTlt@lSh5iv3xK|lo5{Iq--UYTEqgjQzZ(nbB8aj3{Z2u=jnC|u?9$w!LP znv396@@i=-U{;a<5fACk#mq%ciHKy7K}UTn*z{kVBdAZ|gbC15DCZS8#XOM{0`^fb zqBG)h=Wr+l)h7{6SOfl#IejP>^AvMdpD<@)45C--0>xZTu_01FqK=g-Y*=yF!42h< z7>S5Fp(?oV&}D?=iGxa_Ig1^T=xU9FHs~TWomsARh4tW!So(pJhiW47P^K8WTM1r< zpd8_pWjkoLg-fP{J~}1oyHjFo;N+%F5JP%+Ia7gn(32zcw-`jhQ<(GN%c0d_MkQDR z2vvjdt0eIosA0*N@dH*mcc?=xVo_kRnFAP7hwsTyFqaWvqC~6Q3nyUBKv=v~!cyC{ zs)#LM{V=xBGi1o;Pa|*jCi@L_K|>pycf3vSl*7@e+>yX=ob>uI`d~b`ec|y49H6~E zN<^i?38yWauWw%Vl&}Wr-mYXD0|bGGJ!UpaU<2~73m%Y>Nw$MfMs_7wdDvHZcqi@A z1%KrjTMhC$~|PUJy(# z@ulOD-evyu`t^a2l->R#R5y5DJddw8Q)I5xY@lf1R!>GPVrN;a?Q18wtcKU#?22G^jW4@~HFv;4 z7umH7%Vi;?0f&lOBOt zER6%Bi@-Q?;=t%7Fu9k+ZBt8Wo1D(bm%Xl{VG1wQlCW41OOqj!(U}$CJ)MJ3V{LnI<`SHF32^3%I1AvZkl-v5acba@S0T+3A>5KH<|+$loHdR*O?!Sc zWyy_RZV;nL7t}?bB`;=OluGE69|xvP0#gtNrd$G3Xeki$njJ5%mVD?B6_x_*4+7nx z-xUfmiRtDP#LkRa(n?EVbV)~6c+*YM_|xiu*jm0V7Fmj7mcL4p@8UQx)e@MJI50I5 z7|K#Cl)?%f!Gg7E#yYl?h~MMaVXdVkx?~ngjD^@Ji-A5su!-9rvmnBt>%=xwFSeO1 zA?7Shz32wIu`vmHYq~{e(OWVs21};JXvw0R8dPvhV6*$rHNvV5)>;vl>*9nH6rWKu z5)}Jz3Q6JGjrUs*3lUf&&4SJ&8q$HUdeEvxjZ_;&Y|c>ik_!UF*H8tOS(!m22V6N_ zL$yN2wqU!#X_bNoa~)-wtOI4uSzeP>15;_Mpnpj@uYvw7GmA=`XhXy2#MPi-fDy~g zat4_XS(|eH+`;}=#;CX;CEUVEE2|80e@2Iw7QDQH7w9K4W(zO>8ZJ@Zu7P~AR`~k_ zHDSBt<-qV!W`z4qPQye@J92@kbV7_9vUgJ`nXun9Cp(;cP0m0QkUf+$gLq43y5Yw3 z-~|=7T{tpP)H*kf?t_M2M0@P`hkl^{mx3KkWoshZS?M z0Ci_Ix1l;Z9tLv_4szpk)WQ9|b(K1g+Uf4{<`y)gTUi7HDTqK8N2KnqEkHv6Wl%8X z2rC95XvGYPo&>uQvZi&yQ8kNTNkAe%6Cs-#puGx5w78a_ak=%qyf$hdy0kW?b^MT=e#AuHJd;_FMj(>JJRn@3&n18PfrvKwIUa-2p~E z3vdVk(I^k6%q-Zf4t#aYfi-B(5klM=IIMwj-ULG+G>@=>zHtss0ASULl&z!Cun@aj zIQ2BA=#9#(sNjwI?3_^rkZxuHOe2@q+=gbby?{!qLA7nPJ2Sg02gKTgen}390K+2z zt*{~FR`cgGg0YZFFxw0#^*P-seNIR9vO5VZHI(GoMzE=P2lBvd;^j76!YW?H-vw0% z=T}A!^aNajWk5KU%bHssIg*gH8+Y}%u&*L+-H@h%`!Ag7E4KmTZUFWFh@otRQ-Suj z!)8MU0d0xfHbPDTNAOf*os#8lj3sVlacfrMa+k4j%mn)=S+9o;aPrPFkZg*qqRI9Z zc+ZuQwYa%VviQamd`M(|@eM|@HYS`#4!4-%UNT@kJq@NCv#`$qHM+VS*1FJuV~XRI zVfD(Y@TgmWx`S50zcb%P8fzdN)VJY$CKvO+U~>B`8$&qaI>x|5P#JPqBaXpLJG{ba z7|tn@8}%$?i#&3i@~a5^HN3ow7s98?;3>xPKl;-Nj*T^75_tIv_M5*0ZvE4B*^hOy zl)@*5{5vJLOZ^rJI%g~W9viTHx;mjbzb|Uvp&<{`Y5bROr^oB3SSni z?YEQsS*`0Mu)A=l=yuW88Z4U)^^mNv+VJLW!(!v``WWmh7|PhP7Jo+1`VgSdWvmZ` z;4&C87y&+A53l4YHy_WD$j)6K<4$nPDB806GN_G|kSYJph1(a{lE&>!I3i+06Ef!B zX}{gRbgWMg2n z)2A&774^W=p|?jv=826#HmmY|ZPkbA83DqG{27;m>6d)zmqKNo0(=R^JmS+9hRnS} zgePSO#oz%x_=jnkA#FiWTjtZ2c`kTwdhKlak$|@SlT?{LZzCyWG~GFL`_NXcXWDNx zLoqV5?o{2b+REBu{F!AN%1~DJoq^i}TZcV8{;cW^^+!^4^PY=dX1jVjk1g*EXpjC) zTGmE;NShth7WuSA9;H|9d6O+}{XpCHR1b;$LzYaH6Ur?0l=?C&0JAawPUr2;tuI0? z8e1Xd+>(1~chlI4PSBCgJ+YA<%A@WT+$~@$55jqac^wb?RAIskSA-Q!+d$$WsbDrIIHlJgLZ&8lE)dDGi>qMO-mkuR@5N&c=yatW`6eL+1)B#8|2y0yP{dFT|3!Pb{f)z9ki>X|rHl zMi+3;R;Z7;B)U)%QX~mc%@vzV+|7MtFo?`TIZ*;%+hhjI&wF`3-2fxdF~AImBhJEf z04-Il3v0{v85h%upOuR?bhO|Kr=WS23*Ma$JDg+}R^ixo!*09jHuVX!GO%Rt!-?@Z z_-sxMtB7w1F#N(Ziq=qe0YjLTSq`g~R$x{RbN6rxv1JV_VI~GPgLCdoa(psG0aRzW zIOY_v%mH@`5Ddw~IaMfb2`jBLGtL#jn8w)=*&H(<|7io7xie`V8gX#o=2S9Zgw=RX zo()ljRrAh88;+S_1&B|aaD{g`RtYSpoNnW3Zgc|-BSH))Pzw6sAoHU+hj3q63ac2~ z92#0NVBE8^JP*aQx?FIk-)vad4ewBH@Z^EEPf!#nfE}F)KuR!zx?q4^nFjkLJ2@H= zH`?L6E<`YM9kK><6*!lh_#S|wAUtG3F%7wZ@-u)H2%%VVqE!)`XAIOK^$bLT$^wrT zZe0(6LK(U_SfB#-ZstG3C7cS=2CID`oV+x<6jr$Aol9ISn_CA&n}y{N7|A09#3Jh# zT$Y4Wr(q@t^_I$+q(X{N-;fAXiImsT+;NdGd%`&Y6gS1TxCAroY8s7QoUCzb;X$&-fu$0zaU|;)C>C~T$vTEP0ddTd zRSX3 zeieYF=Q8p$o70E`FaHQcbSdBgW;Rc<3hs>D9{K9n`pAz|GR;*vYh7hkYhm?CcK8ab znhLA?*nx|z>QY!ez@B`KRb3$uldS4OSlz?+Syk+bVz6k&v-Al#XB0%RJY&CqWrFWUP6Tmc@;Is;Q|( zFfg5(a%YR%3w<`1ea=DAYtW4s&GK*>OzMSR>8|9Pb<`=S)zPP*R+}LfaFT^3x2_(X z9FPM}iSHYCQvH%!3w`Q!n5C2BM4{4u3o8(-h1rCQTUB4rEinQ38uNccpgVnxoK1_% z1(2u1No~rRd8ZwjoEXiVGRpiEU#qqhtYZu3{b|@+!Q{!zG;LbTQbKkP9!* zFW9HaL2k?`Or7*>7-VD&&YPJ=yr7aC^ATSDJzm^+`4}%hfeWx7r*nakV`Oq98d@t7 zi5i(m6Fxv19`hZ9R)NP3@L~WQLkDNe68ZHRd}YF^hz{To^7u1c0!v95y&wV`XQN?jEYB*~QS%kxuRo-^u&b^pElW zw4+I$>do4f!40Fr?J;*_ZS&-nU0(e$e%`$%?^AHO!#m~Yq!?1i&Xo89ypCV^V$ySZ9_&6$lV{>b~72EWq zipN(-$Yn8PO9Mya9o`vcTL#&)lkAj(ggc+48G>nrzO+JrTJd_{-=>#)`p|-MTM=yP j_BC}sYWwTXzv%R*kF5`UU!mTp`sI<`9GN1YG^hU;sMlz# diff --git a/.gitignore b/.gitignore index 4f160266..d235da80 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ .temp_ag_kit/ antigravity-doc +__pycache__ diff --git a/scripts/__pycache__/ai_reviewer.cpython-314.pyc b/scripts/__pycache__/ai_reviewer.cpython-314.pyc deleted file mode 100644 index 29692a7329820c1ff098dbd29bb74c043531b133..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7920 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