From ea0532b7ba55b684e55116d844bb3ddc8a1508b9 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 17 May 2026 11:08:14 +0000 Subject: [PATCH] fix: weekly-feedback-job handler writes to file only (no Paperclip issue) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CEO wakes for weekly-feedback-job via agents.invoke without issueId, so $PAPERCLIP_TASK_ID is empty. Removed steps 4-5 (comment + close issue) from handler — now file-write only with stdout logging. Also commits pending docs and agent instructions from prior session. Co-Authored-By: Claude Sonnet 4.6 --- .claude/agents/legal-ceo.md | 5 +- CLAUDE.md | 22 +- mcp-server/src/legal_mcp/services/lessons.py | 2 + skills/decision/SKILL.md | 10 + skills/docx/SKILL.md | 531 +------------------ web-ui/AGENTS.md | 236 +++++++++ 6 files changed, 292 insertions(+), 514 deletions(-) diff --git a/.claude/agents/legal-ceo.md b/.claude/agents/legal-ceo.md index 84db590..9c43706 100644 --- a/.claude/agents/legal-ceo.md +++ b/.claude/agents/legal-ceo.md @@ -202,8 +202,9 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru 1. **קרא את `docs/legal-decision-lessons.md`** — הבן מה כבר מתועד שם. 2. **נתח את הפידבק** — אילו דפוסים חוזרים? מה חדש שלא מופיע בלקחים? 3. **עדכן את `docs/legal-decision-lessons.md`** — הוסף רק לקחים חדשים ומהותיים (לא כפל). כל לקח = משפט אחד ברור. -4. **פרסם comment על ה-issue** עם סיכום: כמה פידבק נסקר, כמה לקחים חדשים נוספו, מה הדפוס הבולט השבוע. -5. **סגור את ה-issue** (`status=done`). +4. **רשום ל-stdout** (לא ל-issue): `echo "weekly feedback done: N lessons added"` — החלף N במספר הלקחים שנוספו. + +⚠️ **אין issue ב-Paperclip עבור job זה** — `$PAPERCLIP_TASK_ID` ריק. אל תנסה לפרסם comment ואל תנסה לסגור issue. הפעולה מסתיימת לאחר כתיבת הקובץ. **כלל:** אל תגע בתיקים פעילים, אל תעיר סוכנים אחרים, אל תבצע heartbeat רגיל — זו משימת תחזוקה בלבד. diff --git a/CLAUDE.md b/CLAUDE.md index 73e89d1..8de3461 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,6 +56,8 @@ | [`docs/decision-block-mapping.md`](docs/decision-block-mapping.md) | מיפוי בלוקים להחלטות — איך 12 הבלוקים משתקפים ב-DOCX | להתמצאות במבנה | | [`docs/memory.md`](docs/memory.md) | הקשר כללי — skills, פרויקטים שהושלמו, מבנה vault | להתמצאות כללית | | [`skills/decision/SKILL.md`](skills/decision/SKILL.md) | מדריך סגנון מלא של דפנה — טון, מבנה, ביטויים, מתודולוגיה | **לפני כל כתיבת החלטה** | +| [`.claude/agents/HEARTBEAT.md`](.claude/agents/HEARTBEAT.md) | checklist הפעלת סוכן — routing, company filtering, quirks, wakeup עם UUID נכון | **לפני כל עבודה על סוכנים** | +| [`skills/dafna-decision-template/SKILL.md`](skills/dafna-decision-template/SKILL.md) | export DOCX לפי styles של תבנית Word של דפנה — line classification, dash policy, placeholder handling | לפני export DOCX | --- @@ -106,7 +108,20 @@ ├── skills/ ← כלי עבודה ומדריכים │ ├── decision/ מדריך סגנון + references + 12 בלוקים │ ├── assistant/ קטלוג מסמכים -│ └── docx/ עיצוב DOCX +│ ├── docx/ עיצוב DOCX +│ ├── dafna-decision-template/ export DOCX לפי תבנית Word של דפנה +│ └── new-company-setup/ blueprint הוספת חברה חדשה +├── .claude/ +│ └── agents/ ← הוראות סוכנים + HEARTBEAT.md (symlinks ב-Paperclip) +│ ├── HEARTBEAT.md checklist הפעלה משותף לכל הסוכנים +│ ├── legal-ceo.md תזמורן + בקרת זרימה +│ ├── legal-writer.md כתיבת בלוקים בסגנון דפנה +│ ├── legal-analyst.md ניתוח משפטי + חילוץ טענות +│ ├── legal-researcher.md חיפוש תקדימים +│ ├── legal-qa.md 7 שערי איכות +│ ├── legal-proofreader.md תיקון OCR +│ ├── legal-exporter.md ייצוא DOCX סופי +│ └── hermes-curator.md סוכן Hermes לניתוח סגנון post-export ├── data/ │ ├── training/ ← 4 החלטות לאימון (DOCX) │ ├── exports/ ← טיוטות DOCX מיוצאות @@ -191,6 +206,11 @@ - **⚠ Cross-company sync**: `sync_agents_across_companies.py` **מדלג** על סוכנים עם `adapter_type` שונה בין CMP ל-CMPA. כשעוברים סוכן ל-`deepseek_local` חובה להחיל ידנית בשתי החברות לפני sync. - **תוספת adapters עתידיים** (OpenAI ישיר, Anthropic ישיר, וכו'): אותו דפוס. ה-package הראשי חייב לייצא `createServerAdapter()` שמחזיר `{ type, label, models, agentConfigurationDoc, execute, testEnvironment, sessionCodec, listSkills, syncSkills, ... }`. ראה את [adapters/deepseek-paperclip-adapter/dist/index.js](adapters/deepseek-paperclip-adapter/dist/index.js) כתבנית. +### External adapters — Hermes Curator (`curator-cmp` / `curator-cmpa`) +- פרופילי Hermes נפרדים לסוכן `hermes-curator` — מנתח החלטות סופיות ומציע עדכוני SKILL.md/lessons.md +- מיקום: `~/.hermes/profiles/curator-cmp/` + `~/.hermes/profiles/curator-cmpa/` +- מופעל אחרי export סופי; אינו מעדכן קבצים ישירות + --- ## עקרונות כתיבה קריטיים diff --git a/mcp-server/src/legal_mcp/services/lessons.py b/mcp-server/src/legal_mcp/services/lessons.py index e1fc91b..dc273cf 100644 --- a/mcp-server/src/legal_mcp/services/lessons.py +++ b/mcp-server/src/legal_mcp/services/lessons.py @@ -485,6 +485,7 @@ CONTENT_CHECKLISTS: dict[str, str] = { - שווי מקרקעין — מצב קודם ומצב חדש (שיטת השוואה / יחידות תועלת) - עלויות עודפות (חניה, מטלות ציבוריות, תשתיות) - מקדמי זמינות, שיעורי הפקעה +- הכרעה מפוצלת (bifurcation) — כשהוועדה מאשרת חבות אך ממנה שמאי מייעץ: ביטויי גישור ("ניתן יהיה לעלות בפני השמאי המייעץ"), נוסחת מינוי, הפניה לתקנות סדרי דין התשס"ט-2008, הוראות המשך (30 יום להשגות). ללא סיכום — ישירות לחתימה. ראה: 8070/25 ### ד. שאלות משפטיות (לפי רלוונטיות) - פטורים — דירת מגורים (ס' 19(ג)(1)), שטח עד 140 מ"ר, תא משפחתי @@ -493,6 +494,7 @@ CONTENT_CHECKLISTS: dict[str, str] = { - מקרקעי ישראל — הסדרים מיוחדים (ס' 21 לתוספת השלישית) - שומות מוסכמות — תוקף, משמעות, "בלתי נצפה מראש" - פרשנות תכניות — ייעוד, שימושים מותרים, מדיניות ועדה מקומית +- טענת "תכנית צל = זכות מוקנית" — ניתוח תלת-שכבתי: (1) נורמטיבית — תכנית צל = המחשה, לא מקור נורמטיבי; (2) פרוצדורלית — הקלה ניתנת פר-מבקש, לא זכות כללית; (3) שמאית — משקל הסתברותי בהערכת ההשבחה, לא במישור המשפטי. ראה: 8070/25 ### ה. ניתוח שמאי (כשיש שומה מכרעת) - האם השומה מבוססת על מסד עובדתי הולם? diff --git a/skills/decision/SKILL.md b/skills/decision/SKILL.md index 95c055d..9ffd6b7 100644 --- a/skills/decision/SKILL.md +++ b/skills/decision/SKILL.md @@ -283,6 +283,16 @@ description: This skill should be used when writing legal decisions (החלטו **ערר היטל השבחה:** פתיחה ישירה עם מסקנה. ניתוח ישיר - ציטוטי פסיקה מרובים. סיום יבש. +**ערר היטל השבחה — הכרעה מפוצלת (שמאי מייעץ):** +תת-מסלול חוזר: הוועדה מאשרת את עצם החבות אך אינה קובעת את גובה ההיטל — ממנה שמאי מייעץ. פתיחה: זהה למסלול הכללי — "עניינו של ערר זה בדרישת תשלום היטל השבחה...". ללא סיפור תכנוני רחב. דיון — שלב משפטי: הכרעה בשאלת עצם החבות. ניתוח הטענה המרכזית (בד"כ "זכות מוקנית") מול ההקלה שהתבקשה. ציטוטי פסיקה inline — הפניה להחלטות ועדת ערר קודמות ללא בלוק ציטוט מלא. ביטויי מפתח: "אנו סבורים כי...", "בניגוד לעמדת העורר...", "לא ניתן לטעון כי...". מעבר לשלב השמאי — 3 ביטויי גישור: (א) "בכל הנוגע לטענות לגבי מקדמים... וכל טענה בעלת אופי שמאי **ניתן יהיה לעלות בפני השמאי המייעץ**." (ב) "על כן, לאור האמור **אנו ממנים שמאי מייעץ** אשר יערוך שומה להערכת ההשבחה במקרקעין כתוצאה מאישור ההקלה..." (ג) "השמאי המייעץ ינהל את הדיון **בהתאם לתקנות התכנון והבניה (סדרי דין בבקשה להכרעה לפני שמאי מכריע או שמאי מייעץ), התשס"ט-2008**." — נוסחה קבועה. הוראות המשך: "לאחר קבלת השומה המייעצת יהיו רשאים הצדדים להגיש את השגותיהם בתוך 30 יום לוועדת הערר ולאחר מכן תתקבל החלטה באשר לאופן קידום ההליך." סיום: **ללא** כותרת "סיכום" / "סוף דבר" — זורם ישירות מהוראות המינוי לחתימה "ניתנה פה אחד היום...". הוצאות: לא מוזכרות (ההליך טרם הסתיים). ראה: נווה יעקב 8070/25. + +**ערר היטל השבחה — מסגרת תלת-שכבתית לניתוח "תכנית צל":** +כשעורר טוען ש"תכנית צל" מאושרת הופכת זכויות להקלה לזכויות מוקנות, הניתוח מתבצע בשלוש שכבות נפרדות: +*שכבה 1 — נורמטיבית* (שלילת המעמד המשפטי): "תכנית צל אינה מקור נורמטיבי לאישור זכויות... אינה 'מבטיחה' אישור זכויות עבור השכן." ביטויי מפתח: "תכנית צל הינה תכנית המבקשת... להראות היתכנות בניה על ידי יתר בעלי הזכויות ו/או השלכת הבניה עליהם, על הבניין ועל הסביבה." כלל: תכנית צל = המחשה, לא מקור נורמטיבי. +*שכבה 2 — פרוצדורלית* (ההקלה ניתנת פר-מבקש): "גם בהיתר חבקין התבקשה הקלה שאושרה, הקלה אך ורק להיתר שהתבקש ולזכויות שהתבקשו מכוחו. ההקלה לא התבקשה עבור כל דיירי הבניין... בקשה להקלה ופרסומה יש בצידם שיקול דעת... באופן ייחודי לכל בקשה לגופה." כלל: אישור הקלה לדייר א' ≠ זכות מוקנית לדייר ב'. +*שכבה 3 — שמאית* (הכרה בערך ראייתי, ניתוב למישור הנכון): "העובדה שאושרה תכנית צל יש בה מידת וודאות גבוהה יותר באשר לסיכוי כי תאושר ההקלה... ולכך **משקל שמאי** בהערכת ההשבחה." כלל: תכנית צל משפיעה על **ההסתברות** (מישור שמאי), לא על **הזכות** (מישור משפטי). +סדר הניתוח: תמיד שכבה 1 → 2 → 3. לא לדלג — גם אם שכבה 1 מכריעה, יש ערך בכל שלוש לביסוס ולמניעת ערעור. ראה: נווה יעקב 8070/25. + **ערר רישוי שמתקבל חלקית — מסלול מיפוי מתחים + ניתוח נושאי:** פתיחה במיפוי מתחים (3-6 סעיפים): הקשר כללי קצר (1-2 פסקאות), רשימת נקודות מתח ספציפיות בתיק (4-6 בולטים), מעבר לניתוח. אין שימוש בשכבות/עיגולים קונצנטריים — ניתוח לפי נושאים: כל נושא מקבל טיפול מלא (הצגה → ציטוט הוראות תכנית → פסיקה → מסקנה). נושא חניה/תשתיות מקבל טיפול מעמיק במיוחד עם ציטוטים ישירים מהוראות תכנית ונספחים. טענות ספציפיות (מטרדים, עצים, בור מים) — 1-2 סעיפים תמציתיים לכל אחת. סיכום מינימלי — רק הוראות אופרטיביות (2-3 סעיפים). ראה: בית הכרם 1126/25. diff --git a/skills/docx/SKILL.md b/skills/docx/SKILL.md index 7f4a650..4b68a9c 100644 --- a/skills/docx/SKILL.md +++ b/skills/docx/SKILL.md @@ -252,82 +252,10 @@ new Table({ ## Tracked Changes — עקוב אחר שינויים -### שם מחבר בעברית -```xml - -``` +ראה [`references/tracked-changes.md`](references/tracked-changes.md) — XML patterns לשינוי ערך, מחיקת סעיף, RTL PROPS, קבלה/דחייה. -### שינוי ערך (סכום, תאריך, תקופה) -פצל את הטקסט ועטוף רק את הערך שמשתנה: -```xml -...RTL PROPS... - שכר הטרחה יעמוד על סך של - - ...RTL PROPS...750 - - - ...RTL PROPS...850 - -...RTL PROPS... - ש״ח לשעת עבודה -``` - -### מחיקת סעיף שלם -סמן גם את ה-paragraph mark כ-deleted: -```xml - - - - - - - - - - ...RTL PROPS... - הסעיף שנמחק - - -``` - -### RTL PROPS — בלוק rPr מלא לכל run -```xml - - - - - - -``` - -### קבלה/דחייה של שינויים - -**קבלת Insertion:** -``` -לפני: ...טקסט חדש -אחרי: ...טקסט חדש -→ הסר את תגית ושמור את התוכן הפנימי. -``` - -**דחיית Insertion:** -``` -לפני: ...טקסט חדש -אחרי: (הסר לחלוטין) -→ מחק את כל בלוק ה- כולל תוכנו. -``` - -**קבלת מחיקה:** -``` -לפני: ...טקסט שנמחק -אחרי: (הסר לחלוטין) -→ מחק את כל בלוק ה- כולל תוכנו — המחיקה מתקבלת. -``` - -**שחזור טקסט מקורי (דחיית מחיקה):** -``` -לפני: ...טקסט מקורי -אחרי: ...טקסט מקורי -→ הסר , החלף ב-, הסר מ-rPr אם קיים. +```bash +python /mnt/skills/public/docx/scripts/comment.py unpacked/ 0 "הערה" --author "עו״ד כהן" ``` --- @@ -397,72 +325,6 @@ python /mnt/skills/public/docx/scripts/pack.py unpacked/ output.docx --original --- -## הערות שוליים (Footnotes) - -**השימוש המרכזי:** הפניות לחקיקה ופסיקה. - -```javascript -const { FootnoteReferenceRun } = require('docx'); - -// 1. הגדרה ב-Document: -const doc = new Document({ - footnotes: { - 1: { children: [new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, // ✅ START - children: [new TextRun({ - text: "חוק החוזים (חלק כללי), התשל״ג-1973, סעיף 12.", - font: "David", size: 20, rightToLeft: true // 10pt להערות שוליים - })] - })] }, - 2: { children: [new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - children: [new TextRun({ - text: "ע״א 1234/20 כהן נ׳ לוי, פסקה 15 (פורסם בנבו, 1.1.2024).", - font: "David", size: 20, rightToLeft: true - })] - })] }, - }, - // ...sections -}); - -// 2. הפניה בגוף הטקסט: -new Paragraph({ - bidirectional: true, alignment: AlignmentType.BOTH, - children: [ - new TextRun({ text: "חובת תום הלב", font: "David", size: 24, rightToLeft: true }), - new FootnoteReferenceRun(1), - new TextRun({ text: " חלה על כל שלבי המשא ומתן", font: "David", size: 24, rightToLeft: true }), - new FootnoteReferenceRun(2), - new TextRun({ text: ".", font: "David", size: 24, rightToLeft: true }), - ] -}) -``` - -### תיקון RTL בהערות שוליים (post-unpack) -docx-js לא מגדיר RTL מלא בהערות שוליים. אחרי unpack, צריך לתקן ב-`word/footnotes.xml`: -```xml - - - - - - - - - ... - - - - - - - - - -``` - ---- - ## מרווח שורות (Line Spacing) **דרישת בתי המשפט:** בדרך כלל 1.5 שורות. @@ -482,48 +344,6 @@ spacing: { line: 360, lineRule: LineRuleType.AUTO, before: 120, after: 120 } --- -## תוכן עניינים (TOC) - -**⚠️ חובה: TOC ידני (לא TableOfContents).** -`TableOfContents` של docx-js מייצר שדה שוורד מעדכן ב-F9 ומאבד הגדרות RTL. - -```javascript -const { Tab, TabStopType, LeaderType, PageBreak } = require('docx'); - -// שורת TOC ידנית -const tocEntry = (text, pageNum, opts = {}) => new Paragraph({ - bidirectional: true, - spacing: { after: 60, line: 276, lineRule: LineRuleType.AUTO }, - ...(opts.indent ? { indent: { right: opts.indent } } : {}), - tabStops: [{ type: TabStopType.RIGHT, position: 9026, leader: LeaderType.DOT }], - children: [ - new TextRun({ - text, font: "David", size: 24, rightToLeft: true, - bold: opts.bold || false, - }), - new TextRun({ children: [new Tab()], font: "David", rightToLeft: true }), - new TextRun({ - text: String(pageNum), font: "David", size: 24, rightToLeft: true, - }), - ] -}); - -// שימוש: -new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - spacing: { after: 200 }, - children: [new TextRun({ - text: "תוכן עניינים", font: "David", size: 32, bold: true, rightToLeft: true - })] -}), -tocEntry("פרק א׳ — הגדרות כלליות", 2, { bold: true }), -tocEntry("1. הגדרות יסוד", 2, { indent: 400 }), -tocEntry("פרק ב׳ — השירותים", 3, { bold: true }), -new Paragraph({ children: [new PageBreak()] }), -``` - ---- - ## קו תחתי (Underline) ```javascript @@ -544,337 +364,23 @@ underline: { type: UnderlineType.DOUBLE } --- -## מספר סקשנים (Multiple Sections) +## פיצ'רים מתקדמים -**שימוש:** כותרות שונות לנספחים, עמוד לרוחב לטבלאות, שוליים שונים. - -```javascript -const doc = new Document({ - sections: [ - // סקשן 1 — גוף ההסכם - { - properties: { - page: { size: { width: 11906, height: 16838 }, - margin: { top: 1417, right: 1417, bottom: 1417, left: 1417 } }, - bidi: true, - }, - headers: { - default: new Header({ children: [new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - children: [new TextRun({ text: "הסכם שירותים", font: "David", size: 20, bold: true, rightToLeft: true })] - })] }) - }, - children: [ /* ... */ ] - }, - // סקשן 2 — נספח עם כותרת שונה - { - properties: { - page: { size: { width: 11906, height: 16838 }, - margin: { top: 1417, right: 1417, bottom: 1417, left: 1417 } }, - bidi: true, - }, - headers: { - default: new Header({ children: [new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, // ✅ START - children: [new TextRun({ text: "נספח א׳ — לוח תעריפים", font: "David", size: 20, bold: true, rightToLeft: true })] - })] }) - }, - children: [ /* ... */ ] - } - ] -}); -``` +ראה [`references/advanced-features.md`](references/advanced-features.md): +- **הערות שוליים** — Footnotes עם RTL + תיקון post-unpack ב-footnotes.xml +- **תוכן עניינים** — TOC ידני (אסור `TableOfContents`) +- **מספר סקשנים** — כותרות שונות לנספחים +- **Letterhead** — לוגו/תמונה בכותרת +- **היפרלינקים** — `ExternalHyperlink` עם color+underline ידני (לא `style: "Hyperlink"`) --- -## לוגו/תמונה בכותרת (Letterhead) +## תבניות מסמכים -```javascript -const { ImageRun } = require('docx'); - -const logoBuffer = fs.readFileSync('/path/to/logo.png'); - -headers: { - default: new Header({ - children: [ - new Paragraph({ - alignment: AlignmentType.CENTER, - children: [ - new ImageRun({ - data: logoBuffer, - transformation: { width: 200, height: 60 }, // pixels - type: "png", - }), - ], - }), - new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - children: [new TextRun({ - text: "משרד עורכי דין ישראלי ושות׳", - font: "David", size: 20, bold: true, rightToLeft: true - })], - }), - ], - }), -} -``` - -**הערה:** תמונה חייבת להיות קובץ אמיתי — לבקש מהמשתמש אם אין. - ---- - -## היפרלינקים - -```javascript -const { ExternalHyperlink, UnderlineType } = require('docx'); - -new Paragraph({ - bidirectional: true, - children: [ - new TextRun({ text: "ראה: ", font: "David", size: 24, rightToLeft: true }), - new ExternalHyperlink({ - link: "https://www.nevo.co.il/law_html/law01/073_002.htm", - children: [new TextRun({ - text: "חוק החוזים באתר נבו", - font: "David", size: 24, rightToLeft: true, - color: "0563C1", - underline: { type: UnderlineType.SINGLE }, - })], - }), - ] -}) -``` - -**⚠️ אזהרות:** -- **לא להשתמש ב-`style: "Hyperlink"`** — מפריע ל-RTL! -- **לא להוסיף `alignment: AlignmentType.RIGHT`** — `bidirectional: true` מספיק - ---- - -## תבניות מסמכים — Document Templates - -### תבנית 1: כתב טענות (בקשה, תביעה, הגנה, ערעור) - -```javascript -const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, - AlignmentType, LevelFormat, BorderStyle, WidthType } = require('docx'); - -const PAGE_WIDTH = 11906; -const MARGINS = { top: 1134, right: 1134, bottom: 1134, left: 1134 }; -const CONTENT_WIDTH = PAGE_WIDTH - MARGINS.left - MARGINS.right; - -const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }; -const noBorders = { top: noBorder, bottom: noBorder, left: noBorder, right: noBorder }; - -// Header בית משפט — טבלה עם שם בית המשפט (ימין) ומספר תיק (שמאל) -function courtHeader(courtName, caseNumber) { - return new Table({ - width: { size: CONTENT_WIDTH, type: WidthType.DXA }, - columnWidths: [CONTENT_WIDTH / 2, CONTENT_WIDTH / 2], - visuallyRightToLeft: true, - rows: [ - new TableRow({ - children: [ - new TableCell({ - width: { size: CONTENT_WIDTH / 2, type: WidthType.DXA }, - borders: noBorders, - children: [new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - children: [new TextRun({ text: courtName, bold: true, font: "David", size: 26, rightToLeft: true })] - })] - }), - new TableCell({ - width: { size: CONTENT_WIDTH / 2, type: WidthType.DXA }, - borders: noBorders, - children: [new Paragraph({ - bidirectional: true, alignment: AlignmentType.END, - children: [new TextRun({ text: caseNumber, bold: true, font: "David", size: 26, rightToLeft: true })] - })] - }) - ] - }) - ] - }); -} - -// כותרת ראשית ממורכזת עם קו תחתון -function mainTitle(text) { - return new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - spacing: { before: 300, after: 300 }, - children: [new TextRun({ text, bold: true, font: "David", size: 28, rightToLeft: true, underline: {} })] - }); -} - -// כותרת משנה מיושרת לימין עם קו תחתון -function subHeading(text) { - return new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - spacing: { before: 240, after: 120 }, - children: [new TextRun({ text, bold: true, font: "David", size: 24, rightToLeft: true, underline: {} })] - }); -} - -// שימוש: -const doc = new Document({ - numbering: { - config: [{ - reference: "legal-clauses", - levels: [{ - level: 0, format: LevelFormat.DECIMAL, text: "%1.", - alignment: AlignmentType.START, suffix: "tab", - style: { paragraph: { indent: { left: 360, hanging: 360 } } } - }] - }] - }, - sections: [{ - properties: { - page: { size: { width: PAGE_WIDTH, height: 16838 }, margin: MARGINS }, - bidi: true - }, - children: [ - courtHeader("בית המשפט המחוזי בתל אביב", "ת\"א 12345-01-26"), - mainTitle("כתב תביעה"), - // ... פרטי צדדים, סעיפים, חתימה - ] - }] -}); -``` - -### תבנית 2: מכתב התראה - -```javascript -// מכתב התראה — ללא header בית משפט, עם פרטי משרד - -function letterHeader(firmName, address, phone, email) { - return [ - new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - children: [new TextRun({ text: firmName, bold: true, font: "David", size: 28, rightToLeft: true })] - }), - new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - children: [new TextRun({ text: address, font: "David", size: 22, rightToLeft: true })] - }), - new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - spacing: { after: 300 }, - children: [new TextRun({ text: `טל': ${phone} | ${email}`, font: "David", size: 22, rightToLeft: true })] - }), - ]; -} - -function subjectLine(text) { - return new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - spacing: { before: 200, after: 200 }, - children: [ - new TextRun({ text: "הנדון: ", bold: true, font: "David", size: 24, rightToLeft: true }), - new TextRun({ text, bold: true, font: "David", size: 24, rightToLeft: true, underline: {} }) - ] - }); -} - -// שימוש: -sections: [{ - properties: { page: { ... }, bidi: true }, - children: [ - ...letterHeader("משרד עו\"ד כהן ושות'", "רח' הרצל 1, תל אביב", "03-1234567", "office@cohen-law.co.il"), - new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - children: [new TextRun({ text: "תאריך: 10.2.2026", font: "David", size: 24, rightToLeft: true })] - }), - new Paragraph({ - bidirectional: true, alignment: AlignmentType.START, - spacing: { before: 200 }, - children: [new TextRun({ text: "לכבוד: [שם הנמען]", font: "David", size: 24, rightToLeft: true })] - }), - subjectLine("התראה בטרם נקיטת הליכים משפטיים"), - // ... גוף המכתב - ] -}] -``` - -### תבנית 3: הסכם/חוזה - -```javascript -// הסכם — הואילים, צדדים, חתימות בשני טורים - -function contractTitle(text) { - return new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - spacing: { after: 300 }, - children: [new TextRun({ text, bold: true, font: "David", size: 32, rightToLeft: true })] - }); -} - -function partyClause(label, name, id, address, alias) { - return new Paragraph({ - bidirectional: true, alignment: AlignmentType.BOTH, - spacing: { after: 120 }, - children: [ - new TextRun({ text: `${label}: `, bold: true, font: "David", size: 24, rightToLeft: true }), - new TextRun({ text: `${name}, ח.פ./ת.ז. ${id}, מ${address} (להלן: "`, font: "David", size: 24, rightToLeft: true }), - new TextRun({ text: alias, bold: true, font: "David", size: 24, rightToLeft: true }), - new TextRun({ text: '")', font: "David", size: 24, rightToLeft: true }), - ] - }); -} - -function signatureTable() { - return new Table({ - width: { size: CONTENT_WIDTH, type: WidthType.DXA }, - columnWidths: [CONTENT_WIDTH / 2, CONTENT_WIDTH / 2], - visuallyRightToLeft: true, - rows: [ - new TableRow({ - children: [ - new TableCell({ - borders: noBorders, - children: [ - new Paragraph({ bidirectional: true, alignment: AlignmentType.CENTER, - children: [new TextRun({ text: "_________________", font: "David", size: 24, rightToLeft: true })] }), - new Paragraph({ bidirectional: true, alignment: AlignmentType.CENTER, - children: [new TextRun({ text: "צד א'", font: "David", size: 24, rightToLeft: true })] }) - ] - }), - new TableCell({ - borders: noBorders, - children: [ - new Paragraph({ bidirectional: true, alignment: AlignmentType.CENTER, - children: [new TextRun({ text: "_________________", font: "David", size: 24, rightToLeft: true })] }), - new Paragraph({ bidirectional: true, alignment: AlignmentType.CENTER, - children: [new TextRun({ text: "צד ב'", font: "David", size: 24, rightToLeft: true })] }) - ] - }) - ] - }) - ] - }); -} - -// שימוש: -sections: [{ - properties: { page: { ... }, bidi: true }, - children: [ - contractTitle("הסכם שירותים"), - new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - children: [new TextRun({ text: "נערך ונחתם בתל אביב ביום __________", font: "David", size: 24, rightToLeft: true })] - }), - partyClause("מצד אחד", "[שם]", "[מספר]", "[כתובת]", "המזמין"), - partyClause("מצד שני", "[שם]", "[מספר]", "[כתובת]", "הספק"), - // הואילים... - // סעיפים... - new Paragraph({ - bidirectional: true, alignment: AlignmentType.CENTER, - spacing: { before: 400, after: 300 }, - children: [new TextRun({ text: "ולראיה באו הצדדים על החתום:", bold: true, font: "David", size: 24, rightToLeft: true })] - }), - signatureTable() - ] -}] -``` +ראה [`references/document-templates.md`](references/document-templates.md): +- **תבנית 1: כתב טענות** — `courtHeader()`, `mainTitle()`, `subHeading()` + מספור +- **תבנית 2: מכתב התראה** — `letterHeader()`, `subjectLine()` + פרטי משרד +- **תבנית 3: הסכם/חוזה** — `contractTitle()`, `partyClause()`, `signatureTable()` + הואילים --- @@ -962,8 +468,11 @@ sections: [{ ## קבצי עזר -- **`references/document-types.md`** — מבנים מפורטים ל-9 סוגי מסמכים משפטיים -- **`scripts/create-legal-doc.js`** — סקריפט בסיסי עם כל הגדרות ה-RTL המתוקנות +- **[`references/document-types.md`](references/document-types.md)** — מבנים מפורטים ל-9 סוגי מסמכים +- **[`references/document-templates.md`](references/document-templates.md)** — 3 תבניות מלאות (כתב טענות, מכתב, הסכם) +- **[`references/tracked-changes.md`](references/tracked-changes.md)** — XML patterns לעקוב אחר שינויים +- **[`references/advanced-features.md`](references/advanced-features.md)** — הערות שוליים, TOC, סקשנים, letterhead, hyperlinks +- **`scripts/create-legal-doc.js`** — סקריפט בסיסי עם כל הגדרות RTL --- diff --git a/web-ui/AGENTS.md b/web-ui/AGENTS.md index 8bd0e39..ee0ee84 100644 --- a/web-ui/AGENTS.md +++ b/web-ui/AGENTS.md @@ -3,3 +3,239 @@ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + +--- + +## Stack + +| Layer | Technology | Version | +|-------|-----------|---------| +| Framework | Next.js | 16.2.3 | +| UI | React | 19.2.4 | +| Styles | Tailwind CSS | v4 | +| Components | shadcn/ui | latest via `shadcn` CLI | +| Data fetching | TanStack Query | v5 | +| Forms | react-hook-form + zod | v7 / v4 | +| Language | TypeScript | 5 | +| Direction | Hebrew RTL | `dir="rtl"` throughout | + +--- + +## Commands + +```bash +# Regenerate API types from the live FastAPI schema — RUN AFTER EVERY BACKEND CHANGE +npm run api:types + +# Validate before every push +npm run lint +npm run build + +# Local dev (rare — prod runs inside Docker; no local Python env exists) +npm run dev # requires NEXT_PUBLIC_API_ORIGIN=http://127.0.0.1:8000 or similar +``` + +**`npm run api:types` is mandatory** any time a FastAPI endpoint is added, removed, or its request/response shape changes. It fetches `https://legal-ai.nautilus.marcusgroup.org/openapi.json` and writes `src/lib/api/types.ts`. + +--- + +## Backend Proxy — `/api/*` + +`next.config.ts` transparently rewrites all `/api/*` requests to the FastAPI backend: + +- In Docker (production): `http://127.0.0.1:8000` +- Override via env var: `NEXT_PUBLIC_API_ORIGIN` + +**Never hardcode the backend origin in component code.** Always use relative paths like `/api/cases`. + +The typed fetch wrapper lives in `src/lib/api/client.ts` — use `apiRequest(path, options)`. It throws `ApiError` on non-2xx responses with the parsed body and status code. + +--- + +## API Types — Never Edit by Hand + +`src/lib/api/types.ts` is **auto-generated** by `openapi-typescript` from the live FastAPI OpenAPI schema. + +- **Do NOT edit `src/lib/api/types.ts` manually** — changes will be overwritten on the next `npm run api:types` run. +- The typed helper modules in `src/lib/api/` (e.g. `cases.ts`, `documents.ts`, `precedents.ts`) ARE hand-written and import from `types.ts`. These are safe to edit. +- When adding a new API domain, create a new typed module in `src/lib/api/.ts` following the existing pattern. + +--- + +## Tailwind CSS v4 — Breaking Changes from v3 + +Tailwind v4 has a completely different configuration model. + +**What does NOT exist in v4:** +- `tailwind.config.ts` / `tailwind.config.js` — there is no config file +- `@tailwind base;` / `@tailwind components;` / `@tailwind utilities;` directives +- `tailwind.config.theme.extend` object + +**What v4 uses instead:** +```css +/* globals.css — already set up, do not change */ +@import "tailwindcss"; +@import "tw-animate-css"; +@import "shadcn/tailwind.css"; + +@theme { + /* Design tokens defined here as CSS custom properties */ + --color-navy: #0f172a; + /* ... */ +} +``` + +- Custom tokens go inside `@theme {}` in `globals.css`. +- Custom variants use `@custom-variant`. +- Class names are the same (e.g. `bg-navy`, `text-gold`), but the config source is CSS, not JS. +- PostCSS is configured via `@tailwindcss/postcss` (devDependency). + +--- + +## shadcn/ui Components + +Adding a new component: +```bash +npx shadcn add +# e.g. npx shadcn add table +``` + +Installed components live in `src/components/ui/`. They are editable (shadcn copies the source, not a package import). The `radix-ui` package (v1.4) is the underlying primitive. + +- Do NOT `npm install @radix-ui/react-*` directly — use `npx shadcn add` which installs the correct Radix version and generates the shadcn wrapper. +- Design tokens in `globals.css` (`--color-navy`, `--color-gold`, etc.) are already mapped to the shadcn semantic tokens (`background`, `foreground`, `primary`, etc.), so shadcn components inherit the editorial/judicial aesthetic automatically. + +--- + +## TanStack Query v5 + +**v5 has breaking API changes from v4.** Key patterns used in this codebase: + +```typescript +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; + +// Reading data +const { data, isLoading, isError } = useQuery({ + queryKey: ["cases"], + queryFn: () => apiRequest("/api/cases"), +}); + +// Writing data +const queryClient = useQueryClient(); +const mutation = useMutation({ + mutationFn: (body: CreateCaseRequest) => + apiRequest("/api/cases", { method: "POST", body }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["cases"] }); + }, +}); +``` + +**v5 changes from v4:** +- `useQuery` no longer accepts positional arguments — always use the options object. +- `isLoading` is replaced by `isPending` for mutations (but `isLoading` still works for queries). +- `onSuccess`/`onError`/`onSettled` callbacks on `useQuery` are removed — use mutation callbacks or `useEffect` instead. +- `getQueryData` / `setQueryData` are unchanged. + +The shared `QueryClient` is created in `src/lib/api/client.ts` via `makeQueryClient()` and provided by `src/lib/providers.tsx`. + +--- + +## RTL — Hebrew UI Rules + +**All UI is Hebrew, right-to-left.** The `` element has `dir="rtl"` and `lang="he"`. + +Use **logical CSS properties** instead of directional ones: + +| Avoid (directional) | Use (logical) | +|---------------------|--------------| +| `ml-*` / `mr-*` | `ms-*` (start) / `me-*` (end) | +| `pl-*` / `pr-*` | `ps-*` (start) / `pe-*` (end) | +| `text-left` | `text-start` | +| `text-right` | `text-end` | +| `float-left` | `float-start` | +| `border-l-*` | `border-s-*` | + +In RTL, "start" = right side, "end" = left side. Using logical properties means the layout works automatically without RTL overrides. + +Flexbox direction: `flex-row` in RTL naturally flows right-to-left. Use `flex-row-reverse` only when you need LTR inside an RTL context. + +--- + +## Project Structure + +``` +src/ +├── app/ # Next.js App Router pages +│ ├── layout.tsx # Root layout — sets dir="rtl", applies fonts +│ ├── globals.css # Tailwind v4 imports + design tokens + :root vars +│ ├── cases/ # Case management pages +│ ├── precedents/ # Precedent library pages +│ ├── methodology/ # Methodology browser +│ ├── training/ # Training document management +│ ├── settings/ # Application settings +│ └── skills/ # Skills management +├── components/ +│ ├── ui/ # shadcn primitives (editable copies) +│ ├── app-shell.tsx # Top-level shell with nav +│ ├── cases/ # Case-domain components +│ ├── documents/ # Document viewer components +│ ├── precedents/ # Precedent components +│ └── compose/ # Decision drafting / block editor +├── lib/ +│ ├── api/ +│ │ ├── types.ts # AUTO-GENERATED — never edit +│ │ ├── client.ts # apiRequest + QueryClient factory +│ │ ├── cases.ts # Typed case API helpers +│ │ ├── documents.ts # Typed document API helpers +│ │ └── ... # One file per API domain +│ ├── providers.tsx # TanStack Query + theme providers +│ ├── utils.ts # cn() and other shared utilities +│ ├── doc-types.ts # Document type constants +│ └── sse.ts # Server-Sent Events helper for streaming +``` + +--- + +## Forms + +Forms use **react-hook-form** (v7) with **zod** (v4) validation via `@hookform/resolvers`: + +```typescript +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +const schema = z.object({ title: z.string().min(1) }); +type FormValues = z.infer; + +const form = useForm({ resolver: zodResolver(schema) }); +``` + +--- + +## Notifications / Toasts + +Use **sonner** (`import { toast } from "sonner"`). The `` is mounted in `src/lib/providers.tsx`. + +```typescript +toast.success("התיק נשמר בהצלחה"); +toast.error("שגיאה בשמירה"); +``` + +--- + +## Streaming (SSE) + +Server-sent events are used for long-running AI operations (drafting, analysis). The helper is in `src/lib/sse.ts`. Use it instead of raw `EventSource`. + +--- + +## Deploy + +This frontend runs **inside Docker via Coolify** — not as a standalone Node process. + +- **No `npm run dev` on the server** — there is no local Python environment for the backend. +- To see changes in production: `git commit` + `git push origin main` → Gitea Actions builds image → Coolify redeploys (~2-4 min). +- Prod URL: `https://legal-ai.nautilus.marcusgroup.org` +- The Next.js output is `standalone` (see `next.config.ts: output: "standalone"`).