Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0198fd3
ci: add python-core-coverage gate for band-sdk-core-core
AlexanderZ-Band Sep 14, 2026
10b5622
ci: allow manual dispatch of python-core-coverage
AlexanderZ-Band Sep 14, 2026
4947bb5
test: throwaway trigger for python-core-coverage.yml verification
AlexanderZ-Band Sep 14, 2026
e07764e
fix: authenticate the band-sdk-core checkout with a scoped App token
AlexanderZ-Band Sep 14, 2026
259f318
test: remove throwaway python-core-coverage.yml trigger
AlexanderZ-Band Sep 14, 2026
e61dcdd
fix: harden python-core-coverage.yml against automated review findings
AlexanderZ-Band Sep 14, 2026
71f94df
fix: address cycle-2 review findings on python-core-coverage.yml
AlexanderZ-Band Sep 14, 2026
f4ed698
fix: run coverage guard test with Git Bash on Windows
AlexanderZ-Band Sep 15, 2026
a7651e2
fix: use scoped key for Core coverage checkout
AlexanderZ-Band Sep 15, 2026
a0bf60c
fix: bump Core coverage dependency
AlexanderZ-Band Sep 15, 2026
ea8b00e
ci: report weekly Core coverage
AlexanderZ-Band Sep 15, 2026
b66931b
fix: skip cancelled weekly coverage reports
AlexanderZ-Band Sep 15, 2026
50e859d
ci: report manual Core coverage runs
AlexanderZ-Band Sep 15, 2026
3ef1a51
ci: clarify Core coverage digests
AlexanderZ-Band Sep 15, 2026
f9f24d8
chore: schedule Core coverage for Sunday night
AlexanderZ-Band Sep 15, 2026
f778a0a
ci: improve Core coverage email layout
AlexanderZ-Band Sep 15, 2026
5b17eb1
style: format Core coverage report files
AlexanderZ-Band Sep 15, 2026
36641cf
fix: address Core coverage review findings
AlexanderZ-Band Sep 17, 2026
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
11 changes: 11 additions & 0 deletions .github/actions/git-https-auth/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: "Configure git for HTTPS GitHub access"
description: >-
Rewrites git@github.com: URLs to https://github.com/ so a later checkout
or clone authenticates over HTTPS (e.g. with a token) instead of SSH.

runs:
using: composite
steps:
- name: Configure git to use HTTPS for GitHub
shell: bash
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
Comment thread
AlexanderZ-Band marked this conversation as resolved.
Comment thread
AlexanderZ-Band marked this conversation as resolved.
3 changes: 1 addition & 2 deletions .github/actions/setup-e2e/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ runs:
using: composite
steps:
- name: Configure git to use HTTPS for GitHub
shell: bash
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
uses: ./.github/actions/git-https-auth

- name: Install uv (with cache) and Python
uses: astral-sh/setup-uv@v7
Expand Down
202 changes: 202 additions & 0 deletions .github/scripts/post-core-coverage-digest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Post a compact weekly digest for a Core consumer-coverage artifact."""

from __future__ import annotations

import os
import subprocess
from dataclasses import dataclass
from pathlib import Path

LOW_COVERAGE_PERCENT = 80.0
MAX_MISSED_LINE_RANGES = 8
MAX_LOW_COVERAGE_FILES = 8


@dataclass(frozen=True)
class FileCoverage:
path: str
found: int
hit: int
functions_found: int
functions_hit: int
missed_lines: tuple[int, ...]

@property
def percent(self) -> float:
return 100 * self.hit / self.found if self.found else 100.0

@property
def missed(self) -> int:
return self.found - self.hit


def display_path(path: str) -> str:
marker = "/crates/"
return path[path.index(marker) + 1 :] if marker in path else Path(path).name


def format_line_ranges(numbers: tuple[int, ...]) -> str:
ranges: list[str] = []
start = previous = None
for number in numbers:
if start is None:
start = previous = number
elif number == previous + 1:
previous = number
else:
ranges.append(str(start) if start == previous else f"{start}-{previous}")
start = previous = number
if start is not None:
ranges.append(str(start) if start == previous else f"{start}-{previous}")
shown = ", ".join(ranges[:MAX_MISSED_LINE_RANGES])
return (
shown
if len(ranges) <= MAX_MISSED_LINE_RANGES
else f"{shown}, … ({len(ranges) - MAX_MISSED_LINE_RANGES} more ranges)"
)


def coverage_marker(percent: float) -> str:
if percent >= LOW_COVERAGE_PERCENT:
return "🟢"
if percent >= 50:
return "🟠"
return "🔴"


def parse_lcov(path: Path) -> list[FileCoverage]:
records: list[FileCoverage] = []
source: str | None = None
found = hit = functions_found = functions_hit = 0
missed_lines: list[int] = []
for line in path.read_text(encoding="utf-8").splitlines():
if line.startswith("SF:"):
source = line[3:]
elif line.startswith("LF:"):
found = int(line[3:])
elif line.startswith("LH:"):
hit = int(line[3:])
elif line.startswith("FNF:"):
functions_found = int(line[4:])
elif line.startswith("FNH:"):
functions_hit = int(line[4:])
elif line.startswith("DA:"):
line_number, count = line[3:].split(",", maxsplit=1)
if count == "0":
missed_lines.append(int(line_number))
elif line == "end_of_record" and source is not None:
records.append(
FileCoverage(
display_path(source),
found,
hit,
functions_found,
functions_hit,
tuple(missed_lines),
)
)
source = None
found = hit = functions_found = functions_hit = 0
missed_lines = []
return records


def render_digest(
*, lcov_path: Path, label: str, recipients: str, run_url: str, result: str
) -> str:
header = "## 📊 Weekly Core coverage"
if not lcov_path.is_file():
return "\n".join(
[
header,
recipients,
"",
f"⚠️ **Coverage unavailable** · workflow `{result}`",
"",
"No LCOV report was produced. Open the run for the failure details.",
"",
f"[Open run]({run_url})",
]
)

files = parse_lcov(lcov_path)
found = sum(item.found for item in files)
hit = sum(item.hit for item in files)
functions_found = sum(item.functions_found for item in files)
functions_hit = sum(item.functions_hit for item in files)
gaps = sorted(
(item for item in files if item.percent < LOW_COVERAGE_PERCENT),
key=lambda item: (item.percent, -item.found, item.path),
)
line_percent = 100 * hit / found if found else 0.0
function_percent = 100 * functions_hit / functions_found if functions_found else 0.0
healthy_files = len(files) - len(gaps)
lines = [
header,
"",
recipients,
"",
f"**{label}**",
"",
"### Coverage snapshot",
"",
"| Signal | Result |",
"| --- | --- |",
f"| Lines | {coverage_marker(line_percent)} **{line_percent:.2f}%** · {hit}/{found} covered · {found - hit} missing |",
f"| Functions | {coverage_marker(function_percent)} **{function_percent:.2f}%** · {functions_hit}/{functions_found} covered · {functions_found - functions_hit} missing |",
f"| Files at target | {coverage_marker(100 * healthy_files / len(files) if files else 0)} **{healthy_files}/{len(files)}** at or above {LOW_COVERAGE_PERCENT:.0f}% |",
"",
]
if gaps:
lines.extend(
[
"### 🎯 Where to focus",
"",
"| Source file | Coverage gap |",
"| --- | --- |",
]
)
lines.extend(
f"| `{item.path}` | {coverage_marker(item.percent)} **{item.percent:.2f}%** · {item.missed} lines missing<br>Lines `{format_line_ranges(item.missed_lines)}` |"
for item in gaps[:MAX_LOW_COVERAGE_FILES]
)
if len(gaps) > MAX_LOW_COVERAGE_FILES:
lines.extend(
[
"",
f"_Plus {len(gaps) - MAX_LOW_COVERAGE_FILES} more low-coverage files in the artifact._",
]
)
else:
lines.append("✅ Every measured source file meets the coverage target.")
lines.extend(
["", f"[View the run and full coverage artifact →]({run_url}#artifacts)"]
)
return "\n".join(lines)


def main() -> None:
digest = render_digest(
lcov_path=Path(os.environ["LCOV_PATH"]),
label=os.environ["REPORT_LABEL"],
recipients=os.environ["RECIPIENTS"],
run_url=os.environ["RUN_URL"],
result=os.environ["WORKFLOW_RESULT"],
)
subprocess.run(
[
"gh",
"api",
f"repos/{os.environ['REPO']}/commits/{os.environ['SHA']}/comments",
"--method",
"POST",
"-f",
f"body={digest}",
],
check=True,
)


if __name__ == "__main__":
main()
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Configure git to use HTTPS for GitHub
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
uses: ./.github/actions/git-https-auth

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Configure git to use HTTPS for GitHub
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
uses: ./.github/actions/git-https-auth

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand Down Expand Up @@ -124,7 +124,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Configure git to use HTTPS for GitHub
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
uses: ./.github/actions/git-https-auth

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand Down Expand Up @@ -187,7 +187,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Configure git to use HTTPS for GitHub
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
uses: ./.github/actions/git-https-auth

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand Down Expand Up @@ -233,7 +233,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Configure git to use HTTPS for GitHub
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
uses: ./.github/actions/git-https-auth

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand Down
Loading
Loading