feat(style-acq T4+T5): פנקס-התאמה draft↔final + דיסטילציה אוטומטית דרך ה-curator

סוגר את לולאת-הלמידה (INV-LRN4): כל החלטה נסגרת מול הסופי, וכל סופי
מנותח מול הטיוטה. מזין את הטבלאות ש-T15 כבר קורא מהן.

T5 — פנקס-התאמה:
- SCHEMA_V26: טבלת draft_final_pairs (snapshot draft + final + diff + analysis + status).
- db: create/update/list_draft_final_pairs.
- mark-final (app.py): תופס snapshot של הטיוטה (decision_blocks) ברגע החתימה,
  לפני שאפשר לדרוס אותו, ופותח שורת-פנקס (status=final_received).

T4 — דיסטילציה אוטומטית:
- learning_loop.process_final_version: משתמש ב-snapshot (לא בבלוקים שאולי השתנו),
  מסווג style_method↔substance, שומר הצעה ב-pair (status=analyzed).
  **הוסר ה-auto-upsert של style_patterns** — ביטל את ה-bug שדרס את שער-היו"ר
  וזיהם סגנון במהות (INV-LRN1 + INV-LRN5).
- LESSONS_PROMPT: הפרדת style_method↔substance מפורשת + לקח מופשט בלבד.
- curator wake + hermes-curator.md: מריץ ingest_final_version ראשון; מציע רק
  style_method שלא תועד; substance→מסלול precedent.

INV-LRN1 (שער-יו"ר, אין auto-commit) · INV-LRN4 (ניגוד-אמת) · INV-LRN5 (טוהר).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 17:20:57 +00:00
parent 014eb4937e
commit 0d995483ce
5 changed files with 174 additions and 43 deletions

View File

@@ -3248,6 +3248,27 @@ async def api_mark_final(case_number: str, filename: str):
UUID(case["id"]),
)
# T5/INV-LRN4 — reconciliation ledger: snapshot the AI draft NOW (before any
# later edit can overwrite decision_blocks) and open a draft↔final pair. The
# LLM distillation (curator) fills final_text/diff_stats/analysis afterwards.
pair_id: str | None = None
try:
decision = await db.get_decision_by_case(UUID(case["id"]))
draft_text = ""
if decision:
async with pool.acquire() as conn:
brows = await conn.fetch(
"SELECT content FROM decision_blocks "
"WHERE decision_id = $1 AND word_count > 0 ORDER BY block_index",
UUID(decision["id"]),
)
draft_text = "\n\n".join(b["content"] for b in brows if b["content"])
pair_id = await db.create_draft_final_pair(
UUID(case["id"]), draft_text, str(final_path),
)
except Exception as e:
logger.warning("draft_final_pair snapshot failed for %s: %s", case_number, e)
case_dir = config.find_case_dir(case_number)
if case_dir.exists():
commit_and_push(case_dir, f"גרסה סופית: {final_name}")

View File

@@ -1083,9 +1083,16 @@ async def wake_curator_for_final(
description = (
f"דפנה סימנה את ההחלטה הסופית של תיק {case_number} כסופית.\n"
f"קובץ סופי: `{final_filename}`\n\n"
f"סקור את ההחלטה מול skills/decision/SKILL.md ו-docs/legal-decision-lessons.md.\n"
f"חפש 3-5 דפוסי סגנון/דיון שלא תועדו. כתוב comment בעברית, ניטרלי, "
f"ממוספר. עדכן את MEMORY.md שלך. סגור את ה-issue (status=done)."
f"**שלב 1 — דיסטילציה (חובה, draft↔final):** הרץ "
f"`mcp__legal-ai__ingest_final_version(case_number=\"{case_number}\")`. "
f"הוא משווה את הטיוטה (snapshot מפנקס-ההתאמה) לסופי, מסווג כל שינוי "
f"style_method מול substance (INV-LRN5), ושומר את ההצעה ב-draft_final_pairs "
f"(status→analyzed). **אל תקבע לקח לבד — זו הצעה לאישור.**\n"
f"**שלב 2 — הצעה:** מתוך השינויים מסוג style_method בלבד, בחר 3-5 דפוסי "
f"סגנון/שיטה שלא תועדו ב-skills/decision/SKILL.md / docs/legal-decision-lessons.md / "
f"daphna-voice-fingerprint.md (אל תציע מה שכבר שם). כתוב comment בעברית, ניטרלי, ממוספר.\n"
f"**שלב 3:** עדכן MEMORY.md, סגור issue (status=done). substance (הלכות/עובדות) — "
f"לא נכנס לקול; אם זוהתה הלכה חדשה הפנה למסלול precedent."
)
child_resp = await pc_request(
"POST",