feat(mcp): FU-14 GAP-49 — תיקון שם-הכלי המטעה (precedent_search_library)

INV-TOOL2: `precedent_search_library` (שמחפש ציטוטים מצורפים-לתיק) היה הפוך
וכמעט-זהה ל-`search_precedent_library` (ספריית-הפסיקה הסמכותית, מקור CREAC),
מה שסיכן ציטוט מהמקור הלא-נכון בהחלטה. שונה ל-`search_case_precedents` (שם
ברור: case-attached). השם הישן נשמר כ-@mcp.tool() alias deprecated המנתב לחדש
→ אפס שבירה לסוכנים חיים.

docstrings של שני כלי-הפסיקה הובהרו (case-attached מול authoritative).
עודכנו: web/app.py (typeahead), legal-researcher/legal-writer docs, precedent_library docstring.

5 כלי-החיפוש הנותרים (search_decisions/case_documents/find_similar/internal/
precedent_library) מחפשים קורפוסים מובחנים בשמות סבירים — לא בוצע rename המוני
(churn גבוה, ערך נמוך מול הסיכון).

בדיקות: 182/182 עוברים. אחרי deploy — סנכרון cross-company של doc-הסוכן.

Invariants: מקדם INV-TOOL2 + G2. מתועד ב-X9 + gap-audit פרוסה 8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 18:51:17 +00:00
parent 172511339f
commit 14568fdd15
8 changed files with 39 additions and 15 deletions

View File

@@ -19,7 +19,7 @@ tools:
- mcp__legal-ai__extract_references
- mcp__legal-ai__precedent_attach
- mcp__legal-ai__precedent_list
- mcp__legal-ai__precedent_search_library
- mcp__legal-ai__search_case_precedents
- mcp__legal-ai__search_precedent_library
- mcp__legal-ai__internal_decision_upload
- mcp__legal-ai__precedent_library_upload
@@ -191,7 +191,7 @@ mcp__legal-ai__internal_decision_upload(
**שלושת הקורפוסים — אל תבלבל:**
- `search_precedent_library` = פסיקה חיצונית סמכותית עם הלכות מאושרות (עליון/מנהלי/ועדות ערר אחרות) + supporting_quote מוכן.
- `search_decisions` = החלטות דפנה (style_corpus) — הקאנון האישי שלה.
- `precedent_search_library` = ציטוטים שדפנה צירפה ידנית לתיקים בעבר (case_precedents).
- `search_case_precedents` = ציטוטים שדפנה צירפה ידנית לתיקים בעבר (case_precedents).
#### 2ב.1 — קורפוס סמכותי (`search_precedent_library`) — חובה
@@ -285,7 +285,7 @@ search_internal_decisions(
#### 2ב.5 — תיעוד פסיקה חסרה (`missing_precedent_create`) — חובה
**מתי לקרוא:** לכל ציטוט שהצדדים הביאו (בכתב ערר / תגובה / תגובת ועדה) **שלא נמצא בקורפוס** אחרי חיפוש מובנה לפי פרוטוקול 2ב.4א (`search_precedent_library` + `search_internal_decisions` + `precedent_search_library`, כולל שאילתה עם הקשר/מספר תיק).
**מתי לקרוא:** לכל ציטוט שהצדדים הביאו (בכתב ערר / תגובה / תגובת ועדה) **שלא נמצא בקורפוס** אחרי חיפוש מובנה לפי פרוטוקול 2ב.4א (`search_precedent_library` + `search_internal_decisions` + `search_case_precedents`, כולל שאילתה עם הקשר/מספר תיק).
**למה זה חשוב:**
- ה-writer יודע שלא להסתמך על פסיקה שלא ב-DB ("טוענים שמופיע" ≠ "אומת")

View File

@@ -351,7 +351,7 @@ fi
**הבחנה בין כלים:**
- `search_decisions` = החלטות דפנה עצמה (סגנון, אסטרטגיה, ג'וריספרודנציה אישית).
- `search_precedent_library` = פסיקה חיצונית סמכותית (מחייבת או משכנעת — בית המשפט העליון, מנהלי, ועדות ערר אחרות).
- `precedent_search_library` (שונה!) = ציטוטים שדפנה צירפה ידנית לתיקים בעבר. לא לבלבל.
- `search_case_precedents` (שונה!) = ציטוטים שדפנה צירפה ידנית לתיקים בעבר. לא לבלבל.
חפש לפי `practice_area` (rishuy_uvniya / betterment_levy / compensation_197) ולפי `subject_tag` רלוונטי. הלכות שלא אושרו ע"י דפנה לא מוחזרות מהכלי — אם החיפוש ריק, חזור ל-`search_decisions` בלבד.

View File

@@ -48,8 +48,8 @@ JSON-RPC 2.0 (result/error envelope) (https://www.jsonrpc.org/specification) ·
**מקורות:** Anthropic — *Writing effective tools / clear names* (https://www.anthropic.com/engineering/writing-tools-for-agents) ·
Google *API Design Guide* (naming) (https://cloud.google.com/apis/design/naming_convention) ·
Zalando *RESTful API Guidelines* | סטטוס: verified
**אכיפה:** איחוד/מיזוג כלי-חיפוש + כלי-בלוק; rename של שמות-מטעים. **כיום אין.**
**הפרה ידועה:** `precedent_search_library` מחפש למעשה quotes מצורפים-לתיק (שם מטעה); 6 כלי-חיפוש + 6 כלי-בלוק חופפים ([gap-audit GAP-49/50](gap-audit.md)).
**אכיפה:** איחוד/מיזוג כלי-חיפוש + כלי-בלוק; rename של שמות-מטעים. **GAP-49 (חלק קריטי) ✅ נסגר (2026-06-06):** הכלי המטעה `precedent_search_library` (חיפוש ציטוטים מצורפים-לתיק) שונה ל-**`search_case_precedents`** — מבטל את ההיפוך המסוכן מול `search_precedent_library` (הספרייה הסמכותית); הישן נשמר כ-alias deprecated לתאימות. docstrings של שני הכלים הובהרו (case-attached מול authoritative). 5 כלי-החיפוש הנותרים (search_decisions=סגנון-דפנה · search_case_documents=תיק · find_similar_cases=cross-case · search_internal_decisions=ועדות-ערר · search_precedent_library=פסיקה-סמכותית) מחפשים קורפוסים מובחנים עם שמות סבירים.
**הפרה ידועה:** 6 כלי-בלוק חופפים ([gap-audit GAP-50](gap-audit.md)) — בטיפול נפרד.
### INV-TOOL3: idempotency בכל כלי-מוטציה
**כלל:** כלי שמשנה-מצב הוא **idempotent על מפתח דטרמיניסטי** — קריאה חוזרת אינה יוצרת כפילות. מופע של

View File

@@ -83,7 +83,7 @@
| GAP-46 | הרשאות-סוכן לא-מתועדות (analyst/researcher חסרי כלים) | INV-AG3/TOOL6 | High | `.claude/agents/legal-analyst.md`, `legal-researcher.md` | יישור tools↔instructions |
| GAP-47 | `draft_section` ללא provenance (chunk→document/page); הנחיות-יו"ר ב-md ולא DB | INV-TOOL4, G9 | Medium | `mcp-server/.../drafting.py` | provenance בפלט + DB ל-directions |
| GAP-48 | envelope-תשובה לא-עקבי (71 כלים: string/JSON/{error}) | INV-TOOL1, G2 | Medium | `mcp-server/.../server.py`, tools/ | wrapper `{status,data,message}` |
| GAP-49 | 6 כלי-חיפוש חופפים + `precedent_search_library` שם-מטעה | INV-TOOL2, G2 | Medium | `server.py` (search_*), `precedents.py:81` | מיזוג + rename לפי-קורפוס |
| GAP-49 | 6 כלי-חיפוש חופפים + `precedent_search_library` שם-מטעה | INV-TOOL2, G2 | Medium | `server.py` (search_*), `precedents.py:81` | **שם-מטעה תוקן** (`precedent_search_library``search_case_precedents`, alias deprecated); 5 הנותרים = קורפוסים מובחנים בשמות סבירים |
| GAP-50 | 6 כלי-כתיבת-בלוק חופפים (draft_section/get_block_context/write_*/save_*) | INV-TOOL2, G2 | Medium | `server.py:500-616` | מיזוג context↔write |
| GAP-51 | `set_outcome` enum-mismatch (3≠4); אוצרות-מילים סותרות | INV-TOOL1/UI1 | Medium | `block_writer.py:442` מול `lessons.py:11`, `workflow.py:145` | SSoT יחיד ל-outcome |
| GAP-52 | רוב הכלים לא-idempotent (case_create/document_upload/precedent_attach) | INV-TOOL3, G3 | Medium | `server.py`, tools/ | upsert/ON CONFLICT |
@@ -204,7 +204,8 @@
- **סטטוס חלקי (פרוסה 4, 2026-06-06):** ✅ **GAP-47 (חלק provenance, INV-TOOL4/G9)**`draft_section` חושף בפלט `document_id`+`page`+`score` לכל קטע ב-`case_documents`/`precedents` (ה-provenance כבר נשלף ב-`search_similar` ונזרק; כעת מוחזר), כך שהכותב יכול לעקוב אל המקור ולצטטו. תוספתי, לא-שובר. **נותר ב-GAP-47:** העברת הנחיות-יו"ר מ-`analysis-and-research.md` ל-DB (`get_chair_directions`) — שינוי-מסלול גדול יותר הנוגע ל-UI של דפנה ולזרימת-האנליסט; לפרוסה נפרדת.
- **סטטוס חלקי (פרוסה 5, 2026-06-06):** 🔄 **GAP-48 (envelope אחיד, INV-TOOL1) — תחילת מיגרציה הדרגתית.** נוצר `tools/envelope.py` כ-SSoT יחיד (`ok`/`empty`/`err``{status,data,message}`, status מבחין הצלחה/ריק/שגיאה) המחליף 3 מוסכמות סותרות (raw payload / `{error}` / `{status,message}` אד-הוק) ו-5 עותקי `_ok`/`_err` משוכפלים. **משפחת-החיפוש הראשונה הומרה** (`search_decisions`/`search_case_documents`/`find_similar_cases`/`search_internal_decisions`); `web/app.py` מפרק דרך `envelope_unwrap` לשמירת חוזה-ה-API (X6) ללא-שינוי; טסט `test_search_domain_scope` עודכן לחוזה החדש (5/5 ✅). **החלטה הנדסית:** הדרגתי לפי-משפחה ולא big-bang — מפת-צרכנים (Explore) הראתה ש-server.py הוא pass-through, web-ui מבודד (`/api/*`), ורק 17 כלים נצרכים ישירות מ-app.py — כך הסיכון לסוכנים החיים ממוזער. נותרו ~73 כלים בפרוסות הבאות.
- **סטטוס חלקי (פרוסה 6, 2026-06-06):** 🔄 **GAP-48 — מיגרציה רוחבית.** הומרו 10 משפחות נוספות ל-envelope: `precedent_library` (14), `citations` (3), `internal_decisions` (1), `missing_precedents` (4), `training_enrichment` (2), `precedents` (4), `legal_arguments` (2), `cases` (7), `documents` (8), `workflow` (9). בוטלו 5 עותקי `_ok`/`_err` משוכפלים (alias ל-SSoT, G2). עיקרון: envelope-`status` = הצלחת-הקריאה; תוצאה-עסקית (idempotent_existing/noop/...) ב-`data`. צרכני-app.py של cases/workflow/precedents חוּוטו דרך `envelope_unwrap` + בדיקת `status=="error"`→4xx, לשמירת חוזה-ה-API. כל הטסטים עוברים (182/182; `test_corpus_constraints` עודכן לחוזה). **נותר:** משפחת `drafting` (18 כלים — מסלול הפקת-ההחלטה) בפרוסה נפרדת.
- **פרוסה 7, 2026-06-06 — ✅ GAP-48 הושלם.** משפחת `drafting` (18 כלים) הומרה ל-envelope. export_docx/revise_draft/apply_user_edit משתמשים ב-`err`-לכשל (כך שהסוכן והמשתמש רואים את הכשל ברמת-המעטפת), כש-`failed_gates` רוכב ב-`data`; 6 צרכני-app.py (get_decision_template/apply_user_edit×2/revise_draft/list_bookmarks/export_docx) חוּוטו עם בדיקת envelope-status; `test_export_qa_gate` עודכן לחוזה (182/182 עוברים). **GAP-48 סגור — כל ~12 המשפחות אחידות.** נותר ב-FU-14: GAP-49/50 (מיזוג+rename — שובר), GAP-54 (איחוד קליטת-פסיקה), GAP-47-חלק-ב (הנחיות-יו"ר→DB).
- **פרוסה 7, 2026-06-06 — ✅ GAP-48 הושלם.** משפחת `drafting` (18 כלים) הומרה ל-envelope. export_docx/revise_draft/apply_user_edit משתמשים ב-`err`-לכשל (כך שהסוכן והמשתמש רואים את הכשל ברמת-המעטפת), כש-`failed_gates` רוכב ב-`data`; 6 צרכני-app.py (get_decision_template/apply_user_edit×2/revise_draft/list_bookmarks/export_docx) חוּוטו עם בדיקת envelope-status; `test_export_qa_gate` עודכן לחוזה (182/182 עוברים). **GAP-48 סגור — כל ~12 המשפחות אחידות.**
- **פרוסה 8, 2026-06-06 — ✅ GAP-49 (החלק הקריטי).** השם המטעה `precedent_search_library` (ציטוטים מצורפים-לתיק) שונה ל-`search_case_precedents` ובכך בוטל ההיפוך המסוכן מול `search_precedent_library` (ספרייה סמכותית — מקור CREAC). הישן נשמר כ-alias deprecated (ב-server.py) → אפס שבירה לסוכנים חיים. docstrings הובהרו; עודכנו app.py (typeahead) + legal-researcher/legal-writer docs + precedent_library docstring. 5 כלי-החיפוש הנותרים מחפשים קורפוסים מובחנים בשמות סבירים — לא בוצע rename-המוני (churn גבוה, ערך נמוך). 182/182 עוברים. **⚠ אחרי merge+deploy:** סנכרון cross-company של doc-הסוכן (frontmatter `search_case_precedents`). נותר ב-FU-14: GAP-50 (מיזוג כלי-בלוק — נוגע בתהליך-הכתיבה, דורש הכרעת-יו"ר), GAP-54, GAP-47-חלק-ב.
### FU-15 — deploy/env/secrets
- **מכסה:** GAP-55..62 · **invariants:** INV-ENV1ENV5 · **effort:** M · **תלויות:**

View File

@@ -170,13 +170,33 @@ async def precedent_remove(precedent_id: str) -> str:
return await precedents.precedent_remove(precedent_id)
@mcp.tool()
async def search_case_precedents(
query: str, practice_area: str = "", limit: int = 10,
) -> str:
"""חיפוש בציטוטי-פסיקה שדפנה צירפה ידנית לתיקים (טבלת case_precedents) —
קורפוס "case-attached". זה **לא** ספריית-הפסיקה הסמכותית.
GAP-49 (INV-TOOL2): שם קודם היה `precedent_search_library` — הפוך וכמעט-זהה
ל-`search_precedent_library` (הספרייה הסמכותית), מה שסיכן ציטוט מהמקור הלא-נכון.
אל תצטט מכאן כמקור-סמכות ל-CREAC; לזה השתמש ב-`search_precedent_library`.
Args:
query: מחרוזת חיפוש (מול citation ו-quote)
practice_area: סינון תחום משפטי (אופציונלי)
limit: תקרת תוצאות
"""
return await precedents.search_case_precedents(query, practice_area, _clamp_limit(limit))
@mcp.tool()
async def precedent_search_library(
query: str, practice_area: str = "", limit: int = 10,
) -> str:
"""חיפוש בציטוטים שדפנה צירפה ידנית לתיקים בעבר (case_precedents).
שונה מ-search_precedent_library שמחפש בקורפוס הפסיקה הסמכותית."""
return await precedents.precedent_search_library(query, practice_area, _clamp_limit(limit))
"""DEPRECATED (GAP-49) — שם-מטעה. השתמש ב-`search_case_precedents` (ציטוטים
מצורפים-לתיק) או ב-`search_precedent_library` (ספריית-הפסיקה הסמכותית).
Alias זמני לתאימות-לאחור — מנתב ל-search_case_precedents."""
return await precedents.search_case_precedents(query, practice_area, _clamp_limit(limit))
# ── External Precedent Library — authoritative case-law corpus ─────

View File

@@ -3,7 +3,8 @@
This is distinct from:
- ``precedents`` (case_precedents table) — chair-attached quotes scoped to
a specific case section. Use ``precedent_search_library`` for that.
a specific case section. Use ``search_case_precedents`` for that (GAP-49:
renamed from the misleading ``precedent_search_library``).
- ``style_corpus`` (Daphna's prior decisions) — searched via
``search_decisions`` for style/voice.

View File

@@ -83,10 +83,12 @@ async def precedent_remove(precedent_id: str) -> str:
return ok({"deleted": deleted, "precedent_id": precedent_id})
async def precedent_search_library(
async def search_case_precedents(
query: str, practice_area: str = "", limit: int = 10,
) -> str:
"""חיפוש בספרייה הרוחבית — כל הפסיקות שצורפו אי-פעם בכל התיקים.
"""חיפוש רוחבי בציטוטי-הפסיקה שצורפו ידנית לתיקים (case_precedents) — קורפוס
"case-attached". GAP-49 (INV-TOOL2): שם קודם `precedent_search_library` (מטעה).
זו **אינה** ספריית-הפסיקה הסמכותית — לזו השתמש ב-`search_precedent_library`.
Args:
query: מחרוזת חיפוש (מתחרה מול citation ומול quote)

View File

@@ -3049,7 +3049,7 @@ async def api_precedent_delete(precedent_id: str):
@app.get("/api/precedents/search")
async def api_precedent_search(q: str, practice_area: str = "", limit: int = 10):
"""Cross-case library typeahead. Returns one row per distinct citation."""
result = await precedents_tools.precedent_search_library(q, practice_area, limit)
result = await precedents_tools.search_case_precedents(q, practice_area, limit) # GAP-49 rename
parsed = json.loads(result) # GAP-48: typeahead expects an array
if isinstance(parsed, dict) and parsed.get("status") == "error":
raise HTTPException(400, parsed.get("message") or "")