Skip to content

Commit b844851

Browse files
committed
Merge remote-tracking branch 'upstream/main' into realtime-dash
# Conflicts: # .github/workflows/pr-review-dashboard.yml
2 parents ccf87d8 + 83c1b4f commit b844851

162 files changed

Lines changed: 830 additions & 511 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/agents/knowledge/general-rules.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ Reason about visibility from "what does the advice method directly reference?".
105105
## [Style] `@SuppressWarnings` Usage
106106

107107
- Place `@SuppressWarnings` on the single member that needs it, or on the class when two
108-
or more members in the class need the same suppression.
108+
or more members in the class need the same suppression. Do not move an existing
109+
suppression from a member to the class unless multiple members need it.
109110
- **Do not add `@SuppressWarnings("deprecation")` unless the build fails without it.**
110111
The project disables javac's `-Xlint:deprecation` globally and uses a custom Error Prone
111112
check (`OtelDeprecatedApiUsage`) instead. Only add the annotation when it is actually

.github/agents/knowledge/gradle-conventions.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,10 @@ usesService(gradle.sharedServices.registrations["testcontainersBuildService"].se
199199
```
200200

201201
Place the declaration in `withType<Test>().configureEach` when **all** test tasks in the
202-
module use Testcontainers. Otherwise place it on **only** the specific task(s) that do.
202+
module use Testcontainers **and** the module has multiple test tasks. When the module has
203+
only a single test task, put the declaration directly inside `tasks.test { ... }` — do not
204+
introduce a `withType<Test>().configureEach` block just for `usesService`. Otherwise place
205+
it on **only** the specific task(s) that use Testcontainers.
203206

204207
**Do not over-apply.** Adding `usesService` to a task that does not use Testcontainers
205208
unnecessarily throttles it against the 2-slot concurrency limit. A module may have some
@@ -215,9 +218,10 @@ because the dependency may only be used by some suites in the module.
215218

216219
## Prefer `withType<Test>().configureEach` (ONLY when multiple test tasks exist)
217220

218-
When a module has custom test tasks (e.g., `testStableSemconv`), system properties and JVM
219-
args that apply to **all** test tasks should be set once in a `withType<Test>().configureEach`
220-
block, not repeated on each individual task.
221+
When a module has custom test tasks (e.g., `testStableSemconv`), shared configuration
222+
(system properties, JVM args, `usesService` declarations, etc.) that applies to **all**
223+
test tasks should be set once in a `withType<Test>().configureEach` block, not repeated
224+
on each individual task.
221225

222226
If a property or JVM arg is moved into `withType<Test>().configureEach`, remove any now-redundant
223227
copies from individual tasks unless a task intentionally overrides the shared value.

.github/agents/knowledge/testing-general-patterns.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@
8383
`hasAttributesSatisfyingExactly(...)` in the same assertion chain, because the exact
8484
variant already validates the total attribute count. Remove the `hasTotalAttributeCount`
8585
call.
86+
- These rules apply to **span** attribute assertions only. Metric point assertions are
87+
different: metric point assertions do not have a `hasTotalAttributeCount(...)` method, so
88+
the span guidance about preferring `hasTotalAttributeCount(0)` for zero-attribute checks
89+
does not apply. For metric points, used `point.hasAttributes(Attributes.empty())` — it
90+
reads more clearly than the no-arg `hasAttributesSatisfyingExactly()` form.
8691
- For non-semconv attribute keys in `equalTo(...)`, use inline `AttributeKey` factory
8792
methods — `longKey("name")`, `stringKey("name")`, etc. — directly in the assertion.
8893
Do **not** extract them into class-level `private static final AttributeKey<T>` constants.

.github/agents/pr-review.agent.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
---
2+
description: "Review a PR in opentelemetry-java-instrumentation and post a pending GitHub review with inline comments and code suggestions. The review stays as a draft until the caller submits it."
3+
tools: [read, edit, execute, search]
4+
---
5+
6+
You are a code review agent for the `opentelemetry-java-instrumentation` repository.
7+
8+
Primary responsibilities:
9+
10+
- Review PR changes against repository standards and the knowledge base.
11+
- Post a **pending** (draft) GitHub review with inline comments and `suggestion` blocks.
12+
- The caller submits the review manually after inspecting it.
13+
14+
Do not stop until all in-scope files are reviewed and the review is posted.
15+
16+
## Knowledge Loading
17+
18+
Always load first:
19+
20+
- `docs/contributing/style-guide.md`
21+
- `.github/agents/knowledge/general-rules.md` — review checklist and core rules
22+
23+
Then load additional knowledge files **only** when their scope trigger fires.
24+
Use the **Knowledge File** column in the checklist table inside `general-rules.md`.
25+
26+
## Review Workflow
27+
28+
### Phase 1: Resolve PR
29+
30+
1. Get current branch:
31+
32+
```
33+
git branch --show-current
34+
```
35+
36+
2. If branch is `main`, stop with:
37+
> "Aborting: cannot review the main branch. Please check out a PR branch first."
38+
39+
3. Resolve PR:
40+
41+
```
42+
gh pr list --head <branch-name> --json number,title,url,headRefOid --jq '.[0]'
43+
```
44+
45+
4. If no PR exists, stop:
46+
> "No open PR found for branch `<branch-name>`. Push the branch and open a PR first."
47+
48+
5. Announce: `Reviewing PR #<number>: <title>`
49+
50+
### Phase 2: Build Diff Scope
51+
52+
1. Get changed file names:
53+
54+
```
55+
gh pr diff <number> --name-only
56+
```
57+
58+
2. Get the full unified diff:
59+
60+
```
61+
gh pr diff <number> --color never
62+
```
63+
64+
3. Parse the diff to build a map of `file → set of changed right-side line numbers`.
65+
66+
4. For each changed file, also parse the diff **hunk boundaries** (the `@@ ... @@`
67+
headers). Record the right-side line ranges that each hunk covers.
68+
A review comment or suggestion can only target lines **inside a diff hunk**.
69+
70+
### Phase 3: Read Files and Load Knowledge
71+
72+
1. Skip non-reviewable files:
73+
- binary files
74+
- files under `licenses/`
75+
2. Read each changed file's full content.
76+
3. Scan file contents to decide which additional knowledge articles to load
77+
(e.g., load `javaagent-advice-patterns.md` when `@Advice` classes are in scope).
78+
79+
### Phase 4: Review
80+
81+
For each file, apply all rules from the loaded knowledge articles.
82+
Only flag issues on lines that were changed in the PR diff.
83+
84+
Do not flag:
85+
86+
- Non-capturing lambdas or method references as unnecessary allocations.
87+
- Patterns explicitly allowed by the style guide or knowledge articles.
88+
89+
For each finding, record:
90+
91+
- `path` — repo-relative file path
92+
- `line` — right-side line number in the current file
93+
- `start_line` — (optional) first line, if the comment spans multiple lines
94+
- `category` — tag like `[Style]`, `[Concurrency]`, `[Javaagent]`, etc.
95+
- `body` — concise comment text
96+
- `suggestion` — (optional) replacement text for a `suggestion` block
97+
98+
### Phase 5: Build and Post Review
99+
100+
#### Comment Format
101+
102+
Each comment body should be concise. When a concrete fix is possible, include a
103+
GitHub suggestion block:
104+
105+
````
106+
<comment text>
107+
108+
```suggestion
109+
<replacement lines>
110+
```
111+
````
112+
113+
The suggestion block replaces the lines from `start_line` (or `line` if single-line)
114+
through `line` inclusive. The replacement text must be the **exact literal content**
115+
that should appear in those lines — no fenced-code markup inside the suggestion.
116+
117+
#### Hunk Validation
118+
119+
Before adding a comment, verify that **all** lines from `start_line` through `line`
120+
fall inside a diff hunk. If any line is outside a hunk:
121+
122+
- Try narrowing the range (e.g., drop to single-line, remove the suggestion).
123+
- If the line cannot be commented on at all, skip it and note it in the summary.
124+
125+
#### Multi-line Suggestion Rules
126+
127+
- `start_line` and `start_side` are required for multi-line comments.
128+
- Both `side` and `start_side` must be `"RIGHT"`.
129+
- The suggestion text replaces the entire `start_line..line` range.
130+
131+
#### Posting
132+
133+
1. Collect all valid comments into a JSON array.
134+
135+
2. Build the review payload:
136+
137+
```json
138+
{
139+
"commit_id": "<head SHA from Phase 1>",
140+
"body": "<summary text>",
141+
"comments": [ ... ]
142+
}
143+
```
144+
145+
Do **not** include an `"event"` field — omitting it creates a PENDING review.
146+
147+
3. Write the JSON to a temp file in the workspace (e.g., `_review-payload.json`).
148+
149+
4. Post the review:
150+
151+
```
152+
gh api repos/{owner}/{repo}/pulls/{number}/reviews --method POST --input _review-payload.json --jq '.id'
153+
```
154+
155+
5. If the API returns a `422` with `"Line could not be resolved"`:
156+
- One or more comments reference lines outside diff hunks.
157+
- Use binary search: split comments into halves and post each half separately to
158+
identify the offending comment(s).
159+
- Fix or drop the offending comments, then retry.
160+
161+
6. Delete the temp file after a successful post.
162+
163+
7. Report the review ID and comment count:
164+
> Posted pending review `<id>` with N comments on PR #<number>.
165+
> Submit it from the GitHub UI or via:
166+
> `gh api repos/{owner}/{repo}/pulls/{number}/reviews/{id}/events --method POST -f event=COMMENT`
167+
168+
## Review Checklist and Core Rules
169+
170+
Load `knowledge/general-rules.md` — it contains the review checklist table and all
171+
core rules that apply to every review.

.github/labeler.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

.github/scripts/pull-request-dashboard.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,16 @@
101101
# ---------------------------------------------------------------- gh helpers
102102

103103

104-
def gh_api(path: str, paginate: bool = False) -> Any:
104+
def gh_api(path: str, paginate: bool = False, token: str | None = None) -> Any:
105105
cmd = ["gh", "api", "-H", "Accept: application/vnd.github+json"]
106106
if paginate:
107107
cmd += ["--paginate", "--slurp"]
108108
cmd.append(path)
109+
env = {**os.environ, "GH_TOKEN": token} if token else None
109110
proc = subprocess.run(
110111
cmd, capture_output=True, text=True, check=False,
111112
encoding="utf-8", errors="replace",
113+
env=env,
112114
)
113115
if proc.returncode != 0:
114116
raise RuntimeError(f"gh api {path} failed: {proc.stderr.strip()}")
@@ -222,14 +224,22 @@ def detect_repo() -> str:
222224

223225

224226
def load_reviewer_set(org: str) -> set[str]:
227+
# Reading org team membership requires a token with org:read scope.
228+
# The default Actions GITHUB_TOKEN can't do this, so use OTELBOT_TOKEN
229+
# (a GitHub App installation token) when present; fall back to the
230+
# default GH_TOKEN otherwise (useful for local runs with a user token).
231+
token = os.environ.get("OTELBOT_TOKEN") or None
225232
reviewers: set[str] = set()
226233
for slug in APPROVER_TEAM_SLUGS:
227-
members = gh_api(f"/orgs/{org}/teams/{slug}/members?per_page=100", paginate=True)
234+
members = gh_api(
235+
f"/orgs/{org}/teams/{slug}/members?per_page=100",
236+
paginate=True, token=token,
237+
)
228238
reviewers.update(m["login"] for m in members)
229239
if not reviewers:
230240
raise RuntimeError(
231241
f"no reviewers found in teams {APPROVER_TEAM_SLUGS}; "
232-
f"the GH_TOKEN must have org:read permission"
242+
f"the token must have org:read permission"
233243
)
234244
return {r.lower() for r in reviewers}
235245

@@ -774,6 +784,12 @@ def render_markdown_compact(
774784
res = results.get(pr["number"]) or {}
775785
decision = res.get("decision") or {}
776786
side = _infer_side(decision) if decision else "unknown"
787+
# PRs authored by app/otelbot are never "waiting on authors" — the
788+
# bot doesn't respond to review feedback, so the ball is always in
789+
# an approver's court (review, close, or take over).
790+
pr_author_login = ((pr.get("author") or {}).get("login") or "").lower()
791+
if side == "author" and pr_author_login == "app/otelbot":
792+
side = "approver"
777793
# Promote approved PRs that are waiting on approvers into the
778794
# "maintainer" bucket (deterministic): GitHub already says they have
779795
# the required approvals, so a maintainer can merge them.
Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
name: Build pull request
22

3+
# Optional test variants (openj9, windows) are gated by `test openj9` /
4+
# `test windows` labels on the PR, read from the event payload. We
5+
# deliberately do NOT subscribe to `pull_request: [labeled]` — see
6+
# https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/18446
7+
# for the history of why every previous attempt to use that trigger failed.
8+
#
9+
# Practical consequence: adding a `test openj9` / `test windows` label
10+
# doesn't retrigger the build. Push another commit or close/reopen the
11+
# PR to pick it up. `comment-on-test-label.yml` posts a reminder when
12+
# one of those labels is added.
13+
#
14+
# `test native` is detected automatically from the PR's changed paths
15+
# (the `resolve-native` job below). The manual `test native` label is
16+
# still honored as a force-enable.
17+
318
on:
419
pull_request:
520
types:
621
- opened
722
- synchronize
823
- reopened
9-
- labeled
1024

1125
concurrency:
1226
group: build-pull-request-${{ github.event.pull_request.number }}
@@ -16,22 +30,44 @@ permissions:
1630
contents: read
1731

1832
jobs:
33+
# Run native tests automatically when the PR touches a library
34+
# instrumentation module with native support (logback-appender, jdbc,
35+
# spring), the native smoke test harness, or the top-level build config
36+
# that affects them.
37+
resolve-native:
38+
runs-on: ubuntu-latest
39+
outputs:
40+
run-native-tests: ${{ steps.filter.outputs.native }}
41+
steps:
42+
- name: Detect native-relevant changes
43+
id: filter
44+
env:
45+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46+
PR_NUMBER: ${{ github.event.pull_request.number }}
47+
run: |
48+
# `|| true` so an empty result from `grep -v` (everything filtered)
49+
# doesn't fail the step under pipefail.
50+
files=$(
51+
gh api --paginate "/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" \
52+
--jq '.[].filename' |
53+
grep -Ev '^instrumentation/spring/.+/javaagent/' || true
54+
)
55+
include='^instrumentation/logback/logback-appender-1\.0/library/'
56+
include+='|^instrumentation/jdbc/library/'
57+
include+='|^instrumentation/spring/'
58+
include+='|^smoke-tests-otel-starter/'
59+
include+='|^dependencyManagement/build\.gradle\.kts$'
60+
include+='|^settings\.gradle\.kts$'
61+
if grep -Eq "$include" <<<"$files"; then
62+
echo "native=true" >> "$GITHUB_OUTPUT"
63+
else
64+
echo "native=false" >> "$GITHUB_OUTPUT"
65+
fi
66+
1967
build:
20-
# On `labeled` events, only proceed when the added label affects what we
21-
# build. All other event types (opened, synchronize, reopened) always
22-
# proceed.
23-
if: |
24-
github.event.action != 'labeled' ||
25-
github.event.label.name == 'test native' ||
26-
github.event.label.name == 'test openj9' ||
27-
github.event.label.name == 'test windows'
68+
needs: [resolve-native]
2869
uses: ./.github/workflows/reusable-pr-build.yml
2970
with:
30-
# it's rare for only the openj9 tests, openj9 smoke variants, the windows
31-
# smoke tests, or the native tests to break, so they are gated by labels.
32-
# `test native` is applied automatically by .github/labeler.yml when the
33-
# PR diff touches native-relevant paths; `test openj9` and `test windows`
34-
# are applied manually.
35-
skip-native-tests: ${{ !contains(github.event.pull_request.labels.*.name, 'test native') }}
71+
skip-native-tests: ${{ !(needs.resolve-native.outputs.run-native-tests == 'true' || contains(github.event.pull_request.labels.*.name, 'test native')) }}
3672
skip-openj9-tests: ${{ !contains(github.event.pull_request.labels.*.name, 'test openj9') }}
3773
skip-windows-smoke-tests: ${{ !contains(github.event.pull_request.labels.*.name, 'test windows') }}

0 commit comments

Comments
 (0)