X Tutup
Skip to content

Commit 48d75c0

Browse files
authored
Merge branch 'master' into claude/fix-graphql-java-4278-v6Z3y
2 parents 50f05ba + d96a3f4 commit 48d75c0

29 files changed

+2016
-334
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.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@v8": {
4+
"repo": "actions/github-script",
5+
"version": "v8",
6+
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
7+
},
8+
"github/gh-aw/actions/setup@v0.49.0": {
9+
"repo": "github/gh-aw/actions/setup",
10+
"version": "v0.49.0",
11+
"sha": "0eb518a648ba8178f4f42559a4c250d3e513acd1"
12+
}
13+
}
14+
}

.github/scripts/parse-jacoco.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Shared JaCoCo XML parser used by CI workflows.
2+
// Extracts overall and per-class coverage counters from a JaCoCo XML report.
3+
4+
const fs = require('fs');
5+
6+
const zeroCov = { covered: 0, missed: 0 };
7+
8+
function parseJacocoXml(jacocoFile) {
9+
const result = { overall: {}, classes: {} };
10+
11+
if (!fs.existsSync(jacocoFile)) {
12+
return null;
13+
}
14+
15+
const xml = fs.readFileSync(jacocoFile, 'utf8');
16+
17+
// Overall counters (outside <package> tags)
18+
const stripped = xml.replace(/<package[\s\S]*?<\/package>/g, '');
19+
const re = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
20+
let m;
21+
while ((m = re.exec(stripped)) !== null) {
22+
const entry = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
23+
if (m[1] === 'LINE') result.overall.line = entry;
24+
else if (m[1] === 'BRANCH') result.overall.branch = entry;
25+
else if (m[1] === 'METHOD') result.overall.method = entry;
26+
}
27+
28+
// Per-class counters from <package>/<class> elements.
29+
// The negative lookbehind (?<!\/) prevents matching self-closing <class .../> tags
30+
// (interfaces, annotations) which have no body and would otherwise steal the next
31+
// class's counters.
32+
const pkgRe = /<package\s+name="([^"]+)">([\s\S]*?)<\/package>/g;
33+
let pkgMatch;
34+
while ((pkgMatch = pkgRe.exec(xml)) !== null) {
35+
const pkgBody = pkgMatch[2];
36+
const classRe = /<class\s+name="([^"]+)"[^>]*(?<!\/)>([\s\S]*?)<\/class>/g;
37+
let classMatch;
38+
while ((classMatch = classRe.exec(pkgBody)) !== null) {
39+
const className = classMatch[1].replace(/\//g, '.');
40+
const classBody = classMatch[2];
41+
const counters = { line: { ...zeroCov }, branch: { ...zeroCov }, method: { ...zeroCov } };
42+
const cntRe = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
43+
let cntMatch;
44+
while ((cntMatch = cntRe.exec(classBody)) !== null) {
45+
const entry = { covered: parseInt(cntMatch[3]), missed: parseInt(cntMatch[2]) };
46+
if (cntMatch[1] === 'LINE') counters.line = entry;
47+
else if (cntMatch[1] === 'BRANCH') counters.branch = entry;
48+
else if (cntMatch[1] === 'METHOD') counters.method = entry;
49+
}
50+
// Skip classes with 0 total lines (empty interfaces, annotations)
51+
if (counters.line.covered + counters.line.missed > 0) {
52+
result.classes[className] = counters;
53+
}
54+
}
55+
}
56+
57+
return result;
58+
}
59+
60+
function pct(covered, missed) {
61+
const total = covered + missed;
62+
return total === 0 ? 0 : (covered / total * 100);
63+
}
64+
65+
module.exports = { parseJacocoXml, pct, zeroCov };

.github/workflows/ci-doctor.lock.yml

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

.github/workflows/ci-doctor.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
description: |
3+
This workflow is an automated CI failure investigator that triggers when monitored workflows fail.
4+
Performs deep analysis of GitHub Actions workflow failures to identify root causes,
5+
patterns, and provide actionable remediation steps. Analyzes logs, error messages,
6+
and workflow configuration to help diagnose and resolve CI issues efficiently.
7+
8+
on:
9+
workflow_run:
10+
workflows: ["Master Build and Publish", "Pull Request Build"]
11+
types:
12+
- completed
13+
14+
# Only trigger for failures - check in the workflow body
15+
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
16+
17+
permissions: read-all
18+
19+
network: defaults
20+
21+
safe-outputs:
22+
create-issue:
23+
title-prefix: "${{ github.workflow }}"
24+
labels: [automation, ci]
25+
add-comment:
26+
27+
timeout-minutes: 10
28+
29+
source: githubnext/agentics/workflows/ci-doctor.md@ee50a3b7d1d3eb4a8c409ac9409fd61c9a66b0f5
30+
---
31+
32+
# CI Failure Doctor
33+
34+
You are the CI Failure Doctor, an expert investigative agent that analyzes failed GitHub Actions workflows to identify root causes and patterns. Your goal is to conduct a deep investigation when the CI workflow fails.
35+
36+
## Current Context
37+
38+
- **Repository**: ${{ github.repository }}
39+
- **Workflow Run**: ${{ github.event.workflow_run.id }}
40+
- **Conclusion**: ${{ github.event.workflow_run.conclusion }}
41+
- **Run URL**: ${{ github.event.workflow_run.html_url }}
42+
- **Head SHA**: ${{ github.event.workflow_run.head_sha }}
43+
44+
## Investigation Protocol
45+
46+
**ONLY proceed if the workflow conclusion is 'failure' or 'cancelled'**. Exit immediately if the workflow was successful.
47+
48+
### Phase 1: Initial Triage
49+
50+
1. **Verify Failure**: Check that `${{ github.event.workflow_run.conclusion }}` is `failure` or `cancelled`
51+
2. **Get Workflow Details**: Use `get_workflow_run` to get full details of the failed run
52+
3. **List Jobs**: Use `list_workflow_jobs` to identify which specific jobs failed
53+
54+
### Phase 2: Log Analysis
55+
56+
1. **Retrieve Logs**: Use `get_job_logs` with `failed_only=true` to get logs from all failed jobs
57+
2. **Extract Key Information**:
58+
- Error messages and stack traces
59+
- Test names that failed and their assertions
60+
- Compilation errors with file paths and line numbers
61+
- Dependency installation failures
62+
63+
### Phase 3: Root Cause Investigation
64+
65+
1. **Categorize Failure Type**:
66+
- **Test Failures**: Identify specific test methods and assertions that failed
67+
- **Compilation Errors**: Analyze errors with exact file paths and line numbers
68+
- **Dependency Issues**: Version conflicts or missing packages
69+
- **Flaky Tests**: Intermittent failures or timing issues
70+
71+
2. **Reporting**:
72+
- Create an issue with investigation results
73+
- Comment on the related PR with analysis (if PR-triggered)
74+
- Provide specific file locations and line numbers for fixes
75+
- Suggest code changes to fix the issue
76+
77+
## Output Requirements
78+
79+
### Investigation Issue Template
80+
81+
When creating an investigation issue, use this structure:
82+
83+
```markdown
84+
# CI Failure - Run #${{ github.event.workflow_run.run_number }}
85+
86+
## Failure Details
87+
- **Run**: [${{ github.event.workflow_run.id }}](${{ github.event.workflow_run.html_url }})
88+
- **Commit**: ${{ github.event.workflow_run.head_sha }}
89+
90+
## Failed Jobs and Errors
91+
[List of failed jobs with key error messages and line numbers]
92+
93+
## Root Cause
94+
[What went wrong, based solely on the build output]
95+
96+
## Recommended Fix
97+
- [ ] [Specific actionable steps with file paths and line numbers]
98+
```
99+
100+
## Important Guidelines
101+
102+
- **Build Output Only**: Base your analysis solely on the job logs — do not search issues, PRs, or external sources
103+
- **Be Specific**: Provide exact file paths, line numbers, and error messages from the logs
104+
- **Action-Oriented**: Focus on actionable fix recommendations, not just analysis
105+
- **Security Conscious**: Never execute untrusted code from logs or external sources
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Auto-merge Dependabot PRs after CI passes.
2+
# Triggers on push to dependabot/* branches so it does NOT appear as a PR check.
3+
name: Dependabot auto-merge
4+
on:
5+
push:
6+
branches:
7+
- 'dependabot/**'
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
dependabot:
15+
runs-on: ubuntu-latest
16+
if: github.actor == 'dependabot[bot]' && github.repository == 'graphql-java/graphql-java'
17+
steps:
18+
- name: Enable auto-merge for Dependabot PRs
19+
run: |
20+
PR_URL=$(gh pr list --head "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --json url --jq '.[0].url')
21+
if [ -n "$PR_URL" ]; then
22+
gh pr merge --auto --squash "$PR_URL"
23+
fi
24+
env:
25+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/master.yml

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ jobs:
123123
script: |
124124
const fs = require('fs');
125125
const path = require('path');
126+
const { parseJacocoXml } = require('./.github/scripts/parse-jacoco.js');
126127
127128
const versions = ['java11', 'java17', 'java21', 'java25', 'jcstress'];
128129
const zeroTest = { total: 0, passed: 0, failed: 0, errors: 0, skipped: 0 };
129-
const zeroCov = { covered: 0, missed: 0 };
130130
131131
// Read current baseline
132132
const baselineFile = 'test-baseline.json';
@@ -147,48 +147,8 @@ jobs:
147147
}
148148
149149
// Update coverage from JaCoCo XML
150-
let coverage = { overall: {}, classes: {} };
151150
const jacocoFile = path.join('coverage', 'jacocoTestReport.xml');
152-
if (fs.existsSync(jacocoFile)) {
153-
const xml = fs.readFileSync(jacocoFile, 'utf8');
154-
155-
// Overall counters (outside <package> tags)
156-
const stripped = xml.replace(/<package[\s\S]*?<\/package>/g, '');
157-
const re = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
158-
let m;
159-
while ((m = re.exec(stripped)) !== null) {
160-
if (m[1] === 'LINE') coverage.overall.line = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
161-
else if (m[1] === 'BRANCH') coverage.overall.branch = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
162-
else if (m[1] === 'METHOD') coverage.overall.method = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
163-
}
164-
165-
// Per-class counters from <package>/<class> elements
166-
const pkgRe = /<package\s+name="([^"]+)">([\s\S]*?)<\/package>/g;
167-
let pkgMatch;
168-
while ((pkgMatch = pkgRe.exec(xml)) !== null) {
169-
const pkgName = pkgMatch[1].replace(/\//g, '.');
170-
const pkgBody = pkgMatch[2];
171-
const classRe = /<class\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/class>/g;
172-
let classMatch;
173-
while ((classMatch = classRe.exec(pkgBody)) !== null) {
174-
const className = classMatch[1].replace(/\//g, '.');
175-
const classBody = classMatch[2];
176-
const counters = { line: { ...zeroCov }, branch: { ...zeroCov }, method: { ...zeroCov } };
177-
const cntRe = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
178-
let cntMatch;
179-
while ((cntMatch = cntRe.exec(classBody)) !== null) {
180-
const entry = { covered: parseInt(cntMatch[3]), missed: parseInt(cntMatch[2]) };
181-
if (cntMatch[1] === 'LINE') counters.line = entry;
182-
else if (cntMatch[1] === 'BRANCH') counters.branch = entry;
183-
else if (cntMatch[1] === 'METHOD') counters.method = entry;
184-
}
185-
// Skip classes with 0 total lines (interfaces, annotations, abstract classes)
186-
if (counters.line.covered + counters.line.missed > 0) {
187-
coverage.classes[className] = counters;
188-
}
189-
}
190-
}
191-
}
151+
const coverage = parseJacocoXml(jacocoFile) || { overall: {}, classes: {} };
192152
193153
const updated = { tests, coverage };
194154
fs.writeFileSync(baselineFile, JSON.stringify(updated, null, 2) + '\n');

.github/workflows/pr-report.yml

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ jobs:
6464
}
6565
console.log(`Posting report for PR #${prNumber}`);
6666
67+
const { parseJacocoXml, pct, zeroCov } = require('./.github/scripts/parse-jacoco.js');
68+
6769
const versions = ['java11', 'java17', 'java21', 'java25', 'jcstress'];
6870
const zeroTest = { total: 0, passed: 0, failed: 0, errors: 0, skipped: 0 };
69-
const zeroCov = { covered: 0, missed: 0 };
7071
7172
// --- Read current test stats from artifacts ---
7273
const current = {};
@@ -90,44 +91,12 @@ jobs:
9091
const baseClasses = (baseline.coverage || {}).classes || {};
9192
9293
// --- Parse JaCoCo XML for coverage ---
93-
let covLine = null, covBranch = null, covMethod = null;
94-
const classCounters = {};
9594
const jacocoFile = path.join('coverage', 'jacocoTestReport.xml');
96-
if (fs.existsSync(jacocoFile)) {
97-
const xml = fs.readFileSync(jacocoFile, 'utf8');
98-
const stripped = xml.replace(/<package[\s\S]*?<\/package>/g, '');
99-
const re = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
100-
let m;
101-
while ((m = re.exec(stripped)) !== null) {
102-
const entry = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
103-
if (m[1] === 'LINE') covLine = entry;
104-
else if (m[1] === 'BRANCH') covBranch = entry;
105-
else if (m[1] === 'METHOD') covMethod = entry;
106-
}
107-
108-
const pkgRe = /<package\s+name="([^"]+)">([\s\S]*?)<\/package>/g;
109-
let pkgMatch;
110-
while ((pkgMatch = pkgRe.exec(xml)) !== null) {
111-
const pkgName = pkgMatch[1].replace(/\//g, '.');
112-
const pkgBody = pkgMatch[2];
113-
const classRe = /<class\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/class>/g;
114-
let classMatch;
115-
while ((classMatch = classRe.exec(pkgBody)) !== null) {
116-
const className = classMatch[1].replace(/\//g, '.');
117-
const classBody = classMatch[2];
118-
const counters = { line: { ...zeroCov }, branch: { ...zeroCov }, method: { ...zeroCov } };
119-
const cntRe = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
120-
let cntMatch;
121-
while ((cntMatch = cntRe.exec(classBody)) !== null) {
122-
const entry = { covered: parseInt(cntMatch[3]), missed: parseInt(cntMatch[2]) };
123-
if (cntMatch[1] === 'LINE') counters.line = entry;
124-
else if (cntMatch[1] === 'BRANCH') counters.branch = entry;
125-
else if (cntMatch[1] === 'METHOD') counters.method = entry;
126-
}
127-
classCounters[className] = counters;
128-
}
129-
}
130-
}
95+
const parsed = parseJacocoXml(jacocoFile);
96+
const covLine = parsed?.overall?.line || null;
97+
const covBranch = parsed?.overall?.branch || null;
98+
const covMethod = parsed?.overall?.method || null;
99+
const classCounters = parsed?.classes || {};
131100
132101
// --- Helpers ---
133102
function delta(curr, prev, positiveIsGood) {
@@ -143,11 +112,6 @@ jobs:
143112
return `${curr} (${delta(curr, prev, positiveIsGood)})`;
144113
}
145114
146-
function pct(covered, missed) {
147-
const total = covered + missed;
148-
return total === 0 ? 0 : (covered / total * 100);
149-
}
150-
151115
function fmtPct(value) {
152116
return value.toFixed(1) + '%';
153117
}

0 commit comments

Comments
 (0)
X Tutup