feat(mcp): query ב-codekeeper_get_file — חיפוש בתוך קובץ שמור

Merged
AM
1mo ago
+1537-36

✨ תיאור קצר

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 אמיתי, לא דרך פונקציות פנימיות.

כל בדיקה הוכחה שהיא מסוגלת ליפול, בשתי דרכים:

  1. על הקוד שלפני התוספת — 17 מתוך 20 נפלו. שלושת העוברים הם בדיוק טסטי התוספתיות, שאמורים לעבור בשני הצדדים.
  2. מטריצת מוטציות — 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 יראה מעכשיו ערך שלא הכיר.

ממצאים לדיווח (לא תוקנו כאן, בכוונה)

  1. codekeeper_search_repo חותך snippet ב-500 תווים ולא בבתים. נמדד שזה אינו שובר אותו — תקציב הבתים שם כן נאכף בבתים, ולכן ההשפעה היא פחות שורות שנכנסות לתשובה ולא פלט פגום. להרחיב את ה-PR לכלי משוחרר לא היה בבקשה.
  2. 63 קבצי .py בריפו מכילים תווי BiDi בלתי נראים (U+200F/U+200E), ו-bandit מסמן B613 על אלה שמחוץ ל-tests/. CI מריץ bandit -q -r . || true, ולכן אין גייט — הספירה זהה לפני ואחרי השינוי הזה.
  3. 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.