feat(spec): X11 citation-corroboration + INV-G10 amendment + Opus 4.8 halacha extraction

ספ חדש לשכבת citator פנימית — תיקוף הלכות לפי טיפול-שיפוטי מצטבר (ציטוטים נכנסים),
לצמצום היקף האישור-הידני של היו"ר:

- docs/spec/X11-citation-corroboration.md — 6 invariants (INV-COR1–COR6), כל אחד עם
  ≥3 מקורות מקצועיים (Shepard's/KeyCite, Hellyer LLJ 2018, UNC Law, NCSC/JTC, CEPEJ).
- docs/spec/00-constitution.md — תיקון מבוקר ל-INV-G10: השער מסופק ע"י טיפול-שיפוטי-מצטבר
  לתת-הקבוצה החיובית, שער-היו"ר נשאר חובה לזנב ולשלילי. + X11 באינדקס.
- Opus 4.8 @ xhigh כמודל חילוץ הלכות (config HALACHA_EXTRACT_MODEL/EFFORT, env-tunable;
  claude_session model/effort params; halacha_extractor מחווט). מבוסס A/B 2026-05-31:
  פחות חילוץ-יתר, 100% quote-verified, ביטחון מכויל.
- scripts/ab_halacha_opus48.py — harness A/B לא-הרסני להשוואת מודל/effort בחילוץ הלכות.
- .taskmaster #70 (FU-2c-b) — תיעוד dedup שפר + סריקת-קורפוס (0 stubs תקועים נותרו).

תנאי-קדם (זהות נקייה) הושלם: שפר מוזג לרשומה קנונית + סריקת 128 רשומות.
audit-findings גלויים ב-X11 §7: קישור הלכה↔ציטוט + סיווג-טיפול = greenfield, ל-implementation plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 18:42:13 +00:00
parent d83a2a2fb2
commit 887079535c
8 changed files with 426 additions and 5 deletions

View File

@@ -42,6 +42,19 @@ POSTGRES_URL = os.environ.get(
# Redis
REDIS_URL = os.environ.get("REDIS_URL", "redis://127.0.0.1:6380/0")
# Claude CLI — model + effort for halacha extraction.
# All LLM calls go through the local `claude -p` CLI (claude_session.py).
# By default the CLI uses the developer's session default model with no
# explicit effort. For halacha extraction we pin Opus 4.8 @ xhigh: the
# 2026-05-31 A/B (scripts/ab_halacha_opus48.py) showed it cuts over-extraction
# (~124→51 on שטיין) at 100% quote-verification with honest confidence
# calibration. Env-overridable so the model/effort can be tuned without a
# code change (set to "" to fall back to the CLI default). Other extractors
# (claims, metadata, block-writing, QA) keep the CLI default unless similarly
# pinned.
HALACHA_EXTRACT_MODEL = os.environ.get("HALACHA_EXTRACT_MODEL", "claude-opus-4-8")
HALACHA_EXTRACT_EFFORT = os.environ.get("HALACHA_EXTRACT_EFFORT", "xhigh")
# Voyage AI
VOYAGE_API_KEY = os.environ.get("VOYAGE_API_KEY", "")
VOYAGE_MODEL = os.environ.get("VOYAGE_MODEL", "voyage-law-2")

View File

@@ -47,6 +47,8 @@ async def query(
max_turns: int = 1,
*,
system: str | None = None,
model: str | None = None,
effort: str | None = None,
) -> str:
"""Send a prompt to Claude Code headless and return the text response.
@@ -62,6 +64,13 @@ async def query(
CLI doesn't expose API-level caching. The parameter exists so
extractors can structure their calls cleanly today, and to make
a future SDK-backed path drop-in.
model: Optional model alias/id (e.g. ``claude-opus-4-8``). When set,
passed as ``--model``; otherwise the CLI's session default is
used. Lets quality-sensitive extractors (halacha) pin a stronger
model without changing the default for every caller.
effort: Optional effort level (``low``/``medium``/``high``/``xhigh``/
``max``). When set, passed as ``--effort``. Pairs with ``model``;
an empty string is treated as "unset" (CLI default).
Returns:
The text response from Claude.
@@ -80,6 +89,10 @@ async def query(
"--output-format", "json",
"--max-turns", str(max_turns),
]
if model:
cmd += ["--model", model]
if effort:
cmd += ["--effort", effort]
try:
proc = await asyncio.create_subprocess_exec(
@@ -135,12 +148,15 @@ async def query_json(
timeout: int = DEFAULT_TIMEOUT,
*,
system: str | None = None,
model: str | None = None,
effort: str | None = None,
) -> dict | list | None:
"""Send a prompt and parse the response as JSON.
Uses parse_llm_json for robust parsing (handles markdown wrapping, truncation).
``model``/``effort`` are forwarded to :func:`query` (see its docstring).
"""
raw = await query(prompt, timeout=timeout, system=system)
raw = await query(prompt, timeout=timeout, system=system, model=model, effort=effort)
return parse_llm_json(raw)

View File

@@ -304,7 +304,12 @@ async def _extract_chunk(
last_err: Exception | None = None
for attempt in range(CHUNK_RETRY_ATTEMPTS + 1):
try:
result = await claude_session.query_json(user_msg, system=base_prompt)
result = await claude_session.query_json(
user_msg,
system=base_prompt,
model=config.HALACHA_EXTRACT_MODEL or None,
effort=config.HALACHA_EXTRACT_EFFORT or None,
)
except Exception as e:
last_err = e
logger.warning(