chore: checkpoint local wip for issue 892

This commit is contained in:
Alexander Whitestone
2026-04-05 13:31:06 -04:00
parent f8f5d08678
commit d7abb7db36
47 changed files with 113 additions and 149 deletions

View File

@@ -1,6 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Tiny auth gate for nginx auth_request. Sets a cookie after successful basic auth.""" """Tiny auth gate for nginx auth_request. Sets a cookie after successful basic auth."""
import hashlib, hmac, http.server, time, base64, os, sys import base64
import hashlib
import hmac
import http.server
import os
import sys
import time
SECRET = os.environ.get("AUTH_GATE_SECRET", "") SECRET = os.environ.get("AUTH_GATE_SECRET", "")
USER = os.environ.get("AUTH_GATE_USER", "") USER = os.environ.get("AUTH_GATE_USER", "")

View File

@@ -1,5 +1,4 @@
import os
import sys import sys
from pathlib import Path from pathlib import Path
@@ -8,6 +7,7 @@ sys.path.insert(0, str(Path(__file__).parent / "src"))
from timmy.memory_system import memory_store from timmy.memory_system import memory_store
def index_research_documents(): def index_research_documents():
research_dir = Path("docs/research") research_dir = Path("docs/research")
if not research_dir.is_dir(): if not research_dir.is_dir():

View File

@@ -1,9 +1,7 @@
from logging.config import fileConfig from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context from alembic import context
from sqlalchemy import engine_from_config, pool
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
@@ -19,7 +17,7 @@ if config.config_file_name is not None:
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
from src.dashboard.models.database import Base from src.dashboard.models.database import Base
from src.dashboard.models.calm import Task, JournalEntry
target_metadata = Base.metadata target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py, # other values from the config, defined by the needs of env.py,

View File

@@ -5,17 +5,16 @@ Revises:
Create Date: 2026-03-02 10:57:55.537090 Create Date: 2026-03-02 10:57:55.537090
""" """
from typing import Sequence, Union from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '0093c15b4bbf' revision: str = '0093c15b4bbf'
down_revision: Union[str, Sequence[str], None] = None down_revision: str | Sequence[str] | None = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: str | Sequence[str] | None = None
depends_on: Union[str, Sequence[str], None] = None depends_on: str | Sequence[str] | None = None
def upgrade() -> None: def upgrade() -> None:

View File

@@ -5,7 +5,6 @@ Usage:
python scripts/add_pytest_markers.py python scripts/add_pytest_markers.py
""" """
import re
from pathlib import Path from pathlib import Path
@@ -93,7 +92,7 @@ def main():
print(f"⏭️ {rel_path:<50} (already marked)") print(f"⏭️ {rel_path:<50} (already marked)")
print(f"\n📊 Total files marked: {marked_count}") print(f"\n📊 Total files marked: {marked_count}")
print(f"\n✨ Pytest markers configured. Run 'pytest -m unit' to test specific categories.") print("\n✨ Pytest markers configured. Run 'pytest -m unit' to test specific categories.")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,8 +1,7 @@
import os
def fix_l402_proxy(): def fix_l402_proxy():
path = "src/timmy_serve/l402_proxy.py" path = "src/timmy_serve/l402_proxy.py"
with open(path, "r") as f: with open(path) as f:
content = f.read() content = f.read()
# 1. Add hmac_secret to Macaroon dataclass # 1. Add hmac_secret to Macaroon dataclass
@@ -132,7 +131,7 @@ if _MACAROON_SECRET_RAW == _MACAROON_SECRET_DEFAULT or _HMAC_SECRET_RAW == _HMAC
def fix_xss(): def fix_xss():
# Fix chat_message.html # Fix chat_message.html
path = "src/dashboard/templates/partials/chat_message.html" path = "src/dashboard/templates/partials/chat_message.html"
with open(path, "r") as f: with open(path) as f:
content = f.read() content = f.read()
content = content.replace("{{ user_message }}", "{{ user_message | e }}") content = content.replace("{{ user_message }}", "{{ user_message | e }}")
content = content.replace("{{ response }}", "{{ response | e }}") content = content.replace("{{ response }}", "{{ response | e }}")
@@ -142,7 +141,7 @@ def fix_xss():
# Fix history.html # Fix history.html
path = "src/dashboard/templates/partials/history.html" path = "src/dashboard/templates/partials/history.html"
with open(path, "r") as f: with open(path) as f:
content = f.read() content = f.read()
content = content.replace("{{ msg.content }}", "{{ msg.content | e }}") content = content.replace("{{ msg.content }}", "{{ msg.content | e }}")
with open(path, "w") as f: with open(path, "w") as f:
@@ -150,7 +149,7 @@ def fix_xss():
# Fix briefing.html # Fix briefing.html
path = "src/dashboard/templates/briefing.html" path = "src/dashboard/templates/briefing.html"
with open(path, "r") as f: with open(path) as f:
content = f.read() content = f.read()
content = content.replace("{{ briefing.summary }}", "{{ briefing.summary | e }}") content = content.replace("{{ briefing.summary }}", "{{ briefing.summary | e }}")
with open(path, "w") as f: with open(path, "w") as f:
@@ -158,7 +157,7 @@ def fix_xss():
# Fix approval_card_single.html # Fix approval_card_single.html
path = "src/dashboard/templates/partials/approval_card_single.html" path = "src/dashboard/templates/partials/approval_card_single.html"
with open(path, "r") as f: with open(path) as f:
content = f.read() content = f.read()
content = content.replace("{{ item.title }}", "{{ item.title | e }}") content = content.replace("{{ item.title }}", "{{ item.title | e }}")
content = content.replace("{{ item.description }}", "{{ item.description | e }}") content = content.replace("{{ item.description }}", "{{ item.description | e }}")
@@ -168,7 +167,7 @@ def fix_xss():
# Fix marketplace.html # Fix marketplace.html
path = "src/dashboard/templates/marketplace.html" path = "src/dashboard/templates/marketplace.html"
with open(path, "r") as f: with open(path) as f:
content = f.read() content = f.read()
content = content.replace("{{ agent.name }}", "{{ agent.name | e }}") content = content.replace("{{ agent.name }}", "{{ agent.name | e }}")
content = content.replace("{{ agent.role }}", "{{ agent.role | e }}") content = content.replace("{{ agent.role }}", "{{ agent.role | e }}")

View File

@@ -8,8 +8,7 @@ from existing history so the LOOPSTAT panel isn't empty.
import json import json
import os import os
import re import re
import subprocess from datetime import UTC, datetime
from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
@@ -227,7 +226,7 @@ def generate_summary(entries: list[dict]):
stats["avg_duration"] = round(stats["total_duration"] / stats["count"]) stats["avg_duration"] = round(stats["total_duration"] / stats["count"])
summary = { summary = {
"updated_at": datetime.now(timezone.utc).isoformat(), "updated_at": datetime.now(UTC).isoformat(),
"window": len(recent), "window": len(recent),
"total_cycles": len(entries), "total_cycles": len(entries),
"success_rate": round(len(successes) / len(recent), 2) if recent else 0, "success_rate": round(len(successes) / len(recent), 2) if recent else 0,

View File

@@ -17,7 +17,7 @@ import importlib.util
import json import json
import sys import sys
import time import time
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
import requests import requests
@@ -216,7 +216,7 @@ def generate_markdown(all_results: dict, run_date: str) -> str:
lines.append(f"- **Result:** {bres.get('detail', bres.get('error', 'n/a'))}") lines.append(f"- **Result:** {bres.get('detail', bres.get('error', 'n/a'))}")
snippet = bres.get("code_snippet", "") snippet = bres.get("code_snippet", "")
if snippet: if snippet:
lines.append(f"- **Generated code snippet:**") lines.append("- **Generated code snippet:**")
lines.append(" ```python") lines.append(" ```python")
for ln in snippet.splitlines()[:8]: for ln in snippet.splitlines()[:8]:
lines.append(f" {ln}") lines.append(f" {ln}")
@@ -287,7 +287,7 @@ def parse_args() -> argparse.Namespace:
def main() -> int: def main() -> int:
args = parse_args() args = parse_args()
run_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") run_date = datetime.now(UTC).strftime("%Y-%m-%d %H:%M UTC")
print(f"Model Benchmark Suite — {run_date}") print(f"Model Benchmark Suite — {run_date}")
print(f"Testing {len(args.models)} model(s): {', '.join(args.models)}") print(f"Testing {len(args.models)} model(s): {', '.join(args.models)}")

View File

@@ -46,8 +46,7 @@ import argparse
import json import json
import re import re
import subprocess import subprocess
import sys from datetime import UTC, datetime
from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent REPO_ROOT = Path(__file__).resolve().parent.parent
@@ -91,7 +90,7 @@ def _epoch_tag(now: datetime | None = None) -> tuple[str, dict]:
When the date rolls over, the counter resets to 1. When the date rolls over, the counter resets to 1.
""" """
if now is None: if now is None:
now = datetime.now(timezone.utc) now = datetime.now(UTC)
iso_cal = now.isocalendar() # (year, week, weekday) iso_cal = now.isocalendar() # (year, week, weekday)
week = iso_cal[1] week = iso_cal[1]
@@ -221,7 +220,7 @@ def update_summary() -> None:
for k, v in sorted(by_weekday.items())} for k, v in sorted(by_weekday.items())}
summary = { summary = {
"updated_at": datetime.now(timezone.utc).isoformat(), "updated_at": datetime.now(UTC).isoformat(),
"current_epoch": current_epoch, "current_epoch": current_epoch,
"window": len(recent), "window": len(recent),
"measured_cycles": len(measured), "measured_cycles": len(measured),
@@ -293,7 +292,7 @@ def main() -> None:
truly_success = args.success and args.main_green truly_success = args.success and args.main_green
# Generate epoch turnover tag # Generate epoch turnover tag
now = datetime.now(timezone.utc) now = datetime.now(UTC)
epoch_tag, epoch_parts = _epoch_tag(now) epoch_tag, epoch_parts = _epoch_tag(now)
entry = { entry = {

View File

@@ -11,7 +11,6 @@ Usage: python scripts/dev_server.py [--port PORT]
""" """
import argparse import argparse
import datetime
import os import os
import socket import socket
import subprocess import subprocess
@@ -81,8 +80,8 @@ def _ollama_url() -> str:
def _smoke_ollama(url: str) -> str: def _smoke_ollama(url: str) -> str:
"""Quick connectivity check against Ollama.""" """Quick connectivity check against Ollama."""
import urllib.request
import urllib.error import urllib.error
import urllib.request
try: try:
req = urllib.request.Request(url, method="GET") req = urllib.request.Request(url, method="GET")
@@ -101,14 +100,14 @@ def _print_banner(port: int) -> None:
hr = "" * 62 hr = "" * 62
print(flush=True) print(flush=True)
print(f" {hr}") print(f" {hr}")
print(f" ┃ Timmy Time — Development Server") print(" ┃ Timmy Time — Development Server")
print(f" {hr}") print(f" {hr}")
print() print()
print(f" Dashboard: http://localhost:{port}") print(f" Dashboard: http://localhost:{port}")
print(f" API docs: http://localhost:{port}/docs") print(f" API docs: http://localhost:{port}/docs")
print(f" Health: http://localhost:{port}/health") print(f" Health: http://localhost:{port}/health")
print() print()
print(f" ── Status ──────────────────────────────────────────────") print(" ── Status ──────────────────────────────────────────────")
print(f" Backend: {ollama_url} [{ollama_status}]") print(f" Backend: {ollama_url} [{ollama_status}]")
print(f" Version: {version}") print(f" Version: {version}")
print(f" Git commit: {git}") print(f" Git commit: {git}")

View File

@@ -319,9 +319,9 @@ def main(argv: list[str] | None = None) -> int:
print(f"Exported {count} training examples to: {args.output}") print(f"Exported {count} training examples to: {args.output}")
print() print()
print("Next steps:") print("Next steps:")
print(f" mkdir -p ~/timmy-lora-training") print(" mkdir -p ~/timmy-lora-training")
print(f" cp {args.output} ~/timmy-lora-training/train.jsonl") print(f" cp {args.output} ~/timmy-lora-training/train.jsonl")
print(f" python scripts/lora_finetune.py --data ~/timmy-lora-training") print(" python scripts/lora_finetune.py --data ~/timmy-lora-training")
else: else:
print("No training examples exported.") print("No training examples exported.")
return 1 return 1

View File

@@ -18,9 +18,8 @@ Called by: deep_triage.sh (before the LLM triage), timmy-loop.sh (every 50 cycle
from __future__ import annotations from __future__ import annotations
import json import json
import sys
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timezone, timedelta from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent REPO_ROOT = Path(__file__).resolve().parent.parent
@@ -52,7 +51,7 @@ def parse_ts(ts_str: str) -> datetime | None:
try: try:
dt = datetime.fromisoformat(ts_str.replace("Z", "+00:00")) dt = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc) dt = dt.replace(tzinfo=UTC)
return dt return dt
except (ValueError, TypeError): except (ValueError, TypeError):
return None return None
@@ -60,7 +59,7 @@ def parse_ts(ts_str: str) -> datetime | None:
def window(entries: list[dict], days: int) -> list[dict]: def window(entries: list[dict], days: int) -> list[dict]:
"""Filter entries to the last N days.""" """Filter entries to the last N days."""
cutoff = datetime.now(timezone.utc) - timedelta(days=days) cutoff = datetime.now(UTC) - timedelta(days=days)
result = [] result = []
for e in entries: for e in entries:
ts = parse_ts(e.get("timestamp", "")) ts = parse_ts(e.get("timestamp", ""))
@@ -344,7 +343,7 @@ def main() -> None:
recommendations = generate_recommendations(trends, types, repeats, outliers, triage_eff) recommendations = generate_recommendations(trends, types, repeats, outliers, triage_eff)
insights = { insights = {
"generated_at": datetime.now(timezone.utc).isoformat(), "generated_at": datetime.now(UTC).isoformat(),
"total_cycles_analyzed": len(cycles), "total_cycles_analyzed": len(cycles),
"trends": trends, "trends": trends,
"by_type": types, "by_type": types,
@@ -371,7 +370,7 @@ def main() -> None:
header += f" · current epoch: {latest_epoch}" header += f" · current epoch: {latest_epoch}"
print(header) print(header)
print(f"\n TRENDS (7d vs previous 7d):") print("\n TRENDS (7d vs previous 7d):")
r7 = trends["recent_7d"] r7 = trends["recent_7d"]
p7 = trends["previous_7d"] p7 = trends["previous_7d"]
print(f" Cycles: {r7['count']:>3d} (was {p7['count']})") print(f" Cycles: {r7['count']:>3d} (was {p7['count']})")
@@ -383,14 +382,14 @@ def main() -> None:
print(f" PRs merged: {r7['prs_merged']:>3d} (was {p7['prs_merged']})") print(f" PRs merged: {r7['prs_merged']:>3d} (was {p7['prs_merged']})")
print(f" Lines net: {r7['lines_net']:>+5d}") print(f" Lines net: {r7['lines_net']:>+5d}")
print(f"\n BY TYPE:") print("\n BY TYPE:")
for t, info in sorted(types.items(), key=lambda x: -x[1]["count"]): for t, info in sorted(types.items(), key=lambda x: -x[1]["count"]):
print(f" {t:12s} n={info['count']:>2d} " print(f" {t:12s} n={info['count']:>2d} "
f"ok={info['success_rate']*100:>3.0f}% " f"ok={info['success_rate']*100:>3.0f}% "
f"avg={info['avg_duration']//60}m{info['avg_duration']%60:02d}s") f"avg={info['avg_duration']//60}m{info['avg_duration']%60:02d}s")
if repeats: if repeats:
print(f"\n REPEAT FAILURES:") print("\n REPEAT FAILURES:")
for rf in repeats[:3]: for rf in repeats[:3]:
print(f" #{rf['issue']} failed {rf['failure_count']}x") print(f" #{rf['issue']} failed {rf['failure_count']}x")

View File

@@ -360,7 +360,7 @@ def main(argv: list[str] | None = None) -> int:
return rc return rc
# Default: train # Default: train
print(f"Starting LoRA fine-tuning") print("Starting LoRA fine-tuning")
print(f" Model: {model_path}") print(f" Model: {model_path}")
print(f" Data: {args.data}") print(f" Data: {args.data}")
print(f" Adapter path: {args.adapter_path}") print(f" Adapter path: {args.adapter_path}")

View File

@@ -9,11 +9,10 @@ This script runs before commits to catch issues early:
- Syntax errors in test files - Syntax errors in test files
""" """
import sys
import subprocess
from pathlib import Path
import ast import ast
import re import subprocess
import sys
from pathlib import Path
def check_imports(): def check_imports():
@@ -70,7 +69,7 @@ def check_test_syntax():
for test_file in tests_dir.rglob("test_*.py"): for test_file in tests_dir.rglob("test_*.py"):
try: try:
with open(test_file, "r") as f: with open(test_file) as f:
ast.parse(f.read()) ast.parse(f.read())
print(f"{test_file.relative_to(tests_dir.parent)} has valid syntax") print(f"{test_file.relative_to(tests_dir.parent)} has valid syntax")
except SyntaxError as e: except SyntaxError as e:
@@ -86,7 +85,7 @@ def check_platform_specific_tests():
# Check for hardcoded /Users/ paths in tests # Check for hardcoded /Users/ paths in tests
tests_dir = Path("tests").resolve() tests_dir = Path("tests").resolve()
for test_file in tests_dir.rglob("test_*.py"): for test_file in tests_dir.rglob("test_*.py"):
with open(test_file, "r") as f: with open(test_file) as f:
content = f.read() content = f.read()
if 'startswith("/Users/")' in content: if 'startswith("/Users/")' in content:
issues.append( issues.append(
@@ -110,7 +109,7 @@ def check_docker_availability():
if docker_test_files: if docker_test_files:
for test_file in docker_test_files: for test_file in docker_test_files:
with open(test_file, "r") as f: with open(test_file) as f:
content = f.read() content = f.read()
has_skipif = "@pytest.mark.skipif" in content or "pytestmark = pytest.mark.skipif" in content has_skipif = "@pytest.mark.skipif" in content or "pytestmark = pytest.mark.skipif" in content
if not has_skipif and "docker" in content.lower(): if not has_skipif and "docker" in content.lower():

View File

@@ -83,8 +83,8 @@ def test_tcp_connection(host: str, port: int, timeout: float) -> tuple[bool, soc
return True, sock return True, sock
except OSError as exc: except OSError as exc:
print(f" ✗ Connection failed: {exc}") print(f" ✗ Connection failed: {exc}")
print(f" Checklist:") print(" Checklist:")
print(f" - Is Bannerlord running with GABS mod enabled?") print(" - Is Bannerlord running with GABS mod enabled?")
print(f" - Is port {port} open in Windows Firewall?") print(f" - Is port {port} open in Windows Firewall?")
print(f" - Is the VM IP correct? (got: {host})") print(f" - Is the VM IP correct? (got: {host})")
return False, None return False, None
@@ -92,7 +92,7 @@ def test_tcp_connection(host: str, port: int, timeout: float) -> tuple[bool, soc
def test_ping(sock: socket.socket) -> bool: def test_ping(sock: socket.socket) -> bool:
"""PASS: JSON-RPC ping returns a 2.0 response.""" """PASS: JSON-RPC ping returns a 2.0 response."""
print(f"\n[2/4] JSON-RPC ping") print("\n[2/4] JSON-RPC ping")
try: try:
t0 = time.monotonic() t0 = time.monotonic()
resp = _rpc(sock, "ping", req_id=1) resp = _rpc(sock, "ping", req_id=1)
@@ -109,7 +109,7 @@ def test_ping(sock: socket.socket) -> bool:
def test_game_state(sock: socket.socket) -> bool: def test_game_state(sock: socket.socket) -> bool:
"""PASS: get_game_state returns a result (game must be in a campaign).""" """PASS: get_game_state returns a result (game must be in a campaign)."""
print(f"\n[3/4] get_game_state call") print("\n[3/4] get_game_state call")
try: try:
t0 = time.monotonic() t0 = time.monotonic()
resp = _rpc(sock, "get_game_state", req_id=2) resp = _rpc(sock, "get_game_state", req_id=2)
@@ -120,7 +120,7 @@ def test_game_state(sock: socket.socket) -> bool:
if code == -32601: if code == -32601:
# Method not found — GABS version may not expose this method # Method not found — GABS version may not expose this method
print(f" ~ Method not available ({elapsed_ms:.1f} ms): {msg}") print(f" ~ Method not available ({elapsed_ms:.1f} ms): {msg}")
print(f" This is acceptable if game is not yet in a campaign.") print(" This is acceptable if game is not yet in a campaign.")
return True return True
print(f" ✗ RPC error ({elapsed_ms:.1f} ms) [{code}]: {msg}") print(f" ✗ RPC error ({elapsed_ms:.1f} ms) [{code}]: {msg}")
return False return False
@@ -191,7 +191,7 @@ def main() -> int:
args = parser.parse_args() args = parser.parse_args()
print("=" * 60) print("=" * 60)
print(f"GABS Connectivity Test Suite") print("GABS Connectivity Test Suite")
print(f"Target: {args.host}:{args.port}") print(f"Target: {args.host}:{args.port}")
print(f"Timeout: {args.timeout}s") print(f"Timeout: {args.timeout}s")
print("=" * 60) print("=" * 60)

View File

@@ -150,7 +150,7 @@ def test_model_available(model: str) -> bool:
def test_basic_response(model: str) -> bool: def test_basic_response(model: str) -> bool:
"""PASS: model responds coherently to a simple prompt.""" """PASS: model responds coherently to a simple prompt."""
print(f"\n[2/5] Basic response test") print("\n[2/5] Basic response test")
messages = [ messages = [
{"role": "user", "content": "Reply with exactly: HERMES_OK"}, {"role": "user", "content": "Reply with exactly: HERMES_OK"},
] ]
@@ -188,7 +188,7 @@ def test_memory_usage() -> bool:
def test_tool_calling(model: str) -> bool: def test_tool_calling(model: str) -> bool:
"""PASS: model produces a tool_calls response (not raw text) for a tool-use prompt.""" """PASS: model produces a tool_calls response (not raw text) for a tool-use prompt."""
print(f"\n[4/5] Tool-calling test") print("\n[4/5] Tool-calling test")
messages = [ messages = [
{ {
"role": "user", "role": "user",
@@ -236,7 +236,7 @@ def test_tool_calling(model: str) -> bool:
def test_timmy_persona(model: str) -> bool: def test_timmy_persona(model: str) -> bool:
"""PASS: model accepts a Timmy persona system prompt and responds in-character.""" """PASS: model accepts a Timmy persona system prompt and responds in-character."""
print(f"\n[5/5] Timmy-persona smoke test") print("\n[5/5] Timmy-persona smoke test")
messages = [ messages = [
{ {
"role": "system", "role": "system",

View File

@@ -26,7 +26,7 @@ import argparse
import json import json
import sys import sys
import time import time
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import Any from typing import Any
try: try:

View File

@@ -16,7 +16,7 @@ import json
import os import os
import re import re
import sys import sys
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
# ── Config ────────────────────────────────────────────────────────────── # ── Config ──────────────────────────────────────────────────────────────
@@ -277,7 +277,7 @@ def update_quarantine(scored: list[dict]) -> list[dict]:
"""Auto-quarantine issues that have failed >= 2 times. Returns filtered list.""" """Auto-quarantine issues that have failed >= 2 times. Returns filtered list."""
failures = load_cycle_failures() failures = load_cycle_failures()
quarantine = load_quarantine() quarantine = load_quarantine()
now = datetime.now(timezone.utc).isoformat() now = datetime.now(UTC).isoformat()
filtered = [] filtered = []
for item in scored: for item in scored:
@@ -366,7 +366,7 @@ def run_triage() -> list[dict]:
backup_data = QUEUE_BACKUP_FILE.read_text() backup_data = QUEUE_BACKUP_FILE.read_text()
json.loads(backup_data) # Validate backup json.loads(backup_data) # Validate backup
QUEUE_FILE.write_text(backup_data) QUEUE_FILE.write_text(backup_data)
print(f"[triage] Restored queue.json from backup") print("[triage] Restored queue.json from backup")
except (json.JSONDecodeError, OSError) as restore_exc: except (json.JSONDecodeError, OSError) as restore_exc:
print(f"[triage] ERROR: Backup restore failed: {restore_exc}", file=sys.stderr) print(f"[triage] ERROR: Backup restore failed: {restore_exc}", file=sys.stderr)
# Write empty list as last resort # Write empty list as last resort
@@ -377,7 +377,7 @@ def run_triage() -> list[dict]:
# Write retro entry # Write retro entry
retro_entry = { retro_entry = {
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"total_open": len(all_issues), "total_open": len(all_issues),
"scored": len(scored), "scored": len(scored),
"ready": len(ready), "ready": len(ready),

View File

@@ -35,9 +35,9 @@ from dashboard.routes.chat_api_v1 import router as chat_api_v1_router
from dashboard.routes.daily_run import router as daily_run_router from dashboard.routes.daily_run import router as daily_run_router
from dashboard.routes.db_explorer import router as db_explorer_router from dashboard.routes.db_explorer import router as db_explorer_router
from dashboard.routes.discord import router as discord_router from dashboard.routes.discord import router as discord_router
from dashboard.routes.energy import router as energy_router
from dashboard.routes.experiments import router as experiments_router from dashboard.routes.experiments import router as experiments_router
from dashboard.routes.grok import router as grok_router from dashboard.routes.grok import router as grok_router
from dashboard.routes.energy import router as energy_router
from dashboard.routes.health import router as health_router from dashboard.routes.health import router as health_router
from dashboard.routes.hermes import router as hermes_router from dashboard.routes.hermes import router as hermes_router
from dashboard.routes.loop_qa import router as loop_qa_router from dashboard.routes.loop_qa import router as loop_qa_router
@@ -48,6 +48,7 @@ from dashboard.routes.models import router as models_router
from dashboard.routes.nexus import router as nexus_router from dashboard.routes.nexus import router as nexus_router
from dashboard.routes.quests import router as quests_router from dashboard.routes.quests import router as quests_router
from dashboard.routes.scorecards import router as scorecards_router from dashboard.routes.scorecards import router as scorecards_router
from dashboard.routes.self_correction import router as self_correction_router
from dashboard.routes.sovereignty_metrics import router as sovereignty_metrics_router from dashboard.routes.sovereignty_metrics import router as sovereignty_metrics_router
from dashboard.routes.sovereignty_ws import router as sovereignty_ws_router from dashboard.routes.sovereignty_ws import router as sovereignty_ws_router
from dashboard.routes.spark import router as spark_router from dashboard.routes.spark import router as spark_router
@@ -55,7 +56,6 @@ from dashboard.routes.system import router as system_router
from dashboard.routes.tasks import router as tasks_router from dashboard.routes.tasks import router as tasks_router
from dashboard.routes.telegram import router as telegram_router from dashboard.routes.telegram import router as telegram_router
from dashboard.routes.thinking import router as thinking_router from dashboard.routes.thinking import router as thinking_router
from dashboard.routes.self_correction import router as self_correction_router
from dashboard.routes.three_strike import router as three_strike_router from dashboard.routes.three_strike import router as three_strike_router
from dashboard.routes.tools import router as tools_router from dashboard.routes.tools import router as tools_router
from dashboard.routes.tower import router as tower_router from dashboard.routes.tower import router as tower_router

View File

@@ -19,7 +19,6 @@ Refs: #1009
""" """
import asyncio import asyncio
import json
import logging import logging
import subprocess import subprocess
import time import time

View File

@@ -24,8 +24,8 @@ from infrastructure.models.registry import (
model_registry, model_registry,
) )
from infrastructure.models.router import ( from infrastructure.models.router import (
TierLabel,
TieredModelRouter, TieredModelRouter,
TierLabel,
classify_tier, classify_tier,
get_tiered_router, get_tiered_router,
) )

View File

@@ -27,7 +27,6 @@ References:
- Issue #882 — Model Tiering Router: Local 8B / Hermes 70B / Cloud API Cascade - Issue #882 — Model Tiering Router: Local 8B / Hermes 70B / Cloud API Cascade
""" """
import asyncio
import logging import logging
import re import re
import time import time

View File

@@ -20,13 +20,11 @@ Usage::
from __future__ import annotations from __future__ import annotations
import json
import logging import logging
import sqlite3 import sqlite3
import uuid import uuid
from collections.abc import Generator from collections.abc import Generator
from contextlib import closing, contextmanager from contextlib import closing, contextmanager
from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -21,7 +21,6 @@ import base64
import json import json
import logging import logging
from datetime import UTC, datetime from datetime import UTC, datetime
from pathlib import Path
from typing import Any from typing import Any
import httpx import httpx

View File

@@ -22,21 +22,20 @@ import sqlite3
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from timmy.thinking._db import Thought, _get_conn
from timmy.thinking.engine import ThinkingEngine
from timmy.thinking.seeds import (
SEED_TYPES,
_SENSITIVE_PATTERNS,
_META_OBSERVATION_PHRASES,
_THINK_TAG_RE,
_THINKING_PROMPT,
)
# Re-export HOT_MEMORY_PATH and SOUL_PATH so existing patch targets continue to work. # Re-export HOT_MEMORY_PATH and SOUL_PATH so existing patch targets continue to work.
# Tests that patch "timmy.thinking.HOT_MEMORY_PATH" or "timmy.thinking.SOUL_PATH" # Tests that patch "timmy.thinking.HOT_MEMORY_PATH" or "timmy.thinking.SOUL_PATH"
# should instead patch "timmy.thinking._snapshot.HOT_MEMORY_PATH" etc., but these # should instead patch "timmy.thinking._snapshot.HOT_MEMORY_PATH" etc., but these
# re-exports are kept for any code that reads them from the top-level namespace. # re-exports are kept for any code that reads them from the top-level namespace.
from timmy.memory_system import HOT_MEMORY_PATH, SOUL_PATH # noqa: F401 from timmy.memory_system import HOT_MEMORY_PATH, SOUL_PATH # noqa: F401
from timmy.thinking._db import Thought, _get_conn
from timmy.thinking.engine import ThinkingEngine
from timmy.thinking.seeds import (
_META_OBSERVATION_PHRASES,
_SENSITIVE_PATTERNS,
_THINK_TAG_RE,
_THINKING_PROMPT,
SEED_TYPES,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -4,7 +4,6 @@ import logging
from pathlib import Path from pathlib import Path
from config import settings from config import settings
from timmy.thinking.seeds import _META_OBSERVATION_PHRASES, _SENSITIVE_PATTERNS from timmy.thinking.seeds import _META_OBSERVATION_PHRASES, _SENSITIVE_PATTERNS
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -5,11 +5,11 @@ import random
from datetime import UTC, datetime from datetime import UTC, datetime
from timmy.thinking.seeds import ( from timmy.thinking.seeds import (
SEED_TYPES,
_CREATIVE_SEEDS, _CREATIVE_SEEDS,
_EXISTENTIAL_SEEDS, _EXISTENTIAL_SEEDS,
_OBSERVATION_SEEDS, _OBSERVATION_SEEDS,
_SOVEREIGNTY_SEEDS, _SOVEREIGNTY_SEEDS,
SEED_TYPES,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -1,7 +1,7 @@
"""System snapshot and memory context mixin for the thinking engine.""" """System snapshot and memory context mixin for the thinking engine."""
import logging import logging
from datetime import UTC, datetime from datetime import datetime
from timmy.memory_system import HOT_MEMORY_PATH, SOUL_PATH from timmy.memory_system import HOT_MEMORY_PATH, SOUL_PATH

View File

@@ -7,8 +7,7 @@ from difflib import SequenceMatcher
from pathlib import Path from pathlib import Path
from config import settings from config import settings
from timmy.thinking._db import _DEFAULT_DB, Thought, _get_conn, _row_to_thought
from timmy.thinking._db import Thought, _DEFAULT_DB, _get_conn, _row_to_thought
from timmy.thinking._distillation import _DistillationMixin from timmy.thinking._distillation import _DistillationMixin
from timmy.thinking._issue_filing import _IssueFilingMixin from timmy.thinking._issue_filing import _IssueFilingMixin
from timmy.thinking._seeds_mixin import _SeedsMixin from timmy.thinking._seeds_mixin import _SeedsMixin

View File

@@ -27,7 +27,6 @@ from infrastructure.router.cascade import (
ProviderStatus, ProviderStatus,
) )
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -10,13 +10,13 @@ Covers:
- "Plan the optimal path to become Hortator" → LOCAL_HEAVY - "Plan the optimal path to become Hortator" → LOCAL_HEAVY
""" """
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock
import pytest import pytest
from infrastructure.models.router import ( from infrastructure.models.router import (
TierLabel,
TieredModelRouter, TieredModelRouter,
TierLabel,
_is_low_quality, _is_low_quality,
classify_tier, classify_tier,
get_tiered_router, get_tiered_router,

View File

@@ -5,7 +5,6 @@ from __future__ import annotations
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest import pytest
from timmy.backlog_triage import ( from timmy.backlog_triage import (
@@ -28,7 +27,6 @@ from timmy.backlog_triage import (
score_issue, score_issue,
) )
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -4,7 +4,6 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest import pytest
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# exceeds_local_capacity # exceeds_local_capacity
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -34,7 +34,6 @@ from timmy.quest_system import (
update_quest_progress, update_quest_progress,
) )
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -15,7 +15,6 @@ if "serpapi" not in sys.modules:
from timmy.research_tools import get_llm_client, google_web_search # noqa: E402 from timmy.research_tools import get_llm_client, google_web_search # noqa: E402
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# google_web_search # google_web_search
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -6,8 +6,7 @@ Refs: #957 (Session Sovereignty Report Generator)
import base64 import base64
import json import json
import time import time
from datetime import UTC, datetime from datetime import UTC
from pathlib import Path
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
@@ -18,14 +17,12 @@ from timmy.sovereignty.session_report import (
_format_duration, _format_duration,
_gather_session_data, _gather_session_data,
_gather_sovereignty_data, _gather_sovereignty_data,
_render_markdown,
commit_report, commit_report,
generate_and_commit_report, generate_and_commit_report,
generate_report, generate_report,
mark_session_start, mark_session_start,
) )
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# _format_duration # _format_duration
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -7,11 +7,8 @@ from __future__ import annotations
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest
from timmy.tools.search import _extract_crawl_content, scrape_url, web_search from timmy.tools.search import _extract_crawl_content, scrape_url, web_search
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -12,9 +12,7 @@ import argparse
import json import json
import sys import sys
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock, patch from unittest.mock import patch
import pytest
# Add timmy_automations to path for imports # Add timmy_automations to path for imports
_TA_PATH = Path(__file__).resolve().parent.parent.parent / "timmy_automations" / "daily_run" _TA_PATH = Path(__file__).resolve().parent.parent.parent / "timmy_automations" / "daily_run"

View File

@@ -7,7 +7,6 @@ falls back to the Ollama backend without crashing.
Refs #1284 Refs #1284
""" """
import sys
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest

View File

@@ -11,11 +11,9 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
from infrastructure.energy.monitor import ( from infrastructure.energy.monitor import (
_DEFAULT_MODEL_SIZE_GB,
EnergyBudgetMonitor, EnergyBudgetMonitor,
InferenceSample, InferenceSample,
_DEFAULT_MODEL_SIZE_GB,
_EFFICIENCY_SCORE_CEILING,
_WATTS_PER_GB_HEURISTIC,
) )

View File

@@ -1,9 +1,5 @@
"""Unit tests for infrastructure.self_correction.""" """Unit tests for infrastructure.self_correction."""
import os
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest import pytest

View File

@@ -13,10 +13,9 @@ Usage:
import argparse import argparse
import dataclasses import dataclasses
import json import json
import os
import sys import sys
import time import time
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
try: try:
@@ -28,12 +27,14 @@ except ImportError:
# Add parent dir to path so levels can be imported # Add parent dir to path so levels can be imported
sys.path.insert(0, str(Path(__file__).parent)) sys.path.insert(0, str(Path(__file__).parent))
from levels import level_0_coin_flip from levels import (
from levels import level_1_tic_tac_toe level_0_coin_flip,
from levels import level_2_resource_mgmt level_1_tic_tac_toe,
from levels import level_3_battle_tactics level_2_resource_mgmt,
from levels import level_4_trade_route level_3_battle_tactics,
from levels import level_5_mini_campaign level_4_trade_route,
level_5_mini_campaign,
)
ALL_LEVELS = [ ALL_LEVELS = [
level_0_coin_flip, level_0_coin_flip,
@@ -86,7 +87,7 @@ def run_benchmark(
levels_to_run = list(range(len(ALL_LEVELS))) levels_to_run = list(range(len(ALL_LEVELS)))
print(f"\n{'=' * 60}") print(f"\n{'=' * 60}")
print(f" Timmy Cognitive Benchmark — Project Bannerlord M0") print(" Timmy Cognitive Benchmark — Project Bannerlord M0")
print(f"{'=' * 60}") print(f"{'=' * 60}")
print(f" Model: {model}") print(f" Model: {model}")
print(f" Levels: {levels_to_run}") print(f" Levels: {levels_to_run}")
@@ -100,7 +101,7 @@ def run_benchmark(
"model": model, "model": model,
"skipped": True, "skipped": True,
"reason": f"Model '{model}' not available", "reason": f"Model '{model}' not available",
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
} }
else: else:
print(f" ERROR: Model '{model}' not found in Ollama.", file=sys.stderr) print(f" ERROR: Model '{model}' not found in Ollama.", file=sys.stderr)
@@ -110,7 +111,7 @@ def run_benchmark(
results = { results = {
"model": model, "model": model,
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(UTC).isoformat(),
"skipped": False, "skipped": False,
"levels": {}, "levels": {},
"summary": {}, "summary": {},

View File

@@ -21,11 +21,10 @@ import json
import os import os
import sys import sys
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
# ── Configuration ───────────────────────────────────────────────────────── # ── Configuration ─────────────────────────────────────────────────────────
@@ -260,7 +259,7 @@ def score_issue_for_path(issue: dict) -> int:
if updated_at: if updated_at:
try: try:
updated = datetime.fromisoformat(updated_at.replace("Z", "+00:00")) updated = datetime.fromisoformat(updated_at.replace("Z", "+00:00"))
days_old = (datetime.now(timezone.utc) - updated).days days_old = (datetime.now(UTC) - updated).days
if days_old < 7: if days_old < 7:
score += 2 score += 2
elif days_old < 30: elif days_old < 30:
@@ -388,7 +387,7 @@ def build_golden_path(
4. One more micro-fix or docs (closure) 4. One more micro-fix or docs (closure)
""" """
path = GoldenPath( path = GoldenPath(
generated_at=datetime.now(timezone.utc).isoformat(), generated_at=datetime.now(UTC).isoformat(),
target_minutes=target_minutes, target_minutes=target_minutes,
) )
@@ -478,7 +477,7 @@ def generate_golden_path(
if not client.is_available(): if not client.is_available():
# Return empty path with error indication # Return empty path with error indication
return GoldenPath( return GoldenPath(
generated_at=datetime.now(timezone.utc).isoformat(), generated_at=datetime.now(UTC).isoformat(),
target_minutes=target_minutes, target_minutes=target_minutes,
items=[], items=[],
) )

View File

@@ -17,11 +17,11 @@ import json
import os import os
import sys import sys
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
# ── Configuration ───────────────────────────────────────────────────────── # ── Configuration ─────────────────────────────────────────────────────────
@@ -327,7 +327,7 @@ def check_critical_issues(client: GiteaClient, config: dict) -> IssueSignal:
issues=all_critical[:10], # Limit stored issues issues=all_critical[:10], # Limit stored issues
) )
except (HTTPError, URLError) as exc: except (HTTPError, URLError):
return IssueSignal( return IssueSignal(
count=0, count=0,
p0_count=0, p0_count=0,
@@ -419,7 +419,7 @@ def check_token_economy(config: dict) -> TokenEconomySignal:
try: try:
# Read last 24 hours of transactions # Read last 24 hours of transactions
since = datetime.now(timezone.utc) - timedelta(hours=24) since = datetime.now(UTC) - timedelta(hours=24)
recent_mint = 0 recent_mint = 0
recent_burn = 0 recent_burn = 0
@@ -511,7 +511,7 @@ def generate_snapshot(config: dict, token: str | None) -> HealthSnapshot:
overall = calculate_overall_status(ci, issues, flakiness) overall = calculate_overall_status(ci, issues, flakiness)
return HealthSnapshot( return HealthSnapshot(
timestamp=datetime.now(timezone.utc).isoformat(), timestamp=datetime.now(UTC).isoformat(),
overall_status=overall, overall_status=overall,
ci=ci, ci=ci,
issues=issues, issues=issues,

View File

@@ -19,11 +19,11 @@ import argparse
import json import json
import os import os
import sys import sys
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
# ── Token Economy Integration ────────────────────────────────────────────── # ── Token Economy Integration ──────────────────────────────────────────────
# Import token rules helpers for tracking Daily Run rewards # Import token rules helpers for tracking Daily Run rewards
@@ -31,12 +31,11 @@ from urllib.error import HTTPError, URLError
sys.path.insert( sys.path.insert(
0, str(Path(__file__).resolve().parent.parent) 0, str(Path(__file__).resolve().parent.parent)
) )
from utils.token_rules import TokenRules, compute_token_reward
# Health snapshot lives in the same package # Health snapshot lives in the same package
from health_snapshot import generate_snapshot as _generate_health_snapshot from health_snapshot import generate_snapshot as _generate_health_snapshot
from health_snapshot import get_token as _hs_get_token from health_snapshot import get_token as _hs_get_token
from health_snapshot import load_config as _hs_load_config from health_snapshot import load_config as _hs_load_config
from utils.token_rules import TokenRules, compute_token_reward
# ── Configuration ───────────────────────────────────────────────────────── # ── Configuration ─────────────────────────────────────────────────────────
@@ -284,7 +283,7 @@ def generate_agenda(issues: list[dict], config: dict) -> dict:
items.append(item) items.append(item)
return { return {
"generated_at": datetime.now(timezone.utc).isoformat(), "generated_at": datetime.now(UTC).isoformat(),
"time_budget_minutes": agenda_time, "time_budget_minutes": agenda_time,
"item_count": len(items), "item_count": len(items),
"items": items, "items": items,
@@ -322,7 +321,7 @@ def print_agenda(agenda: dict) -> None:
def fetch_recent_activity(client: GiteaClient, config: dict) -> dict: def fetch_recent_activity(client: GiteaClient, config: dict) -> dict:
"""Fetch recent issues and PRs from the lookback window.""" """Fetch recent issues and PRs from the lookback window."""
lookback_hours = config.get("lookback_hours", 24) lookback_hours = config.get("lookback_hours", 24)
since = datetime.now(timezone.utc) - timedelta(hours=lookback_hours) since = datetime.now(UTC) - timedelta(hours=lookback_hours)
since_str = since.isoformat() since_str = since.isoformat()
activity = { activity = {
@@ -399,7 +398,7 @@ def load_cycle_data() -> dict:
continue continue
# Get entries from last 24 hours # Get entries from last 24 hours
since = datetime.now(timezone.utc) - timedelta(hours=24) since = datetime.now(UTC) - timedelta(hours=24)
recent = [ recent = [
e for e in entries e for e in entries
if e.get("timestamp") and datetime.fromisoformat(e["timestamp"].replace("Z", "+00:00")) >= since if e.get("timestamp") and datetime.fromisoformat(e["timestamp"].replace("Z", "+00:00")) >= since
@@ -426,7 +425,7 @@ def load_cycle_data() -> dict:
def generate_day_summary(activity: dict, cycles: dict) -> dict: def generate_day_summary(activity: dict, cycles: dict) -> dict:
"""Generate a day summary from activity data.""" """Generate a day summary from activity data."""
return { return {
"generated_at": datetime.now(timezone.utc).isoformat(), "generated_at": datetime.now(UTC).isoformat(),
"lookback_hours": 24, "lookback_hours": 24,
"issues_touched": len(activity.get("issues_touched", [])), "issues_touched": len(activity.get("issues_touched", [])),
"issues_closed": len(activity.get("issues_closed", [])), "issues_closed": len(activity.get("issues_closed", [])),

View File

@@ -25,7 +25,6 @@ import sys
from collections import Counter from collections import Counter
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen from urllib.request import Request, urlopen

View File

@@ -12,7 +12,6 @@ Refs: #1105
from __future__ import annotations from __future__ import annotations
import json
import logging import logging
import os import os
import shutil import shutil