
הבעיה
סוכני קוד AI (כמו Claude Code) מנווטים בקוד באמצעות grep לאיתור קבצים ו-read לפתיחתם. יחידת הקריאה היא הקובץ.
אבל מה קורה כשקובץ אחד מכיל 20 פונקציות?
צריך את type אחד בשם CrossError — read
→ 19 פונקציות מיותרות נלוות אליו
→ זיהום של ה-context
המחקר “Lost in the Middle” (Stanford, 2024) דיווח שכאשר מידע רלוונטי קבור באמצע ה-context, ביצועי LLM יורדים ביותר מ-30%. המחקר “Context Length Alone Hurts LLM Performance” (Amazon, 2025) מצא שטוקנים מיותרים — אפילו אם הם ריקים — גורמים לירידת ביצועים של 13.9% עד 85%.
המחקר הוכיח ש"context קצר יותר — טוב יותר". אולם לא היה כלי שמפרק קוד מבנית ומאפשר להכניס רק את מה שצריך.
filefunc ממלא את החלל הזה. זוהי מוסכמת מבנה קוד וכלי CLI לפיתוח אפליקציות Go — שירותי backend, כלי CLI, מחוללי קוד, ומאמתי SSOT.
עיקרון הליבה
קובץ אחד, מושג אחד. שם הקובץ = שם המושג.
בין אם מדובר ב-func, type, interface, או קבוצת const — הכלל זהה. כל שאר הכללים נגזרים מעיקרון יחיד זה.
# ללא filefunc
read utils.go → 20 funcs, 19 מיותרות. זיהום context.
# עם filefunc
read check_one_file_one_func.go → func אחד. בדיוק מה שצריך.
חשוב יותר לא לפתוח 290 קבצים מיותרים, מאשר לבחור 5–10 קבצים נכונים.
האזרח הראשי הוא סוכן ה-AI
מבנה הקוד של filefunc מותאם לסוכן AI, לא לאדם.
סוכן AI מנווט עם grep, לא עם ls. בין אם יש 500 קבצים ובין אם 1000 — rg '//ff:func feature=validate' פעם אחת מספיק. ככל שיש יותר קבצים, כל קובץ קטן יותר, ויש פחות רעש בכל read — וזה דווקא יתרון.
השאלה “האם לא יצטברו יותר מדי קבצים?” היא לגיטימית — עבור בני אדם. אבל אי-הנוחות האנושית נפתרת בשכבת התצוגה (כמו הרחבות VSCode). מבנה filefunc לא מתפשר לטובת נוחות האדם.
ניווט שונה לחלוטין
גישה מסורתית
בקשת משתמש
→ ls, find כי לא יודעים מה יש
→ פותחים קבצים כדי להבין מבנה
→ grep נוסף לאיתור קבצים קשורים
→ פותחים קובץ — 20 funcs, רובם מיותרים
→ עלות ניווט > זמן עבודה בפועל
עם filefunc
בקשת משתמש + codebook
→ קוראים את ה-codebook ומנסחים מיד query ל-grep
→ read של 20–30 קבצים (כל אחד מושג אחד, הכל context תקף)
→ עבודה
אם כל 30 הקבצים שקראת הם context תקף — 30 קבצים זה לא בעיה. הבעיה היא לקרוא קובץ אחד ולקבל 30 קבצים שווי-ערך של רעש.
ה-Codebook
ה-codebook הוא המרכיב החשוב ביותר בתכנון filefunc. הוא מקדים את כללי ה-annotation. codebook מתוכנן היטב מוביל ל-query מדויק ב-grep, ו-grep מדויק מוביל לרשימת read נקייה.
# codebook.yaml
required:
feature: [validate, annotate, chain, parse, codebook, report, cli]
type: [command, rule, parser, walker, model, formatter, loader, util]
optional:
pattern: [error-collection, file-visitor, rule-registry]
level: [error, warning, info]
מפתחות required חייבים להופיע בכל annotation מסוג //ff:func ו-//ff:type. זה מבטיח אמינות ב-grep — אין ערכים חסרים תחת מפתחות required. מפתחות optional משמשים רק כשרלוונטי.
ה-codebook הוא מפת הפרויקט של סוכן ה-AI. ללא codebook, הניווט מתחיל בלי מילון. עם codebook, ניתן לשלוח מיד query מדויק כמו feature=validate, type=rule — ללא שלב חיפוש.
שימוש בערך שלא מופיע ב-codebook הוא ERROR. נרמול המילון דרך ה-codebook חושף feature חסר, type כפול, וסיווג עמום. רק כשרואים את הפרצה — אפשר לנהל. גם ה-codebook עצמו עובר אימות — לפחות מפתח required אחד, ללא כפילויות, רק אותיות קטנות ומקפים.
Metadata Annotation
כל קובץ מקבל annotation בראשו. כדי שניתן יהיה להבין את המטא-דאטה מכמה שורות בלבד, ללא read של כל ה-body.
//ff:func feature=validate type=rule control=sequence
//ff:what F1: validates one func per file
//ff:why Primary citizen is AI agent. 1 file 1 concept prevents context pollution.
//ff:checked llm=gpt-oss:20b hash=a3f8c1d2
func CheckOneFileOneFunc(gf *model.GoFile) []model.Violation {
| Annotation | תוכן | חובה |
|---|---|---|
//ff:func | מטא-דאטה לקובץ func: feature, type, control ועוד | חובה בקבצי func |
//ff:type | מטא-דאטה לקובץ type: feature, type ועוד | חובה בקבצי type |
//ff:what | הסבר בשורה אחת — מה הפונקציה/הטיפוס עושה | חובה |
//ff:why | למה הוחלט כך — הנימוק מאחורי ההחלטה | אופציונלי |
//ff:checked | חתימת אימות LLM (נוצרת אוטומטית על-ידי llmc) | אוטומטי |
הפורמט הוא //ff:key key1=value1 key2=value2. ניתן לחיפוש מיידי עם grep/ripgrep, ומובנה כ-key-value לפרסור על-ידי כלים. זהו אותו דפוס של //go:generate ו-//go:embed ב-Go.
control — 1 func, 1 control
control= חובה בכל קובץ func. הערך הוא אחד מתוך שלושה:
| control | משמעות | הגבלת depth |
|---|---|---|
sequence | ביצוע רציף | 2 |
selection | הסתעפות (switch) | 2 |
iteration | לולאה | dimension + 1 |
מבוסס על משפט Böhm-Jacopini (1966): כל תוכנית היא צירוף של sequence, selection ו-iteration. filefunc מאכף זאת ברמת הפונקציה — פונקציה אחת, זרימת בקרה אחת.
פונקציה עם control=iteration חייבת לכלול גם dimension= — ממד הנתונים שעליהם עוברים. dimension=1 הוא רשימה שטוחה (depth ≤ 2), dimension ≥ 2 דורש מיבנים מוגדרים (struct/interface).
filefunc מאמת גם את ההתאמה בין control לקוד בפועל. אם control=selection אבל אין switch, או control=sequence אבל יש switch או loop — זה ERROR.
פייפליין ניווט LLM
ה-annotation פועל כאינדקס חיפוש, ללא תשתית כבדה כמו vector embedding.
1. צמצום מבני (ללא LLM, grep)
בניית query על בסיס codebook
→ חילוץ 20–30 קבצים מועמדים
2. שיפוט מטא (ללא LLM או LLM זעיר)
read רק של annotation בראש כל קובץ
→ צמצום ל-5–10 על בסיס name/input/output/what
3. עבודה מדויקת (LLM גדול, context מינימלי)
full read רק של 5–10 קבצים
→ עריכה/יצירת קוד
ככל שהשלב מתקדם, ה-context מצטמצם. כשה-LLM הגדול נכנס לפעולה — נשארו רק הקבצים שבאמת נחוצים.
CLI
validate — אימות כללי מבנה הקוד
filefunc validate # תיקייה נוכחית
filefunc validate /path/to/project # שורש פרויקט מפורש
filefunc validate --format json
שורש הפרויקט צריך לכלול go.mod ו-codebook.yaml. קריאה בלבד. exit code 1 בעת הפרה.
chain — מעקב אחר יחסי קריאה
filefunc chain func RunAll # קרוב 1 (ברירת מחדל)
filefunc chain func RunAll --chon 2 # קרוב 2 (כולל פונקציות שנקראות יחד)
filefunc chain func RunAll --chon 3 # קרוב 3 (מקסימום)
filefunc chain func RunAll --child-depth 3 # רק קריאות ילד
filefunc chain func RunAll --parent-depth 3 # רק קוראים הורה
filefunc chain feature validate # feature שלם
ניתוח AST בזמן אמת. --chon הוא מרחק הקשר. קרוב 1 הוא קריאה/נקרא ישיר, קרוב 2 כולל פונקציות שנקראות יחד.
go callgraph הקיים מנתח את כל הקריאות סטטית ומייצר אלפי nodes. chain עוקב רק בתוך אותו feature. ה-feature מה-codebook הוא רמת הזום.
llmc — אימות LLM
filefunc llmc # תיקייה נוכחית
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9
מאמת שה-//ff:what מתאים ל-func body באמצעות LLM מקומי (ollama). ציון 0.0–1.0, סף ברירת מחדל 0.8. אם עובר, רושם אוטומטית //ff:checked llm=שם-מודל hash=hash. שינוי ב-body משנה את ה-hash ודורש אימות מחדש.
זוהי פתרון ל-annotation drift — //ff:what הוא טקסט חופשי שלא ניתן לאמת מכנית — באמצעות LLM קטן. האפשרות קיימת כי 1 file 1 func מבטיח התאמה 1:1 ברמת הקובץ.
כללים
כללי מבנה קובץ
| כלל | בעת הפרה |
|---|---|
| func אחד לקובץ (שם הקובץ = שם הפונקציה) | ERROR |
| type אחד לקובץ (שם הקובץ = שם הטיפוס) | ERROR |
| method: 1 file 1 method | ERROR |
| init() לא עומד לבד (חייב עם var או func) | ERROR |
_test.go מאפשר func מרובות | חריג |
| const שהם יחידה סמנטית מותרים באותו קובץ | חריג |
כללי איכות קוד
| כלל | בעת הפרה |
|---|---|
| עומק הקינון: sequence=2, selection=2, iteration=dimension+1 | ERROR |
| מקסימום 1000 שורות לפונקציה | ERROR |
| המלצה: sequence/iteration 100 שורות, selection 300 שורות | WARNING |
עומק הקינון תלוי בסוג ה-control. sequence ו-selection — depth 2. iteration — dimension + 1. עבור dimension=1 (רשימה שטוחה) זה depth 2, עבור dimension=2 (מבנה מקונן) זה depth 3. בשילוב עם דפוס early return של Go, רוב הקוד נכנס בתוך המגבלות.
ל-selection (switch) נוטה להיות case ארוכים, ולכן מגבלת השורות המומלצת היא 300.
.ffignore
קובץ .ffignore בשורש הפרויקט מוציא נתיבים מכל פקודות filefunc. תחביר זהה ל-.gitignore.
vendor/
*.pb.go
*_gen.go
internal/legacy/
מיועד להוצאת קוד שלא ניתן לאכוף עליו את כללי filefunc — כמו קוד שנוצר אוטומטית (protobuf, קוד-גן) או קוד vendor חיצוני.
שילוב עם whyso
כיוון ש-func = file, היסטוריית שינויים ברמת פונקציה נופלת בדיוק על היסטוריה ברמת קובץ.
whyso history check_ssac_openapi.go # היסטוריית שינויים של פונקציית CheckSSaCOpenAPI
כשקובץ מכיל כמה פונקציות, צריך לחפש ב-diff כדי לדעת איזו פונקציה השתנתה. עם filefunc, שינוי בקובץ = שינוי בפונקציה. עלות מעקב אפסית.
זיהוי coupling סמוי
whyso coupling check_ssac_openapi.go
פונקציות שהשתנו יחד באותה בקשה:
check_response_fields.go 8 פעמים
check_err_status.go 5 פעמים
types.go 4 פעמים
אם קובץ מופיע שוב ושוב בסטטיסטיקת coupling ללא קשר מפורש — זה סימן לתלות נסתרת. ייתכן שמדובר בפונקציות שמממשות אותו כלל עסקי מזוויות שונות, שמתאמות format בשתיקה ללא interface, או שבאגים בהן תמיד מופיעים יחד.
למה דווקא Go?
מחוץ ל-Go, מבנה filefunc קשה ליישום. gofmt מאכף פורמט קוד, early return הוא מוסכמה, אין exceptions, ו-package = directory. הרחבה לשפות אחרות דורשת אסטרטגיית כפייה מבנית ברמת gofmt — וזה מחוץ לתחום של filefunc.
גם הייעוד ברור: שירותי backend, כלי CLI, מחוללי קוד, מאמתי SSOT. ספריות אלגוריתמיות, תכנות מערכת ברמה נמוכה, ו-hot path קריטי לביצועים — אלה לא היעד.
סיכום
מבנה הקוד בעידן ה-LLM צריך להיות מותאם ליעילות הניווט של AI, לא לנוחות הניווט האנושית. filefunc הוא הצעד הראשון במעבר הזה.
קובץ אחד, מושג אחד. נרמול מילון דרך codebook, צירוף מטא-דאטה דרך annotation, ומציאת הקובץ המדויק עם grep אחד. אין קוד מיותר שנסחב עם כל read. זיהום ה-context נחסם על-ידי מבנה הקוד עצמו.