feat(ui): דף /scripts — קטלוג סקריפטים read-only מ-SCRIPTS.md
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 8s
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 8s
מגיש את 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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/` — סקריפטים שהושלמו
|
||||
|
||||
|
||||
86
web-ui/src/app/scripts/page.tsx
Normal file
86
web-ui/src/app/scripts/page.tsx
Normal file
@@ -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 (
|
||||
<AppShell>
|
||||
<section className="space-y-6">
|
||||
<div className="flex items-end justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-navy">סקריפטים</h1>
|
||||
<p className="text-sm text-ink-muted mt-1">
|
||||
קטלוג כל הסקריפטים בתיקיית{" "}
|
||||
<code className="rounded bg-rule-soft px-1 py-0.5 font-mono text-[0.78rem]">
|
||||
scripts/
|
||||
</code>{" "}
|
||||
— שם, סוג, תפקיד ותזמון. מקור-האמת הוא{" "}
|
||||
<code className="rounded bg-rule-soft px-1 py-0.5 font-mono text-[0.78rem]">
|
||||
scripts/SCRIPTS.md
|
||||
</code>
|
||||
; עריכה דרך git, לא מכאן.
|
||||
</p>
|
||||
</div>
|
||||
{data?.gitea_url ? (
|
||||
<a
|
||||
href={data.gitea_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="shrink-0 text-sm text-gold-deep hover:text-gold underline underline-offset-2"
|
||||
>
|
||||
מקור ב-Gitea ↗
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<Card className="bg-surface border-rule shadow-sm">
|
||||
<CardContent className="px-6 py-5">
|
||||
{isLoading ? (
|
||||
<p className="text-sm text-ink-muted">טוען קטלוג…</p>
|
||||
) : isError ? (
|
||||
<p className="text-sm text-danger">
|
||||
שגיאה בטעינת הקטלוג: {(error as Error)?.message ?? "לא ידוע"}
|
||||
</p>
|
||||
) : data ? (
|
||||
<>
|
||||
<Markdown content={data.content} />
|
||||
{lastModified ? (
|
||||
<p className="mt-6 pt-3 border-t border-rule text-xs text-ink-muted">
|
||||
עודכן לאחרונה: {lastModified}
|
||||
</p>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
@@ -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: "הגדרות" },
|
||||
];
|
||||
|
||||
18
web-ui/src/lib/api/scripts.ts
Normal file
18
web-ui/src/lib/api/scripts.ts
Normal file
@@ -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<ScriptsCatalog>("/api/scripts/catalog", { signal });
|
||||
}
|
||||
27
web/app.py
27
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.
|
||||
|
||||
Reference in New Issue
Block a user