Skip to content

Shiro Daily Scan

Shiro Daily Scan #119

name: Shiro Daily Scan
on:
workflow_dispatch:
schedule:
- cron: "0 12 * * *"
permissions:
contents: read
jobs:
gemini-warden:
name: Gemini Warden Daily Scan
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Run Gemini warden scan
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GEMINI_MODEL: gemini-2.5-flash
GEMINI_CONTEXT_PATH: GEMINI.md
GEMINI_REPORT_PATH: logs/health_checks/cloud_wardens/gemini_warden_status.json
GEMINI_MANIFEST_PATH: logs/health_checks/cloud_wardens/gemini_warden_manifest.json
run: |
python3 - <<'PY'
import hashlib
import json
import os
import subprocess
import traceback
import urllib.error
import urllib.request
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
repo = Path(os.environ["GITHUB_WORKSPACE"]).resolve()
report_path = repo / os.environ["GEMINI_REPORT_PATH"]
manifest_path = repo / os.environ["GEMINI_MANIFEST_PATH"]
report_path.parent.mkdir(parents=True, exist_ok=True)
def utcnow() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def write_report(payload: dict) -> None:
report_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
def list_files() -> list[str]:
result = subprocess.run(
["git", "ls-files", "-z"],
cwd=repo,
check=True,
capture_output=True,
)
return [item for item in result.stdout.decode("utf-8").split("\0") if item]
def sha256_for(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def build_inventory(files: list[str]) -> tuple[dict, dict]:
manifest = {}
extensions = Counter()
top_levels = Counter()
total_bytes = 0
for rel_path in files:
relative_path = Path(rel_path)
path = repo / relative_path
if not path.is_file():
continue
manifest[rel_path] = sha256_for(path)
suffix = path.suffix or "<no_ext>"
extensions[suffix] += 1
top_levels[relative_path.parts[0] if relative_path.parts else "."] += 1
total_bytes += path.stat().st_size
summary = {
"timestamp": utcnow(),
"repository": os.environ.get("GITHUB_REPOSITORY"),
"ref": os.environ.get("GITHUB_REF"),
"commit": os.environ.get("GITHUB_SHA"),
"file_count": len(manifest),
"total_bytes": total_bytes,
"top_level_counts": dict(top_levels.most_common(20)),
"extension_counts": dict(extensions.most_common(20)),
"sample_files": list(sorted(manifest))[:200],
}
return manifest, summary
def extract_text(response_json: dict) -> str:
candidates = response_json.get("candidates") or []
if not candidates:
return ""
parts = candidates[0].get("content", {}).get("parts") or []
return "".join(part.get("text", "") for part in parts).strip()
def extract_json_block(text: str):
candidate = text.strip()
if candidate.startswith("```"):
lines = candidate.splitlines()
if lines and lines[0].startswith("```"):
lines = lines[1:]
if lines and lines[-1].startswith("```"):
lines = lines[:-1]
candidate = "\n".join(lines).strip()
start = candidate.find("{")
end = candidate.rfind("}")
if start == -1 or end == -1 or end < start:
return None
try:
return json.loads(candidate[start : end + 1])
except json.JSONDecodeError:
return None
def query_gemini(prompt: str) -> tuple[dict | None, str]:
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
raise RuntimeError("GEMINI_API_KEY is not configured")
model = os.environ["GEMINI_MODEL"]
url = (
"https://generativelanguage.googleapis.com/v1beta/models/"
f"{model}:generateContent?key={api_key}"
)
payload = {
"contents": [
{
"parts": [
{
"text": prompt
}
]
}
],
"generationConfig": {
"temperature": 0.1,
"responseMimeType": "application/json"
}
}
request = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=120) as response:
response_json = json.loads(response.read().decode("utf-8"))
response_text = extract_text(response_json)
return extract_json_block(response_text), response_text
try:
files = list_files()
manifest, summary = build_inventory(files)
manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
context = (repo / os.environ["GEMINI_CONTEXT_PATH"]).read_text(encoding="utf-8")
prompt = (
f"{context}\n\n"
"Task: Act as the daily Gemini steward/warden for this repository. "
"Analyze the repository summary below and return strict JSON only.\n\n"
f"Repository summary:\n{json.dumps(summary, indent=2)}\n"
)
parsed, raw_text = query_gemini(prompt)
report = {
"warden_name": "gemini_warden",
"runtime": "github_actions",
"status": "healthy",
"timestamp": utcnow(),
"workflow": "shiro-daily-scan",
"model_name": os.environ["GEMINI_MODEL"],
"scan": summary,
"findings": [],
"issues": [],
"recommendations": [],
"raw_response": raw_text,
}
if isinstance(parsed, dict):
report["status"] = parsed.get("status", "healthy")
report["summary"] = parsed.get("summary", "")
report["findings"] = parsed.get("findings", [])
report["issues"] = parsed.get("issues", [])
report["recommendations"] = parsed.get("recommendations", [])
else:
report["status"] = "warning"
report["issues"] = ["Gemini response was not valid JSON"]
report["recommendations"] = [
"Inspect the raw_response field and adjust the prompt if needed"
]
write_report(report)
except Exception as exc:
failure_report = {
"warden_name": "gemini_warden",
"runtime": "github_actions",
"status": "critical",
"timestamp": utcnow(),
"workflow": "shiro-daily-scan",
"model_name": os.environ.get("GEMINI_MODEL", "gemini-2.5-flash"),
"scan": {
"repository": os.environ.get("GITHUB_REPOSITORY"),
"ref": os.environ.get("GITHUB_REF"),
"commit": os.environ.get("GITHUB_SHA"),
},
"findings": [],
"issues": [str(exc)],
"recommendations": [
"Review the workflow logs and rerun the Gemini warden scan"
],
"traceback": traceback.format_exc(),
}
write_report(failure_report)
if not manifest_path.exists():
manifest_path.write_text("{}", encoding="utf-8")
PY
- name: Show Gemini warden report
if: always()
run: |
python3 - <<'PY'
import json
from pathlib import Path
report_path = Path("logs/health_checks/cloud_wardens/gemini_warden_status.json")
if report_path.exists():
print(json.dumps(json.loads(report_path.read_text(encoding="utf-8")), indent=2))
else:
print("Gemini warden report missing")
PY
- name: Upload Gemini warden artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: gemini-warden-${{ github.run_id }}
path: |
logs/health_checks/cloud_wardens/gemini_warden_status.json
logs/health_checks/cloud_wardens/gemini_warden_manifest.json
if-no-files-found: error
# Keep several daily runs available for download while health checks only
# require the latest artifact to be fresher than 36 hours.
retention-days: 30