mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-13 04:26:28 +03:00
fix: relocate binary analysis skill
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Diagnostics persistence — accumulate and retrieve diagnostics across commands.
|
||||
|
||||
Diagnostics are persisted as JSONL in project/diagnostics.jsonl, one
|
||||
JSON object per line. Each entry has: severity, category, message, recoverable,
|
||||
command, and timestamp.
|
||||
|
||||
The diagnostics file grows across the project lifecycle: warnings and errors
|
||||
from analyze, triage, suspicious-apis, and other commands are accumulated
|
||||
and retrievable via the `binary diagnostics` command.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from binary_analysis.projects.atomic import atomic_append_text
|
||||
|
||||
DIAGNOSTICS_FILENAME = "diagnostics.jsonl"
|
||||
|
||||
|
||||
def _diagnostics_path(project_path: str) -> str:
|
||||
"""Return the path to the diagnostics file within a project workspace."""
|
||||
return os.path.join(project_path, DIAGNOSTICS_FILENAME)
|
||||
|
||||
|
||||
def persist_diagnostics(
|
||||
project_path: str,
|
||||
diagnostics: list[dict[str, Any]],
|
||||
command: str = "unknown",
|
||||
) -> None:
|
||||
"""Persist diagnostic entries to the project's diagnostics file.
|
||||
|
||||
Each diagnostic entry is augmented with a command field and timestamp
|
||||
before being appended atomically to the JSONL file.
|
||||
|
||||
Args:
|
||||
project_path: Absolute path to the project workspace directory.
|
||||
diagnostics: List of diagnostic dicts to persist.
|
||||
command: Name of the command that produced these diagnostics.
|
||||
"""
|
||||
if not diagnostics:
|
||||
return
|
||||
|
||||
path = _diagnostics_path(project_path)
|
||||
timestamp = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
for diag in diagnostics:
|
||||
entry = {
|
||||
"severity": diag.get("severity", "INFO"),
|
||||
"category": diag.get("category", "general"),
|
||||
"message": diag.get("message", ""),
|
||||
"recoverable": diag.get("recoverable", True),
|
||||
"command": command,
|
||||
"timestamp": timestamp,
|
||||
}
|
||||
# Preserve optional fields
|
||||
if "component" in diag:
|
||||
entry["component"] = diag["component"]
|
||||
if "remediation" in diag:
|
||||
entry["remediation"] = diag["remediation"]
|
||||
|
||||
line = json.dumps(entry, ensure_ascii=False)
|
||||
atomic_append_text(path, line)
|
||||
|
||||
|
||||
def load_diagnostics(project_path: str) -> list[dict[str, Any]]:
|
||||
"""Load all accumulated diagnostics from the project's diagnostics file.
|
||||
|
||||
Returns an empty list if the file does not exist or is empty.
|
||||
|
||||
Args:
|
||||
project_path: Absolute path to the project workspace directory.
|
||||
|
||||
Returns:
|
||||
List of diagnostic dicts ordered by appearance in the file
|
||||
(oldest first).
|
||||
"""
|
||||
path = _diagnostics_path(project_path)
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
|
||||
diagnostics: list[dict[str, Any]] = []
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
for line_num, line in enumerate(f, start=1):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
diagnostics.append(entry)
|
||||
except json.JSONDecodeError:
|
||||
# Skip corrupted lines but note in a diagnostic
|
||||
diagnostics.append(
|
||||
{
|
||||
"severity": "WARNING",
|
||||
"category": "diagnostics-file",
|
||||
"message": f"Corrupted diagnostics entry at line {line_num}",
|
||||
"recoverable": True,
|
||||
"command": "diagnostics",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
return diagnostics
|
||||
|
||||
|
||||
def clear_diagnostics(project_path: str) -> None:
|
||||
"""Remove the diagnostics file (e.g., on project clean).
|
||||
|
||||
Args:
|
||||
project_path: Absolute path to the project workspace directory.
|
||||
"""
|
||||
path = _diagnostics_path(project_path)
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
def get_diagnostics_summary(
|
||||
diagnostics: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
"""Compute a summary of diagnostic entries.
|
||||
|
||||
Args:
|
||||
diagnostics: List of diagnostic dicts.
|
||||
|
||||
Returns:
|
||||
Dict with total count and breakdown by severity.
|
||||
"""
|
||||
by_severity: dict[str, int] = {"INFO": 0, "WARNING": 0, "ERROR": 0}
|
||||
for d in diagnostics:
|
||||
sev = d.get("severity", "INFO")
|
||||
if sev in by_severity:
|
||||
by_severity[sev] += 1
|
||||
|
||||
return {
|
||||
"total": len(diagnostics),
|
||||
"by_severity": by_severity,
|
||||
}
|
||||
Reference in New Issue
Block a user