تخطَّ إلى المحتوى

ذكاء الشفرة

تمنح Control Center الوكيل أربع طرق يفهم بها الشفرة، تتجاوز قراءتها. الأسئلة مختلفة فعلًا، ولا آلية واحدة تجيب عنها كلها.

الآلية ما تجيب عنه الكلفة
مخطط الشفرة ما الموجود هنا، وما الذي يعتمد عليه مرور فهرسة، والاستعلام رخيص
خادم اللغة هل يُصرَّف هذا فعلًا، وما نوعه عملية خادم لكل مشروع
البحث البنيوي أين يظهر هذا «الشكل» تحليل لكل ملف
المصحّح ما الذي كان صحيحًا في هذه اللحظة، وفي هذا الإطار عملية متوقفة

الفجوة التي يسدّها خادم اللغة

قسم بعنوان “الفجوة التي يسدّها خادم اللغة”

يُبنى مخطط الشفرة من tree-sitter: رموز، وحواف استدعاء، ونصف قطر تأثير. هو سريع ورخيص، ولا يعرف شيئًا عن الأنواع. لا يخبرك إن كان الاستدعاء الذي كتبته للتو يُصرَّف، ولا إن كان الحقل موجودًا على الكائن، ولا إن كانت إعادة التسمية قد أفلتت إعادة تصدير.

من غير خادم لغة يعرف الوكيل الأمر وقت الاختبار، أو لا يعرفه أبدًا. يكتب ما يبدو سليمًا، ويمضي، ويظهر الفشل بعد ثلاث جولات ملصقًا بتغيير آخر.

المكسب في التغليف، لا في أداة lsp نفسها. write وedit وapply_patch مغلَّفة: بعد كل كتابة يمر الملف بخادم اللغة، وتُدمج التشخيصات المُستحدَثة في نتيجة الأداة. يعلم الوكيل أنه كسر البناء لحظة كسره، سواء خطر له أن يسأل أو لم يخطر.

تكرار ما عرفه الوكيل أسوأ من الصمت: يملأ النتيجة بضجيج يتعلم أن يمرّ عليه مرور الكرام، ويضيع معه الخطأ الجديد الوحيد. يتتبع دفتر لكل تشغيل ما أُبلغ عنه، ومفتاحه الشدّة ورمز التشخيص والرسالة، لا الموضع، فالتشخيص الذي نزل أربعة أسطر فحسب لا يُبلَّغ عنه ثانية على أنه جديد.

لا يبدأ أي خادم عند الإقلاع. يبدأ خادم عند أول طلب يحتاج إليه، وهو في الغالب أول تعديل لملف يتولاه، ثم تُوقَف الخوادم الخاملة. المضيف الذي لا يمس Dart لا يشغّل محلّلًا.

الاكتشاف تقاطع: لا بد أن توجد علامة جذر في نسخة العمل و أن يُحَل مسار الثنائي. العلامة وحدها تقول «هذا المشروع Python» ولا تقول شيئًا عن تثبيت الخادم، والثنائي القابل للحل وحده يقول إن المضيف يملك سلسلة أدوات ولا يقول شيئًا عن نسخة العمل هذه. لا ينجح الاستعلام إلا باجتماعهما، والأداة التي تُعرَض وتفشل دائمًا أسوأ من الأداة الغائبة.

لماذا البحث البنيوي ما دام grep موجودًا

قسم بعنوان “لماذا البحث البنيوي ما دام grep موجودًا”

grep يطابق المحارف. هو الأداة الصحيحة للاسم، والخاطئة للشكل.

يُحلَّل النمط البنيوي بقواعد الملف النحوية نفسها، فيجد dispose($X) الاستدعاء مهما كان تباعده أو التفافه أو كسر أسطره، ولا يطابق المحارف نفسها داخل نص حرفي أو تعليق.

ما يجعله أكثر من تعبير نمطي بخطوات إضافية أن المتغيّر الوصفي، إذا تكرّر في نمط واحد، لا بد أن يلتقط النص نفسه:

if ($X != null) $X.dispose()

يطابق حارسًا يستدعي dispose على ما اختبره، ولا يطابق حارسًا يستدعيه على شيء غيره. هذا صنف أخطاء لا يُصاغ بالنص أصلًا.

يعمل على tree-sitter المربوط أصلًا

قسم بعنوان “يعمل على tree-sitter المربوط أصلًا”

ليس هناك محرّك ثانٍ. cc_natives يحمّل tree-sitter وقواعده النحوية لمخطط الشفرة أصلًا، ومعها ذاكرة مؤقتة للاستعلامات المُصرَّفة وجدول للغات. المُطابِق Dart خالص فوق أشجار كانت تُحلَّل أصلًا. محرّك بنيوي ثانٍ يعني محلّلًا نحويًا ثانيًا، ومجموعة قواعد ثانية تُبنى وتُجهَّز على كل منصة، وجوابين عن «ما تعبير الاستدعاء».

الشذرة تحتاج موضعًا تعيش فيه

قسم بعنوان “الشذرة تحتاج موضعًا تعيش فيه”

القواعد النحوية لا تعرّف إلا ما يكونه الملف كاملًا. إذا حُلّل dispose(x) عاريًا في المستوى الأعلى من Dart، فهو توقيع دالة سليم تمامًا: تصريح اسمه dispose يأخذ معاملًا x، بلا أي خطأ يفضح ذلك. مُطابِق مبني على هذا التحليل يبحث عن التصريحات، فلا يجد استدعاءات، ويبلّغ «لا تطابقات» عن نمط لم يُقرأ قط كما يبدو.

فيُجرَّب النمط على سلّم من الأغلفة النحوية: عبارة، ثم تعبير، ثم عضو صنف، ثم المستوى الأعلى، وأول غلاف يقبله هو الفائز. الشذرة التي لا يقبلها أي غلاف تُبلَّغ بتعذّر تحليلها، بدل أن يُبحث عنها ثم لا تُوجَد في صمت.

لماذا تُجهَّز إعادة الكتابة بدل أن تُكتَب

قسم بعنوان “لماذا تُجهَّز إعادة الكتابة بدل أن تُكتَب”

إعادة الكتابة البنيوية أوسع تعديل يجريه وكيل: نمط واحد، وأربعون ملفًا، ولا أحد يقرأ النتيجة سطرًا سطرًا، لا النموذج ولا من طلبها. كتابتها ثم الإبلاغ بأنها «تمت» يعني أن أول نظرة صادقة إلى التغيير تكون git diff بعد ذلك.

لذلك يبلّغ ast_edit بما كان سيفعله: العدد، والملفات، ومواضع التطابق. ولا يستقر التغيير إلا حين يثبّته resolve. وهذا ما يضع فرقًا وقرار قبول أو تجاهل أمام الإنسان قبل أن يصل أي شيء إلى القرص.

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

ما يجيب عنه المصحّح ولا تجيب عنه عبارة الطباعة

قسم بعنوان “ما يجيب عنه المصحّح ولا تجيب عنه عبارة الطباعة”

حلقة عبارة الطباعة هي: أضف طباعة، أعد تشغيل الأمر كله، اقرأ الخرج، احذف الطباعة. تكلف تشغيل اختبار كامل لكل سؤال، ولا تجيب إلا عن السؤال الذي خطر لك قبل التشغيل.

الإطار المتوقف يجيب دفعة واحدة عن أسئلة تلك اللحظة كلها: كل متغير محلي، وكل مستدعٍ، وتعبير يُقيَّم في نطاق الإطار نفسه. لعملية عالقة، أو لفشل يتكرر مرة في كل عشرة تشغيلات، فهذا هو الفرق بين عصر كامل ودقيقة.

محدود كالحاوية المعزولة، وللسبب نفسه

قسم بعنوان “محدود كالحاوية المعزولة، وللسبب نفسه”

يملك محوّل التصحيح عملية متوقفة تحتفظ بما تحتفظ به تلك العملية: منفذ، وقفل، واتصال بقاعدة بيانات. الجلسة التي لا يقودها أحد تسرّب يبقى بعد الجولة التي أطلقته. لذلك توجد مهلة TTL صارمة، وجلسة واحدة بالضبط لكل محادثة، ويُرفَض launch ثانٍ بدل استبدال الأول في صمت: الوكيل الذي يبدأ ثانية من غير إنهاء الأولى قد فقد أثر الأولى على الأرجح.

يشترك Debug Adapter Protocol مع Language Server Protocol في تأطير Content-Length، ولا يشاركه في شيء فوق ذلك: مفاتيح ارتباط مختلفة، وشكل فشل مختلف، ونوع رسالة ثالث (الأحداث) بلا مقابل في LSP. التأطير في موضع واحد، والبروتوكولان يرتكزان عليه منفصلين.

كتابة التأطير مرتين تعني جوابين عن «رسالة تمتد على قطعتين»، و«قطعة تحمل ثلاث رسائل»، و«الخادم كتب لافتة إلى stdout قبل إطاره الأول»، ولا ينال الإصلاح إلا أحد الجوابين.