4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
103 lines
3.5 KiB
Python
103 lines
3.5 KiB
Python
"""Community follow-up summarization for GitHub workflow tools."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from integrations.github.tools.workflow.models import CommunityFollowup
|
|
|
|
_ISSUE_NUMBER_RE = re.compile(r"/issues/(?P<number>\d+)(?:$|[#?])")
|
|
|
|
|
|
def issue_number_from_url(url: str) -> int | None:
|
|
match = _ISSUE_NUMBER_RE.search(url)
|
|
if match is None:
|
|
return None
|
|
return int(match.group("number"))
|
|
|
|
|
|
def normalize_community_comment(raw: dict[str, Any]) -> CommunityFollowup:
|
|
"""Normalize GitHub repository issue-comment payloads."""
|
|
|
|
issue_number = raw.get("issue_number")
|
|
if not isinstance(issue_number, int):
|
|
issue_url = str(raw.get("issue_url") or raw.get("html_url") or "")
|
|
issue_number = issue_number_from_url(issue_url)
|
|
return CommunityFollowup(
|
|
issue_number=issue_number,
|
|
issue_title=str(raw.get("issue_title", "")),
|
|
author=str(raw.get("author") or (raw.get("user") or {}).get("login", "")),
|
|
body=str(raw.get("body", "")),
|
|
created_at=str(raw.get("created_at", "")),
|
|
url=str(raw.get("url") or raw.get("html_url") or ""),
|
|
)
|
|
|
|
|
|
def _is_question(text: str) -> bool:
|
|
lowered = text.lower()
|
|
return "?" in text or lowered.startswith(
|
|
("when ", "what ", "who ", "how ", "where ", "can ", "could ")
|
|
)
|
|
|
|
|
|
def _is_agenda_item(text: str) -> bool:
|
|
lowered = text.lower()
|
|
return any(term in lowered for term in ("agenda", "standup"))
|
|
|
|
|
|
def _question_answered_later(
|
|
question: CommunityFollowup,
|
|
comments: list[CommunityFollowup],
|
|
maintainers: set[str],
|
|
) -> bool:
|
|
for comment in comments:
|
|
if comment.issue_number != question.issue_number:
|
|
continue
|
|
if comment.created_at <= question.created_at:
|
|
continue
|
|
if comment.author.lower() in maintainers:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _suggest_reply(question: CommunityFollowup) -> dict[str, Any]:
|
|
return {
|
|
"issue_number": question.issue_number,
|
|
"issue_title": question.issue_title,
|
|
"context": question.body,
|
|
"suggested_reply": (
|
|
"Thanks for the question — we should confirm the current owner/status "
|
|
"and reply in this thread with the next concrete step."
|
|
),
|
|
"url": question.url,
|
|
}
|
|
|
|
|
|
def summarize_community_followups_from_comments(
|
|
*,
|
|
comments: list[dict[str, Any]],
|
|
maintainer_logins: list[str] | None = None,
|
|
) -> dict[str, Any]:
|
|
normalized_comments = [normalize_community_comment(comment) for comment in comments]
|
|
maintainers = {login.lower() for login in (maintainer_logins or [])}
|
|
questions = [comment for comment in normalized_comments if _is_question(comment.body)]
|
|
unanswered = [
|
|
question
|
|
for question in questions
|
|
if question.author.lower() not in maintainers
|
|
and not _question_answered_later(question, normalized_comments, maintainers)
|
|
]
|
|
agenda_items = [comment for comment in normalized_comments if _is_agenda_item(comment.body)]
|
|
return {
|
|
"unanswered_questions": [question.to_dict() for question in unanswered],
|
|
"agenda_items": [item.to_dict() for item in agenda_items],
|
|
"suggested_replies": [_suggest_reply(question) for question in unanswered],
|
|
"counts": {
|
|
"comments": len(normalized_comments),
|
|
"unanswered_questions": len(unanswered),
|
|
"agenda_items": len(agenda_items),
|
|
},
|
|
"side_effects": [],
|
|
}
|