From c492164fbf1230f1d7e7635368463f310eccc5c2 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Thu, 23 Apr 2026 03:56:04 -0500 Subject: [PATCH] Add codeflash org CI audit case study and interactive Dash report Case study in .codeflash/krrt7/codeflash-ai/ci-audit/ with README, status, and raw data (fork activity, PRs merged). Interactive Dash report in reports/codeflash-ci-audit/ with two tabs: Executive Summary (hero metrics, cost impact charts, before/after) and Full Detail (fork breakdown, findings table, PR inventory, methodology). Key numbers: 71% fewer workflow runs, ~$12K/yr in Enterprise overage savings, 200+ forks disabled, 11 PRs merged across 2 repos. --- .../krrt7/codeflash-ai/ci-audit/README.md | 112 +++ .../ci-audit/data/fork-ci-activity.tsv | 13 + .../codeflash-ai/ci-audit/data/prs-merged.tsv | 12 + .../krrt7/codeflash-ai/ci-audit/status.md | 27 + reports/codeflash-ci-audit/app.py | 919 ++++++++++++++++++ reports/codeflash-ci-audit/data.json | 96 ++ reports/codeflash-ci-audit/theme.py | 80 ++ 7 files changed, 1259 insertions(+) create mode 100644 .codeflash/krrt7/codeflash-ai/ci-audit/README.md create mode 100644 .codeflash/krrt7/codeflash-ai/ci-audit/data/fork-ci-activity.tsv create mode 100644 .codeflash/krrt7/codeflash-ai/ci-audit/data/prs-merged.tsv create mode 100644 .codeflash/krrt7/codeflash-ai/ci-audit/status.md create mode 100644 reports/codeflash-ci-audit/app.py create mode 100644 reports/codeflash-ci-audit/data.json create mode 100644 reports/codeflash-ci-audit/theme.py diff --git a/.codeflash/krrt7/codeflash-ai/ci-audit/README.md b/.codeflash/krrt7/codeflash-ai/ci-audit/README.md new file mode 100644 index 0000000..b401fa2 --- /dev/null +++ b/.codeflash/krrt7/codeflash-ai/ci-audit/README.md @@ -0,0 +1,112 @@ +# Codeflash Org CI Audit + +Comprehensive CI/CD audit of the `codeflash-ai` GitHub org: 200+ forks, 2 main repos (`codeflash`, `codeflash-internal`). + +## Background + +While working on Unstructured optimization case studies, discovered that codeflash-ai org forks were running GitHub Actions unnecessarily — Dependabot updates, upstream scheduled CI, and failing workflows creating ~960 noise runs/month across 26 active forks out of 200+. + +Expanded scope to a full audit of CI hygiene across both main repos. + +## Results + +### Fork CI + +| Metric | Before | After | +|---|---|---| +| Forks with active CI | 26 of 200+ | 0 | +| Wasted runs/month | ~960 | 0 | +| Estimated cost/yr (private rates) | $248 | $0 | + +Top offenders before disable: + +| Repo | Runs (audit period) | Cost/yr | Pattern | +|---|---|---|---| +| sglang | 4,936 | $4 | CI Failure Monitor (cron, skips on fork) | +| kornia | 308 | $226 | Daily macOS + Windows test matrix (91% of all fork cost) | +| ray | 527 | $3 | Dependabot + Stale PR bot | +| lerobot | 330 | $12 | Dependabot + Full Tests | +| next.js | 217 | $3 | Dependabot only | +| 21 others | 694 | <$5 | Dependabot / misc | + +Actions disabled on all 200+ forks on 2026-04-23. + +### Main Repo — codeflash + +| Finding | Impact | Fix | +|---|---|---| +| Wildcard path triggers (`paths: ['**']`) on 12 E2E workflows | Every PR (including README edits) triggered ~2hrs of E2E tests | Targeted path filters (#2025) | +| Broken claude-code-action (v1.0.90 Bedrock OIDC regression) | 60-100% failure rate on Claude Code workflows | Pinned to v1.0.89 (#2026) | +| Dependabot scanning test fixtures (no `dependabot.yml`) | 70% of Dependabot runs failing on `code_to_optimize/` fixtures | Added explicit config excluding fixtures (#2027) | +| 13 ghost workflows (deleted source files, still active in UI) | Actions dashboard clutter, confusing status signals | `gh workflow disable` on all 13 | +| 17 separate workflow files with individual required checks | Workflow-only PRs stuck at "Pending", required admin merge | Consolidated into single `ci.yaml` with gate job (#2044) | +| Stale `codeflash.yaml` | Superseded by `codeflash-optimize.yaml`, still active | Deleted (#2056) | +| No npm/Maven caching, duplicate workflows | Slow JS/Java CI, redundant compute | Consolidated + caching (#2050) | +| Broken Windows paths, outdated action versions | CI failures on Windows matrix | Fixed paths + upgraded actions (#2052, #2053) | +| Flaky E2E tests | False failures blocking merges | Increased test data size + timeout (#2051, #2057) | + +### Main Repo — codeflash-internal + +| Finding | Impact | Fix | +|---|---|---| +| Deploy AI Service path filter included `.github/workflows/**` | Any workflow edit triggered a production deploy | Scoped to actual service paths (#2588) | +| Claude Code workflow had no path filters | Fired on every PR/comment regardless of files changed | Added `paths-ignore` (#2588) | +| Publish to PyPI permanently disabled (`if: false`) | Created skipped run noise on every push to main | Disabled via API + workflow fix (#2588) | +| Broken claude-code-action (same v1.0.90 regression) | 85% failure rate | Pinned to v1.0.89 (#2587) | + +### Operational Improvements + +| Before | After | +|---|---| +| 17 individual workflow files, 13 required checks in branch protection | Single `ci.yaml`, 1 gate check (`required-checks-passed`) | +| Workflow-only PRs stuck at "Pending", required admin merge | Skipped jobs report as "skipped", gate accepts them, self-service merge | +| Branch protection rules | Repository rulesets (modern GitHub standard) | +| ~$1.85 compute burned on every non-code PR | ~$0.001 (change detection only) | +| 22 workflow files in repo | 7 workflow files | + +## PRs Merged + +### codeflash + +| PR | Date | Description | +|---|---|---| +| [#2025](https://github.com/codeflash-ai/codeflash/pull/2025) | Apr 9 | Replace wildcard path triggers on 12 E2E workflows | +| [#2026](https://github.com/codeflash-ai/codeflash/pull/2026) | Apr 9 | Pin claude-code-action to v1.0.89 (Bedrock auth fix) | +| [#2027](https://github.com/codeflash-ai/codeflash/pull/2027) | Apr 9 | Exclude test fixtures from Dependabot | +| [#2044](https://github.com/codeflash-ai/codeflash/pull/2044) | Apr 9 | Consolidate 17 workflows into single ci.yaml with gate job | +| [#2047](https://github.com/codeflash-ai/codeflash/pull/2047) | Apr 9 | Path filters, validate-pr action, fetch-depth, continue-on-error | +| [#2050](https://github.com/codeflash-ai/codeflash/pull/2050) | Apr 9 | npm cache, Maven consolidation, remove duplicate workflow | +| [#2052](https://github.com/codeflash-ai/codeflash/pull/2052) | Apr 10 | Upgrade action versions, add uv cache, fix broken paths, DRY publish | +| [#2053](https://github.com/codeflash-ai/codeflash/pull/2053) | Apr 10 | Fix shell: bash for Windows conditional install | +| [#2056](https://github.com/codeflash-ai/codeflash/pull/2056) | Apr 10 | Delete disabled codeflash.yaml workflow | + +### codeflash-internal + +| PR | Date | Description | +|---|---|---| +| [#2587](https://github.com/codeflash-ai/codeflash-internal/pull/2587) | Apr 9 | Pin claude-code-action to v1.0.89 | +| [#2588](https://github.com/codeflash-ai/codeflash-internal/pull/2588) | Apr 9 | Fix deploy path filter, Claude Code paths, disable PyPI | + +### Direct Actions + +| Action | Date | Details | +|---|---|---| +| Disabled 13 ghost workflows | Apr 9 | `gh workflow disable` on codeflash | +| Disabled Publish to PyPI workflow | Apr 9 | `gh workflow disable` on codeflash-internal | +| Closed stale Dependabot PR #2012 | Apr 9 | vite bump to test fixture | +| Migrated branch protection to rulesets | Apr 9 | Modern GitHub standard, single gate check | +| Disabled Actions on 200+ forks | Apr 23 | `gh api --method PUT` on all org forks | + +## Methodology + +1. **Inventory** — `gh repo list codeflash-ai` to enumerate all 200+ repos, classify as fork vs primary +2. **Fork scan** — Query Actions run counts per fork since Apr 2025, identify 26 active forks +3. **Compute cost** — Sample job-level data (duration, runner type), calculate at GitHub rates ($0.008/min Linux, $0.016/min Windows, $0.08/min macOS) +4. **Main repo audit** — List all workflows, check run history, failure rates, ghost detection, trigger config +5. **Root cause analysis** — Compare working vs broken runs by commit SHA + timestamp to pinpoint regressions + +## Monitoring + +- **anthropics/claude-code-action#1196** — unpin from v1.0.89 once Bedrock OIDC fix lands upstream +- **Dependabot alerts** — 24 known vulnerabilities (1 critical, 8 high) at time of audit; new `dependabot.yml` targets real deps only +- **Fork re-enable** — if a fork is needed for active development: `echo '{"enabled":true}' | gh api --method PUT repos/codeflash-ai//actions/permissions --input -` diff --git a/.codeflash/krrt7/codeflash-ai/ci-audit/data/fork-ci-activity.tsv b/.codeflash/krrt7/codeflash-ai/ci-audit/data/fork-ci-activity.tsv new file mode 100644 index 0000000..4f26186 --- /dev/null +++ b/.codeflash/krrt7/codeflash-ai/ci-audit/data/fork-ci-activity.tsv @@ -0,0 +1,13 @@ +repo runs_audit_period cost_yr_usd runner_types pattern +sglang 4936 4 Linux CI Failure Monitor (cron, skips on fork) +ray 527 3 Linux Dependabot + Stale PR bot +lerobot 330 12 Linux Dependabot + Full Tests +kornia 308 226 Linux + Windows + macOS Daily scheduled CPU test matrix +next.js 217 3 Linux Dependabot only +strapi 118 1 Linux Dependabot (lodash across 20+ packages) +rasa 85 1 Linux Dependabot +Rocket.Chat 80 1 Linux Dependabot +nest 73 1 Linux Dependabot (@nestjs/core across 20+ samples) +openclaw 68 1 Linux Dependabot +appsmith 64 1 Linux Dependabot +other_15 206 1 Linux Dependabot / misc diff --git a/.codeflash/krrt7/codeflash-ai/ci-audit/data/prs-merged.tsv b/.codeflash/krrt7/codeflash-ai/ci-audit/data/prs-merged.tsv new file mode 100644 index 0000000..0a6409f --- /dev/null +++ b/.codeflash/krrt7/codeflash-ai/ci-audit/data/prs-merged.tsv @@ -0,0 +1,12 @@ +pr repo date title +2025 codeflash 2026-04-09 ci: replace wildcard path triggers on E2E tests +2026 codeflash 2026-04-09 ci: pin claude-code-action to v1.0.89 (fix Bedrock auth) +2027 codeflash 2026-04-09 ci: exclude test fixtures from Dependabot +2044 codeflash 2026-04-09 ci: consolidate required checks into single gate workflow +2047 codeflash 2026-04-09 ci: consolidate improvements — paths, validate-pr action, fetch-depth, continue-on-error +2050 codeflash 2026-04-09 Optimize CI: npm cache, Maven consolidation, remove duplicate workflow +2052 codeflash 2026-04-10 ci: upgrade action versions, add uv cache, fix broken paths, DRY publish +2053 codeflash 2026-04-10 fix(ci): add shell: bash to conditional install step for Windows +2056 codeflash 2026-04-10 chore: delete disabled codeflash.yaml workflow +2587 codeflash-internal 2026-04-09 ci: pin claude-code-action to v1.0.89 +2588 codeflash-internal 2026-04-09 ci: fix deploy path filter, Claude Code paths, disable PyPI diff --git a/.codeflash/krrt7/codeflash-ai/ci-audit/status.md b/.codeflash/krrt7/codeflash-ai/ci-audit/status.md new file mode 100644 index 0000000..a35750a --- /dev/null +++ b/.codeflash/krrt7/codeflash-ai/ci-audit/status.md @@ -0,0 +1,27 @@ +# ci-audit Status + +Last updated: 2026-04-23 + +## Current state + +Complete. All audit findings addressed, all forks disabled, all PRs merged. + +## What was completed + +### Session 1 (Apr 9) +- Full CI audit of codeflash-ai org (93 forks at the time + 2 main repos) +- 5 PRs merged: path triggers (#2025), claude-code-action pin (#2026, #2587), Dependabot config (#2027), internal fixes (#2588) +- 13 ghost workflows disabled +- CI gate workflow consolidated (#2044) +- Additional CI improvements (#2047, #2050, #2052, #2053, #2056) +- Branch protection migrated to rulesets +- Audit report written to `~/Desktop/work/cf_org/codeflash-ci-audit/` + +### Session 2 (Apr 23) +- Disabled GitHub Actions on all 200+ org forks (previously blocked by missing org admin) +- Created case study in `.codeflash/krrt7/codeflash-ai/ci-audit/` + +## Remaining / monitoring + +- Unpin claude-code-action once anthropics/claude-code-action#1196 is fixed upstream +- Re-enable Actions on specific forks if needed for active optimization work diff --git a/reports/codeflash-ci-audit/app.py b/reports/codeflash-ci-audit/app.py new file mode 100644 index 0000000..b7d65aa --- /dev/null +++ b/reports/codeflash-ci-audit/app.py @@ -0,0 +1,919 @@ +"""Codeflash Org — CI Audit Report + +Two-tab report served at http://localhost:8051/: + 1. Executive Summary — hero metrics, key findings, before/after + 2. Full Detail — per-PR inventory, fork breakdown, methodology +""" + +import json +import os +from pathlib import Path + +import plotly.graph_objects as go +from dash import Dash, Input, Output, clientside_callback, dash_table, dcc, html +from theme import ( + ACCENT, + AMBER, + BG, + BLUE, + CARD, + CARD_BG, + CARD_BORDER, + DARK, + FONT, + GRAY, + GREEN, + GRID_OVERLAY, + LIGHT_GRAY, + LIGHT_GREEN, + LIGHT_RED, + MONO, + PURPLE, + RED, + SLATE, + TABLE_CELL, + TABLE_DATA, + TABLE_DATA_CONDITIONAL, + TABLE_HEADER, + TABLE_WRAP, + WHITE, +) + +# ── Data ──────────────────────────────────────────────────────────────────── +_DATA = json.loads((Path(__file__).parent / "data.json").read_text()) + +CODEFLASH_BASE = _DATA["codeflash_base"] +INTERNAL_BASE = _DATA["internal_base"] +FORK_CI = _DATA["fork_ci"] +FINDINGS = _DATA["findings"] +PRS_MERGED = _DATA["prs_merged"] +DIRECT_ACTIONS = _DATA["direct_actions"] +OPS = _DATA["operational_before_after"] +RUN_VOL = _DATA["run_volume"] +BILLING = _DATA["billing"] + +REPO_BASES = { + "codeflash": CODEFLASH_BASE, + "codeflash-internal": INTERNAL_BASE, +} + + +# ── Helpers ────────────────────────────────────────────────────────────────── + + +def hero_metric(value, label, detail, color=GREEN): + return html.Div( + [ + html.Div( + value, + style={ + "fontSize": "42px", + "fontWeight": "800", + "color": color, + "lineHeight": "1", + "letterSpacing": "-0.02em", + "fontFamily": FONT, + }, + ), + html.Div( + label, + style={ + "fontSize": "15px", + "fontWeight": "600", + "color": SLATE, + "marginTop": "8px", + }, + ), + html.Div( + detail, + style={"fontSize": "13px", "color": GRAY, "marginTop": "4px"}, + ), + ], + style={ + "background": CARD_BG, + "borderRadius": "16px", + "padding": "32px 24px", + "textAlign": "center", + "flex": "1 1 0%", + "minWidth": "0", + "border": f"1px solid {CARD_BORDER}", + }, + ) + + +def section(title, subtitle=None): + children = [ + html.H2( + title, + style={ + "fontSize": "22px", + "fontWeight": "700", + "color": SLATE, + "margin": "0", + "fontFamily": FONT, + "letterSpacing": "-0.01em", + }, + ) + ] + if subtitle: + children.append( + html.P( + subtitle, + style={ + "fontSize": "14px", + "color": GRAY, + "margin": "6px 0 0", + "lineHeight": "1.5", + }, + ) + ) + return html.Div(children, style={"margin": "56px 0 24px"}) + + +def card(children, **kw): + style = {**CARD} + for k, v in kw.items(): + style[k] = v + return html.Div(children, style=style) + + +def _logo_lockup(bolt_size="20px", text_size="24px", gap="12px", offset="4px"): + return html.Div( + [ + html.Span( + "⚡", + style={ + "fontSize": bolt_size, + "position": "relative", + "top": offset, + }, + ), + html.Span( + "codeflash", + style={ + "fontSize": text_size, + "fontWeight": "800", + "color": ACCENT, + "fontFamily": FONT, + "letterSpacing": "-0.03em", + }, + ), + ], + style={"display": "inline-flex", "alignItems": "center", "gap": gap}, + ) + + +def _badge(text, color): + return html.Span( + text, + style={ + "padding": "2px 10px", + "borderRadius": "999px", + "fontSize": "11px", + "fontWeight": "700", + "background": color, + "color": DARK, + }, + ) + + +# ── Charts ─────────────────────────────────────────────────────────────────── + + +def make_fork_chart(): + """Horizontal bar: fork CI runs by repo.""" + repos = [f["repo"] for f in FORK_CI[:8]] + runs = [f["runs"] for f in FORK_CI[:8]] + repos.reverse() + runs.reverse() + + fig = go.Figure() + fig.add_trace( + go.Bar( + y=repos, + x=runs, + orientation="h", + marker_color=[ACCENT if r > 500 else BLUE for r in runs], + marker_cornerradius=4, + text=[f"{r:,}" for r in runs], + textposition="outside", + textfont={"size": 12, "color": SLATE}, + ) + ) + fig.update_layout( + plot_bgcolor="rgba(0,0,0,0)", + paper_bgcolor="rgba(0,0,0,0)", + font={"family": FONT, "size": 13, "color": SLATE}, + xaxis={"title": "Workflow Runs (audit period)", "gridcolor": CARD_BORDER, "zeroline": False}, + yaxis={"title": "", "automargin": True}, + margin={"t": 10, "b": 50, "l": 10, "r": 60}, + height=320, + showlegend=False, + ) + return fig + + +def make_fork_cost_chart(): + """Pie chart: fork CI cost breakdown.""" + labels = [f["repo"] for f in FORK_CI if f["cost_yr"] >= 3] + values = [f["cost_yr"] for f in FORK_CI if f["cost_yr"] >= 3] + other = sum(f["cost_yr"] for f in FORK_CI if f["cost_yr"] < 3) + if other > 0: + labels.append("All others") + values.append(other) + + colors = [ACCENT, BLUE, GREEN, PURPLE, AMBER, GRAY, LIGHT_GRAY, RED][:len(labels)] + + fig = go.Figure() + fig.add_trace( + go.Pie( + labels=labels, + values=values, + hole=0.55, + marker={"colors": colors}, + textinfo="label+percent", + textfont={"size": 12, "color": WHITE}, + hovertemplate="%{label}: $%{value}/yr", + ) + ) + fig.update_layout( + plot_bgcolor="rgba(0,0,0,0)", + paper_bgcolor="rgba(0,0,0,0)", + font={"family": FONT, "size": 13, "color": SLATE}, + margin={"t": 10, "b": 10, "l": 10, "r": 10}, + height=300, + showlegend=False, + annotations=[ + { + "text": f"${sum(values)}/yr", + "x": 0.5, + "y": 0.5, + "font": {"size": 18, "color": ACCENT, "family": MONO}, + "showarrow": False, + } + ], + ) + return fig + + +def make_before_after_chart(): + """Grouped bar: operational metrics before vs after.""" + cats = ["Workflow Files", "Required Checks", "Failing Fork Runs/mo"] + before = [OPS["workflow_files"][0], OPS["required_checks"][0], OPS["fork_failing_runs_monthly"][0]] + after = [OPS["workflow_files"][1], OPS["required_checks"][1], OPS["fork_failing_runs_monthly"][1]] + + fig = go.Figure() + fig.add_trace( + go.Bar( + name="Before", + x=cats, + y=before, + marker_color=LIGHT_GRAY, + marker_cornerradius=6, + text=[str(v) for v in before], + textposition="outside", + textfont={"size": 13, "color": GRAY}, + ) + ) + fig.add_trace( + go.Bar( + name="After", + x=cats, + y=after, + marker_color=GREEN, + marker_cornerradius=6, + text=[str(v) for v in after], + textposition="outside", + textfont={"size": 13, "color": GREEN}, + ) + ) + fig.update_layout( + barmode="group", + bargap=0.3, + bargroupgap=0.1, + plot_bgcolor="rgba(0,0,0,0)", + paper_bgcolor="rgba(0,0,0,0)", + font={"family": FONT, "size": 13, "color": SLATE}, + yaxis={"gridcolor": CARD_BORDER, "zeroline": False}, + xaxis={"title": ""}, + margin={"t": 20, "b": 60, "l": 50, "r": 20}, + legend={ + "orientation": "h", + "yanchor": "bottom", + "y": 1.05, + "xanchor": "center", + "x": 0.5, + "font": {"size": 13}, + }, + height=360, + ) + return fig + + +def make_run_volume_chart(): + """Bar chart: monthly workflow runs before vs after.""" + months = ["Dec '25", "Jan '26", "Feb '26", "Mar '26", "Apr '26\n(projected)"] + runs = [4150, 9391, 21307, 14753, RUN_VOL["codeflash_apr_projected"]] + + fig = go.Figure() + fig.add_trace( + go.Bar( + x=months, + y=runs, + marker_color=[LIGHT_GRAY, LIGHT_GRAY, RED, AMBER, GREEN], + marker_cornerradius=6, + text=[f"{r:,}" for r in runs], + textposition="outside", + textfont={"size": 13, "color": SLATE}, + ) + ) + fig.add_hline( + y=RUN_VOL["codeflash_apr_projected"], + line_dash="dot", + line_color=GREEN, + opacity=0.5, + ) + fig.add_annotation( + x="Feb '26", + y=21307, + text="Audit starts (Apr 9)", + showarrow=True, + arrowhead=2, + arrowcolor=ACCENT, + ax=40, + ay=-40, + font={"size": 12, "color": ACCENT}, + ) + fig.update_layout( + plot_bgcolor="rgba(0,0,0,0)", + paper_bgcolor="rgba(0,0,0,0)", + font={"family": FONT, "size": 13, "color": SLATE}, + yaxis={"title": "Workflow Runs", "gridcolor": CARD_BORDER, "zeroline": False}, + xaxis={"title": ""}, + margin={"t": 20, "b": 60, "l": 70, "r": 20}, + height=360, + showlegend=False, + ) + return fig + + +def make_billing_chart(): + """Stacked bar: Enterprise minutes allotment vs overage.""" + cats = ["Before (Feb)", "After (Apr)"] + included = [BILLING["enterprise_included_min"], BILLING["enterprise_included_min"]] + overage = [BILLING["overage_before_min"], BILLING["overage_after_min"]] + + fig = go.Figure() + fig.add_trace( + go.Bar( + name="Included (50K)", + x=cats, + y=included, + marker_color=BLUE, + marker_cornerradius=6, + ) + ) + fig.add_trace( + go.Bar( + name="Overage", + x=cats, + y=overage, + marker_color=RED, + marker_cornerradius=6, + text=[f"{v:,} min\n(${v * 0.008:,.0f}/mo)" for v in overage], + textposition="outside", + textfont={"size": 12, "color": RED}, + ) + ) + fig.update_layout( + barmode="stack", + plot_bgcolor="rgba(0,0,0,0)", + paper_bgcolor="rgba(0,0,0,0)", + font={"family": FONT, "size": 13, "color": SLATE}, + yaxis={"title": "Billed Minutes/month", "gridcolor": CARD_BORDER, "zeroline": False}, + xaxis={"title": ""}, + margin={"t": 40, "b": 60, "l": 70, "r": 20}, + legend={ + "orientation": "h", + "yanchor": "bottom", + "y": 1.05, + "xanchor": "center", + "x": 0.5, + "font": {"size": 13}, + }, + height=380, + ) + return fig + + +# ── Tab: Executive Summary ─────────────────────────────────────────────────── + + +def _build_summary_tab(): + return html.Div( + id="summary-view", + children=[ + # Hero metrics + html.Div( + style={"display": "flex", "gap": "16px", "marginTop": "32px", "flexWrap": "wrap"}, + children=[ + hero_metric(f"~${BILLING['overage_saved_annual_usd']:,}/yr", "Overage Savings", "Enterprise minutes overage reduced 58%", GREEN), + hero_metric(f"{RUN_VOL['codeflash_reduction_pct']}%", "Fewer Runs", f"{RUN_VOL['codeflash_feb']:,} → {RUN_VOL['codeflash_apr_projected']:,}/mo", ACCENT), + hero_metric("200+", "Forks Disabled", "GitHub Actions turned off org-wide", BLUE), + hero_metric("22 → 7", "Workflows Consolidated", "Single ci.yaml with gate job", PURPLE), + ], + ), + + section( + "What We Found", + "Full CI/CD audit of the codeflash-ai GitHub org: 200+ forks and 2 main repos.", + ), + + # Key findings grid + html.Div( + style={"display": "grid", "gridTemplateColumns": "1fr 1fr", "gap": "16px"}, + children=[ + card([ + html.Div("Fork CI Waste", style={"fontWeight": "700", "color": ACCENT, "fontSize": "16px", "marginBottom": "12px"}), + html.P( + "26 of 200+ forks were running GitHub Actions — Dependabot updates, upstream scheduled CI, " + "and failing workflows creating ~960 noise runs/month. kornia alone was 91% of fork CI cost " + "due to a daily macOS + Windows test matrix.", + style={"color": GRAY, "fontSize": "14px", "lineHeight": "1.6", "margin": "0"}, + ), + ]), + card([ + html.Div("Wildcard Path Triggers", style={"fontWeight": "700", "color": RED, "fontSize": "16px", "marginBottom": "12px"}), + html.P( + "All 12 E2E workflows used paths: ['**'] — any file change (README, docs) " + "triggered the full E2E suite. A single docs-only PR burned ~2 hours of compute.", + style={"color": GRAY, "fontSize": "14px", "lineHeight": "1.6", "margin": "0"}, + ), + ]), + card([ + html.Div("Ghost Workflows", style={"fontWeight": "700", "color": AMBER, "fontSize": "16px", "marginBottom": "12px"}), + html.P( + "13 workflow files had been deleted from the repo but their entries remained active in " + "GitHub Actions. These cluttered the Actions UI and created confusing status signals.", + style={"color": GRAY, "fontSize": "14px", "lineHeight": "1.6", "margin": "0"}, + ), + ]), + card([ + html.Div("Broken claude-code-action", style={"fontWeight": "700", "color": RED, "fontSize": "16px", "marginBottom": "12px"}), + html.P( + "v1.0.90 broke Bedrock OIDC auth. Every Claude Code run was failing with 403s. " + "60-100% failure rate on codeflash, 85% on codeflash-internal.", + style={"color": GRAY, "fontSize": "14px", "lineHeight": "1.6", "margin": "0"}, + ), + ]), + ], + ), + + section( + "Run Volume & Cost Impact", + "Workflow runs dropped 71%. Enterprise minutes overage cut from ~215K to ~90K/month.", + ), + + # Run volume + billing side by side + html.Div( + style={"display": "grid", "gridTemplateColumns": "1fr 1fr", "gap": "16px"}, + children=[ + card([ + html.Div("Monthly Workflow Runs (codeflash)", style={"fontWeight": "700", "color": SLATE, "fontSize": "15px", "marginBottom": "12px"}), + dcc.Graph(figure=make_run_volume_chart(), config={"displayModeBar": False}), + ]), + card([ + html.Div("Enterprise Minutes Billing", style={"fontWeight": "700", "color": SLATE, "fontSize": "15px", "marginBottom": "12px"}), + html.P( + f"50K included minutes/month. Overage dropped from ~{BILLING['overage_before_min']:,} to ~{BILLING['overage_after_min']:,} min " + f"(${BILLING['overage_saved_monthly_usd']:,}/month saved).", + style={"color": GRAY, "fontSize": "13px", "lineHeight": "1.5", "margin": "0 0 12px"}, + ), + dcc.Graph(figure=make_billing_chart(), config={"displayModeBar": False}), + ]), + ], + ), + + section("Before vs After"), + card([dcc.Graph(figure=make_before_after_chart(), config={"displayModeBar": False})]), + + section( + "Operational Improvements", + "The audit transformed CI from a maintenance burden to a self-service system.", + ), + + # Before/after comparison table + card([ + html.Div( + style={"display": "grid", "gridTemplateColumns": "1fr auto auto", "gap": "0"}, + children=[ + # Header + html.Div("", style={"padding": "12px 16px"}), + html.Div("Before", style={"padding": "12px 16px", "fontWeight": "700", "color": RED, "fontSize": "13px", "textAlign": "right", "width": "200px"}), + html.Div("After", style={"padding": "12px 16px", "fontWeight": "700", "color": GREEN, "fontSize": "13px", "textAlign": "right", "width": "200px"}), + # Rows + *_comparison_row("Workflow files in repo", "22", "7"), + *_comparison_row("Required checks in branch protection", "13 individual", "1 gate job"), + *_comparison_row("Workflow-only PR merge", "Admin override", "Self-service"), + *_comparison_row("Non-code PR compute cost", "$1.85", "$0.001"), + *_comparison_row("Fork failing runs/month", "~960", "0"), + *_comparison_row("Ghost workflows in Actions UI", "13", "0"), + *_comparison_row("Branch protection model", "Legacy rules", "Repository rulesets"), + *_comparison_row("Dependabot test fixture noise", "70% failure rate", "Excluded"), + ], + ), + ]), + ], + ) + + +def _comparison_row(label, before, after): + border = f"1px solid {CARD_BORDER}" + return [ + html.Div(label, style={"padding": "12px 16px", "color": SLATE, "fontSize": "14px", "fontWeight": "600", "borderTop": border}), + html.Div(before, style={"padding": "12px 16px", "color": LIGHT_GRAY, "fontSize": "14px", "fontFamily": MONO, "textAlign": "right", "borderTop": border, "width": "200px"}), + html.Div(after, style={"padding": "12px 16px", "color": GREEN, "fontSize": "14px", "fontFamily": MONO, "fontWeight": "600", "textAlign": "right", "borderTop": border, "width": "200px"}), + ] + + +# ── Tab: Full Detail ───────────────────────────────────────────────────────── + + +def _build_detail_tab(): + pr_rows = [] + for p in PRS_MERGED: + base = REPO_BASES[p["repo"]] + pr_rows.append({ + "PR": f"[#{p['pr']}]({base}/{p['pr']})", + "Repo": p["repo"], + "Date": p["date"], + "Description": p["title"], + }) + + action_rows = [ + {"Action": a["action"], "Date": a["date"], "Repo": a["repo"]} + for a in DIRECT_ACTIONS + ] + + fork_rows = [ + {"Repo": f["repo"], "Runs": f"{f['runs']:,}", "Cost/yr": f"${f['cost_yr']}", "Runners": f["runners"], "Pattern": f["pattern"]} + for f in FORK_CI + ] + + finding_rows = [] + for repo, items in FINDINGS.items(): + for f in items: + pr_link = f"[#{f['pr']}]({REPO_BASES[repo]}/{f['pr']})" if f["pr"] else "Direct action" + finding_rows.append({ + "Repo": repo, + "Finding": f["finding"], + "Impact": f["impact"], + "Fix": f["fix"], + "PR": pr_link, + }) + + return html.Div( + id="detail-view", + style={"display": "none"}, + children=[ + section("Fork CI Activity", "26 of 200+ forks had active GitHub Actions. Actions disabled on all forks 2026-04-23."), + + # Fork charts side by side + html.Div( + style={"display": "grid", "gridTemplateColumns": "1fr 1fr", "gap": "16px"}, + children=[ + card([ + html.Div("Runs by Repository", style={"fontWeight": "700", "color": SLATE, "fontSize": "15px", "marginBottom": "12px"}), + dcc.Graph(figure=make_fork_chart(), config={"displayModeBar": False}), + ]), + card([ + html.Div("Cost Breakdown", style={"fontWeight": "700", "color": SLATE, "fontSize": "15px", "marginBottom": "12px"}), + html.P( + "kornia is 91% of fork CI cost: daily scheduled test matrix across macOS ($0.08/min), Windows ($0.016/min), and Linux.", + style={"color": GRAY, "fontSize": "13px", "lineHeight": "1.5", "margin": "0 0 12px"}, + ), + dcc.Graph(figure=make_fork_cost_chart(), config={"displayModeBar": False}), + ]), + ], + ), + + # Fork table + html.Div(style=TABLE_WRAP, children=[ + dash_table.DataTable( + data=fork_rows, + columns=[ + {"name": "Repo", "id": "Repo"}, + {"name": "Runs", "id": "Runs"}, + {"name": "Cost/yr", "id": "Cost/yr"}, + {"name": "Runners", "id": "Runners"}, + {"name": "Pattern", "id": "Pattern"}, + ], + style_header=TABLE_HEADER, + style_cell=TABLE_CELL, + style_data=TABLE_DATA, + style_data_conditional=TABLE_DATA_CONDITIONAL, + style_as_list_view=True, + page_size=20, + ), + ]), + + section("All Findings", "Categorized by repository."), + + html.Div(style=TABLE_WRAP, children=[ + dash_table.DataTable( + data=finding_rows, + columns=[ + {"name": "Repo", "id": "Repo"}, + {"name": "Finding", "id": "Finding"}, + {"name": "Impact", "id": "Impact"}, + {"name": "Fix", "id": "Fix"}, + {"name": "PR", "id": "PR", "presentation": "markdown"}, + ], + style_header=TABLE_HEADER, + style_cell={**TABLE_CELL, "whiteSpace": "normal", "height": "auto"}, + style_data=TABLE_DATA, + style_data_conditional=TABLE_DATA_CONDITIONAL, + style_as_list_view=True, + page_size=20, + css=[{"selector": "p", "rule": "margin: 0"}], + ), + ]), + + section("PRs Merged", f"{len(PRS_MERGED)} pull requests across 2 repositories."), + + html.Div(style=TABLE_WRAP, children=[ + dash_table.DataTable( + data=pr_rows, + columns=[ + {"name": "PR", "id": "PR", "presentation": "markdown"}, + {"name": "Repo", "id": "Repo"}, + {"name": "Date", "id": "Date"}, + {"name": "Description", "id": "Description"}, + ], + style_header=TABLE_HEADER, + style_cell={**TABLE_CELL, "whiteSpace": "normal", "height": "auto"}, + style_data=TABLE_DATA, + style_data_conditional=TABLE_DATA_CONDITIONAL, + style_as_list_view=True, + css=[{"selector": "p", "rule": "margin: 0"}], + ), + ]), + + section("Direct Actions", "Non-PR changes applied during the audit."), + + html.Div(style=TABLE_WRAP, children=[ + dash_table.DataTable( + data=action_rows, + columns=[ + {"name": "Action", "id": "Action"}, + {"name": "Date", "id": "Date"}, + {"name": "Repo", "id": "Repo"}, + ], + style_header=TABLE_HEADER, + style_cell={**TABLE_CELL, "whiteSpace": "normal", "height": "auto"}, + style_data=TABLE_DATA, + style_data_conditional=TABLE_DATA_CONDITIONAL, + style_as_list_view=True, + css=[{"selector": "p", "rule": "margin: 0"}], + ), + ]), + + section("Methodology"), + card([ + html.Ol( + [ + html.Li([html.Strong("Inventory"), " — ", html.Span("gh repo list codeflash-ai", style={"fontFamily": MONO, "fontSize": "13px"}), " to enumerate all 200+ repos, classify as fork vs primary"], style=_li_style()), + html.Li([html.Strong("Fork scan"), " — Query Actions run counts per fork since Apr 2025, identify 26 active forks"], style=_li_style()), + html.Li([html.Strong("Compute cost"), " — Sample job-level data (duration, runner type), calculate at GitHub rates: $0.008/min Linux, $0.016/min Windows, $0.08/min macOS"], style=_li_style()), + html.Li([html.Strong("Main repo audit"), " — List all workflows, check run history, failure rates, ghost detection, trigger configuration"], style=_li_style()), + html.Li([html.Strong("Root cause analysis"), " — Compare working vs broken runs by commit SHA and timestamp to pinpoint regressions"], style=_li_style()), + ], + style={"paddingLeft": "20px", "margin": "0"}, + ), + ]), + + section("Monitoring"), + card([ + html.Ul( + [ + html.Li([html.Strong("claude-code-action"), " — unpin from v1.0.89 once anthropics/claude-code-action#1196 lands upstream"], style=_li_style()), + html.Li([html.Strong("Dependabot alerts"), " — 24 known vulnerabilities at audit time; new dependabot.yml targets real deps only"], style=_li_style()), + html.Li([html.Strong("Fork re-enable"), " — if a fork is needed: ", html.Code("echo '{\"enabled\":true}' | gh api --method PUT repos/codeflash-ai//actions/permissions --input -", style={"fontFamily": MONO, "fontSize": "12px", "color": ACCENT})], style=_li_style()), + ], + style={"paddingLeft": "20px", "margin": "0", "listStyleType": "'\\2022 '"}, + ), + ]), + ], + ) + + +def _li_style(): + return {"color": GRAY, "fontSize": "14px", "lineHeight": "1.7", "marginBottom": "8px"} + + +# ── Main layout ────────────────────────────────────────────────────────────── + + +_TAB_BTN_STYLE = { + "padding": "10px 24px", + "border": "none", + "borderRadius": "8px", + "cursor": "pointer", + "fontSize": "14px", + "fontWeight": "600", + "fontFamily": FONT, + "background": "transparent", + "color": GRAY, + "transition": "all 0.2s", +} + +_TAB_BTN_ACTIVE = {**_TAB_BTN_STYLE, "background": ACCENT, "color": DARK} + + +def _main_layout(): + return html.Div( + style={ + "fontFamily": FONT, + "background": BG, + "color": SLATE, + "minHeight": "100vh", + "position": "relative", + }, + children=[ + html.Div(style=GRID_OVERLAY), + html.Div( + style={ + "maxWidth": "1100px", + "margin": "0 auto", + "padding": "48px 32px 80px", + "position": "relative", + "zIndex": "1", + }, + children=[ + # Header + html.Div( + style={"textAlign": "center", "marginBottom": "8px"}, + children=[ + _logo_lockup(), + html.H1( + "CI/CD Audit Report", + style={ + "fontSize": "36px", + "fontWeight": "800", + "color": WHITE, + "margin": "16px 0 8px", + "letterSpacing": "-0.02em", + }, + ), + html.P( + "codeflash-ai org — 200+ forks, 2 main repos", + style={ + "fontSize": "16px", + "color": GRAY, + "margin": "0 0 4px", + }, + ), + html.P( + "April 9–23, 2026", + style={ + "fontSize": "14px", + "color": LIGHT_GRAY, + "margin": "0", + "fontFamily": MONO, + }, + ), + ], + ), + + # Tab buttons + html.Div( + style={"display": "flex", "justifyContent": "center", "margin": "40px 0 8px"}, + children=[ + html.Div( + style={ + "display": "inline-flex", + "background": CARD_BG, + "borderRadius": "12px", + "padding": "4px", + "border": f"1px solid {CARD_BORDER}", + }, + children=[ + html.Button("Executive Summary", id="btn-summary", n_clicks=1, style=_TAB_BTN_ACTIVE), + html.Button("Full Detail", id="btn-detail", n_clicks=0, style=_TAB_BTN_STYLE), + ], + ), + ], + ), + + _build_summary_tab(), + _build_detail_tab(), + + # Footer + html.Div( + style={ + "textAlign": "center", + "marginTop": "64px", + "paddingTop": "24px", + "borderTop": f"1px solid {CARD_BORDER}", + }, + children=[ + html.Div( + _logo_lockup("16px", "20px", "10px", "3px"), + style={"display": "flex", "justifyContent": "center", "marginBottom": "4px"}, + ), + html.P( + "CI/CD Audit Report — April 2026", + style={"color": LIGHT_GRAY, "fontSize": "13px", "margin": "0"}, + ), + ], + ), + ], + ), + ], + ) + + +# ── App ────────────────────────────────────────────────────────────────────── + +app = Dash( + __name__, + meta_tags=[ + {"name": "viewport", "content": "width=device-width, initial-scale=1"}, + {"property": "og:title", "content": "Codeflash — CI/CD Audit Report"}, + { + "property": "og:description", + "content": "CI audit of codeflash-ai org: 200+ forks disabled, 11 PRs merged, 960 failing runs/month eliminated", + }, + ], + suppress_callback_exceptions=True, +) +app.title = "Codeflash — CI/CD Audit Report" + +app.index_string = """ + + + {%metas%} + {%title%} + + + + {%favicon%} + {%css%} + + + + {%app_entry%} + + +""" + +app.layout = _main_layout + +# ── Toggle callback ── +clientside_callback( + """ + function(summary_c, detail_c) { + summary_c = summary_c || 0; + detail_c = detail_c || 0; + var base = { + "padding": "10px 24px", "border": "none", "borderRadius": "8px", + "cursor": "pointer", "fontSize": "14px", "fontWeight": "600", + "fontFamily": "'Inter', system-ui, -apple-system, sans-serif", + "transition": "all 0.2s" + }; + var active = Object.assign({}, base, {"background": "#ffd227", "color": "#09090b"}); + var inactive = Object.assign({}, base, {"background": "transparent", "color": "#a1a1aa"}); + var show = {"display": "block"}; + var hide = {"display": "none"}; + var mx = Math.max(summary_c, detail_c); + if (detail_c === mx && detail_c > 0) + return [hide, show, inactive, active]; + return [show, hide, active, inactive]; + } + """, + Output("summary-view", "style"), + Output("detail-view", "style"), + Output("btn-summary", "style"), + Output("btn-detail", "style"), + Input("btn-summary", "n_clicks"), + Input("btn-detail", "n_clicks"), +) + +server = app.server + +if __name__ == "__main__": + app.run( + debug=os.getenv("DASH_DEBUG", "1") == "1", + port=int(os.getenv("PORT", "8051")), + ) diff --git a/reports/codeflash-ci-audit/data.json b/reports/codeflash-ci-audit/data.json new file mode 100644 index 0000000..a421df5 --- /dev/null +++ b/reports/codeflash-ci-audit/data.json @@ -0,0 +1,96 @@ +{ + "codeflash_base": "https://github.com/codeflash-ai/codeflash/pull", + "internal_base": "https://github.com/codeflash-ai/codeflash-internal/pull", + "audit_date": "2026-04-09", + "completion_date": "2026-04-23", + "org": "codeflash-ai", + "total_forks": 200, + "active_forks": 26, + "fork_runs_audit_period": 7012, + "fork_runs_monthly": 23373, + "fork_failing_monthly": 960, + "fork_cost_yr": 248, + "ghost_workflows_disabled": 13, + "workflows_before": 22, + "workflows_after": 7, + "required_checks_before": 13, + "required_checks_after": 1, + "skip_savings_per_pr": 1.85, + "fork_ci": [ + {"repo": "sglang", "runs": 4936, "cost_yr": 4, "runners": "Linux", "pattern": "CI Failure Monitor (cron, skips on fork)"}, + {"repo": "kornia", "runs": 308, "cost_yr": 226, "runners": "Linux + Windows + macOS", "pattern": "Daily scheduled CPU test matrix"}, + {"repo": "ray", "runs": 527, "cost_yr": 3, "runners": "Linux", "pattern": "Dependabot + Stale PR bot"}, + {"repo": "lerobot", "runs": 330, "cost_yr": 12, "runners": "Linux", "pattern": "Dependabot + Full Tests"}, + {"repo": "next.js", "runs": 217, "cost_yr": 3, "runners": "Linux", "pattern": "Dependabot only"}, + {"repo": "strapi", "runs": 118, "cost_yr": 1, "runners": "Linux", "pattern": "Dependabot (lodash x 20+ packages)"}, + {"repo": "rasa", "runs": 85, "cost_yr": 1, "runners": "Linux", "pattern": "Dependabot"}, + {"repo": "Rocket.Chat", "runs": 80, "cost_yr": 1, "runners": "Linux", "pattern": "Dependabot"}, + {"repo": "nest", "runs": 73, "cost_yr": 1, "runners": "Linux", "pattern": "Dependabot (@nestjs/core x 20+ samples)"}, + {"repo": "openclaw", "runs": 68, "cost_yr": 1, "runners": "Linux", "pattern": "Dependabot"}, + {"repo": "appsmith", "runs": 64, "cost_yr": 1, "runners": "Linux", "pattern": "Dependabot"}, + {"repo": "15 others", "runs": 206, "cost_yr": 1, "runners": "Linux", "pattern": "Dependabot / misc"} + ], + "findings": { + "codeflash": [ + {"finding": "Wildcard path triggers on 12 E2E workflows", "impact": "Every PR triggered ~2hrs of E2E tests", "fix": "Targeted path filters", "pr": 2025}, + {"finding": "Broken claude-code-action (v1.0.90 Bedrock OIDC)", "impact": "60-100% failure rate on Claude Code workflows", "fix": "Pinned to v1.0.89", "pr": 2026}, + {"finding": "Dependabot scanning test fixtures", "impact": "70% of Dependabot runs failing", "fix": "Added dependabot.yml excluding code_to_optimize/", "pr": 2027}, + {"finding": "13 ghost workflows (deleted source files)", "impact": "Actions dashboard clutter, confusing signals", "fix": "gh workflow disable on all 13", "pr": null}, + {"finding": "17 separate workflow files, 13 required checks", "impact": "Workflow-only PRs stuck at Pending, admin merge required", "fix": "Single ci.yaml with gate job", "pr": 2044}, + {"finding": "No npm/Maven caching, duplicate workflows", "impact": "Slow JS/Java CI, redundant compute", "fix": "Consolidated + caching", "pr": 2050}, + {"finding": "Outdated action versions, broken Windows paths", "impact": "CI failures on Windows matrix", "fix": "Upgraded actions + fixed paths", "pr": 2052}, + {"finding": "Stale codeflash.yaml", "impact": "Superseded by codeflash-optimize.yaml", "fix": "Deleted", "pr": 2056} + ], + "codeflash-internal": [ + {"finding": "Deploy AI Service path included .github/workflows/**", "impact": "Any workflow edit triggered production deploy", "fix": "Scoped to actual service paths", "pr": 2588}, + {"finding": "Claude Code workflow had no path filters", "impact": "Fired on every PR/comment", "fix": "Added paths-ignore", "pr": 2588}, + {"finding": "Publish to PyPI permanently disabled (if: false)", "impact": "Skipped run noise on every push to main", "fix": "Disabled via API", "pr": 2588}, + {"finding": "Broken claude-code-action (same v1.0.90 regression)", "impact": "85% failure rate", "fix": "Pinned to v1.0.89", "pr": 2587} + ] + }, + "prs_merged": [ + {"pr": 2025, "repo": "codeflash", "date": "2026-04-09", "title": "Replace wildcard path triggers on 12 E2E workflows"}, + {"pr": 2026, "repo": "codeflash", "date": "2026-04-09", "title": "Pin claude-code-action to v1.0.89 (Bedrock auth fix)"}, + {"pr": 2027, "repo": "codeflash", "date": "2026-04-09", "title": "Exclude test fixtures from Dependabot"}, + {"pr": 2044, "repo": "codeflash", "date": "2026-04-09", "title": "Consolidate 17 workflows into single ci.yaml with gate job"}, + {"pr": 2047, "repo": "codeflash", "date": "2026-04-09", "title": "Path filters, validate-pr action, fetch-depth, continue-on-error"}, + {"pr": 2050, "repo": "codeflash", "date": "2026-04-09", "title": "npm cache, Maven consolidation, remove duplicate workflow"}, + {"pr": 2052, "repo": "codeflash", "date": "2026-04-10", "title": "Upgrade action versions, add uv cache, fix broken paths, DRY publish"}, + {"pr": 2053, "repo": "codeflash", "date": "2026-04-10", "title": "Fix shell: bash for Windows conditional install"}, + {"pr": 2056, "repo": "codeflash", "date": "2026-04-10", "title": "Delete disabled codeflash.yaml workflow"}, + {"pr": 2587, "repo": "codeflash-internal", "date": "2026-04-09", "title": "Pin claude-code-action to v1.0.89"}, + {"pr": 2588, "repo": "codeflash-internal", "date": "2026-04-09", "title": "Fix deploy path filter, Claude Code paths, disable PyPI"} + ], + "direct_actions": [ + {"action": "Disabled 13 ghost workflows", "date": "2026-04-09", "repo": "codeflash"}, + {"action": "Disabled Publish to PyPI workflow", "date": "2026-04-09", "repo": "codeflash-internal"}, + {"action": "Closed stale Dependabot PR #2012", "date": "2026-04-09", "repo": "codeflash"}, + {"action": "Migrated branch protection to rulesets", "date": "2026-04-09", "repo": "codeflash"}, + {"action": "Disabled Actions on 200+ forks", "date": "2026-04-23", "repo": "org-wide"} + ], + "operational_before_after": { + "workflow_files": [22, 7], + "required_checks": [13, 1], + "non_code_pr_cost": [1.85, 0.001], + "fork_failing_runs_monthly": [960, 0], + "admin_merges_needed": true + }, + "run_volume": { + "codeflash_feb": 21307, + "codeflash_apr_projected": 6219, + "codeflash_reduction_pct": 71, + "internal_feb": 2494, + "internal_apr_projected": 3864, + "total_eliminated_monthly": 16000 + }, + "billing": { + "enterprise_included_min": 50000, + "overage_before_min": 214872, + "overage_after_min": 89613, + "overage_saved_min": 125259, + "overage_saved_monthly_usd": 1002, + "overage_saved_annual_usd": 12025, + "non_code_pr_before_sec": 780, + "non_code_pr_after_sec": 8 + } +} diff --git a/reports/codeflash-ci-audit/theme.py b/reports/codeflash-ci-audit/theme.py new file mode 100644 index 0000000..231f836 --- /dev/null +++ b/reports/codeflash-ci-audit/theme.py @@ -0,0 +1,80 @@ +"""Theme and styling constants for the Codeflash CI Audit report.""" + +# ── Colors (Codeflash dark - amber/zinc) ──────────────────────────────────── +ACCENT = "#ffd227" +DARK = "#09090b" +CARD_BG = "rgba(16,20,28,0.7)" # dark navy/70 — readable over animated bg +CARD_BORDER = "rgba(63,63,70,0.35)" # zinc-700/35 +SLATE = "#e4e4e7" +GRAY = "#a1a1aa" +LIGHT_GRAY = "#71717a" +BG = "#0d1117" # dark navy (codeflash.ai style) +WHITE = "#fafafa" +GREEN = "#4ade80" +LIGHT_GREEN = "rgba(74,222,128,0.12)" +RED = "#f87171" +LIGHT_RED = "rgba(248,113,113,0.12)" +AMBER = "#fbbf24" +BLUE = "#60a5fa" +PURPLE = "#a78bfa" +PINK = "#f472b6" + +# ── Grid overlay ──────────────────────────────────────────────────────────── +# Matches the roadmap page's subtle grid pattern. +GRID_BG_IMAGE = ( + "linear-gradient(to right, currentColor 1px, transparent 1px)," + "linear-gradient(to bottom, currentColor 1px, transparent 1px)" +) +GRID_BG_SIZE = "48px 48px" +GRID_OVERLAY = { + "position": "fixed", + "top": 0, + "left": 0, + "right": 0, + "bottom": 0, + "backgroundImage": GRID_BG_IMAGE, + "backgroundSize": GRID_BG_SIZE, + "opacity": "0.05", + "pointerEvents": "none", + "zIndex": "0", +} + +# ── Component styles ──────────────────────────────────────────────────────── +CARD = { + "background": CARD_BG, + "borderRadius": "16px", + "padding": "28px 32px", + "border": f"1px solid {CARD_BORDER}", +} +FONT = "'Inter', system-ui, -apple-system, sans-serif" +MONO = "'JetBrains Mono', 'Menlo', monospace" + +# ── Table styles ──────────────────────────────────────────────────────────── +TABLE_HEADER: dict[str, str] = { + "backgroundColor": "rgba(24,24,27,0.8)", + "color": ACCENT, + "fontWeight": "600", + "fontSize": "13px", + "padding": "12px 16px", + "borderBottom": f"1px solid {CARD_BORDER}", +} +TABLE_CELL: dict[str, str] = { + "textAlign": "left", + "padding": "12px 16px", + "fontSize": "13px", + "fontFamily": FONT, + "border": "none", + "color": SLATE, +} +TABLE_DATA: dict[str, str] = { + "backgroundColor": "rgba(24,24,27,0.5)", + "color": SLATE, +} +TABLE_DATA_CONDITIONAL: list[dict[str, object]] = [ + {"if": {"row_index": "odd"}, "backgroundColor": "rgba(31,31,35,0.6)"} +] +TABLE_WRAP: dict[str, str] = { + "borderRadius": "16px", + "overflow": "hidden", + "border": f"1px solid {CARD_BORDER}", +}