עד כה claims.party_name נשאר ריק והצבירה/הכתיבה גזרו את הפרדת-המשיבים
מ-source_document בזמן-ריצה (שדה-מת). עכשיו הוא מאוכלס במקור ונקרא ע"י הצרכנים.
- claims_extractor.extract_and_store_claims: מחתים party_name=source_document
ל-SPLIT_PARTIES (respondent/permit_applicant); single-voice נשאר ''. אותו
כלל SPLIT_PARTIES של הצבירה (G2, import לא-מעגלי).
- SCHEMA_V52: backfill idempotent ל-claims קיימים (רק split + party_name ריק).
- argument_aggregator + block_writer._build_claims_context: קוראים
claims.party_name, עם fallback ל-source_document ל-claims מדור-קודם. התנהגות
זהה (party_name==source_document) — אפס-רגרסיה, אך השדה כעת מקור-האמת (G1).
py_compile + טעינת-שרשרת-ייבוא נקיים. סוגר את הפתוח האחרון של #224.
Invariants: G1 (אכלוס במקור, לא גזירה-בקריאה), G2 (כלל-פיצול יחיד).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
השלמת #224 לבלוק י' (דיון והכרעה): הקשר-הטענות כבר מופרד לפי כתב-תשובה
(PR#401, אותו _build_claims_context), אך ה-prompt של block-yod לא הנחה
להתייחס לכל משיב לגופו. נוסף כלל "צדדים מרובים": התייחס לעמדת כל משיב/כתב-תשובה
בנפרד (משיבות 2-3 מול משיבים 4-6), אל תמזג ל"טענות המשיבים" גורפות, וכשעמדות
מנוגדות — ציין והכרע בנימוק.
שינוי-prompt בלבד; משפיע על ניסוח בלוק י' בהחלטות עם כמה משיבים. py_compile ירוק.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
בלוק ז' של טיוטת-הביניים מיזג את כל המשיבים לסעיף אחד גנרי (או השמיטם) —
כי (א) _build_claims_context קיבץ רק לפי party_role, ו-(ב) ה-prompt נעל
"מבנה קבוע 3 חלקים: עוררים / ועדה מקומית / מבקשי-היתר" — בלי "משיבים" בכלל.
לכן בתיק 1043-02-26 המשיבים 2-3 ו-4-6 לא הופיעו כצדדים מובחנים, אף שהנתונים
(129 טענות-משיבים מפוצלות לפי כתב-תשובה) תקינים ב-claims. הפרדת-המשיבים של
#224 יושמה בצבירה+פאנל בלבד (PR#392) — לא בכותב-הבלוקים.
- `_build_claims_context` מקבץ לפי (party_role, brief) כאשר brief=source_document
ל-SPLIT_PARTIES (respondent/permit_applicant) — **אותו כלל בדיוק כמו הצבירה**
(import SPLIT_PARTIES מ-argument_aggregator, G2). התוצאה: סעיף נפרד לכל
כתב-תשובה ("טענות המשיבים — כתב תשובה משיבות 2 3", "— משיבים 4 6"...).
- prompt בלוק ז': מבנה-צדדים דינמי לפי הכותרות בקונטקסט; הוראה מפורשת לא למזג
משיבים שונים ולשקף עמדות-מנוגדות; יעד-אורך גמיש לכמה כתבי-תשובה.
משפיע גם על בלוק י' (אותו _build_claims_context) — הקשר מופרד. py_compile ירוק.
עבודה על MCP → דורש host reload + הרצה-מחדש כדי לראות בפועל.
Invariants: G2 (נגזרת-הפרדה יחידה משותפת עם הצבירה), G1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
עד כה ה-"מייצר…" כיסה רק את שנייה-ה-POST; אחרי זה לא היה חיווי אם ההפקה
רצה/הצליחה/נכשלה — ריצה שבוטלה נראתה כמו הצלחה. עכשיו צ'יפ-סטטוס ליד כל
כפתור-הפקה: בתור · רץ+שעון-חי · הושלם+מתי · נכשל+סיבה+"נסה שוב".
**נשמר-שרת (דרישת חיים):** הסטטוס נגזר בשרת מ-issue-הילד של ה-CEO +
ה-heartbeat_run האחרון + קיום-הקובץ — לא מזיכרון-הדף. שעון-הריצה מעוגן
ל-started_at של השרת, כך שעזיבה+חזרה לדף מציגות את המצב והזמן הנכונים,
בלי צורך להישאר בדף.
Backend:
- paperclip_client.get_generation_run_status — קורא issue-ילד + latest run
מ-Paperclip DB (read-only, מאחורי שער-הפלטפורמה G12); dedup לפי כותרת.
- GET /api/cases/{n}/generate/{action}/status — ממפה ל-idle/queued/running/
done/failed + output_ready + started_at/finished_at (UTC). run שהסתיים בלי
קובץ = failed (שלא ייראה כהצלחה).
Frontend:
- useGenerateStatus (poll 5s רק כשפעיל; עוצר בטרמינלי) + GenerateStatusChip
(שעון-ריצה חי מ-started_at; רכיב חסר-מצב). כפתורי-ההפקה מרעננים את
שאילתת-הסטטוס ב-onSuccess. שורה 3 (החלטה מלאה, סינכרוני) — בלי צ'יפ.
עיצוב: מוקאפ 29-generate-status-indicator אושר ע"י חיים דרך שער Claude Design (X17).
tsc + eslint + py_compile ירוקים. (api:types לריענון post-deploy.)
Invariants: G12 (Paperclip רק בשער), G1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
באג: כפתורי "הפקת מסמכים" (טיוטת-ביניים / סיכום-מנהלים) העירו את ה-CEO
*על ה-issue הראשי*. כשהתיק ב-in_review משויך-לחיים (המצב הרגיל של תיק
ממתין-ליו"ר), Paperclip ביטל את הריצה מיד (issue_assignee_changed) — הכלום
הופק, וה-endpoint החזיר 202 בכל מקרה, כך שהכשל היה שקוף.
תיקון: wake_ceo_for_action יוצר issue-ילד משויך-ל-CEO תחת ה-issue הראשי
ומעיר על הילד — בדיוק כדפוס wake_analyst_for_argument_aggregation שעובד.
ה-CEO בעל הילד → הריצה לא מבוטלת. ההוראה מוכלת בתיאור-הילד (הרץ את הכלי,
comment, סגור done) ולא תלויה בפענוח payload.action. legal-ceo.md מעודכן
שההערה מגיעה על issue-ילד ייעודי שיש לסגור בסיום.
מגע-Paperclip רק בשער-הפלטפורמה (G12). זהו החצי הראשון (ב) של #227; חצי
שני (ג) = סימון-סטטוס ב-UI. py_compile ירוק.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
הצבירה קיבצה את כל המשיבים תחת מפלגה אחת "respondent", כך שכתבי-תשובה
נפרדים (משיבות 2-3 מול משיבים 4-6) עם עמדות שעלולות להיות מנוגדות נבלעו
לרשימה אחת. התגלה בתיק 1043-02-26.
השינוי מפצל את הצדדים הרב-משתתפים (respondent/permit_applicant) לפי
source_document — כל כתב-תשובה משותף = יחידת-ליטיגציה קוהרנטית נפרדת —
ומאחסן את התווית ב-legal_arguments.party_name (מיגרציה V50). עוררים/ועדה
נשארים קבוצה יחידה. בונוס: פיצול ה-129 טענות ל-קריאות-Claude קטנות מתקן
את הכשל-בשקט המקורי (קריאה של 100+ פרופוזיציות החזירה non-JSON והפילה את
כל הצד).
- db.py: SCHEMA_V50_SQL — legal_arguments.party_name (ADD COLUMN IF NOT EXISTS, אידמפוטנטי)
- argument_aggregator: קיבוץ (party, party_name); _build_prompt מקבל שם-כתב; SELECT מחזיר party_name
- UI: PartySection מתת-קבץ לפי כתב-תשובה; טיפוס LegalArgument.party_name
טווח v1: הקיבוץ לפי source_document; אכלוס party_name ב-extractor וזיהוי
עמדות-מנוגדות בבלוק ז/י — המשך ב-#224.
Invariants: G1 (נרמול-במקור — party_name בסכמה, לא תיקון-בקריאה) · G2 (אין
מסלול-צבירה מקביל, אותו pipeline) · §6 (אין בליעה שקטה — הכשל-בשקט תוקן).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
הכפתור בבאנר-התיק רק ביצע setTab("decision") — כפילות מדויקת של טאב
"ההחלטה" שכבר קיים ברצועת-הטאבים (עורך-ההחלטה אוחד לתוכה, /compose נמחק).
שריד שאיבד משמעות אחרי איחוד-המסכים; הוסר.
בנוסף: regen של src/lib/api/types.ts מול הסכמה החיה — קולט את שדות
DocumentPatchRequest (is_post_hearing/protocol_scope) שנוספו ב-#390.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
חלוקה פנימית (לנוחות בלבד, לא משנה doc_type) של רשימת מסמכי-התיק:
1. עיקריים (appeal/response/appraisal) · 2. נלווים · 3. פרוטוקול ועדת הערר ·
4. לאחר הדיון. הסיווג נגזר בצד-הלקוח מ-doc_type + שני דגלי-metadata.
שני פקדים חדשים בעורך-התיוג:
- מתג "התקבל אחרי הדיון" → metadata.is_post_hearing (כבר נצרך ע"י בלוק-ח);
גובר על הסיווג-לפי-סוג ומעביר לקבוצה 4.
- בורר "פרוטוקול של: ועדת הערר / ועדה מקומית-מחוזית" → metadata.protocol_scope
(נראה רק כשהסוג protocol); ברירת-מחדל appeal→קבוצה 3, lower→קבוצה 2.
Backend: PATCH /documents/{id} + MCP document_update מקבלים is_post_hearing
ו-protocol_scope, נשמרים ב-metadata JSONB (אותו מסלול כמו appraiser_side).
עיצוב אושר ב-Claude Design (X17): 18k-case-documents-grouped.html.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
הגבול _MAX_PROMPT_CHARS=400_000 היה שומר מלאכותי שתאם את קיר ה-200K-טוקן
של בניית-Opus הסטנדרטית (~400K תווים עברית @~2 תווים/טוקן) — לא גבול-מודל.
Opus 4.8 מציע חלון 1M-טוקן במחיר רגיל; `claude -p` חושף אותו ישירות כ-model-id
`claude-opus-4-8[1m]` (אומת CLI 2.1.196, בלי beta-header). בלוק-י (דיון), שנושא
את התיק המלא כ-source-context (805K/891K תווים), נחסם רק ע"י השומר המיושן.
שינויים:
- block_writer: GENERATION_MODEL_1M + סף אסקלציה _CTX_1M_THRESHOLD_CHARS=350_000.
פרומפט > הסף → `[1m]`; אחרת הבנייה הסטנדרטית (בלוקים קטנים נשארים זולים/מהירים).
- _MAX_PROMPT_CHARS 400_000 → 1_500_000 (~750K טוקנים; מרווח מתחת ל-1M) כשומר-קשיח
סופי גם על בניית-ה-1M; הודעת-השגיאה מפנה לצמצום-source-context.
- model_used (provenance) משקף את הבנייה בפועל (200K/[1m]).
- claude_session: אזהרת-גודל מעודכנת (350K + רמז ל-[1m]).
- ספ 04-analysis-writing §1.3: תיעוד האסקלציה כעידון של אותו pin (לא מסלול מקביל, G2).
Invariants: מקיים G2 (אותו מודל-נעוץ, חלון רחב — לא מסלול מקביל), G1, INV-WR*;
לא בולע שגיאות (שומר-קשיח סופי נשאר). xhigh לבלוק-י ללא שינוי.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
מוקאפ 18i v2 (מאושר ב-Claude Design) + תיקון-מקור להצטברות pending interactions.
UI (agent-activity-feed.tsx):
- רוסטר-סוכנים קומפקטי, כל הסוכנים בשורה אחת (grid-cols-9); הוסרה שורת
"מה הסוכן עושה עכשיו" — היא שייכה תגובה לפי role-fallback, כך שסוכנים
בעלי אותו תפקיד הציגו טקסט זהה.
- כל אירוע "ממתין לתשובתך" מקופל כאקורדיון (הראשון פתוח).
- כפתור "התעלם" לכל בקשה ממתינה — מבטל שאלה כפולה/מיושנת בלי להעיר את הסוכן.
- ציר-הזמן מקובץ לפי משימה (issue), כל קבוצה אקורדיון עם מזהה+סטטוס+מונה.
Backend (#215 — מניעת הצטברות pending):
- cancel_interaction: ביטול interaction בודד ישירות ל-cancelled, ללא
wake_assignee (resolve רגיל היה מעיר את הסוכן). אין triggers על הטבלה.
- reap_stale_interactions: reaper שמבטל pending על issues סגורים
(done/cancelled) — המקור הדומיננטי לערימה. רץ כל 15 דק' ב-lifespan.
- endpoint POST /api/cases/{case}/agents/interaction-dismiss (מאחורי כפתור
"התעלם"). הכל דרך agent_platform_port (G12).
supersede-at-creation נדחה (תועד ב-#215): ביטול-אוטומטי של שאלה על issue
פעיל אחד על-פני אחר אינו דטרמיניסטי; הליבה הבטוחה = reaper-ליתומים +
כפתור-ביטול, והשורש האמיתי הוא ריסון לולאות-recovery של Paperclip.
Invariants: מקיים G12 (מגע-Paperclip רק דרך הפורט), G2 (אין מסלול מקביל —
מרחיב את endpoints הסוכנים הקיימים), §6 (אין בליעת-שגיאות שקטה — הביטול
מחזיר {ok,error}, ה-reaper מתעד warning).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerate web-ui/src/lib/api/types.ts from the PROD OpenAPI schema
(https://legal-ai.nautilus.marcusgroup.org/openapi.json) via `npm run api:types`.
Adds 3 drifted paths (pure additions, generated — not hand-edited):
- POST /api/cases/{case_number}/generate/party-claims-summary (#214)
- POST /api/cases/{case_number}/generate/interim-draft (#214)
- /api/status-model (drift)
Invariants:
- X6 (UI<->API contract in sync) — generated types now match the deployed surface.
- G2 (single generated client) — openapi-typescript output only, no hand edits.
Verified: `npx tsc --noEmit` passes clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
מוסיף כרטיס "הפקת מסמכים" בראש טאב "טיוטות והערות" (mockup 26 — תוספת ל-18h),
תוספת לעמוד הקיים, כל הסקשנים נשמרים.
- כפתור 1 "הפק סיכום מנהלים" → endpoint חדש POST /generate/party-claims-summary
→ CEO wakeup סטרוקטורלי action="party_claims_summary" (שלב H2, host-side).
Polling: GET /research/party-claims-summary (200=מוכן → פתח/DOCX).
- כפתור 2 "הפק טיוטה של טענות הצדדים" → endpoint חדש POST /generate/interim-draft
→ CEO wakeup action="interim_draft" (שלב H, host-side).
Polling: רשימת ה-exports עד שמופיע טיוטת-ביניים-{case}-vN.docx.
- כפתור 3 "הפק טיוטת החלטה מלאה" → ה-useExportDocx הקיים (in-container, ללא LLM);
הועבר מהבאנר (כפתור "הפק DOCX" הוסר משם — אין כפילות). disabled+title כשאין
בלוקים כתובים (isDraftReady).
מסלול-עירור יחיד דרך ה-Platform Port (pc_wake_ceo_for_action) → API helper
/api/agents/{id}/wakeup, לעולם לא DB insert. Israel-time + cache-invalidation.
Invariants: G12 (מגע-Paperclip רק ב-paperclip_client + agent_platform_port;
leak-guard ירוק) · G2 (אין מסלול-עירור מקביל — שימוש חוזר ב-pc_request הקיים) ·
G10 (הפקה מגודרת-יו"ר/CEO) · INV-UI9 (Asia/Jerusalem) · X6 (UI↔API contract).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the status SSoT (#382): analyst_verified + research_complete are
now first-class canonical statuses, so refresh the docs that still framed them
as legacy/decorative.
- HEARTBEAT.md §7: rewritten to point at the single source
(case_status_model.py / GET /api/status-model), list the 12 canonical statuses
by phase, and state the two intermediates are canonical (not legacy); genuinely
old markers (proofread/analysis_enriched/…) are display-only fallbacks.
- legal-qa.md / legal-ceo.md status maps: dropped the "legacy/optional" framing
on research_complete; point to the canonical model.
- scripts/backfill_case_status_trim.py: removed analyst_verified +
research_complete from the downgrade map (they are valid statuses again — must
NOT be downgraded) + updated the docstring.
Docs/prompts only — no behaviour change. py_compile clean.
NOTE: the host ~/legal-ai needs `git pull` for the Paperclip agents to pick up
the refreshed prompts (they run on the host tree, not the container).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 3 drafts cards rendered final-decision before draft-files; mockup 18h has
the draft-files card first, then final+learning, then feedback. Switched the
container to flex-col and gave each card an explicit `order` so the visual order
matches the mockup without reordering any JSX (zero-risk).
tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes mockup 18i beyond the earlier minimal version:
- AgentRoster — a 5-up grid of agents, each with a status dot, name, role label
and its current activity (derived from the agent's most recent comment).
Replaces the simple AgentStatusWidget list at the top of the feed (reset still
lives in the banner status-rail popover).
- "ממתין לתשובתך" now renders the actual pending InteractionCards (the
approve/reject/answer forms) at the top, not just a count pointer — and those
pending interactions are excluded from the timeline below so they aren't shown
twice.
All interaction logic / mutations preserved. tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the case where the H1 chip, the pipeline stepper and the manual-changer
dropdown disagreed (e.g. 1043-02-26 on analyst_verified): the status sat between
the canonical 10 and a legacy bucket, so each surface read a different list.
Root fix — ONE authoritative status model + canonicalize the real intermediate
states (chair-approved):
- New mcp-server/.../case_status_model.py — the single registry: ordered
StatusDef list (key/label/description/phase/selectable/terminal/on_enter),
the 5 phases, STATUS_ORDER, phase_of/label_of, and a drift assertion that the
CaseStatus enum matches it. `on_enter` is the seam for a status to *do*
something on entry, declared next to its definition (no dispatcher wired yet).
- models.CaseStatus enum: analyst_verified + research_complete promoted to
first-class canonical statuses (the agents set them — they belong in the
model, not a legacy fallback).
- tools/cases.py: the forward-only STATUS_ORDER guard now derives from the
registry instead of an inline list.
- GET /api/status-model exposes the model so the frontend mirror can be
generated against it.
- web-ui case-status.ts mirrors it: the two intermediates moved from the
legacy map into CASE_STATUSES / PHASES(thinking) / STATUS_LABELS /
STATUS_DESCRIPTIONS; status-badge icons+tones extended. So the chip (status),
stepper (its phase) and dropdown (now includes it) all agree.
Invariants: G2 — collapses the scattered status definitions (enum, inline
STATUS_ORDER, two frontend label maps) onto one backend authority + a documented
frontend mirror; no parallel status list remains. Agent-prompt alignment +
backfill follow in a separate change. py_compile + tsc + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The summarize_party_claims tool (#202) generated a distilled executive
summary of party claims but was orphaned: not granted to any agent and
no CEO trigger. Wire it parallel to the interim-draft (שלב H).
- Grant mcp__legal-ai__summarize_party_claims in legal-ceo.md frontmatter.
- Add שלב H2 (סיכום מנהלים) mirroring שלב H: side-quest (no cases.status
change, no sub-agent issues), in_progress → run tool → in_review + notify.
Two triggers: chair comment ("סיכום מנהלים"/"סיכום טענות"/"סיכום לקראת
דיון"/"executive summary") OR structured action
$PAPERCLIP_WAKE_PAYLOAD_JSON action == "party_claims_summary".
- Add structured-action trigger to שלב H (interim draft):
action == "interim_draft" — same deterministic UI-button path.
- שלב 0 + HEARTBEAT.md: route structured actions deterministically.
- Spec note in docs/spec/04-analysis-writing.md §1.4.
Invariants: G2 (reuse existing summarize_party_claims tool/path, no parallel
route), G10 (chair-requested side-quest). Agent-prompt/docs only — no app code.
Note: CEO prompt is shared across CMP+CMPA via instructionsFilePath; after
merge the host tree needs `git pull` (agents run host-side reading
.claude/agents/*.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Groups the drafts/comments tab into distinct cards instead of one long scroll:
- "פסיקה שצוטטה" (CitationsSection) moved inside the chair's signed-final card
as its sub-section (was floating between final and the files table).
- "קבצי טיוטה" and "הערות יו״ר" sections each wrapped in a bordered surface card.
Result: draft-ready banner + active-draft badge at top, then three cards —
final-decision+learning (gold), draft files, chair feedback. All logic,
mutations, dialogs and the feedback list preserved verbatim.
tsc --noEmit + eslint clean. Design-gate: mockup 18h approved in Claude Design X17.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Agents tab: render the AgentStatusWidget roster at the top of the activity
feed ("who's doing what right now") and surface pending agent interactions in
an amber "ממתין לתשובתך" banner above the timeline (mockup 18i key wins).
- Drafts tab: Hebraize the drafts-table headers File/Size/Date → קובץ/גודל/תאריך
(mockup 18h).
All existing feed logic, mutations and interaction forms preserved.
tsc --noEmit + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>