נרמול קוד (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