From 865152932709a598cae218f4ceca268cc75c1eb8 Mon Sep 17 00:00:00 2001 From: Chaim Date: Thu, 11 Jun 2026 19:42:44 +0000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=D7=93=D7=A3=20/scripts=20=E2=80=94?= =?UTF-8?q?=20=D7=A7=D7=98=D7=9C=D7=95=D7=92=20=D7=A1=D7=A7=D7=A8=D7=99?= =?UTF-8?q?=D7=A4=D7=98=D7=99=D7=9D=20read-only=20=D7=9E-SCRIPTS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit מגיש את scripts/SCRIPTS.md כדף ב-/scripts: שם · סוג · תפקיד · תזמון לכל סקריפט בתיקיית scripts/. מקור-האמת היחיד נשאר SCRIPTS.md (G2 — אין מסלול-תוכן מקביל); עריכה דרך git, לא מה-UI. - web/app.py: GET /api/scripts/catalog קורא את הקובץ בזמן-ריצה (מחקה את דפוס get_curator_prompt; HTTPException על כשל — אין בליעה שקטה §6) - Dockerfile: COPY scripts/SCRIPTS.md (לא הועתק לקונטיינר עד כה) - web-ui: דף /scripts (AppShell + רכיב Markdown הקיים) + מודול api + קישור ניווט - SCRIPTS.md: תיעוד ingest_bulletins.py — היה הקובץ היחיד מ-73 שלא תועד Invariants: G2 (מקור-אמת יחיד), G12 (אין מגע-Paperclip), X6 (UI↔API). Co-Authored-By: Claude Opus 4.8 (1M context) --- Dockerfile | 3 + scripts/SCRIPTS.md | 1 + web-ui/src/app/scripts/page.tsx | 86 +++++++++++++++++++++++++++++ web-ui/src/components/app-shell.tsx | 1 + web-ui/src/lib/api/scripts.ts | 18 ++++++ web/app.py | 27 +++++++++ 6 files changed, 136 insertions(+) create mode 100644 web-ui/src/app/scripts/page.tsx create mode 100644 web-ui/src/lib/api/scripts.ts diff --git a/Dockerfile b/Dockerfile index 9ccf0d8..771471a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,6 +74,9 @@ COPY skills/decision/SKILL.md ./skills/decision/SKILL.md COPY docs/legal-decision-lessons.md ./docs/legal-decision-lessons.md COPY docs/corpus-analysis.md ./docs/corpus-analysis.md +# Scripts catalog surfaced read-only at /scripts (GET /api/scripts/catalog). +COPY scripts/SCRIPTS.md ./scripts/SCRIPTS.md + # Make mcp-server source available to web/app.py (it does sys.path.insert for legal_mcp) ENV PYTHONPATH=/app/mcp-server/src diff --git a/scripts/SCRIPTS.md b/scripts/SCRIPTS.md index dcb5061..8c01503 100644 --- a/scripts/SCRIPTS.md +++ b/scripts/SCRIPTS.md @@ -74,6 +74,7 @@ | `monitor_halacha_quality.py` | python | מנטר איכות חילוץ הלכות. בודק drift של `avg(confidence)` בין baseline היסטורי לחלון אחרון. מחזיר JSON מטריקות + alert ב-stderr אם drift > threshold (ברירת מחדל 5%). 2 סדרות: trusted (approved+published) ו-all_extracted. תומך `--window N` / `--threshold X` / `--min-sample N` / `--silent` / `--exit-on-alert`. רץ ב-container או מקומית עם `mcp-server/.venv` (אין תלות ב-LLM, רק SQL). **תזמון מומלץ**: `0 8 * * 1` (יום ראשון 08:00, שבועי) | `0 8 * * 1` (לתזמן) | | `audit_training_corpus.py` | python | audit של `style_corpus` — לכל החלטה: שדות מטא-דאטה מאוכלסים (`summary`/`outcome`/`key_principles`/`appeal_subtype`/`subject_categories`), קישור ל-`documents` (FK + chunks + embeddings). מפיק `data/audit/corpus-YYYY-MM-DD.json` + summary בקונסול. דרוש `POSTGRES_URL` או POSTGRES_*. אין תלויות חיצוניות מלבד asyncpg. **רץ מהמכונה המקומית** (לא קונטיינר) — חיבור ישיר ל-Postgres :5433 | ידני / קדם-עבודה לפני enrichment של מטא-דאטה | | `backfill_style_exemplars.py` | python | **T1 (style-acquisition)** — מאכלס `style_exemplars` מקורפוס דפנה (`style_corpus` + `internal_committee` chair=דפנה): מפצל לסעיפים (`chunker._split_into_sections`) → פסקאות (25-450 מילים) → embed (Voyage) → שמירה עם `section`/`outcome`/`practice_area`. מאפשר לכותב לאחזר פסקאות-בלוק אמיתיות של דפנה (T2/T3). מקור-סגנון בלבד (INV-LRN5). אידמפוטנטי (מנקה per-decision). `--dry-run` (default) / `--apply`. דורש POSTGRES_URL + Voyage. **רץ מקומית** (venv). | ידני (`python scripts/backfill_style_exemplars.py --apply`) | +| `ingest_bulletins.py` | python | קליטת ארכיון העלון החודשי **"עו"ד על נדל"ן"** לקורפוס-הגילוי (X12) — כל PDF ב-`data/bulletins/incoming/` מפוצל ע"י LLM למצביעי-פסיקה (`digest_kind='decision'`) + מאמרים (`digest_kind='article'`), עם tag `publication='עו"ד על נדל"ן'`, דרך `bulletin_library.ingest_bulletin`. idempotent (dedup per-item לפי content_hash; הרצה חוזרת מוסיפה רק חדשים); כשל בעלון אחד לא עוצר את ה-batch. **רץ מהמכונה המקומית** (LLM מקומי-בלבד) עם venv של mcp-server: `mcp-server/.venv/bin/python scripts/ingest_bulletins.py [--dir PATH] [--limit N]`. | ידני, per-batch | ## תיקיית `.archive/` — סקריפטים שהושלמו diff --git a/web-ui/src/app/scripts/page.tsx b/web-ui/src/app/scripts/page.tsx new file mode 100644 index 0000000..7a7e14c --- /dev/null +++ b/web-ui/src/app/scripts/page.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; + +import { AppShell } from "@/components/app-shell"; +import { Card, CardContent } from "@/components/ui/card"; +import { Markdown } from "@/components/ui/markdown"; +import { fetchScriptsCatalog } from "@/lib/api/scripts"; + +/* + * /scripts — read-only catalog of everything under scripts/. + * + * The content is `scripts/SCRIPTS.md` verbatim (active · archived · deleted + * tables), served by GET /api/scripts/catalog. SCRIPTS.md is the single + * source of truth — CLAUDE.md mandates updating it on every script change — + * so we render it directly rather than re-describing the scripts here. + */ +export default function ScriptsPage() { + const { data, isLoading, isError, error } = useQuery({ + queryKey: ["scripts-catalog"], + queryFn: ({ signal }) => fetchScriptsCatalog(signal), + }); + + const lastModified = + data?.last_modified != null + ? new Date(data.last_modified * 1000).toLocaleDateString("he-IL", { + year: "numeric", + month: "long", + day: "numeric", + }) + : null; + + return ( + +
+
+
+

סקריפטים

+

+ קטלוג כל הסקריפטים בתיקיית{" "} + + scripts/ + {" "} + — שם, סוג, תפקיד ותזמון. מקור-האמת הוא{" "} + + scripts/SCRIPTS.md + + ; עריכה דרך git, לא מכאן. +

+
+ {data?.gitea_url ? ( + + מקור ב-Gitea ↗ + + ) : null} +
+ + + + {isLoading ? ( +

טוען קטלוג…

+ ) : isError ? ( +

+ שגיאה בטעינת הקטלוג: {(error as Error)?.message ?? "לא ידוע"} +

+ ) : data ? ( + <> + + {lastModified ? ( +

+ עודכן לאחרונה: {lastModified} +

+ ) : null} + + ) : null} +
+
+
+
+ ); +} diff --git a/web-ui/src/components/app-shell.tsx b/web-ui/src/components/app-shell.tsx index 97b2349..46c6693 100644 --- a/web-ui/src/components/app-shell.tsx +++ b/web-ui/src/components/app-shell.tsx @@ -72,6 +72,7 @@ const KNOWLEDGE_MENUS: NavMenuDef[] = [ const ADMIN_ITEMS: NavItem[] = [ { href: "/skills", label: "מיומנויות" }, { href: "/operations", label: "תפעול" }, + { href: "/scripts", label: "סקריפטים" }, { href: "/diagnostics", label: "אבחון" }, { href: "/settings", label: "הגדרות" }, ]; diff --git a/web-ui/src/lib/api/scripts.ts b/web-ui/src/lib/api/scripts.ts new file mode 100644 index 0000000..0c3008e --- /dev/null +++ b/web-ui/src/lib/api/scripts.ts @@ -0,0 +1,18 @@ +import { apiRequest } from "./client"; + +// ── Scripts catalog ─────────────────────────────────────────────── +// Surfaces the maintained `scripts/SCRIPTS.md` (single source of truth for +// every script under scripts/) read-only at /scripts. Edits go through +// git/Gitea, not the UI — the backend just reads the file at runtime. + +export type ScriptsCatalog = { + content: string; + filename: string; + bytes: number; + last_modified: number; + gitea_url: string; +}; + +export function fetchScriptsCatalog(signal?: AbortSignal) { + return apiRequest("/api/scripts/catalog", { signal }); +} diff --git a/web/app.py b/web/app.py index 293c9f3..3446708 100644 --- a/web/app.py +++ b/web/app.py @@ -1256,6 +1256,33 @@ async def get_curator_prompt(): } +@app.get("/api/scripts/catalog") +async def get_scripts_catalog(): + """Return the maintained ``scripts/SCRIPTS.md`` catalog (read-only). + + ``scripts/SCRIPTS.md`` is the single source of truth describing every + script under ``scripts/`` — its role, type, and schedule (CLAUDE.md + mandates updating it on any add/remove/change). The UI just surfaces it + at ``/scripts`` for transparency; edits go through git/Gitea, not here. + """ + path = Path(__file__).resolve().parent.parent / "scripts" / "SCRIPTS.md" + if not path.exists(): + raise HTTPException(404, f"scripts catalog not found at {path}") + try: + content = path.read_text(encoding="utf-8") + stat = path.stat() + except OSError as e: + raise HTTPException(500, f"failed to read scripts catalog: {e}") + gitea_url = f"{_GITEA_REPO_BASE}/src/branch/main/scripts/SCRIPTS.md" + return { + "content": content, + "filename": path.name, + "bytes": stat.st_size, + "last_modified": stat.st_mtime, + "gitea_url": gitea_url, + } + + @app.get("/api/training/curator/style-analyzer-prompt") async def get_style_analyzer_prompt(): """Return the system prompt that style_analyzer.py uses to extract patterns.