filefunc — مفهوم واحد لكل ملف

المشكلة

وكلاء الكود الذكية (مثل Claude Code) تستخدم grep للبحث عن الملفات، وread لفتحها. وحدة read هي الملف.

لكن ماذا يحدث حين يحتوي الملف الواحد على 20 دالة؟

نحتاج type واحد هو CrossError فنفتح الملف
→ تأتي معه 19 دالة لا نحتاجها
→ تلوث السياق (context pollution)

أفادت دراسة “Lost in the Middle” (Stanford, 2024) بأن أداء LLM ينخفض أكثر من 30% حين تكون المعلومات ذات الصلة مدفونة في وسط السياق. وأوضحت دراسة “Context Length Alone Hurts LLM Performance” (Amazon, 2025) أن الرموز غير الضرورية، حتى لو كانت مسافات فارغة، تُخفّض الأداء من 13.9% إلى 85%.

أثبتت الأبحاث أن “السياق كلما قصر كان أفضل”. لكن لم تكن هناك أداة لتقسيم الكود هيكليًا وإدراج ما يلزم فقط.

filefunc يملأ هذه الفجوة. إنه اتفاقية هيكلة كود وأداة CLI مخصصة لتطوير تطبيقات Go — خدمات backend، وأدوات CLI، ومولّدات كود، ومدققات SSOT.


المبدأ الأساسي

مفهوم واحد لكل ملف. اسم الملف = اسم المفهوم.

سواء أكان func أم type أم interface أم مجموعة const، المبدأ واحد. كل القواعد تنبثق من هذا المبدأ وحده.

# بدون filefunc
read utils.go → 20 دالة، 19 منها غير ضرورية. تلوث السياق.

# مع filefunc
read check_one_file_one_func.go → دالة واحدة. بالضبط ما نحتاجه.

الأهم من فتح 5-10 ملفات ضرورية هو عدم فتح 290 ملفًا غير ضروري.


المواطن الأول هو الوكيل الذكي

هيكل كود filefunc مصمّم للوكيل الذكي لا للإنسان.

الوكيل الذكي يستكشف باستخدام grep لا ls. سواء كان هناك 500 ملف أو 1000، يكفي أمر واحد rg '//ff:func feature=validate'. كلما زادت الملفات، صغر كل ملف، وقلّ الضجيج المصاحب لكل read، وهذا ميزة لا عيب.

قد يُثار سؤال: “ألا تكثر الملفات جدًا؟” نعم بالنسبة للإنسان. لكن ذلك يُحلّ في طبقة العرض (كإضافات VSCode). لا تُساوَم بنية filefunc لتلائم الإنسان.


مسار الاستكشاف يتغير

الطريقة التقليدية

طلب المستخدم
→ ls وfind لأننا لا نعرف ما يوجد
→ فتح الملفات لفهم البنية
→ grep مجدداً للبحث عن الملفات ذات الصلة
→ عند الفتح: 20 دالة، معظمها غير ضروري
→ تكلفة الاستكشاف > وقت العمل الفعلي

مع filefunc

طلب المستخدم + تقديم الـ codebook
→ بناء استعلام grep فورًا من الـ codebook
→ read لـ 20-30 ملفًا (كل منها مفهوم واحد، كل السياق صالح)
→ تنفيذ المهمة

إن كان السياق بأكمله صالحًا في 30 ملفًا، فالمشكلة ليست العدد. المشكلة أن يحمل ملف واحد ما يعادل 30 ملفًا.


الـ Codebook

الـ codebook يحتل أهم موقع في تصميم filefunc. الـ codebook يسبق قواعد الأنوتيشن. تصميم الـ codebook بدقة يجعل استعلامات grep دقيقة، ودقة grep تُنظّف قائمة الملفات المقروءة.

# 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 يجب أن تكون موجودة في كل أنوتيشن //ff:func و//ff:type. لضمان موثوقية grep — لا فراغات في المفاتيح المطلوبة. مفاتيح optional تُستخدم فقط عند الصلة.

الـ codebook هو خريطة المشروع للوكيل الذكي. بدونه يبدأ الاستكشاف بلا مفردات. معه يُطلق استعلامات دقيقة كـ feature=validate وtype=rule مباشرة دون استكشاف.

قيمة غير موجودة في الـ codebook داخل الأنوتيشن = خطأ ERROR. تقنين المفردات عبر الـ codebook يكشف features المفقودة والأنواع المكررة والتصنيفات الغامضة. الثغرات يجب أن تظهر لكي تُدار. الـ codebook ذاته خاضع للتحقق — مفتاح واحد على الأقل في required، لا قيم مكررة، أحرف صغيرة وشرطات فقط.


أنوتيشنات البيانات الوصفية

تُضاف الأنوتيشنات في أعلى كل ملف. حتى لا تضطر إلى read كامل الملف، تكفي السطور الأولى للحصول على البيانات الوصفية.

//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 {
الأنوتيشنالمحتوىمطلوب
//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 — دالة واحدة، تحكم واحد

control= مطلوب في كل ملف func. القيمة واحدة من ثلاث:

controlالمعنىحد عمق التداخل
sequenceتنفيذ متسلسل2
selectionتفريع (switch)2
iterationتكرار (loop)dimension + 1

مستند إلى نظرية Böhm-Jacopini (1966): كل برنامج هو تركيبة من ثلاث بنى تحكم: sequence وselection وiteration. filefunc يُطبّق هذا على مستوى الدالة — كل دالة تملك تدفق تحكم واحد فقط.

دالة بـ control=iteration تستلزم dimension= أيضًا لتحديد أبعاد البيانات المُكرَّرة. dimension=1 يعني قائمة مسطّحة (عمق ≤ 2)، وdimension ≥ 2 يستلزم تداخل أنواع مسماة (struct/interface).

filefunc يتحقق أيضًا من تطابق control مع الكود الفعلي. إن كان control=selection بلا switch، أو control=sequence مع switch أو loop، فهو ERROR.


خط أنابيب استكشاف LLM

تعمل الأنوتيشنات كفهرس بحث. تعمل دون بنية تحتية ثقيلة كالـ vector embeddings.

1. تضييق هيكلي (لا يحتاج LLM، يستخدم grep)
   بناء استعلام grep من الـ codebook
   → استخراج 20-30 ملف مرشح

2. تصفية بيانات وصفية (لا يحتاج LLM أو يحتاج نموذجًا صغيرًا)
   read لأنوتيشنات أعلى كل ملف فقط
   → تضييق القائمة إلى 5-10 ملفات بناءً على name/input/output/what

3. عمل دقيق (LLM كبير، سياق أدنى)
   full read لـ 5-10 ملفات فقط
   → تعديل/توليد الكود

السياق يتقلص مع كل مرحلة. حين يتدخل 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              # درجة أولى (افتراضي)
filefunc chain func RunAll --chon 2     # درجة ثانية (تشمل الدوال المستدعاة معًا)
filefunc chain func RunAll --chon 3     # درجة ثالثة (الحد الأقصى)
filefunc chain func RunAll --child-depth 3   # الاستدعاءات الفرعية فقط
filefunc chain func RunAll --parent-depth 3  # المستدعون الأعلى فقط
filefunc chain feature validate         # feature بأكملها

تحليل AST آني. --chon هو مسافة العلاقة. الدرجة الأولى: استدعاء مباشر أو استدعاء مباشر منه. الدرجة الثانية: تشمل الدوال المستدعاة معًا.

go callgraph التقليدي يحلل جميع الاستدعاءات ثابتًا فينتج آلاف العقد. chain يتتبع داخل نفس feature فقط. feature في الـ codebook هي مستوى التكبير.

llmc — التحقق بـ LLM

filefunc llmc                           # الدليل الحالي
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9

يتحقق باستخدام LLM محلي (ollama) من توافق //ff:what مع جسم الدالة. درجة من 0.0 إلى 1.0، وعتبة افتراضية 0.8. عند النجاح يُسجّل تلقائيًا //ff:checked llm=اسم-النموذج hash=الهاش. إن تغيّر جسم الدالة تغيّر الهاش ووجب إعادة التحقق.

هذا يحل المشكلة الجوهرية لانحراف الأنوتيشن — //ff:what نص طبيعي لا يخضع للتحقق الآلي — عبر LLM صغير. ممكن لأن 1 file 1 func يضمن تطابقًا 1:1 على مستوى الملف.


القواعد

قواعد هيكل الملف

القاعدةعند المخالفة
دالة واحدة لكل ملف (اسم الملف = اسم الدالة)ERROR
نوع واحد لكل ملف (اسم الملف = اسم النوع)ERROR
1 file 1 method للمتوابعERROR
init() لا تنفرد (يجب مع var أو func)ERROR
_test.go يسمح بدوال متعددةاستثناء
const المترابطة دلاليًا في ملف واحد مسموحاستثناء

قواعد جودة الكود

القاعدةعند المخالفة
عمق التداخل: sequence=2، selection=2، iteration=dimension+1ERROR
الحد الأقصى للدالة: 1000 سطرERROR
الموصى به: sequence/iteration 100 سطر، selection 300 سطرWARNING

عمق التداخل يتفاوت حسب نوع control. sequence وselection: عمق 2. iteration: dimension + 1 — dimension=1 (قائمة مسطّحة) يعني عمق 2، وdimension=2 (بنية متداخلة) يعني عمق 3. بالدمج مع نمط early return في Go، يندرج معظم الكود ضمن هذه الحدود.

selection (switch) تميل case فيه للإطالة، لذا الموصى به 300 سطر.


.ffignore

وضع .ffignore في جذر المشروع يُستثني المسارات المحددة من جميع أوامر filefunc. نفس صيغة .gitignore.

vendor/
*.pb.go
*_gen.go
internal/legacy/

للاستثناء من فرض قواعد filefunc على الكود المولَّد (protobuf، مخرجات codegen) أو الكود الخارجي الـ vendor.


التكامل مع whyso

بما أن func = file، يقع سجل تغييرات كل دالة في ملف مستقل بدقة تامة.

whyso history check_ssac_openapi.go   # سجل تغييرات دالة CheckSSaCOpenAPI

حين تتعدد الدوال في ملف واحد، يلزم تمشيط الـ diff لمعرفة أي دالة تغيرت. مع filefunc، تغيير الملف = تغيير الدالة. تكلفة التتبع صفر.

كشف الترابط الضمني

whyso coupling check_ssac_openapi.go

الدوال التي عُدِّلت معًا في نفس الطلب:
  check_response_fields.go  8 مرات
  check_err_status.go       5 مرات
  types.go                  4 مرات

إن ظهرت دالة باستمرار في إحصاءات coupling دون علاقة صريحة، فهذا مؤشر اعتماد مخفي. دوال تُنفّذ قاعدة عمل واحدة من زوايا مختلفة، أو تتوافق ضمنيًا على تنسيق دون interface، أو أخطاء تنفجر دائمًا معًا.


لماذا Go فقط؟

هيكلة filefunc ليست سهلة خارج Go. gofmt يُلزم بتنسيق الكود، وearly return سلوك متعارف، ولا استثناءات، والحزمة = المجلد. التوسع لغات أخرى يستلزم استراتيجية إلزام هيكلي بمستوى gofmt، وهذا خارج نطاق filefunc.

نطاق التطبيق واضح أيضًا: خدمات backend، وأدوات CLI، ومولّدات كود، ومدققات SSOT. مكتبات الخوارزميات، وبرمجة الأنظمة المنخفضة، والمسارات الحرجة للأداء — خارج النطاق.


الخلاصة

هيكل الكود في عصر LLM يجب أن يُحسَّن لكفاءة استكشاف الذكاء الاصطناعي، لا لراحة الإنسان في التصفح. filefunc هو الخطوة الأولى في هذا التحول.

مفهوم واحد لكل ملف. تقنين المفردات عبر الـ codebook، وإرفاق البيانات الوصفية بالأنوتيشنات، والوصول إلى الملف الصحيح بـ grep واحد. لا يصطحب read واحد كودًا غير ضروري. الهيكل نفسه يوقف تلوث السياق.

الكود: github.com/park-jun-woo/filefunc