Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,27 @@ jobs:
- name: (د) حارس سلامة البيان (بلا شبكة)
run: python scripts/check_sync.py --validate

# (AR) على `workflow_dispatch` يكون `base_ref` فارغًا ⇒ `origin/` ⇒ يفشل
# `git show` ⇒ قفلُ أساسٍ فارغ ⇒ يتخطّى الحارسُ نفسَه **صامتًا** ويُقرأ
# أخضرَ. الأخضرُ حينها يعني «لم يُقَس» لا «مرّ»؛ فنقصره على الـPR صراحةً.
- name: (أ) حارس القفل — لا تثبيت بصمةٍ دون مراجعة فصلها
if: github.event_name == 'pull_request'
run: |
BASE="origin/${{ github.base_ref }}"
git show "$BASE:sync/sources.lock.json" > base.lock.json 2>/dev/null || : > base.lock.json
# (AR) فرّق بين «لا قفلَ في الأساس» (تخطٍّ مشروع) و«تعذّر بلوغُ الأساس»
# (حمرة). كان `|| :` يبتلع الحالتين معًا، فأيُّ فشلٍ لـ`git show` —
# مرجعٌ غيرُ مجلوب، نقلُ مسارِ القفل — يُنتج أساسًا فارغًا فيتخطّى
# الحارسُ نفسَه ويُرجع صفرًا: فاشلٌ مفتوحًا.
git rev-parse --verify "$BASE" >/dev/null 2>&1 \
|| { echo "::error::مرجعُ الأساس $BASE غيرُ مجلوب — الحارسُ لم يُقَس."; exit 1; }
if git cat-file -e "$BASE:sync/sources.lock.json" 2>/dev/null; then
git show "$BASE:sync/sources.lock.json" > base.lock.json
else
: > base.lock.json # لا قفلَ في الأساس فعلًا — تخطٍّ مشروع
fi
# (AR) بيانُ الأساس يلزم لقياس المفاتيح **المُسقَطة**: المفتاحُ المحذوف
# لا أثرَ له في البيان الحاليّ، فلا يُعرَف أيُّ فصلٍ كان يستشهد به.
git show "$BASE:sync/sources.yaml" > base.sources.yaml 2>/dev/null || : > base.sources.yaml
git diff --name-only "$BASE"...HEAD > changed.txt
python scripts/check_sync.py --guard-lock --base base.lock.json --changed-files changed.txt
python scripts/check_sync.py --guard-lock --base base.lock.json \
--base-manifest base.sources.yaml --changed-files changed.txt
14 changes: 13 additions & 1 deletion .github/workflows/sync-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,21 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
set +e
set +e
python scripts/check_sync.py --ref "${{ steps.ref.outputs.ref }}" --json > report.json
echo "drift=$?" >> "$GITHUB_OUTPUT"
RC=$?
set -e
# (AR) 0 و1 وحدهما قياسان (لا انجراف / انجراف). أيُّ رمزٍ سواهما — و`2`
# يُرجَع عند غياب القفل قبل طباعةِ أيّ JSON — كان يجعل الشرطين
# كاذبَين معًا: لا قضيّةَ تُفتح، ولا خطوةَ «لا انجراف»، والوظيفةُ
# خضراء وهي لم تقس شيئًا.
if [ "$RC" != "0" ] && [ "$RC" != "1" ]; then
echo "::error::رمزُ خروجٍ غيرُ مقيس ($RC) — لم يُقَس الانجراف."
cat report.json || true
exit "$RC"
fi
echo "drift=$RC" >> "$GITHUB_OUTPUT"
cat report.json

- name: صياغة جسم القضيّة
Expand Down
261 changes: 234 additions & 27 deletions scripts/check_sync.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
# الجزء الأول · البدء

- [إعداد البيئة والبناء](getting-started/setup.md)
- [أوّل مساهمة (Walkthrough)](getting-started/first-contribution.md)
- [خريطة المستودع](getting-started/repo-map.md)
- [أوّل مساهمة (Walkthrough)](getting-started/first-contribution.md)

# الجزء الثاني · المعمارية

Expand Down
9 changes: 6 additions & 3 deletions src/architecture/interconnected.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

> **ماذا ستتعلّم:** لماذا لا يوجد «تغيير معزول» في لغة ص، وأيّ ملفّات يَمَسّها كل نوع تغيير.

> 📎 المجلّدات المذكورة أدناه مبصومةٌ **بأسماء مدخلاتها** على `dev` — فظهورُ ملفٍّ
> جديدٍ فيها أو اختفاؤه يُنذر هذا الفصل، ولا يُنذره تعديلُ محتوًى داخلها.

كل ميزة تعبر عدّة أنظمة: مصدر الحقيقة + المُولَّد + المعجمي/النحوي + المفسّر + المترجم +
الأخطاء + التوثيق + الاختبارات. تجاهل أحدها يكسر CI أو تجربة المستخدم.

## جدول الأثر (File List) حسب التغيير
| التغيير | الملفّات المتأثّرة عادةً |
|---------|--------------------------|
| **كلمة مفتاحيّة** | `language-truth/keywords.yaml` → `shared/lexer/generated/keywords_generated.{h,cpp}` (مُولَّد) + `shared/parser/src/<dir>/` + `shared/ast/include/` + `interpreter/src/visitors/` + `compiler/src/frontend/` (+opcode في `sir_types.h`) + اختبار `.ص` |
| **دالة مضمنة** | `language-truth/builtins/<domain>.yaml` (+`_index.yaml`) → مُولَّد `builtin_registry_generated.h` + `interpreter/src/builtins/` + `compiler/src/backend/llvm/builders/builtins/` + اختبار |
| **كلمة مفتاحيّة** | `language-truth/keywords.yaml` → `shared/lexer/generated/keywords_generated.{h,cpp}` (مُولَّد) + `shared/parser/src/<dir>/` + `shared/ast/include/` + `interpreter/src/visitors/` (٣٠ ملفَّ `.cpp` في المجلّد — لا كلُّها يمسُّها كلُّ تغيير) + `compiler/src/frontend/` (+opcode في `sir_types.h`) + اختبار `.ص` |
| **دالة مضمنة** | `language-truth/builtins/<domain>.yaml` (+`_index.yaml`) → مُولَّد `shared/builtins/generated/builtin_registry_generated.h` + `interpreter/src/builtins/` + `compiler/src/backend/llvm/builders/builtins/` (٩ مدخلات: ٨ `.cpp` + `README.md`) + اختبار |
| **رمز خطأ** | `language-truth/errors/<cat>.yaml` (مصدر) + `shared/errors/include/error_codes.h` + مُولَّد + اختبار |
| **توجيه `@`** | `language-truth/directives.yaml` + مُولَّد + parser + AST + visitors + codegen + اختبار |
| **قاعدة نحويّة** | `language-truth/grammar/*.yaml` (SoT) + `shared/parser/src/` + توثيق مُولَّد `docs/parser_rule/_generated/` |
| **قاعدة نحويّة** | `language-truth/grammar/*.yaml` (SoT) + `shared/parser/src/` + توثيق مُولَّد `docs/parser_rule/_generated/` (٨ أقسام + `INDEX.md`) |
| **opcode SIR** | `compiler/include/frontend/sir_types.h` + `SIRBuilder` + `compiler/src/backend/llvm/` + اختبار |

## مخطّط التشابك (مثال: كلمة مفتاحيّة)
Expand Down
5 changes: 3 additions & 2 deletions src/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
| المترجم | `compiler/` | AST → SIR → LLVM IR → ملفّ تنفيذيّ (SIR يدعم تعليمات ملكية) |
| الخلفيّة الأصليّة | `compiler/include/backend/native/` | SIR → شيفرة آلة → ELF64 ساكن بلا LLVM ولا رابطٍ أجنبيّ — [الفصل](../backend/native.md) |
| ~~الآلة الافتراضية~~ | — | `vm/` أُزيل من الشجرة بالإيداع `bcf0a746` («ستُعاد كتابتها من الصفر») — لا فصل له حتّى تُكتب |
| المكتبة القياسية | `stdlib/` | وحدات عربية: core/io/math/string/network/graphics |
| الأدوات | `tools/` | ١٥ مجلّدًا على `dev`: analyze · apk_builder · build · check · compiler (واجهة `sad-build`) · formatter · hub (موزِّع `sad`) · installers · lsp · pkg · profiler · repl · security-scanner · shared · wasm |
| المكتبة القياسية | `stdlib/` | ثماني وحدات `.ص` عربيّة في الجذر + سبعةَ عشرَ مجلّدَ دعمٍ C++ — التعدادُ في [خريطة المستودع](../getting-started/repo-map.md) |
| الرسومات | `features/graphics/` | SadUI: محرّك التخطيط ومفاتيح الخصائص — **ليست في `stdlib/`** — [الفصل](../systems/sadui-layout.md) |
| الأدوات | `tools/` | ١٥ مجلّدًا على `dev`؛ منها `compiler` (واجهة `sad-build`) و`hub` (موزِّع `sad`) — التعدادُ في [خريطة المستودع](../getting-started/repo-map.md) |
| مصدر الحقيقة | `language-truth/` | YAML SoT لكل بيانات اللغة + القواعد |

## القاعدة الطبقيّة (CW-02)
Expand Down
5 changes: 3 additions & 2 deletions src/contributing/definition-of-done.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
## التنفيذ المزدوج والاختبار
- [ ] الدعم مضاف في **المفسّر والمترجم** (أو `@skip_compiler` موثّق بسبب صريح).
- [ ] **اختبار `.ص`** جديد (إيجابيّ + سلبيّ) بصيغة `@expected` الصحيحة.
- [ ] `runner.py --level P0` (وقسم الميزة) يمرّ **100%**.
- [ ] `runner.py --level P1` يمرّ **قبل أي PR** — لا تراجع (BF-29).
- [ ] `python tests/runner.py --level P0` (وقسم الميزة) يمرّ **100%**.
- [ ] `python tests/runner.py --level P1` يمرّ **قبل أي PR** — لا تراجع (BF-29).
- [ ] الثنائيّان مبنيّان في **تهيئةٍ واحدة** — `tests/config.yaml` يقرأ كليهما من `build/bin/Debug/`.
- [ ] `sad-build` يبني بلا أخطاء، و`sad-run` يعمل بلا تراجع.

## الجودة والتوافق
Expand Down
78 changes: 68 additions & 10 deletions src/contributing/freshness.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,48 @@ chapters:
- language-truth/keywords.yaml
```

المصدر إمّا **مسارٌ كامل** (ملف أو مجلد، يُرصد أيّ تغيّر تحته) أو **كائن `{path, lines}`**
يبصم **نطاقًا بعينه** فقط. النطاق يقلّل **الإنذارات الكاذبة** في الملفّات الكبيرة
(تعديلٌ خارج المنطقة الموثَّقة لا يُطلِق إنذارًا) ويرصد **تعفّن** المنطقة المقصودة تحديدًا.
للمصدر **أربعةُ أنماطِ بصم**، ولكلٍّ دعوًى يطابقها:

| النمط | الصيغة | يرصد | لا يرصد |
|---|---|---|---|
| مسارٌ كامل | `path/to/file` | أيّ تغيّرٍ في الملفّ/المجلّد | — |
| نطاقُ أسطر | `{path, lines: "10-90"}` | تعفّنَ المنطقة الموثَّقة | تعديلًا خارجها |
| **أسماءُ المدخلات** | `{path, names: true}` | ظهورَ مدخلٍ أو اختفاءَه أو تبدّلَ نوعه | تعديلَ محتوًى داخل المجلّد |
| **مصدرٌ داخليّ** | `self:path` | تغيّرَ أدواتِ الدليل نفسِها (بلا شبكة، ويُقاس في الـPR) | — |

> بصمةُ `self:` تُحسب على الأسطر مُطبَّعةً (`\n`)، فلا تنقلب بتبديل CRLF/LF بين
> ويندوز ولينكس — مقيسٌ لا مفترَض. وبصمةُ `names` الداخليّة **تُشتقّ من `git
> ls-tree` لا من مسحِ القرص**، فلا تدخلها مخرجاتُ البناء ولا `.venv` ولا ملفّاتُ
> خطوةِ الحارسِ نفسِها (قِيس: جذرُ مستودعِ اللغة ٤٣ مدخلًا في git و٧٠ على القرص).
>
> وقاعدةٌ تسري على الأنماط كلِّها: **العدمُ ليس قياسًا.** مجلّدٌ فارغ · نطاقٌ خارجَ
> الملفّ · ردٌّ فارغٌ من `gh` · مجلّدٌ تجاوز سقفَ ١٠٠٠ مدخلٍ في `contents` API —
> كلُّها تُقرأ «متعذّرًا» يحمرّ، لا بصمةً تُثبَّت. لولا ذلك لأنتجت هذه الحالاتُ
> **بصمةً واحدةً مشتركة** (`sha256:e3b0c442…`)، فيحمرُّ الفحصُ مرّةً ثمّ يثبّتها
> `--update`، ويبقى المصدرُ أخضرَ إلى الأبد وهو غيرُ مقيسٍ أصلًا.

النطاق يقلّل **الإنذارات الكاذبة** في الملفّات الكبيرة (تعديلٌ خارج المنطقة الموثَّقة لا
يُطلِق إنذارًا) ويرصد **تعفّن** المنطقة المقصودة تحديدًا.

نمط **`names`** هو الوحيد الذي يطابق دعوى «هذا المجلّد يحوي كذا وكذا» — وهي دعوى
`repo-map.md` و`overview.md`. المقيسُ يُبيّن لِمَ لا تصلح بصمةُ الشجرة (tree sha) لها:
بين إيداعَين على `dev` تباعدا ٢٥٠ إيداعًا، انقلبت بصمةُ شجرة `tools/` وبقيت أسماءُ
مدخلاتها **كما هي** — أي إنذارٌ أسبوعيٌّ دائمٌ بلا واقعة. وفي المدّة نفسِها التقط
النمطُ في `stdlib/` ما يجب التقاطُه بالضبط: **اختفاء** `async` و`audio3d` و`crypto`
و`embedded` و**ظهور** `جيسون.ص`. ويُكتب `path: "."` لبصم جذر المستودع — فعودةُ `vm/`
أو ظهورُ جزءٍ جديدٍ تُنذر خريطةَ المستودع.

ونمط **`self:`** يبصم من **هذا المستودع** لا من مستودع اللغة، لأنّ فصلًا كهذا يوثّق
`check_sync.py` و`sync-check.yml` أنفسَهما. وسببُه واقعة: أُصلح افتراضُ المرجع في
الأداة، وبقي هذا الفصلُ يصف الافتراضَ المنقوض في اليوم نفسِه — بلا كاشف. الآن أيُّ
تعديلٍ في الأداتين يُنذر الفصلَ الذي يصفهما.

الحقل `covers_version` يوثّق **حالة اللغة التي رُوجِع الدليل تجاهها**. ولمّا كان
`ref: dev` — لا وسمَ إصدارٍ — فصيغتُه اليوم `dev@<إيداع مختصر>` (مثلًا
`dev@1138f5e1`)، أدقُّ من رقم semver لأنّها تسمّي **الإيداع** الذي قِيست عنده
البصمات لا إصدارًا مُتخيَّلًا. يُكتَب في البيان والقفل معًا: `--update --set-version`
ينسخه إلى القفل، و`--update` وحدَه يأخذه من البيان. لا يقيّده مخطّطٌ ولا يقرؤه حارس؛
البصمات لا إصدارًا مُتخيَّلًا. و`--update` **يشتقّه** من `git ls-remote` عند
المرجع المقيس بدل نسخِه من البيان — فلا يصير عددًا منثورًا صادقًا بالمصادفة؛
و`--set-version` يتقدّم عليه حين تُثبِّت عند وسمِ إصدار. لا يقيّده مخطّطٌ ولا يقرؤه حارس؛
مستهلِكُه الوحيد نصُّ قضيّة `sync-check.yml` الذي يطبعه كما هو.

## 2) كاشف الانجراف — `scripts/check_sync.py`
Expand All @@ -51,9 +85,13 @@ flowchart LR
CHK -->|اختلاف| DRIFT["⚠️ انجراف → فصول متأثّرة"]
```

## 3) الأتمتة — مربوطة بالإصدارات
`.github/workflows/sync-check.yml` يقيس الانجراف تجاه **آخر وسم إصدار** للغة (لا تجاه
`dev` المتقلّب)، فتعني النتيجة «هل يطابق الدليلُ الإصدارَ المنشور؟». يعمل:
## 3) الأتمتة
`.github/workflows/sync-check.yml` يقيس الانجراف تجاه **مرجع القفل نفسِه**
(`sources.lock.json`.ref — اليوم `dev`)، لأنّ البصمات أُخذت عنده، فقياسُها عند مرجعٍ
سواه يقارن شيئًا بغيره. (كان الافتراضُ «آخر وسم إصدار»، فأعلن التقريرُ عند `v1.0.0`
حذفَ ٣٠ مسارًا وتعذّرَ ٤٧ — وكلّها قائمةٌ سليمةٌ على `dev` — فصار الإنذارُ ضجيجًا لا
يُقرأ؛ القضيّة #1 شاهدُه.) ولقياس الدليل تجاه إصدارٍ منشور: أعِد البصم عند وسمِه
(`--ref vX --update --set-version X`) فيصير هو مرجعَ القفل. يعمل:
- **عند إصدار لغة جديد** (`repository_dispatch: language-release` يطلقه مستودع اللغة)،
- **أسبوعيًّا** (شبكة أمان)، و**يدويًّا** (مع تحديد ref اختياريّ).

Expand All @@ -64,8 +102,28 @@ flowchart LR
الكشف وحده لا يكفي؛ يلزم منعُ الالتفاف عليه. حارسان في CI (`ci.yml`) على كل PR:
- **حارس القفل (لا كتم صامت):** يرفض تقدّم أيّ بصمةٍ في `sources.lock.json` **ما لم
يُعدَّل الفصل المرتبط بها في نفس الـPR**. فلا يصير `--update` زرَّ إسكاتٍ بلا مراجعة.
- **حارس البيان:** يتحقّق (بلا شبكة) من وجود ملفّات الفصول، صحّة نطاقات الأسطر، وأنّ
**كل فصل تقنيّ في `SUMMARY` مسجَّلٌ** مصادرُه — فلا يَفلت فصلٌ جديد من المظلّة.
ويقيس **الاتّجاهين**: التقدّمَ و**الإسقاط**. فإزالةُ مصدرٍ من البيان ثمّ `--update`
كتمٌ صامتٌ من البابِ المقابل — يُقتَل الكاشفُ لفصولٍ كاملةٍ بلا مراجعةِ حرف. ولمّا
كان المفتاحُ المُسقَط لا أثرَ له في البيان الحاليّ، تُقرأ فصولُه من **بيان
الأساس** عبر `--base-manifest`؛ وإن لم يُمرَّر، احمرّ الحارسُ ولم يمرّ: الحارسُ
الذي لا يجد ما يقيس به لا يقول «مرّ».
- **حارس البيان:** يتحقّق (بلا شبكة) من وجود ملفّات الفصول، صحّة نطاقات الأسطر،
وسلامةِ النمطين الجديدين (`names` و`lines` لا يجتمعان؛ ومصدرُ `self:` موجودٌ فعلًا)،
وأنّ **كلّ فصلٍ في `SUMMARY`** تحت الأقسام السبعة `frontend/` · `backend/` ·
`systems/` · `sot/` · `architecture/` · `getting-started/` · `contributing/`
له مصادرُ. كانت المظلّةُ أربعةَ أقسامٍ فقط، فبقيت الثلاثةُ الأخيرة — وهي حاملةُ
دعاوى الأوامرِ التي يُشغّلها القارئ — **خارجها كلّيًّا** حتّى تعفّنت. (المرجع:
`GUARDED_SECTIONS` في `scripts/check_sync.py`.)
صفحاتُ الجذر (`introduction` · `glossary` · `status` · `SUMMARY`) خارج المظلّة
عمدًا: دعاواها عن الدليل لا عن اللغة. ومنها ما يُسجَّل **طوعًا** حين يحمل دعوًى
مقيسة — كـ`glossary.md` وأسماءِ الثنائيّات.
ويقيس هذا الحارسُ كذلك **تعفّنَ مصادر `self:` في نفس الـPR**: هي بلا شبكة، فلا
عذرَ لتأجيلها إلى الفحص الأسبوعيّ. فلو عُدِّل `check_sync.py` وحده دون تثبيتٍ
ومراجعةِ هذا الفصل، احمرّ الـPR — لا مرّ أخضرَ وفصلُه يصفُ سلوكًا منقوضًا (وهي
عينُ الواقعة التي وُلد منها النمط). و**غيابُ القفل نفسِه خطأٌ صريح** في
`--validate` و`--guard-lock` كليهما: حذفُ ملفٍّ واحدٍ كان يُطفئ البوّابةَ صامتةً
وخضراء. وحين يَنقص البيانُ فصلًا محروسًا، يُختَم السجلُّ بـ`❌` لا بـ`✅` —
لأنّ مَن يمسح سجلَّ CI بعينه يقرأ آخرَ سطرٍ حكمًا.

## 5) العقد عبر المستودعين
العقد يجب أن يصل لمن **يغيّر اللغة فعلًا** (في مستودع اللغة، لا هنا):
Expand Down
Loading
Loading