Skip to content

Commit ca53fae

Browse files
Copilotmrjf
andauthored
Merge origin/main and resolve conflicts
Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
2 parents 913475e + e5f796e commit ca53fae

112 files changed

Lines changed: 2175 additions & 519 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/autoloop.lock.yml

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/autoloop.md

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ safe-outputs:
4545
title-prefix: "[Autoloop] "
4646
labels: [automation, autoloop]
4747
protected-files: fallback-to-issue
48+
preserve-branch-name: true
4849
max: 1
4950
push-to-pull-request-branch:
5051
target: "*"
@@ -434,10 +435,26 @@ steps:
434435
# Look up existing PR for the selected program's canonical branch
435436
existing_pr = None
436437
head_branch = None
438+
439+
def verify_pr_is_open(pr_number):
440+
"""Check if a PR is still open via the GitHub API. Returns True if open."""
441+
try:
442+
verify_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
443+
verify_req = urllib.request.Request(verify_url, headers={
444+
"Authorization": f"token {github_token}",
445+
"Accept": "application/vnd.github.v3+json",
446+
})
447+
with urllib.request.urlopen(verify_req, timeout=30) as verify_resp:
448+
pr_data = json.loads(verify_resp.read().decode())
449+
return pr_data.get("state") == "open"
450+
except Exception:
451+
return True # If we can't verify, assume it's open (best effort)
452+
437453
if selected:
438454
head_branch = f"autoloop/{selected}"
439455
owner = repo.split("/")[0] if "/" in repo else ""
440456
if owner:
457+
# Strategy 1: exact branch match (works when branch has no framework suffix)
441458
try:
442459
pr_api_url = (
443460
f"https://api.github.com/repos/{repo}/pulls"
@@ -451,22 +468,54 @@ steps:
451468
open_prs = json.loads(pr_resp.read().decode())
452469
if open_prs:
453470
existing_pr = open_prs[0]["number"]
454-
print(f" Found existing PR #{existing_pr} for branch {head_branch}")
455-
else:
456-
print(f" No existing PR found for branch {head_branch}")
471+
print(f" Found existing PR #{existing_pr} for exact branch {head_branch}")
457472
except Exception as e:
458-
print(f" Warning: could not check for existing PRs: {e}")
473+
print(f" Warning: could not check for existing PRs by exact branch: {e}")
474+
475+
# Strategy 2: search by title and branch prefix (catches framework-generated
476+
# hash suffixes like autoloop/name-a1b2c3d4e5f6g7h8 created by create-pull-request)
477+
if existing_pr is None:
478+
try:
479+
title_marker = f"[Autoloop: {selected}]"
480+
branch_prefix = head_branch # e.g. autoloop/perf-comparison
481+
list_url = (
482+
f"https://api.github.com/repos/{repo}/pulls"
483+
f"?state=open&per_page=100&sort=created&direction=desc"
484+
)
485+
list_req = urllib.request.Request(list_url, headers={
486+
"Authorization": f"token {github_token}",
487+
"Accept": "application/vnd.github.v3+json",
488+
})
489+
with urllib.request.urlopen(list_req, timeout=30) as list_resp:
490+
all_open_prs = json.loads(list_resp.read().decode())
491+
# Match branch names: exact canonical name or canonical + framework hash suffix
492+
branch_pattern = re.compile(r'^' + re.escape(branch_prefix) + r'(-[0-9a-f]{16})?$')
493+
for pr in all_open_prs:
494+
pr_title = pr.get("title", "")
495+
pr_head_ref = pr.get("head", {}).get("ref", "")
496+
if title_marker in pr_title or branch_pattern.match(pr_head_ref):
497+
existing_pr = pr["number"]
498+
print(f" Found existing PR #{existing_pr} by title/branch-prefix (branch: {pr_head_ref})")
499+
break
500+
if existing_pr is None:
501+
print(f" No existing PR found for program {selected}")
502+
except Exception as e:
503+
print(f" Warning: could not search for existing PRs by title/prefix: {e}")
459504
else:
460505
print(f" Warning: could not parse owner from GITHUB_REPOSITORY='{repo}'")
461506
462-
# Also check the state file for a recorded PR number as fallback
507+
# Strategy 3: check the state file for a recorded PR number as fallback
463508
if existing_pr is None:
464509
state = read_program_state(selected)
465510
pr_field = state.get("pr") or ""
466511
pr_match = re.match(r'^#?(\d+)$', pr_field.strip())
467512
if pr_match:
468-
existing_pr = int(pr_match.group(1))
469-
print(f" Found PR #{existing_pr} from state file for {selected}")
513+
pr_num = int(pr_match.group(1))
514+
if verify_pr_is_open(pr_num):
515+
existing_pr = pr_num
516+
print(f" Found open PR #{existing_pr} from state file for {selected}")
517+
else:
518+
print(f" PR #{pr_num} from state file is no longer open — ignoring")
470519
471520
result = {
472521
"selected": selected,

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ dist/
33
*.tsbuildinfo
44
package-lock.json
55
*.tgz
6+
playground/benchmarks/
7+
playground/dist/
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Benchmark: Categorical from codes on 100k-element array"""
2+
import json, time
3+
import numpy as np
4+
import pandas as pd
5+
6+
ROWS = 100_000
7+
WARMUP = 3
8+
ITERATIONS = 10
9+
10+
categories = ["apple", "banana", "cherry", "date", "elderberry"]
11+
codes = np.arange(ROWS) % len(categories)
12+
13+
for _ in range(WARMUP):
14+
pd.Categorical.from_codes(codes, categories=categories)
15+
16+
start = time.perf_counter()
17+
for _ in range(ITERATIONS):
18+
pd.Categorical.from_codes(codes, categories=categories)
19+
total = (time.perf_counter() - start) * 1000
20+
21+
print(json.dumps({ "function": "cat_from_codes", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total }))

benchmarks/pandas/bench_cut.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Benchmark: cut (bin into 10 bins) on 100k-element Series"""
2+
import json, time
3+
import numpy as np
4+
import pandas as pd
5+
6+
ROWS = 100_000
7+
WARMUP = 3
8+
ITERATIONS = 10
9+
10+
data = (np.arange(ROWS) % 10000) * 0.01
11+
s = pd.Series(data)
12+
13+
for _ in range(WARMUP):
14+
pd.cut(s, 10)
15+
16+
start = time.perf_counter()
17+
for _ in range(ITERATIONS):
18+
pd.cut(s, 10)
19+
total = (time.perf_counter() - start) * 1000
20+
21+
print(json.dumps({ "function": "cut", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total }))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Benchmark: DataFrame covariance matrix on 1000x10 DataFrame"""
2+
import json, time
3+
import numpy as np
4+
import pandas as pd
5+
6+
ROWS = 1_000
7+
COLS = 10
8+
WARMUP = 3
9+
ITERATIONS = 10
10+
11+
data = {f"col{c}": np.sin(np.arange(ROWS) * 0.01 + c) for c in range(COLS)}
12+
df = pd.DataFrame(data)
13+
14+
for _ in range(WARMUP):
15+
df.cov()
16+
17+
start = time.perf_counter()
18+
for _ in range(ITERATIONS):
19+
df.cov()
20+
total = (time.perf_counter() - start) * 1000
21+
22+
print(json.dumps({ "function": "dataframe_cov", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total }))

benchmarks/pandas/bench_ewm_std.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Benchmark: ewm std (alpha=0.1) on 100k-element Series"""
2+
import json, time
3+
import numpy as np
4+
import pandas as pd
5+
6+
ROWS = 100_000
7+
WARMUP = 3
8+
ITERATIONS = 10
9+
10+
data = np.sin(np.arange(ROWS) * 0.01)
11+
s = pd.Series(data)
12+
13+
for _ in range(WARMUP):
14+
s.ewm(alpha=0.1).std()
15+
16+
start = time.perf_counter()
17+
for _ in range(ITERATIONS):
18+
s.ewm(alpha=0.1).std()
19+
total = (time.perf_counter() - start) * 1000
20+
21+
print(json.dumps({ "function": "ewm_std", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total }))

benchmarks/pandas/bench_ewm_var.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Benchmark: ewm var (alpha=0.1) on 100k-element Series"""
2+
import json, time
3+
import numpy as np
4+
import pandas as pd
5+
6+
ROWS = 100_000
7+
WARMUP = 3
8+
ITERATIONS = 10
9+
10+
data = np.sin(np.arange(ROWS) * 0.01)
11+
s = pd.Series(data)
12+
13+
for _ in range(WARMUP):
14+
s.ewm(alpha=0.1).var()
15+
16+
start = time.perf_counter()
17+
for _ in range(ITERATIONS):
18+
s.ewm(alpha=0.1).var()
19+
total = (time.perf_counter() - start) * 1000
20+
21+
print(json.dumps({ "function": "ewm_var", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total }))
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Benchmark: expanding std on 100k-element Series"""
2+
import json, time
3+
import numpy as np
4+
import pandas as pd
5+
6+
ROWS = 100_000
7+
WARMUP = 3
8+
ITERATIONS = 10
9+
10+
data = np.sin(np.arange(ROWS) * 0.01)
11+
s = pd.Series(data)
12+
13+
for _ in range(WARMUP):
14+
s.expanding().std()
15+
16+
start = time.perf_counter()
17+
for _ in range(ITERATIONS):
18+
s.expanding().std()
19+
total = (time.perf_counter() - start) * 1000
20+
21+
print(json.dumps({ "function": "expanding_std", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total }))
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Benchmark: expanding sum on 100k-element Series"""
2+
import json, time
3+
import numpy as np
4+
import pandas as pd
5+
6+
ROWS = 100_000
7+
WARMUP = 3
8+
ITERATIONS = 10
9+
10+
data = np.sin(np.arange(ROWS) * 0.01)
11+
s = pd.Series(data)
12+
13+
for _ in range(WARMUP):
14+
s.expanding().sum()
15+
16+
start = time.perf_counter()
17+
for _ in range(ITERATIONS):
18+
s.expanding().sum()
19+
total = (time.perf_counter() - start) * 1000
20+
21+
print(json.dumps({ "function": "expanding_sum", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total }))

0 commit comments

Comments
 (0)