chore: fix .gitignore inline comments + add untracked code files
Inline # comments in gitignore are not supported — they were silently breaking three patterns (data/checkpoints/, data/adapter-migration-state.json, .claude/agents/.generated/). Moved comments to their own lines and added missing entries for runtime dirs (data/audit/, data/logs/, etc.) and temp files (.interaction_tmp.json, .design-build/, .taskmaster bak files). Also tracks previously untracked legitimate files: scripts, tests, docs, skills references, .env.example, taskmaster templates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# ממצאי ביקורת — ארכיטקטורת קורפוס־הפסיקה + מצב הדאטה בפועל
|
||||
|
||||
> **מקור:** Claude (Opus 4.8) · **תאריך:** 2026-06-20 · **קונטקסט:** חקירה לקראת תכנון־מחדש של קורפוס־הפסיקה.
|
||||
> מסמך זה הוא **אחד מכמה** קלטי־סוכנים שחיים אוסף; ייעודו להזין את שלב־הסינתזה. אינו תכנית — הוא **אבחון**.
|
||||
>
|
||||
> **שאלת־המוצא של חיים:** "הקורפוס נבנה מראש לא נכון, אני כל הזמן מתעסק בתיקונים. האם כדאי ליצור מחדש את קורפוס־הפסיקה ולהתחיל דף נקי?"
|
||||
>
|
||||
> **המודל הרצוי (כפי שחיים תיאר אותו):** מאגר פסקי־דין והחלטות ועדות־ערר; חוקר־התקדימים מזהה בשלב ניתוח־הערר פס"ד/החלטות שדנו במקרה דומה או הלכה דומה; הסוכן־הכותב משייך ומזכיר אותם בפרק הדיון וההכרעה בסגנון דפנה. שלושה מקורות־הזנה: (1) החלטות דפנה עצמה, (2) ועדות־ערר אחרות שמצטטות פסיקה, (3) פס"ד עליון/מחוזי.
|
||||
|
||||
---
|
||||
|
||||
## 0. תקציר־מנהלים (TL;DR)
|
||||
|
||||
**מה ש"בנוי לא נכון" אינו הסכמה — היא שכבת־הביצוע.** הסכמה כבר תואמת בדיוק את המודל שחיים תיאר: טבלה אחת (`case_law`) ששלושת המקורות נכנסים אליה דרך `source_kind`/`source_type`; שלושת ה־`search_*` הם שלושה *מסננים* על אותו מקור, לא שלושה מאגרים מקבילים (G2 מקוים ברמת־הסכמה). **רֵבילד של הסכמה ייצר בדיוק את אותה סכמה** — ולכן אינו פותר דבר, ומסכן ב־second-system syndrome.
|
||||
|
||||
מה שכן מחולל את "התיקונים האינסופיים" — שלושה כשלי־ביצוע מדידים:
|
||||
1. **חוזה־קליטה רופף** → 66% מהפסיקה בלי `practice_area`, 31 רשומות ריקות, אכיפת־שלמות (INV-DM1) מופרת בפועל.
|
||||
2. **צינור הלכות→קנוני מייצר רעש** → 5,472 קנוני, מתוכם 5,456 סינגלטונים, **0 published** → השכבה שאמורה להזין את הכותב (INV-G10) **אינרטית לגמרי**.
|
||||
3. **כפילות `style_corpus`** → 55 החלטות דפנה חיות בשני נתיבי־אחזור.
|
||||
|
||||
**המלצה:** לא לשרוף את הסכמה. כן לבצע "איפוס שכבות־נגזרות" צר (truncate ל-chunks+halachot+canonical והרצה־מחדש), **אבל רק אחרי תיקון החוזה והסף** — אחרת מחזירים את אותו בלגן (G1: תיקון במקור, לא בקריאה). מסמכי־המקור (363 רשומות, 332 עם full_text) נשמרים; הם יקרים ו/או ניתנים לקליטה־מחדש מ־PDF.
|
||||
|
||||
---
|
||||
|
||||
## 1. מצב הדאטה בפועל (שאילתות חיות מול legal_ai @ localhost:5433, 2026-06-20)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┬─────────┬──────────────────────────────────┐
|
||||
│ Metric │ Count │ Reading │
|
||||
├─────────────────────────────────────┼─────────┼──────────────────────────────────┤
|
||||
│ case_law (total precedents) │ 363 │ קטן — re-ingestable │
|
||||
│ • external_upload (court rulings) │ 240 │ מקור (3) — עליון/מחוזי │
|
||||
│ • internal_committee (ועדות ערר) │ 92 │ מקור (1)+(2) │
|
||||
│ • (יתר — ללא source_kind מובהק) │ 31 │ = הרשומות הריקות (ראה למטה) │
|
||||
│ style_corpus (החלטות דפנה) │ 55 │ כפילות עם internal_committee │
|
||||
│ precedent_chunks │ 11,904 │ נגזר — מתחדש מ-full_text │
|
||||
│ halachot (total) │ 5,489 │ נגזר │
|
||||
│ • approved/published │ 1,352 │ 25% בלבד │
|
||||
│ • pending_review (backlog ידני) │ 2,402 │ 44% — צוואר־בקבוק │
|
||||
│ canonical_halachot (V41) │ 5,472 │ כמעט 1:1 עם halachot ⚠️ │
|
||||
│ • singletons (instance_count=1) │ 5,456 │ דה־דופ כמעט לא קרה │
|
||||
│ • merged (instance_count>=2) │ 16 │ 0.3% מיזוג │
|
||||
│ • published (מגיע לסוכן הכותב) │ 0 │ ⚠️ השכבה אינרטית לחלוטין │
|
||||
│ case_law w/o practice_area │ 240 │ 66% — חופף-בדיוק לפסיקה החיצונית │
|
||||
│ case_law missing summary │ 27 │ │
|
||||
│ case_law w/ 0 chunks / no full_text │ 31 │ רשומות שבורות/ריקות │
|
||||
│ distinct practice_area │ 4 │ rishuy/betterment/197/(ריק) │
|
||||
└─────────────────────────────────────┴─────────┴──────────────────────────────────┘
|
||||
|
||||
practice_area breakdown:
|
||||
(ריק) 240 ← כל הפסיקה החיצונית ללא סיווג
|
||||
rishuy_uvniya 70
|
||||
betterment_levy 50
|
||||
compensation_197 3
|
||||
```
|
||||
|
||||
**קריאות מפתח:**
|
||||
- **240 = 240:** מספר הפסיקה־החיצונית שווה־בדיוק למספר חסרי־`practice_area`. כלומר אף פס"ד חיצוני לא סווג לתחום — סינון לפי תחום באחזור פשוט לא עובד עליהם.
|
||||
- **5,456 / 5,472 סינגלטונים:** מנוע הקנוניזציה (V41) רץ אך לא מאחד. סף 0.85 כנראה הדוק מדי, או שהחילוץ מנסח כל הלכה ייחודית מספיק כדי לא להתלכד.
|
||||
- **0 published canonical:** לפי INV-G10 רק קנוני `published` מגיע לכתיבה. אפס. **כל מנגנון V41 כרגע מנותק מהכתיבה בפועל.**
|
||||
- **2,402 pending_review:** צוואר־הבקבוק הוא אישור־אנושי ידני, לא טכנולוגיה.
|
||||
|
||||
---
|
||||
|
||||
## 2. מפת הארכיטקטורה — קורפוס אחד, שלושה מסננים
|
||||
|
||||
**פיזית: טבלה אחת.** כל שלושת המקורות מתכנסים ל-`case_law`, מתחתיה `precedent_chunks` (FK) ו-`halachot` (FK), ומעל ה-`halachot` שכבת `canonical_halachot` (V41).
|
||||
|
||||
```
|
||||
canonical_halachot (עקרונות מאוחדים — V41; כיום אינרטי, 0 published)
|
||||
▲ 1:many
|
||||
halachot (מופע-להלכה per precedent; review_status gate)
|
||||
▲ FK
|
||||
case_law (רישום מרכזי — court rulings + ועדות-ערר)
|
||||
├─ source_kind='external_upload' → פס"ד עליון/מחוזי [מקור 3]
|
||||
└─ source_kind='internal_committee'→ ועדות-ערר + דפנה [מקור 1+2]
|
||||
▼ FK
|
||||
precedent_chunks (chunks + embedding vector(1024))
|
||||
|
||||
בנפרד:
|
||||
style_corpus (55 החלטות דפנה — נתיב-אחזור מקביל ל-search_decisions)
|
||||
document_chunks (מסמכי-תיק + style; FK→documents→cases)
|
||||
```
|
||||
|
||||
**שלושת נתיבי־האחזור (לא שלושה מאגרים — שלושה scopes):**
|
||||
|
||||
```
|
||||
┌──────────────────────────────┬─────────────────────────┬────────────────────────────┐
|
||||
│ Tool │ Table / filter │ Purpose │
|
||||
├──────────────────────────────┼─────────────────────────┼────────────────────────────┤
|
||||
│ search_decisions │ document_chunks │ סגנון/קול: החלטות דפנה │
|
||||
│ │ (scoped case/area) │ + מסמכי-תיק │
|
||||
│ search_precedent_library │ case_law + chunks/halach │ source_kind=external_upload│
|
||||
│ │ ot, source_kind filter │ → פסיקה חיצונית │
|
||||
│ search_internal_decisions │ אותן פונקציות DB, │ source_kind=internal_ │
|
||||
│ │ source_kind אחר │ committee → ועדות-ערר │
|
||||
└──────────────────────────────┴─────────────────────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
שתי האחרונות קוראות **לאותן פונקציות DB** (`search_precedent_library_semantic`/`_lexical`) עם `source_kind` שונה. זו הפרדה־בשאילתה, לא קוד מקביל.
|
||||
|
||||
---
|
||||
|
||||
## 3. שלושת המקורות של חיים → איפה הם נופלים היום
|
||||
|
||||
```
|
||||
┌────────────────────────────────────┬───────────────────────────────┬─────────────────────────┐
|
||||
│ Source (חיים) │ Stored as │ Retrieval │
|
||||
├────────────────────────────────────┼───────────────────────────────┼─────────────────────────┤
|
||||
│ (1) החלטות דפנה עצמה │ style_corpus + (מהוגר ל-) │ search_decisions + │
|
||||
│ │ case_law internal_committee │ search_internal_decisions│
|
||||
│ │ chair_name='דפנה תמיר' │ ← כפילות / נתיב-כפול │
|
||||
│ (2) ועדות-ערר אחרות │ case_law internal_committee │ search_internal_decisions│
|
||||
│ │ chair_name=<אחר>, district │ │
|
||||
│ (3) פס"ד עליון/מחוזי │ case_law external_upload │ search_precedent_library │
|
||||
│ │ source_type='court_ruling' │ │
|
||||
└────────────────────────────────────┴───────────────────────────────┴─────────────────────────┘
|
||||
```
|
||||
|
||||
**מסקנה:** המודל המנטלי של חיים **כבר ממומש בסכמה**. אין צורך להמציא מבנה חדש — צריך לאכוף את המבנה הקיים בקליטה, ולחבר את שכבת־הקנוני לכתיבה.
|
||||
|
||||
---
|
||||
|
||||
## 4. הדיאגנוזה — מה "בנוי לא נכון" (3 מחוללי־כאב)
|
||||
|
||||
### 4.1 חוזה־קליטה רופף (root cause #1)
|
||||
- 66% מהפסיקה ללא `practice_area`; 27 ללא summary; 31 ללא full_text/chunks.
|
||||
- אין אכיפה ש"`searchable=false` עד שהמטא שלם" → INV-DM1 מופר בפועל.
|
||||
- **כל העלאה מוסיפה חוב** במקום רשומה שלמה. זה המקור לתיקונים החוזרים.
|
||||
|
||||
### 4.2 צינור הלכות→קנוני מייצר רעש, לא ערך (root cause #2)
|
||||
- 5,456/5,472 סינגלטונים → דה־דופ לא עובד (סף 0.85? ניסוח־חילוץ?).
|
||||
- **0 published** → השכבה שאמורה להזין את הכותב (INV-G10) מנותקת.
|
||||
- 2,402 בתור־אישור־ידני → הצינור מייצר מהר יותר ממה שאדם מאשר.
|
||||
- **זה בולע את רוב זמן־התחזוקה.**
|
||||
|
||||
### 4.3 כפילות style_corpus (root cause #3)
|
||||
- 55 החלטות דפנה בשני מקומות + שני נתיבי־אחזור.
|
||||
- צריך מקור־אמת אחד: או `style_corpus` SoT וה-`case_law` נגזר, או הפוך.
|
||||
|
||||
---
|
||||
|
||||
## 5. ההמלצה — לא רֵבילד־סכמה; "איפוס שכבות־נגזרות" אחרי תיקון־חוזה
|
||||
|
||||
**אסור** לשרוף ולהעלות־מחדש את `case_law` — תבנה אותה סכמה ותאבד מטא־דאטה ידני. **כן** הגיוני רֵבילד צר של ה**נגזר**, אבל בסדר הזה (G1 — מקור לפני תסמין):
|
||||
|
||||
1. **קודם החוזה:** אכוף ב-`*_upload` שדות־חובה (practice_area, summary, full_text); כשל → `searchable=false`. נקה/מחק את 31 הריקות.
|
||||
2. **תקן את הקנוניזציה:** כוונן סף 0.85, הגדר מתי `published`, ובדוק ניסוח־החילוץ. בלי זה אין טעם להריץ מחדש.
|
||||
3. **רק אז** re-derive מהמקור הקיים: chunks → halachot → canonical.
|
||||
4. **הכרע style_corpus:** מקור־אמת אחד.
|
||||
|
||||
**מתי רֵבילד־מלא כן מוצדק:** רק אם יתגלה שמסמכי־המקור עצמם (PDF/full_text של 363) פגומים/חסרים. המספרים *לא* מראים זאת (332/363 עם full_text תקין).
|
||||
|
||||
---
|
||||
|
||||
## 6. שאלות פתוחות לשלב־הסינתזה
|
||||
- **למה 0 published בקנוני?** האם זה סף, ניסוח־חילוץ, או שפשוט אף אחד לא אישר? (קריטי — קובע אם V41 שמיש בכלל.)
|
||||
- **style_corpus מול case_law:** מי SoT? (משפיע על search_decisions מול search_internal_decisions.)
|
||||
- **practice_area לפסיקה חיצונית:** לחלץ אוטומטית בקליטה, או להשאיר ידני?
|
||||
- **תור־האישור (2,402):** האם המנגנון (פאנל/active-learning, #133) יכול לסגור את הפער, או שצריך לחתוך את קצב־החילוץ?
|
||||
|
||||
---
|
||||
|
||||
## 7. נספח — קבצים מרכזיים (file:line)
|
||||
- סכמה: `mcp-server/src/legal_mcp/services/db.py` (case_law, precedent_chunks, halachot, canonical_halachot)
|
||||
- קליטה חיצונית: `mcp-server/src/legal_mcp/tools/precedent_library.py` (`precedent_library_upload`)
|
||||
- קליטה פנימית: `mcp-server/src/legal_mcp/tools/internal_decisions.py` (`internal_decision_upload`)
|
||||
- הגירת style→case_law: `mcp-server/src/legal_mcp/services/internal_decisions.py` (`migrate_from_style_corpus`, chair/district hardcoded)
|
||||
- אחזור: `mcp-server/src/legal_mcp/tools/search.py`, `services/hybrid_search.py`, `services/db.py` (`search_precedent_library_semantic`/`_lexical`)
|
||||
- ספ: `docs/spec/02-data-model.md` (INV-DM1–7), `docs/spec/03-retrieval.md` (INV-RET1–5), `docs/spec/00-constitution.md` (G2/G10)
|
||||
@@ -0,0 +1,125 @@
|
||||
# 06 — נדיבות-המחלץ: `application` בהחלטות-ועדה (כימות חוצה-קורפוס)
|
||||
|
||||
> **קלט-נתונים** ליוזמה, נמדד חי על **5,489 רשומות `halachot`** (כל הקורפוס, 2026-06-20).
|
||||
> נולד משאלת-חיים: "8508-03-24 מפיק 71 הלכות ממתינות — האם המחלץ נדיב מדי על החלטות-ועדה,
|
||||
> או שזה ספציפי לתיק הזה?" התשובה: **שיטתי, לא ספציפי — אבל הנדיבות מוצדקת.**
|
||||
>
|
||||
> **מתכתב עם [`00-final-synthesis.md`](00-final-synthesis.md):** הנתונים כאן **מחזקים** את הכרעת-הסינתזה
|
||||
> ("לא לחתוך") ומוסיפים שני דברים שלא היו לה: (א) כימות חוצה-קורפוס של ה-`application`, (ב) ממצא
|
||||
> חדש וגדול יותר — `nli_unsupported` על הפסיקה החיצונית. ראה §4 (יישוב-מתח) ו-§5 (דרכי-פעולה).
|
||||
|
||||
---
|
||||
|
||||
## 1. הממצא המרכזי — `application` הוא תופעה שיטתית של החלטות-ועדה
|
||||
|
||||
הפרדנו את הקורפוס לפי `authority` (נגזר דטרמיניסטית מ-`precedent_level`, [02-data-model §162](../spec/02-data-model.md)):
|
||||
`binding` = פסיקת-עליון/מנהלי · `persuasive` = ועדת-ערר מחוזית.
|
||||
|
||||
```text
|
||||
source rows rt=application nli_unsupported any-flag
|
||||
court (binding) 3,603 0.2% 39.7% 45.4%
|
||||
committee (persuasive) 1,842 13.7% 25.0% 30.5%
|
||||
```
|
||||
|
||||
- **`rule_type='application'` הוא כמעט-בלעדית של ועדות:** 13.7% מול 0.2%. מתוך 258 רשומות-`application`
|
||||
בכל הקורפוס, **~252 מגיעות מהחלטות-ועדה.** פער של פי-~70.
|
||||
- **עקבי בין תחומים** (לא עניין-שמאות נקודתי):
|
||||
```text
|
||||
committee, rt=application by practice_area:
|
||||
rishuy_uvniya 13.5% betterment_levy 14.1% compensation_197 18.8%
|
||||
```
|
||||
|
||||
**הפרשנות (תואם [02-data-model §163](../spec/02-data-model.md)):** `application` = "החלה תלוית-עובדות —
|
||||
לרוב לא-הלכה". ועדת-ערר היא גוף מיישֵם: היא לא יוצרת הלכה (רק בית-משפט עושה זאת), אלא **מיישמת
|
||||
פסיקת-עליון קיימת** (לוסטרניק, דלי-דליה) על עובדות-התיק. לכן חילוץ מהחלטת-ועדה מייצר באופן מובנה
|
||||
שיעור גבוה של יישומי-דוקטרינה. **זו תכונה של מקור-הנתונים, לא באג של המחלץ.**
|
||||
|
||||
## 2. 8508-03-24 — מייצג בקצה-העליון, לא חריג
|
||||
|
||||
```text
|
||||
דירוג 8508-03-24: 7 מתוך 45 תיקי-ועדה (≥15 רשומות) · 27% application (rt או flag)
|
||||
חציון תיקי-הוועדה: 11.9%
|
||||
מעליו: 1001-02-19 (40%) · 1044-08-22 (37%) · 9002-24 (33%) · 1007-01-25 (30%)
|
||||
```
|
||||
|
||||
8508 הוא ~פי-2.3 מהחציון (רבעון-עליון), אבל מה שבולט בו הוא בעיקר ש**הוא התיק הארוך בקורפוס**
|
||||
(111 רשומות) — אז 27% נותן 30 פריטי-`application` במספר מוחלט, הגבוה בקורפוס. כלומר: **כמות גבוהה,
|
||||
שיעור גבוה-אך-נורמלי.** אין כאן פתולוגיה ייחודית לתיק.
|
||||
|
||||
## 3. מנגנון-הניתוב הקיים (איך זה כבר מטופל ב-UI)
|
||||
|
||||
הפיצול בתור-ההלכות (`/precedents` → "תור הלכות") אינו לפי `exclude_low_quality`, אלא לפי
|
||||
`isExtractionFixItem(h) = (quality_flags.length>0) && !panel_round`
|
||||
([web-ui/.../precedent-library.ts:652](../../web-ui/src/lib/api/precedent-library.ts#L652)):
|
||||
|
||||
```text
|
||||
8508-03-24, 71 ממתינות (מצב 2026-06-20):
|
||||
bucket # panel? →"להכרעתך" →"דורש תיקון-חילוץ"
|
||||
clean (ללא דגל) 40 15/40 40 0
|
||||
application 23 0/23 0 23
|
||||
nli_unsupported 4 4/4 4 0
|
||||
thin_restatement 4 0/4 0 4
|
||||
```
|
||||
|
||||
**משמעות:** פריטי-`application` (חסרי-פאנל) כבר מנותבים ל**"דורש תיקון-חילוץ"** — **מחוץ** לתור-ההכרעה
|
||||
של היו"ר. כלומר המערכת כבר מסננת אותם מהעומס-הידני. (הפאנל התלת-מודלי, [halacha_panel_approve.py:191](../../scripts/halacha_panel_approve.py#L191),
|
||||
מטפל רק בדליים `clean`+`nli`; `application` ו-`defect` עוקפים אותו במכוון.)
|
||||
|
||||
## 4. ⚠️ יישוב-המתח מול הסינתזה — `application` ≠ "רעש"
|
||||
|
||||
זו הנקודה הקריטית להעברה. **אסור** לתרגם "13.7% application" ל"13.7% רעש לחיתוך". המבחן בסינתזה
|
||||
(§2 שם) כבר הוכיח שחיתוך-אגרסיבי על 8508 השמיד את **לוסטרניק** ו-~22 עקרונות-ליבה. רוב פריטי-ה-`application`
|
||||
הם בדיוק יישומי-הדוקטרינה הללו — **בני-ציטוט שהכותב צריך**. דוגמאות אמיתיות מ-8508 שמסומנות `application`:
|
||||
|
||||
```text
|
||||
"ציפיות הנובעות אך ממיקומם של המקרקעין... אין לנטרלן" ← לוסטרניק מיושמת — לשמור!
|
||||
"בחישוב שווי במצב קודם יש לכלול ציפיות כלליות... ולא ספציפיות" ← ליבת חישוב היטל-ההשבחה
|
||||
"קביעת מקדם מצויה בליבת שיקול-דעת השמאי, והוועדה לא תתערב" ← סטנדרט אי-התערבות מיושם
|
||||
```
|
||||
|
||||
לכן: **הנתון הזה תומך ב"שמור-בספק" של עמוד-1/2 בסינתזה.** המסקנה הנכונה אינה "לסנן application" אלא
|
||||
"`application` מאשר שעקרוני-הוועדה הם persuasive-יישומיים — לדרג אותם נמוך באחזור (רמה B), לא למחוק
|
||||
אותם (רמה A)". `importance=0` ל-8508 כבר משקיע אותם ממילא.
|
||||
|
||||
## 5. ⭐ ממצא-לוואי גדול יותר — `nli_unsupported` על הפסיקה החיצונית
|
||||
|
||||
```text
|
||||
nli_unsupported: court (binding) 39.7% ≫ committee 25.0%
|
||||
any-flag: court 45.4% · committee 30.5%
|
||||
```
|
||||
|
||||
**כמעט מחצית מעקרוני-הפסיקה-החיצונית נושאים דגל-איכות, בעיקר `nli_unsupported`** (הכלל אינו נגזר
|
||||
לוגית מהציטוט התומך שלו, [halacha_quality.py:282](../../mcp-server/src/legal_mcp/services/halacha_quality.py#L282)).
|
||||
זה **מגמד מספרית** את סוגיית-ה-`application`, ונוגע ישירות ל"רמה A = ניקוי-רעש" של הסינתזה:
|
||||
**מהו ה"רעש" שמנקים?** הדגל הדומיננטי הוא `nli`, והוא מרוכז בפסיקה, לא בוועדות.
|
||||
|
||||
שתי השערות מתחרות, שצריך להכריע ביניהן **לפני** שמשתמשים ב-`nli` כמסנן-רעש ברמה A:
|
||||
- **(א) הבודק מחמיר מדי** — סף-ה-NLI חותך יישורים לגיטימיים → 40% הם false-positives, וה"רעש" מדומה.
|
||||
- **(ב) החילוץ-מהפסיקה לקוי** — ציטוטים תומכים שלא מיישרים לכלל → בעיית-חילוץ אמיתית בקנה-מידה.
|
||||
|
||||
ההכרעה משנה את כל אסטרטגיית רמה-A. **אנו ממליצים לאמת זאת על מדגם-זהב לפני כל שימוש ב-`nli` כסיגנל.**
|
||||
|
||||
---
|
||||
|
||||
## 6. דרכי-פעולה מוצעות (לסוכן-הקורפוס)
|
||||
|
||||
ממוין מהשמרני לאגרסיבי. ההמלצה שלנו: **B כברירת-מחדל + C כעבודה-מקבילה**; להימנע מ-A ומ-D.
|
||||
|
||||
| # | פעולה | טיעון | סיכון | המלצה |
|
||||
|---|-------|-------|-------|-------|
|
||||
| **A** | לכוונן את ה-prompt לדכא `application` מוועדות במקור | מטפל-בשורש (G1); חוסך 14% רשומות | **גבוה** — סותר את מבחן-8508; משמיד יישומי-לוסטרניק בני-ציטוט | ✗ לא |
|
||||
| **B** | להשאיר את החילוץ; לסמוך על הניתוב הקיים (`application`→"תיקון", מחוץ לתור-היו"ר) + לדרג נמוך ב-RRF | תואם-סינתזה (שמור-בספק + דרג-בזמן-אחזור); אפס סיכון-אובדן | הרעש נשאר ב-DB (אחסון בלבד) | ✓ **כן — ברירת-מחדל** |
|
||||
| **C** | לחקור קודם את `nli_unsupported` (40% פסיקה): מדגם-זהב, להכריע (א) מחמיר-מדי מול (ב) חילוץ-לקוי | זה הסיגנל הגדול; הכרחי לפני שמגדירים "רעש" ברמה A | דורש מדגם מתויג-ידנית | ✓ **כן — במקביל** |
|
||||
| **D** | להפסיק חילוץ-עקרונות מוועדות לגמרי | רוב עקרוני-הוועדה הם שכתוב-persuasive של עליון | **קיצוני** — נוגד 07-learning §61 (ועדות ברות-ציטוט במכוון); נוגע INV-LRN | ✗ לא (אלא בהכרעת-יו"ר מפורשת) |
|
||||
|
||||
### ההמלצה המזוקקת
|
||||
1. **לא לגעת בחילוץ-מוועדות** — הנתון מאשר שהנדיבות מוצדקת; `application`=יישום-בר-ציטוט, לא זבל. עקבי עם
|
||||
הכרעת-הסינתזה "לא לחתוך".
|
||||
2. **רמה B עושה את העבודה** — `importance` boost ב-RRF מטביע את עקרוני-הוועדה ה-persuasive מתחת
|
||||
לפסיקה-המחייבת, בלי למחוק דבר. 8508 (`importance≈0`) שוקע ממילא.
|
||||
3. **להעביר את ה-`nli` לראש תור-המחקר** — לפני שמשתמשים בו כמסנן-רעש ברמה A, לאמת אם 40% אמיתי.
|
||||
|
||||
---
|
||||
|
||||
> **מקור-הנתונים:** `GET /api/halachot?limit=100000` (5,489 שורות) + per-case `?case_law_id=…`.
|
||||
> ניתן לשחזר את כל המספרים מהשאילתות האלו. הקאנון הוא live — שיעורים ינועו ככל שהדריינר/פאנל רצים.
|
||||
348
docs/superpowers/plans/2026-05-16-hooks-execution.md
Normal file
348
docs/superpowers/plans/2026-05-16-hooks-execution.md
Normal file
@@ -0,0 +1,348 @@
|
||||
# Hooks: Case Status Webhooks Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** כאשר status של תיק משתנה ב-legal-ai (e.g. `qa_failed`, `exported`), ה-plugin מקבל webhook, מעדכן את ה-issue ב-Paperclip, ומעיר את ה-CEO במקרה הצורך.
|
||||
|
||||
**Architecture:** Legal-ai REST API קורא `pc_request("POST", "/api/plugins/marcusgroup.legal-ai/webhooks/case-status", ...)` אחרי כל שינוי status. ה-plugin מטפל ב-`onWebhook()` ומגיב: מוסיף תגובה לissue, מעיר CEO אם QA נכשל.
|
||||
|
||||
**Tech Stack:** TypeScript (plugin-legal-ai), Python/FastAPI (legal-ai web), `@paperclipai/plugin-sdk@2026.325.0`, `httpx` (Python), `pc_request` helper.
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| Action | File |
|
||||
|--------|------|
|
||||
| Modify | `plugin-legal-ai/src/worker.ts` — add `onWebhook()` to `definePlugin({})` |
|
||||
| Modify | `plugin-legal-ai/plugin.json` — add `"webhooks.receive"` capability |
|
||||
| Modify | `plugin-legal-ai/src/manifest.ts` — add webhook capability |
|
||||
| Modify | `legal-ai/web/app.py` — emit webhook after `PUT /api/cases/{case_number}` |
|
||||
| Modify | `legal-ai/web/paperclip_api.py` — add `emit_webhook()` helper |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Add `emit_webhook` helper ב-Python
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/web/paperclip_api.py`
|
||||
|
||||
- [ ] **Step 1: קרא את הקובץ הקיים**
|
||||
|
||||
```bash
|
||||
head -90 /home/chaim/legal-ai/web/paperclip_api.py
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף את ה-helper בסוף הקובץ**
|
||||
|
||||
פתח `/home/chaim/legal-ai/web/paperclip_api.py` והוסף אחרי הפונקציה `pc_request`:
|
||||
|
||||
```python
|
||||
async def emit_case_status_webhook(
|
||||
case_number: str,
|
||||
old_status: str,
|
||||
new_status: str,
|
||||
company_id: str | None = None,
|
||||
run_id: str | None = None,
|
||||
) -> None:
|
||||
"""Notify the Paperclip plugin that a case status changed.
|
||||
|
||||
Fire-and-forget: logs errors but never raises, so callers aren't blocked.
|
||||
"""
|
||||
try:
|
||||
await pc_request(
|
||||
"POST",
|
||||
"/api/plugins/marcusgroup.legal-ai/webhooks/case-status",
|
||||
json={
|
||||
"caseNumber": case_number,
|
||||
"oldStatus": old_status,
|
||||
"newStatus": new_status,
|
||||
"companyId": company_id,
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
},
|
||||
run_id=run_id,
|
||||
timeout=5.0,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("emit_case_status_webhook failed: %s", exc)
|
||||
```
|
||||
|
||||
> **הערה:** `datetime` ו-`logger` כבר מיובאים ב-`app.py`. בדוק שהם מיובאים גם ב-`paperclip_api.py` — אם לא, הוסף `from datetime import datetime` ו-`import logging; logger = logging.getLogger(__name__)` בראש הקובץ.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add web/paperclip_api.py
|
||||
git commit -m "feat: add emit_case_status_webhook helper"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: צרף webhook לendpoint `PUT /api/cases/{case_number}`
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/web/app.py`
|
||||
|
||||
- [ ] **Step 1: מצא את ה-endpoint**
|
||||
|
||||
```bash
|
||||
grep -n "PUT\|case_number\|update_case" /home/chaim/legal-ai/web/app.py | head -30
|
||||
```
|
||||
|
||||
- [ ] **Step 2: קרא את הenable endpoint המלא**
|
||||
|
||||
זהה את הסקציה המלאה של ה-endpoint ואת הייבוא הקיים.
|
||||
|
||||
- [ ] **Step 3: הוסף import ל-emit_webhook**
|
||||
|
||||
בראש `app.py`, בסקציית ה-imports מ-`paperclip_api`:
|
||||
```python
|
||||
from .paperclip_api import pc_request, emit_case_status_webhook
|
||||
```
|
||||
|
||||
- [ ] **Step 4: הוסף webhook emit בתוך הendpoint**
|
||||
|
||||
אחרי שהקוד מעדכן את ה-case (לפני ה-`return`), הוסף:
|
||||
|
||||
```python
|
||||
# Notify plugin about status change (fire-and-forget)
|
||||
if updates.get("status") and old_status != updates["status"]:
|
||||
background_tasks.add_task(
|
||||
emit_case_status_webhook,
|
||||
case_number=case_number,
|
||||
old_status=old_status,
|
||||
new_status=updates["status"],
|
||||
company_id=str(case.get("company_id")),
|
||||
)
|
||||
```
|
||||
|
||||
> אם ה-endpoint כבר מקבל `background_tasks: BackgroundTasks` — השתמש בו. אם לא, הוסף `background_tasks: BackgroundTasks` לחתימת הפונקציה. הוסף `from fastapi import BackgroundTasks` ל-imports.
|
||||
|
||||
- [ ] **Step 5: שמור את ה-`old_status` לפני ה-update**
|
||||
|
||||
בתחילת ה-endpoint handler, לפני קריאת ה-DB update:
|
||||
```python
|
||||
old_status = (await db.get_case(case_number) or {}).get("status", "")
|
||||
```
|
||||
|
||||
- [ ] **Step 6: בדיקה בסיסית — שלח PUT ידני**
|
||||
|
||||
```bash
|
||||
curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "in_progress"}' | jq .status
|
||||
```
|
||||
|
||||
בדוק ב-Paperclip logs שהwebhook נשלח (עדיין לא מטופל בצד ה-plugin):
|
||||
```bash
|
||||
pm2 logs paperclip --lines 20
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add web/app.py
|
||||
git commit -m "feat: emit case-status webhook on PUT /api/cases/:case"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: הוסף `onWebhook()` ל-plugin
|
||||
|
||||
**Files:**
|
||||
- Modify: `plugin-legal-ai/src/worker.ts`
|
||||
|
||||
- [ ] **Step 1: קרא את `definePlugin({})` הקיים**
|
||||
|
||||
```bash
|
||||
grep -n "definePlugin\|onWebhook\|onHealth\|onShutdown" /home/chaim/plugin-legal-ai/src/worker.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף את `onWebhook` handler**
|
||||
|
||||
בתוך הobject שמועבר ל-`definePlugin({})`, אחרי `setup(ctx)`:
|
||||
|
||||
```typescript
|
||||
onWebhook: async (input) => {
|
||||
const { endpointKey, payload, companyId } = input as {
|
||||
endpointKey: string;
|
||||
payload: {
|
||||
caseNumber: string;
|
||||
oldStatus: string;
|
||||
newStatus: string;
|
||||
companyId: string;
|
||||
timestamp: string;
|
||||
};
|
||||
companyId: string;
|
||||
};
|
||||
|
||||
if (endpointKey !== "case-status") return;
|
||||
|
||||
const { caseNumber, oldStatus, newStatus } = payload;
|
||||
ctx.logger.info(`Webhook: ${caseNumber} ${oldStatus} → ${newStatus}`);
|
||||
|
||||
// Find the Paperclip issue linked to this case
|
||||
const stateKey = `case:${caseNumber}`;
|
||||
const issueId = await ctx.state.get({ companyId }, stateKey);
|
||||
if (!issueId) {
|
||||
ctx.logger.warn(`No issue found for case ${caseNumber}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
in_progress: "🔄 בעבודה",
|
||||
drafted: "✍️ טיוטה מוכנה",
|
||||
qa_failed: "❌ QA נכשל",
|
||||
exported: "📄 יוצא ל-DOCX",
|
||||
reviewed: "✅ נבדק",
|
||||
final: "🎯 סופי",
|
||||
};
|
||||
|
||||
const label = statusLabels[newStatus] ?? newStatus;
|
||||
|
||||
// Post a status comment on the issue
|
||||
await ctx.issues.createComment({
|
||||
issueId: issueId as string,
|
||||
body: `**עדכון סטטוס:** ${label} (היה: ${oldStatus})`,
|
||||
});
|
||||
|
||||
// Wake CEO if QA failed
|
||||
if (newStatus === "qa_failed") {
|
||||
const companies = await ctx.companies.list();
|
||||
const company = companies.find((c) => c.id === companyId);
|
||||
if (!company) return;
|
||||
|
||||
const CEO_IDS: Record<string, string> = {
|
||||
"42a7acd0-30c5-4cbd-ac97-7424f65df294": "752cebdd-6748-4a04-aacd-c7ab0294ef33",
|
||||
"8639e837-4c9d-47fa-a76b-95788d651896": "cdbfa8bc-3d61-41a4-a2e7-677ec7d34562",
|
||||
};
|
||||
const ceoId = CEO_IDS[companyId];
|
||||
if (ceoId) {
|
||||
await ctx.agents.invoke(ceoId, companyId, {
|
||||
prompt: `תיק ${caseNumber} נכשל בבדיקת QA. עיין בתוצאות QA ותקן את הבעיות.`,
|
||||
reason: "qa_failed webhook",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
> **הערה:** `ctx` חייב להיות נגיש ב-`onWebhook`. אם `ctx` מוגדר בתוך `setup()` בלבד — הוצא אותו ל-closure חיצוני של ה-plugin object (ראה את הדפוס הקיים ב-`worker.ts`).
|
||||
|
||||
- [ ] **Step 3: בדק TypeScript**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai && npx tsc --noEmit
|
||||
```
|
||||
|
||||
Expected: 0 errors.
|
||||
|
||||
- [ ] **Step 4: Build**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai && npm run build
|
||||
```
|
||||
|
||||
Expected: `dist/worker.js` נוצר ללא שגיאות.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai
|
||||
git add src/worker.ts
|
||||
git commit -m "feat: add onWebhook handler for case-status events"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: הוסף capability ל-`plugin.json` ול-`manifest.ts`
|
||||
|
||||
**Files:**
|
||||
- Modify: `plugin-legal-ai/plugin.json`
|
||||
- Modify: `plugin-legal-ai/src/manifest.ts`
|
||||
|
||||
- [ ] **Step 1: הוסף `"webhooks.receive"` ל-capabilities**
|
||||
|
||||
ב-`plugin-legal-ai/plugin.json`, בarray `"capabilities"`, הוסף:
|
||||
```json
|
||||
"webhooks.receive"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף גם ב-`manifest.ts`**
|
||||
|
||||
```bash
|
||||
grep -n "capabilities\|webhooks" /home/chaim/plugin-legal-ai/src/manifest.ts
|
||||
```
|
||||
|
||||
הוסף `"webhooks.receive"` לarray שם.
|
||||
|
||||
- [ ] **Step 3: Re-install plugin**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai && npm run build
|
||||
npx paperclipai plugin uninstall marcusgroup.legal-ai \
|
||||
--api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026
|
||||
npx paperclipai plugin install /home/chaim/plugin-legal-ai \
|
||||
--api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026
|
||||
pm2 restart paperclip
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai
|
||||
git add plugin.json src/manifest.ts
|
||||
git commit -m "feat: add webhooks.receive capability to plugin manifest"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: בדיקה end-to-end
|
||||
|
||||
- [ ] **Step 1: Deploy legal-ai**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git push origin main
|
||||
# המתן לבנייה (~2-4 דקות)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: שנה סטטוס תיק**
|
||||
|
||||
```bash
|
||||
curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "qa_failed"}' | jq .status
|
||||
```
|
||||
|
||||
- [ ] **Step 3: בדוק שהתגובה הוספה ל-Paperclip issue**
|
||||
|
||||
```bash
|
||||
# מצא את ה-issue הקשור לתיק 1130-25
|
||||
pm2 logs paperclip --lines 30 | grep "1130-25\|webhook\|qa_failed"
|
||||
```
|
||||
|
||||
Expected: תגובה "❌ QA נכשל" הוספה לissue. CEO הועיר.
|
||||
|
||||
- [ ] **Step 4: בדוק שינוי סטטוס שגרתי (לא QA)**
|
||||
|
||||
```bash
|
||||
curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "drafted"}' | jq .status
|
||||
```
|
||||
|
||||
Expected: תגובה "✍️ טיוטה מוכנה" בissue. CEO **לא** הועיר.
|
||||
|
||||
---
|
||||
|
||||
## אימות סופי
|
||||
|
||||
| בדיקה | פקודה | תוצאה מצופה |
|
||||
|-------|-------|-------------|
|
||||
| QA נכשל → CEO מועיר | `PUT status=qa_failed` | תגובה + agent invocation |
|
||||
| exported → תגובה בלבד | `PUT status=exported` | תגובה בלבד |
|
||||
| שינוי ללא status | `PUT title=...` | שום webhook |
|
||||
| תיק ללא issue | webhook לתיק חדש | לוג warning, ללא crash |
|
||||
306
docs/superpowers/plans/2026-05-16-per-agent-claude-md.md
Normal file
306
docs/superpowers/plans/2026-05-16-per-agent-claude-md.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Per-Agent CLAUDE.md Versioning & Validation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** הוסף validation ו-version tracking לקבצי הוראות הסוכנים. כעת, לפני שה-sync script מחיל שינויים, הוא מאמת שכל `instructionsFilePath` קיים. בנוסף, metadata של הסוכן ב-DB יכיל `claude_md_mtime` — השינוי האחרון בקובץ — כדי לזהות drift.
|
||||
|
||||
**Architecture:** `sync_agents_across_companies.py` מקבל `--check-instructions` flag שסורק את כל הסוכנים ומדווח על קבצים חסרים/ישנים. ב-`--apply`, מתווספת בדיקת pre-flight שמבטלת את הsync אם קובץ חסר. `agents.metadata` מקבל `claude_md_mtime` עם ה-mtime בפועל של הקובץ.
|
||||
|
||||
**Tech Stack:** Python 3.10+, asyncpg, httpx, `os.path.getmtime()`, Paperclip REST API (`PATCH /api/agents/{id}`).
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| Action | File |
|
||||
|--------|------|
|
||||
| Modify | `legal-ai/scripts/sync_agents_across_companies.py` — `--check-instructions` flag, pre-flight, metadata update |
|
||||
|
||||
זה הקובץ היחיד שצריך לגעת בו. כל שאר הלוגיקה קיימת כבר.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: הוסף `--check-instructions` flag
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/scripts/sync_agents_across_companies.py`
|
||||
|
||||
- [ ] **Step 1: קרא את החלק של `argparse` בסקריפט**
|
||||
|
||||
```bash
|
||||
grep -n "argparse\|add_argument\|--verify\|--dry-run\|--apply" \
|
||||
/home/chaim/legal-ai/scripts/sync_agents_across_companies.py | head -20
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף את הargument**
|
||||
|
||||
מצא את הסקציה שמגדירה `args` והוסף:
|
||||
|
||||
```python
|
||||
parser.add_argument(
|
||||
"--check-instructions",
|
||||
action="store_true",
|
||||
help="Scan all agents' instructionsFilePath and report missing/outdated files",
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: הוסף את הפונקציה `check_instructions()`**
|
||||
|
||||
הוסף לפני `async def main()`:
|
||||
|
||||
```python
|
||||
async def check_instructions(agents: list[dict]) -> bool:
|
||||
"""Print a report of all agents' instruction files. Returns True if all OK."""
|
||||
all_ok = True
|
||||
print(f"\n{'Agent':<30} {'File':<60} {'Status':<15} {'Size':>8} {'Modified'}")
|
||||
print("-" * 120)
|
||||
|
||||
for agent in agents:
|
||||
adapter_cfg = agent.get("adapter_config") or {}
|
||||
if isinstance(adapter_cfg, str):
|
||||
import json as _json
|
||||
adapter_cfg = _json.loads(adapter_cfg)
|
||||
|
||||
file_path = adapter_cfg.get("instructionsFilePath", "")
|
||||
name = agent.get("name", agent.get("id", "?"))[:29]
|
||||
|
||||
if not file_path:
|
||||
print(f"{name:<30} {'(none)':<60} {'⚠️ NOT SET':<15}")
|
||||
continue
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
print(f"{name:<30} {file_path[-59:]:<60} {'❌ MISSING':<15}")
|
||||
all_ok = False
|
||||
continue
|
||||
|
||||
stat = os.stat(file_path)
|
||||
size_kb = stat.st_size // 1024
|
||||
mtime = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
# Compare with DB metadata
|
||||
metadata = agent.get("metadata") or {}
|
||||
if isinstance(metadata, str):
|
||||
import json as _json
|
||||
metadata = _json.loads(metadata)
|
||||
|
||||
db_mtime = metadata.get("claude_md_mtime", "")
|
||||
actual_mtime = str(int(stat.st_mtime))
|
||||
drift = " ⚠️ DRIFT" if db_mtime and db_mtime != actual_mtime else ""
|
||||
|
||||
print(f"{name:<30} {file_path[-59:]:<60} {'✅ OK':<15} {size_kb:>6}KB {mtime}{drift}")
|
||||
|
||||
print()
|
||||
return all_ok
|
||||
```
|
||||
|
||||
> `from datetime import datetime` ו-`import os` — בדוק שמיובאים בראש הסקריפט. אם לא, הוסף.
|
||||
|
||||
- [ ] **Step 4: הוסף קריאה ל-`check_instructions()` ב-`main()`**
|
||||
|
||||
בתוך `async def main()`, אחרי שloading הagents מה-DB:
|
||||
|
||||
```python
|
||||
if args.check_instructions:
|
||||
all_ok = await check_instructions(master_agents + mirror_agents)
|
||||
sys.exit(0 if all_ok else 1)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: בדיקה**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
python scripts/sync_agents_across_companies.py --check-instructions
|
||||
```
|
||||
|
||||
Expected: טבלה עם כל הסוכנים, paths, סטטוס ✅/❌.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add scripts/sync_agents_across_companies.py
|
||||
git commit -m "feat: add --check-instructions flag to sync script"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: הוסף pre-flight validation לפני `--apply`
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/scripts/sync_agents_across_companies.py`
|
||||
|
||||
- [ ] **Step 1: מצא את נקודת הכניסה של `--apply`**
|
||||
|
||||
```bash
|
||||
grep -n "args.apply\|if.*apply\|apply.*mode" \
|
||||
/home/chaim/legal-ai/scripts/sync_agents_across_companies.py | head -10
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף pre-flight לפני apply**
|
||||
|
||||
בתחילת בלוק `--apply`, לפני כל שינוי:
|
||||
|
||||
```python
|
||||
if args.apply:
|
||||
# Pre-flight: abort if any agent is missing its instructions file
|
||||
print("🔍 Pre-flight: checking instruction files...")
|
||||
all_ok = await check_instructions(master_agents + mirror_agents)
|
||||
if not all_ok:
|
||||
print("❌ Abort: one or more instruction files are missing. Fix paths before --apply.")
|
||||
sys.exit(1)
|
||||
print("✅ Pre-flight passed.\n")
|
||||
# ... rest of apply logic ...
|
||||
```
|
||||
|
||||
- [ ] **Step 3: בדיקה — הפעל עם קובץ חסר (סימולציה)**
|
||||
|
||||
```bash
|
||||
# שנה זמנית path לקובץ שלא קיים
|
||||
cd /home/chaim/legal-ai
|
||||
python scripts/sync_agents_across_companies.py --dry-run 2>&1 | head -5
|
||||
```
|
||||
|
||||
Expected: dry-run עובר. אם תנסה `--apply` עם agent שhis file חסר — הsync יבוטל.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add scripts/sync_agents_across_companies.py
|
||||
git commit -m "feat: add pre-flight instruction file validation before --apply"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: עדכן `agents.metadata` עם `claude_md_mtime`
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/scripts/sync_agents_across_companies.py`
|
||||
|
||||
- [ ] **Step 1: מצא את `compute_diff()` או `apply_diff()`**
|
||||
|
||||
```bash
|
||||
grep -n "def compute_diff\|def apply_diff\|def build_patch\|metadata" \
|
||||
/home/chaim/legal-ai/scripts/sync_agents_across_companies.py | head -20
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף פונקציה `get_claude_md_mtime()`**
|
||||
|
||||
```python
|
||||
def get_claude_md_mtime(adapter_config: dict) -> str | None:
|
||||
"""Return the Unix mtime of the agent's instructionsFilePath, or None if missing."""
|
||||
path = adapter_config.get("instructionsFilePath", "")
|
||||
if not path or not os.path.exists(path):
|
||||
return None
|
||||
return str(int(os.path.getmtime(path)))
|
||||
```
|
||||
|
||||
- [ ] **Step 3: שלב mtime בbuild של metadata patch**
|
||||
|
||||
מצא את המקום שמכין את ה-`metadata` לsync. הוסף:
|
||||
|
||||
```python
|
||||
# Build metadata patch with claude_md_mtime
|
||||
current_metadata = master_agent.get("metadata") or {}
|
||||
if isinstance(current_metadata, str):
|
||||
import json as _json
|
||||
current_metadata = _json.loads(current_metadata)
|
||||
|
||||
adapter_cfg = master_agent.get("adapter_config") or {}
|
||||
if isinstance(adapter_cfg, str):
|
||||
import json as _json
|
||||
adapter_cfg = _json.loads(adapter_cfg)
|
||||
|
||||
mtime = get_claude_md_mtime(adapter_cfg)
|
||||
if mtime:
|
||||
current_metadata["claude_md_mtime"] = mtime
|
||||
current_metadata["claude_md_last_synced"] = datetime.utcnow().isoformat() + "Z"
|
||||
```
|
||||
|
||||
כלול את ה-`current_metadata` המעודכן ב-PATCH לAPI.
|
||||
|
||||
- [ ] **Step 4: בדוק שה-metadata מתעדכן**
|
||||
|
||||
הפעל `--dry-run` וחפש `claude_md_mtime` בoutput:
|
||||
```bash
|
||||
python scripts/sync_agents_across_companies.py --dry-run 2>&1 | grep -i "mtime\|metadata" | head -10
|
||||
```
|
||||
|
||||
לאחר `--apply`, בדוק ב-DB:
|
||||
```bash
|
||||
psql -h localhost -p 54329 -U paperclip -c \
|
||||
"SELECT name, metadata->>'claude_md_mtime' AS mtime FROM agents WHERE metadata->>'claude_md_mtime' IS NOT NULL LIMIT 5" \
|
||||
paperclip
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add scripts/sync_agents_across_companies.py
|
||||
git commit -m "feat: track claude_md_mtime in agents.metadata during sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: הוסף `make check-agents` shortcut
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/Makefile` (אם קיים) אחרת הוסף alias
|
||||
|
||||
- [ ] **Step 1: בדוק אם Makefile קיים**
|
||||
|
||||
```bash
|
||||
ls /home/chaim/legal-ai/Makefile 2>/dev/null && echo "EXISTS" || echo "MISSING"
|
||||
```
|
||||
|
||||
- [ ] **Step 2א: אם Makefile קיים — הוסף target**
|
||||
|
||||
```makefile
|
||||
check-agents:
|
||||
python scripts/sync_agents_across_companies.py --check-instructions
|
||||
|
||||
sync-agents-dry:
|
||||
python scripts/sync_agents_across_companies.py --dry-run
|
||||
|
||||
sync-agents:
|
||||
python scripts/sync_agents_across_companies.py --apply
|
||||
```
|
||||
|
||||
- [ ] **Step 2ב: אם Makefile לא קיים — הוסף alias ל-`~/.bashrc`**
|
||||
|
||||
```bash
|
||||
echo "alias check-agents='cd /home/chaim/legal-ai && python scripts/sync_agents_across_companies.py --check-instructions'" >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
- [ ] **Step 3: בדוק**
|
||||
|
||||
```bash
|
||||
# אם Makefile:
|
||||
make -C /home/chaim/legal-ai check-agents
|
||||
# אם alias:
|
||||
check-agents
|
||||
```
|
||||
|
||||
Expected: טבלת סוכנים מוצגת.
|
||||
|
||||
- [ ] **Step 4: Commit (אם Makefile)**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add Makefile
|
||||
git commit -m "feat: add check-agents and sync-agents make targets"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## אימות סופי
|
||||
|
||||
| בדיקה | פקודה | תוצאה מצופה |
|
||||
|-------|-------|-------------|
|
||||
| `--check-instructions` | `python sync_agents... --check-instructions` | טבלה עם ✅ לכל agent |
|
||||
| Pre-flight בולם apply | מחק קובץ זמנית + `--apply` | Abort עם הודעה ברורה |
|
||||
| mtime ב-DB | `SELECT metadata->>'claude_md_mtime' FROM agents` | timestamp לכל agent |
|
||||
| DRIFT זוהה | שנה קובץ + `--check-instructions` | ⚠️ DRIFT מוצג |
|
||||
| shortcut | `check-agents` או `make check-agents` | עובד |
|
||||
412
docs/superpowers/plans/2026-05-16-scheduled-agents.md
Normal file
412
docs/superpowers/plans/2026-05-16-scheduled-agents.md
Normal file
@@ -0,0 +1,412 @@
|
||||
# Scheduled Background Agents Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** הוסף 2 cron jobs לplugin: (1) תזכורת על תיקים תקועים 3+ ימים, (2) ניתוח פידבק יו"ר שבועי עם עדכון `decision-lessons.md` אוטומטי.
|
||||
|
||||
**Architecture:** שני jobs חדשים נרשמים ב-`ctx.jobs.register()`. Job 1 קורא `/api/cases/stale?days=3` (endpoint חדש ב-legal-ai) ומוסיף תגובה לissues תקועים. Job 2 קורא `/api/chair-feedback/weekly-summary` (endpoint חדש), שולח לCEO agent שמעדכן את `decision-lessons.md`.
|
||||
|
||||
**Tech Stack:** TypeScript (plugin-legal-ai jobs), Python/FastAPI (legal-ai web), asyncpg, Paperclip SDK `ctx.jobs.register()`, `ctx.agents.invoke()`.
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| Action | File |
|
||||
|--------|------|
|
||||
| Modify | `plugin-legal-ai/src/worker.ts` — register 2 new jobs |
|
||||
| Modify | `plugin-legal-ai/plugin.json` — declare 2 new job entries |
|
||||
| Modify | `plugin-legal-ai/src/manifest.ts` — add to jobs array |
|
||||
| Modify | `legal-ai/web/app.py` — add `GET /api/cases/stale` + `GET /api/chair-feedback/weekly-summary` |
|
||||
| Modify | `legal-ai/web/database.py` (or equivalent DB module) — add `get_stale_cases()` + `get_weekly_chair_feedback()` |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: הוסף endpoint `GET /api/cases/stale`
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/web/app.py`
|
||||
- Modify: `legal-ai/web/database.py` (שם DB queries מנוהלים — בדוק עם `grep -n "async def get_cases\|from.*database\|import.*db" /home/chaim/legal-ai/web/app.py | head -10`)
|
||||
|
||||
- [ ] **Step 1: מצא את module ה-DB**
|
||||
|
||||
```bash
|
||||
grep -n "^from\|^import\|db\." /home/chaim/legal-ai/web/app.py | head -20
|
||||
```
|
||||
|
||||
זהה את שם הmodule שמכיל את DB queries (בד"כ `database.py` או `db.py`).
|
||||
|
||||
- [ ] **Step 2: הוסף `get_stale_cases()` לmodule ה-DB**
|
||||
|
||||
```python
|
||||
async def get_stale_cases(days: int = 3) -> list[dict]:
|
||||
"""Return cases whose status is not 'final' and haven't been updated in `days` days."""
|
||||
async with get_db_connection() as conn:
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT case_number, title, status, company_id,
|
||||
updated_at,
|
||||
now() - updated_at AS age
|
||||
FROM cases
|
||||
WHERE status NOT IN ('final', 'new')
|
||||
AND updated_at < now() - ($1 || ' days')::interval
|
||||
ORDER BY updated_at ASC
|
||||
""",
|
||||
str(days),
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
```
|
||||
|
||||
> `get_db_connection()` — השתמש בדפוס הקיים בקובץ. אם זה `asyncpg.connect()` ישיר, `asyncpg.create_pool()`, או context manager — העתק את הדפוס.
|
||||
|
||||
- [ ] **Step 3: הוסף endpoint ב-`app.py`**
|
||||
|
||||
```python
|
||||
@app.get("/api/cases/stale")
|
||||
async def list_stale_cases(days: int = 3):
|
||||
"""Cases stuck in non-final status for more than `days` days."""
|
||||
cases = await db.get_stale_cases(days=days)
|
||||
return {
|
||||
"cases": [
|
||||
{
|
||||
"case_number": c["case_number"],
|
||||
"title": c["title"],
|
||||
"status": c["status"],
|
||||
"company_id": str(c["company_id"]),
|
||||
"days_stale": c["age"].days,
|
||||
}
|
||||
for c in cases
|
||||
],
|
||||
"total": len(cases),
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: בדיקה**
|
||||
|
||||
```bash
|
||||
curl -s "https://legal-ai.nautilus.marcusgroup.org/api/cases/stale?days=1" | jq .total
|
||||
```
|
||||
|
||||
Expected: JSON עם רשימת תיקים.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add web/app.py web/database.py # או השם הנכון
|
||||
git commit -m "feat: add GET /api/cases/stale endpoint"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: הוסף endpoint `GET /api/chair-feedback/weekly-summary`
|
||||
|
||||
**Files:**
|
||||
- Modify: `legal-ai/web/app.py`
|
||||
- Modify: DB module
|
||||
|
||||
- [ ] **Step 1: בדוק את מבנה טבלת `chair_feedback`**
|
||||
|
||||
```bash
|
||||
sqlite3 /home/chaim/.paperclip/instances/default/data/app.db \
|
||||
".schema chair_feedback" 2>/dev/null || \
|
||||
psql -h localhost -p 5433 -U legal_ai -c "\d chair_feedback" legal_ai 2>/dev/null || \
|
||||
grep -rn "chair_feedback" /home/chaim/legal-ai/mcp-server/src/ | head -10
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף `get_weekly_chair_feedback()` לDB module**
|
||||
|
||||
```python
|
||||
async def get_weekly_chair_feedback(days: int = 7) -> list[dict]:
|
||||
"""Return chair feedback entries from the last `days` days."""
|
||||
async with get_db_connection() as conn:
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT cf.case_number, cf.feedback_text, cf.created_at,
|
||||
cf.feedback_type, c.title
|
||||
FROM chair_feedback cf
|
||||
JOIN cases c ON c.case_number = cf.case_number
|
||||
WHERE cf.created_at > now() - ($1 || ' days')::interval
|
||||
ORDER BY cf.created_at DESC
|
||||
""",
|
||||
str(days),
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
```
|
||||
|
||||
> אם שמות השדות שונים (בדוק ב-Step 1) — התאם.
|
||||
|
||||
- [ ] **Step 3: הוסף endpoint**
|
||||
|
||||
```python
|
||||
@app.get("/api/chair-feedback/weekly-summary")
|
||||
async def get_chair_feedback_weekly(days: int = 7):
|
||||
"""Feedback entries from the past week, formatted for the learning agent."""
|
||||
entries = await db.get_weekly_chair_feedback(days=days)
|
||||
if not entries:
|
||||
return {"summary": "", "entry_count": 0}
|
||||
|
||||
lines = [
|
||||
f"- תיק {e['case_number']} ({e['title']}): {e['feedback_text']}"
|
||||
for e in entries
|
||||
]
|
||||
summary = "\n".join(lines)
|
||||
return {"summary": summary, "entry_count": len(entries), "entries": entries}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: בדיקה**
|
||||
|
||||
```bash
|
||||
curl -s "https://legal-ai.nautilus.marcusgroup.org/api/chair-feedback/weekly-summary" | jq .entry_count
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai
|
||||
git add web/app.py web/database.py
|
||||
git commit -m "feat: add GET /api/chair-feedback/weekly-summary endpoint"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: הוסף jobs לplugin
|
||||
|
||||
**Files:**
|
||||
- Modify: `plugin-legal-ai/plugin.json`
|
||||
- Modify: `plugin-legal-ai/src/manifest.ts`
|
||||
|
||||
- [ ] **Step 1: קרא את ה-`jobs` הקיים ב-`plugin.json`**
|
||||
|
||||
```bash
|
||||
cat /home/chaim/plugin-legal-ai/plugin.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d['jobs'], indent=2))"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: הוסף 2 jobs חדשים לarray `"jobs"` ב-`plugin.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"jobKey": "stale-case-reminder",
|
||||
"displayName": "תזכורת תיקים תקועים",
|
||||
"description": "מזהה תיקים שלא עודכנו 3+ ימים ומוסיף תגובה לissue",
|
||||
"schedule": "0 8 * * *"
|
||||
},
|
||||
{
|
||||
"jobKey": "weekly-feedback-analysis",
|
||||
"displayName": "ניתוח פידבק שבועי",
|
||||
"description": "מסכם פידבק יו\"ר מהשבוע האחרון ומעדכן את decision-lessons.md",
|
||||
"schedule": "0 19 * * 0"
|
||||
}
|
||||
```
|
||||
|
||||
> `"0 8 * * *"` = כל יום בשעה 08:00. `"0 19 * * 0"` = כל ראשון ב-19:00.
|
||||
|
||||
- [ ] **Step 3: עדכן `manifest.ts`**
|
||||
|
||||
```bash
|
||||
grep -n "jobs\|jobKey\|schedule" /home/chaim/plugin-legal-ai/src/manifest.ts
|
||||
```
|
||||
|
||||
הוסף את אותם 2 objects לarray `jobs` ב-`manifest.ts`.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai
|
||||
git add plugin.json src/manifest.ts
|
||||
git commit -m "feat: declare stale-case-reminder and weekly-feedback-analysis jobs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Implement job handlers ב-`worker.ts`
|
||||
|
||||
**Files:**
|
||||
- Modify: `plugin-legal-ai/src/worker.ts`
|
||||
|
||||
- [ ] **Step 1: קרא את handler של `sync-case-status` הקיים**
|
||||
|
||||
```bash
|
||||
grep -n "sync-case-status\|jobs.register\|jobKey" /home/chaim/plugin-legal-ai/src/worker.ts
|
||||
```
|
||||
|
||||
העתק את הדפוס.
|
||||
|
||||
- [ ] **Step 2: הוסף את `stale-case-reminder` handler**
|
||||
|
||||
בתוך `setup(ctx)`, אחרי רישום ה-job הקיים:
|
||||
|
||||
```typescript
|
||||
ctx.jobs.register("stale-case-reminder", async (job) => {
|
||||
ctx.logger.info("stale-case-reminder: starting");
|
||||
|
||||
const config = await ctx.config.get();
|
||||
const apiBase = (config.legalApiBaseUrl as string) ?? "http://localhost:8085";
|
||||
|
||||
const resp = await ctx.http.fetch(`${apiBase}/api/cases/stale?days=3`);
|
||||
if (!resp.ok) {
|
||||
ctx.logger.error(`stale-case-reminder: API error ${resp.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await resp.json()) as {
|
||||
cases: Array<{
|
||||
case_number: string;
|
||||
title: string;
|
||||
status: string;
|
||||
company_id: string;
|
||||
days_stale: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
for (const staleCase of data.cases) {
|
||||
const issueId = await ctx.state.get(
|
||||
{ companyId: staleCase.company_id },
|
||||
`case:${staleCase.case_number}`
|
||||
);
|
||||
if (!issueId) continue;
|
||||
|
||||
await ctx.issues.createComment({
|
||||
issueId: issueId as string,
|
||||
body: `⚠️ **תיק תקוע** — ${staleCase.days_stale} ימים ללא עדכון (סטטוס: ${staleCase.status}). האם יש צורך בפעולה?`,
|
||||
});
|
||||
|
||||
ctx.logger.info(
|
||||
`stale-case-reminder: posted reminder for ${staleCase.case_number} (${staleCase.days_stale}d stale)`
|
||||
);
|
||||
}
|
||||
|
||||
ctx.logger.info(`stale-case-reminder: done. ${data.cases.length} cases reminded`);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 3: הוסף את `weekly-feedback-analysis` handler**
|
||||
|
||||
```typescript
|
||||
ctx.jobs.register("weekly-feedback-analysis", async (job) => {
|
||||
ctx.logger.info("weekly-feedback-analysis: starting");
|
||||
|
||||
const config = await ctx.config.get();
|
||||
const apiBase = (config.legalApiBaseUrl as string) ?? "http://localhost:8085";
|
||||
|
||||
const resp = await ctx.http.fetch(`${apiBase}/api/chair-feedback/weekly-summary`);
|
||||
if (!resp.ok) {
|
||||
ctx.logger.error(`weekly-feedback-analysis: API error ${resp.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await resp.json()) as {
|
||||
summary: string;
|
||||
entry_count: number;
|
||||
};
|
||||
|
||||
if (data.entry_count === 0) {
|
||||
ctx.logger.info("weekly-feedback-analysis: no feedback this week, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Invoke the CEO agent to process feedback and update decision-lessons.md
|
||||
const companies = await ctx.companies.list();
|
||||
for (const company of companies) {
|
||||
// CEO IDs per company
|
||||
const CEO_IDS: Record<string, string> = {
|
||||
"42a7acd0-30c5-4cbd-ac97-7424f65df294": "752cebdd-6748-4a04-aacd-c7ab0294ef33",
|
||||
"8639e837-4c9d-47fa-a76b-95788d651896": "cdbfa8bc-3d61-41a4-a2e7-677ec7d34562",
|
||||
};
|
||||
const ceoId = CEO_IDS[company.id];
|
||||
if (!ceoId) continue;
|
||||
|
||||
await ctx.agents.invoke(ceoId, company.id, {
|
||||
prompt: `ניתוח פידבק שבועי יו"ר (${data.entry_count} פריטים):
|
||||
|
||||
${data.summary}
|
||||
|
||||
המשימה: עדכן את /home/chaim/legal-ai/docs/legal-decision-lessons.md עם הלקחים החדשים שעולים מהפידבק. הוסף רק לקחים חדשים שלא קיימים כבר. קבץ לפי נושא.`,
|
||||
reason: "weekly-feedback-analysis scheduled job",
|
||||
});
|
||||
|
||||
ctx.logger.info(
|
||||
`weekly-feedback-analysis: invoked CEO for company ${company.id} (${data.entry_count} feedback entries)`
|
||||
);
|
||||
break; // One CEO is enough — lessons file is shared
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 4: TypeScript check**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai && npx tsc --noEmit
|
||||
```
|
||||
|
||||
Expected: 0 errors.
|
||||
|
||||
- [ ] **Step 5: Build**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai && npm run build
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai
|
||||
git add src/worker.ts
|
||||
git commit -m "feat: implement stale-case-reminder and weekly-feedback-analysis jobs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Re-install plugin + בדיקה
|
||||
|
||||
- [ ] **Step 1: Deploy legal-ai**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/legal-ai && git push origin main
|
||||
# המתן ~3 דקות
|
||||
curl -s https://legal-ai.nautilus.marcusgroup.org/api/health | jq .status
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Re-install plugin**
|
||||
|
||||
```bash
|
||||
cd /home/chaim/plugin-legal-ai && npm run build
|
||||
npx paperclipai plugin uninstall marcusgroup.legal-ai \
|
||||
--api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026
|
||||
npx paperclipai plugin install /home/chaim/plugin-legal-ai \
|
||||
--api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026
|
||||
pm2 restart paperclip
|
||||
```
|
||||
|
||||
- [ ] **Step 3: בדוק שה-jobs רשומים**
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: Bearer pcapi_legal_install_key_2026" \
|
||||
http://localhost:3100/api/plugins/marcusgroup.legal-ai/jobs | jq .[].jobKey
|
||||
```
|
||||
|
||||
Expected: `"sync-case-status"`, `"stale-case-reminder"`, `"weekly-feedback-analysis"`.
|
||||
|
||||
- [ ] **Step 4: הפעל job ידנית לבדיקה**
|
||||
|
||||
```bash
|
||||
curl -s -X POST -H "Authorization: Bearer pcapi_legal_install_key_2026" \
|
||||
http://localhost:3100/api/plugins/marcusgroup.legal-ai/jobs/stale-case-reminder/run | jq .
|
||||
```
|
||||
|
||||
Expected: Job הופעל. בדוק logs:
|
||||
```bash
|
||||
pm2 logs paperclip --lines 30 | grep "stale-case-reminder"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## אימות סופי
|
||||
|
||||
| בדיקה | פקודה | תוצאה מצופה |
|
||||
|-------|-------|-------------|
|
||||
| API stale endpoint | `curl .../api/cases/stale?days=1` | JSON עם cases |
|
||||
| API feedback endpoint | `curl .../api/chair-feedback/weekly-summary` | JSON עם summary |
|
||||
| Jobs רשומים | `GET .../api/plugins/.../jobs` | 3 jobs רשומים |
|
||||
| Stale reminder ידני | `POST .../jobs/stale-case-reminder/run` | תגובות בissues |
|
||||
| Feedback analysis ידני | `POST .../jobs/weekly-feedback-analysis/run` | CEO מועיר |
|
||||
Reference in New Issue
Block a user