ci: שער undefined-names (pyflakes) — שהבאג של PR #249 לא יחזור + תיקון NameError חבוי #250

Merged
chaim merged 1 commits from worktree-ci-undefined-name-guard into main 2026-06-14 09:59:12 +00:00
4 changed files with 85 additions and 1 deletions
Showing only changes of commit 0a3bc35623 - Show all commits

View File

@@ -0,0 +1,27 @@
name: Lint — undefined names
# High-signal static gate for the bug class behind PR #249 (case-rename 500):
# a name referenced but never imported/defined. Invisible to tests when it sits
# in a rarely-hit branch or a fire-and-forget background task — it only
# NameErrors at runtime. pyflakes catches it before merge. Gates ONLY on
# undefined names (not unused imports / f-strings — those are noise). Uses a
# throwaway venv so it is immune to PEP-668 externally-managed environments.
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
undefined-names:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run undefined-name guard
run: |
python3 -m venv /tmp/lintvenv
/tmp/lintvenv/bin/pip install --quiet pyflakes==3.4.0
/tmp/lintvenv/bin/python scripts/check_undefined_names.py

View File

@@ -7,7 +7,7 @@ import hashlib
import json
import logging
import re
from datetime import date
from datetime import date, datetime
from uuid import UUID, uuid4
import asyncpg

View File

@@ -11,6 +11,7 @@
| `pc.sh` | bash | **wrapper לכל קריאות Paperclip API מסוכנים** — מוסיף Authorization, X-Paperclip-Run-Id (audit trail), Content-Type, base URL. תחביר: `pc.sh <METHOD> <PATH> [BODY_JSON]`. אסור `curl` ישיר ל-`$PAPERCLIP_API_URL`. ראה `HEARTBEAT.md §0`. counterpart ב-Python: `web/paperclip_api.py`. | נקרא ע"י סוכנים |
| `spec-guard.sh` | bash | **PreToolUse hook לאכיפת "פרוטוקול כתיבת-קוד"** (CLAUDE.md §פרוטוקול כתיבת-קוד) — בכל Edit/Write/MultiEdit על נתיב-קוד (`web/`, `mcp-server/`, `web-ui/src/`, `scripts/`, `adapters/`) מזריק תזכורת ל-Claude לקרוא את `docs/spec/00-constitution.md`+ספ-התחום ולוודא קיום G1G12 — לפני שכותבים. **+ leak-guard בזמן-אמת (G12):** על כתיבה ל-`mcp-server/src/*` בודק את התוכן-הנכתב (`new_string`/`content`) ומזהיר אם מוזרק מונח-Paperclip לשכבת-האינטליגנציה (לא-deduped). המקבילה האינטראקטיבית ל-INV-AG1. קלט JSON ב-stdin, פלט `hookSpecificOutput.additionalContext` (non-blocking, exit 0). Dedup פעם-בסשן לתזכורת-הספ. רשום ב-`.claude/settings.json`. | נקרא אוטומטית ע"י Claude Code (hook) |
| `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 <file>...` = קבצים נתונים (ל-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 |
| `migrate_gap51_outcomes.py` | python | **GAP-51 (FU-14)** — נרמול ערכי `outcome` לאוצר הקנוני (rejected→rejection, accepted→full_acceptance, partial→partial_acceptance) ב-`decisions.outcome` + `cases.expected_outcome`. `betterment_levy` לא ממופה (practice_area, לא outcome). `--dry-run` (ברירת-מחדל) / `--apply` (גיבוי ל-`data/audit/gap51-outcome-backup-*.csv` + UPDATE טרנזקציוני). דורש POSTGRES_URL. בוצע 2026-06-06 (9 שורות). נוגע רק ב-cases/decisions — בטוח במקביל לחילוץ. | חד-פעמי (בוצע) |
| `sync_missing_agent_skills.py` | python | סקריפט "אל-כשל" להוספת `paperclipSkillSync` ל-`הגהת מסמכים` ו-`מנתח משפטי` שפיספסו את ה-sync ההיסטורי (Gap #28). תומך `--verify`/`--dry-run`/`--apply`. גיבוי אוטומטי ל-`agents-pre-skill-sync-*.sql`. דורש `PAPERCLIP_BOARD_API_KEY` (Infisical /paperclip ב-nautilus env). idempotent. | חד-פעמי (בוצע 2026-05-04). שמור לרפרנס |
| `sync_agents_across_companies.py` | python | **סנכרון סוכנים מ-CMP (1xxx, master) ל-CMPA (8xxx, mirror)** — Gap #25. משווה adapter_config (model/timeout/instructions/skills/etc), runtime_config (heartbeat), ושדות top-level (budget/metadata/icon/title/role). מסנן אוטומטית local skills שלא קיימים ב-mirror. לוגיקת subset (mirror יכול להחזיק יותר skills כי ה-API מוסיף required runtime skills). תומך `--verify`/`--dry-run`/`--apply [--only NAME]`. גיבוי אוטומטי. דורש `PAPERCLIP_BOARD_API_KEY`. **להריץ אחרי כל שינוי הגדרות ב-CMP.** **⚠ אם `adapter_type` שונה בין CMP ל-CMPA — `--apply` מדלג על הסוכן; `--verify` מדווח אותו רם כ-DRIFT.** בעת מעבר adapter (למשל ל-`deepseek_local`) חובה לעדכן ידנית בשתי החברות. **`--verify` יוצא exit≠0 על כל drift** (needs-sync / adapter-mismatch / missing-in-mirror) — שמיש כ-gate ל-cron/CI (GAP-21/FU-8a). | ידני אחרי כל שינוי |

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""CI guard: fail on undefined-name references (the pyflakes F821 class).
This is the exact bug class behind the case-rename 500 (PR #249): a name
referenced but never imported/defined. It is invisible to tests when it sits
inside a rarely-hit branch or a fire-and-forget ``background_tasks`` callable —
it only NameErrors when that code path runs in production. pyflakes catches it
statically, before merge.
Scope is deliberately narrow — we gate ONLY on undefined names, not on the
other pyflakes findings (unused imports, f-strings without placeholders, unused
locals). Those are style noise, not runtime crashes; gating on them would make
the check too noisy to keep green. Keep this gate high-signal.
Requires pyflakes importable by the running interpreter (the workflow installs
it into a throwaway venv and runs this script with that venv's python).
"""
from __future__ import annotations
import subprocess
import sys
# Paths that ship into the running app / are executed operationally.
TARGETS = ["web", "mcp-server/src", "scripts"]
# pyflakes messages that mean "this reference will NameError at runtime".
FATAL_MARKERS = ("undefined name", "may be undefined")
def main() -> int:
proc = subprocess.run(
[sys.executable, "-m", "pyflakes", *TARGETS],
capture_output=True,
text=True,
)
# pyflakes exits non-zero whenever it has ANY finding; we re-classify so
# that only the fatal class fails the build.
lines = (proc.stdout + proc.stderr).splitlines()
fatal = [ln for ln in lines if any(m in ln for m in FATAL_MARKERS)]
if fatal:
print("❌ undefined name(s) detected — these crash at runtime:\n")
for ln in fatal:
print(f" {ln}")
print(
f"\n{len(fatal)} undefined-name finding(s). Import or define the "
"name, or delete the dead reference."
)
return 1
print(f"✓ no undefined names in: {', '.join(TARGETS)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())