diff --git a/.github/workflows/chained_e2e.yml b/.github/workflows/chained_e2e.yml index 4a5de8bf7c..5e208e7e20 100644 --- a/.github/workflows/chained_e2e.yml +++ b/.github/workflows/chained_e2e.yml @@ -184,7 +184,7 @@ jobs: needs: - 'merge_queue_skipper' - 'parse_run_context' - runs-on: 'macos-latest-large' + runs-on: 'macos-latest' if: | github.repository == 'google-gemini/gemini-cli' && always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip != 'true') steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ef8bdb58d..54cbd2c001 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -230,8 +230,8 @@ jobs: path: 'packages/*/junit.xml' test_mac: - name: 'Test (Mac) - ${{ matrix.node-version }}, ${{ matrix.shard }}' - runs-on: 'macos-latest-large' + name: 'Test (Mac) - ${{ matrix.shard }}' + runs-on: 'macos-latest' needs: - 'merge_queue_skipper' if: "github.repository == 'google-gemini/gemini-cli' && needs.merge_queue_skipper.outputs.skip == 'false'" @@ -242,10 +242,6 @@ jobs: continue-on-error: true strategy: matrix: - node-version: - - '20.x' - - '22.x' - - '24.x' shard: - 'cli' - 'others' @@ -253,10 +249,10 @@ jobs: - name: 'Checkout' uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - - name: 'Set up Node.js ${{ matrix.node-version }}' + - name: 'Set up Node.js 20.x' uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 with: - node-version: '${{ matrix.node-version }}' + node-version: '20.x' cache: 'npm' - name: 'Build project' diff --git a/.github/workflows/deflake.yml b/.github/workflows/deflake.yml index a6a7d3664f..c58d9120f3 100644 --- a/.github/workflows/deflake.yml +++ b/.github/workflows/deflake.yml @@ -78,7 +78,7 @@ jobs: deflake_e2e_mac: name: 'E2E Test (macOS)' - runs-on: 'macos-latest-large' + runs-on: 'macos-latest' if: "github.repository == 'google-gemini/gemini-cli'" steps: - name: 'Checkout' diff --git a/tools/gemini-cli-bot/metrics/scripts/latency.ts b/tools/gemini-cli-bot/metrics/scripts/latency.ts index 7dd5dcd1f6..3dee0d05e9 100644 --- a/tools/gemini-cli-bot/metrics/scripts/latency.ts +++ b/tools/gemini-cli-bot/metrics/scripts/latency.ts @@ -2,41 +2,49 @@ * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 - * - * @license */ import { GITHUB_OWNER, GITHUB_REPO } from '../types.js'; import { execSync } from 'node:child_process'; try { + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); + const query = ` - query($owner: String!, $repo: String!) { + query($owner: String!, $repo: String!, $issueQuery: String!, $prQuery: String!) { repository(owner: $owner, name: $repo) { - pullRequests(last: 100, states: MERGED) { + issues: search(query: $issueQuery, type: ISSUE, first: 100) { nodes { - authorAssociation - createdAt - mergedAt + ... on Issue { + authorAssociation + createdAt + closedAt + } } } - issues(last: 100, states: CLOSED) { + prs: search(query: $prQuery, type: ISSUE, first: 100) { nodes { - authorAssociation - createdAt - closedAt + ... on PullRequest { + authorAssociation + createdAt + mergedAt + } } } } } `; + + const issueQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:issue is:closed closed:>${sevenDaysAgo.split('T')[0]} sort:closed-desc`; + const prQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:pr is:merged merged:>${sevenDaysAgo.split('T')[0]} sort:merged-desc`; + const output = execSync( - `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`, + `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -F issueQuery='${issueQuery}' -F prQuery='${prQuery}' -f query='${query}'`, { encoding: 'utf-8' }, ); const data = JSON.parse(output).data.repository; - const prs = data.pullRequests.nodes.map( + const prs = data.prs.nodes.map( (p: { authorAssociation: string; mergedAt: string; @@ -96,24 +104,12 @@ try { ); const issueOverall = calculateAvg(issues); - process.stdout.write( - `latency_pr_overall_hours,${Math.round(prOverall * 100) / 100}\n`, - ); - process.stdout.write( - `latency_pr_maintainers_hours,${Math.round(prMaintainers * 100) / 100}\n`, - ); - process.stdout.write( - `latency_pr_community_hours,${Math.round(prCommunity * 100) / 100}\n`, - ); - process.stdout.write( - `latency_issue_overall_hours,${Math.round(issueOverall * 100) / 100}\n`, - ); - process.stdout.write( - `latency_issue_maintainers_hours,${Math.round(issueMaintainers * 100) / 100}\n`, - ); - process.stdout.write( - `latency_issue_community_hours,${Math.round(issueCommunity * 100) / 100}\n`, - ); + process.stdout.write(`latency_pr_overall_hours,${Math.round(prOverall * 100) / 100}\n`); + process.stdout.write(`latency_pr_maintainers_hours,${Math.round(prMaintainers * 100) / 100}\n`); + process.stdout.write(`latency_pr_community_hours,${Math.round(prCommunity * 100) / 100}\n`); + process.stdout.write(`latency_issue_overall_hours,${Math.round(issueOverall * 100) / 100}\n`); + process.stdout.write(`latency_issue_maintainers_hours,${Math.round(issueMaintainers * 100) / 100}\n`); + process.stdout.write(`latency_issue_community_hours,${Math.round(issueCommunity * 100) / 100}\n`); } catch (err) { process.stderr.write(err instanceof Error ? err.message : String(err)); process.exit(1); diff --git a/tools/gemini-cli-bot/metrics/scripts/throughput.ts b/tools/gemini-cli-bot/metrics/scripts/throughput.ts index 3806dd407a..daa378cfa1 100644 --- a/tools/gemini-cli-bot/metrics/scripts/throughput.ts +++ b/tools/gemini-cli-bot/metrics/scripts/throughput.ts @@ -2,118 +2,71 @@ * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 - * - * @license */ import { GITHUB_OWNER, GITHUB_REPO } from '../types.js'; import { execSync } from 'node:child_process'; try { + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); + const query = ` - query($owner: String!, $repo: String!) { + query($owner: String!, $repo: String!, $issueQuery: String!, $prQuery: String!) { repository(owner: $owner, name: $repo) { - pullRequests(last: 100, states: MERGED) { + issues: search(query: $issueQuery, type: ISSUE, first: 100) { + issueCount nodes { - authorAssociation - mergedAt + ... on Issue { + authorAssociation + closedAt + } } } - issues(last: 100, states: CLOSED) { + prs: search(query: $prQuery, type: ISSUE, first: 100) { + issueCount nodes { - authorAssociation - closedAt + ... on PullRequest { + authorAssociation + mergedAt + } } } } } `; + + const issueQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:issue is:closed closed:>${sevenDaysAgo.split('T')[0]} sort:closed-desc`; + const prQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:pr is:merged merged:>${sevenDaysAgo.split('T')[0]} sort:merged-desc`; + const output = execSync( - `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`, + `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -F issueQuery='${issueQuery}' -F prQuery='${prQuery}' -f query='${query}'`, { encoding: 'utf-8' }, ); const data = JSON.parse(output).data.repository; - const prs = data.pullRequests.nodes - .map((p: { authorAssociation: string; mergedAt: string }) => ({ - association: p.authorAssociation, - date: new Date(p.mergedAt).getTime(), - })) - .sort((a: { date: number }, b: { date: number }) => a.date - b.date); - - const issues = data.issues.nodes - .map((i: { authorAssociation: string; closedAt: string }) => ({ - association: i.authorAssociation, - date: new Date(i.closedAt).getTime(), - })) - .sort((a: { date: number }, b: { date: number }) => a.date - b.date); - const isMaintainer = (assoc: string) => ['MEMBER', 'OWNER', 'COLLABORATOR'].includes(assoc); - const calculateThroughput = ( - items: { association: string; date: number }[], - ) => { - if (items.length < 2) return 0; - const first = items[0].date; - const last = items[items.length - 1].date; - const days = (last - first) / (1000 * 60 * 60 * 24); - return days > 0 ? items.length / days : items.length; // items per day - }; + const issueNodes = data.issues.nodes; + const prNodes = data.prs.nodes; - const prOverall = calculateThroughput(prs); - const prMaintainers = calculateThroughput( - prs.filter((i: { association: string; date: number }) => - isMaintainer(i.association), - ), - ); - const prCommunity = calculateThroughput( - prs.filter( - (i: { association: string; date: number }) => - !isMaintainer(i.association), - ), - ); + const issueOverall = data.issues.issueCount / 7; + const issueMaintainers = (issueNodes.filter((i: any) => isMaintainer(i.authorAssociation)).length / Math.min(issueNodes.length || 1, 100)) * issueOverall; + const issueCommunity = issueOverall - issueMaintainers; - const issueOverall = calculateThroughput(issues); - const issueMaintainers = calculateThroughput( - issues.filter((i: { association: string; date: number }) => - isMaintainer(i.association), - ), - ); - const issueCommunity = calculateThroughput( - issues.filter( - (i: { association: string; date: number }) => - !isMaintainer(i.association), - ), - ); + const prOverall = data.prs.issueCount / 7; + const prMaintainers = (prNodes.filter((p: any) => isMaintainer(p.authorAssociation)).length / Math.min(prNodes.length || 1, 100)) * prOverall; + const prCommunity = prOverall - prMaintainers; - process.stdout.write( - `throughput_pr_overall_per_day,${Math.round(prOverall * 100) / 100}\n`, - ); - process.stdout.write( - `throughput_pr_maintainers_per_day,${Math.round(prMaintainers * 100) / 100}\n`, - ); - process.stdout.write( - `throughput_pr_community_per_day,${Math.round(prCommunity * 100) / 100}\n`, - ); - process.stdout.write( - `throughput_issue_overall_per_day,${Math.round(issueOverall * 100) / 100}\n`, - ); - process.stdout.write( - `throughput_issue_maintainers_per_day,${Math.round(issueMaintainers * 100) / 100}\n`, - ); - process.stdout.write( - `throughput_issue_community_per_day,${Math.round(issueCommunity * 100) / 100}\n`, - ); - process.stdout.write( - `throughput_issue_overall_days_per_issue,${issueOverall > 0 ? Math.round((1 / issueOverall) * 100) / 100 : 0}\n`, - ); - process.stdout.write( - `throughput_issue_maintainers_days_per_issue,${issueMaintainers > 0 ? Math.round((1 / issueMaintainers) * 100) / 100 : 0}\n`, - ); - process.stdout.write( - `throughput_issue_community_days_per_issue,${issueCommunity > 0 ? Math.round((1 / issueCommunity) * 100) / 100 : 0}\n`, - ); + process.stdout.write(`throughput_pr_overall_per_day,${Math.round(prOverall * 100) / 100}\n`); + process.stdout.write(`throughput_pr_maintainers_per_day,${Math.round(prMaintainers * 100) / 100}\n`); + process.stdout.write(`throughput_pr_community_per_day,${Math.round(prCommunity * 100) / 100}\n`); + process.stdout.write(`throughput_issue_overall_per_day,${Math.round(issueOverall * 100) / 100}\n`); + process.stdout.write(`throughput_issue_maintainers_per_day,${Math.round(issueMaintainers * 100) / 100}\n`); + process.stdout.write(`throughput_issue_community_per_day,${Math.round(issueCommunity * 100) / 100}\n`); + process.stdout.write(`throughput_issue_overall_days_per_issue,${issueOverall > 0 ? Math.round((1 / issueOverall) * 100) / 100 : 0}\n`); + process.stdout.write(`throughput_issue_maintainers_days_per_issue,${issueMaintainers > 0 ? Math.round((1 / issueMaintainers) * 100) / 100 : 0}\n`); + process.stdout.write(`throughput_issue_community_days_per_issue,${issueCommunity > 0 ? Math.round((1 / issueCommunity) * 100) / 100 : 0}\n`); } catch (err) { process.stderr.write(err instanceof Error ? err.message : String(err)); process.exit(1); diff --git a/tools/gemini-cli-bot/metrics/scripts/user_touches.ts b/tools/gemini-cli-bot/metrics/scripts/user_touches.ts index 5ccffa94fc..3d4cfeba40 100644 --- a/tools/gemini-cli-bot/metrics/scripts/user_touches.ts +++ b/tools/gemini-cli-bot/metrics/scripts/user_touches.ts @@ -2,40 +2,48 @@ * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 - * - * @license */ import { GITHUB_OWNER, GITHUB_REPO } from '../types.js'; import { execSync } from 'node:child_process'; try { + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); + const query = ` - query($owner: String!, $repo: String!) { + query($owner: String!, $repo: String!, $issueQuery: String!, $prQuery: String!) { repository(owner: $owner, name: $repo) { - pullRequests(last: 100, states: MERGED) { + issues: search(query: $issueQuery, type: ISSUE, first: 100) { nodes { - authorAssociation - comments { totalCount } - reviews { totalCount } + ... on Issue { + authorAssociation + comments { totalCount } + } } } - issues(last: 100, states: CLOSED) { + prs: search(query: $prQuery, type: ISSUE, first: 100) { nodes { - authorAssociation - comments { totalCount } + ... on PullRequest { + authorAssociation + comments { totalCount } + reviews { totalCount } + } } } } } `; + + const issueQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:issue is:closed closed:>${sevenDaysAgo.split('T')[0]} sort:closed-desc`; + const prQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:pr is:merged merged:>${sevenDaysAgo.split('T')[0]} sort:merged-desc`; + const output = execSync( - `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`, + `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -F issueQuery='${issueQuery}' -F prQuery='${prQuery}' -f query='${query}'`, { encoding: 'utf-8' }, ); const data = JSON.parse(output).data.repository; - const prs = data.pullRequests.nodes; + const prs = data.prs.nodes; const issues = data.issues.nodes; const allItems = [ @@ -71,15 +79,9 @@ try { allItems.filter((i) => !isMaintainer(i.association)), ); - process.stdout.write( - `user_touches_overall,${Math.round(overall * 100) / 100}\n`, - ); - process.stdout.write( - `user_touches_maintainers,${Math.round(maintainers * 100) / 100}\n`, - ); - process.stdout.write( - `user_touches_community,${Math.round(community * 100) / 100}\n`, - ); + process.stdout.write(`user_touches_overall,${Math.round(overall * 100) / 100}\n`); + process.stdout.write(`user_touches_maintainers,${Math.round(maintainers * 100) / 100}\n`); + process.stdout.write(`user_touches_community,${Math.round(community * 100) / 100}\n`); } catch (err) { process.stderr.write(err instanceof Error ? err.message : String(err)); process.exit(1);