דילוג לתוכן הראשי
פרויקט אישי

הפיכת Code Review בסיוע LLM למערכת תחומה ומדודה

פרויקט אישי: להפוך את התנהגות הסקירה למפורשת, ניתנת לשחזור, מדודה וניתנת להעברה

מקור
פרויקט אישילצפייה בקוד ב-GitHub
Tech Stack
Agent Skills · Git · GitHub · Python
AI Engineering, Developer Experience (DevEx), אמינות,

אמל״ק

הבעיה
סקירות קוד של AI טעו, ותצפיות שימושיות של מודל לא הופכות אותו ל-reviewer שאפשר לסמוך עליו בלי מדיניות מוגדרת, גבולות מצב וכללי ראיות.
מה בניתי
כמתכנן ומממש יחיד הגדרתי את סמנטיקת הסקירה, את גבולות המדיניות, את אסטרטגיית הבדיקות, את האריזה, את ארכיטקטורת ה-capabilities ואת תשתית ההערכה לשני משטחי הסקירה.
תוצאה וראיות
חוזה משותף של ראיות, חומרה והחלטה עומד מאחורי שני משטחי סקירה, ונבדק בהרצות מתוזמנות. זהו פרויקט אישי, ואין נתוני שימוש או הפחתת פגמים.
להעמקה בפרטים הטכניים

זהו פרויקט אישי ויזום. הוא אינו ניסיון מקצועי בפרודקשן. הוא אינו מחליף סקירה אנושית. הוא אינו טוען לאימוץ או לשימוש. הוא בוחן את ההנדסה שנדרשת כדי ש-Code Review בסיוע LLM יהיה אמין.

הבעיה

נתקלתי שוב ושוב בטעויות בסקירות קוד שבוצעו באמצעות AI. רציתי יותר ביטחון ב-pull requests שלי. רציתי גם נקודת מבט שנייה על הסקירה, כזו שעקבית מהרצה להרצה.

מודל יכול להעיר הערות שימושיות על קוד. זה לבדו לא הופך אותו ל-reviewer אמין. השאלות הקשות נוגעות למערכת שמסביב למודל. מה נסקר? על איזה revision התוצאה חלה? של מי הסקירה? מה נחשב חוסם? האם ממצא ישן עדיין תקף? מה מותר ל-reviewer לעשות?

בניתי את הפרויקט כדי לענות על השאלות האלה במפורש. לא רציתי להשאיר אותן לשיחה או לפרשנות של מודל בכל הרצה.

הקשר

המאגר מספק שני Code Review Agent Skills ניתנים להעברה. אחד סוקר שינוי מקומי לפני שקיים pull request. השני סוקר pull request קיים ב-GitHub. הוא יכול להפיק דוח פסיבי, וכשיש הרשאה נפרדת הוא יכול גם למסור סקירה ב-GitHub.

שני ה-skills חולקים סטנדרט סקירה אחד. לכל אחד מנגנוני בדיקה ומסירה משלו.

אילוצים

  • סקירה מקומית וסקירה ב-GitHub היו צריכות להחיל את אותם כללי ראיות, חומרה וחסימה. היעדים והמסירה שלהן אינם זהים.
  • סמכות הסקירה הייתה צריכה להישאר תחומה. קוד המימוש הוא לקריאה בלבד. סקירה עצמית לא יכולה להפוך לאישור עצמי. פסק דין מנומק אינו מעניק הרשאה לשנות משהו ב-GitHub.
  • כל תוצאה הייתה צריכה להתייחס ליעד ול-revision מוגדרים. HEAD של pull request שהשתנה לא יכול לרשת תוצאה ירוקה קודמת.
  • הערות קודמות וממצאים שנסגרו יכולים לשמש מידע לסקירה. הם אינם נכונים אוטומטית מול קוד שהשתנה.
  • הליבה הייתה צריכה להישאר ניתנת להעברה בין runtimes תואמי Agent Skills.

התפקיד שלי

אני המתכנן והמממש היחיד. הגדרתי את סמנטיקת הסקירה, את גבולות המדיניות, את ה-runbooks, את אסטרטגיית הבדיקות, את מודל האריזה, את ארכיטקטורת ה-capabilities, את תשתית ההערכה ואת התיעוד לשני משטחי הסקירה.

ארכיטקטורה וגישה

לפרויקט שלוש שכבות, שנבנו ברצף. קודם לתחום את ה-reviewer. אחר כך למדוד אותו. אחר כך להרחיב אותו בלי להקל על הגבולות האלה.

Shared Review Standard
    ├── Local Code Review
    └── GitHub PR Review

Capability Architecture (declarative ownership, progressive loading)
    └── capability.yaml per capability -> generated package manifest

Benchmark / Evaluation Subsystem
    ├── deterministic structural corpus tests
    └── behavioral proof against the live reviewer

סמנטיקת סקירה משותפת. השכבה המשותפת אחראית על ההיקף, על דרישות הראיות, על חומרת P0/P1/P2 ועל כלל החסימה. היא אחראית גם על הקשר הסקירה, על ראיות מסקירות קודמות, על בעלות ועל מבנה הממצא. ה-skill המקומי מחיל את הכללים האלה על שינויי Git שנעשה להם commit, שנמצאים ב-stage, שלא ב-stage ושאינם במעקב. ה-skill של GitHub מחיל אותם על ה-delta של pull request. אחר כך הוא מטפל בעניינים של GitHub: אימות מחדש מול HEAD מדויק, מיקום inline, הרשאה ומסירה ב-batch.

הקשר המאגר והקשר הדרישות יכולים למקד את הסקירה. הם לעולם לא מרחיבים את השינוי שקובע את התוצאה. ממצאים מועמדים עוברים שלב התאמה אחד. לכפילויות, לחומרה הסופית ולהחלטה יש בעלים יחיד.

ארכיטקטורת capabilities. כל תחום מדיניות מוצהר ב-manifest בשם capability.yaml. דוגמאות הן ביצוע מקבילי, פרסום ב-GitHub ועומק סקירה שנקבע לפי קנה מידה. ה-manifest קובע מה ה-capability טוענת ומתי. הוא מונה את קבצי המדיניות שבבעלותה ואת ה-capabilities שהיא דורשת. הוא מצביע על ה-corpus של ה-benchmark שמוכיח אותה. generator בונה את ה-manifest של האריזה מההצהרות האלה. בדיקה מכשילה את ה-build אם ה-manifest שנשמר שונה מזה שנוצר זה עתה.

טעינה הדרגתית. קובצי המדיניות של Skill הם טקסט הוראות. הם נטענים להקשר של המודל. טעינה של כל ה-capabilities בכל סקירה יקרה, גם כשרובן לא רלוונטיות. עם טעינה הדרגתית (progressive loading) כל capability נטענת רק כשתנאי ההפעלה שלה מתקיים. כך כל סקירה טוענת רק את המדיניות הרלוונטית לה.

תת-מערכת ההערכה. corpus של תרחישי סקירה ריאליסטיים מגבה שני סוגי בדיקות. הם עונים על שאלות שונות.

  • בדיקות דטרמיניסטיות בודקות את הצורה של ה-corpus ושל ה-fixtures. הן לעולם לא תלויות בשיקול דעת של LLM.
  • הוכחה התנהגותית מריצה את ה-Skills ללא שינוי מול fixtures אמיתיים ומדרגת את הפלט מול ה-corpus. baseline שמור תומך בהשוואת רגרסיה ובזיהוי drift.

ההרצות מתוזמנות. הרצה קלה יותר רצה כל כמה ימים. הרצה מלאה יותר רצה פעם בשבוע.

החלטות מרכזיות

  • להפוך את ה-Governance לחלק מהסקירה. בעלות על ה-reviewer, גבולות קריאה בלבד, היקף היעד, סמכות פעולה והתנהגות סקירה חוזרת הם חוזים מפורשים. הם אינם הנחות שמסביב לקריאה למודל.
  • לגזור את ההחלטה באופן מכני מהחומרה. ממצאי P0 ו-P1 חוסמים. ממצאי P2 אינם חוסמים. ההחלטה הסופית נגזרת פעם אחת, מהממצאים אחרי ההתאמה. הרגרסיה שלהלן הראתה שכלל שכתוב רק בפרוזה לא הספיק.
  • לקשור את מצב הסקירה ל-revision מדויק. סקירת GitHub רושמת ובודקת מחדש את ה-HEAD שנסקר לפני המסירה. אם ה-HEAD משתנה, התוצאה הקודמת היא ראיה על מצב ישן יותר. היא אינה אישור של המצב החדש.
  • להתייחס להיסטוריית סקירה כראיה ולא כאמת. ממצאים קודמים ושיחות שנסגרו נבדקים מול היעד הנוכחי. אפשר להשתמש בהם שוב, לסגור אותם או להעריך אותם מחדש. הם לעולם לא מועתקים קדימה באופן מכני.
  • להשאיר את המדיניות המשותפת במרכז ואת שכבות המסירה דקות. קבוצת מדיניות אחת שומרת על אותו סטנדרט בסקירה המקומית ובסקירת GitHub.
  • להפוך אותות הקשורים לחומרה לדטרמיניסטיים. עומק הסקירה נקבע מקטלוג קבוע של אותות סיכון. לכל ממצא יש ערך ביטחון מתוך קבוצה סגורה. את שניהם אפשר לבדוק. אף אחד מהם לא יכול לשנות את חומרת הממצא או את ההחלטה הנגזרת.

רגרסיה אמיתית: כשממצא P2 שלא אמור לחסום הוביל בכל זאת לחסימה

הרצתי את ה-reviewer על המאגר של עצמו. סקירה אחת הכילה ממצא יחיד ברמת P2. היא בכל זאת החזירה פסק דין חוסם. זה סתר את כלל ההחלטה של הפרויקט עצמו.

טקסט המדיניות היה נכון. אבל שום דבר בנוהל לא אילץ את המודל לציין ספירת P0/P1 לפני שכתב את ההחלטה. פסק הדין יכול היה לנטות אחרי הניסוח של הממצא ולא אחרי החומרה שנספרה.

התיקון העביר את הכלל מפרוזה לנוהל. שני ה-skills מציינים עכשיו את ספירת ה-P0/P1 הממשית כבסיס להחלטה. ספירת ה-P2 אינה קלט.

אחר כך הוספתי ל-benchmark מקרי רגרסיה. אחד הוא מקרה של P2 בלבד בניסוח מתון. אחד הוא מקרה של P2 בלבד בניסוח דחוף במכוון. אחד הוא מקרה נקי בלי ממצאים. כולם רצים דרך נתיב ה-reviewer האמיתי. ההרצות המתוזמנות עוקבות עכשיו אחרי הכשל הזה.

טעינה הדרגתית מבוססת ראיות

התייחסתי לטעינה ההדרגתית כאל השערה ולא כאל פריסה. הרצף היה:

  1. לתעד baseline של מה שסקירה טוענת תמיד.
  2. להעביר capability אחת מאחורי תנאי הפעלה.
  3. למדוד את ההפחתה בהוראות שנטענות.
  4. להריץ הוכחה התנהגותית חיה כדי לאשר שה-Skills עדיין סוקרים נכון.
  5. להחליט בנקודת ביקורת אם להמשיך.

החילוץ הראשון הפחית את ההוראות שנטענו כשה-capability הזו לא הייתה רלוונטית. נקודת הביקורת לא אישרה פריסה בכמות. היא אישרה capability אחת נוספת בדיוק. החילוץ השני נמדד באותה דרך. ההפחתה הייתה קטנה יותר, כי ה-capability הזו היא חלק קטן יותר מההוראות.

המעבר חלקי בתכנון. רק מעט capabilities עברו את מחזור המדידה המלא ולאחריו ההכללה. שאר ה-capabilities עדיין נטענות ללא תנאי, עד שתצטבר גם עבורן אותה רמת ראיות.

שיקולים ופשרות

מדיניות מפורשת, manifests והערכה התנהגותית הופכים את המאגר לגדול בהרבה מקובץ skill בודד. זו הנקודה. אפשר לבדוק, לבחון ולשנות את התנהגות הסקירה, את האריזה ואת טעינת ההוראות בלי לשנות בשקט התנהגות שאינה קשורה. אריזה ניטרלית ל-runtime נמנעת מנעילה לספק. המחיר הוא שיכולות runtime אופציונליות נשארות מחוץ לליבה הניתנת להעברה, ותמיד נשאר fallback סדרתי.

המערכת לא מריצה קוד ממאגר שנסקר. זה מגביל אימות דינמי. זה שומר על גבול הקריאה בלבד. גישה לשינויים לא מהימנים אינה הרשאה להריץ אותם.

ה-benchmarks הם ראיה תחומה על ה-fixtures שהם מכסים. הם לא מוכיחים נכונות לכל סקירה. ניתוח ה-drift של הפרויקט עצמו מראה מגבלה. ציר ההתאמה המבני של פגמים לא מאוכלס בפועל מפלט אמיתי של ה-reviewer. הדירוג חוזר לבדיקת דמיון טקסטואלי גסה יותר לעיתים קרובות מהמתוכנן. אני רואה בכך מגבלה ידועה של הדיוק של ה-benchmark.

תוצאות ואימות

התוצאה היא מערכת סקירה תחומה, לא רק שני skills ארוזים.

  • חוזה משותף אחד של ראיות, חומרה והחלטה עומד מאחורי שני משטחי סקירה.
  • הבעלות על ה-capabilities מוצהרת, וה-manifest של האריזה נוצר מההצהרות האלה.
  • לטעינה ההדרגתית יש מדידה לכל חילוץ.
  • תת-מערכת ההערכה מפרידה בין נכונות מבנית לנכונות התנהגותית. היא עוקבת אחרי drift מול baseline שמור ורצה בתזמון.
  • הגרסאות מנוהלות. הגרסה נגזרת באופן דטרמיניסטי מה-changelog.

הפרויקט מראה שאפשר לבנות artifact של זמן ריצה בשפה טבעית עם משמעת הנדסית רגילה. כלומר בעלות קנונית, חוזים מפורשים, בדיקות, אריזה ניתנת לשחזור והערכה אמפירית.

אין נתונים על מהירות סקירה, הפחתת פגמים, שימוש או אימוץ של הפרויקט. אף אחד מהם לא נטען.

מה למדתי

  • החלק הקשה הוא Governance ולא מציאת באגים. היכולת של המודל לא קובעת של מי הסקירה, אילו פעולות מורשות או לאיזה מצב ההחלטה מתייחסת.
  • להחלטת סקירה נדרשת זהות. לתוצאה יש משמעות רק כשהיא קשורה ליעד ול-revision.
  • כלל נכון בפרוזה אינו כלל נכון בפועל. רגרסיית ה-P2 קרתה כשהמדיניות הנכונה כבר הייתה כתובה. התיקון היה צעד מאלץ בנוהל ומקרה רגרסיה.
  • בדיקות דטרמיניסטיות ו-benchmarks התנהגותיים עונים על שאלות שונות. בדיקות מבניות מאשרות של-fixture יש הצורה שהוא טוען לה. רק הערכה התנהגותית מראה מה ה-reviewer עושה על קלט אמיתי.
  • התנהגות הסתברותית דורשת מדידה ולא רק חוזים. כללים מגבילים מה המערכת רשאית לעשות. corpus מראה אם היא עושה זאת.
  • הכללה מחייבת מדידה. חילוץ מוצלח אחד הוא נקודת מידע. הוא אינו מנדט לחזור על הדפוס בכל מקום.

הכיוון הנוכחי

הבעלות על ה-capabilities היא בעיקר לוגית כיום. כל capability.yaml מצהיר על מה ה-capability אחראית. הקבצים שהוא מצביע עליהם עדיין נמצאים בתיקיות משותפות. אני הופך את זה למבנה פיזי במאגר צעד אחר צעד. תת-מערכת ההערכה מופרדת קודם מהתיעוד הכללי.

הקצב האיטי מכוון. בזמן תכנון המבנה היעד גיליתי שתיקיית platform/ ברמה העליונה תתנגש עם מודול platform של ספריית התקן של Python. היא תסתיר את המודול בכל import platform שיבוא אחריה. אחד ממקומות הקריאה האלה הוא בדיקת sandbox רגישה לאבטחה. תפסתי את זה בשלב התכנון ושיניתי את שם התיקייה ל-runtime_platform/ לפני שהזזתי קבצים. עכשיו אני בודק מעבר מבני מול ה-runtime האמיתי שלו לפני שאני נוגע בקובץ.