feat: consolidate legacy workflows into gyxx-flow

This commit is contained in:
2026-07-28 14:51:15 +08:00
commit c23b62a8c8
374 changed files with 132990 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
from __future__ import annotations
import hashlib
import os
from collections.abc import Iterable
from pathlib import Path
from typing import TypedDict
from .models import DEFAULT_CODE_EXTENSIONS
class Aggregate(TypedDict):
file_count: int
total_bytes: int
class DataSummary(Aggregate):
by_extension: dict[str, Aggregate]
by_top_level: dict[str, Aggregate]
_EXCLUDED_DIRECTORY_NAMES = frozenset(
{
".cache",
".git",
".hermes_tmp",
".mypy_cache",
".pytest_cache",
".ruff_cache",
".tox",
".venv",
"__pycache__",
"cache",
"data",
"debug",
"evidence",
"log",
"logs",
"node_modules",
"reports",
"runtime",
"state",
"tmp",
"var",
}
)
_SENSITIVE_DIRECTORY_MARKERS = ("cookie", "profile")
_SENSITIVE_FILE_NAMES = frozenset(
{
".env",
"credentials.json",
"secrets.json",
"service-account.json",
"service_account.json",
}
)
_SENSITIVE_FILE_MARKERS = ("cookie", "credential", "secret", "token")
def _is_excluded_directory(name: str) -> bool:
normalized = name.casefold()
return normalized in _EXCLUDED_DIRECTORY_NAMES or any(
marker in normalized for marker in _SENSITIVE_DIRECTORY_MARKERS
)
def _is_sensitive_file(name: str) -> bool:
normalized = name.casefold()
if normalized in _SENSITIVE_FILE_NAMES or normalized.startswith(".env."):
return True
stem = Path(normalized).stem
return any(marker in stem for marker in _SENSITIVE_FILE_MARKERS)
def _walk_files(
root: Path,
*,
exclude_runtime: bool,
excluded_roots: frozenset[Path] = frozenset(),
) -> Iterable[Path]:
if not root.is_dir():
raise FileNotFoundError(f"inventory root is not a directory: {root}")
for directory, dirnames, filenames in os.walk(root, followlinks=False):
if exclude_runtime:
dirnames[:] = sorted(
name
for name in dirnames
if not _is_excluded_directory(name)
and not (Path(directory) / name).is_symlink()
and (Path(directory) / name).resolve(strict=False) not in excluded_roots
)
else:
dirnames[:] = sorted(
name for name in dirnames if not (Path(directory) / name).is_symlink()
)
for filename in sorted(filenames):
path = Path(directory) / filename
if not path.is_symlink():
yield path
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def collect_code_inventory(
root: Path,
*,
allowed_extensions: frozenset[str] = DEFAULT_CODE_EXTENSIONS,
excluded_roots: Iterable[Path] = (),
) -> list[dict[str, str | int]]:
"""Hash code-like files without retaining file contents or config values."""
root = Path(root)
normalized_extensions = frozenset(extension.casefold() for extension in allowed_extensions)
normalized_excluded_roots = frozenset(
Path(excluded_root).resolve(strict=False) for excluded_root in excluded_roots
)
inventory: list[dict[str, str | int]] = []
for path in _walk_files(
root, exclude_runtime=True, excluded_roots=normalized_excluded_roots
):
if path.suffix.casefold() not in normalized_extensions or _is_sensitive_file(path.name):
continue
stat = path.stat()
inventory.append(
{
"relative_path": path.relative_to(root).as_posix(),
"size_bytes": stat.st_size,
"mtime_ns": stat.st_mtime_ns,
"sha256": _sha256(path),
}
)
return sorted(inventory, key=lambda item: str(item["relative_path"]).casefold())
def _increment(target: dict[str, Aggregate], key: str, size: int) -> None:
aggregate = target.setdefault(key, {"file_count": 0, "total_bytes": 0})
aggregate["file_count"] += 1
aggregate["total_bytes"] += size
def collect_data_summary(root: Path) -> DataSummary:
"""Summarize a data tree without exposing names, paths, times, hashes, or values."""
root = Path(root)
summary: DataSummary = {
"file_count": 0,
"total_bytes": 0,
"by_extension": {},
"by_top_level": {},
}
for path in _walk_files(root, exclude_runtime=False):
size = path.stat().st_size
relative = path.relative_to(root)
top_level = relative.parts[0] if len(relative.parts) > 1 else "."
extension = path.suffix.casefold() or "[no_extension]"
summary["file_count"] += 1
summary["total_bytes"] += size
_increment(summary["by_extension"], extension, size)
_increment(summary["by_top_level"], top_level, size)
summary["by_extension"] = dict(sorted(summary["by_extension"].items()))
summary["by_top_level"] = dict(sorted(summary["by_top_level"].items()))
return summary