generated from coulomb/repo-seed
session-memory: weekly retro entrypoint + hub publish (AGENTIC-WP-0010)
The analysis half of the weekly coding retrospection. retro/build.py: windowed detect+measure -> top-3 improvement suggestions per repo (cross-flavor first, recommendations pulled from the Pattern Catalog) + fleet snapshot. retro/publish.py: publishes the report to the hub as the coding_retro read model (event_type= coding_retro progress event) + local JSON/md, graceful degrade. retro entrypoint with --window-days/--publish/--json. Live verify over real sessions surfaced per-repo suggestions with catalog recommendations. 13 new tests; suite 152/152. Consumed by activity-core ACTIVITY-WP-0008 (Weekly Coding Retrospection, Sat 19:00). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
78
session_memory/retro/publish.py
Normal file
78
session_memory/retro/publish.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Publish the weekly retro (AGENTIC-WP-0010 T02).
|
||||
|
||||
The retro is published to the State Hub as a **read model** — a progress event of
|
||||
``event_type=coding_retro`` whose ``detail`` carries the structured report. This is
|
||||
exactly how ``daily-triage-report`` surfaces, and it is what activity-core's
|
||||
``coding_retro`` resolver (ACTIVITY-WP-0008) reads. A local JSON + markdown report
|
||||
is always written; the hub publish is best-effort and **degrades gracefully** when
|
||||
the hub is unreachable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
from typing import Callable, Optional
|
||||
|
||||
DEFAULT_HUB = "http://127.0.0.1:8000"
|
||||
|
||||
|
||||
def render_markdown(report: dict) -> str:
|
||||
w = report.get("window", {})
|
||||
lines = [
|
||||
f"# Weekly Coding Retro ({w.get('since', '')[:10]} → {w.get('until', '')[:10]})",
|
||||
f"_{report.get('n_sessions', 0)} real sessions · generated {report.get('generated_at', '')}_",
|
||||
"",
|
||||
"## Top improvement suggestions (cross-flavor first, ≤3 per repo)",
|
||||
]
|
||||
if not report.get("suggestions"):
|
||||
lines.append("- (no recurring problems above threshold this week)")
|
||||
for s in report.get("suggestions", []):
|
||||
flag = " [CROSS-FLAVOR]" if s.get("cross_flavor") else ""
|
||||
lines.append(f"- **{s['repo']}** ({s['priority']}, score={s['score']}){flag}: "
|
||||
f"{s['title']} — {s['recommendation']}")
|
||||
m = report.get("measure", {})
|
||||
lines += ["", "## Fleet snapshot",
|
||||
f"- infra-overhead median: {m.get('infra_overhead_share_median')}",
|
||||
f"- error rate: {m.get('error_rate')} · schema-thrash: {m.get('schema_thrash_sessions')}",
|
||||
f"- success rate: {m.get('success_rate')} · tokens p50: {m.get('tokens_p50')}"]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def write_local(report: dict, json_path: str, md_path: Optional[str] = None) -> None:
|
||||
os.makedirs(os.path.dirname(json_path) or ".", exist_ok=True)
|
||||
with open(json_path, "w", encoding="utf-8") as fh:
|
||||
json.dump(report, fh, indent=2, sort_keys=True)
|
||||
fh.write("\n")
|
||||
if md_path:
|
||||
with open(md_path, "w", encoding="utf-8") as fh:
|
||||
fh.write(render_markdown(report))
|
||||
fh.write("\n")
|
||||
|
||||
|
||||
def _http_post(url: str, payload: dict) -> None:
|
||||
req = urllib.request.Request(url, data=json.dumps(payload).encode(),
|
||||
headers={"Content-Type": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=10) as r:
|
||||
r.read()
|
||||
|
||||
|
||||
def publish_to_hub(report: dict, *, base_url: str = DEFAULT_HUB,
|
||||
poster: Optional[Callable[[str, dict], None]] = None) -> bool:
|
||||
"""POST the retro as an event_type=coding_retro progress event. Best-effort."""
|
||||
poster = poster or _http_post
|
||||
n = report.get("n_sessions", 0)
|
||||
k = len(report.get("suggestions", []))
|
||||
payload = {
|
||||
"event_type": "coding_retro",
|
||||
"author": "helix-forge",
|
||||
"summary": f"Weekly coding retro: {k} ranked suggestions across "
|
||||
f"{report.get('window', {}).get('days', 7)} days ({n} sessions).",
|
||||
"detail": report,
|
||||
}
|
||||
try:
|
||||
poster(f"{base_url.rstrip('/')}/progress/", payload)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
Reference in New Issue
Block a user