feat(mcp): query ב-codekeeper_get_file — חיפוש בתוך קובץ שמור
✨ תיאור קצר
codekeeper_search_repo כבר מחזיר path+line+snippet עם context_lines לקבצי ריפו — הקבצים הפרטיים פשוט לא קיבלו את הכלי הזה, ולכן סוכן שרצה שדה אחד מתוך CobaltNext.json משך 31KB, או ניחש טווח. codekeeper_get_file מקבל עכשיו query="..." שמחזיר מופעים במקום תוכן, בדיוק בתבנית שבה outline=true מחזיר מפה במקום תוכן ב-codekeeper_get_repo_file. זה לא פרסר ולא כלי חדש — זו סריקת מחרוזת על טקסט שנשלף ממונגו בכל מקרה, והסינון קורה אחרי השליפה.
📦 שינויים עיקריים
- קוד (Backend)
- בוט טלגרם
- מסד נתונים/מיגרציות
- תיעוד (docs/)
- DevOps/CI/CD
פירוט נקודות:
mcp_server/handlers.py— הסורק הטהורscan_file_query, העוזרclip_to_bytes, השערfile_query_error, והקבועים.get_fileמצמידcontext_linesו-max_results.mcp_server/backend.py—_apply_query_to_fileבונה את המעטפת ומסיר את התוכן דרך_HEAVY_FIELDSהקיים. הדחייה שלquery+linesושל שאילתה פסולה יושבת בראשProductionBackend.get_file, לפני הקריאה למסד — אותו מיקום בדיוק כמוoutline_and_linesב-repo_backend.mcp_server/server.py— שלושת הפרמטרים, התיאור שהסוכן קורא, והעברת מעטפת ה-queryכמות שהיא.mcp_server/analytics.py— ערך רביעי ל-ck_read_mode.docs/mcp-server.rst— עוגן חדשmcp-file-query, שורות בטבלת הקבועים, ברשימת ה-statusובטבלתck_read_mode.docs/whats-new.rst— רשומה.mcp_server/README.md— שורת הכלי.
צורת התשובה — זהה ל-codekeeper_search_repo
{"found": true, "status": "query",
"file": {"file_name": "CobaltNext.json", "version": 2, "lines_count": 812},
"query": "timeout", "count": 2, "total": 2, "truncated": false,
"results": [{"line": 41, "snippet": "\"timeout\": 30,"},
{"line": 207, "snippet": "\"timeout_ms\": 4500,"}]}
אותם count/total/truncated/results, אותם line/snippet, ואותו context_lines שמוסיף context_before/context_after רק כשביקשו אותו. אין path, כי זה קובץ אחד. אין אוצר מילים שני לאותו דבר.
שלוש ההחלטות
query + lines נדחים | query_and_lines, כמו outline_and_lines. לא התעלמות שקטה מאחד מהם |
| תקרת מופעים ותקציב בתים | הערכים נגזרים מ-repo_handlers ולא מומצאים (50 · 100 · 10 · 256,000). חריגה מוצהרת ב-truncated, בלי חיתוך שקט |
| אפס מופעים אינו כשל | הצלחה עם רשימה ריקה. query ריק דווקא כן נדחה — קריאתו כ"בלי query" הייתה מחזירה את הקובץ המלא למי שביקש מופעים |
מה שלא נכנס
אין stemming ואין רג'קס — תו מיוחד בשאילתה הוא תו. codekeeper_search_code לא נגע: line שם דורש גישור על ה-stemming של מונגו, וזו בקשה נפרדת.
עברית — התקרה נמדדת בבתים
תקרת הטקסט של רשומה היא 500 בתים ולא תווים. המספר נלקח מ-codekeeper_search_repo; מה שהוחלף היא היחידה. תו עברי הוא שני בתים ותו CJK שלושה, וחסם בתווים כבר נפל כאן פעם אחת — 200 תווים לרשומה נתנו עמוד של 323,000 בתים מול תקציב של 256,000. החיתוך נוחת תמיד על גבול תו: encode()[:n] לבדו מחזיר רצף פגום שנשבר אצל הצרכן, לא במקום שגרם לו. תקציב הפלט נמדד על הסריאליזציה האמיתית ב-UTF-8.
תוספתיות
קריאה בלי query מחזירה בדיוק את מה שהחזירה קודם — {found, file} ותו לא, בלי status. אף פרמטר חדש אינו required בסכימה, ו-query מקבל את אותה צורה בדיוק כמו file_name הקיים.
🧪 בדיקות
- Unit
- Integration
- Manual
1,054 טסטים עוברים (tests/test_mcp_*.py + טסטי התיעוד וה-RST), אחד מדולג. הקובץ החדש tests/test_mcp_file_query.py — 22 בדיקות, כולן דרך ממשק ה-MCP (mcp.call_tool) מעל ProductionBackend אמיתי, לא דרך פונקציות פנימיות.
כל בדיקה הוכחה שהיא מסוגלת ליפול, בשתי דרכים:
- על הקוד שלפני התוספת — 17 מתוך 20 נפלו. שלושת העוברים הם בדיוק טסטי התוספתיות, שאמורים לעבור בשני הצדדים.
- מטריצת מוטציות — 12/12 נתפסו. כל מוטציה מבטלת תיקון אחד ומוודאת שהטסט שלו נופל: ביטול הדחייה של
query+lines; מחרוזת ריקה שנקראת כ"בליquery"; חיתוך בתווים במקום בבתים; ביטול תווית ה-read mode;truncatedשאינו נגזר מהתקרה;splitlines()במקוםsplit("\n"); התאמה רגישה לרישיות; ביטול תקציב הבתים; התוכן שנשאר בתשובה; אפס מופעים כשגיאה; ביטול ההצמדה; והסתעפות על צורת המסמך במקום על מה שביקשו.
הבדיקה הקריטית היא השרשור: query= ואז lines=[line, line] על מה שחזר מחזיר את הקטע שהעיגון הצביע עליו. היא רצה על קובץ שמכיל \x0c, בדיוק מפני ש-splitlines() מפצל עליו ו-split("\n") לא — זה מה שהופך את ההבטחה "כל שורה שחוזרת ניתנת לבקשה" לנבדקת ולא להצהרה.
בדיקות שרצו מעבר לטסטים:
flake8 --select=E9,F63,F7,F82(החוסם ב-CI) — 0.mypy --ignore-missing-importsעלmcp_server/— 0 שגיאותattr-defined/return-value(אלה שחוסמות).ruff check— אין ממצא חדש, נמדד בהשוואת פלט לפני/אחרי.bandit— ממצאים זהים לפני ואחרי.- בניית Sphinx עם
-Wעל שני העמודים שנערכו. היא תפסה אזהרה אמיתית שהכנסתי (Explicit markup ends without a blank line) והיא תוקנה; אחריה נותרה אזהרה אחת בלבד, וריצת בקרה על התיעוד שלפני השינוי מפיקה בדיוק אותה אזהרה — כלומר היא נובעת מבניית תת-קבוצה של עמודים ולא מהשינוי.
🧪 בדיקות נדרשות ב-PR
- 🔍 Code Quality & Security
- Unit Tests (3.11)
- Unit Tests (3.12)
📝 סוג שינוי
- feat: פיצ'ר חדש
✅ צ'קליסט
- הקוד עוקב אחרי הסגנון (Black/isort/flake8/mypy)
- בדיקות רצות ועוברות
- תיעוד עודכן (README/Docs)
- לא נוספו ג'ובים חדשים
- לא נוספו/שונו משתני סביבה
- לא נוספו/השתנו טוקנים
- אין סודות/מפתחות בקוד
- אין מחיקות מסוכנות/פעולות על root
- הודעת הקומיט תואמת Conventional Commits
- כל ה-Required Checks לעיל ירוקים אצלי מקומית
- צילום/וידאו UI — לא רלוונטי
- עיינתי במסמכי אתר התיעוד — נתיב:
docs/mcp-server.rst(mcp-limits,mcp-outline,mcp-line-range,mcp-read-mode,mcp-analytics),docs/doc-authoring.rst,docs/versioning-stable-anchors.rst,AI-MAP.md| המשפט: "linesו-outlineאינם מצטברים. קריאה שמעבירה את שניהם מוחזרת כ-{"ok": false, "error": "outline_and_lines"}, ולא מתעלמת בשקט מאחד מהם."
🧩 השפעות/סיכונים
נמוך. הפיצ'ר תוספתי לחלוטין — כל מסלול קיים מחזיר בדיוק את מה שהחזיר קודם, וזה נבדק במפורש. אין נגיעה במסד, אין משתני סביבה חדשים, ואין עוגן תיעוד שנשבר (רק נוסף). המימוש הוא סריקת מחרוזת בזיכרון על טקסט שכבר נשלף, ללא שאילתה נוספת.
מה כן ראוי לשים לב אליו: ck_read_mode מקבל ערך רביעי. בלעדיו כל קריאת query הייתה נספרת כקריאת קובץ מלא, כלומר מנפחת בדיוק את העמודה שהמאפיין נבנה כדי למדוד. הדשבורד ב-/admin/mcp יראה מעכשיו ערך שלא הכיר.
ממצאים לדיווח (לא תוקנו כאן, בכוונה)
codekeeper_search_repoחותךsnippetב-500 תווים ולא בבתים. נמדד שזה אינו שובר אותו — תקציב הבתים שם כן נאכף בבתים, ולכן ההשפעה היא פחות שורות שנכנסות לתשובה ולא פלט פגום. להרחיב את ה-PR לכלי משוחרר לא היה בבקשה.- 63 קבצי
.pyבריפו מכילים תווי BiDi בלתי נראים (U+200F/U+200E), ו-banditמסמןB613על אלה שמחוץ ל-tests/. CI מריץbandit -q -r . || true, ולכן אין גייט — הספירה זהה לפני ואחרי השינוי הזה. - RST לא תומך בקינון סימון פנימי, ולכן צורות כמו
**lines...**בתיעוד מרנדרות גרשיים גלויים. השורות שלי נכתבו נכון; הקיימות לא נגעתי בהן.
🔗 קישורים
- Issues קשורים: —
- מסמכים רלוונטיים:
docs/mcp-server.rst§ :ref:mcp-file-query
🧯 סיכון / החזרה לאחור (Rollback)
git revert של הקומיט היחיד. אין מיגרציה, אין מצב מתמיד, ואין צרכן חיצוני שנשען על הפרמטר החדש — לקוח קיים שאינו מעביר query אינו מבחין בהבדל.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QVLb1gSu75BigzTzz3Eurw
Generated by Claude Code
Summary by Sourcery
Enable codekeeper_get_file to search within stored files and return bounded, line-anchored matches without changing existing read modes.
New Features:
- Add string-based, case-insensitive searching to
codekeeper_get_file, returning matching line occurrences with snippets and optional context instead of full file content.
Bug Fixes:
- Reject invalid or ambiguous query requests explicitly, including empty or multiline queries, query combined with line ranges, and query display options without a query.
Enhancements:
- Add byte-aware result and snippet limits with explicit truncation reporting while preserving existing full-file and range-read behavior.
- Track query reads separately in MCP analytics and constrain read-mode detection to parameters owned by each tool.
Documentation:
- Document private-file querying, response limits, read modes, and the new MCP tool behavior.
Tests:
- Add comprehensive MCP-level coverage for query matching, limits, UTF-8 safety, validation, analytics classification, response compatibility, and chaining query results into line-range reads.