Skip to content

Commit cddf9c0

Browse files
committed
Convert module cleanup to GitHub agentic workflow
1 parent a48447f commit cddf9c0

12 files changed

Lines changed: 1616 additions & 872 deletions

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
*.cmd text eol=crlf
55

66
licenses/** linguist-generated
7+
8+
.github/workflows/*.lock.yml linguist-generated=true merge=ours

.github/aw/actions-lock.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"entries": {
3+
"actions/github-script@v9.0.0": {
4+
"repo": "actions/github-script",
5+
"version": "v9.0.0",
6+
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
7+
},
8+
"github/gh-aw-actions/setup@v0.71.5": {
9+
"repo": "github/gh-aw-actions/setup",
10+
"version": "v0.71.5",
11+
"sha": "b8068426813005612b960b5ab0b8bd2c27142323"
12+
}
13+
}
14+
}

.github/scripts/module-cleanup/build-cleanup-matrix.py

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,60 @@
11
#!/usr/bin/env python3
2-
"""Build the ordered list of instrumentation modules for this review run.
2+
"""Pick the next instrumentation module for this cleanup run.
33
4-
Reads module list from settings.gradle.kts, filters out already-reviewed
5-
modules (read from the otelbot/module-cleanup-progress branch by the workflow
6-
and passed via REVIEW_PROGRESS), respects the open-PR cap, and writes a
7-
`modules` JSON array + `has_work` flag to $GITHUB_OUTPUT.
4+
Reads the module list from settings.gradle.kts, filters out already-processed
5+
modules (passed via REVIEW_PROGRESS), and emits a single module to walk this
6+
run plus a count of how many unprocessed modules remain after it.
87
9-
The review job processes modules sequentially on a single branch, stopping
10-
after it accumulates at least `FILE_THRESHOLD` modified files, so the list
11-
emitted here is an upper-bound slice the job is allowed to walk through.
8+
The workflow chains itself one module at a time. The finalize step uses
9+
`queue_remaining` to decide whether to self-dispatch or flush the pending
10+
queue into a PR.
1211
1312
Environment variables:
14-
GITHUB_OUTPUT - path to the GitHub Actions output file
15-
GH_TOKEN - token for `gh` CLI (set automatically by the workflow)
16-
REVIEW_PROGRESS - newline-separated list of reviewed module names
17-
(contents of reviewed.txt on the progress branch)
13+
GITHUB_OUTPUT - path to the GitHub Actions output file
14+
GH_TOKEN - token for `gh` CLI (set automatically by the workflow)
15+
REVIEW_PROGRESS - newline-separated list of processed module names
16+
(contents of processed.txt on the memory branch, plus
17+
shorts already in inflight module-cleanup PR bodies)
18+
19+
Outputs (to $GITHUB_OUTPUT):
20+
has_work - "true" if a module was picked, "false" otherwise
21+
short_name - picked module's gradle short name (e.g. "akka-actor:javaagent")
22+
module_dir - picked module's repo-relative directory
23+
queue_remaining - count of unprocessed modules left AFTER this one
1824
"""
1925

20-
import json
2126
import os
2227
import re
2328
import subprocess
2429
from pathlib import Path
2530

2631
SETTINGS_FILE = "settings.gradle.kts"
27-
# Skip the run entirely if at least this many automated review PRs are already open.
32+
# Skip the run entirely if at least this many module-cleanup PRs are already open.
2833
MAX_OPEN_PRS = 5
29-
# Upper bound on modules the review job will walk through in a single run,
30-
# even if the file-count threshold is never reached. Keeps one run bounded.
31-
MODULE_LIMIT_PER_RUN = 50
3234

3335

3436
def parse_modules() -> list[tuple[str, str]]:
3537
"""Return list of (gradle_name, module_dir) from settings.gradle.kts."""
3638
text = Path(SETTINGS_FILE).read_text(encoding="utf-8")
37-
# Match include(":instrumentation:activej-http:6.0:javaagent")
3839
raw = re.findall(r'include\(":instrumentation:([^"]+)"\)', text)
3940
pairs = []
4041
for entry in sorted(raw):
4142
parts = entry.split(":")
42-
# Skip shared/helper modules (e.g. "cdi-testing") that don't follow the
43-
# <library>:<variant> layout used for real instrumentation modules.
4443
if len(parts) < 2:
4544
continue
4645
module_dir = "instrumentation/" + "/".join(parts)
47-
# Gradle module name: second-to-last:last
4846
gradle_name = f"{parts[-2]}:{parts[-1]}"
4947
pairs.append((gradle_name, module_dir))
5048
return pairs
5149

5250

53-
def load_reviewed() -> set[str]:
54-
"""Load already-reviewed module names from the REVIEW_PROGRESS env var."""
51+
def load_processed() -> set[str]:
52+
"""Load already-processed module names from the REVIEW_PROGRESS env var."""
5553
progress = os.environ.get("REVIEW_PROGRESS", "")
5654
return {line.strip() for line in progress.splitlines() if line.strip()}
5755

5856

5957
def count_open_prs() -> int:
60-
"""Count open PRs with the module cleanup label."""
6158
result = subprocess.run(
6259
["gh", "pr", "list", "--label", "module cleanup",
6360
"--state", "open", "--json", "number", "--jq", "length"],
@@ -67,46 +64,49 @@ def count_open_prs() -> int:
6764

6865

6966
def write_output(key: str, value: str) -> None:
70-
"""Append a key=value to $GITHUB_OUTPUT. Values must not contain newlines."""
7167
assert "\n" not in value, f"multi-line $GITHUB_OUTPUT value not supported: {value!r}"
7268
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f:
7369
f.write(f"{key}={value}\n")
7470

7571

72+
def emit_no_work() -> None:
73+
write_output("has_work", "false")
74+
write_output("short_name", "")
75+
write_output("module_dir", "")
76+
write_output("queue_remaining", "0")
77+
78+
7679
def main() -> None:
7780
all_modules = parse_modules()
7881
print(f"Total instrumentation modules: {len(all_modules)}")
7982

80-
reviewed = load_reviewed()
81-
print(f"Already reviewed: {len(reviewed)}")
83+
processed = load_processed()
84+
print(f"Already processed: {len(processed)}")
8285

83-
remaining = [(name, d) for name, d in all_modules if name not in reviewed]
86+
remaining = [(n, d) for n, d in all_modules if n not in processed]
8487
print(f"Remaining modules: {len(remaining)}")
8588

8689
if not remaining:
87-
print("All modules have been reviewed!")
88-
write_output("has_work", "false")
89-
write_output("modules", "[]")
90+
print("All modules have been processed!")
91+
emit_no_work()
9092
return
9193

9294
open_prs = count_open_prs()
93-
print(f"Open review PRs: {open_prs}")
94-
95+
print(f"Open module-cleanup PRs: {open_prs}")
9596
if open_prs >= MAX_OPEN_PRS:
9697
print(f"PR cap reached ({open_prs} open >= {MAX_OPEN_PRS}). Skipping this cycle.")
97-
write_output("has_work", "false")
98-
write_output("modules", "[]")
98+
emit_no_work()
9999
return
100100

101-
batch = remaining[:MODULE_LIMIT_PER_RUN]
102-
print(f"Dispatching {len(batch)} modules (upper bound for this run)")
103-
104-
modules = [{"short_name": name, "module_dir": d} for name, d in batch]
105-
modules_json = json.dumps(modules)
106-
print(json.dumps(modules, indent=2))
101+
short_name, module_dir = remaining[0]
102+
queue_remaining = len(remaining) - 1
103+
print(f"Picked: {short_name} ({module_dir})")
104+
print(f"Queue remaining after this run: {queue_remaining}")
107105

108106
write_output("has_work", "true")
109-
write_output("modules", modules_json)
107+
write_output("short_name", short_name)
108+
write_output("module_dir", module_dir)
109+
write_output("queue_remaining", str(queue_remaining))
110110

111111

112112
if __name__ == "__main__":

0 commit comments

Comments
 (0)