Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 87 additions & 51 deletions .github/scripts/pitot_e2e_report.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Publish a trusted, sticky PR summary for Pitot's per-agent E2E workflows."""
"""Publish a trusted, sticky PR summary for Pitot's unified E2E workflow."""

from __future__ import annotations

Expand All @@ -9,20 +9,22 @@
import os
from pathlib import Path
import re
import sys
import urllib.parse
import urllib.request
import zipfile


MARKER = "<!-- pitot-e2e-report -->"
WORKFLOWS = {
"claude": "Pitot E2E — Claude",
"cursor": "Pitot E2E — Cursor",
"codex": "Pitot E2E — Codex",
"gemini": "Pitot E2E — Gemini",
"opencode": "Pitot E2E — OpenCode",
}
PLATFORMS = ("ubuntu", "macos", "windows")
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "labs/15-pitot/scripts"))
from pitot_adapter_supervisor import ContractError, validate_manifest


MANIFEST = json.loads((ROOT / "labs/15-pitot/adapter-verification.json").read_text(encoding="utf-8"))
AGENTS = tuple(agent["id"] for agent in MANIFEST["agents"])
AGENT_LABELS = {agent["id"]: agent["label"] for agent in MANIFEST["agents"]}
PLATFORMS = tuple(platform["id"] for platform in MANIFEST["platforms"])
MAX_ARTIFACT_BYTES = 1_000_000


Expand Down Expand Up @@ -93,10 +95,21 @@ def validate_result(
return value


def aggregate(platform_results: dict[str, dict[str, object] | None]) -> str:
if any(platform not in platform_results or platform_results[platform] is None for platform in PLATFORMS):
def validate_inventory(value: object) -> dict[str, object]:
try:
validate_manifest(value)
except ContractError as error:
raise ValueError(f"invalid supervised inventory: {error}") from error
return value


def aggregate(
platform_results: dict[str, dict[str, object] | None],
platforms: tuple[str, ...] = PLATFORMS,
) -> str:
if any(platform not in platform_results or platform_results[platform] is None for platform in platforms):
return "pending"
return "passing" if all(platform_results[p]["status"] == "pass" for p in PLATFORMS) else "failing"
return "passing" if all(platform_results[p]["status"] == "pass" for p in platforms) else "failing"


def result_cell(result: dict[str, object] | None) -> str:
Expand Down Expand Up @@ -125,6 +138,8 @@ def render_comment(
results: dict[str, dict[str, dict[str, object] | None]],
run_urls: dict[str, str],
head_sha: str,
agent_records: tuple[dict[str, str], ...] | None = None,
platforms: tuple[str, ...] = PLATFORMS,
) -> str:
lines = [
MARKER,
Expand All @@ -136,16 +151,18 @@ def render_comment(
"|---|---|---|---|---|---|",
]
icons = {"passing": "✅ Passing", "failing": "❌ Failing", "pending": "⏳ Pending"}
for agent in WORKFLOWS:
platforms = results.get(agent, {})
status = aggregate(platforms)
records = agent_records or tuple({"id": agent, "label": AGENT_LABELS[agent]} for agent in AGENTS)
for record in records:
agent = record["id"]
platform_results = results.get(agent, {})
status = aggregate(platform_results, platforms)
url = run_urls.get(agent)
label = agent.capitalize() if agent != "opencode" else "OpenCode"
label = record["label"]
linked_label = f"[{label}]({url})" if url else label
evidence = "All platforms are required" if status != "pending" else "Waiting for all platform artifacts"
lines.append(
f"| {linked_label} | {result_cell(platforms.get('ubuntu'))} | "
f"{result_cell(platforms.get('macos'))} | {result_cell(platforms.get('windows'))} | "
f"| {linked_label} | {result_cell(platform_results.get('ubuntu'))} | "
f"{result_cell(platform_results.get('macos'))} | {result_cell(platform_results.get('windows'))} | "
f"{icons[status]} | {evidence} |"
)
lines.extend(["", f"Source commit: `{head_sha[:12]}`", ""])
Expand Down Expand Up @@ -213,6 +230,45 @@ def load_artifact(
)


def load_inventory(github: GitHub, artifact: dict[str, object]) -> dict[str, object]:
archive = github.download(str(artifact["archive_download_url"]))
with zipfile.ZipFile(io.BytesIO(archive)) as bundle:
names = bundle.namelist()
if names != ["adapter-verification.json"] or bundle.getinfo(names[0]).file_size > MAX_ARTIFACT_BYTES:
raise ValueError("inventory artifact must contain one bounded adapter-verification.json")
return validate_inventory(json.loads(bundle.read(names[0])))


def collect_results(
github: GitHub,
by_name: dict[str, dict[str, object]],
inventory: dict[str, object],
head_sha: str,
run_url: str,
) -> dict[str, dict[str, dict[str, object]]]:
agent_ids = tuple(agent["id"] for agent in inventory["agents"])
platforms = tuple(platform["id"] for platform in inventory["platforms"])
results: dict[str, dict[str, dict[str, object]]] = {agent: {} for agent in agent_ids}
for agent in agent_ids:
for platform in platforms:
artifact = by_name.get(f"pitot-e2e-{agent}-{platform}")
if artifact is not None:
try:
results[agent][platform] = load_artifact(
github,
artifact,
agent=agent,
platform=platform,
expected_sha=head_sha,
expected_run_url=run_url,
)
continue
except (ValueError, json.JSONDecodeError, zipfile.BadZipFile):
pass
results[agent][platform] = failed_result(agent, platform, head_sha, run_url)
return results


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--event", type=Path, required=True)
Expand All @@ -236,41 +292,21 @@ def main() -> int:
print("No pull request is associated with this workflow run; nothing to report.")
return 0
head_sha = workflow_run["head_sha"]
query = urllib.parse.urlencode({"head_sha": head_sha, "event": "pull_request", "per_page": 100})
runs = github.request("GET", f"/actions/runs?{query}")["workflow_runs"]
run_url = workflow_run["html_url"]

results: dict[str, dict[str, dict[str, object] | None]] = {
agent: {platform: None for platform in PLATFORMS} for agent in WORKFLOWS
}
run_urls: dict[str, str] = {}
for agent, workflow_name in WORKFLOWS.items():
candidates = [run for run in runs if run.get("name") == workflow_name]
if not candidates:
continue
run = max(candidates, key=lambda value: value["id"])
run_urls[agent] = run["html_url"]
if run["status"] != "completed":
continue
artifacts = github.request("GET", f"/actions/runs/{run['id']}/artifacts")["artifacts"]
by_name = {artifact["name"]: artifact for artifact in artifacts if not artifact.get("expired")}
for platform in PLATFORMS:
artifact = by_name.get(f"pitot-e2e-{agent}-{platform}")
if artifact is not None:
try:
results[agent][platform] = load_artifact(
github,
artifact,
agent=agent,
platform=platform,
expected_sha=run["head_sha"],
expected_run_url=run["html_url"],
)
except (ValueError, json.JSONDecodeError, zipfile.BadZipFile):
results[agent][platform] = failed_result(agent, platform, head_sha, run["html_url"])
elif run.get("conclusion") != "success":
results[agent][platform] = failed_result(agent, platform, head_sha, run["html_url"])
artifacts = github.request("GET", f"/actions/runs/{workflow_run['id']}/artifacts")["artifacts"]
by_name = {artifact["name"]: artifact for artifact in artifacts if not artifact.get("expired")}
inventory_artifact = by_name.get("pitot-e2e-inventory")
if inventory_artifact is None:
raise ValueError("unified E2E run is missing its supervised adapter inventory")
inventory = load_inventory(github, inventory_artifact)
agent_records = tuple(inventory["agents"])
agent_ids = tuple(agent["id"] for agent in agent_records)
platforms = tuple(platform["id"] for platform in inventory["platforms"])
run_urls = {agent: run_url for agent in agent_ids}
results = collect_results(github, by_name, inventory, head_sha, run_url)

body = render_comment(results, run_urls, head_sha)
body = render_comment(results, run_urls, head_sha, agent_records, platforms)
issue_number = pull_requests[0]["number"]
comments = github.request("GET", f"/issues/{issue_number}/comments?per_page=100")
existing = next(
Expand Down
26 changes: 12 additions & 14 deletions .github/workflows/pitot-e2e-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ on:
description: Supported Pitot coding-agent host
required: true
type: string
platform:
description: Canonical Pitot platform identifier
required: true
type: string
runner:
description: GitHub-hosted runner selected by the supervisor
required: true
type: string

permissions:
contents: read
Expand All @@ -16,18 +24,8 @@ env:

jobs:
e2e:
name: ${{ inputs.agent }} (${{ matrix.platform }})
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
platform: ubuntu
- runner: macos-latest
platform: macos
- runner: windows-latest
platform: windows
runs-on: ${{ matrix.runner }}
name: ${{ inputs.agent }} (${{ inputs.platform }})
runs-on: ${{ inputs.runner }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
Expand Down Expand Up @@ -75,15 +73,15 @@ jobs:
run: >-
python labs/15-pitot/tests/run_e2e_report.py
--agent "${{ inputs.agent }}"
--platform "${{ matrix.platform }}"
--platform "${{ inputs.platform }}"
--output "${{ runner.temp }}/pitot-e2e/result.json"
-- "$PITOT_BASH" "labs/15-pitot/tests/e2e_${{ inputs.agent }}_cli_test.sh"
< /dev/null
- name: Upload structured E2E result
if: always()
uses: actions/upload-artifact@v4
with:
name: pitot-e2e-${{ inputs.agent }}-${{ matrix.platform }}
name: pitot-e2e-${{ inputs.agent }}-${{ inputs.platform }}
path: ${{ runner.temp }}/pitot-e2e/result.json
if-no-files-found: error
retention-days: 14
Expand Down
19 changes: 0 additions & 19 deletions .github/workflows/pitot-e2e-claude.yml

This file was deleted.

19 changes: 0 additions & 19 deletions .github/workflows/pitot-e2e-codex.yml

This file was deleted.

19 changes: 0 additions & 19 deletions .github/workflows/pitot-e2e-cursor.yml

This file was deleted.

19 changes: 0 additions & 19 deletions .github/workflows/pitot-e2e-gemini.yml

This file was deleted.

19 changes: 0 additions & 19 deletions .github/workflows/pitot-e2e-opencode.yml

This file was deleted.

8 changes: 2 additions & 6 deletions .github/workflows/pitot-e2e-report.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# Generated by pitot_adapter_supervisor.py. Do not edit directly.
name: Report Pitot E2E results

on:
workflow_run:
workflows:
- Pitot E2E — Claude
- Pitot E2E — Cursor
- Pitot E2E — Codex
- Pitot E2E — Gemini
- Pitot E2E — OpenCode
workflows: [Pitot E2E]
types: [completed]

permissions:
Expand Down
Loading
Loading