mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-12 03:56:53 +03:00
fix: relocate binary analysis skill
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
"""Atomic file write utility using tempfile + os.rename.
|
||||
|
||||
Provides safe atomic write patterns for all persistent state:
|
||||
manifests, audit logs, cache, and reports.
|
||||
|
||||
Key guarantees:
|
||||
- Writes to a temporary file first (in the same directory as the target).
|
||||
- os.rename is atomic on the same filesystem — it either replaces or it doesn't.
|
||||
- A process crash mid-write leaves the previous valid state intact.
|
||||
- The target file is never partially written or truncated.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Any
|
||||
|
||||
|
||||
def atomic_write_text(
|
||||
path: str,
|
||||
content: str,
|
||||
encoding: str = "utf-8",
|
||||
mode: int = 0o644,
|
||||
) -> None:
|
||||
"""Atomically write text content to a file.
|
||||
|
||||
Writes content to a temporary file in the same directory, then atomically
|
||||
renames it to the target path. If the process crashes mid-write, the
|
||||
temporary file is left behind and the target file is unaffected.
|
||||
|
||||
Args:
|
||||
path: Target file path.
|
||||
content: Text content to write.
|
||||
encoding: Character encoding (default utf-8).
|
||||
mode: File permissions (default 0o644).
|
||||
"""
|
||||
dirname = os.path.dirname(path)
|
||||
fd, tmp_path = tempfile.mkstemp(dir=dirname, suffix=".tmp")
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding=encoding) as f:
|
||||
f.write(content)
|
||||
os.chmod(tmp_path, mode)
|
||||
os.replace(tmp_path, path) # Atomic rename on same filesystem
|
||||
except BaseException:
|
||||
# Clean up temp file on any error, then re-raise
|
||||
with contextlib.suppress(OSError):
|
||||
os.unlink(tmp_path)
|
||||
raise
|
||||
|
||||
|
||||
def atomic_write_json(
|
||||
path: str,
|
||||
data: dict[str, Any],
|
||||
indent: int = 2,
|
||||
encoding: str = "utf-8",
|
||||
mode: int = 0o644,
|
||||
) -> None:
|
||||
"""Atomically write JSON data to a file.
|
||||
|
||||
Serializes the data to JSON, then atomically writes it using
|
||||
atomic_write_text. Invalid JSON data (non-serializable) raises
|
||||
before any file is touched.
|
||||
|
||||
Args:
|
||||
path: Target file path.
|
||||
data: JSON-serializable dict to write.
|
||||
indent: JSON indentation level.
|
||||
encoding: Character encoding.
|
||||
mode: File permissions.
|
||||
"""
|
||||
content = json.dumps(data, indent=indent, ensure_ascii=False)
|
||||
atomic_write_text(path, content, encoding=encoding, mode=mode)
|
||||
|
||||
|
||||
def atomic_append_text(
|
||||
path: str,
|
||||
line: str,
|
||||
encoding: str = "utf-8",
|
||||
mode: int = 0o644,
|
||||
) -> None:
|
||||
"""Atomically append a single line to a file.
|
||||
|
||||
For append-only files like audit logs (events.jsonl), this reads the
|
||||
existing content, appends the line, and writes atomically. This ensures
|
||||
no partial lines or interleaving in the canonical file.
|
||||
|
||||
Args:
|
||||
path: Target file path.
|
||||
line: Single line to append (newline added if not present).
|
||||
encoding: Character encoding.
|
||||
mode: File permissions.
|
||||
"""
|
||||
if not line.endswith("\n"):
|
||||
line += "\n"
|
||||
|
||||
# Read existing content or start fresh
|
||||
try:
|
||||
with open(path, encoding=encoding) as f:
|
||||
existing = f.read()
|
||||
except FileNotFoundError:
|
||||
existing = ""
|
||||
|
||||
new_content = existing + line
|
||||
atomic_write_text(path, new_content, encoding=encoding, mode=mode)
|
||||
|
||||
|
||||
def atomic_write_binary(
|
||||
path: str,
|
||||
data: bytes,
|
||||
mode: int = 0o644,
|
||||
) -> None:
|
||||
"""Atomically write binary data to a file.
|
||||
|
||||
Writes binary data to a temporary file, then renames atomically.
|
||||
|
||||
Args:
|
||||
path: Target file path.
|
||||
data: Binary content to write.
|
||||
mode: File permissions.
|
||||
"""
|
||||
dirname = os.path.dirname(path)
|
||||
fd, tmp_path = tempfile.mkstemp(dir=dirname, suffix=".tmp")
|
||||
try:
|
||||
with os.fdopen(fd, "wb") as f:
|
||||
f.write(data)
|
||||
os.chmod(tmp_path, mode)
|
||||
os.replace(tmp_path, path)
|
||||
except BaseException:
|
||||
with contextlib.suppress(OSError):
|
||||
os.unlink(tmp_path)
|
||||
raise
|
||||
|
||||
|
||||
def atomic_write_lines(
|
||||
path: str,
|
||||
lines: list[str],
|
||||
encoding: str = "utf-8",
|
||||
mode: int = 0o644,
|
||||
) -> None:
|
||||
"""Atomically write a list of lines to a file.
|
||||
|
||||
Each line is written with a trailing newline.
|
||||
|
||||
Args:
|
||||
path: Target file path.
|
||||
lines: List of lines to write.
|
||||
encoding: Character encoding.
|
||||
mode: File permissions.
|
||||
"""
|
||||
content = "".join(line if line.endswith("\n") else line + "\n" for line in lines)
|
||||
atomic_write_text(path, content, encoding=encoding, mode=mode)
|
||||
Reference in New Issue
Block a user