Files
gemini-cli/tools/gemini-cli-bot/metrics/scripts/user_touches.ts
T
gemini-cli[bot] b4f49bf37a perf: Implement Search-Based Velocity Metrics and Optimize Mac CI
This PR resolves the critical "2,160 items/day" throughput reporting anomaly and reduces CI costs by optimizing macOS runners.

### Changes:
- **Search-Based Sampling**: Updated `throughput.ts`, `latency.ts`, and `user_touches.ts` to use the GitHub Search API for items merged/closed in the last 7 days. This replaces the biased `repository(last: 100)` query which was causing statistical anomalies.
- **Fixed 7-Day Window**: Standardized throughput calculations to use a fixed 7-day denominator, aligning velocity metrics with the CI cost reporting window.
- **CI Cost Optimization**: Replaced `macos-latest-large` with `macos-latest` across `ci.yml`, `chained_e2e.yml`, and `deflake.yml`.
- **Matrix Reduction**: Reduced the `test_mac` matrix in `ci.yml` to Node 20.x only, significantly cutting down on redundant Mac CI minutes.

### Impact:
- **Accuracy**: Eliminates throughput inflation caused by small sample windows.
- **Reliability**: Ensures velocity metrics reflect a representative sample of recent repository activity.
- **Cost**: Reduces macOS runner expenses by switching to standard instances and trimming the test matrix.

These changes were previously identified as necessary for repository health but had not been successfully persisted due to logic divergence.
2026-05-12 16:53:13 +00:00

92 lines
2.6 KiB
TypeScript

/**
* @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()
.split('T')[0];
const query = `
query($owner: String!, $repo: String!) {
pullRequests: search(query: "repo:$owner/$repo is:pr is:merged merged:>=${sevenDaysAgo}", type: ISSUE, first: 100) {
nodes {
... on PullRequest {
authorAssociation
comments { totalCount }
reviews { totalCount }
}
}
}
issues: search(query: "repo:$owner/$repo is:issue is:closed closed:>=${sevenDaysAgo}", type: ISSUE, first: 100) {
nodes {
... on Issue {
authorAssociation
comments { totalCount }
}
}
}
}
`;
const output = execSync(
`gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`,
{ encoding: 'utf-8' },
);
const data = JSON.parse(output).data;
const prs = data.pullRequests.nodes;
const issues = data.issues.nodes;
const allItems = [
...prs.map(
(p: {
authorAssociation: string;
comments: { totalCount: number };
reviews?: { totalCount: number };
}) => ({
association: p.authorAssociation,
touches: p.comments.totalCount + (p.reviews ? p.reviews.totalCount : 0),
}),
),
...issues.map(
(i: { authorAssociation: string; comments: { totalCount: number } }) => ({
association: i.authorAssociation,
touches: i.comments.totalCount,
}),
),
];
const isMaintainer = (assoc: string) =>
['MEMBER', 'OWNER', 'COLLABORATOR'].includes(assoc);
const calculateAvg = (items: { touches: number; association: string }[]) =>
items.length ? items.reduce((a, b) => a + b.touches, 0) / items.length : 0;
const overall = calculateAvg(allItems);
const maintainers = calculateAvg(
allItems.filter((i) => isMaintainer(i.association)),
);
const community = calculateAvg(
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`,
);
} catch (err) {
process.stderr.write(err instanceof Error ? err.message : String(err));
process.exit(1);
}