From 80a5daa3e317e9ddd45709057a7826f2b3e5e1db Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 28 Jun 2026 22:20:13 +0000 Subject: [PATCH] =?UTF-8?q?feat(ops):=20host=5Fsync.sh=20=E2=80=94=20auto-?= =?UTF-8?q?sync=20host=20tree=20to=20origin/main=20(P-#160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The writer, learning panels, and legal_mcp tools run from the HOST tree (Paperclip agents claude -p with cwd=~/legal-ai; pm2 services import from mcp-server/src) — not the container. A merge + Coolify deploy only refreshes the FastAPI container; host-side code stayed on the last manual `git pull`. On 2026-06-28 the host was 16 commits behind, so a day of learning-loop fixes sat dormant until pulled by hand. host_sync.sh (run every minute via cron, like the existing data/training pull) fast-forwards the host tree to origin/main when it falls behind. Fast-forward ONLY — never forces, so the perpetually-dirty .taskmaster/tasks/tasks.json is never clobbered (clean ff leaves it; an upstream conflict logs + bails for manual handling). Agent runs spawn fresh and pick up new code with no restart; only the long-running pm2 services that import app code (chat_service, court_fetch_service, reaper) are restarted, and only when their own files changed (precise, minimal churn). Logs to data/logs/host_sync.log. Cron entry (added on the host, flock-guarded): * * * * * flock -n /tmp/host_sync.lock bash ~/legal-ai/scripts/host_sync.sh SCRIPTS.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/SCRIPTS.md | 1 + scripts/host_sync.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100755 scripts/host_sync.sh diff --git a/scripts/SCRIPTS.md b/scripts/SCRIPTS.md index c07f9fb..363dfa9 100644 --- a/scripts/SCRIPTS.md +++ b/scripts/SCRIPTS.md @@ -97,6 +97,7 @@ | `leak_guard.py` | python | **המאכף הקנוני של INV-G12 (שער-הפלטפורמה / docs/spec/X15 §4 / R4).** שני כללים קשיחים: (1) `mcp-server/src` ללא סמלי-Paperclip (allowlist מנומק לפי substring); (2) רק `web/agent_platform_port.py` (+ קבצי-המעטפת) מייבאים את לקוח-Paperclip. stdlib-בלבד (אין venv). `leak_guard.py` = סריקת-repo (exit 1 על הפרה); `leak_guard.py ...` = קבצים נתונים (ל-hook). משותף ל-spec-guard.sh (hook), ל-CI (`.gitea/workflows/leak-guard.yaml`) ול-`mcp-server/tests/test_platform_port_leak_guard.py`. | CI + hook + pytest | | `check_undefined_names.py` | python | **CI gate ל-undefined names (מחלקת ה-NameError).** מריץ pyflakes על `web`, `mcp-server/src`, `scripts` ומפיל build (exit 1) רק על "undefined name"/"may be undefined" — לא על imports-לא-בשימוש/f-strings (רעש). זו בדיוק מחלקת-הבאג של PR #249 (שינוי-שם תיק → 500): שם שמופנה אך לא מיובא/מוגדר, חבוי בתוך `background_tasks` עד זמן-ריצה. דורש pyflakes (ה-workflow מתקין ל-venv זמני). משותף ל-CI (`.gitea/workflows/lint.yaml`). | CI | | `auto-sync-cases.sh` | bash | סנכרון תיקי ערר ל-Gitea — רץ כל דקה | `* * * * *` (cron) | +| `host_sync.sh` | bash | מסנכרן את עץ-המארח `~/legal-ai` ל-origin/main (ff-only) כדי שקוד-המארח (כותב/פאנלים/MCP שרצים מהעץ, לא בקונטיינר) יתעדכן אחרי merge; restart מדויק ל-chat/court-fetch/reaper רק כשקבציהם משתנים. בטוח: אף-פעם לא force; tasks.json הדירטי נשמר. סוגר את פער-פריסת-המארח (TaskMaster #160) | `* * * * *` (cron, flock) | | `notify.py` | python | שליחת מייל התראה מסוכנים via SMTP (Gmail) | נקרא ע"י סוכנים | | `bidi_table.py` | python | יצירת טבלאות box-drawing עם תמיכה ב-BiDi (עברית+אנגלית) | ספריית עזר | | `convert_decision_template.py` | python | המרת `data/training/טיוטת החלטה.dotx` → `skills/docx/decision_template.docx` לטעינה ב-python-docx | להריץ כשמתעדכנת התבנית | diff --git a/scripts/host_sync.sh b/scripts/host_sync.sh new file mode 100755 index 0000000..8a5ebe5 --- /dev/null +++ b/scripts/host_sync.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# host_sync.sh — keep the HOST working tree (~/legal-ai) in sync with origin/main. +# +# Why this exists: the writer (block_writer), the learning panels +# (style_lesson_panel), and the legal_mcp MCP tools run from the host tree — the +# Paperclip agents `claude -p` with cwd=/home/chaim/legal-ai, and the long-running +# pm2 services import legal_mcp from mcp-server/src. A merge to main + Coolify +# deploy only refreshes the CONTAINER (FastAPI endpoints); host-side code stays on +# whatever was last `git pull`ed. On 2026-06-28 the host was 16 commits behind, so a +# full day of learning-loop fixes sat dormant until a manual pull. This closes that +# gap (TaskMaster #160). +# +# Safety: fast-forward ONLY (never force, never lose local work). The host tree +# carries a perpetually-dirty .taskmaster/tasks/tasks.json (uncommitted by policy) — +# a clean ff leaves it untouched; if upstream ever touches the same file the ff +# fails and we log + bail rather than clobber it. Agent runs spawn fresh, so they +# pick up new code on the next wakeup with no restart; only the long-running pm2 +# services that IMPORT app code are restarted, and only when their own files change. +set -uo pipefail + +REPO=/home/chaim/legal-ai +LOG="$REPO/data/logs/host_sync.log" +mkdir -p "$REPO/data/logs" +log() { echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $*" >> "$LOG"; } + +cd "$REPO" || { log "ERROR cd $REPO failed"; exit 1; } + +# Only sync when on main (don't disturb a manual branch checkout). +branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) +[ "$branch" = "main" ] || { log "skip: on branch '$branch', not main"; exit 0; } + +git fetch origin main --quiet 2>>"$LOG" || { log "ERROR git fetch failed"; exit 1; } +local_head=$(git rev-parse HEAD) +remote_head=$(git rev-parse origin/main) +[ "$local_head" = "$remote_head" ] && exit 0 # up to date — quiet no-op + +changed=$(git diff --name-only "$local_head" "$remote_head") +if ! git pull --ff-only origin main --quiet 2>>"$LOG"; then + log "ERROR ff-only pull failed ($local_head → $remote_head) — manual intervention needed (likely local tasks.json vs upstream); NOT forcing" + exit 1 +fi +log "synced $local_head → $(git rev-parse --short HEAD) ($(echo "$changed" | wc -l) files)" + +# Restart only the long-running services whose OWN code changed (precise, minimal +# churn). The agent path (writer/panels via fresh `claude -p`) needs no restart. +restart() { pm2 restart "$1" --update-env >/dev/null 2>>"$LOG" && log "restarted $1"; } +echo "$changed" | grep -q '^mcp-server/src/legal_mcp/chat_service/' && restart legal-chat-service +echo "$changed" | grep -qE '^mcp-server/src/legal_mcp/court_fetch_service/' && restart legal-court-fetch-service +echo "$changed" | grep -q '^scripts/reap_orphan_procs.py' && restart legal-reaper +exit 0