كيف نظّمنا العمل بين 30 مستودعًا باستخدام manifest؟
الموضوعات: Git · المعمارية · الأدوات
عندما يسمع أحد أن لدينا نحو 30 مستودعًا، يكون السؤال الأول غالبًا: لماذا لا تنقلونها إلى monorepo؟
السؤال منطقي، لكنه يبدأ من افتراض أن تعدد المستودعات هو المشكلة. في حالتنا، كانت حدود الملكية والصلاحيات والإصدار مهمة، وكان لكل مستودع خط بناء وتاريخ مستقلان. أردنا الحفاظ على هذا الاستقلال، مع طريقة توضّح كيف ترتبط المشاريع وتتيح تجهيز مساحة العمل نفسها على أجهزة مختلفة.
الحل كان ملف manifest مركزيًا، مستوحى من أداة Android repo. لا يجمع الكود في مستودع واحد. هو فقط يصف المستودعات التي تكوّن مساحة العمل، ومسار كل واحد منها، والـ revision المتوقع.
إعداد البيئة كان يعتمد على معرفة الأفراد
قبل وجود خريطة المستودعات، يعرف كل مهندس جزءًا من الصورة. شخص يتذكر أي خدمة تحتاجها واجهة معينة، وآخر يحتفظ بسكربت إعداد محلي، وثالث لديه المجلد الوحيد الذي عمل في آخر مرة.
هذه المعرفة لا تظهر في Git. وعندما ينضم مهندس جديد أو يحتاج أحد إلى إعادة بناء البيئة، تبدأ سلسلة أسئلة: ما المستودعات المطلوبة؟ أي الفروع تعمل معًا؟ هل هذه الحزمة مشتركة أم نسخة قديمة؟ ولماذا ينجح البناء على جهاز ولا ينجح على آخر؟
لا يحل monorepo هذا النوع من الغموض وحده. قد يجمع الملفات في مكان واحد، لكنه يغيّر في المقابل الصلاحيات وخطوط الإصدار وطريقة العمل. لذلك عالجنا المشكلة الأصغر أولًا: جعل شكل مساحة العمل صريحًا.
ماذا يوجد في manifest؟
يعرض هذا المثال المبسّط المستودعات التي تتكوّن منها مساحة العمل ومساراتها:
<manifest>
<remote name="origin" fetch="..." />
<default remote="origin" revision="main" />
<project name="service-a" path="services/service-a" />
<project name="console-a" path="apps/console-a" />
<project name="shared-ui" path="packages/shared-ui" />
</manifest>يجيب manifest عن أسئلة مساحة العمل: ما المستودعات الموجودة؟ أين تُنزّل؟ ومن أي مصدر؟ وأي revision نعده الحالة المعروفة عند الحاجة إلى تكرار بناء معين؟
أراجع تغيير هذا الملف مثل أي تغيير في الكود. إذا أضفنا مستودعًا أو نقلناه أو أخرجناه من مساحة عمل، يظهر القرار في pull request وفي سجل واضح، لا في رسالة تضيع بعد أسبوع.
أمر واحد بدل قائمة تعليمات
يقرأ سكربت الإعداد الـ manifest، وينشئ بنية المجلدات، ثم يزامن المستودعات المطلوبة. يجب أن يكون تشغيله أكثر من مرة آمنًا. لا أريد أمرًا ينجح على مجلد فارغ ثم يفسد البيئة عندما يعاد تشغيله.
كما يجب ألا يخفي Git. إذا احتوى مستودع على تعديلات محلية، أو كان على revision مختلف، أو لا يملك المستخدم صلاحية الوصول إليه، تظهر الحالة باسمه ومساره. لا يفيدني سطر يقول إن المزامنة فشلت من دون أن يخبرني أين.
يعرض أمر status المفيد على الأقل:
- الفرع أو الـ revision الحالي؛
- هل توجد تغييرات محلية؟
- ما الحالة المتوقعة إن كانت مثبتة؟
- هل فشل الوصول أو التنزيل؟
هذه التفاصيل وفرت علينا نوعًا مزعجًا من التشخيص: النظام لا يعمل، لكن كل مستودع يبدو صحيحًا حين تفحصه وحده. خريطة المستودعات تجعل الاختلاف بين الحالة الحالية والمتوقعة مرئيًا.
ولا أنزّل المستودعات الثلاثين في كل مهمة. يسمح الملف بتعريف مجموعات مرتبطة بمنتج أو مسار عمل، فيختار المهندس الجزء الذي يحتاجه. الفرق أن المجموعة مشتقة من مصدر واحد؛ ليست قائمة ثانية نحتاج إلى تحديثها يدويًا.
المستودعات بقيت مستقلة
لم نحاول جعل الـ manifest طبقة تتحكم في كل شيء. كل مستودع يحتفظ بصلاحياته وCI وإصداراته وملكية شيفرته. هذه الحدود هي سبب اختيار polyrepo أصلًا.
لو احتاج كل commit عادي إلى تعديل الـ manifest، لصنعنا عنق زجاجة جديدًا. لذلك يتغير الملف عندما يتغير شكل مساحة العمل، أو عندما نحتاج إلى تثبيت مجموعة revisions لبناء يمكن تكراره. لا يتغير لمجرد أن مستودعًا أصدر نسخة داخل حدوده المعتادة.
هذا الفصل مهم أيضًا للمراجعة. صاحب الواجهة يراجع كود الواجهة، وصاحب الخدمة يراجع عقدها، أما تغيير خريطة المستودعات فيراجعه من يفهم الأثر بين المستودعات.
تنسيق التغييرات بين المستودعات
هذه كلفة حقيقية، ولا أحاول إخفاءها. لا يوجد commit واحد يغير الواجهة والخدمة وحزمة مشتركة وخط النشر في اللحظة نفسها.
لذلك نصمم العقود كي تتحمل انتقالًا تدريجيًا. نضيف الحقل الجديد قبل حذف القديم. نجعل الخدمة تقبل النسختين خلال فترة الترحيل. ننشر الحزمة المشتركة قبل أن نفرض استخدامها. وإذا كان التغيير لا يمكن تشغيله إلا دفعة واحدة، نحتاج إلى خطة إصدار واضحة توضّح ترتيب تحديث المستودعات.
يمكن للـ manifest أن يثبت مجموعة revisions نعرف أنها تعمل معًا. لكنه لا يعفي العقود من التوافق. هو يساعدنا على رؤية الحالة وإعادتها؛ لا يجمع المستودعات فعليًا في مستودع موحّد.
CI يقرأ خريطة المستودعات نفسها
إذا كانت خريطة المستودعات موثوقة محليًا، يجب ألا يبني CI قائمة أخرى للمستودعات. يستطيع خط التحقق أن ينشئ مساحة نظيفة من الـ manifest، ثم يشغّل اختبار العقود أو اختبار end-to-end على المجموعة المطلوبة.
أشدد على كلمة «نظيفة». إعادة استخدام مساحة قديمة قد تخفي مستودعًا ناقصًا أو revision غير صحيح أو خطوة إعداد لم نكتبها. الاختبار الحقيقي للخريطة هو قدرتها على بناء الحالة من الصفر.
ويمكن للسكربتات الأخرى قراءة المعلومات نفسها: فحص المستودعات التي تحتوي على تعديلات محلية، مقارنة الحالة الحالية بالمتوقعة، أو تشغيل أمر موحد على مجموعة معينة. كل أداة لا تحتاج إلى معرفة أسماء المستودعات يدويًا.
خريطة المستودعات تفيد وكلاء البرمجة أيضًا
أدوات مثل Codex وClaude جيدة في قراءة مستودع واحد. المشكلة تبدأ عندما تمتد المهمة إلى واجهة وخدمة وحزمة مشتركة، ولا يعرف الوكيل أين تنتهي الحدود.
الـ manifest يعطيه خريطة خاضعة لإدارة الإصدارات بدل أن يخمن من أسماء المجلدات. نستطيع بناء مهارة أو أمر يختار المجموعة المطلوبة، يزامنها، يقرأ تعليمات كل مستودع، ثم يشغّل الفحوصات المناسبة.
هذا لا يعني منحه وصولًا مفتوحًا إلى كل شيء. العكس: خريطة المستودعات تسمح بتحديد النطاق بدقة. يعرف الوكيل المستودعات التي تخص المهمة، ويبقى خارج البقية. وعند انتهاء العمل، يعرض status واضحًا للتغييرات التي صنعها.
في عملنا على مهارات مخصصة للوكلاء وMCP واختبارات end-to-end، كان وجود مصدر واحد لشكل النظام أهم من كتابة prompt أطول. السياق المنظم أفضل من شرح جديد في كل مرة.
ما الذي لا يحله هذا الأسلوب؟
لا يوحد إصدارات الأدوات. لا يتيح تطبيق تغيير على عدة مستودعات في commit واحد. ولا يلغي الحاجة إلى صيانة السكربت نفسه. إذا أصبح الملف قديمًا، سيكون مصدر ثقة خاطئًا، وهذا أسوأ من عدم وجوده.
لذلك نعامل خريطة المستودعات كجزء من البنية: لها مالك، وتراجع تغييراتها، وتُختبر عملية الإعداد في بيئة نظيفة. وأبقي السكربت المحيط بها صغيرًا بقدر الإمكان. عندما يحدث خطأ، أريد أن أرى أمر Git الذي فشل، من دون البحث بين طبقات كثيرة من الأدوات.
وقد لا يكون هذا النهج مناسبًا لفريق آخر. إذا كانت كل المشاريع تصدر معًا، ويتشارك الجميع الصلاحيات والأدوات نفسها، فقد يكون monorepo أبسط. يتوقف الاختيار على طريقة عمل الفريق، والصلاحيات واستقلال الإصدارات التي يحتاجها.
بالنسبة إلينا، بقيت المستودعات مستقلة، وأصبح لدينا مكان واحد يصف كيف تجتمع. هذا هو دور الـ manifest: ليس أن يحل كل مشاكل polyrepo، بل أن يوفّر وصفًا واضحًا لمساحة العمل يمكن قراءته واستخدامه في الإعداد والتحقق.