From 9a77a7b893078496eb35331d704ad90b8203a9b4 Mon Sep 17 00:00:00 2001 From: Sad Lang Dev Date: Tue, 1 Sep 2026 14:28:54 +0300 Subject: [PATCH] =?UTF-8?q?=D8=AF=D9=84=D9=8A=D9=84:=20=D8=A7=D9=84=D8=AD?= =?UTF-8?q?=D8=A7=D8=B1=D8=B3=D9=8F=20=D9=8A=D9=82=D9=8A=D8=B3=D9=8F=20?= =?UTF-8?q?=D8=A7=D9=84=D8=A7=D8=AA=D9=91=D8=AC=D8=A7=D9=87=D9=8A=D9=86?= =?UTF-8?q?=D8=8C=20=D9=88=D8=A7=D9=84=D8=B9=D8=AF=D9=85=D9=8F=20=D9=84?= =?UTF-8?q?=D9=8A=D8=B3=20=D9=82=D9=8A=D8=A7=D8=B3=D9=8B=D8=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit الموجةُ الثالثة سدّت ثقبًا بنيويًّا: أحدَ عشرَ فصلًا كانت خارجَ مظلّة الحارس لا إهمالًا، بل لأنّ الأداةَ لم يكن فيها نمطُ بصمٍ يطابق دعواها. ثمّ كشفت ثلاثُ مراجعاتٍ مستقلّةٍ أنّ الحارسَ نفسَه كان يقيس اتّجاهًا واحدًا. نمطا بصمٍ جديدان - {path, names: true} — يبصم أسماءَ وأنواعَ مدخلات المجلّد مرتَّبةً، لا محتواها. وهو الوحيدُ الذي يطابق دعوى «هذا المجلّد يحوي كذا». قِيس على ٢٥٠ إيداعًا: بصمةُ شجرةِ tools/ انقلبت (d3fffd2e ⇐ d0f55ac4) والأسماءُ ثابتة — أي إنذارٌ أسبوعيٌّ دائمٌ بلا واقعة، وهو الضجيجُ الذي يُسكِت الكاشفَ كلَّه. وفي المدّة نفسِها التقط النمطُ في stdlib/ ما يجب: اختفاءَ async·audio3d·crypto·embedded وظهورَ جيسون.ص. - self: — يبصم من هذا المستودع بلا شبكة، فيصير بوّابةَ PR لا كاشفًا أسبوعيًّا. وسببُه واقعة: أُصلح افتراضُ المرجع في الأداة وبقي الفصلُ يصف المنقوضَ في اليوم نفسِه، بلا كاشف. المظلّة: من أربعة أقسامٍ إلى سبعة (GUARDED_SECTIONS)، فدخلت architecture/ وgetting-started/ وcontributing/ — وهي حاملةُ دعاوى الأوامرِ التي يُشغّلها القارئ. التغطية الآن ٢٩ فصلًا محروسًا، صفرُ فصلٍ يفلت. الحارسُ يقيس الاتّجاهين - الإسقاطُ كتمٌ صامتٌ أيضًا: إزالةُ مصدرٍ من البيان ثمّ --update كانت تقتل الكاشفَ لفصولٍ كاملةٍ والبوّابتان خضراوان. تُقرأ فصولُ المفتاح المُسقَط من بيان الأساس عبر --base-manifest؛ وإن لم يُمرَّر، احمرّ ولم يمرّ. - العدمُ ليس قياسًا: مجلّدٌ فارغ · نطاقٌ خارجَ الملفّ · ردٌّ فارغٌ من gh · تجاوزُ سقفِ ١٠٠٠ مدخلٍ في contents API — كلُّها كانت تُنتج بصمةً واحدةً مشتركة (e3b0c442…) تُثبَّت فتخضرّ إلى الأبد. صارت None يحمرّ. - غيابُ القفل كان يُطفئ البوّابةَ صامتةً — صار خطأً صريحًا في --validate و--guard-lock. - رمزُ الخروج ٢ في sync-check.yml كان يجعل شرطَي drift=='1' و=='0' كاذبَين معًا: لا قضيّة، ولا خطوةَ «لا انجراف»، والوظيفةُ خضراء وهي لم تقس شيئًا. - git show || : كان يبتلع «تعذّر بلوغُ الأساس» مع «لا قفلَ في الأساس» — فُصلا. - «✅ سليم … ١ تحذيرًا» مع RC=1 صار «❌ البيانُ ناقص»: مَن يمسح سجلَّ CI بعينه يقرأ آخرَ سطرٍ حكمًا. - self:+names كان يمسح القرص (٧٠ مدخلًا) والوعدُ «ما يخزّنه git» (٤٣) — صار يُشتقُّ من git ls-tree. - covers_version كان يُنسخ بيدٍ فيصدُق بالمصادفة — صار يُشتقُّ من git ls-remote. الفصول، مقيسةً على origin/dev@1138f5e1 - setup: CMake ٣٫١٥ لا ٣٫٢٠ · x.py بوّابةً · sadc اسمٌ متقاعد · sad.exe موزِّعٌ لا مفسّر · python tests/runner.py (لا runner.py في الجذر). و«LLVM اختياريّ» كانت تَعِدُ بطريقٍ لا يُكمله أيُّ أمرٍ في الصفحة: هدفُ sad-build يُعرَّف داخل if(ENABLE_LLVM_BACKEND AND LLVM_FOUND)، والبديلُ الحقيقيّ sad-build-native. - repo-map: القوائمُ العربيّةُ أُخرجت من كتلة الكود إلى جدول — theme/rtl.css يفرض direction: ltr على كلّ pre، فكانت تُعرَض معكوسةَ الترتيب وبنجومها. وأوامرُ x.py سبعةٌ لا خمسة. - first-contribution: الاختبارُ تحت tests/behavior/ لا tests/ — وكان القارئ يمرّ بالدعوى المنقوضة قبل الفصل الذي يصحّحها؛ فأُعيد ترتيبُ المسار: إعداد ⇐ خريطة ⇐ أوّل مساهمة. - overview · interconnected · glossary · dod: التكرارُ الحرفيُّ أُزيل (التعدادُ في الخريطة، والدورُ ورابطٌ في النظرة العامّة)، والأعدادُ صارت تقول إنّها حجمُ مجلّدٍ لا حجمُ أثر. ثلاثُ دعاوى كانت مقيسةً بلا حارس صارت مبصومة: apps/CMakeLists.txt#L292-310 · tests/config.yaml#L15-23 · #L27 في فصلين. البراهين (كسرٌ متعمّدٌ ثمّ استعادة، لا قراءةُ كود) - إسقاطُ .#names ⇒ ❌ يسمّي overview·repo-map·setup — RC=1 - _chunk_digest للحالات الأربع ⇒ None - غيابُ القفل ⇒ RC=1 في --validate و--guard-lock - حذفُ تسجيلِ فصلٍ ⇒ ❌ البيانُ ناقص - تعديلُ check_sync.py بلا تثبيت ⇒ ❌ يسمّي freshness.md الحالة: ٩٢ بصمة · ٣٠ فصلًا مسجَّلًا · drift: false · mdbook نظيف. راجعه ثلاثةُ وكلاء: تجربةُ القارئ · محاكمةُ كلِّ دعوًى على origin/dev · جولةٌ ثالثةٌ على الأداة. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 22 +- .github/workflows/sync-check.yml | 14 +- scripts/check_sync.py | 261 +++++++++++++++++++--- src/SUMMARY.md | 2 +- src/architecture/interconnected.md | 9 +- src/architecture/overview.md | 5 +- src/contributing/definition-of-done.md | 5 +- src/contributing/freshness.md | 78 ++++++- src/getting-started/first-contribution.md | 13 +- src/getting-started/repo-map.md | 40 +++- src/getting-started/setup.md | 74 ++++-- src/glossary.md | 5 +- sync/sources.lock.json | 23 +- sync/sources.yaml | 66 ++++++ 14 files changed, 532 insertions(+), 85 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cf91e6..e0a61a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/sync-check.yml b/.github/workflows/sync-check.yml index 78285e4..022693b 100644 --- a/.github/workflows/sync-check.yml +++ b/.github/workflows/sync-check.yml @@ -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: صياغة جسم القضيّة diff --git a/scripts/check_sync.py b/scripts/check_sync.py index 5f946cc..4e5dcee 100644 --- a/scripts/check_sync.py +++ b/scripts/check_sync.py @@ -8,6 +8,14 @@ - ملفّ/مجلد كامل: بصمة git (blob/tree sha) عبر `gh api .../contents`. - نطاق أسطر (تقليل الضجيج): نجلب المحتوى الخام، نقتطع النطاق، ونبصمه sha256. هكذا لا يُطلِق تعديلٌ خارج النطاق إنذارًا كاذبًا، ويُرصد «تعفّن» المنطقة الموثَّقة. + - أسماء المدخلات `{path, names: true}`: نبصم **أسماءَ وأنواعَ** مدخلات المجلّد + (مرتَّبةً) لا محتواها. هذا النمطُ هو الوحيد الذي يطابق دعوى «هذا المجلّد يحوي + كذا وكذا» — فبصمةُ الشجرة (tree sha) تنقلب مع أيّ تعديلِ محتوًى داخله فتُنتج + إنذارًا أسبوعيًّا دائمًا، وهو عينُ الضجيجِ الذي يُسكِت الكاشفَ كلَّه. + ويُكتب `path: "."` لبصم جذر المستودع (دعاوى خريطة الشجرة). + - مصدرٌ داخليّ `self:`: يُبصَم من **هذا المستودع** لا من مستودع اللغة، بلا + شبكة. لأنّ فصولًا (مثل freshness.md) توثّق أدواتِ الدليل نفسِها، فكانت تتعفّن + عنها بلا كاشف — وقد وقع ذلك فعلًا: وصف الفصلُ سلوكًا نُقض في نفس اليوم. الأطوار: (افتراضيّ) فحص وتقرير؛ يفشل (1) عند الانجراف. @@ -59,13 +67,28 @@ # ── تطبيع المصادر ────────────────────────────────────────────────────────────── def normalize_source(s) -> dict: - """يحوّل مصدرًا (نصّ أو كائن {path, lines}) إلى صيغة موحّدة بمفتاح فريد.""" + """يحوّل مصدرًا (نصّ أو كائن {path, lines|names}) إلى صيغة موحّدة بمفتاح فريد.""" if isinstance(s, str): - return {"path": s, "lines": None, "key": s} + return {"path": s, "lines": None, "names": False, "key": s} path = s["path"] lines = s.get("lines") - key = f"{path}#L{lines}" if lines else path - return {"path": path, "lines": lines, "key": key} + names = bool(s.get("names")) + if names: + key = f"{path}#names" + elif lines: + key = f"{path}#L{lines}" + else: + key = path + return {"path": path, "lines": lines, "names": names, "key": key} + + +def is_self(spec: dict) -> bool: + """مصدرٌ داخليّ: يُبصَم من مستودع الدليل نفسِه، لا من مستودع اللغة.""" + return spec["path"].startswith("self:") + + +def self_path(spec: dict) -> Path: + return ROOT / spec["path"][len("self:"):] def parse_lines(spec: str) -> tuple[int, int]: @@ -74,6 +97,16 @@ def parse_lines(spec: str) -> tuple[int, int]: # ── جلب البصمات عبر gh ───────────────────────────────────────────────────────── +def _run(args: list[str]) -> str | None: + """أمرٌ محلّيّ (git) — يعيد None عند أيّ فشل، فلا يُقرأ العجزُ نجاحًا.""" + try: + out = subprocess.run(args, capture_output=True, text=True, + check=True, encoding="utf-8") + except (subprocess.CalledProcessError, FileNotFoundError, OSError): + return None + return out.stdout + + def _gh(args: list[str]) -> str | None: try: out = subprocess.run(["gh", *args], capture_output=True, text=True, @@ -104,22 +137,99 @@ def _gh_tree_sha(repo: str, ref: str, path: str) -> str | None: return out.strip() if out else None +def gh_entry_names(repo: str, ref: str, path: str) -> str | None: + """أسماءُ وأنواعُ مدخلات المجلّد مرتَّبةً — لا محتواها.""" + api_path = "" if path in (".", "") else path + # (AR) `contents` يقتطع عند 1000 مدخلًا **صامتًا** — لا يُخطئ. فقائمةٌ مقتطعةٌ + # تُبصَم بصمةً ثابتةً تخضرّ أبدًا. نجعل التجاوزَ «متعذّرًا» لا بصمة. + out = _gh(["api", f"repos/{repo}/contents/{api_path}?ref={ref}", + "--jq", 'if type=="array" then (if length >= 1000 then "CAP" ' + 'else ([.[] | .type + ":" + .name] | sort | join("\n")) end) ' + 'else "NOTDIR" end']) + if out is None: + return None + val = out.strip() + if val == "CAP": + print(f"⚠️ {path}: تجاوز سقفَ 1000 مدخلٍ في contents API — لم يُقَس.", + file=sys.stderr) + return None + return None if val in ("", "NOTDIR") else val + + +def _names_digest(listing: str) -> str: + return "names:" + hashlib.sha256(listing.encode("utf-8")).hexdigest()[:16] + + def gh_raw(repo: str, ref: str, path: str) -> str | None: return _gh(["api", "-H", "Accept: application/vnd.github.raw", f"repos/{repo}/contents/{path}?ref={ref}"]) +def _chunk_digest(raw: str, lines_spec: str | None) -> str | None: + lines = raw.splitlines() + if lines_spec: + a, b = parse_lines(lines_spec) + lines = lines[a - 1:b] + # (AR) لا مقطعَ ⇒ «متعذّر» يحمرّ، لا بصمةَ خواءٍ تُثبَّت فتخضرّ للأبد. + # أربعُ حالاتٍ متباينةٍ كانت تُنتج البصمةَ نفسَها (`e3b0c442…`): ردٌّ فارغٌ + # من gh · نطاقٌ خارجَ الملفّ · ملفٌّ صفريّ · ملفٌّ انكمش دون النطاق. + # فلو انكمش الملفُّ لاحمرَّ الفحصُ مرّةً ثمّ ثبّته `--update`، ومنذُها + # يبقى المصدرُ أخضرَ وهو غيرُ مقيسٍ أصلًا. + if not lines: + return None + digest = hashlib.sha256("\n".join(lines).encode("utf-8")).hexdigest()[:16] + return f"sha256:{digest}" + + +def fetch_self_sha(spec: dict) -> str | None: + """بصمةُ مصدرٍ داخليّ من قرص هذا المستودع — بلا شبكة.""" + p = self_path(spec) + if spec["names"]: + # (AR) الوعدُ في freshness.md أن نستبعدَ ما لا يخزّنه git. مسحُ القرص + # بقائمةِ استثناءاتٍ منثورةٍ لا يفي به: قِيس أنّ جذرَ مستودعِ اللغة + # 43 مدخلًا في git و70 على القرص (`book/` · `.venv` · بل ملفّاتُ + # خطوةِ الحارسِ نفسِها). فنشتقُّ الحقيقةَ من git لا من القرص. + if not p.is_dir(): + return None + # (AR) القرصُ يحوي ما لا يحويه git (`.git` · `__pycache__` · مخلّفاتُ بناء)، + # فبصمةٌ تشملها تنقلب بلا واقعةٍ في المستودع — ضجيجٌ غيرُ حتميّ. + # نستبعدها لتبقى البصمةُ دالّةً على الشجرة المُودَعة وحدها. + skip = {".git", "__pycache__", ".mypy_cache", ".pytest_cache"} + rel = spec["path"][len("self:"):].strip("/") + out = _run(["git", "-C", str(ROOT), "ls-tree", "HEAD", + (rel + "/") if rel else "./"]) + if out is not None: + listed = [] + for line in out.splitlines(): + if not line.strip(): + continue + meta, _, name = line.partition("\t") + kind = meta.split()[1] + listed.append(("dir:" if kind == "tree" else "file:") + + Path(name).name) + return _names_digest("\n".join(sorted(listed))) if listed else None + entries = sorted(("dir:" if c.is_dir() else "file:") + c.name + for c in p.iterdir() if c.name not in skip) + # (AR) مجلّدٌ فارغ ⇒ None كجانب github تمامًا (git لا يخزّن مجلّدًا فارغًا)، + # فلا نسجّل «بصمةَ خواء» تُقرأ نجاحًا. + return _names_digest("\n".join(entries)) if entries else None + if not p.is_file(): + return None + return _chunk_digest(p.read_text(encoding="utf-8"), spec["lines"]) + + def fetch_sha(repo: str, ref: str, spec: dict) -> str | None: - """بصمة المصدر: نطاق أسطر ⇒ sha256 للمقطع؛ غير ذلك ⇒ git sha.""" + """بصمة المصدر: أسماءُ مدخلات ⇒ names؛ نطاقُ أسطر ⇒ sha256؛ غيرُهما ⇒ git sha.""" + if is_self(spec): + return fetch_self_sha(spec) + if spec["names"]: + listing = gh_entry_names(repo, ref, spec["path"]) + return None if listing is None else _names_digest(listing) if spec["lines"]: raw = gh_raw(repo, ref, spec["path"]) if raw is None: return None - lines = raw.splitlines() - a, b = parse_lines(spec["lines"]) - chunk = "\n".join(lines[a - 1:b]) - digest = hashlib.sha256(chunk.encode("utf-8")).hexdigest()[:16] - return f"sha256:{digest}" + return _chunk_digest(raw, spec["lines"]) return gh_content_sha(repo, ref, spec["path"]) @@ -162,12 +272,24 @@ def collect_shas(manifest: dict, ref: str) -> tuple[dict, list]: # ── ملف القفل ────────────────────────────────────────────────────────────────── +def derived_version(repo: str, ref: str) -> str | None: + """`dev@<إيداع>` مشتقًّا من المرجعِ البعيد — لا منسوخًا بيدٍ فيتقادم.""" + out = _run(["git", "ls-remote", f"https://github.com/{repo}.git", ref]) + if not out or not out.split(): + return None + return f"{ref}@{out.split()[0][:8]}" + + def write_lock(manifest: dict, ref: str, shas: dict, version: str | None) -> None: + # (AR) الفصلُ يقول إنّ `covers_version` «يسمّي الإيداعَ الذي قِيست عنده + # البصمات». فلا يُنسخ من البيان — يُشتقّ. المكتوبُ بيدٍ صادقٌ اليومَ + # بالمصادفة، ولا شيءَ يمنع كذبَه غدًا (عددٌ منثورٌ نسخةً ثانيةً لواقعة). + auto = None if version is not None else derived_version(manifest["repo"], ref) lock = { "repo": manifest["repo"], "ref": ref, "covers_version": version if version is not None - else manifest.get("covers_version"), + else (auto or manifest.get("covers_version")), "sources": shas, } LOCKFILE.parent.mkdir(parents=True, exist_ok=True) @@ -177,20 +299,54 @@ def write_lock(manifest: dict, ref: str, shas: dict, version: str | None) -> Non # ── (د) حارس البيان دون شبكة ──────────────────────────────────────────────────── +# (AR) الأجزاءُ التي تستوجب تسجيلَ المصادر. كانت أربعةً (`frontend` · `backend` · +# `systems` · `sot`) فبقيت `architecture/` و`getting-started/` و`contributing/` +# **خارجَ المظلّة كلّيًّا** — وهي التي تحمل دعاوى أوامرَ يُشغّلها القارئ +# (`runner.py`، مسارُ البناء، شجرةُ المستودع)، فتعفّنت بلا كاشف. النطاقُ الآن +# كلُّ فصلٍ في قسمٍ من الأقسام السبعة؛ وصفحاتُ الجذر (`introduction` · +# `glossary` · `status` · `SUMMARY`) خارجه عمدًا: دعاواها عن الدليلِ نفسِه +# لا عن اللغة، ومنها ما يُسجَّل طوعًا حين يحمل دعوى مقيسة. +GUARDED_SECTIONS = ("frontend|backend|systems|sot|" + "architecture|getting-started|contributing") + + def technical_chapters_in_summary() -> set[str]: - """فصول SUMMARY التي تحت أقسامٍ تقنيّة تستوجب تسجيل مصادرها.""" + """فصول SUMMARY التي تحت أقسامٍ محروسة تستوجب تسجيل مصادرها.""" import re if not SUMMARY.exists(): return set() text = SUMMARY.read_text(encoding="utf-8") - # نلتقط مسارات الفصول داخل الأجزاء التقنيّة (الأماميّة/الخلفيّة/الأنظمة/مصدر الحقيقة) - techy = re.findall(r"\]\((src/(?:frontend|backend|systems|sot)/[\w\-/]+\.md)\)", + techy = re.findall(r"\]\((src/(?:" + GUARDED_SECTIONS + r")/[\w\-/]+\.md)\)", text) # SUMMARY يستخدم مسارات نسبيّة بلا بادئة src/ — لذا نعيد التقاطها بنمطين - rel = re.findall(r"\]\(((?:frontend|backend|systems|sot)/[\w\-/]+\.md)\)", text) + rel = re.findall(r"\]\(((?:" + GUARDED_SECTIONS + r")/[\w\-/]+\.md)\)", text) return {("src/" + p) for p in rel} | set(techy) +def self_drift_errors(manifest: dict) -> list[str]: + """تعفّنُ مصادرِ `self:` مقيسًا بلا شبكة — بوّابةُ PR لا كاشفٌ أسبوعيّ.""" + if not LOCKFILE.exists(): + # (AR) لا تُرجِع «سليم» لغيابِ ما تقيسُ به. حذفُ ملفٍّ واحدٍ كان يُطفئ + # البوّابةَ صامتةً وخضراء — وهو عينُ «الأخضرُ يعني لم يُقَس». + if any(is_self(s) for s in unique_specs(manifest).values()): + return ["لا يوجد sources.lock.json — فلا يمكن قياسُ تعفّنِ مصادر " + "`self:` المسجَّلة. شغّل `--update` أوّلًا."] + return [] + locked = json.loads(LOCKFILE.read_text(encoding="utf-8")).get("sources", {}) + out = [] + for key, spec in sorted(unique_specs(manifest).items()): + if not is_self(spec) or key not in locked: + continue + cur = fetch_self_sha(spec) + if cur is None or cur == locked[key]: + continue + chapters = ", ".join(chapters_for_key(manifest, key)) or "—" + out.append(f"مصدرٌ داخليّ تغيّر ولم تُثبَّت بصمتُه: {key}\n" + f" راجِع وعدِّل: {chapters}‏ ثمّ شغّل " + f"`python scripts/check_sync.py --update`.") + return out + + def cmd_validate(manifest: dict) -> int: errs, warns = [], [] seen: dict[str, str] = {} @@ -198,6 +354,17 @@ def cmd_validate(manifest: dict) -> int: if spec["key"] in seen and seen[spec["key"]] != ch: pass # نفس المصدر لفصول متعدّدة مسموح seen[spec["key"]] = ch + if spec["names"] and spec["lines"]: + errs.append(f"مصدرٌ يجمع `names` و`lines` معًا (متعارضان): {spec['key']}") + if is_self(spec): + p = self_path(spec) + if spec["names"] and not p.is_dir(): + errs.append(f"مصدرٌ داخليّ `names` ليس مجلّدًا: {spec['key']}") + elif not spec["names"] and not p.is_file(): + errs.append(f"مصدرٌ داخليّ غير موجود: {spec['key']}") + elif not spec["names"] and fetch_self_sha(spec) is None: + errs.append("مصدرٌ داخليّ نطاقُه خارجَ الملفّ (لا شيءَ يُقاس): " + f"{spec['key']}") if spec["lines"]: try: a, b = parse_lines(spec["lines"]) @@ -214,20 +381,35 @@ def cmd_validate(manifest: dict) -> int: if (ROOT / ch).exists() and ch not in chapter_files: warns.append(f"فصلٌ تقنيّ غير مسجَّل في sources.yaml: {ch}") + # (AR) مصادرُ `self:` لا تحتاج شبكة، فيمكن قياسُ تعفّنها **في نفس الـPR** لا أن + # ينتظر الأسبوعيّ. بدون هذا كان النمطُ الجديد كاشفًا أسبوعيًّا لا بوّابةَ PR: + # يُعدَّل `check_sync.py` وحده فيمرّ الـPR أخضرَ والفصلُ الذي يصفه متعفّن. + errs.extend(self_drift_errors(manifest)) + for w in warns: print(f"⚠️ {w}") for e in errs: print(f"❌ {e}") if errs: return 1 - print(f"✅ البيان سليم ({len(chapter_files)} فصلًا مسجَّلًا)" - + (f"، {len(warns)} تحذيرًا" if warns else "") + ".") - return 1 if warns else 0 + # (AR) لا تُنهِ سجلًّا فاشلًا بسطرِ «✅». مَن يمسح سجلَّ CI بعينه يقرأ آخرَ + # سطرٍ حكمًا؛ فكان «✅ البيان سليم … 1 تحذيرًا» يُقرأ نجاحًا ورمزُه 1. + if warns: + print(f"❌ البيانُ ناقص: {len(chapter_files)} فصلًا مسجَّلًا، " + f"و{len(warns)} فصلًا محروسًا بلا مصادر — فشل.") + return 1 + print(f"✅ البيان سليم ({len(chapter_files)} فصلًا مسجَّلًا).") + return 0 # ── (أ) حارس القفل: لا كتم صامت ───────────────────────────────────────────────── -def cmd_guard_lock(manifest: dict, base_path: str, changed_path: str) -> int: - """يرفض تقدّم بصمةٍ في القفل دون تعديل الفصل المرتبط في نفس الـPR.""" +def cmd_guard_lock(manifest: dict, base_path: str, changed_path: str, + base_manifest_path: str | None = None) -> int: + """يرفض تقدّمَ بصمةٍ **أو إسقاطَها** دون تعديل الفصل المرتبط في نفس الـPR.""" + if not LOCKFILE.exists(): + print("❌ لا يوجد sync/sources.lock.json — الحارسُ لم يُقَس. " + "شغّل `--update` أوّلًا.") + return 1 cur = json.loads(LOCKFILE.read_text(encoding="utf-8")).get("sources", {}) base_file = Path(base_path) if not base_file.exists() or not base_file.read_text(encoding="utf-8").strip(): @@ -245,21 +427,43 @@ def cmd_guard_lock(manifest: dict, base_path: str, changed_path: str) -> int: # (AR) القاعدة: **كلّ** فصلٍ يستشهد بالمصدر يجب أن يُعدَّل، لا أيّ فصلٍ منها. # المفتاحُ المشترك (`scripts/codegen` بين codegen.md وphilosophy.md مثلًا) # كان يمرّ بتعديل أحدهما فَيَسِمُ الآخرَ طازجًا دون أن يقرأه أحد — أي أنّ الحارسَ - # نفسَه كان يكتم ما بُني لِيَكشِفَه. (أربعة مفاتيحَ مشتركةٍ اليوم.) + # نفسَه كان يكتم ما بُني لِيَكشِفَه. (عددُ المفاتيح المشتركة يُشتقّ من + # البيان عند التشغيل — لا يُكتب هنا رقمًا يتقادم.) + # (AR) الإسقاطُ كتمٌ صامتٌ أيضًا، من البابِ المقابل: بدل تقديمِ البصمةِ بلا + # مراجعة، إزالتُها بلا مراجعة. إسقاطُ مصدرٍ من البيان ثمّ `--update` + # كان يقتل الكاشفَ لفصولٍ كاملةٍ والبوّابتان خضراوان — قِيس بإسقاط + # `.#names` فمرّ الحارسُ بـ«0 بصمةٍ تقدّمت» وثلاثةُ فصولٍ فقدت دعواها. + dropped = sorted(k for k in base if k not in cur) + base_manifest = None + if base_manifest_path: + raw = Path(base_manifest_path).read_text(encoding="utf-8") + if raw.strip(): + base_manifest = yaml.safe_load(raw) offenders = [] for key in advanced + added: chapters = chapters_for_key(manifest, key) untouched = [c for c in chapters if c not in changed_files] if untouched or not chapters: offenders.append((key, chapters, untouched)) + for key in dropped: + # (AR) الفصولُ تُقرأ من **بيانِ الأساس**: المفتاحُ المُسقَط لا أثرَ له في + # البيان الحاليّ، فقياسُه عليه يعطي قائمةً فارغةً دائمًا. + if base_manifest is None: + offenders.append((f"{key} (أُسقِط)", [], + ["<بيانُ الأساس غير مُمرَّر — تعذّر القياس>"])) + continue + was = chapters_for_key(base_manifest, key) + untouched = [c for c in was if c not in changed_files] + if untouched or not was: + offenders.append((f"{key} (أُسقِط)", was, untouched)) if offenders: - print("❌ كتمٌ صامت مرصود: تقدّمت بصماتٌ في القفل دون تعديل فصولها:\n") + print("❌ كتمٌ صامت مرصود: تغيّرت بصماتٌ في القفل دون تعديل فصولها:\n") for key, chapters, untouched in offenders: if not chapters: - print(f" • المصدر `{key}` تقدّم، ولا فصلَ يستشهد به أصلًا!") + print(f" • المصدر `{key}` تغيّر، ولا فصلَ يستشهد به أصلًا!") continue shared = [c for c in chapters if c not in untouched] - print(f" • المصدر `{key}` تقدّم، ولم يُعدَّل: " + print(f" • المصدر `{key}` تغيّر، ولم يُعدَّل: " f"{', '.join(untouched)}") if shared: print(f" (مشتركٌ مع فصولٍ عُدّلت: " @@ -267,8 +471,8 @@ def cmd_guard_lock(manifest: dict, base_path: str, changed_path: str) -> int: print("\nالقاعدة: لا تثبّت بصمةً جديدة (`--update`) إلّا بعد مراجعة الفصل " "المرتبط وتعديله فعليًّا. راجع الفصل، عدّله، ثم أعِد --update.") return 1 - print(f"✅ الحارس مرّ: كل بصمةٍ تقدّمت ({len(advanced)+len(added)}) رافقها " - "تعديلُ **كلّ** فصلٍ يستشهد بها.") + print(f"✅ الحارس مرّ: كل بصمةٍ تقدّمت ({len(advanced)+len(added)}) " + f"أو أُسقِطت ({len(dropped)}) رافقها تعديلُ **كلّ** فصلٍ يستشهد بها.") return 0 @@ -351,6 +555,8 @@ def main() -> int: help="حارس CI: امنع تقدّم القفل دون تعديل الفصل") ap.add_argument("--base", help="مع --guard-lock: ملف قفل الأساس") ap.add_argument("--changed-files", help="مع --guard-lock: ملفّ بقائمة الملفّات المعدَّلة") + ap.add_argument("--base-manifest", + help="مع --guard-lock: بيانُ الأساس (لقياس المفاتيح المُسقَطة)") args = ap.parse_args() manifest = load_manifest() @@ -361,7 +567,8 @@ def main() -> int: if args.guard_lock: if not (args.base and args.changed_files): sys.exit("‏--guard-lock يستلزم --base و--changed-files") - return cmd_guard_lock(manifest, args.base, args.changed_files) + return cmd_guard_lock(manifest, args.base, args.changed_files, + args.base_manifest) if args.update: shas, missing = collect_shas(manifest, ref) write_lock(manifest, ref, shas, args.set_version) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9b31225..50fd2d1 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) # الجزء الثاني · المعمارية diff --git a/src/architecture/interconnected.md b/src/architecture/interconnected.md index 222039d..81ddeaa 100644 --- a/src/architecture/interconnected.md +++ b/src/architecture/interconnected.md @@ -2,17 +2,20 @@ > **ماذا ستتعلّم:** لماذا لا يوجد «تغيير معزول» في لغة ص، وأيّ ملفّات يَمَسّها كل نوع تغيير. +> 📎 المجلّدات المذكورة أدناه مبصومةٌ **بأسماء مدخلاتها** على `dev` — فظهورُ ملفٍّ +> جديدٍ فيها أو اختفاؤه يُنذر هذا الفصل، ولا يُنذره تعديلُ محتوًى داخلها. + كل ميزة تعبر عدّة أنظمة: مصدر الحقيقة + المُولَّد + المعجمي/النحوي + المفسّر + المترجم + الأخطاء + التوثيق + الاختبارات. تجاهل أحدها يكسر CI أو تجربة المستخدم. ## جدول الأثر (File List) حسب التغيير | التغيير | الملفّات المتأثّرة عادةً | |---------|--------------------------| -| **كلمة مفتاحيّة** | `language-truth/keywords.yaml` → `shared/lexer/generated/keywords_generated.{h,cpp}` (مُولَّد) + `shared/parser/src//` + `shared/ast/include/` + `interpreter/src/visitors/` + `compiler/src/frontend/` (+opcode في `sir_types.h`) + اختبار `.ص` | -| **دالة مضمنة** | `language-truth/builtins/.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//` + `shared/ast/include/` + `interpreter/src/visitors/` (٣٠ ملفَّ `.cpp` في المجلّد — لا كلُّها يمسُّها كلُّ تغيير) + `compiler/src/frontend/` (+opcode في `sir_types.h`) + اختبار `.ص` | +| **دالة مضمنة** | `language-truth/builtins/.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/.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/` + اختبار | ## مخطّط التشابك (مثال: كلمة مفتاحيّة) diff --git a/src/architecture/overview.md b/src/architecture/overview.md index 0d6d26f..842e29a 100644 --- a/src/architecture/overview.md +++ b/src/architecture/overview.md @@ -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) diff --git a/src/contributing/definition-of-done.md b/src/contributing/definition-of-done.md index 154c971..4ff753a 100644 --- a/src/contributing/definition-of-done.md +++ b/src/contributing/definition-of-done.md @@ -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` يعمل بلا تراجع. ## الجودة والتوافق diff --git a/src/contributing/freshness.md b/src/contributing/freshness.md index 9c76746..ed49b94 100644 --- a/src/contributing/freshness.md +++ b/src/contributing/freshness.md @@ -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` @@ -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 اختياريّ). @@ -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) العقد عبر المستودعين العقد يجب أن يصل لمن **يغيّر اللغة فعلًا** (في مستودع اللغة، لا هنا): diff --git a/src/getting-started/first-contribution.md b/src/getting-started/first-contribution.md index 9d2b1aa..c3fc268 100644 --- a/src/getting-started/first-contribution.md +++ b/src/getting-started/first-contribution.md @@ -19,7 +19,7 @@ cd /c/s_lang/temp-brunch/builtin-جذر ## 3) ابدأ من مصدر الحقيقة (لا من الكود المُولَّد) أضف الدالة إلى `language-truth/builtins/.yaml`، ثم أعد التوليد: ```bash -python scripts/codegen/gen_all_builtins_yaml.py # أو المولّد المعنيّ +python x.py gen # أو مولِّدٌ بعينه: scripts/codegen/gen_all_builtins_yaml.py ``` ## 4) نفّذ في الطبقة الصحيحة @@ -27,13 +27,14 @@ python scripts/codegen/gen_all_builtins_yaml.py # أو المولّد المع - المترجم: `compiler/src/backend/llvm/builders/builtins/*.cpp`. ## 5) اكتب اختبار `.ص` (إيجابيّ + سلبيّ) -ملف تحت `tests/` بصيغة `@expected` الصحيحة. +ملف `.ص` تحت **`tests/behavior/`** (لا تحت `tests/` مباشرةً) بصيغة `@expected` +الصحيحة — و`tests_dir: tests/behavior` في `tests/config.yaml` هو ما يجعل الـrunner +يراه. راجع [خريطة المستودع](repo-map.md) للحِزَم الستّ وأيُّها يناسب اختبارَك. ## 6) ابنِ وشغّل (تنفيذ مزدوج) ```bash -cmake --build build --config Debug --target sad-run -cmake --build build --config Release --target sad-build -python runner.py --level P1 # يجب أن يمرّ 100% بلا تراجع +python x.py build # المحرّكان معًا في تهيئة واحدة +python tests/runner.py --level P1 # يجب أن يمرّ 100% بلا تراجع ``` ## 7) أودِع (موقّع GPG) وافتح PR @@ -54,4 +55,4 @@ git branch -D agent/builtin-جذر > ✅ راجع [معيار الإنجاز](../contributing/definition-of-done.md) قبل إعلان الانتهاء. --- -**اقرأ بعده:** [خريطة المستودع](repo-map.md). +**اقرأ بعده:** [نظرة عامّة على الطبقات](../architecture/overview.md). diff --git a/src/getting-started/repo-map.md b/src/getting-started/repo-map.md index 0373ca5..7d4ce95 100644 --- a/src/getting-started/repo-map.md +++ b/src/getting-started/repo-map.md @@ -2,8 +2,12 @@ > **ماذا ستتعلّم:** أين يعيش كل شيء في `sadlang/s-programming-language`. +> 📎 مقيسٌ على `dev` — الأسماء والأعداد أدناه من شجرة المستودع نفسها لا من الذاكرة. + ``` s-programming-language/ +├── x.py ← البوّابة الموحّدة: سبعةُ أوامر (build · test · gen · +│ verify · configure · conformance · clean) ├── shared/ ← النواة المشتركة │ ├── lexer/ ← المحلل المعجمي + Token + Position │ ├── parser/ ← المحلل النحوي (recursive descent) @@ -13,31 +17,55 @@ s-programming-language/ ├── interpreter/ ← المفسّر الشجريّ (InterpreterCore + visitors + builtins) ├── compiler/ ← المترجم: AST → SIR → LLVM IR → تنفيذيّ │ ├── src/frontend/ ← SIRBuilder + sir_types.h (opcodes الملكية) -│ └── src/backend/llvm/ ← LLVMCodeGen + builders -├── stdlib/ ← المكتبة القياسية (core/io/math/string/network/graphics) +│ ├── src/backend/llvm/ ← LLVMCodeGen + builders +│ └── include/backend/native/ +│ ← الخلفيّة الأصليّة (بلا LLVM) +├── stdlib/ ← المكتبة القياسية: وحدات `.ص` في الجذر + مجلّدات دعم C++ +├── features/ ← أنظمةٌ كبرى مستقلّة (منها SadUI للرسومات) ├── runtime/ ← ABI/FFI المستقلّ + الوضع الحرّ (freestanding) -├── tools/ ← analyze · build · check · compiler(واجهة sad-build) · formatter · hub · lsp · pkg · profiler · repl · security-scanner · wasm · apk_builder +├── tools/ ← ١٥ مجلّدَ أدوات (sad-build · sad · lsp · repl …) ├── language-truth/ ← ⭐ مصدر الحقيقة الموحّد (YAML) │ ├── keywords.yaml · operators.yaml · types.yaml · directives.yaml │ ├── builtins/ · errors/ · grammar/ ← قواعد الإنتاج (SoT) │ └── _schemas/ ← مخطّطات JSON للتحقّق ├── scripts/codegen/ ← gen_*.py (تقرأ YAML وتُنتج C++/توثيق) ├── docs/ ← توثيق (incl. parser_rule/_generated المُولَّد) -├── tests/ ← اختبارات .ص + rules_matrix + comprehensive +├── examples/ ← أمثلة `.ص` مرقّمة (`01_مرحبا.ص` … `07_مكوّن_مركّب.ص`) +├── tests/ ← `runner.py` · `config.yaml` · `framework/` · `metrics/` +│ └── behavior/ ← ⟵ هنا تعيش حِزَم `.ص` الستّ (انظر الجدول أدناه) ├── _bmad-output/ ← نظام الحوكمة (سياسات/ستوريات/قرارات) └── .github/skills/ ← مهارات الوكلاء (sad-lang-dev …) ``` + +### ماذا في `stdlib/` و`features/` و`tests/behavior/` +هذه القوائمُ **خارج كتلة الكود عمدًا**: كتلُ الكود تُعرَض LTR (`theme/rtl.css`)، +فينقلبُ فيها ترتيبُ أيّ سلسلةٍ عربيّةٍ أمامَ عين القارئ. + +| المجلّد | ما فيه | +|------|------| +| `stdlib/` — وحدات `.ص` | رياضيات · نصوص · مصفوفات · خرائط · ملفات · شبكات · جيسون · وقت | +| `stdlib/` — مجلّدات دعم C++ (١٧) | `io` · `math` · `string` · `json` · `xml` · `database` · `filesystem` · `image` · `include` · `low_level` · `platform` · `system` · `test` · `freestanding` · `إضافات` · `نص` · `ويب` | +| `features/` | `graphics` (SadUI: التخطيط ومفاتيح الخصائص) · `network` — **الرسومات ليست في `stdlib`** | +| `tools/` (١٥) | `analyze` · `apk_builder` · `build` · `check` · `compiler` (واجهة `sad-build`) · `formatter` · `hub` (موزِّع `sad`) · `installers` · `lsp` · `pkg` · `profiler` · `repl` · `security-scanner` · `shared` · `wasm` | +| `tests/behavior/` (٦ حِزَم) | `P0_smoke` · `sections` · `rules_matrix` · `grammar_gaps` · `null_safety` · `_regression` | + +> ⚠️ `rules_matrix` **تحت `tests/behavior/`** لا تحت `tests/` مباشرةً — ولهذا يكتب CI +> `--dir rules_matrix` بعد أن يقرأ `tests_dir: tests/behavior` من `config.yaml`. +> ولا وجود لمجلّد `tests/comprehensive`. + ## ملفّات تُقرأ أولًا | الملف | لماذا | |------|------| | `shared/lexer/include/token.h` | أنواع الرموز و`Position` | -| `shared/lexer/src/lexer_keywords.cpp` | تسجيل الكلمات المحجوزة (40) | +| `language-truth/keywords.yaml` | معجم اللغة كلُّه: المحجوزُ والعواملُ اللفظيّة والسياقيُّ والأنواعُ المضمنة — ومنه يُولَّد المعجم | +| `shared/lexer/src/lexer_keywords.cpp` | تسجيل الكلمات المحجوزة (المولَّد منها) | | `shared/types/include/value.h` | نوع القيم في وقت التشغيل | | `shared/parser/include/parser_core.h` | واجهة المحلل (كل دوال `parse*`) | | `interpreter/include/core/interpreter_core.h` | نقطة دخول المفسّر | | `compiler/include/frontend/sir_types.h` | تعليمات/أنواع SIR | | `language-truth/README.md` | مصدر الحقيقة | +| `tests/config.yaml` | مسارا الثنائيَّين اللذان يقيس بهما الـrunner | --- -**اقرأ بعده:** [نظرة عامّة على الطبقات](../architecture/overview.md). +**اقرأ بعده:** [أوّل مساهمة](first-contribution.md). diff --git a/src/getting-started/setup.md b/src/getting-started/setup.md index ce6ccf5..8282114 100644 --- a/src/getting-started/setup.md +++ b/src/getting-started/setup.md @@ -2,11 +2,22 @@ > **ماذا ستتعلّم:** كيف تجلب المستودع، تهيّئ الأدوات، وتبني المفسّر والمترجم. +> 📎 المصدر: [`CMakeLists.txt`](https://github.com/sadlang/s-programming-language/blob/dev/CMakeLists.txt) · +> [`tests/config.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/tests/config.yaml) + ## المتطلبات - **C++17** ومُصرِّف حديث (MSVC على Windows، أو Clang/GCC). -- **CMake ≥ 3.20**. -- **LLVM 18** — *اختياريّ*، للمترجم `sad-build` فقط (`ENABLE_LLVM_BACKEND=ON`). -- **Python 3** — لمولّدات الكود (`scripts/codegen/gen_*.py`) و`runner.py`. +- **CMake ≥ 3.15** — هذا ما يشترطه `cmake_minimum_required` فعلًا، لا 3.20. +- **LLVM 18** — للمترجم `sad-build`. الخيار `ENABLE_LLVM_BACKEND` **افتراضيّه + `ON`**، فلا تحتاج تمريره. ودقّق: هدفُ `sad-build` **يُعرَّف داخل** + `if(ENABLE_LLVM_BACKEND AND LLVM_FOUND)` في `apps/CMakeLists.txt` — فبلا LLVM + لا يوجد الهدفُ أصلًا، ويسقط `x.py build` معه، ولا يُنتَج + `build/bin/Debug/sad-build.exe` الذي يشترطه `tests/config.yaml` **أبدًا**. + البديلُ الحقيقيّ هو الهدفُ المستقلّ **`sad-build-native`** (المترجم النحيل، بلا + LLVM، في كلّ التهيئات) — راجع [فصل LLVM](../backend/llvm.md) و[الخلفيّة + الأصليّة](../backend/native.md). فإن لم تنوِ استعمالَ الخلفيّة الأصليّة، + اعتبِر LLVM **شرطًا** لا خيارًا. +- **Python 3** — لمولّدات الكود (`scripts/codegen/gen_*.py`) و`tests/runner.py`. - **Git + GPG** — للمساهمة (الفروع المحميّة تشترط توقيع GPG). ## الجلب @@ -15,44 +26,63 @@ git clone https://github.com/sadlang/s-programming-language.git cd s-programming-language ``` -## البناء (PowerShell على Windows) +## البناء +الطريق المُوصى به هو البوّابة الموحّدة `x.py` — تبني المحرّكين **معًا لكلّ تهيئة**، +فلا تقع في اختلاف التهيئتين الموصوف تحت: +```bash +python x.py build # بناء المحرّكين +python x.py test # بناء + تحقّق + تشغيل الـrunner +python x.py gen # توليد المصدر من language-truth/ +``` +(الأوامر الأخرى: `configure` · `verify` · `conformance` · `clean`.) + +وإن بنيتَ بـCMake مباشرةً (PowerShell على Windows): ```powershell -cmake -S . -B build # تهيئة أولى -cmake --build build --config Debug --target sad-run # المفسّر (أسرع) -cmake --build build --config Release --target sad-build # المترجم (LLVM) -cmake --build build --config Debug # كل شيء +cmake -S . -B build # تهيئة أولى +cmake --build build --config Debug --target sad-run # المفسّر +cmake --build build --config Debug --target sad-build # المترجم +cmake --build build --config Debug # كل شيء ``` -> ⚠️ **ثلاثة فخاخ:** +> ⚠️ **فخاخُ البناء:** +> - **ابنِ الاثنين في تهيئةٍ واحدة، وأعِد بناءهما معًا.** `tests/config.yaml` يقرأ +> المسارَين من **`build/bin/Debug/`** كليهما. فإن بنيتَ `sad-build` في `Release` +> لم يجده الـrunner، وإن أعدتَ بناءَ أحدهما وحدَه قِستَ ثنائيًّا بائتًا. وهذه +> بعينها عثرةُ «مفسّر Debug + مترجم Release» التي **يقول تعليقُ +> `tests/config.yaml` نفسِه** إنّها أُزيلت جذريًّا في `cmake/llvm.cmake`؛ +> لا تُعِدها بيدك. > - **`sadc.exe` اسمٌ متقاعد — لا يُنتجه أيّ هدف.** اسمُ الهدف مُوحَّدٌ مع اسم -> المُخرَج: `sad-run` ⇒ `sad-run.exe`، و`sad-build` ⇒ `sad-build.exe` -> ([`tests/config.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/tests/config.yaml)). -> فلا تنسخ ثنائيًّا باسمٍ آخر لأجل `runner.py` — فهو يقرأ المسارَين من ذلك الملفّ. +> المُخرَج: `sad-run` ⇒ `sad-run.exe`، و`sad-build` ⇒ `sad-build.exe`. +> فلا تنسخ ثنائيًّا باسمٍ آخر لأجل الـrunner — فهو يقرأ المسارَين من ذلك الملفّ. > - `sad.exe` ليس المفسّر بل **موزِّع أوامرٍ (hub)** يُشغّل الأدواتِ عمليّاتٍ فرعيّة؛ > المفسّرُ الفعليّ `sad-run.exe`، فاستدعِه مباشرةً. -> - الـrunner يقرأ تهيئتَي الثنائيَّين من `tests/config.yaml` — أعد بناءهما معًا -> حتّى لا تقيس ثنائيًّا بائتًا. ## التشغيل +الأمثلة الفعليّة تحت `examples/` بأسماء عربيّة مرقّمة (`01_مرحبا.ص` … `07_مكوّن_مركّب.ص`): ```powershell -.\build\bin\Debug\sad-run.exe examples\test_simple.ص # تفسير -.\build\bin\Release\sad-build.exe examples\test_simple.ص # ترجمة لملف تنفيذيّ +.\build\bin\Debug\sad-run.exe examples\01_مرحبا.ص # تفسير +.\build\bin\Debug\sad-build.exe examples\01_مرحبا.ص # ترجمة لملف تنفيذيّ ``` ## الاختبارات -الاختبارات **معطّلة افتراضيًّا**؛ فعّلها بـ`-DBUILD_TESTS=ON`. للتنفيذ المزدوج (مفسّر + مترجم) استخدم `runner.py`: +الاختبارات **معطّلة افتراضيًّا** (`option(BUILD_TESTS ... OFF)`)؛ فعّلها بـ`-DBUILD_TESTS=ON`. +للتنفيذ المزدوج (مفسّر + مترجم) استخدم **`tests/runner.py`** — لا يوجد `runner.py` +في جذر المستودع، وهذا هو الاستدعاء الذي يستعمله CI نفسه: ```bash -python runner.py --level P0 # الحزمة الأساسيّة -python runner.py --level P1 # المطلوبة قبل أي PR (لا تراجع) +python tests/runner.py --level P0 # الحزمة الأساسيّة +python tests/runner.py --level P1 # المطلوبة قبل أي PR (لا تراجع) ``` +(و`python x.py test` يبني المحرّكين ثمّ يستدعي هذا الـrunner نفسَه — استعمِله حين +تريد البناءَ والقياسَ في خطوة، والاستدعاءَ المباشرَ حين يكون البناءُ حاضرًا.) ## توليد الكود من مصدر الحقيقة بعد تعديل أي YAML في `language-truth/`: ```bash -python scripts/codegen/gen_keywords.py # مثال: الكلمات المفتاحيّة -python scripts/codegen/gen_parser_grammar_docs.py # توثيق القواعد +python x.py gen # كلُّ المولّدات دفعةً واحدة +python scripts/codegen/gen_keywords.py # أو مولِّدٌ بعينه +python scripts/codegen/gen_parser_grammar_docs.py # توثيق القواعد ``` > راجع [توليد الكود](../sot/codegen.md). --- -**اقرأ بعده:** [أوّل مساهمة](first-contribution.md). +**اقرأ بعده:** [خريطة المستودع](repo-map.md). diff --git a/src/glossary.md b/src/glossary.md index af6a842..7057617 100644 --- a/src/glossary.md +++ b/src/glossary.md @@ -18,10 +18,11 @@ | **DoD** | Definition of Done — معيار اعتبار التغيير منجَزًا. | | **worktree** | فرع git في مجلد منفصل يشارك نفس المستودع (`C:/s_lang/temp-brunch/`). | | **CW-NN / BF-NN / GR-NN** | قواعد كتابة الكود / إصلاح الأخطاء / الحوكمة (مراجع معياريّة). | -| **sad-build** | مُترجِم لغة ص (AST → SIR → LLVM → تنفيذيّ). هدفُ CMake ومُخرَجُه `sad-build.exe`. | -| **sad-run** | المفسّر الشجريّ (`sad-run.exe`). | +| **sad-build** | مُترجِم لغة ص (AST → SIR → LLVM → تنفيذيّ). هدفُ CMake ومُخرَجُه `sad-build.exe`، ويقرأه الـrunner من `build/bin/Debug/`. | +| **sad-run** | المفسّر الشجريّ (`sad-run.exe`) — من `build/bin/Debug/` كذلك: الثنائيّان في **تهيئةٍ واحدة**. | | **sad** | موزِّع الأوامر (hub) — يُشغّل الأدوات عمليّاتٍ فرعيّة، وليس المفسّر. | | **~~sadc~~** | اسمٌ **متقاعد**: لا هدفَ يُنتجه اليوم. المترجم هو `sad-build`. | +| **`tests/config.yaml`** | مصدرُ مسارَي الثنائيَّين و`tests_dir` — لا تنسخ ثنائيًّا باسمٍ آخر لأجل الـrunner. | --- [العودة للمقدّمة](introduction.md) diff --git a/sync/sources.lock.json b/sync/sources.lock.json index f51c16a..2fc1989 100644 --- a/sync/sources.lock.json +++ b/sync/sources.lock.json @@ -3,8 +3,11 @@ "ref": "dev", "repo": "sadlang/s-programming-language", "sources": { + ".#names": "names:1ccbd21d37ca347e", ".github/BRANCH_PROTECTION_POLICY.md": "2cf24d5c67e9c2c9932ce7e9dae784d85216c4d2", ".github/skills/sad-lang-dev/references/governance.md": "9d6d0e8524cf7c27820fc0f6f25167ab2d702ed3", + "CMakeLists.txt#L29-70": "sha256:78a24c2c7cf95d39", + "apps/CMakeLists.txt#L292-310": "sha256:d208b667973d02f8", "compiler/include/backend/native/elf64_writer.h": "8ebd8626f881e72be4d7b91b32598255a4fb9e0b", "compiler/include/backend/native/sir_lowering_common.h": "259cceb18f6b2fc4ef0e978ee6e9c32ed406903e", "compiler/include/backend/native/sir_native_lowering.h#L1-30": "sha256:9a217df04a7742ee", @@ -13,6 +16,7 @@ "compiler/include/frontend/sir_constants.h": "a194d187bc7c4298d07a1340adfc68ce9877b297", "compiler/include/frontend/sir_types.h": "d7adf45f13c2aa9071f7a8bd36550abeeb5f0fcb", "compiler/src/backend/llvm": "525f452df4773db03c579b2b2f84454361920cbf", + "compiler/src/backend/llvm/builders/builtins#names": "names:c8b7f2f962cb6152", "compiler/src/backend/llvm/builders/builtins/io_builtins_ops.cpp": "169fdb572efd32d02919dc09c5e94b4a621d3468", "compiler/src/backend/llvm/builders/builtins/security_builtins_ops.cpp": "96616d075384d3b2a1cff7773ba63ed721c5b1cb", "compiler/src/backend/llvm/builders/collections/map_ops.cpp": "40b02975f418d9db94ba3314f06a07d539c46472", @@ -21,6 +25,9 @@ "compiler/src/frontend/builders/expression_comp2.cpp": "a857b9752d53eaa9a36c8a9f4ef4d4e03f5c260d", "compiler/src/frontend/builders/expression_comprehensions.cpp": "48284a8c8ceea0663dbd250bbb9b0efa8ac787d8", "compiler/src/frontend/builders/statement_for_range.cpp": "6345f89e25b5f426a8ea4f6a4ca836a50f1b28e4", + "docs/parser_rule/_generated#names": "names:8e43d6199dcb4ce5", + "examples#names": "names:4e8e58a6b7295198", + "features#names": "names:4b0db2883814c7ca", "features/graphics/core/include/sad_ui/prop_keys.h": "149ca3c9abcd815c662f446fff0c9c0d64d6e85a", "features/graphics/core/src/layout.cpp": "b02fcd06d7a1ebfd98904c411cc633043714a45d", "interpreter/include/channel.h": "119dc0e9cfea1b7e1b514a7d2b49614c1e499860", @@ -30,6 +37,7 @@ "interpreter/src/builtins/builtin_module_assertions.cpp": "1cf037f1f5ffb75e9c13aa3709b23e20967f50ef", "interpreter/src/builtins/builtin_module_crypto.cpp": "7bb771626147ce04f7e43a715beec99bafeefc44", "interpreter/src/core/interpreter_core.cpp": "a6d3445646d9843e4b26bba1ef5e95f72600ec7f", + "interpreter/src/visitors#names": "names:da81f693ba6445f2", "interpreter/src/visitors/expression_evaluator_members_advanced.cpp": "bd2003b66b8be9c340e4650b2b851c9348765191", "language-truth": "c96c2155d17c2cae855b72b5ad371b583ea80ecc", "language-truth/_schemas": "113cd3948583dfb130fe4169f18e4ff6c7e16cd7", @@ -45,14 +53,19 @@ "language-truth/types.yaml": "73b5c1d165ec83f73b819c4d6af52333e2c217e4", "language-truth/ui_props.yaml": "89cbf23a799f309698e97b29ecc595c293798963", "scripts/codegen": "54744b199f588b1a93b08ad677a069b263c21233", + "scripts/codegen#names": "names:4135e860113590d5", "scripts/codegen/gen_error_messages.py": "3a70f260e525f133b9a93f9919b3622e9056e871", "scripts/codegen/gen_grammar_lowers_to.py": "3c7466870b2218d12089eea65d2b3361123b11b1", "scripts/codegen/gen_native_diagnostics.py": "f0accb963fc8f296e38c1f1d4226c0c5a5edee29", "scripts/codegen/gen_parser_grammar_docs.py": "7b9f7a8a87252ee4599b4fa6da79cb18bbb05120", "scripts/codegen/gen_ui_props.py": "5f750bc52cbe4e85bb67f087a94d67a840fcc6fe", "scripts/native_backend/run_native_proofs.sh": "84069b1cd325573c7336b131d37997788a4d1885", + "self:.github/workflows/ci.yml": "sha256:3d9b1b76ba0585b0", + "self:.github/workflows/sync-check.yml": "sha256:a22d7b9c3e9ae66d", + "self:scripts/check_sync.py": "sha256:d1ad2b646e901155", "shared/ast/include/ast_visitor.h": "6a19219711e62321697a041044962642888091f5", "shared/ast/include/expressions.h": "8c8ffe93587aa833c10890b9c4e12c2e9855825d", + "shared/builtins/generated#names": "names:0087d3f78e675aaa", "shared/errors/include/error_codes.h": "893d39e3bf05aaeb798d9ff46f64e347fb46dff1", "shared/errors/include/error_manager.h": "63c70109de759eaedf809642d11681a1453db5d3", "shared/lexer/include/token.h": "b899691a3f715415d2cd1780ed0601b0d1ade52e", @@ -70,9 +83,17 @@ "shared/types/include/sad_type_system.h": "abc0ace4395e26799a23ff5346efbe0714515d4b", "shared/types/include/value.h": "4d16c5e5f6c17fd4393f1d1da32716582d15750d", "shared/types/src/class_manager.cpp": "3afe4d8387045573f7ae9d1c19b54eee7bb1226f", + "stdlib#names": "names:df15efe2b81d162d", + "tests#names": "names:c1b294150aba797a", + "tests/behavior#names": "names:e842e431bdc20e99", + "tests/config.yaml#L15-23": "sha256:6756185d98a72012", + "tests/config.yaml#L25-26": "sha256:22a5e208ad3f2032", + "tests/config.yaml#L27": "sha256:9b597def0e032eda", + "tools#names": "names:5839c50dd6aeb34b", "tools/compiler/compiler_driver_android_linker.cpp": "86c4974e713923f47c0f16077b4db8ede94eef28", "tools/compiler/compiler_driver_backend_llvm_absent.cpp": "7ea38eea2362fccc22e6d5d6666a9b4d55e09ac2", "tools/compiler/compiler_driver_native.cpp": "74676054c3660ec7ac0df65a1da9aa8d3baab0dd", - "tools/compiler/runtime/sad_embedded_runtime.c": "c4465bac5594536e01b41f099a6dddf9fc80999d" + "tools/compiler/runtime/sad_embedded_runtime.c": "c4465bac5594536e01b41f099a6dddf9fc80999d", + "x.py#L1310-1350": "sha256:f6301ea3d5272221" } } diff --git a/sync/sources.yaml b/sync/sources.yaml index ab8b444..a207cd9 100644 --- a/sync/sources.yaml +++ b/sync/sources.yaml @@ -157,3 +157,69 @@ chapters: - file: src/contributing/governance.md sources: - .github/skills/sad-lang-dev/references/governance.md + + # الموجة الثالثة: فصولٌ كانت خارج الحارس، ودعاواها أوامرُ يُشغّلها القارئ. + # (AR) نطاقُ `tests/config.yaml` مشطورٌ بحسب الدعوى: 25-26 مسارا الثنائيَّين، + # و27 `tests_dir` وحده. الجمعُ في نطاقٍ واحد (24-27) كان يقرن **خمسةَ** فصول + # بمفتاحٍ واحد، فيلزم حارسُ القفل تعديلَ فصلٍ لا علاقةَ لدعواه بما تغيّر. + - file: src/getting-started/setup.md + sources: + - { path: CMakeLists.txt, lines: "29-70" } # cmake_minimum_required(3.15) + BUILD_TESTS OFF + ENABLE_LLVM_BACKEND ON + - { path: tests/config.yaml, lines: "25-26" } # مسارا الثنائيَّين من build/bin/Debug + - { path: x.py, lines: "1310-1350" } # الأوامر الفرعيّة للبوّابة الموحّدة + - { path: ".", names: true } # دعوى «لا runner.py في الجذر» + - { path: tests, names: true } # دعوى «tests/runner.py هو المستدعى» + - { path: examples, names: true } # دعوى «examples\01_مرحبا.ص» + - { path: apps/CMakeLists.txt, lines: "292-310" } # sad-build داخل شرط LLVM + البديل sad-build-native + - { path: tests/config.yaml, lines: "15-23" } # تعليقُ «Debug+Release» وعلاجُه في cmake/llvm.cmake + + - file: src/getting-started/first-contribution.md + sources: + - { path: tests, names: true } # `python tests/runner.py` — لا runner.py في الجذر + - { path: tests/config.yaml, lines: "27" } # دعوى «الاختبار تحت tests/behavior» + - { path: x.py, lines: "1310-1350" } # `python x.py build` + + - file: src/getting-started/repo-map.md + sources: + - language-truth/keywords.yaml # ٤٠ محجوزة · ٣ · ٤٤ · ١٧ + - { path: tests/config.yaml, lines: "27" } # tests_dir: tests/behavior + - { path: ".", names: true } # شجرة الجذر كما تُرسم في الفصل + - { path: tools, names: true } + - { path: stdlib, names: true } + - { path: tests, names: true } # لا tests/comprehensive + - { path: tests/behavior, names: true } # حِزَم `.ص` الستّ + - { path: examples, names: true } # 01_مرحبا … 07_مكوّن_مركّب + - { path: x.py, lines: "1310-1350" } # الفصل يسمّي الأوامر الفرعيّة في شجرته + + - file: src/contributing/definition-of-done.md + sources: + - { path: tests/config.yaml, lines: "25-26" } # الثنائيّان من تهيئةٍ واحدة + - { path: tests, names: true } # `python tests/runner.py` + + # ── الحلّ الجذريّ: دعاوى «شكل الشجرة» تُبصَم بأسماء المدخلات (names) لا بمحتواها، + # وفصولُ الدليل التي توثّق أدواتِه تُبصَم من هذا المستودع نفسِه (self:). + - file: src/architecture/overview.md + sources: + - { path: ".", names: true } # جذر الشجرة — يرصد عودة vm/ أو ظهور جزءٍ جديد + - { path: tools, names: true } # «١٥ مجلّدًا» بأسمائها + - { path: stdlib, names: true } # وحدات `.ص` + مجلّدات الدعم + - { path: features, names: true } # features/graphics — الرسومات ليست في stdlib + + - file: src/architecture/interconnected.md + sources: + - { path: scripts/codegen, names: true } + - { path: interpreter/src/visitors, names: true } + - { path: compiler/src/backend/llvm/builders/builtins, names: true } + - { path: docs/parser_rule/_generated, names: true } + - { path: shared/builtins/generated, names: true } # دعوى مسار builtin_registry_generated.h + + - file: src/contributing/freshness.md + sources: + - self:scripts/check_sync.py # الفصل يصف هذه الأداة بعينها + - self:.github/workflows/sync-check.yml # ومسارَ الفحص الأسبوعيّ + - self:.github/workflows/ci.yml # والفصلُ يصف حارسَي ci.yml بعينهما + + - file: src/glossary.md + sources: + - { path: tests/config.yaml, lines: "25-26" } # sad-run · sad-build · sadc المتقاعد + - { path: tests/config.yaml, lines: "27" } # دعوى `tests_dir`