filefunc — קובץ אחד, מושג אחד

הבעיה

סוכני קוד 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 methodERROR
init() לא עומד לבד (חייב עם var או func)ERROR
_test.go מאפשר func מרובותחריג
const שהם יחידה סמנטית מותרים באותו קובץחריג

כללי איכות קוד

כללבעת הפרה
עומק הקינון: sequence=2, selection=2, iteration=dimension+1ERROR
מקסימום 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 נחסם על-ידי מבנה הקוד עצמו.

קוד: github.com/park-jun-woo/filefunc