Skip to content

Claude/mcp codekeeper webapp ldnzsg - #3202

Merged
amirbiron merged 34 commits into
mainfrom
claude/mcp-codekeeper-webapp-ldnzsg
Aug 1, 2026
Merged

Claude/mcp codekeeper webapp ldnzsg#3202
amirbiron merged 34 commits into
mainfrom
claude/mcp-codekeeper-webapp-ldnzsg

Conversation

@amirbiron

@amirbiron amirbiron commented Aug 1, 2026

Copy link
Copy Markdown
Owner

נוסף למדריך ונדחף (PR #3201).

מה עשיתי

המסמך היה תכנון עם סימוני "✅ מומש" פזורים — שזה שימושי למי שרוצה לדעת מה קיים, אבל חסר בדיוק את מה שאתה צריך: המלכודות שהתגלו תוך כדי. הוספתי סעיף 14 עם אחד־עשר תתי־סעיפים, שכל אחד מהם מבוסס על סבב תיקון אמיתי שעברנו — לא על תיאוריה.

הדברים שלדעתי יחסכו לך הכי הרבה זמן בפרויקט השני:

ל‑SDK אין scopes פר־כלי. זו ההפתעה שמשנה איך בונים את השרת: הרישום של כלי הוא דקורטור, לא הצהרת הרשאה. כל כלי כותב חייב לקרוא require_write(ctx) בעצמו בשורה הראשונה. אצלנו זה עשר נקודות אכיפה נפרדות, כל אחת צריכה לזכור לבד.

הסתרת כלי מ‑tools/list היא UX, לא אבטחה. ה‑dispatcher מריץ גם כלי שאינו ברשימה — מי שיודע את השם קורא לו ישירות.

כלי שמקבל מזהה משאב הוא IDOR עד שהוכח אחרת. זה הלקח החד ביותר: הוספנו כלי ציבורי עם פרמטר repo אופציונלי, וזה עקף את כל שער האדמין שבנינו בפאזה ד'. הכלל להכללה: כל repo/tenant/project בכלי לא מוגן צריך allowlist.

שינוי scopes דורש רישום DCR מחדש — תפס אותנו בפרודקשן. תכנן את ה‑scopes מראש גם אם לא תממש את כולם ביום הראשון.

שירות שקורא ממצב על דיסק חייב לסנכרן את הדיסק של עצמו — ה‑MCP הגיש גרסאות ישנות של קבצים בלי שום שגיאה, כי ה‑sync רץ בשירות אחר עם דיסק אחר.

בנוסף: גשר הזהות החתום בין הוובאפ ל‑MCP, תקציב טוקנים כדרישה תפקודית (ולמה לעולם לא מחזירים למודל רק "לא נמצא"), תיאור הכלי כממשק שקובע מה המודל יבחר, ומקרי הקצה של פענוח RST.

עדכנתי גם את הכותרת ל"תכנון ולקחי מימוש" עם הנחיית קריאה בראש — כדי שמי שפותח את המסמך למטרת מימוש ידע להתחיל מסעיף 14 ולא לקרוא 380 שורות תכנון קודם.

כל המספרים אומתו מול הקוד ולא מהזיכרון — 18 כלים, 17 מודולים, 5 require_write ו‑5 require_admin. תיקנתי תוך כדי שתי טענות שכתבתי בהתחלה לא מדויק (מספר נקודות האכיפה וגודל קובץ ה‑RST).

הערה קטנה: הקובץ יושב ב‑FEATURE_SUGGESTIONS/ ולא ב‑docs/, אז הוא לא נכנס לבניית RTD ואין סיכון לשבור את התיעוד.

Summary by Sourcery

Document MCP integration lessons and add Markdown explicit anchor support across web preview and repo browser.

Enhancements:

  • Introduce md-anchors utility and wire it into Markdown renderers and previews to preserve explicit HTML anchors while keeping html rendering disabled.
  • Improve hash-based scrolling in Markdown preview pages to support non-CSS-valid IDs and percent-encoded anchors.

Documentation:

  • Expand MCP Claude integration design document with post-implementation lessons, updated status, and new RST docs tool.
  • Add guidance on RTD toctree and exclude_patterns usage for new documentation pages, and introduce config-inspector docs stub.

Tests:

  • Add standalone Node-based tests for md-anchors behavior and DOM anchor application.

claude and others added 30 commits July 20, 2026 09:18
שלושה כלים חדשים מעל קולקציית sticky_notes הקיימת של הוובאפ:
- codekeeper_list_notes — פתקי הקובץ (קריאה טהורה, בלי ה-backfill של ה-GET בוובאפ)
- codekeeper_create_note — פתק חדש על קובץ קיים; line מעגן לשורת מקור,
  בלעדיו הפתק נוצר צף עם sentinel __floating__ מפורש (אחרת ה-JS מעגן
  אוטומטית לשורה הקרובה ודורס את הכוונה)
- codekeeper_update_note — עדכון חלקי לפי note_id, עם annotation נפרד
  (destructive+idempotent) כי פתק נדרס במקום ואין לו היסטוריית גרסאות

עקרונות:
- כותבים בדיוק את סכמת הוובאפ (scope_id מ-sticky_notes_scope.make_scope_id
  הקנוני, ברירות מחדל בפריטת הקליינט) — פתק מה-MCP מופיע מיד ב-UI
- זהות תמיד מהטוקן; יצירה/עדכון מאחורי require_write
- תוכן >5000 תווים נדחה בשגיאה (לא קיטום שקט — סוכן לא ישים לב לאובדן)
- מגן אנטי-לולאה: עד 200 פתקים לקובץ ביצירה
- אינדקס (user_id, scope_id) שחסר היום נוצר lazy/best-effort — משרת גם את
  שאילתת ה-scope הזהה של הוובאפ
- בלי מחיקה ובלי תזכורות (non-goal מתועד); אפס שינויי קוד בוובאפ —
  דיפלוי לשירות ה-MCP בלבד

בדיקות: 19 טסטים הרמטיים חדשים (סניטציה, ולידציות, sentinel, צבעים,
scope filter מול make_scope_id האמיתי, סריאליזציה) + עדכון טסט הרישום.
188 טסטי MCP עוברים; black/flake8/doc8 נקיים; Sphinx נבנה עם 0 אזהרות.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
הוספת פריט "תיאור" בתפריט 3-הנקודות בעמוד הצפייה בקובץ (view_file),
ראשון ברשימה, שפותח מודאל קטן לעריכת שדה התיאור — חוסך את הכניסה
לעריכת קובץ מלאה רק כדי לשנות תיאור. "נעץ לדשבורד" עלה לשני, "שתף"
לשלישי.

- משתמש ב-endpoint הקיים POST /api/file/<id>/quick-update שמעדכן את
  התיאור in-place (מטא-דאטה, בלי גרסה חדשה) — אין קוד שרת חדש
- מודאל בדפוס מודאל השיתוף הקיים (Escape, לחיצה על הרקע, טוסט הצלחה),
  עם textarea ומונה תווים (עד 500, תואם למגבלת ה-endpoint)
- עדכון חי של התצוגה מתחת לשם ושל תווית התפריט בלי רענון עמוד
- זמין לכל הקבצים (התיאור הוא מטא-דאטה אוניברסלי)
- אין בעיית מודאל-בתוך-מודאל: התפריט (dropdown) נסגר לפני שהמודאל
  נפתח, בדיוק כמו "שתף קובץ" הקיים
- שינוי template בלבד; דורש דיפלוי לוובאפ

בדיקות: Jinja parse תקין, תחביר JS תקין; אין קוד Python שהשתנה.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
באג: אחרי deploy של תבנית, משתמשים שצפו בקובץ לאחרונה המשיכו לראות את
הגרסה הישנה (בלי אלמנטים חדשים) הרבה זמן; אחרים כן ראו חדש. השורש:
ה-ETag של /file/<id> ו-/md/<id> חושב מנתוני הקובץ בלבד (updated_at/תוכן/
version/theme) בלי גרסת ה-deploy — אז קובץ שלא נערך החזיר ETag זהה בין
deploys → הדפדפן קיבל 304 והציג HTML ישן מה-cache. תורם שני: מסלול
If-Modified-Since מבוסס updated_at החזיר 304 בנפרד.

תיקון שורשי (webapp/app.py + cache_manager.py):
- _compute_file_etag כולל עכשיו את _STATIC_VERSION (גרסת deploy) — כל
  deploy מבטל ETags ישנים. קורא מרכזי אחד ⇒ מכסה view_file וגם md_preview.
- שלושת מסלולי If-Modified-Since מכבדים RFC 7232 §3.3: מדלגים כשקיים
  If-None-Match (אחרת 304 מיושן גם אחרי שה-ETag השתנה).
- מפתח ה-cache צד-שרת של md_preview כולל את גרסת ה-deploy (אחרת HTML
  מרונדר ישן מוגש עד 30 דק').
- נלווה: invalidate_file_related מבטל עכשיו גם את המפתח האמיתי
  web:md_preview:user:*:{file_id}:* (היה prefix שגוי — עריכת קובץ לא
  ביטלה את cache ה-md שלו).

ב-Render גרסת ה-deploy מגיעה מ-RENDER_GIT_COMMIT (משתנה לכל commit).

אימות: py_compile + flake8 + 9 טסטי cache/invalidation עוברים. את לוגיקת
ה-ETag אימתתי בבידוד (הרצת הפונקציה האמיתית: גרסת deploy משנה את ה-ETag,
יציב לאותו קלט, ותוכן עדיין משנה). טסט יחידה שמייבא webapp.app לא ישים —
טסטי ה-webapp מדולגים ב-CI ("צינור הבוט") ו-flask לא זמין בסביבה.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
מתחת לכל קובץ באוסף שיש לו תיאור מופיע אייקון ℹ️; לחיצה פותחת מודאל
קטן (קריאה בלבד) עם התיאור — בלי להיכנס לקובץ.

- Backend: get_collection_items מצרף עכשיו את ה-description של הקובץ
  לכל פריט, דרך אותו batch שכבר מחשב is_file_active (הרחבת ה-projection
  ל-file_name+description) — בלי N+1 ובלי שדות כבדים (Smart Projection
  נשמר; description ≤500 תווים). קובץ בלי תיאור/לא-פעיל ⇒ "".
- Frontend: אייקון ℹ️ ב-.collection-card__meta רק אם יש תיאור;
  openDescriptionModal בדפוס .collection-modal הקיים (Escape/רקע סוגרים,
  textContent — בטוח מ-XSS).
- מצב workspace לא נכלל בשלב זה.

בדיקות: 3 טסטי enrichment חדשים (fakes שתומכים ב-projection) + 49 טסטי
collections_manager עוברים; node --check ל-JS; py_compile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
אייקון התיאור בכרטיס קובץ עבר משורת ה-meta אל צד שם הקובץ (משמאל, RTL) באותה
שורה, עם רווח. כשהשם ארוך ונשבר לשתי שורות — האייקון "קופץ" לשורת 4 הכפתורים,
משמאל להם (פונקציה layoutDescIcons שמנצלת את זיהוי is-wrapped של autoFitText,
בסדר reset→autoFit→move כדי למנוע oscillation).

ארכיון לאוספים: שדה is_archived חדש (נפרד מ-is_active), toggle 🗄️ "הצג ארכיון"
בסיידבר, וכפתור ארכב/שחזר בכותרת האוסף (מגודר ל-non-workspace). list_collections
קיבל archived_only ו-include_archived; ברירת המחדל מחריגה מאורכבים (ne:True מכסה
גם אוספים ישנים ללא השדה). הקאש כבר מבחין לפי querystring, וה-PUT מנקה את שתי
התצוגות. הגיבוי האישי משתמש ב-include_archived=True כדי לא לפספס אוספים בארכיון.

- database/collections_manager.py: doc-build, allow-list, list filter, serializer, index+backfill
- webapp/collections_api.py: פרמטר archived ב-GET, דילוג על "שולחן עבודה" בתצוגת ארכיון
- webapp/static/js/collections.js + collections.css: אייקון, toggle, כפתורי ארכב/שחזר
- services/personal_backup_service.py: include_archived=True בגיבוי ובבדיקת כפילות
- tests/test_collections_archive.py: ארכוב/שחזור/include/legacy

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
main קלט את אייקון-התיאור הבסיסי דרך squash (#3191), ולכן נוצר קונפליקט מול
העבודה החדשה בענף (הזזת האייקון + ארכיון). מיזגתי את main לענף ופתרתי:
- collections.css: נשמרה הגרסה שלי (superset — בסיס .desc-info + flex + Slot 2 + toggle ארכיון).
- collections.js: הוחזר לגרסה שלי כדי למנוע שכפול של כפתור התיאור שהמיזוג האוטומטי יצר.
תוצאת המיזוג זהה בדיוק לעבודה שכבר נבדקה (36 טסטים ירוקים).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
לפי theming_and_css.rst אסורים צבעים קשיחים בקבצי רכיבים — רק var(--token).
שתי חריגות הומרו:
- מודאל "ערוך תיאור" (view_file.html): הרקע הכהה הקבוע (#1f2a44, #fff, rgba לבנים,
  focus בצבע primary קשיח) הוחלף בטוקנים סמנטיים — bg-secondary/tertiary,
  text-primary/secondary/muted, glass-border, primary; ה-scrim וה-shadow קיבלו
  טוקן-רכיב עם fallback (var(--modal-backdrop, ...), var(--solid-surface-shadow, ...)).
  כך המודאל מקבל את צבעי הערכה גם בערכות בהירות (rose-pine-dawn, classic).
- כפתור "הצג ארכיון" במצב לחוץ (collections.css): rgba לבנים קשיחים הוחלפו
  בטוקני glass קיימים (glass-hover/glass-border/glass).

מודאל ה-ℹ️ באוספים נבדק ונמצא תקין (יורש var(--collections-modal-*)) — ללא שינוי.
מודאל השיתוף הסמוך הוא legacy קיים ולא נכלל (חוב נפרד).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…קט collections.css

main קלט את הארכיון והזזת האייקון דרך squash (#3192), והענף ממשיך עם תיקון
הטוקנים (991b1ad) שנגע באותה שורה. נשמרה גרסת הטוקנים של #toggleArchivedBtn
(var(--glass-*)) — ההבדל היחיד מול main, שהוחלף בכוונה. תוצאת המיזוג זהה
לחלוטין לעץ שכבר נבדק.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
באג שורש: המחרוזת `סה""כ` פיצלה f-strings לשתי מחרוזות סמוכות — השנייה (בלי
קידומת f) הציגה placeholders כטקסט מילולי (למשל "{len(items)}"). תוקן ב-3 מקומות
(handlers/documents.py, conversation_handlers.py, handlers/save_flow.py) ע"י
מעבר ל-f-string רציף אחד עם מרכאות בודדות. כך "✅ נוסף: X (סה"כ N קבצים)" מציג
את המספר בפועל, וכן כותרת רשימת ה-ZIP השמורים ומסך איסוף הקוד הארוך.

פיצ'ר: שלב בחירת שם ל-ZIP. אחרי "✅ סיום" הבוט מבקש שם (או "⏭️ דלג" לשם אוטומטי):
- conversation_handlers.py: helper משותף finalize_zip_create + _cleanup_zip_state;
  zip_create_finish מציב awaiting_zip_name ומבקש שם; callback חדש zip_create_skip_name.
- main.py: hook בראש handle_text_message שתופס את השם (נבדק ראשון כדי שלא ייבלע/ייחשב קוד).
- שם מנוקה דרך TextUtils.clean_filename + סיומת .zip; fallback ל-my-files-<timestamp>.zip.

טסט: חיזוק test_handle_document_collects_zip_items לאימות שהמספר מוצג בפועל (הגנת רגרסיה).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…hread, מסלול ביטול

מענה ל-review findings ב-PR #3194:
- ניקוי שם רשומת ZIP (utils.safe_zip_entry_name): basename בלבד, דחיית נתיב מוחלט/
  מקונן ו-"."/".." — הגנת Zip-Slip. משמש בבניית הארכיון במקום השם הגולמי.
- בניית ה-ZIP חולצה ל-utils.build_zip_bytes (טהור) ורצה תחת asyncio.to_thread כדי
  לא לחסום את לולאת האירועים (בהתאם לכלל ה-Performance ב-CLAUDE.md).
- אכיפת מגבלות איסוף (ZIP_CREATE_MAX_FILES=50, ZIP_CREATE_MAX_TOTAL_BYTES=45MB)
  בזמן צבירת הקבצים ב-handlers/documents.py, עם הגנה כפולה גם בשלב הבנייה.
- מסלול ביטול במצב "המתנה לשם": zip_create_cancel עובר דרך _cleanup_zip_state
  (מנקה גם awaiting_zip_name), ונוסף כפתור "❌ ביטול" למקלדת בקשת השם — כך שמשתמש
  שמתחרט לא ישלח ארכיון בטעות בהודעת טקסט כלשהי.
- טסטים: tests/test_zip_bundle_utils.py (ניקוי שמות + מגבלות, נבדק ע"י פענוח ה-ZIP).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…ון docstring

מענה ל-review (סבב 2) ב-PR #3194:
- handlers/documents.py: בדיקות מגבלת ה-ZIP (מספר קבצים + גודל לפי document.file_size)
  הוזזו לפני get_file()/download_to_memory() — לא מורידים לזיכרון קובץ שנדחה מראש.
  בדיקת len(raw) נשמרה כאימות סופי לפער אפשרי מול הגודל המוצהר.
- utils.build_zip_bytes: מניעת שמות רשומה כפולים — הראשון נשמר, הבאים מקבלים סיומת
  ממספרת (x.txt, x_2.txt) עם שמירת הסיומת.
- utils: תיקון docstring (שורה ריקה אחרי רשימת ה-bullets) — מבטל אזהרת docutils/RTD.
- tests: חיזוק test_...skips (file_1/file_2 מדויק) + regression לכפילות שמות.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…ך-יתר של URL

שלושה תיקונים שורשיים ב-Config Inspector (שרץ בתהליך ה-webapp וקורא os.getenv):

1) הפרדה לפי שירות: הצלבנו את כל 236 המשתנים מול עמודת "רכיב" ב-
   docs/environment-variables.rst וגם מול הקוד עצמו (import-closure סטטי של
   webapp/bot/mcp + חיפוש הקוראים של כל KEY). 41 משתנים שנקראים רק בבוט (34,
   כולל ה-webserver הפנימי שרץ בתוך תהליך הבוט), ב-MCP (6) או בסקריפטים (1)
   הוסטו מהעמוד הראשי ל"עמוד 2" חדש (טאב "שירותים אחרים") שמציג מטא-דאטה בלבד
   — בלי Status ובלי Active Value, כי ערכיהם חיים בתהליכים אחרים ואינם נגישים
   מה-webapp. שדה service חדש ב-ConfigDefinition + get_other_services_entries().
   OTEL_EXPORTER_* נשארו בעמוד webapp (ה-SDK קורא אותם מה-env בכל תהליך);
   BOT_USERNAME נשאר webapp ו-BOT_TOKEN נוסף כהגדרה חסרה (נקרא ב-auth_routes).

2) סטטוס "Set" חדש: ערך שהוגדר בסביבה (למשל ברנדר) כשאין ברירת מחדל בקוד אינו
   "Modified" — אין דיפולט שממנו סטינו. determine_status מחזיר Set במקרה זה
   (MCP_SERVER_URL, GITHUB_TOKENS, GITHUB_WEBHOOK_SECRET,
   ALERTMANAGER_WEBHOOK_SECRET, ALERT_TELEGRAM_BOT_TOKEN ודומיהם). נוספו
   set_count לסקירה, ספירה בכרטיסי הקטגוריות ו-pill כחול (טוקן --info) ב-UI.

3) ביטול מיסוך-יתר: "URL" הוסר מ-SENSITIVE_PATTERNS — כתובת ציבורית
   (MCP_SERVER_URL, WEBAPP_URL, PROMETHEUS_URL, PUBLIC_BASE_URL...) אינה סוד.
   URL שמגלם credentials (MONGODB_URL) נשאר ממוסך דרך sensitive=True מפורש,
   ו-TOKEN/SECRET/URI/KEY ממשיכים להיתפס בתבניות.

בנוסף: תוקנו 5 שורות "רכיב" שגויות ב-docs/environment-variables.rst שהתגלו
בהצלבה (ENABLE_INTERNAL_SHARE_WEB, SENTRY_WEBHOOK_SECRET,
SENTRY_WEBHOOK_DEDUP_WINDOW_SECONDS, DUMMY_BOT_TOKEN → Bot; BOT_TOKEN → Bot/WebApp).

טסטים: 32 ב-test_config_inspector_service.py (כולל 3 מחלקות חדשות: סטטוס Set,
מיסוך URL, הפרדת שירותים) — ירוקים. תחביר Jinja אומת.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…פים בעמוד 2, ותיקון העתק-הכל

שלושה תיקונים בהמשך למשוב:
- ה-webserver הוא שירות Render נפרד (ההרצה הפנימית בתוך תהליך הבוט בוטלה):
  ה-closure שלו חושב בנפרד מהבוט, ומשתנים שנקראים בו (SENTRY_WEBHOOK_SECRET,
  SENTRY_WEBHOOK_DEDUP_WINDOW_SECONDS) מסומנים service=webserver. עודכן גם
  "רכיב" ב-environment-variables.rst (Webserver) ותואר ENABLE_INTERNAL_SHARE_WEB.
- משתנים משותפים: השדה service הוחלף ב-services (tuple) — משתנה יכול להשתייך
  לכמה שירותים. עמוד 2 מציג עכשיו את כל 220 המשתנים ששייכים לשירות שאינו webapp,
  כולל המשותפים (למשל MONGODB_URL: bot + mcp + webserver), עם ציון השירותים בכל
  שורה ותג "גם Webapp" למשתנים שערכיהם מוצגים בעמוד הראשון. עמוד 1 נשאר 196.
- תיקון P2 מה-review: "העתק הכל" הוגבל ל-#inspectorPageWebapp — טבלת השירותים
  האחרים (מטא-דאטה בלי ערכים) לא מייצרת יותר שורות KEY= ריקות בייצוא ה-.env.

טסטים: 33 ב-test_config_inspector_service.py (עודכנו לסמנטיקת services + טסט
משתנה-משותף-בשני-העמודים) — ירוקים. תחביר Jinja אומת.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
סקילים (ZIP עם SKILL.md) נכנסו בטעות למסלול הגיבויים, מה שגרם לשלוש בעיות:
save_backup_bytes דוחס מחדש ומזריק metadata.json (לא byte-for-byte),
cleanup_expired_backups מוחק לפי retention, ו-restore עם purge הרסני.

הפתרון — אחסון עצמאי לחלוטין:
- SkillManager חדש (file_manager.py): קולקציית GridFS "skills" נפרדת, שמירת
  bytes as-is (fs.put ישיר), תמיד מונגו בלי תלות ב-BACKUPS_STORAGE ובלי env var.
  skill_id ייחודי (timestamp+uuid) מונע התנגשות; שם קובץ עם סיומת ייחוד מונע דריסה.
- ניתוב בהעלאה: _maybe_store_zip_copy מציג שני כפתורים "סקיל"/"גיבוי" במקום
  שמירה אוטומטית; ה-bytes נשמרים זמנית עד לבחירה מפורשת (עזרי stash ב-utils.py,
  מחיקות מוגבלות ל-allowlist ייעודי).
- SkillMenuHandler חדש (prefix skill_): רשימה + הורדה/מחיקה/תיוג/הערה, כפתור
  "📝 סקילים" בתפריט "הצג את כל הקבצים שלי". תיוג/הערה דרך ה-facade הגנרי הקיים.

טסטים: שמירה+הורדה byte-for-byte, בידוד מ-cleanup של הגיבויים, ושני סקילים
עם אותו שם שאינם דורסים. טסטי הגיבויים הקיימים נשארים ירוקים.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
תיקון כשלי CI/RTD ויישום ממצאי code review על פיצ'ר הסקילים.

CI/RTD:
- עדכון test_documents להתנהגות "בחירה מפורשת" (כפתורים) במקום שמירה אוטומטית
- עדכון סדר תפריט "הצג את כל הקבצים" בשני טסטי patch_coverage (skill_list)
- תיקון docstring שהכשיל את RTD (הסרת * חשוף שנפרש כ-emphasis)

אבטחה/נכונות:
- main: logger.exception בשמירת הערות, בלי חשיפת שגיאת DB למשתמש
- file_manager: נרמול user_id ל-int לפני שמירת סקיל (עקביות שאילתת list_skills)
- config_inspector: מיסוך URL עם credentials מוטמעים (user:pass@) גם בשם לא-רגיש
- utils: אימות token בטוח לשם קובץ + הרשאות 0o700 לתיקיית ה-pending
- conversation: ניקוי ה-pending רק אחרי שמירה מוצלחת (מאפשר retry בכשל)

ביצועים/עקביות:
- skill_menu: איסוף דירוגים ב-thread, הורדה בסריקת GridFS אחת, safe_edit_message_text
- conversation: load ל-to_thread + safe_edit על תשובות ה-routing
- documents: קבוע PENDING_ZIP_TTL_SECONDS + הגבלת ZIP ממתינים למשתמש

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
הבאג בפרודקשן ("save_skill_bytes: GridFS 'skills' לא זמין"): ב-_get_skills_gridfs
נעשתה בדיקה ``if not mongo_db``, אבל get_mongo_db() מחזיר אובייקט Database אמיתי של
pymongo — ו-bool()/not עליו זורק NotImplementedError. החריגה נבלעה ב-except הכללי,
המתודה החזירה None, והסקיל לא נשמר. חיבור המונגו עצמו תקין (קטעי קוד כן נשמרים).

השורש: pymongo אוסר truth-value testing על Database/Collection/MongoClient. שאר
מתודות ה-facade כבר משוות נכון עם ``is None`` — רק שני עוזרי ה-GridFS השתמשו ב-not.

התיקון (root-cause, שני המקומות):
- SkillManager._get_skills_gridfs: ``if not mongo_db`` → ``if mongo_db is None``.
- BackupManager._get_gridfs: אותו באג רדום (מוסתר בפרודקשן ע"י BACKUPS_STORAGE=fs,
  שמחזיר None עוד קודם) — תוקן גם הוא כדי שלא יתפוצץ במצב BACKUPS_STORAGE=mongo.

טסטים: הטסטים הקיימים מוקים את _get_skills_gridfs ולכן פספסו את המסלול האמיתי.
נוספו טסטי רגרסיה שקוראים למתודה האמיתית עם Database אמיתי של pymongo (connect=False,
בלי שרת) ומוודאים שמוחזר GridFS ולא None. אומת red→green. 11 טסטים ירוקים.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
… קונפליקטים לטובת תיקון הבאג של הסקילים)
…וך שירותים

שלושה תיקונים בעקבות פידבק על העמוד:

1. רווחים שנעלמו: עטיפת הטאבים (.inspector-page) היא display:block, ולכן הילדים
   (כרטיסי סיכום, שורת סינון, קטגוריות, טבלה) איבדו את ה-gap של ה-flex ההורה
   (.config-page). העמוד הפעיל הוא עכשיו flex column עם אותו gap — הרווחים חזרו.

2. שורת סינון לעמוד 2 (שירותים אחרים): קטגוריה + שירות (אין שם Status כי אין
   ערכים חיים), עם סנן/איפוס/העתק הכל. הסינון client-side; "העתק הכל" מעתיק
   KEY=default עבור השורות הגלויות ומציין בטוסט שאלה ערכי ברירת מחדל.

3. דיוק שיוך שירותים (68 משתנים): מיפוי אוטומטי של צריכה בפועל — לכל משתנה נבדק
   באילו קבצים הוא נקרא (getenv/config.X) והאם הקובץ שייך לשירות או נטען בסגירת
   ה-imports שלו (entry: mcp_server/app.py, services/webserver.py). משתנים שסווגו
   ל-MCP/Webserver בלי שימוש אמיתי הוסרו משם (21 מ-MCP, 65 מ-Webserver — למשל
   UPTIME_*, PUSH_*, VAPID_*, MAINTENANCE_*, ALERTMANAGER_*). חריגים ידניים:
   PORT נשאר (נצרך בפקודת ההרצה), PROFILER_* נשארו ב-webserver (profiler_handler
   נטען שם בפועל).

   בפרט BACKUPS_STORAGE/BACKUPS_DIR עברו ל-Bot בלבד (גם ב-rst): רק הבוט טוען את
   file_manager; לוובאפ מנגנון גיבוי נפרד (WEBAPP_BACKUPS_DIR). הגיבויים האלה הם
   ZIP של קטעי הקוד (get_user_files) + גיבויי GitHub/Drive — לא אוספים/סימניות.

טסטים: 67 ירוקים (config_inspector, skill_manager, mcp_docs, rst_parser),
Jinja template מתקמפל.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…לתות ממוקדות

תיקונים בעקבות code review (כל הממצאים אומתו מול הקוד):

אבטחה/פרטיות:
- file_manager: אזהרת user_id לא תקין רושמת רק את סוג הערך, לא את הערך עצמו (PII).
- utils: הערה מבהירה ש-_SAFE_TOKEN_RE ([A-Za-z0-9_-]) מונע path traversal ב-stash
  (false positive של static analysis — אין תיקון קוד).

נכונות:
- documents: stash_pending_zip_bytes ו-cleanup_stale_pending_zips רצים ב-to_thread
  (I/O של דיסק בתוך handler אסינכרוני); אם שליחת הודעת הבחירה נכשלת — הקובץ
  והרשומה שנוצרו עבור אותו ZIP מנוקים (בלי כפתורים אין דרך להשלים את הבחירה).
- skill_menu: query.answer() לפני עריכה גם ב-delete_confirm/delete_execute
  וב-send_rating_prompt; ב-skill_rate המענה לפני עריכת ההצלחה בלבד — answer מוקדם
  גורף היה מבטל את ה-show_alert של הודעות השגיאה הקיימות.
- file_manager: נרמול user_id ל-int בכל המתודות (list/get/info/delete) — קלט str
  לא היה מוצא כלום מול שמירה כ-int.

מכסות ואינדקסים (לסקילים אין retention — בלי מכסה האחסון גדל ללא גבול):
- SKILLS_MAX_PER_USER (ברירת מחדל 100) ו-SKILLS_MAX_TOTAL_BYTES (ברירת מחדל 1GB,
  0 = כיבוי) נאכפים לפני fs.put; תועדו ב-rst וב-config inspector (שירות Bot).
- אינדקסים על skills.files: (user_id, skill_id) ו-(skill_id) — best-effort פעם אחת.

ביצועים/מבנה:
- get_skill_info חדש: שליפת סקיל בודד בשאילתה ממוקדת; _find_skill ופרטי/הורדת
  סקיל כבר לא סורקים את כל הרשימה. delete_skills מסנן ב-DB עם $in.
- conversation: ספירת קבצי ה-ZIP אוחדה עם השמירה להלפר אחד שרץ ב-to_thread.
- main: זרימות הערה לגיבוי/סקיל אוחדו להלפר משותף; save_backup_note ב-to_thread.
- SkillInfo הומר ל-dataclass; send_rating_prompt מלוגג כשל ומודיע במקום pass.

טסטים: fixture עם client.close(); fake תומך $in; טסטי מכסות, נרמול str,
ו-get_skill_info; ניקוי קבצי pending בשני טסטים שהשאירו קבצים ב-tmp (אומת 0
שאריות); עדכון הערת הסדר ב-test_patch_coverage. 77 טסטים ירוקים.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…ואייקון מותאם

שלושה שינויים ברמת התצוגה (שום callback_data או מפתח לוגי לא השתנה — אומת שהתווית
אינה משמשת כמפתח זיהוי בשום handler/regex):

1. הודעת קליטת ZIP (documents.py): "איפה לשמור אותו?" במקום "איך", שם הקובץ בשורת
   הכותרת, תיאורי הקטגוריות (סקילים / גיבויים עם אזכור שחזור ריפו בלחיצה), ואייקון
   הסקיל 🧩 בטקסט ובכפתור. html_escape על שם הקובץ נשמר.

2. התווית "📦 קבצי ZIP" → "📦 קבצי גיבוי" — רוכזה לקבוע BTN_BACKUP_ZIPS
   (i18n/strings_he.py, מקור אמת יחיד) הנצרך בכל 6 מופעי הכפתור: תפריט 📚 (message
   + callback), עיבוד Batch, GitHub upload (כולל המקלדת המשוכפלת), ותפריט Drive.
   בהיקף שאושר עודכנו גם הטקסטים המפנים לכפתור (עזרה, הודעת אישור גיבוי, כותרות
   רשימה, הודעות Drive) והתיעוד (BOT_USER_GUIDE, drive_menu.rst). מופעי "ZIP/גדולים"
   בניסוח אחר, docstrings, וה-webapp — במפורש מחוץ להיקף.

3. אייקון ZIP מותאם: tg_emoji(emoji_id, fallback) גנרי ב-utils (לגוף הודעה עם
   parse_mode=HTML בלבד — כפתורים לא תומכים ב-entities), שדה CUSTOM_EMOJI_ZIP_ID
   ב-config (ברירת מחדל None — ה-ID חי רק ב-ENV, משאב צד ג'). בהודעה: ניסיון עם
   האייקון המותאם; על BadRequest — לוג חד-פעמי לתהליך ושליחה חוזרת עם 📁 (המשתמש
   מקבל את ההודעה בכל מקרה). fallback הוא 📁 בדיוק (שתי יחידות UTF-16). תועד
   ב-rst וב-config inspector (display/Bot).

טסטים: נעילת הנוסח החדש ("איפה לשמור", כפתור "🧩 סקיל") + טסט tg_emoji.
85 טסטים ירוקים; אומת ב-diff שכל שינויי הכפתורים הם תווית בלבד.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
בהמשך לשינוי אייקון הסקיל בהודעת קליטת ה-ZIP — איחוד לכל שאר המופעים של
הסקיל-כישות (📝 → 🧩): כפתור "🧩 סקילים" בתפריט 📚 (שתי הגרסאות), כותרת
הרשימה, כפתורי הפריטים, כותרת פרטי הסקיל, caption בהורדה, הודעת "אין סקילים",
והודעת ההצלחה שמפנה לתפריט. שינוי תצוגה בלבד — שום callback_data לא השתנה.

אייקון ה-📝 בתפקידיו האחרים (הערה / ערוך הערה / שנה שם) נשאר בכוונה — הוא
מציין פעולת כתיבה, לא את ישות הסקיל.

51 טסטים ירוקים; אומת שלא נותר "📝 סקיל/סקילים" בקוד.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…דויק לאימוג'י

שלושת הממצאים אומתו מול הקוד ותוקנו:

- github_menu_handler: שתי עריכות ההודעה בנתיבי השגיאה של רשימת הגיבויים
  ("רכיב גיבוי לא זמין" / "שגיאה בטעינת קבצי גיבוי") עברו ל-
  TelegramUtils.safe_edit_message_text — בולע רק "message is not modified"
  (ו-parse fallbacks), מפיץ כל שגיאה אחרת. תוקנו שתי הקריאות הצמודות באותו
  בלוק (ה-review ציין את השנייה; הראשונה זהה בדפוס).

- documents: סימון _custom_emoji_warned והלוג עברו לאחרי הצלחת שליחת ה-fallback
  עם 📁 — רק אז מוכח שהדחייה נבעה מהאימוג'י המותאם. BadRequest ממקור אחר כבר
  לא מסמן/מלוגג בטעות; ה-retry נשאר (כשל אחר ייכשל שוב ויתגלגל ל-except החיצוני
  שמנקה את ה-pending).

- טסטים: נתיב ה-fallback (טלגרם דוחה tg-emoji → ההודעה נשלחת שוב עם 📁, ה-ZIP
  נשאר ממתין לבחירה, הדגל מסומן) + המסלול החיובי (ID מוגדר → ההודעה מכילה את
  התג, הדגל לא מסומן). ל-tests/config.py (ה-config החלופי של הטסטים) נוסף השדה
  CUSTOM_EMOJI_ZIP_ID כמו בפרודקשן.

41 טסטים ירוקים, אפס קבצי pending שיוריים.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
הידע על ה-Config Inspector חי עד היום רק בהיסטוריית ה-PRים, ולכן מי שמוסיף משתנה
חדש חוזר על אותן טעויות: סטטוס Modified שגוי, שיוך שירות מנוחש, ותיאור שלא תואם
בין הקוד ל-rst. העמוד החדש מרכז את המודל ואת נוהל העבודה.

docs/webapp/config-inspector.rst (חדש) — לצד cache-inspector, אותו דפוס:
- למה עמוד 1 מציג רק משתני Webapp: ה-inspector רץ בתוך תהליך ה-webapp ורואה רק את
  ה-ENV שלו; הצגת Status למשתנה של שירות אחר הייתה מטעה (מידע שגוי גרוע מהיעדר מידע).
- מה בעמוד 2 (Bot/MCP/Webserver): מטא-דאטה בלבד, בלי Status/Active Value, עם תגית
  "גם Webapp" למשתנים משותפים והפניה ל-Render Dashboard לערכים בפועל.
- ארבעת הסטטוסים + הכלל השורשי למניעת Modified שגוי: ה-default בהגדרה חייב להיות
  זהה תו-בתו לדיפולט שבקוד; אין דיפולט בקוד ⇒ להשאיר ריק כדי לקבל Set.
- איך בודקים שיוך שירות לפני הוספה: grep על הצריכה בפועל + טבלת נקודות כניסה
  (main.py/webapp/mcp_server/webserver/scripts), וקובץ משותף לפי שרשרת ה-imports.
- משתנה לכמה שירותים: מתועד בכל המקומות — services מלא, עמודת "רכיב" תואמת ב-rst,
  ותיאור זהה בשני המקומות.
- מיסוך רגישים (כולל למה URL הוסר ו-URI נשאר) וצ'קליסט הוספת משתנה.

קישורים: רישום ב-docs/index.rst (חובה — בלי זה RTD נכשל על אזהרה), ו-seealso
ב-environment-variables.rst שמפנה לכללים מתוך ההנחיה המחייבת שבראש העמוד.

CLAUDE.md: השורה על החרגת examples.rst הייתה מיושנת (העמוד כבר ב-toctree ואינו
ב-exclude_patterns). הוחלפה בכלל השימושי — כל עמוד חדש חייב להירשם ב-docs/index.rst.

ולידציה: בניית Sphinx עם -W (בדיוק כמו RTD) עברה עם 0 אזהרות; אומת שהעמוד נבנה,
מופיע ב-toctree, וכל ההפניות ההדדיות נפתרו. 34 טסטים ירוקים. שינויי תיעוד בלבד.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
השורש (אומת בשחזור מלא מול markdown-it האמיתי): מסמכים רבים מגדירים עוגן לכותרת
בתחביר HTML — `## 1. תקשורת <a id="communication"></a>` — ומפנים אליו מתוכן
העניינים. ה-renderer רץ עם html:false (הגנת XSS על תוכן שמשתמשים מעלים), ולכן
התגית לא הופכת לאלמנט. שלושה נזקים בפועל:
  1. אין אלמנט עם id="communication" → הקישור לא מוביל לשומקום (קישורים בתוך
     התוכן נשענים על התנהגות דפדפן טבעית — אין להם handler).
  2. התגית מוצגת כטקסט זבל גלוי בתוך הכותרת.
  3. הטקסט הזה נבלע ל-id האוטומטי ומייצר slug מעוות
     (id="1-תקשורת-a-idcommunicationa").

התיקון שומר על html:false — לא נפתחה פרצה:
- webapp/static/js/md-anchors.js (חדש): שליפת העוגנים ממקור ה-Markdown לפני
  הרינדור וניקויָם מהטקסט, ואחרי הרינדור הוספת אלמנט יעד בלתי-נראה לפני הכותרת.
  תומך גם בעוגן בשורה נפרדת לפני הכותרת (דפוס GitHub), ב-name= לצד id=, ומדלג
  על בלוקי קוד. ה-id מוצב דרך element.id (DOM property) ולא innerHTML — אין
  וקטור הזרקה, ובנוסף יש סניטציה.
- מזהי הכותרות עצמם לא נדרסים: הם משמשים לסימניות על כותרות, ודריסה הייתה
  שוברת סימניות קיימות. לכן יעד נפרד ולא שינוי של heading.id.
- חובר ל-md_preview.html (הצופה הראשי) ול-live-preview.js (תצוגה מקדימה בעורך,
  אותו באג), עם fallback שקט אם המודול לא נטען.

באג נוסף שתוקן אגב: md_preview.html השתמש ב-querySelector(hash) לגלילה לפי
עוגן בכתובת. מזהה שמתחיל בספרה (כמו "1-תקשורת" מכותרת ממוספרת) אינו סלקטור CSS
חוקי והקריאה זרקה — הוחלף ב-getElementById עם decodeURIComponent.

טסטים: tests/md-anchors.test.js (חדש, `node tests/md-anchors.test.js`) — 21
בדיקות ירוקות: שליפה, דפוס GitHub, דילוג על בלוקי קוד, כמה עוגנים לכותרת,
סניטציה, והחלה על DOM (כולל אי-דריסת מזהה תפוס). 81 טסטי פייתון ירוקים; לא
נגעתי בשום קובץ .py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
claude added 3 commits July 30, 2026 10:46
…zsg' into claude/mcp-codekeeper-webapp-ldnzsg
… החיה

סבב review — כל הממצאים אומתו מול הקוד ותוקנו:

1. כותרות Setext (באג נכונות אמיתי, אומת בשחזור): extractExplicitAnchors ספר
   רק כותרות ATX, אבל markdown-it מייצר h1/h2 גם מכותרות Setext (שורת טקסט
   ואחריה === או ---). התוצאה: האינדקסים זזו והעוגן הוחל על הכותרת הלא נכונה —
   במסמך "מבוא\n====" ואחריו "## תקשורת <a id=...>" העוגן נחת על "מבוא".
   נוסף זיהוי Setext עם שמירה על ההתנהגות ל-ATX, ועם בדיקה שמרנית שמונעת זיהוי
   שגוי: קו מפריד (hr) אחרי שורה ריקה, שורת הפרדה של טבלה, פריט רשימה, ציטוט,
   וקוד מוזח — כולם אינם כותרת. נוסף פרמטר setext (ברירת מחדל true) לכיבוי
   כש-md.disable('lheading') פעיל; אומת שאף צרכן בריפו לא מנטרל אותו.

2. ניתוק המצב המודולרי ב-live-preview: pendingExplicitAnchors המשותף בין
   render() ל-enhance() אפשר לשני רינדורים עוקבים לדרוס זה את העוגנים של זה —
   וזה החמיר בגלל ש-repo-browser.js הוא צרכן נוסף של אותו זוג. עכשיו
   renderWithAnchors() מחזיר { html, anchors } והם מועברים במפורש ל-
   enhance(root, anchors). render() נשאר עם החתימה הקיימת (מחזיר HTML) כדי לא
   לשבור קוראים.

3. repo-browser: מנוצל התיקון — כבר היה שם handler תקין לגלילת עוגנים
   (setupMarkdownAnchorScrolling עם getElementById), רק שהעוגנים מעולם לא נוצרו.
   מעבר ל-renderWithAnchors + טעינת md-anchors.js ב-base_repo.html משלימים את
   התמונה גם בעיון בקבצי GitHub.

4. תיעוד config-inspector: פקודות החיפוש הוחלפו ב-rg עם -w (מילה שלמה) ו-
   -g '!tests/**' — הן תופסות עכשיו גם os.getenv('X') בגרש בודד וגם config.X
   בפקודה אחת, בלי להיתפס ל-X_OTHER. הודגם בפועל לפני העדכון. בנוסף הובהר
   שהמיסוך חל רק על ערכים שסווגו כרגישים, ולא על כל ערך.

טסטים: 33 ירוקים (נוספו 12 ל-Setext, כולל מקרי אי-זיהוי), ובנוסף הצלבה של כל
תשעת המקרים מול ה-DOM ש-markdown-it באמת מייצר — 9/9 תואמים. Sphinx -W עבר,
81 טסטי פייתון ירוקים.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
המסמך היה תכנון עם סימוני סטטוס, אבל כל מה שלמדנו תוך כדי המימוש חי רק
בהיסטוריית ה-PRים. מי שירצה לממש שרת MCP בפרויקט אחר לא היה מוצא שם את
המלכודות — רק את התכנון שקדם להן.

נוסף סעיף 14 (11 תתי-סעיפים), כל אחד מהם מבוסס על סבב תיקון אמיתי:
- ל-SDK אין scopes פר-כלי — האכיפה היא בגוף הכלי (10 נקודות אכיפה נפרדות).
- הסתרת כלי מ-tools/list היא UX בלבד; ה-dispatcher מריץ גם כלי מוסתר.
- שינוי scopes מחייב רישום DCR מחדש — שינוי שובר-תאימות למשתמשים קיימים.
- גשר זהות חתום בין הוובאפ (שם הסשן) לשירות ה-MCP (שם רץ ה-OAuth).
- כלי שמקבל מזהה משאב הוא IDOR עד שהוכח אחרת — המקרה של repo ב-docs_get_section,
  שכלי ציבורי אחד עקף בו את כל שער האדמין של פאזה ד'.
- נרמול נתיבים בשכבת ה-handler ולא הסתמכות על ה-backend (סדר הפעולות קריטי).
- תקציב טוקנים כדרישה תפקודית: למה נולד docs_get_section, ולמה לעולם לא
  מחזירים למודל רק "לא נמצא".
- תיאור הכלי הוא ממשק — הוא קובע איזה כלי המודל יבחר בין שניים חופפים.
- פארסר עצמאי במקום לוגיקה ב-handler, כולל מקרי הקצה של RST.
- שירות שקורא ממצב על דיסק חייב לסנכרן את הדיסק של עצמו (repo_autosync).
- מה שנשאר בתכנום ולא מומש, כדי שהמסמך יישאר מדויק.

הכותרת והמסגור עודכנו ל"תכנון ולקחי מימוש" עם הנחיית קריאה. כל המספרים אומתו
מול הקוד (18 כלים, 17 מודולים, 5 require_write + 5 require_admin).

שינוי תיעוד בלבד; הקובץ מחוץ ל-docs/ ואינו משפיע על בניית RTD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sourcery-ai

sourcery-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds post-implementation lessons to the MCP Claude integration design doc, and introduces Markdown anchor extraction/apply logic across the webapp so explicit HTML anchors in headings (e.g. GitHub-style <a id=...></a>) work safely despite html:false rendering, plus minor RTD/docs and template wiring updates.

Sequence diagram for Markdown rendering with explicit anchors

sequenceDiagram
  participant User
  participant RepoBrowser
  participant MarkdownLiveRenderer
  participant MdAnchors
  participant MarkdownIt as MarkdownRenderer
  participant DOM as PreviewContainer

  User->>RepoBrowser: openMarkdownFile(content)
  RepoBrowser->>MarkdownLiveRenderer: renderWithAnchors(content)
  MarkdownLiveRenderer->>MdAnchors: extractExplicitAnchors(source)
  MdAnchors-->>MarkdownLiveRenderer: { source, anchors }
  MarkdownLiveRenderer->>MarkdownRenderer: render(source)
  MarkdownRenderer-->>MarkdownLiveRenderer: html
  MarkdownLiveRenderer->>RepoBrowser: { html, anchors }
  RepoBrowser->>DOM: previewContent.innerHTML = html
  RepoBrowser->>MarkdownLiveRenderer: enhance(previewContent, anchors)
  MarkdownLiveRenderer->>MdAnchors: applyExplicitAnchors(previewContent, anchors)
  MdAnchors-->>MarkdownLiveRenderer: anchorsApplied
  MarkdownLiveRenderer->>MarkdownLiveRenderer: highlightBlocks/enhanceTaskLists/lazyLoadImages/renderMermaid
Loading

File-Level Changes

Change Details Files
Extend the MCP Claude integration design document with a new implementation-lessons section and updated status/details based on the actual server code.
  • Retitle the document from pure planning to planning plus implementation lessons and add a reading guide that directs implementors to the new section first.
  • Update the status line to reflect additional implemented tools and phases, including the public docs_get_section tool and corrected tool/module counts.
  • Add Section 14 with multiple detailed subsections capturing real-world issues: SDK scope enforcement inside tools, tools/list vs dispatcher behavior, DCR re-registration on scope changes, identity bridge design, IDOR risks for repo/tenant/project parameters, path normalization before backend access, token-budget-driven docs tooling, tool description as interface guidance, a reusable RST parser and its edge cases, per-service disk sync requirements, and a clear list of what remains unimplemented.
  • Shift original sources section to 15 and keep the file under FEATURE_SUGGESTIONS so it stays out of RTD builds.
FEATURE_SUGGESTIONS/FEATURE_MCP_CLAUDE_INTEGRATION.md
Introduce a Markdown anchor handling pipeline that safely preserves and re-applies explicit HTML anchors in headings for previews and repo browser without enabling raw HTML in the renderer.
  • Refactor live-preview Markdown rendering to go through a helper that first extracts and strips explicit anchors from the source, then returns both rendered HTML and an anchors descriptor.
  • Add applyExplicitAnchors helper in live-preview to call into a browser-global anchor API and attach invisible anchor targets to rendered headings, with defensive error handling.
  • Expose a renderWithAnchors method alongside the existing render in the Markdown live renderer, keeping the old signature but allowing consumers to get { html, anchors } for richer behavior.
  • Update the live preview class to use renderWithAnchors and pass anchors into enhance, so authoring previews also support anchor navigation.
  • Wire the new anchor handling into the repo browser’s Markdown preview, rendering with anchors and applying them during enhancement so intra-document links work there too.
webapp/static/js/live-preview.js
webapp/static/js/repo-browser.js
Add a shared md-anchors utility that implements anchor extraction, sanitation, DOM application, and a combined render helper, and cover it with standalone Node-based tests.
  • Create md-anchors.js as a self-contained browser module that finds `<a id
name=...>anchors in Markdown headings, including ATX and Setext styles, strips them from heading text, tracks heading indices, and later injects invisiblespan.md-explicit-anchortargets before the corresponding h1–h6 elements.</li><li>Implement robust parsing around code fences, lists, blockquotes, tables, and Setext detection to avoid misclassifying non-headings as headings, and sanitize anchor IDs to remove unsafe or malformed characters while preserving intent.</li><li>Export an API that works both in Node (via module.exports) and the browser (viawindow.MdAnchors), including extractExplicitAnchors, applyExplicitAnchors, renderWithAnchors, and sanitizeAnchorId.</li><li>Add md-anchors.test.js` as a Node script that loads the browser-style module via a VM sandbox and exercises anchor extraction, Setext handling, ID sanitization, and DOM application logic using a minimal fake DOM, with pass/fail logging and process exit codes.
Wire md-anchors into Markdown templates and improve hash-based scrolling to work with non-CSS-safe IDs.
  • Include the md-anchors script in Markdown preview, edit, upload, and repo browser templates so both initial server-side rendered Markdown and JS live preview shares anchor logic.
  • Adjust the md_preview initial render script to use MdAnchors.renderWithAnchors when available, passing the markdown-it render function and container so anchors are applied after rendering; fall back to a simple render if the module is missing.
  • Define CSS for .md-explicit-anchor targets to be invisible and add scroll-margin-top so anchored headings don’t end up under the fixed top bar when navigated.
  • Improve scroll restoration logic to use getElementById with a decoded hash instead of querySelector, handling IDs that start with digits or contain percent encoding, and avoiding selector syntax errors.
webapp/templates/md_preview.html
webapp/templates/edit_file.html
webapp/templates/repo/base_repo.html
webapp/templates/upload.html
Tighten RTD/docs process guidance and add new documentation stubs for configuration inspection.
  • Clarify documentation rules around registering every new page in docs/index.rst to avoid RTD document isn't included in any toctree warnings that fail the build, and recommend using exclude_patterns for pages that are intentionally not in the toctree.
  • Update docs guidance to treat exclude_patterns as the central place for intentionally unpublished or duplicate pages, emphasizing that exclusions should be deliberate.
  • Add or update docs/index.rst and environment variables docs to reflect the new config inspector documentation and ensure proper inclusion in the toctree.
  • Introduce a new docs/webapp/config-inspector.rst file (or stub) to document the webapp configuration inspector feature.
CLAUDE.md
docs/index.rst
docs/environment-variables.rst
docs/webapp/config-inspector.rst

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

🧯 Dangerous deletes guard report

Policy: see .cursorrules — dangerous deletions are blocked unless wrapped safely.

Summary:

  • Flagged findings (blocking): 0
    0
  • Excluded matches (not blocking): 15
  • Total matches (all files): 129

Flagged findings (file:line:snippet):
(none)

Excluded matches (by path pattern)
./node_modules/mermaid/dist/mermaid.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values for tr … [truncated]
./node_modules/mermaid/dist/mermaid.min.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values fo … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.core/chunk-KS23V3DP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence  … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm/chunk-2M32CCKP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence d … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequen … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs:1:var r={name:"mermaid",version:"11.12.0",description:"Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graph … [truncated]
./node_modules/mermaid/dist/mermaid.min.js:1524:`,"getStyles"),c1e=RQe});var h1e={};dr(h1e,{diagram:()=>NQe});var NQe,f1e=N(()=>{"use strict";$ge();a1e();l1e();u1e();NQe={parser:Fge,db:n1e,renderer:o1e,styles:c1e}});var m1e,g1e=N(()=>{"use  … [truncated]
./node_modules/katex/package.json:153:    "build": "rimraf dist/ && mkdirp dist && cp README.md dist && rollup -c --failAfterWarnings && webpack && node update-sri.js package dist/README.md",
./node_modules/katex/src/fonts/Makefile:139:	rm -rf pfa ff otf ttf woff woff2
./Dockerfile:42:    rm -rf /var/lib/apt/lists/*
./Dockerfile:121:    rm -rf /var/lib/apt/lists/*
./webapp/static/js/md_preview.bundle.js.map:4:  "sourcesContent": ["// Markdown-it plugin to render GitHub-style task lists; see\n//\n// https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments\n// https://github.com/blog/1825-t … [truncated]
./docs/DOCUMENTATION_GUIDE.md:453:rm -rf _build
./docs/Makefile:24:	rm -rf $(BUILDDIR)
./README.md:842:find . -name "__pycache__" -exec rm -rf {} +

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@amirbiron, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 231535c4-6e2a-4d9b-b5ad-22860c06cbfb

📥 Commits

Reviewing files that changed from the base of the PR and between 8510db0 and 4b429d6.

📒 Files selected for processing (1)
  • FEATURE_SUGGESTIONS/FEATURE_MCP_CLAUDE_INTEGRATION.md
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/mcp-codekeeper-webapp-ldnzsg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Expand MCP Claude integration plan with post-implementation lessons learned

📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Retitle the MCP integration document as “plan + implementation lessons”.
• Add a new “what the plan missed” section capturing concrete pitfalls from production.
• Update the implementation status summary to reflect the current tool set and scope.
Diagram

graph TD
A["MCP integration doc"] --> B["Section 14: lessons"] --> C["Auth: enforce in tool body"] --> D["Access: tools/list != security"] --> E["OAuth: DCR re-register on scopes"] --> F["Security: allowlist + path normalize"] --> G["Ops/UX: token budget + disk autosync"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Split lessons learned into a dedicated doc
  • ➕ Keeps the original plan sections stable and easier to reference
  • ➕ Allows publishing/curation separately from planning content
  • ➖ Creates one more artifact to keep in sync with the implementation status
  • ➖ Readers may miss the lessons if they only open the primary doc
2. Move the document into docs/ and link from FEATURE_SUGGESTIONS/
  • ➕ Makes content discoverable via RTD/Sphinx builds and navigation
  • ➕ Encourages ongoing maintenance as “official” documentation
  • ➖ May require Sphinx wiring (toctree/exclude_patterns) and could introduce build risk
  • ➖ Not all planning content may be appropriate for public docs

Recommendation: Current approach (single authoritative document with a prominent “start here” pointer to Section 14) is a good fit for implementers and reduces context-switching. If this content is meant to be long-lived “operational knowledge,” consider later extracting Section 14 into a dedicated docs/ page and leaving a short summary + link in this file.

Files changed (1) +183 / -3

Documentation (1) +183 / -3
FEATURE_MCP_CLAUDE_INTEGRATION.mdAdd post-implementation lessons-learned section and update doc framing +183/-3

Add post-implementation lessons-learned section and update doc framing

• Renames the document title and adds an upfront reading guide emphasizing the new lessons-learned section. Introduces a detailed new Section 14 covering real-world pitfalls (scope enforcement, tool listing vs authorization, DCR scope changes, signed identity bridging, IDOR/allowlists, path normalization, token budget considerations, RST parsing separation, and repo autosync needs) and shifts the original sources section to Section 15.

FEATURE_SUGGESTIONS/FEATURE_MCP_CLAUDE_INTEGRATION.md

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

⏱️ Performance report

(No performance test durations collected. Mark tests with @pytest.mark.performance.)

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully!

To view locally:

  1. Download the artifacts
  2. Extract the zip file
  3. Open index.html in your browser

@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@amirbiron
amirbiron merged commit 6925bff into main Aug 1, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants