Skip to content

Commit 2e7c277

Browse files
authored
fix: remove report-bundle CodeBuild secondary artifact and add --local-run-dir support (#162)
* fix: remove report-bundle CodeBuild secondary artifact and add --local-run-dir support * fix: address PR review feedback for codebuild workflow - Replace report artifact fallback name with static 'report-head' to avoid invalid characters from branch names - Narrow evaluation secondary artifact from '**/*' to specific YAML metric and report files only - Bump upload-artifact from v6 to v7 - Add archive: false to all upload-artifact steps to prevent double-zip
1 parent 799544f commit 2e7c277

6 files changed

Lines changed: 259 additions & 45 deletions

File tree

.github/workflows/codebuild.yml

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ jobs:
238238
echo "========================================"
239239
uv run python run.py full --rules-ref "$RULES_REF"
240240
241-
# Package evaluation results into a zip for the trend report
241+
# Locate the evaluation run directory for trend report input
242242
EVAL_RUN_DIR=$(ls -dt "$EVALUATOR_DIR/runs/"*/*/ 2>/dev/null | head -1)
243243
LOCAL_BUNDLE_ARG=""
244244
if [[ -n "$EVAL_RUN_DIR" ]]; then
@@ -249,13 +249,7 @@ jobs:
249249
sed -i "s|rules_ref:.*|rules_ref: pr-$PR_NUMBER|" "$EVAL_RUN_DIR/run-meta.yaml"
250250
echo "Patched run-meta.yaml: rules_ref -> pr-$PR_NUMBER"
251251
fi
252-
BUNDLE_ZIP="/tmp/current-pr-bundle.zip"
253-
(cd "$EVAL_RUN_DIR" && zip -j "$BUNDLE_ZIP" \
254-
run-meta.yaml run-metrics.yaml test-results.yaml \
255-
contract-test-results.yaml quality-report.yaml \
256-
qualitative-comparison.yaml 2>/dev/null) && \
257-
LOCAL_BUNDLE_ARG="--local-bundle $BUNDLE_ZIP"
258-
cp "$BUNDLE_ZIP" "$CODEBUILD_SRC_DIR/.codebuild/report-bundle.zip"
252+
LOCAL_BUNDLE_ARG="--local-run-dir $EVAL_RUN_DIR"
259253
else
260254
echo "WARNING: No evaluation run folder found -- trend report will not include current PR"
261255
fi
@@ -287,7 +281,16 @@ jobs:
287281
secondary-artifacts:
288282
evaluation:
289283
files:
290-
- '**/*'
284+
- '**/contract-test-results.yaml'
285+
- '**/evaluation-config.yaml'
286+
- '**/qualitative-comparison.yaml'
287+
- '**/quality-report.yaml'
288+
- '**/report.yaml'
289+
- '**/report.md'
290+
- '**/report.html'
291+
- '**/run-meta.yaml'
292+
- '**/run-metrics.yaml'
293+
- '**/test-results.yaml'
291294
name: evaluation
292295
discard-paths: no
293296
base-directory: .codebuild/regression-runs
@@ -297,12 +300,6 @@ jobs:
297300
name: trend
298301
discard-paths: no
299302
base-directory: .codebuild/trend-runs
300-
report-bundle:
301-
files:
302-
- 'report-bundle.zip'
303-
name: report-bundle
304-
discard-paths: yes
305-
base-directory: .codebuild
306303
307304
- name: Build ID
308305
if: always() && steps.cache-check.outputs.cache-hit != 'true'
@@ -356,44 +353,71 @@ jobs:
356353
key: ${{ env.CODEBUILD_PROJECT_NAME }}-${{ github.ref_name }}-${{ github.sha }}
357354

358355
- name: Upload CodeBuild primary artifact
359-
if: ${{ !env.ACT }} # incompatible with act (upload-artifact v6)
360-
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
356+
if: ${{ !env.ACT }}
357+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
361358
with:
362359
name: ${{ env.CODEBUILD_PROJECT_NAME }}.zip
363360
path: ${{ github.workspace }}/.codebuild/downloads/${{ env.CODEBUILD_PROJECT_NAME }}.zip
364361
if-no-files-found: error
365-
362+
archive: false
366363

367364
- name: Upload Evaluation Report
368-
if: ${{ !env.ACT }} # incompatible with act (upload-artifact v6)
369-
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
365+
if: ${{ !env.ACT }}
366+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
370367
with:
371368
name: evaluation.zip
372369
path: ${{ github.workspace }}/.codebuild/downloads/evaluation.zip
373370
if-no-files-found: error
374-
371+
archive: false
375372

376373
- name: Upload Trend Report
377-
if: ${{ !env.ACT }} # incompatible with act (upload-artifact v6)
378-
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
374+
if: ${{ !env.ACT }}
375+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
379376
with:
380377
name: trend.zip
381378
path: ${{ github.workspace }}/.codebuild/downloads/trend.zip
382379
if-no-files-found: error
380+
archive: false
383381

384382

383+
- name: Extract Report Bundle from Evaluation
384+
if: steps.cache-check.outputs.cache-hit != 'true'
385+
run: |
386+
DOWNLOADS="${ACT_CODEBUILD_DIR:-${GITHUB_WORKSPACE}/.codebuild/downloads}"
387+
BUNDLE_DIR="$DOWNLOADS/report-bundle-staging"
388+
mkdir -p "$BUNDLE_DIR"
389+
390+
YAML_FILES=(
391+
run-meta.yaml
392+
run-metrics.yaml
393+
test-results.yaml
394+
contract-test-results.yaml
395+
quality-report.yaml
396+
qualitative-comparison.yaml
397+
)
398+
for f in "${YAML_FILES[@]}"; do
399+
unzip -j -o "$DOWNLOADS/evaluation.zip" "*/$f" -d "$BUNDLE_DIR" 2>/dev/null || true
400+
done
401+
402+
if [[ -f "$BUNDLE_DIR/run-meta.yaml" ]]; then
403+
(cd "$BUNDLE_DIR" && zip -j "$DOWNLOADS/report-bundle.zip" "${YAML_FILES[@]}" 2>/dev/null) || true
404+
echo "Created report-bundle.zip from evaluation.zip contents"
405+
else
406+
echo "WARNING: run-meta.yaml not found in evaluation.zip — report bundle will be empty"
407+
fi
408+
385409
- name: Upload Report Bundle
386410
if: ${{ !env.ACT }}
387-
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
411+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
388412
with:
389413
name: >-
390414
${{ github.event_name == 'pull_request'
391415
&& format('report-pr-{0}', github.event.pull_request.number)
392416
|| github.ref == 'refs/heads/main' && 'report-main'
393-
|| format('report-{0}', github.ref_name) }}
417+
|| format('report-head') }}
394418
path: ${{ github.workspace }}/.codebuild/downloads/report-bundle.zip
395419
if-no-files-found: warn
396-
420+
archive: false
397421

398422
- name: Upload artifacts to release
399423
if: startsWith(github.ref, 'refs/tags/v')

scripts/aidlc-evaluator/packages/trend-reports/src/trend_reports/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
33
Usage:
44
from trend_reports import collect_trend_data, render_trend_markdown
5-
trend = collect_trend_data(zip_paths, baseline_path, repo, work_dir)
5+
trend = collect_trend_data(bundle_paths, baseline_path, repo, work_dir)
66
markdown = render_trend_markdown(trend)
77
88
CLI:
99
python -m trend_reports trend --baseline golden.yaml --format all
1010
"""
1111

12-
from trend_reports.collector import collect_trend_data, compute_deltas, sort_runs
12+
from trend_reports.collector import (
13+
collect_from_directory,
14+
collect_trend_data,
15+
compute_deltas,
16+
sort_runs,
17+
)
1318
from trend_reports.gate import check_regressions
1419
from trend_reports.models import (
1520
BaselineMetrics,
@@ -35,6 +40,7 @@
3540
"TrendReportError",
3641
"VersionDelta",
3742
"check_regressions",
43+
"collect_from_directory",
3844
"collect_trend_data",
3945
"compute_deltas",
4046
"render_trend_html",

scripts/aidlc-evaluator/packages/trend-reports/src/trend_reports/__main__.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ def main() -> None:
7676
dest="local_bundles",
7777
help="Local zip bundle path(s) to include as additional data points",
7878
)
79+
trend_parser.add_argument(
80+
"--local-run-dir",
81+
nargs="*",
82+
dest="local_run_dirs",
83+
help="Local run directory path(s) to include as additional data points",
84+
)
7985

8086
args = parser.parse_args()
8187

@@ -104,6 +110,7 @@ def main() -> None:
104110
gate=args.gate,
105111
tags=args.tags,
106112
local_bundles=args.local_bundles,
113+
local_run_dirs=args.local_run_dirs,
107114
)
108115
sys.exit(exit_code)
109116
except TrendReportError as exc:
@@ -122,6 +129,7 @@ def cmd_trend(
122129
gate: bool,
123130
tags: list[str] | None,
124131
local_bundles: list[str] | None = None,
132+
local_run_dirs: list[str] | None = None,
125133
) -> int:
126134
"""Main orchestration. Returns 0 on success, 1 on gate failure."""
127135
from .collector import collect_trend_data
@@ -145,30 +153,39 @@ def cmd_trend(
145153

146154
# 2a. Release bundles (required)
147155
logger.info("Fetching release bundles from %s …", repo)
148-
zip_paths = fetch_release_bundles(repo, tags, work_dir)
149-
logger.info("Fetched %d release bundle(s)", len(zip_paths))
156+
bundle_paths = fetch_release_bundles(repo, tags, work_dir)
157+
logger.info("Fetched %d release bundle(s)", len(bundle_paths))
150158

151159
# 2b. Local bundles (from --local-bundle flag)
152160
if local_bundles:
153161
for bundle_str in local_bundles:
154162
bundle_path = Path(bundle_str)
155163
if not bundle_path.exists():
156164
raise TrendReportError(f"Local bundle not found: {bundle_path}")
157-
zip_paths.append(bundle_path)
165+
bundle_paths.append(bundle_path)
158166
logger.info("Added local bundle: %s", bundle_path)
159167

168+
# 2b-2. Local run directories (from --local-run-dir flag)
169+
if local_run_dirs:
170+
for dir_str in local_run_dirs:
171+
dir_path = Path(dir_str)
172+
if not dir_path.is_dir():
173+
raise TrendReportError(f"Local run directory not found: {dir_path}")
174+
bundle_paths.append(dir_path)
175+
logger.info("Added local run directory: %s", dir_path)
176+
160177
# 2c. Remote pre-release bundles (from GitHub Actions Artifacts)
161178
logger.info("Fetching pre-release bundles …")
162179
prerelease_paths = fetch_prerelease_bundles(repo, cache_prefix, work_dir)
163180
if prerelease_paths:
164181
logger.info("Fetched %d pre-release bundle(s)", len(prerelease_paths))
165-
zip_paths.extend(prerelease_paths)
182+
bundle_paths.extend(prerelease_paths)
166183
else:
167184
logger.info("No pre-release bundles found — continuing with releases only")
168185

169186
# 3. Collect and assemble
170187
logger.info("Parsing bundles …")
171-
trend = collect_trend_data(zip_paths, Path(baseline), repo, work_dir)
188+
trend = collect_trend_data(bundle_paths, Path(baseline), repo, work_dir)
172189
logger.info("Assembled trend data for %d runs", len(trend.runs))
173190

174191
# 4. Render into a timestamped subdirectory

scripts/aidlc-evaluator/packages/trend-reports/src/trend_reports/collector.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Zip extraction, YAML parsing, run classification, and trend assembly."""
1+
"""Zip/directory extraction, YAML parsing, run classification, and trend assembly."""
22

33
from __future__ import annotations
44

@@ -32,7 +32,7 @@
3232

3333
logger = logging.getLogger(__name__)
3434

35-
# The YAML files we expect inside every report zip.
35+
# The YAML files we expect inside every report bundle (zip or directory).
3636
REQUIRED_YAML = {
3737
"run-meta": "run-meta.yaml",
3838
"run-metrics": "run-metrics.yaml",
@@ -306,13 +306,15 @@ def classify_run(rules_ref: str) -> tuple[RunType, str, SemVer | None, int | Non
306306
# ---------------------------------------------------------------------------
307307

308308

309-
def collect_from_zip(zip_path: Path, work_dir: Path) -> RunData:
310-
"""Extract a zip bundle and parse all YAML files into a RunData."""
311-
run_dir = extract_zip(zip_path, work_dir)
309+
def _collect_from_run_dir(run_dir: Path, source_label: str) -> RunData:
310+
"""Parse YAML files in *run_dir* into a RunData.
311+
312+
*source_label* is used in error messages (e.g. the zip path or directory).
313+
"""
312314
yaml_files = find_yaml_files(run_dir)
313315

314316
if "run-meta" not in yaml_files:
315-
raise CollectorError(f"run-meta.yaml missing from {zip_path} — cannot classify run")
317+
raise CollectorError(f"run-meta.yaml missing from {source_label} — cannot classify run")
316318

317319
meta = parse_run_meta(yaml_files["run-meta"])
318320
run_type, label, semver, pr_number = classify_run(meta.config.rules_ref)
@@ -365,6 +367,23 @@ def collect_from_zip(zip_path: Path, work_dir: Path) -> RunData:
365367
)
366368

367369

370+
def collect_from_zip(zip_path: Path, work_dir: Path) -> RunData:
371+
"""Extract a zip bundle and parse all YAML files into a RunData."""
372+
run_dir = extract_zip(zip_path, work_dir)
373+
return _collect_from_run_dir(run_dir, source_label=str(zip_path))
374+
375+
376+
def collect_from_directory(dir_path: Path) -> RunData:
377+
"""Parse all YAML files from a plain directory into a RunData.
378+
379+
Unlike :func:`collect_from_zip`, no extraction step is needed.
380+
The directory must contain the expected YAML files directly.
381+
"""
382+
if not dir_path.is_dir():
383+
raise CollectorError(f"Not a directory: {dir_path}")
384+
return _collect_from_run_dir(dir_path, source_label=str(dir_path))
385+
386+
368387
def load_baseline(golden_path: Path) -> BaselineMetrics:
369388
"""Parse a golden.yaml baseline file into BaselineMetrics."""
370389
if not golden_path.exists():
@@ -443,12 +462,12 @@ def compute_deltas(runs: list[RunData]) -> list[VersionDelta]:
443462

444463

445464
def collect_trend_data(
446-
zip_paths: list[Path],
465+
bundle_paths: list[Path],
447466
baseline_path: Path,
448467
repo: str,
449468
work_dir: Path | None = None,
450469
) -> TrendData:
451-
"""Parse all zip bundles and assemble a TrendData."""
470+
"""Parse all bundles (zip files or directories) and assemble a TrendData."""
452471
import tempfile
453472

454473
if work_dir is None:
@@ -457,13 +476,16 @@ def collect_trend_data(
457476
baseline = load_baseline(baseline_path)
458477

459478
runs: list[RunData] = []
460-
for zp in zip_paths:
461-
logger.info("Collecting data from %s …", zp.name)
479+
for bp in bundle_paths:
480+
logger.info("Collecting data from %s …", bp.name)
462481
try:
463-
run = collect_from_zip(zp, work_dir)
482+
if bp.is_dir():
483+
run = collect_from_directory(bp)
484+
else:
485+
run = collect_from_zip(bp, work_dir)
464486
runs.append(run)
465487
except CollectorError as exc:
466-
logger.warning("Skipping %s: %s", zp.name, exc)
488+
logger.warning("Skipping %s: %s", bp.name, exc)
467489

468490
if not runs:
469491
raise CollectorError("No runs could be parsed from the provided bundles.")

0 commit comments

Comments
 (0)