feat(feedback): move feedback poster from WIP into pilot/
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"""pragent pilot — daily feedback report delivery.
|
||||
|
||||
Calls `feedback_analyze.analyze()` and posts the markdown report as a
|
||||
comment on a single long-lived "feedback roll-up" issue in
|
||||
`gitea_admin/pragent`. Comments are append-only history — one comment per
|
||||
run, timestamped in the body. This keeps every report in one place, easy
|
||||
to scroll, and avoids the issue-explosion of "one issue per day".
|
||||
|
||||
If the issue doesn't exist yet, create it. Subsequent runs just add a
|
||||
new comment.
|
||||
|
||||
Designed for the daily K8s CronJob (`k8s/pragent-feedback-cronjob.yaml`)
|
||||
but runnable from CLI for ad-hoc checks.
|
||||
|
||||
Env:
|
||||
GITEA_API in-cluster Gitea base URL
|
||||
PRAGENT_BOT_TOKEN bot token (Write collaborator on gitea_admin/pragent)
|
||||
PRAGENT_FEEDBACK_DB path to SQLite (default /data/feedback.db)
|
||||
PRAGENT_FEEDBACK_ISSUE_REPO default gitea_admin/pragent
|
||||
PRAGENT_FEEDBACK_ISSUE_TITLE default "pragent feedback roll-up"
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import ai_review
|
||||
|
||||
from feedback_analyze import analyze
|
||||
|
||||
log = logging.getLogger("pragent.feedback.post")
|
||||
|
||||
|
||||
REPO_DEFAULT = "gitea_admin/pragent"
|
||||
TITLE_DEFAULT = "pragent feedback roll-up"
|
||||
|
||||
|
||||
def _find_or_create_issue(api: str, token: str, repo: str, title: str) -> int:
|
||||
"""Locate the open issue with this title; create one if missing.
|
||||
|
||||
Gitea's issue search is via `GET /repos/{o}/{r}/issues?state=open&q=...`
|
||||
(q matches title + body). We filter client-side for the exact title
|
||||
to avoid query-text false matches.
|
||||
"""
|
||||
status, raw = ai_review.gitea_get(api, repo, "issues?state=open&per_page=50", token)
|
||||
if status == 200:
|
||||
try:
|
||||
for issue in json.loads(raw):
|
||||
if issue.get("title") == title:
|
||||
# NB: the comment URL needs the per-repo `number`, not the
|
||||
# global `id`. `id=60 num=8` for an early-N create; we want
|
||||
# `num=8` for `/repos/o/r/issues/8/comments`.
|
||||
return int(issue["number"])
|
||||
except (json.JSONDecodeError, ValueError, KeyError):
|
||||
pass
|
||||
# Create
|
||||
status, raw = ai_review.gitea_post(
|
||||
api, repo, "issues", token,
|
||||
{"title": title, "body": "pragent feedback roll-up — auto-created."},
|
||||
)
|
||||
if status not in (200, 201):
|
||||
raise RuntimeError(f"issue create failed: HTTP {status} body={raw[:200]!r}")
|
||||
return int(json.loads(raw)["number"])
|
||||
|
||||
|
||||
def _post_comment(api: str, token: str, repo: str, issue_number: int, body: str) -> int:
|
||||
status, raw = ai_review.gitea_post(
|
||||
api, repo, f"issues/{issue_number}/comments", token, {"body": body},
|
||||
)
|
||||
if status not in (200, 201):
|
||||
raise RuntimeError(f"comment post failed: HTTP {status} body={raw[:200]!r}")
|
||||
return json.loads(raw)["id"]
|
||||
|
||||
|
||||
def deliver(
|
||||
*, api: str, token: str, db_path: str,
|
||||
repo: str = REPO_DEFAULT, title: str = TITLE_DEFAULT,
|
||||
since_ts: int | None = None,
|
||||
) -> dict:
|
||||
"""Build the report and post it as a comment. Returns a stats dict."""
|
||||
report = analyze(db_path, since_ts=since_ts)
|
||||
issue_id = _find_or_create_issue(api, token, repo, title)
|
||||
comment_id = _post_comment(api, token, repo, issue_id, report)
|
||||
return {
|
||||
"repo": repo, "issue_id": issue_id, "comment_id": comment_id,
|
||||
"report_bytes": len(report.encode()),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Post the daily feedback report to Gitea.",
|
||||
)
|
||||
p.add_argument("--api", default=os.environ.get(
|
||||
"GITEA_API", "http://gitea-http.gitea.svc.cluster.local:3000",
|
||||
))
|
||||
p.add_argument("--token", default=os.environ.get("PRAGENT_BOT_TOKEN", ""))
|
||||
p.add_argument("--db", default=os.environ.get(
|
||||
"PRAGENT_FEEDBACK_DB", "/data/feedback.db",
|
||||
))
|
||||
p.add_argument("--repo", default=os.environ.get(
|
||||
"PRAGENT_FEEDBACK_ISSUE_REPO", REPO_DEFAULT,
|
||||
))
|
||||
p.add_argument("--title", default=os.environ.get(
|
||||
"PRAGENT_FEEDBACK_ISSUE_TITLE", TITLE_DEFAULT,
|
||||
))
|
||||
p.add_argument("--since", type=int, default=None,
|
||||
help="Unix timestamp; only include findings posted since")
|
||||
args = p.parse_args()
|
||||
|
||||
if not args.token:
|
||||
print("PRAGENT_BOT_TOKEN required", flush=True)
|
||||
return 2
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
stats = deliver(
|
||||
api=args.api, token=args.token, db_path=args.db,
|
||||
repo=args.repo, title=args.title, since_ts=args.since,
|
||||
)
|
||||
print(json.dumps(stats), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user