Repository navigation
chore: rolling promotion dev -> main #899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b764107
9698424
430f96c
d854f50
ecb38a9
046174a
76cf9c2
379b25e
364c756
6200b68
f481755
81d27e3
b7e6c1a
c353b53
9621456
6a4d413
bcefdb9
d89bf6b
c57e399
79286c7
d27d773
45053fa
69df49b
94c6c81
95edf78
04bd7a2
a3c8ab4
79420b3
b3cdadf
6022fff
67b24ae
07ce8c0
fe4b560
3b2cad4
746ffdc
66ba0e6
aabeb39
7c56e45
0227957
7f7cf09
9b4e579
85bc6f3
71b180b
bf2a52f
cb2747b
178d167
81c6016
15fb972
a650570
a67724f
3a3c96b
ad679d8
d0ff218
d22493f
c83843f
eb2c522
8c97c99
4604f1b
d168147
8a537dd
1daadf6
a4b774d
7095edb
60f2cd2
60f0eff
f04db76
1b867b3
3956a0b
0edf0e1
f1919ee
9bf2f05
3573f50
f4675cf
ab28fce
d7a04c6
2fb72ee
bd19d3f
a0f5cac
e9cd0c0
8d76b05
2587cd6
dc60dfe
382a7e4
8b411ff
9dd931a
a592173
fdbed67
1c18bf6
0353644
2523ad7
57dc4a7
1f0b4f2
11d360b
27c86f0
002d1d4
e1f16eb
b4ed145
0dafcab
ccc701d
cba646b
7425ef8
3d1704d
5838dc9
e87365a
b9e2539
ad4093d
1c95ccb
9d5c503
d9e7ce9
1e65f6a
5252baa
06b7df0
f30334d
a2fce50
0bcb971
9134d24
c7455d4
f624fa3
6d6de2d
7559c95
252beac
a1c6c6a
0ee4810
6e83ee9
b623cf0
1de13f2
7fc1c4d
029a1d1
46ca5ea
7b57db0
6c6af9d
b830de1
1efee3a
01fd156
7463fed
59ff1e4
b2d1bbb
5202e1c
2e1900e
d3ecfee
8c34673
056ed43
a4c29fc
9f5e4c0
c0836ef
0204a2a
4b7de1c
3dca39a
fbe8984
6657589
6d20d2e
843b951
213f858
3f4ecc7
798811d
e90e3ce
3ad88c6
380c799
9492b0d
07a84be
36d5ed7
addba87
73f8429
09b5431
1480a54
d3ad3d2
3bfbd72
5bb25fa
6def368
f861da2
aef5c4a
04026bd
e979ec6
4a558d1
f57d0bc
fe39d1e
7c1acc6
b489790
6d90b36
badef10
9f7add8
7d7970f
15b67f9
6ee7bc6
2a82f2e
6485307
6e9f163
212f1e4
13a1bb3
be29b87
9789098
85e1cad
c760d8c
d82f3fd
3e11c04
3fd9bed
3551654
d93d400
dc5ff22
271ea53
0624792
fe3d021
45222d9
1d350ec
fcc4fca
21fba62
5fe5c66
388e9ca
d52a6d5
bdf2e88
53ac527
8291544
e1d15f4
5c82725
9caf4e4
389a942
44a153a
c6d0b3a
add681a
8279c2e
be2b48b
8e7c4b8
0270cf7
047a458
99b96a0
2d6845e
604b26f
69e1cee
381c1c3
14f617b
11dff85
ed92568
bfea41e
83fafb9
4f7b357
9945108
419e4b1
22aa551
1007c0d
28bcc46
0207566
139dcaf
88431fa
492be5d
a212db4
7c21301
795636a
b597c87
6f18de8
49d5027
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # metrics-updater Agent | ||
|
|
||
| Fetches live GitHub metrics daily and updates the README metrics table. | ||
|
|
||
| ## Metrics Collected | ||
|
|
||
| - **Releases/day** — releases published in the last 24h | ||
| - **Avg merge time** — mean lead time (created→merged) for PRs closed in last 7d | ||
| - **SHIP rate** — merged / total closed PRs in last 7d (%) | ||
| - **Merged PRs (7d)** — count of merged PRs in the rolling 7-day window | ||
|
|
||
| ## README Markers | ||
|
|
||
| The agent updates the block between `<!-- METRICS:START -->` and `<!-- METRICS:END -->` in README.md. | ||
|
|
||
| If markers are missing, insert the table after the badges block, before `## What is Genie?`. | ||
|
|
||
| ## Commit Convention | ||
|
|
||
| ``` | ||
| chore: update live metrics (X/day, Yh avg, Z% SHIP) | ||
| ``` | ||
|
|
||
| Push to `dev` branch only. Never push to `main`. | ||
|
|
||
| ## State Files | ||
|
|
||
| - `state.json` — last successful metrics run | ||
| - `runs.jsonl` — append-only log of every run |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| {"timestamp":"2026-03-24T22:44:08Z","duration_ms":4685,"api_calls":2,"tools_generated":0,"tools_available":5,"errors":[],"status":"success","fallback":false,"slowest_step":"unknown","steps":[],"metrics":{"releases_per_day":0,"avg_bugfix_time_hours":2.0,"ship_rate_pct":100.0,"parallel_agents":3,"updated":"2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:54:18Z", "duration_ms": 4579, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "success", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 3023}, {"name": "fetch_prs", "duration_ms": 1218}, {"name": "count_agents", "duration_ms": 65}, {"name": "parse_metrics", "duration_ms": 126}, {"name": "update_readme", "duration_ms": 51}, {"name": "update_state", "duration_ms": 35}, {"name": "commit", "duration_ms": 4}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:05Z", "duration_ms": 4292, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "success", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 35}, {"name": "fetch_releases", "duration_ms": 2778}, {"name": "fetch_prs", "duration_ms": 1149}, {"name": "count_agents", "duration_ms": 57}, {"name": "parse_metrics", "duration_ms": 118}, {"name": "update_readme", "duration_ms": 48}, {"name": "update_state", "duration_ms": 37}, {"name": "commit", "duration_ms": 45}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:11Z", "duration_ms": 3833, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 2640}, {"name": "fetch_prs", "duration_ms": 872}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 124}, {"name": "update_readme", "duration_ms": 47}, {"name": "update_state", "duration_ms": 39}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:17Z", "duration_ms": 4637, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 3344}, {"name": "fetch_prs", "duration_ms": 980}, {"name": "count_agents", "duration_ms": 54}, {"name": "parse_metrics", "duration_ms": 122}, {"name": "update_readme", "duration_ms": 45}, {"name": "update_state", "duration_ms": 33}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:24Z", "duration_ms": 4067, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 37}, {"name": "fetch_releases", "duration_ms": 2732}, {"name": "fetch_prs", "duration_ms": 1008}, {"name": "count_agents", "duration_ms": 55}, {"name": "parse_metrics", "duration_ms": 123}, {"name": "update_readme", "duration_ms": 45}, {"name": "update_state", "duration_ms": 38}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:30Z", "duration_ms": 4728, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 31}, {"name": "fetch_releases", "duration_ms": 3211}, {"name": "fetch_prs", "duration_ms": 1186}, {"name": "count_agents", "duration_ms": 59}, {"name": "parse_metrics", "duration_ms": 126}, {"name": "update_readme", "duration_ms": 52}, {"name": "update_state", "duration_ms": 34}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:37Z", "duration_ms": 4374, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 34}, {"name": "fetch_releases", "duration_ms": 2985}, {"name": "fetch_prs", "duration_ms": 1063}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 131}, {"name": "update_readme", "duration_ms": 47}, {"name": "update_state", "duration_ms": 34}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:43Z", "duration_ms": 4201, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 32}, {"name": "fetch_releases", "duration_ms": 2704}, {"name": "fetch_prs", "duration_ms": 1172}, {"name": "count_agents", "duration_ms": 65}, {"name": "parse_metrics", "duration_ms": 122}, {"name": "update_readme", "duration_ms": 43}, {"name": "update_state", "duration_ms": 35}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-25T00:03:05Z", "duration_ms": 5882, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "success", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 33}, {"name": "fetch_releases", "duration_ms": 3458}, {"name": "fetch_prs", "duration_ms": 1046}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 1191}, {"name": "update_readme", "duration_ms": 45}, {"name": "update_state", "duration_ms": 30}, {"name": "commit", "duration_ms": 4}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.7, "ship_rate_pct": 100.0, "loc_changed_24h": 7418, "commits_24h": 66, "prs_24h": 28, "parallel_agents": 3, "updated": "2026-03-25T00:03:04Z"}} | ||
| {"timestamp":"2026-03-25T00:00:00Z","duration_ms":31579683120,"api_calls":3,"tools_generated":1,"errors":0,"metrics":{"releases_24h":0,"avg_merge_time_hours":0.7,"ship_rate_pct":96,"merged_prs_7d":48}} | ||
| {"timestamp":"2026-03-28T00:00:00Z","duration_ms":45000,"api_calls":3,"tools_generated":0,"errors":[],"metrics":{"releases_24h":0,"avg_merge_time_hours":2.9,"ship_rate_pct":94,"merged_prs_7d":47}} | ||
| {"timestamp":"2026-03-29T00:00:00Z","duration_ms":52000,"api_calls":2,"tools_generated":0,"errors":[],"metrics":{"releases_24h":0,"avg_merge_time_hours":3.0,"ship_rate_pct":92,"merged_prs_7d":46}} | ||
| {"timestamp":"2026-03-30T00:00:00Z","duration_ms":5000,"api_calls":3,"tools_generated":0,"errors":0,"metrics":{"releases_24h":0,"releases_per_day":0,"avg_merge_time_hours":0.3,"ship_rate_pct":84,"merged_prs_7d":32,"closed_prs_7d":38}} | ||
| {"timestamp":"2026-03-31T00:00:00Z","duration_ms":5002,"api_calls":2,"tools_generated":4,"errors":0,"metrics":{"releases_24h":0,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}} | ||
| {"timestamp":"2026-04-01T00:00:00Z","duration_ms":8000,"api_calls":0,"tools_generated":0,"errors":["gh CLI unavailable","GitHub MCP tools not loaded in session"],"status":"failed","fallback":false,"metrics":null} | ||
| {"timestamp":"2026-04-02T00:00:00.000Z","duration_ms":15000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":false,"metrics":null} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "last_run": "2026-04-02T00:00:00.000Z", | ||
| "last_run_status": "failed", | ||
| "last_metrics": { | ||
| "releases_24h": 0, | ||
| "merged_prs_7d": 35, | ||
| "avg_merge_time_h": 0.3, | ||
| "ship_rate_pct": 85 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,38 @@ | ||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||
| # batch-commit.sh — Optimized commit for metrics updates | ||||||||||||||
| # Auto-generated by generate-tools.py | ||||||||||||||
| # Idempotent: safe to run multiple times, regenerate freely. | ||||||||||||||
| # | ||||||||||||||
| # Usage: bash batch-commit.sh <metrics.json> <repo-root> <state-file> | ||||||||||||||
| # | ||||||||||||||
| # Optimizations: | ||||||||||||||
| # - Single git add for all files | ||||||||||||||
| # - Pre-formatted commit message (no subshell) | ||||||||||||||
| # - Skip if working tree is clean | ||||||||||||||
|
|
||||||||||||||
| set -euo pipefail | ||||||||||||||
|
|
||||||||||||||
| METRICS_FILE="${1:?Usage: batch-commit.sh <metrics.json> <repo-root> <state-file>}" | ||||||||||||||
| REPO_ROOT="${2:?}" | ||||||||||||||
| STATE_FILE="${3:?}" | ||||||||||||||
|
|
||||||||||||||
| cd "$REPO_ROOT" | ||||||||||||||
|
|
||||||||||||||
| # Skip if no changes | ||||||||||||||
| if git diff --quiet HEAD -- README.md "$STATE_FILE" 2>/dev/null; then | ||||||||||||||
| echo "No changes to commit" >&2 | ||||||||||||||
| exit 2 | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # Read metrics for commit message | ||||||||||||||
| releases=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('releases_per_day', 0))") | ||||||||||||||
| avg_time=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('avg_bugfix_time_hours', 0))") | ||||||||||||||
| ship_rate=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('ship_rate_pct', 0))") | ||||||||||||||
|
Comment on lines
+28
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This script invokes
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| COMMIT_MSG="chore: update live metrics (${releases}/day, ${avg_time}h avg, ${ship_rate}% SHIP)" | ||||||||||||||
|
|
||||||||||||||
| # Single add + commit | ||||||||||||||
| git add README.md "$STATE_FILE" | ||||||||||||||
| git commit -m "$COMMIT_MSG" | ||||||||||||||
|
|
||||||||||||||
| echo "$COMMIT_MSG" | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| #!/usr/bin/env bash | ||
| # cached-fetch.sh — Cached GitHub API fetcher with TTL | ||
| # Auto-generated by generate-tools.py | ||
| # Idempotent: safe to run multiple times, regenerate freely. | ||
| # | ||
| # Usage: source cached-fetch.sh; cached_gh_api <endpoint> [ttl_seconds] | ||
| # Default TTL: 1800s (30 minutes) — daily agent runs don't need real-time data | ||
|
|
||
| CACHE_DIR="${GENIE_METRICS_CACHE:-/tmp/metrics-updater-cache}" | ||
| DEFAULT_TTL="${GENIE_METRICS_CACHE_TTL:-1800}" | ||
|
|
||
| mkdir -p "$CACHE_DIR" | ||
|
|
||
| cached_gh_api() { | ||
| local endpoint="$1" | ||
| local ttl="${2:-$DEFAULT_TTL}" | ||
| local cache_key | ||
| cache_key=$(echo "$endpoint" | md5sum | cut -d' ' -f1) | ||
| local cache_file="$CACHE_DIR/${cache_key}.json" | ||
| local meta_file="$CACHE_DIR/${cache_key}.meta" | ||
|
|
||
| # Check cache validity | ||
| if [[ -f "$cache_file" && -f "$meta_file" ]]; then | ||
| local cached_at | ||
| cached_at=$(cat "$meta_file" 2>/dev/null || echo 0) | ||
| local now | ||
| now=$(date +%s) | ||
| local age=$((now - cached_at)) | ||
|
|
||
| if (( age < ttl )); then | ||
| echo "[cache] HIT: $endpoint (age=${age}s, ttl=${ttl}s)" >&2 | ||
| cat "$cache_file" | ||
| return 0 | ||
| fi | ||
| echo "[cache] STALE: $endpoint (age=${age}s, ttl=${ttl}s)" >&2 | ||
| else | ||
| echo "[cache] MISS: $endpoint" >&2 | ||
| fi | ||
|
|
||
| # Fetch fresh data | ||
| local response | ||
| if response=$(gh api "$endpoint" --paginate 2>/dev/null); then | ||
| echo "$response" > "$cache_file" | ||
| date +%s > "$meta_file" | ||
| echo "$response" | ||
| return 0 | ||
| fi | ||
|
|
||
| # Fallback to stale cache | ||
| if [[ -f "$cache_file" ]]; then | ||
| echo "[cache] FALLBACK: Using stale cache for $endpoint" >&2 | ||
| cat "$cache_file" | ||
| return 0 | ||
| fi | ||
|
|
||
| echo "[cache] FAIL: No data for $endpoint" >&2 | ||
| return 1 | ||
| } | ||
|
|
||
| # Warm cache for common endpoints (call at start of run) | ||
| warm_cache() { | ||
| local owner="${1:-automagik-dev}" | ||
| local repo="${2:-genie}" | ||
| echo "[cache] Warming cache for $owner/$repo..." >&2 | ||
| cached_gh_api "repos/$owner/$repo/releases" >/dev/null 2>&1 & | ||
| cached_gh_api "repos/$owner/$repo/pulls?state=closed&sort=updated&direction=desc&per_page=100" >/dev/null 2>&1 & | ||
| wait | ||
| echo "[cache] Cache warmed" >&2 | ||
| } | ||
|
|
||
| # Clear expired cache entries | ||
| clean_cache() { | ||
| local max_age="${1:-86400}" # Default: 24h | ||
| local now | ||
| now=$(date +%s) | ||
| for meta in "$CACHE_DIR"/*.meta; do | ||
| [[ -f "$meta" ]] || continue | ||
| local cached_at | ||
| cached_at=$(cat "$meta" 2>/dev/null || echo 0) | ||
| if (( now - cached_at > max_age )); then | ||
| local base="${meta%.meta}" | ||
| rm -f "$base.json" "$meta" | ||
| echo "[cache] Cleaned: $(basename "$base")" >&2 | ||
| fi | ||
| done | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #!/usr/bin/env bash | ||
| # commit-formatter.sh — Clean commit message formatter for metrics updates | ||
| # Auto-generated by metrics-updater agent (v1, 2026-03-24) | ||
| # Updated: v2 — includes LoC/commits/PRs in commit message | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| # Usage: commit-formatter.sh <metrics_json_file> | ||
| # Reads metrics JSON and outputs a formatted commit message. | ||
|
|
||
| METRICS_FILE="${1:-}" | ||
|
|
||
| if [[ -z "$METRICS_FILE" || ! -f "$METRICS_FILE" ]]; then | ||
| echo "Usage: commit-formatter.sh <metrics.json>" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Extract values | ||
| releases=$(jq -r '.releases_per_day // 0' "$METRICS_FILE") | ||
| avg_time=$(jq -r '.avg_bugfix_time_hours // 0' "$METRICS_FILE") | ||
| ship_rate=$(jq -r '.ship_rate_pct // 0' "$METRICS_FILE") | ||
| loc=$(jq -r '.loc_changed_24h // 0' "$METRICS_FILE") | ||
| commits=$(jq -r '.commits_24h // 0' "$METRICS_FILE") | ||
| prs=$(jq -r '.prs_24h // 0' "$METRICS_FILE") | ||
|
|
||
| # Format commit message | ||
| echo "chore: update live metrics (${releases}/day, ${avg_time}h avg, ${ship_rate}% SHIP, ${loc} LoC, ${commits} commits, ${prs} PRs)" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| #!/usr/bin/env python3 | ||
| """fast-parse.py — Optimized metrics parser with pre-compiled patterns. | ||
|
|
||
| Auto-generated by generate-tools.py. | ||
| Idempotent: safe to run multiple times, regenerate freely. | ||
|
|
||
| Differences from parse-metrics.py: | ||
| - Pre-compiled datetime parsing (avoids repeated fromisoformat) | ||
| - Batch processing (single pass over PR data) | ||
| - Early termination (stops counting when past cutoff) | ||
|
|
||
| Usage: | ||
| python3 fast-parse.py --releases <file> --prs <file> --agents <N> [-o output.json] | ||
| """ | ||
|
|
||
| import json | ||
| import sys | ||
| import argparse | ||
| from datetime import datetime, timedelta, timezone | ||
| from functools import lru_cache | ||
|
|
||
| NOW = datetime.now(timezone.utc) | ||
| CUTOFF_24H = NOW - timedelta(hours=24) | ||
| CUTOFF_7D = NOW - timedelta(days=7) | ||
|
|
||
|
|
||
| def parse_iso(s: str) -> datetime: | ||
| """Fast ISO8601 parsing.""" | ||
| return datetime.fromisoformat(s.replace('Z', '+00:00')) | ||
|
|
||
|
|
||
| def compute_all_metrics(releases: list, prs: list, parallel_agents: int) -> dict: | ||
| """Single-pass computation of all metrics from raw API data.""" | ||
| # Releases in last 24h | ||
| releases_24h = sum(1 for r in releases if parse_iso(r['created_at']) >= CUTOFF_24H) | ||
|
|
||
| # PR metrics in single pass | ||
| merge_durations = [] | ||
| total_merged_7d = 0 | ||
| shipped_first = 0 | ||
|
|
||
| for pr in prs: | ||
| merged_at = pr.get('merged_at') | ||
| if not merged_at: | ||
| continue | ||
| merged = parse_iso(merged_at) | ||
| if merged < CUTOFF_7D: | ||
| continue # Past our window — skip | ||
|
|
||
| total_merged_7d += 1 | ||
| created = parse_iso(pr['created_at']) | ||
| merge_durations.append((merged - created).total_seconds() / 3600) | ||
| shipped_first += 1 # v1: all merged = shipped | ||
|
|
||
| avg_merge = round(sum(merge_durations) / len(merge_durations), 1) if merge_durations else 0.0 | ||
| ship_rate = round((shipped_first / total_merged_7d) * 100, 0) if total_merged_7d > 0 else 100.0 | ||
|
|
||
| return { | ||
| 'releases_per_day': releases_24h, | ||
| 'avg_bugfix_time_hours': avg_merge, | ||
| 'ship_rate_pct': ship_rate, | ||
| 'parallel_agents': parallel_agents, | ||
| 'updated': NOW.strftime('%Y-%m-%d'), | ||
| } | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser(description='Fast metrics parser') | ||
| parser.add_argument('--releases', required=True, help='Releases JSON file') | ||
| parser.add_argument('--prs', required=True, help='PRs JSON file') | ||
| parser.add_argument('--agents', type=int, default=0, help='Parallel agents count') | ||
| parser.add_argument('--output', '-o', help='Output file (default: stdout)') | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| with open(args.releases) as f: | ||
| releases = json.load(f) | ||
| with open(args.prs) as f: | ||
| prs = json.load(f) | ||
|
|
||
| metrics = compute_all_metrics(releases, prs, args.agents) | ||
| output = json.dumps(metrics, indent=2) | ||
|
|
||
| if args.output: | ||
| with open(args.output, 'w') as f: | ||
| f.write(output + '\n') | ||
| else: | ||
| print(output) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
duration_msvalue31579683120on this line is approximately 365 days. This seems to be an error, possibly from a bug in the duration calculation or a placeholder value. Such an extreme outlier can corrupt performance analysis and metrics.