feat(pm-agent): an in-cluster AI Program Manager that turns Gitea issues into pull requests #57

Merged
jakman85 merged 7 commits from feat/pm-agent-issue-to-pr into main 2026-10-06 04:12:31 +00:00
2 changed files with 18 additions and 9 deletions
Showing only changes of commit 54418363aa - Show all commits

No files matched your search

@@ -90,6 +90,12 @@ All offline, in the pinned toolbox (`./infra/devcontainer/dev.sh run`), never on
kustomization, because the kustomization's comment named it by filename. Correctly
refused: the distance between a filename in a comment and a line under `resources:` is
one careless edit. The comment now points at it in prose.
- **A self-review of the trigger found and fixed one real defect** before any of this ran:
`last_activity()` compared RFC 3339 timestamp STRINGS to find the newest comment, which is
only correct while every offset Gitea returns is identical — and the failure mode is the
worst available one, the PM answering its own last comment in a loop. It now takes the last
element of the comments list, which is the order the endpoint returns, so there is nothing
about timezones to be right about.
- **`check-jsonschema --schemafile schemas/servicedefinition.schema.json catalog/gitea.yaml`
→ ok.**
- **The golden reproduces:** `python3 catalog/render.py --all catalog -o apps --wave-layout
+12 -9
View File
@@ -599,19 +599,22 @@ def render_thread(cfg: Config, issue: dict, comments, pr: dict, status: dict,
def last_activity(issue: dict, comments) -> tuple:
"""Who wrote the newest thing in this thread, and when.
"""Who wrote the newest thing in this thread, and when. THE TRIGGER DEPENDS ON THIS.
Deliberately NOT `issue.updated_at`: that moves when a label is added or the title is
edited, which would re-trigger the PM for a change nobody asked it about.
And deliberately NOT a comparison of the timestamp STRINGS. Gitea returns RFC 3339 with
a timezone offset, and lexicographic order over such strings is only correct while every
offset is identical — true today, silently wrong after a server timezone change, and the
failure would be the PM answering its own last comment in a loop. The comments endpoint
returns them in creation order, so the last element IS the newest; that is one less thing
to be right about.
"""
actor = (issue.get("user") or {}).get("login")
when = issue.get("created_at") or ""
for c in comments:
ts = c.get("created_at") or ""
if ts >= when:
when = ts
actor = (c.get("user") or {}).get("login")
return actor, when
if comments:
newest = comments[-1]
return (newest.get("user") or {}).get("login"), newest.get("created_at") or ""
return (issue.get("user") or {}).get("login"), issue.get("created_at") or ""
# --------------------------------------------------------------------------- job