fix(ceo): company-wide pending-comment scan with mandatory case binding
Root cause (proven 2026-06-30, legal-ai #164): the comment-sweep wakes the CEO via ctx.agents.invoke, which does NOT populate $PAPERCLIP_WAKE_REASON or payload.issueId. The run transcript showed "generic heartbeat with no scoped wake" — the CEO's `user_commented` branch never fired, it ran a generic heartbeat that scans only CEO-assigned issues, and chair comments on chaim/sub-agent-owned issues (e.g. CMPA-116) were invisible → no-op. Two real 6-day-stale chair requests sat undelivered. Fix: on any wake without a scoped reason, the CEO must DISCOVER pending chair comments itself. Adds "סריקת הערות-יו"ר תלויות (company-wide)": - enumerate all active company issues (/api/companies/{id}/issues) - pending = newest user comment newer than newest agent comment (ignore system); order-independent sort_by(createdAt) since the API is newest-first - MANDATORY case binding from the STRUCTURED project.name ("ערר <case> — …"), never from the title (CMPA-116 has none) nor the comment content (which may name a different case, e.g. "ניסיתי ב-CMPA-89"). No confident case → do NOT act; escalate to chair. - handle each pending comment scoped to its own $ISSUE_ID/$CASE_NUMBER Verified end-to-end against live data: 13 active issues → exactly 2 pending (CMPA-116, CMPA-89), both resolved to case 8125-09-24 via project.name, zero false positives. Step 0 + Step A wired to run the scan on blind wakes. Refs #164. Pairs with the plugin sweep (delivery) — this is discovery+action. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -225,12 +225,14 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru
|
|||||||
### שלב 0: בדוק למה התעוררת
|
### שלב 0: בדוק למה התעוררת
|
||||||
|
|
||||||
**לפני כל דבר אחר** — בדוק את סיבת ההתעוררות (`$PAPERCLIP_WAKE_REASON`):
|
**לפני כל דבר אחר** — בדוק את סיבת ההתעוררות (`$PAPERCLIP_WAKE_REASON`):
|
||||||
- אם ה-reason מכיל `user_commented` → **דלג ישירות לסעיף "טיפול בתגובות חדשות מחיים"**. אל תסרוק תיקים אחרים, אל תבדוק issues, אל תעשה heartbeat רגיל. **טפל רק בתגובה.**
|
- אם ה-reason מכיל `user_commented` **ו**-`$PAPERCLIP_WAKE_PAYLOAD_JSON` כולל `issueId` → **דלג ישירות לסעיף "טיפול בתגובות חדשות מחיים"** עם ה-issue הזה. אל תסרוק תיקים אחרים. **טפל רק בתגובה.**
|
||||||
- אם ה-reason מכיל `agent_completion` → דלג לשלב E/F בהתאם לסוכן שסיים
|
- אם ה-reason מכיל `agent_completion` → דלג לשלב E/F בהתאם לסוכן שסיים
|
||||||
- אם ה-reason מכיל `precedent_extraction_` → **דלג לסעיף "חילוץ פסיקה אוטומטי"**. אל תיגע בתיקים — זו עבודת ספרייה.
|
- אם ה-reason מכיל `precedent_extraction_` → **דלג לסעיף "חילוץ פסיקה אוטומטי"**. אל תיגע בתיקים — זו עבודת ספרייה.
|
||||||
- אם ה-reason מכיל `weekly-feedback-job` → **דלג לסעיף "ניתוח פידבק שבועי"**. אל תיגע בתיקים פעילים.
|
- אם ה-reason מכיל `weekly-feedback-job` → **דלג לסעיף "ניתוח פידבק שבועי"**. אל תיגע בתיקים פעילים.
|
||||||
- אם ה-reason מכיל `feedback_fold_` → **דלג לסעיף "קיפול הערת יו\"ר"**. אל תיגע בתיקים — זו משימת תחזוקת ידע.
|
- אם ה-reason מכיל `feedback_fold_` → **דלג לסעיף "קיפול הערת יו\"ר"**. אל תיגע בתיקים — זו משימת תחזוקת ידע.
|
||||||
- אחרת → המשך לשלב A (heartbeat רגיל)
|
- אחרת (כולל reason ריק) → ⚠️ **קודם הרץ את "סריקת הערות-יו\"ר תלויות" (שער-מסירה)**. רק אם לא נמצאה אף הערה תלויה — המשך לשלב A (heartbeat רגיל).
|
||||||
|
|
||||||
|
> ⚠️ **wake עיוור — חובה לקרוא.** ה-sweep `route-pending-comments` (בפלאגין) מעיר אותך כשיש הערת-יו\"ר שטרם נענתה, דרך `ctx.agents.invoke` — אבל זה **אינו** מאכלס את `$PAPERCLIP_WAKE_REASON` ולא `payload.issueId` (אומת 2026-06-30, legal-ai #164: התעתיק הראה "generic heartbeat with no scoped wake"). לכן **הערת-יו\"ר תלויה מגיעה אליך כ-heartbeat "רגיל" בלי שום סימן** — ואם תסרוק רק את התור המשויך-אליך, היא תיפול. **לכן בכל heartbeat שאין בו reason מכוּון — חובה להריץ קודם את "סריקת הערות-יו\"ר תלויות".**
|
||||||
|
|
||||||
### חילוץ פסיקה אוטומטי
|
### חילוץ פסיקה אוטומטי
|
||||||
|
|
||||||
@@ -320,9 +322,9 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru
|
|||||||
### שלב A: בדיקת מצב — שלמות, בדיקות שליליות, תאימות מתודולוגיה
|
### שלב A: בדיקת מצב — שלמות, בדיקות שליליות, תאימות מתודולוגיה
|
||||||
|
|
||||||
בכל heartbeat **רגיל** (לא comment routing):
|
בכל heartbeat **רגיל** (לא comment routing):
|
||||||
1. בדוק תיקים פעילים (`case_list`)
|
1. **קודם — "סריקת הערות-יו"ר תלויות"** (ראה סעיף ייעודי). ה-wake מה-sweep עיוור; הערת-יו"ר תלויה מגיעה כ-heartbeat רגיל. אם נמצאה הערה תלויה — טפל בה (scoped לתיק שלה) **לפני** כל דבר אחר.
|
||||||
2. בדוק אם יש issues ב-"blocked" — אם כן, טפל בהם קודם
|
2. בדוק תיקים פעילים (`case_list`)
|
||||||
3. בדוק comments מחיים שממתינים לתגובה
|
3. בדוק אם יש issues ב-"blocked" — אם כן, טפל בהם קודם
|
||||||
4. **לפני מעבר לשלב B — בצע את כל הבדיקות למטה. אם בדיקה נכשלת — עצור.**
|
4. **לפני מעבר לשלב B — בצע את כל הבדיקות למטה. אם בדיקה נכשלת — עצור.**
|
||||||
|
|
||||||
#### A1. בדיקת שלמות חילוץ
|
#### A1. בדיקת שלמות חילוץ
|
||||||
@@ -844,9 +846,49 @@ case_prefix="${case_number:0:1}"
|
|||||||
- **ודא עקביות מתודולוגית** — כיוונים סילוגיסטיים (כלל + עובדות + מסקנה), chair_directions שלם (טיפול בטענות + כיוון + סדר סוגיות + תקן ביקורת), התאמה ל-`decision-methodology.md`
|
- **ודא עקביות מתודולוגית** — כיוונים סילוגיסטיים (כלל + עובדות + מסקנה), chair_directions שלם (טיפול בטענות + כיוון + סדר סוגיות + תקן ביקורת), התאמה ל-`decision-methodology.md`
|
||||||
- **סינון תיקים** — עבוד רק על תיקים בטווח המספרים של החברה שלך (ראה טבלה למעלה)
|
- **סינון תיקים** — עבוד רק על תיקים בטווח המספרים של החברה שלך (ראה טבלה למעלה)
|
||||||
|
|
||||||
|
## סריקת הערות-יו"ר תלויות (company-wide) — שער-מסירה
|
||||||
|
|
||||||
|
> **למה זה קיים:** ה-wake מה-sweep מגיע עיוור (ראה ⚠️ בשלב 0). לכן אינך יכול להסתמך על `reason`/`issueId` כדי לדעת שיש הערה לטפל בה — אתה חייב **לגלות** אותה בעצמך. סריקה זו מוצאת כל הערת-יו"ר שטרם נענתה **בכל issue בחברה** (לא רק בתור המשויך-אליך — הערות-יו"ר יושבות על issues של חיים/סוכני-מִשנה, לא עליך).
|
||||||
|
|
||||||
|
**⚠️ כלל-ברזל: שיוך-לתיק ממקור מובנה, לעולם לא מהכותרת ולא מתוכן-ההערה.** הערה עשויה להזכיר תיק אחר (למשל "ניסיתי ב-CMPA-89") — זה **תוכן לטיפול**, לא יעד. היעד הוא תמיד התיק של ה-**project** שאליו שייך ה-issue. אם אי-אפשר לפתור תיק בוודאות → **אל תפעל**.
|
||||||
|
|
||||||
|
**הסריקה (הרץ בכל heartbeat ללא reason מכוּון):**
|
||||||
|
|
||||||
|
1. **רשום את כל issues החברה הפעילים:**
|
||||||
|
```bash
|
||||||
|
ISSUES=$(~/legal-ai/scripts/pc.sh GET "/api/companies/$PAPERCLIP_COMPANY_ID/issues")
|
||||||
|
echo "$ISSUES" | jq -r '(if type=="array" then . else .issues end)
|
||||||
|
| map(select(.status!="done" and .status!="cancelled" and (.hiddenAt|not)))
|
||||||
|
| .[].id'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **לכל issue — האם יש הערת-יו"ר תלויה?** הערה תלויה = הערת-`user` האחרונה **חדשה** מהערת-`agent` האחרונה (התעלם מ-`system`). ⚠️ ה-API מחזיר comments ב-**newest-first** — אל תשתמש ב-`.[-1]`; מיין ב-`createdAt`:
|
||||||
|
```bash
|
||||||
|
C=$(~/legal-ai/scripts/pc.sh GET "/api/issues/$ISSUE_ID/comments")
|
||||||
|
PENDING=$(echo "$C" | jq -r '
|
||||||
|
(map(select(.authorType=="user")) | sort_by(.createdAt) | last) as $u |
|
||||||
|
(map(select(.authorType=="agent")) | sort_by(.createdAt) | last) as $a |
|
||||||
|
if $u==null then "no" elif ($a==null) or ($u.createdAt>$a.createdAt) then "yes" else "no" end')
|
||||||
|
```
|
||||||
|
אם `PENDING != "yes"` → דלג ל-issue הבא.
|
||||||
|
|
||||||
|
3. **שיוך-לתיק (שער-בטיחות חובה)** — חלץ את מספר-התיק מ-**`project.name`** (שדה מובנה: `"ערר <מספר> — ..."`), לא מהכותרת ולא מההערה:
|
||||||
|
```bash
|
||||||
|
CTX=$(~/legal-ai/scripts/pc.sh GET "/api/issues/$ISSUE_ID/heartbeat-context")
|
||||||
|
CASE_NUMBER=$(echo "$CTX" | jq -r '.project.name // ""' \
|
||||||
|
| grep -oE '(ערר|בל"מ) [0-9]{4,5}-[0-9]{2}-[0-9]{2}' \
|
||||||
|
| grep -oE '[0-9]{4,5}-[0-9]{2}-[0-9]{2}' | head -1)
|
||||||
|
```
|
||||||
|
- **`CASE_NUMBER` ריק** (ה-project אינו תיק — למשל "ספריית פסיקה — תור חילוץ", או שיוך לא-ודאי) → **אל תפעל על תיק.** אם נראה שזו הערת-יו"ר אמיתית שאבדה — פרסם comment קצר ב-issue + שלח מייל ליו"ר (סעיף §5), בלי לגעת בשום תיק. עבור ל-issue הבא.
|
||||||
|
- **`CASE_NUMBER` קיים** → טפל בהערה לפי "טיפול בתגובות חדשות מחיים" למטה, **scoped ל-`$ISSUE_ID` ול-`$CASE_NUMBER` הזה בלבד.**
|
||||||
|
|
||||||
|
4. **כמה issues תלויים** (אפשרי — אפילו על אותו תיק) → טפל בכולם, כל אחד ב-scope שלו. אם לא נמצאה אף הערה תלויה → אין פעולת-CEO; המשך לשלב A.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## טיפול בתגובות חדשות מחיים (comment routing)
|
## טיפול בתגובות חדשות מחיים (comment routing)
|
||||||
|
|
||||||
כשאתה מתעורר בגלל תגובה חדשה (reason מכיל "user_commented"):
|
כשאתה מתעורר בגלל תגובה חדשה (reason מכיל "user_commented"), **או** כשהסריקה למעלה זיהתה הערה תלויה ב-`$ISSUE_ID` עם `$CASE_NUMBER` פתור:
|
||||||
|
|
||||||
0. **החזר את ה-issue הראשי ל-`status=in_progress`** — אם ה-issue ב-`in_review` (כי המתנת לחיים) או ב-`blocked` (כי Paperclip חסם אוטומטית), הראשון דבר: עדכן ל-`in_progress` כדי לסמן שאתה עובד עליו.
|
0. **החזר את ה-issue הראשי ל-`status=in_progress`** — אם ה-issue ב-`in_review` (כי המתנת לחיים) או ב-`blocked` (כי Paperclip חסם אוטומטית), הראשון דבר: עדכן ל-`in_progress` כדי לסמן שאתה עובד עליו.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user