From 19dbaff74159a531c7abc8e631cdedaf374d7c80 Mon Sep 17 00:00:00 2001 From: dctouch Date: Fri, 10 Apr 2026 09:36:54 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D0=BF=D1=85=D0=B8=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/patch_graphify_hotfix.py | 168 +++++++++++++++++++++++++++++++ scripts/setup_graphify_codex.ps1 | 3 + 2 files changed, 171 insertions(+) create mode 100644 scripts/patch_graphify_hotfix.py diff --git a/scripts/patch_graphify_hotfix.py b/scripts/patch_graphify_hotfix.py new file mode 100644 index 0000000..c9ab2b1 --- /dev/null +++ b/scripts/patch_graphify_hotfix.py @@ -0,0 +1,168 @@ +from __future__ import annotations + +import sys +from pathlib import Path + + +COLLECT_FILES_FUNCTION = """def collect_files(target: Path, *, follow_symlinks: bool = False) -> list[Path]: + if target.is_file(): + return [target] + _EXTENSIONS = { + ".py", ".js", ".ts", ".tsx", ".go", ".rs", + ".java", ".c", ".h", ".cpp", ".cc", ".cxx", ".hpp", + ".rb", ".cs", ".kt", ".kts", ".scala", ".php", ".swift", + ".lua", ".toc", ".zig", ".ps1", + ".m", ".mm", + } + _SKIP_DIRS = { + "venv", ".venv", "env", ".env", + "node_modules", "__pycache__", ".git", + "dist", "build", "target", "out", + "site-packages", "lib64", + ".pytest_cache", ".mypy_cache", ".ruff_cache", + ".tox", ".eggs", + } + + def _is_noise_dir(part: str) -> bool: + if part in _SKIP_DIRS: + return True + if part.endswith("_venv") or part.endswith("_env"): + return True + if part.endswith(".egg-info"): + return True + return False + + def _load_graphifyignore(root: Path) -> list[str]: + ignore_file = root / ".graphifyignore" + if not ignore_file.exists(): + return [] + patterns: list[str] = [] + for line in ignore_file.read_text(errors="ignore").splitlines(): + line = line.strip() + if line and not line.startswith("#"): + patterns.append(line) + return patterns + + def _is_ignored(path: Path, root: Path, patterns: list[str]) -> bool: + if not patterns: + return False + try: + rel = str(path.relative_to(root)) + except ValueError: + return False + rel = rel.replace(os.sep, "/") + parts = rel.split("/") + for pattern in patterns: + p = pattern.strip("/") + if not p: + continue + if fnmatch.fnmatch(rel, p): + return True + if fnmatch.fnmatch(path.name, p): + return True + for i, part in enumerate(parts): + if fnmatch.fnmatch(part, p): + return True + if fnmatch.fnmatch("/".join(parts[: i + 1]), p): + return True + return False + + ignore_patterns = _load_graphifyignore(target) + + # Walk with symlink following + cycle detection + results = [] + for dirpath, dirnames, filenames in os.walk(target, followlinks=follow_symlinks): + dp = Path(dirpath) + dirnames[:] = [ + d for d in dirnames + if not d.startswith(".") + and not _is_noise_dir(d) + and not _is_ignored(dp / d, target, ignore_patterns) + ] + if os.path.islink(dirpath): + real = os.path.realpath(dirpath) + parent_real = os.path.realpath(os.path.dirname(dirpath)) + if parent_real == real or parent_real.startswith(real + os.sep): + dirnames.clear() + continue + if any(part.startswith(".") for part in dp.parts): + continue + for fname in filenames: + p = dp / fname + if fname.startswith("."): + continue + if _is_ignored(p, target, ignore_patterns): + continue + if p.suffix.lower() in _EXTENSIONS: + results.append(p) + return sorted(results) +""" + + +def _patch_extract_file(path: Path) -> bool: + source = path.read_text(encoding="utf-8") + changed = False + + if "import fnmatch" not in source: + marker = "from __future__ import annotations\n" + if marker in source: + source = source.replace(marker, marker + "import fnmatch\n", 1) + changed = True + else: + raise RuntimeError("Cannot patch extract.py: import marker not found.") + + start = source.find("def collect_files(target: Path, *, follow_symlinks: bool = False) -> list[Path]:") + end = source.find('\n\nif __name__ == "__main__":', start) + if start == -1 or end == -1: + raise RuntimeError("Cannot patch extract.py: collect_files function block not found.") + + current_block = source[start:end] + if current_block.strip() != COLLECT_FILES_FUNCTION.strip(): + source = source[:start] + COLLECT_FILES_FUNCTION + source[end:] + changed = True + + if changed: + path.write_text(source, encoding="utf-8") + return changed + + +def _patch_watch_file(path: Path) -> bool: + source = path.read_text(encoding="utf-8") + target = '(out / "GRAPH_REPORT.md").write_text(report)' + patched = '(out / "GRAPH_REPORT.md").write_text(report, encoding="utf-8")' + if target not in source: + if patched in source: + return False + raise RuntimeError("Cannot patch watch.py: write_text(report) call not found.") + source = source.replace(target, patched, 1) + path.write_text(source, encoding="utf-8") + return True + + +def main() -> int: + try: + import graphify # noqa: PLC0415 + except Exception as exc: # pragma: no cover + print(f"[graphify-hotfix] graphify import failed: {exc}", file=sys.stderr) + return 1 + + package_dir = Path(graphify.__file__).resolve().parent + extract_path = package_dir / "extract.py" + watch_path = package_dir / "watch.py" + + if not extract_path.exists() or not watch_path.exists(): + print("[graphify-hotfix] graphify package files not found", file=sys.stderr) + return 1 + + changed_extract = _patch_extract_file(extract_path) + changed_watch = _patch_watch_file(watch_path) + + status = [] + status.append("extract.py: patched" if changed_extract else "extract.py: already patched") + status.append("watch.py: patched" if changed_watch else "watch.py: already patched") + print("[graphify-hotfix] " + " | ".join(status)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/setup_graphify_codex.ps1 b/scripts/setup_graphify_codex.ps1 index 7f73749..fc0432c 100644 --- a/scripts/setup_graphify_codex.ps1 +++ b/scripts/setup_graphify_codex.ps1 @@ -14,6 +14,7 @@ if (-not $SkipPipInstall) { py -m graphify install --platform codex py -m graphify codex install +py scripts/patch_graphify_hotfix.py if ($InstallGitHooks) { py -m graphify hook install @@ -24,3 +25,5 @@ Write-Host "Graphify configured for Codex in $repoRoot" Write-Host "Use these commands inside Codex:" Write-Host " `$graphify ." Write-Host " `$graphify . --update" +Write-Host "" +Write-Host "Applied local graphify hotfix (fast collect_files + UTF-8 report write)."