diff --git a/.claude/agents/legal-researcher.md b/.claude/agents/legal-researcher.md index f228e3a..2c2f1e4 100644 --- a/.claude/agents/legal-researcher.md +++ b/.claude/agents/legal-researcher.md @@ -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 ("טוענים שמופיע" ≠ "אומת") diff --git a/.claude/agents/legal-writer.md b/.claude/agents/legal-writer.md index 546d9b5..0cd8de6 100644 --- a/.claude/agents/legal-writer.md +++ b/.claude/agents/legal-writer.md @@ -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` בלבד. diff --git a/docs/spec/X9-mcp-tool-contract.md b/docs/spec/X9-mcp-tool-contract.md index 6754516..8b25cad 100644 --- a/docs/spec/X9-mcp-tool-contract.md +++ b/docs/spec/X9-mcp-tool-contract.md @@ -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 על מפתח דטרמיניסטי** — קריאה חוזרת אינה יוצרת כפילות. מופע של diff --git a/docs/spec/gap-audit.md b/docs/spec/gap-audit.md index 5fb671e..8688284 100644 --- a/docs/spec/gap-audit.md +++ b/docs/spec/gap-audit.md @@ -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-ENV1–ENV5 · **effort:** M · **תלויות:** — diff --git a/mcp-server/src/legal_mcp/server.py b/mcp-server/src/legal_mcp/server.py index 0e35bee..e3a4279 100644 --- a/mcp-server/src/legal_mcp/server.py +++ b/mcp-server/src/legal_mcp/server.py @@ -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 ───── diff --git a/mcp-server/src/legal_mcp/tools/precedent_library.py b/mcp-server/src/legal_mcp/tools/precedent_library.py index 2c1b719..70eca08 100644 --- a/mcp-server/src/legal_mcp/tools/precedent_library.py +++ b/mcp-server/src/legal_mcp/tools/precedent_library.py @@ -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. diff --git a/mcp-server/src/legal_mcp/tools/precedents.py b/mcp-server/src/legal_mcp/tools/precedents.py index 68e2428..d0ee3c8 100644 --- a/mcp-server/src/legal_mcp/tools/precedents.py +++ b/mcp-server/src/legal_mcp/tools/precedents.py @@ -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) diff --git a/web/app.py b/web/app.py index ec54b19..bdfe4a4 100644 --- a/web/app.py +++ b/web/app.py @@ -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 "")