تحديث الشيفرة القديمة المعقدة باستخدام وكلاء الذكاء الاصطناعي: نهج ميسترال

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

القراءة الصوتية غير متاحة في هذا المتصفح
تحديث الشيفرة القديمة المعقدة باستخدام وكلاء الذكاء الاصطناعي: نهج ميسترال

الوسوم

ملخص سريع

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

تحديث الشيفرة القديمة المعقدة باستخدام وكلاء الذكاء الاصطناعي: نهج Mistral

نادرًا ما يكون النظام القديم مشكلة واحدة. إنه كومة من المشكلات: بناء لا ينجح إلا على حاسوب مهندس واحد، وقواعد عمل لا توجد إلا في بيانات الإنتاج، ومجموعة اختبارات توثّق نوايا عمرها عقد من الزمن، ومخطط اعتماديات لم يرسّمه أحد بالكامل. أدوات التحديث التقليدية — تعديلات الشيفرة (codemods)، ومُعيدات كتابة شجرة التركيب المجردة (AST)، وعمليات إعادة الهيكلة المكتوبة بسكربتات — ممتازة في الطبقة الميكانيكية وهشة في الطبقة الدلالية. بمجرد أن تُعبَّر قاعدة عبر إرسال ديناميكي (dynamic dispatch) أو ملف مُولَّد أو علامة إعدادات، يتوقف إعادة الكتابة الحتمية.

نشرت Mistral مقالًا حول تحديث الشيفرة القديمة المعقدة باستخدام وكلاء الذكاء الاصطناعي على https://mistral.ai/news/legacy-code-modernization. ويؤطر ذلك المنشور المشكلة بالطريقة نفسها التي تؤطرها هذه المقالة: التحديث مهمة على مستوى المستودع، وليس مهمة إكمال. ما يلي هو معالجة هندسية عملية لذلك الإطار — كيفية إعداد حلقة وكيل تُحدث تغييرات فعلًا في قاعدة شيفرة قديمة، وما الذي يجب التحقق منه، وأين تنكسر المقاربة.

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

ما الذي يجعل تحديث الشيفرة القديمة مختلفًا

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

السلوك غير موثّق. النظام يعمل، ما يعني أن السلوك الحالي هو المواصفة. أي إعادة هيكلة تغيّر المخرجات الملحوظة تُعد تراجعًا، حتى لو بدت المخرجات الجديدة أكثر معقولية.

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

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

نطاق التأثير غير متماثل. حذف دالة تبدو غير مستخدمة قد يكون كارثيًا عندما يُحدَّد موقع الاستدعاء عبر الانعكاس (reflection) أو سلسلة نصية في صف قاعدة بيانات.

على أي مقاربة جادة أن تجيب عن هذه القيود الأربعة قبل أن تجيب عن أي شيء يخص اختيار النموذج.

ما الذي يضيفه الوكلاء ولا تستطيع الأدوات الساكنة إضافته

الفرق الجوهري بين الوكيل ونموذج إكمال الشيفرة هو الحلقة. يقرأ الوكيل، ويحرّر، وينفّذ، ويلاحظ النتيجة، ويعدّل. في العمل على الشيفرة القديمة، تصبح البيئة القابلة للتنفيذ — المترجم، والمدقق (linter)، ومشغّل الاختبارات، ومدقق الأنواع — هي الحقيقة الأرضية التي لا يمكن لأي قدر من التلقين أن يتجاوزها.

ثلاث قدرات تهم هنا تحديدًا:

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

النتيجة العملية: تتوقف عن مطالبة الوكيل بـ كتابة ترحيل وتبدأ في مطالبتته بـ التقارب نحو ترحيل واحد، ضمن قيود تتحكم فيها.

المتطلبات

قبل تثبيت أي شيء، تأكد من توفر ما يلي. كل بند يقابل نمط فشل مكلفًا اكتشافه في منتصف الترحيل.

  • تحكم بالإصدارات مع شجرة عمل نظيفة. ينتج الوكلاء فروقًا كبيرة؛ ومن دون git لا يمكنك فحصها أو تقسيمها ثنائيًا أو التراجع عنها.
  • نقطة دخول بناء قابلة لإعادة الإنتاج. أمر واحد يبني المشروع من الصفر ويُرجع رمز خروج غير صفري عند الفشل.
  • أمر اختبار قابل للتشغيل. حتى مجموعة اختبارات رقيقة تكفي للبدء. إن لم توجد مجموعة، تأتي اختبارات التوصيف أولًا (انظر أمثلة الاستخدام).
  • بيئة تنفيذ معزولة. حاوية أو آلة افتراضية يمكن التخلص منها. لا توجّه أبدًا وكيلًا يملك وصولًا إلى نظام الملفات وصدفة (shell) إلى جهاز يحمل بيانات اعتماد الإنتاج.
  • سلسلة أدوات اللغة للنظام الهدف — المترجم، ومدير الحزم، وأي مولّدات شيفرة يعتمد عليها البناء.
  • Python 3.10 أو أحدث للأداة التشغيلية الموصوفة أدناه.
  • بيانات اعتماد API لمزوّد النموذج لديك، تُخزَّن في متغير بيئة بدلًا من ملف قد يُلتزم به.
  • خطة ميزانية وحدود معدل. الحلقات على مستوى المستودع تُجري استدعاءات كثيرة؛ والوكيل الجامح يمكن أن يستهلك الحصة بسرعة.

التثبيت خطوة بخطوة

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

1. إنشاء مساحة عمل الأداة التشغيلية

mkdir legacy-agent && cd legacy-agent
python3 -m venv .venv
source .venv/bin/activate

الأمر الأول ينشئ مجلدًا لشيفرة التنسيق لديك، والثاني ينشئ بيئة Python معزولة، والثالث ينشّطها بحيث لا تمس عمليات التثبيت اللاحقة مفسّر النظام.

2. تثبيت العميل وأدوات التحقق

pip install --upgrade pip
pip install mistralai

هذا يحدّث pip ويثبّت عميل Mistral Python الرسمي. راجع توثيق العميل الحالي لدى مزوّدك للتوقيع الدقيق للاستيراد والدالة البانية — فهذه تتطور، ويوصى بشدة بتثبيت إصدار في requirements.txt لأي شيء تنوي تشغيله مرارًا.

pip install pytest pytest-cov ruff

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

3. بناء صندوق رمل يمكن التخلص منه

docker run --rm -it \
  -v "$PWD/../legacy-repo:/work" \
  -w /work \
  --network none \
  python:3.12-slim bash

يشغّل هذا حاوية يمكن التخلص منها مع تركيب المستودع الهدف عند /work، والأهم --network none لقطع حركة المرور الصادرة. ركّب فقط ما يحتاجه البناء. إذا كان المشروع يتطلب وصولًا إلى الشبكة لحل الاعتماديات، فحلّها أثناء بناء الصورة وشغّل حلقة الوكيل دون اتصال.

4. تهيئة بيانات الاعتماد والمسارات

export MISTRAL_API_KEY="your-key-here"
export LEGACY_REPO="$HOME/src/legacy-repo"
export AGENT_MODEL="<model available in your account>"

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

5. التقاط خط أساس

cd "$LEGACY_REPO"
git checkout -b modernization/agent-work
./build.sh > ../baseline-build.log 2>&1; echo "exit=$?"
./test.sh  > ../baseline-test.log  2>&1; echo "exit=$?"

تنشئ هذه الأوامر فرعًا مخصصًا وتسجّل نتائج البناء والاختبار الحالية. من دون خط أساس لا يمكنك التمييز بين تراجع أدخله الوكيل واختبار كان يفشل أصلًا عندما بدأت. سجّل رمزي الخروج واحتفظ بالسجلات.

التهيئة: جعل المستودع مقروءًا

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

ملف السياق

ضع ملفًا باسم AGENTS.md في جذر المستودع. يُقرأ في بداية كل مهمة، وينبغي أن يكون قصيرًا وواقعيًا ومملًا.

# Repository context

Build:      ./build.sh          (expect exit 0)
Test:       ./test.sh           (expect exit 0)
Lint:       ruff check src/

## Rules
- Do not modify anything under tests/ or testdata/.
- Do not edit generated files (headers marked "DO NOT EDIT").
- Maximum diff size per task: 400 changed lines.
- If a symbol appears unused, report it. Do not delete it.
- Prefer adding an adapter over changing an existing public signature.

## Known hazards
- src/legacy/pricing.py resolves handlers by string name at runtime.
- The build requires JAVA_HOME to be set.
- Module `reporting` has no test coverage.

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

قائمة الأدوات المسموح بها

عرّف، في ملف إعدادات صغير، بدقة أي أوامر يجوز للوكيل تشغيلها. وينبغي رفض كل ما عدا ذلك افتراضيًا.

# agent-policy.yaml
tools:
  read_file: true
  write_file: true
  search: true
  shell:
    allow:
      - "./build.sh"
      - "./test.sh"
      - "ruff check"
      - "pytest"
    deny:
      - "git push"
      - "rm -rf"
      - "curl"
      - "pip install"
limits:
  max_iterations: 25
  max_files_changed: 15
  max_diff_lines: 400

حدود التكرار والفرق هي الحاجز الأكثر فعالية على الإطلاق. وهي تحوّل "الوكيل أعاد كتابة كل شيء" إلى "الوكيل توقف وطلب المساعدة".

ملف التجاهل

أضف .agent-work/ إلى .gitignore بحيث لا تدخل جرد المساحة المؤقتة والسجلات والتقارير الوسيطة أبدًا في الفرق قيد المراجعة.

أداة تشغيل وكيل بسيطة

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

import json, os, subprocess, pathlib

REPO = pathlib.Path(os.environ["LEGACY_REPO"])
POLICY = json.loads(pathlib.Path("agent-policy.yaml.json").read_text())

def run(cmd: str) -> dict:
    """Execute an allowlisted command and return its output and exit code."""
    if not any(cmd.startswith(a) for a in POLICY["tools"]["shell"]["allow"]):
        return {"error": f"command not allowlisted: {cmd}"}
    p = subprocess.run(cmd, shell=True, cwd=REPO,
                       capture_output=True, text=True, timeout=900)
    return {"exit": p.returncode, "stdout": p.stdout[-4000:], "stderr": p.stderr[-2000:]}

def read_file(path: str) -> str:
    return (REPO / path).read_text(errors="replace")[:20000]

def write_file(path: str, content: str) -> str:
    target = REPO / path
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(content)
    return f"wrote {len(content)} bytes to {path}"

def search(pattern: str) -> str:
    p = subprocess.run(["rg", "-n", "--max-count", "5", pattern, str(REPO)],
                       capture_output=True, text=True)
    return p.stdout[:8000]

def ask_model(messages: list) -> dict:
    """Adapter: return {'tool': name, 'args': {...}} or {'final': text}."""
    raise NotImplementedError("wire this to your provider's client")

TOOLS = {"read_file": read_file, "write_file": write_file,
         "search": search, "shell": run}

def run_agent(task: str) -> str:
    messages = [{"role": "user", "content": task}]
    for step in range(POLICY["limits"]["max_iterations"]):
        reply = ask_model(messages)
        if "final" in reply:
            return reply["final"]
        result = TOOLS[reply["tool"]](**reply["args"])
        messages.append({"role": "assistant", "content": json.dumps(reply)})
        messages.append({"role": "user", "content": json.dumps(result)})
        if reply["tool"] == "shell" and reply["args"]["cmd"] == "./build.sh" \
           and result.get("exit") == 0:
            messages.append({"role": "user",
                             "content": "Build is green. Run ./test.sh to confirm."})
    return "iteration limit reached — escalating to human review"

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

أمثلة الاستخدام

مثال 1 — الجرد قبل التدخل

لا تدع الوكيل يحرّر في المرور الأول أبدًا. ابدأ بمهمة رسم خرائط للقراءة فقط.

Using search and read_file only, produce inventory.json containing:
- every top-level module and its file count
- the ten files with the highest inbound reference count
- modules with no corresponding test file
- any file containing the string "DO NOT EDIT"
Do not modify any file. Report your confidence per module.

تحقق من النتيجة بنفسك باستخدام أدوات قياسية قبل الوثوق بها:

rg --files -g '*.py' | wc -l
rg -n "DO NOT EDIT" -l

الأمر الأول يعدّ ملفات المصدر، والثاني يسرد الملفات المولّدة. قارن كليهما بجرد الوكيل؛ فالتناقضات هي المواضع التي يحتاج فيها ملف السياق لديك إلى تحسين.

مثال 2 — اختبارات التوصيف كشبكة أمان

إذا كانت الوحدة التي تنوي تغييرها بلا اختبارات، فابنِ اختبارًا حول سلوكها الحالي قبل لمسها. الهدف ليس الصحة — بل تثبيت مخرجات اليوم بحيث يمكن مقارنة إعادة الهيكلة غدًا بها.

import subprocess, pytest

CASES = ["order-1001", "order-1002", "refund-partial", "currency-mixed"]

@pytest.mark.parametrize("case", CASES)
def test_current_output_is_preserved(case):
    result = subprocess.run(
        ["./legacy_cli", "--case", case],
        capture_output=True, text=True, check=True,
    )
    assert result.stdout == open(f"golden/{case}.txt").read()

ولّد ملفات golden/ من النظام غير المعدّل، وراجعها يدويًا مرة واحدة، ثم جمّدها. اجعل الوكيل يكتب الأداة؛ وأنت توافق على المخرجات الذهبية.

مثال 3 — الاستخلاص التدريجي بنمط الخانق (strangler pattern)

فكّك الترحيل بحيث تكون كل مهمة قابلة للتراجع عنها بشكل مستقل.

Task: extract the tax calculation from src/legacy/orders.py into
src/tax/calculator.py behind an adapter.

Definition of done:
- src/legacy/orders.py imports the new module
- the original function remains, delegating to the adapter
- ./build.sh exits 0
- ./test.sh exits 0 and coverage on src/tax/ does not decrease
- no file outside src/legacy/orders.py and src/tax/ is modified
- diff is under 400 lines

If any constraint cannot be met, stop and report why.

شغّل التحقق بنفسك بدلًا من قبول ملخص الوكيل:

git diff --stat
git diff --name-only | grep -v -E '^(src/legacy/orders.py|src/tax/)' && echo "SCOPE VIOLATION"
./build.sh && ./test.sh

الأمر الأوسط هو المهم. يفشل بصوت عالٍ إذا لمس الوكيل ملفات خارج النطاق المتفق عليه، وهو الشكل الأكثر شيوعًا للانحراف الصامت.

مثال 4 — ترقية الاعتماديات مدفوعة بتغذية المترجم الراجعة

أخطاء المترجم هي أرخص تغذية راجعة يمكن أن يحصل عليها الوكيل. صِغ المهمة كتقارب، لا كتأليف.

Upgrade the pinned version of <dependency> in requirements.txt to the
next major version. Do not change application logic.

Loop: edit, run ./build.sh, read the errors, fix only what the errors
require. After the build is green, run ./test.sh. If a test fails,
revert the change that caused it and report the failure instead of
adjusting the test.

التعليمة بعدم تعديل الاختبارات جوهرية. فالوكيل الذي يحسّن من أجل مجموعة اختبارات ناجحة سيعيد كتابة التأكيد بسرور، ما يحوّل تراجعًا حقيقيًا إلى بناء ناجح.

الحواجز وأنماط الفشل

أنماط الفشل متسقة بما يكفي للتخطيط لها:

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

تعامل مع كل مخرجات الوكيل كطلب سحب من مساهم غير مألوف: مفيد، ويبدو صحيحًا، ويستلزم مراجعة.

قياس ما إذا كان ينجح

تتبّع مجموعة صغيرة من الأرقام لكل ترحيل، لا لكل مهمة:

  • معدل نجاح البناء بعد محاولة الوكيل الأولى.
  • الوسيط لحجم الفرق، وعدد انتهاكات النطاق.
  • التغطية على الوحدات الملموسة قبل وبعد.
  • معدل التراجع — تغييرات دُمجت ثم تراجعت لاحقًا.
  • دقائق المراجعة البشرية لكل تغيير مدموج.

المقياس الأخير هو المقياس الصادق. فالوكيل الذي ينتج فروقًا مبهرة على البشر تدقيقها ساعة لكل واحدة لم يحسّن شيئًا.

ما الذي يشير إليه منشور Mistral

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

الخاتمة

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

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

افعل ذلك، وسيتولى الوكيل الذيل الطويل من التقارب الميكانيكي الذي يجعل ترحيل الشيفرة القديمة بطيئًا — بينما تبقى أنت مسيطرًا على القرارات التي تجعله خطيرًا.

المصادر