# נרמול קוד (Code Normalization) מסמך זה מרכז את כל מה שסוכן או מפתח צריך לדעת על מנגנון נרמול הקוד של Code Keeper Bot – למה הוא קיים, איך הוא עובד ואיך משתמשים בו ביום־יום. --- ## למה בכלל מנרמלים? - **אחידות בנתונים** – ניהול Snippets ממקורות שונים (טלגרם, WebApp, ייבוא קבצים) בלי הפתעות של CRLF, BOM או תווים נסתרים. - **מניעת באגים ברינדור** – הורדת תווים בלתי נראים מונעת הדבקות ב-HTML/Markdown ותאונות בתצוגה. - **שמירה על היסטוריה נקייה** – קובץ שלא משתנה רק כי נערך מ-Windows/VS Code לא מייצר diff מיותר. - **נוחות טסטים** – טסט דטרמיניסטי שמקבל פלט צפוי, במיוחד כשמריצים Async Handlers או שירותי אפליקציה. --- ## מתי ואיפה המנגנון רץ? - `handlers/save_flow.py` – כל קלט של המשתמש עובר דרך `utils.normalize_code(...)`. - `SnippetService.create_snippet` – קורא ישירות ל-`CodeNormalizer` הדומייני ומבטיח שהנתונים שנשמרים ל-DB אחידים. - `services/code_service.py` וזרימות גיבוי/שחזור – כולם נשענים על אותו API כדי למנוע פיצולים. - WebApp (`webapp/app.py`) – לפני שמירת טפסים במצב מתקדם או שיתוף מהעורך. - ספריות חיצוניות שמחזיקות קוד (כמו import דרך CLI) מחויבות להפעיל את ה-API הזה לפני כתיבה לדאטה-בייס. אם לא ניתן לייבא את שכבת הדומיין (למשל בשל Bootstrapping מוקדם), הקריאה ל-`utils.normalize_code` תריץ fallback פנימי שמסונכרן מול `CodeNormalizer`. --- ## מה בדיוק קורה במהלך הנרמול? 1. **המרת escape literals** – רצפים כמו `\u200B` או `\U0001F600` שמייצגים תווי פורמט נסתרים נמחקים. 2. **הסרת BOM** – מונע אזכורים כפולים בתחילת קובץ. 3. **איחוד שורות** – `\r\n` או `\r` מוחלפים ב-`\n`. 4. **החלפת רווחים מיוחדים** – NBSP/NNBSP ותווי Zs אחרים הופכים לרווח רגיל. 5. **סילוק תווי רוחב-אפס, סימוני כיוון ותווי בקרה/פורמט** – פרט ל-`\t`, `\n`, `\r` שנשמרים. 6. **Trim per line** – רווחים וטאבים בסוף כל שורה נמחקים. 7. **אין שורה ריקה מאולצת** – לאחר ה-Trim, מסירים גם `\n` עודפים בסוף הטקסט. אם צריך newline – דואגים אליו לפני הקריאה לנרמול. > ⚠️ אין שינוי במבנה התוכן עצמו (לא מיישרים אינדנטציה, לא מריצים formatter). זהו מנגנון הגיינה, לא opinionated formatter. --- ## איך לבחור API? | מצב | מה להשתמש | הערות | | --- | --- | --- | | קוד בתוך שכבת דומיין / שירותי אפליקציה | `CodeNormalizer` | מומלץ להחזיק singleton (כמו ב-`SnippetService`) ולהזריק דרך DI. | | Handler / Utility שלא יכול לייבא את הדומיין | `utils.normalize_code` | קורא ל-CodeNormalizer מאחורי הקלעים כשאפשר, או מריץ fallback זהה. | | טסטים | ישירות `CodeNormalizer().normalize(...)` | נותן תוצאה צפויה גם בלי bootstrapping של utils. | --- ## דגשים לסוכנים בעת כתיבת קוד - **שמרו על אחריות בשכבות** – Handlers לא אמורים לדעת על פרטי הנרמול. שירותי אפליקציה או domain helpers צריכים לטפל בזה. - **אל תוסיפו `strip()` אגרסיבי** לפני הקריאה – להבדיל מ-Trim של סוף שורה, אנחנו שומרים על כל הרווחים בראש הטקסט ובאמצעו. - **תמיד התייחסו ל-EOF** – אם אתם צריכים newline בסוף (למשל בעת יצוא ל-patch), חברו `code + "\n"` לפני הנרמול או אחרי השמירה. - **שמרו על בדיקות** – כותבים טסט? תעדיף assert על פלט `CodeNormalizer` כדי לא לכפול לוגיקה. - **קלט לא-מחרוזתי** – גם utils וגם ה-CodeNormalizer מחזירים את הערך המקורי (או `""` עבור `None`). וודאו שאתם יודעים להתמודד עם זה. --- ## טסטים, דיבוג ושחזור תקלות 1. **Unit Tests** – ר' `tests/unit/domain/services/test_code_normalizer.py` כדוגמה. כל edge case מקבל `assert` אחד ברור. 2. **שילוב בשירותים** – `tests/unit/application/services/test_snippet_service.py` מוודא שהשירות משתמש בנרמול לפני שמירת Snippet. 3. **בדיקות רגרסיה** – כשנוגעים בתסריטי Save Flow, עדכנו גם את התיעוד וגם את הטסטים, וודאו שהמחרוזת הצפויה לא כוללת newline עודף. 4. **דיבאג מהיר** – בחמ"ל אפשר להריץ `python3 - <<'PY'` ולהדפיס `repr(CodeNormalizer().normalize(...))` כדי להבין מה המשתמש קיבל. --- ## שאלות נפוצות - **למה לא להוסיף newline בסוף?** – כי המשתמש/ה-handler צריכים שליטה מלאה. יש תסריטים (למשל פקודות חד-שורתיות) שבהם newline ייצור תוצאות שגויות. - **מה לגבי קבצים בינאריים?** – המערכת לא מעבדת אותם עם הנורמלייזר; לפני שמירת קובץ בינארי חובה לעבוד עם storage ייעודי. - **צריך לנקות גם בתוך קבצי DB קיימים?** – לא. תהליך המיגרציה יטפל בזה; סוכנים נוגעים רק בנתונים חדשים. - **אפשר להרחיב את המנגנון (למשל טאב ל-4 רווחים)?** – לא במסגרת הנרמול. שינויים כאלו יישקלו כחלק מפורמט חדש (formatter) וידרשו RFC נפרד. --- ## מה לעשות כשמוסיפים זרימה חדשה? 1. קבעו נקודת נרמול אחת (Domain Service / Utility). 2. תעדו בקצרה ב-PR שהזרימה משתמשת בנרמול (קישור לדף זה). 3. הוסיפו טסט שמוודא שהפלט נקי מרווחי סוף ו-CRLF. 4. אם תסריט תלוי בשורת סיום – תעדו זאת בזרימת העבודה (ראה גם `docs/workflows/save-flow.rst`). --- ## קישורים רלוונטיים - :doc:`../workflows/save-flow` - :doc:`../architecture` - :doc:`../whats-new` - קוד המקור: `src/domain/services/code_normalizer.py`, `utils.py`