العمل داخل Cursor / Claude Code / Aider / Copilot

Aider: SEARCH/REPLACE blocks

4 دقيقة للقراءة

Aider أداة CLI بتجمع LLM مع repo محلي. بتطبّق مخرج الموديل على الملفات بـ format محدد — SEARCH/REPLACE blocks — قابل للقراية من البشر وسهل لأي script يطبّقه. حتى لو مش بتستخدم Aider، الـ format primitive مفيد في prompt engineering: بيجبر الموديل يلتزم بـ النص الموجود بالظبط قبل ما يقترح التغيير.

الـ format صارم:

filename.ext
<<<<<<< SEARCH
(exact existing lines)
=======
(new lines)
>>>>>>> REPLACE

الموديل لازم يعيد إنتاج السطور الموجودة byte-by-byte. لو غلط في حرف واحد، خطوة الـ apply بتفشل. الفشل ده ميزة مش bug — SEARCH block غلط بيقولك إن الموديل قرا الملف غلط، وأحسن تعرف ده قبل التطبيق مش بعديه.

دي مهمة صغيرة بالأسلوب ده:

Apply this change to src/server.py using a SEARCH/REPLACE block:

Goal: convert the synchronous fetch_user call into an async one and add a 5-second timeout.

Current:

def get_profile(user_id: str):
    user = fetch_user(user_id)
    return {"id": user.id, "name": user.name}

Output ONLY a SEARCH/REPLACE block in the format: src/server.py <<<<<<< SEARCH (exact existing lines) ======= (new lines) >>>>>>> REPLACE

Claude Sonnet 4.5 رجّع:

src/server.py
<<<<<<< SEARCH
def get_profile(user_id: str):
    user = fetch_user(user_id)
    return {"id": user.id, "name": user.name}
=======
async def get_profile(user_id: str):
    user = await asyncio.wait_for(fetch_user(user_id), timeout=5.0)
    return {"id": user.id, "name": user.name}
>>>>>>> REPLACE

Captured from Claude Sonnet 4.5 (claude-sonnet-4-5) on 2026-04-27. Re-runs may differ slightly — see the model-lifecycle note in Module 1, lesson 1.

الـ block جاهز للصق في Aider، وكمان قابل للقراية من البشر. الـ reviewer يقدر يمسح SEARCH section، يمسح REPLACE section، ويأكد التغيير من غير تشغيل الأداة.

3 حاجات SEARCH/REPLACE بيجبرها unified diff ما بيجبرهاش:

الخاصيةليه مهمة
سطور موجودة بالظبطالموديل لازم يقرا الملف بدقة قبل اقتراح التغييرات
تطابق single-anchorالـ apply بيفشل على تطابق غامض، فبيظهّر مشاكل خفية
مفيش اعتمادية على line numbersالـ block شغّال حتى لو السطور حواليه اتحرّكت

الخاصية التالتة هي اللي بتخلّي SEARCH/REPLACE قوي جداً في sessions طويلة. Unified diff بـ line numbers بيبقى مش valid أول ما تغيير تاني يحرّك السطور دي. SEARCH block بيفضل valid طول ما المحتوى اللي بيدوّر عليه ما اتغيّرش.

دورة الـ apply في Aider:

دورة الـ apply في Aider

  1. 1الموديل بيطلّع SEARCH/REPLACE

    لازم يعيد إنتاج السطور الموجودة byte-by-byte قبل ما يقترح التغيير

  2. 2Aider بيطابق النص بالظبط

    مفيش أرقام سطور، فالتعديلات اللي حواليه ما بتبطّلش الـ block

  3. 3التطابق فشل؟ قف وبُص

    فشل التطابق معناه الموديل قرا الملف غلط. دي معلومة، وصلتلك قبل ما ملفك يتغيّر

  4. 4اتطبّق — دلوقتي شغّله

    إنه اتطبّق نضيف ما بيقولش حاجة عن إن الكود شغّال. التنفيذ بس هو اللي بيقول

Constraint للسلامة: لما التغيير كبير، اطلب block واحد لكل وحدة منطقية، مش block ضخم:

If the change spans multiple logical edits (e.g., function signature + body + caller updates), output one SEARCH/REPLACE block per edit.

Blocks صغيرة كتير بتفشل بشكل مستقل. لو واحد فشل في الـ apply، بتحتفظ بالباقي. Block ضخم واحد بيفشل atomically — كله أو ولا حاجة — وده نادراً اللي عايزه أثناء التطوير المتكرر.

دلوقتي الجزء اللي يستاهل الدرس كله. المخرج المسجّل فيه عيبين، وواحد بس منهم هو اللي بتلاحظه الأول.

الواضح: الموديل استخدم asyncio.wait_for من غير ما يضيف import asyncio. شغّله وهتاخد NameError: name 'asyncio' is not defined. الحل SEARCH/REPLACE block متابعة بتضيف الـ import، أو prompt أصرم: "Include any imports the change requires; if an import is missing from the file, add a separate SEARCH/REPLACE block to add it."

اللي بيعيش بعد الحل ده: ضيف الـ import وشغّل تاني.

TypeError: An asyncio.Future, a coroutine or an awaitable is required

asyncio.wait_for بتاخد awaitable. في الملف زي ما هو، fetch_user متزامنة — دي أساس المهمة نفسها — يبقى fetch_user(user_id) بترجّع object عادي، و wait_for بترفضه. وأوحش من الـ crash هو اللي الكود بيعمله وهو رايح ليه: Python بتقيّم الـ argument قبل ما تدخل wait_for، فالنداء الحاجب بيخلص الأول. حتى في النسخة اللي ما بترفعش الخطأ ده، الـ timeout بتاع 5 ثواني كان هيحرس ولا حاجة.

المهمة قالت "convert the synchronous fetch_user call into an async one." الموديل لفّ نداء متزامن في ماكينة async بدل كده، وده تغيير تاني بيشبه المطلوب بالصدفة.

نسخة بتعمل اللي اتطلب لازم تشيل النداء الحاجب من على الـ event loop:

async def get_profile(user_id: str):
    user = await asyncio.wait_for(
        asyncio.to_thread(fetch_user, user_id), timeout=5.0
    )
    return {"id": user.id, "name": user.name}

خد بالك من asyncio.to_thread(fetch_user, user_id) — الـ function و arguments بتاعتها متبعتين منفصلين، فمفيش حاجة بتتنادي غير لما الـ thread يشغّلها. (لو fetch_user نفسها كانت async def، السطر الأصلي كان صح زي ما هو. إنت في أنهي عالم من الاتنين دي حقيقة عن الملف، والـ prompt ما قالهاش.)

ليه ده خطر. الـ import الناقص صوته عالي وفوري ومسمّى على اسم الحاجة الناقصة — بتصلّحه في 10 ثواني. عيب الـ wait_for وراه. صلّح الـ import، شوف رسالة الخطأ بتتغيّر، وسهل جداً تقرا الخطأ التاني كأنه ذيل المشكلة الأولى مش مشكلة تانية. الأعطال المتراكبة بتتشخّص كعطل واحد، والتشخيص بيقف عند أول إصلاح بيغيّر المخرج.

العادة اللي بتمسكها: شغّل الكود تاني بعد كل إصلاح، وفضل تشغّله لحد ما يطلّع الإجابة الصح — مش لحد ما يطلّع خطأ مختلف.

دي قيمة الشغل بـ format Aider حتى من برّه. الـ format بيخلّي الفشل مرئي عند نقطة التطبيق. مش قادر يخلّيه مرئي عند التنفيذ — تشغيل الكود بس هو اللي بيعمل كده.

التالي: planning prompts لـ agents بأسلوب Claude Code. :::

اختبار

الوحدة 5: Prompts الأدوات و الـ IDE

خذ الاختبار
هل كان هذا الدرس مفيدًا؟

سجّل الدخول للتقييم