mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Improve PR template and breaking-change label automation (#6473)
* Improve PR template and breaking-change label automation - Add a structured "Related Issue" section using GitHub closing keywords - Add a Review Guide prompt (major changes, impact, reviewer focus) with a note that the focus item is for human reviewers only - Add checklist items for issue linkage / no duplicate PRs and invert the breaking-change item (checked = not breaking) - Extend label-title-prefix to prepend [BREAKING] when the "breaking change" label is added - Add label-breaking-change workflow to apply the "breaking change" label when a PR title contains [BREAKING] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add pull-requests agent skill with dotnet/python links - Add root .github/skills/pull-requests/SKILL.md covering PR description authoring (following the PR template) and the review-comment workflow (review -> plan -> user review -> implement -> reply to all -> resolve) - Symlink the skill from python/.github/skills and dotnet/.github/skills - Reference the skill from python/AGENTS.md and dotnet/AGENTS.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fold breaking-change labeling into label-pr workflow Move the title -> 'breaking change' label logic into the existing label-pr workflow (which already applies the python/.NET labels) and drop the separate label-breaking-change workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR title prefix review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Pin patched MessagePack for .NET restore Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert MessagePack central pin Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move title prefix tests out of tracked GitHub tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Exclude skill docs from CI path filters Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Match skill symlinks in CI path exclusions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Exclude AGENTS docs from CI path filters Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Scope title-prefix normalization to a real prefix The normalization branch in addTitlePrefix matched ^Python (no colon), so titles like "Python samples improvements" or "Pythonic refactor" were treated as already-prefixed and only re-cased, never receiving the "Python: " prefix. Scope the match to ^<prefix>:\s* so only an actual existing prefix is normalized; otherwise the prefix is prepended. Same fix applies to the .NET prefix (e.g. ".NETStandard bump"). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
d7e8d2206d
commit
7e9c043c4c
@@ -1,23 +1,43 @@
|
|||||||
### Motivation and Context
|
### Motivation & Context
|
||||||
|
|
||||||
<!-- Thank you for your contribution to the Agent Framework repo!
|
<!-- Thank you for your contribution to the Agent Framework repo!
|
||||||
Please help reviewers and future users, providing the following information:
|
Please help reviewers and future users, providing the following information:
|
||||||
1. Why is this change required?
|
1. Why is this change required?
|
||||||
2. What problem does it solve?
|
2. What problem does it solve?
|
||||||
3. What scenario does it contribute to?
|
3. What scenario does it contribute to?
|
||||||
4. If it fixes an open issue, please link to the issue here.
|
4. If it fixes an open issue, please link to the issue below.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Description
|
### Description & Review Guide
|
||||||
|
|
||||||
<!-- Describe your changes, the overall approach, the underlying design.
|
<!-- Describe your changes, the overall approach, the underlying design.
|
||||||
|
Highlight what you want the reviewers to focus on.
|
||||||
These notes will help understanding how your code works. Thanks! -->
|
These notes will help understanding how your code works. Thanks! -->
|
||||||
|
|
||||||
|
- **What are the major changes?**
|
||||||
|
- **What is the impact of these changes?**
|
||||||
|
- **What do you want reviewers to focus on?**
|
||||||
|
<!-- NOTE FOR AUTOMATED/AI REVIEWERS: the "What do you want reviewers to focus on?"
|
||||||
|
item above is intended for human reviewers only. Automated/AI reviewers should
|
||||||
|
ignore it and review the entire change rather than narrowing scope to it. -->
|
||||||
|
|
||||||
|
|
||||||
|
### Related Issue
|
||||||
|
|
||||||
|
<!-- Which issue does this PR fix? Link it using a GitHub closing keyword so it is
|
||||||
|
closed automatically when this PR is merged, e.g. "Fixes #123" or "Closes #123".
|
||||||
|
PRs that are not linked to an issue may be closed, no matter how valid the change is.
|
||||||
|
Also check whether an open PR already exists for this issue; if so,
|
||||||
|
explain how this PR is different. -->
|
||||||
|
|
||||||
|
Fixes #
|
||||||
|
|
||||||
### Contribution Checklist
|
### Contribution Checklist
|
||||||
|
|
||||||
<!-- Before submitting this PR, please make sure: -->
|
<!-- Before submitting this PR, please make sure: -->
|
||||||
|
|
||||||
- [ ] The code builds clean without any errors or warnings
|
- [ ] The code builds clean without any errors or warnings
|
||||||
- [ ] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
|
|
||||||
- [ ] All unit tests pass, and I have added new tests where possible
|
- [ ] All unit tests pass, and I have added new tests where possible
|
||||||
- [ ] **Is this a breaking change?** If yes, add "[BREAKING]" prefix to the title of the PR.
|
- [ ] The PR follows the [Contribution Guidelines](https://github.com/microsoft/agent-framework/blob/main/CONTRIBUTING.md)
|
||||||
|
- [ ] This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
|
||||||
|
- [x] **This is not a breaking change.** If it _is_ a breaking change, add the `breaking change` label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
|
||||||
|
const BREAKING_CHANGE_LABEL = 'breaking change';
|
||||||
|
const BREAKING_PREFIX = '[BREAKING]';
|
||||||
|
|
||||||
|
const DEFAULT_PREFIX_LABELS = Object.freeze({
|
||||||
|
python: 'Python',
|
||||||
|
'.NET': '.NET',
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_BRACKET_PREFIX_LABELS = Object.freeze({
|
||||||
|
[BREAKING_CHANGE_LABEL]: BREAKING_PREFIX,
|
||||||
|
});
|
||||||
|
|
||||||
|
function escapeRegExp(value) {
|
||||||
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMatchingValueByKey(valuesByKey, keyToFind) {
|
||||||
|
const matchingKey = Object.keys(valuesByKey).find((key) => key.toLowerCase() === keyToFind.toLowerCase());
|
||||||
|
return matchingKey === undefined ? null : valuesByKey[matchingKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrefixPattern(prefixes) {
|
||||||
|
return prefixes.map(escapeRegExp).join('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalizePrefix(prefix, prefixes) {
|
||||||
|
return prefixes.find((knownPrefix) => knownPrefix.toLowerCase() === prefix.toLowerCase()) ?? prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLeadingBracketPrefix(title, bracketPrefixes) {
|
||||||
|
const bracketPattern = getPrefixPattern(bracketPrefixes);
|
||||||
|
if (!bracketPattern) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leadingBracketPrefix = new RegExp(`^(${bracketPattern})(?=\\s|$)`, 'i');
|
||||||
|
return title.replace(
|
||||||
|
leadingBracketPrefix,
|
||||||
|
(bracketPrefix) => canonicalizePrefix(bracketPrefix, bracketPrefixes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLeadingTitlePrefix(title, titlePrefixes) {
|
||||||
|
const titlePrefixPattern = getPrefixPattern(titlePrefixes);
|
||||||
|
if (!titlePrefixPattern) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = title.match(new RegExp(`^(${titlePrefixPattern}):\\s*`, 'i'));
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefix: canonicalizePrefix(match[1], titlePrefixes),
|
||||||
|
rest: title.slice(match[0].length).trimStart(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBracketPrefixToken(title, bracketPrefix) {
|
||||||
|
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
|
||||||
|
return title
|
||||||
|
.replace(new RegExp(`(^|\\s+)${bracketPrefixPattern}(?=\\s|$)`, 'ig'), '$1')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTitlePrefix(title, prefix, bracketPrefixes = Object.values(DEFAULT_BRACKET_PREFIX_LABELS)) {
|
||||||
|
const bracketPattern = getPrefixPattern(bracketPrefixes);
|
||||||
|
const prefixPattern = escapeRegExp(prefix);
|
||||||
|
|
||||||
|
if (bracketPattern) {
|
||||||
|
const bracketThenTitlePrefix = new RegExp(`^(${bracketPattern})(\\s+)(${prefixPattern})(?=:)`, 'i');
|
||||||
|
if (bracketThenTitlePrefix.test(title)) {
|
||||||
|
return title.replace(
|
||||||
|
bracketThenTitlePrefix,
|
||||||
|
(match, bracketPrefix, spacing) => `${canonicalizePrefix(bracketPrefix, bracketPrefixes)}${spacing}${prefix}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
title = normalizeLeadingBracketPrefix(title, bracketPrefixes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!title.startsWith(`${prefix}: `)) {
|
||||||
|
const existingTitlePrefix = new RegExp(`^${prefixPattern}:\\s*`, 'i');
|
||||||
|
if (existingTitlePrefix.test(title)) {
|
||||||
|
return title.replace(existingTitlePrefix, `${prefix}: `);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${prefix}: ${title}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasBracketPrefix(title, bracketPrefix, titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS)) {
|
||||||
|
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
|
||||||
|
const leadingBracketPrefix = new RegExp(`^${bracketPrefixPattern}(?=\\s|$)`, 'i');
|
||||||
|
if (leadingBracketPrefix.test(title)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leadingTitlePrefix = parseLeadingTitlePrefix(title, titlePrefixes);
|
||||||
|
if (!leadingTitlePrefix) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return leadingBracketPrefix.test(leadingTitlePrefix.rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBracketPrefix(title, bracketPrefix, titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS)) {
|
||||||
|
const bracketPrefixPattern = escapeRegExp(bracketPrefix);
|
||||||
|
const leadingBracketPrefix = new RegExp(`^${bracketPrefixPattern}(?=\\s|$)`, 'i');
|
||||||
|
if (leadingBracketPrefix.test(title)) {
|
||||||
|
return title.replace(leadingBracketPrefix, bracketPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
const leadingTitlePrefix = parseLeadingTitlePrefix(title, titlePrefixes);
|
||||||
|
if (leadingTitlePrefix) {
|
||||||
|
if (leadingBracketPrefix.test(leadingTitlePrefix.rest)) {
|
||||||
|
const normalizedRest = leadingTitlePrefix.rest.replace(leadingBracketPrefix, bracketPrefix);
|
||||||
|
return `${leadingTitlePrefix.prefix}: ${normalizedRest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleWithoutBracketPrefix = removeBracketPrefixToken(leadingTitlePrefix.rest, bracketPrefix);
|
||||||
|
return `${leadingTitlePrefix.prefix}: ${bracketPrefix}`
|
||||||
|
+ (titleWithoutBracketPrefix ? ` ${titleWithoutBracketPrefix}` : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleWithoutBracketPrefix = removeBracketPrefixToken(title, bracketPrefix);
|
||||||
|
return `${bracketPrefix}${titleWithoutBracketPrefix ? ` ${titleWithoutBracketPrefix}` : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasLabel(labels, labelName) {
|
||||||
|
return labels.some((label) => label.toLowerCase() === labelName.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentTitle(context) {
|
||||||
|
switch (context.eventName) {
|
||||||
|
case 'issues':
|
||||||
|
return context.payload.issue.title;
|
||||||
|
case 'pull_request_target':
|
||||||
|
return context.payload.pull_request.title;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognized eventName: ${context.eventName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateTitleForAddedLabel({
|
||||||
|
github,
|
||||||
|
context,
|
||||||
|
core,
|
||||||
|
prefixLabels = DEFAULT_PREFIX_LABELS,
|
||||||
|
bracketPrefixLabels = DEFAULT_BRACKET_PREFIX_LABELS,
|
||||||
|
}) {
|
||||||
|
const labelAdded = context.payload.label?.name;
|
||||||
|
if (!labelAdded) {
|
||||||
|
throw new Error('This script must be run from a labeled event.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTitle = getCurrentTitle(context);
|
||||||
|
let newTitle = null;
|
||||||
|
|
||||||
|
const titlePrefix = getMatchingValueByKey(prefixLabels, labelAdded);
|
||||||
|
if (titlePrefix !== null) {
|
||||||
|
newTitle = addTitlePrefix(currentTitle, titlePrefix, Object.values(bracketPrefixLabels));
|
||||||
|
}
|
||||||
|
|
||||||
|
const bracketPrefix = getMatchingValueByKey(bracketPrefixLabels, labelAdded);
|
||||||
|
if (bracketPrefix !== null) {
|
||||||
|
newTitle = addBracketPrefix(currentTitle, bracketPrefix, Object.values(prefixLabels));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTitle === null) {
|
||||||
|
core.info(`No title prefix configured for label "${labelAdded}".`);
|
||||||
|
return { updated: false, newTitle: currentTitle };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTitle === currentTitle) {
|
||||||
|
core.info(`Title already includes the prefix for label "${labelAdded}".`);
|
||||||
|
return { updated: false, newTitle };
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (context.eventName) {
|
||||||
|
case 'issues':
|
||||||
|
await github.rest.issues.update({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
title: newTitle,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pull_request_target':
|
||||||
|
await github.rest.pulls.update({
|
||||||
|
pull_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
title: newTitle,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognized eventName: ${context.eventName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { updated: true, newTitle };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncBreakingChangeLabelFromTitle({
|
||||||
|
github,
|
||||||
|
context,
|
||||||
|
core,
|
||||||
|
labelName = BREAKING_CHANGE_LABEL,
|
||||||
|
bracketPrefix = BREAKING_PREFIX,
|
||||||
|
titlePrefixes = Object.values(DEFAULT_PREFIX_LABELS),
|
||||||
|
}) {
|
||||||
|
const pullRequest = context.payload.pull_request;
|
||||||
|
if (!pullRequest) {
|
||||||
|
throw new Error('This script must be run from a pull_request_target event.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = pullRequest.title || '';
|
||||||
|
if (!hasBracketPrefix(title, bracketPrefix, titlePrefixes)) {
|
||||||
|
core.info(`Title does not include ${bracketPrefix} in the title prefix.`);
|
||||||
|
return { added: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = pullRequest.labels?.map((label) => label.name).filter(Boolean) ?? [];
|
||||||
|
if (hasLabel(labels, labelName)) {
|
||||||
|
core.info(`PR already has the "${labelName}" label.`);
|
||||||
|
return { added: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
labels: [labelName],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { added: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addBracketPrefix,
|
||||||
|
addTitlePrefix,
|
||||||
|
hasBracketPrefix,
|
||||||
|
syncBreakingChangeLabelFromTitle,
|
||||||
|
updateTitleForAddedLabel,
|
||||||
|
};
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
---
|
||||||
|
name: pull-requests
|
||||||
|
description: >
|
||||||
|
Guidance for creating pull requests and handling PR review comments in the
|
||||||
|
Agent Framework repository. Use this when writing a PR description (filling out
|
||||||
|
the PR template) or when responding to and resolving review comments on an
|
||||||
|
existing PR.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pull Request Workflow
|
||||||
|
|
||||||
|
This skill covers two tasks: (1) writing a high-quality PR description, and
|
||||||
|
(2) handling review comments on an existing PR.
|
||||||
|
|
||||||
|
## 1. Writing the PR description
|
||||||
|
|
||||||
|
Always follow the repository PR template at
|
||||||
|
[`.github/pull_request_template.md`](../../pull_request_template.md). Keep its
|
||||||
|
exact structure and headings. Fill every section:
|
||||||
|
|
||||||
|
### `### Motivation & Context`
|
||||||
|
Explain *why* the change is needed: the problem it solves and the scenario it
|
||||||
|
contributes to. Describe the net change relative to `main` — this is implied, so
|
||||||
|
do **not** spell out "vs main" explicitly.
|
||||||
|
|
||||||
|
### `### Description & Review Guide`
|
||||||
|
Describe the changes, the overall approach, and the design. Answer the three
|
||||||
|
prompts:
|
||||||
|
- **What are the major changes?**
|
||||||
|
- **What is the impact of these changes?**
|
||||||
|
- **What do you want reviewers to focus on?** — This item is for **human
|
||||||
|
reviewers only**. Automated/AI reviewers must ignore it and review the entire
|
||||||
|
change rather than narrowing scope to it.
|
||||||
|
|
||||||
|
### `### Related Issue`
|
||||||
|
Link the issue the PR fixes using a GitHub closing keyword (`Fixes #123` /
|
||||||
|
`Closes #123`) so it closes automatically on merge. A PR with no linked issue may
|
||||||
|
be closed regardless of how valid the change is. Before opening, confirm there is
|
||||||
|
no other open PR for the same issue; if there is, explain how this PR differs.
|
||||||
|
|
||||||
|
### `### Contribution Checklist`
|
||||||
|
Check every item that applies. For the breaking-change item:
|
||||||
|
- Leave **"This is not a breaking change."** checked for the common case.
|
||||||
|
- If the change **is** breaking, add the `breaking change` label **or** put
|
||||||
|
`[BREAKING]` in the title prefix, before or after a language prefix such as
|
||||||
|
`Python:` or `.NET:` — workflows keep the label and the title prefix in sync
|
||||||
|
automatically (see `.github/workflows/label-title-prefix.yml` and
|
||||||
|
`.github/workflows/label-pr.yml`).
|
||||||
|
|
||||||
|
### Do not
|
||||||
|
- Do **not** add ad-hoc sections such as "Validation" or "Tests run"; CI/CD and
|
||||||
|
the checklist already cover validation status.
|
||||||
|
- Do **not** remove or reorder the template's headings.
|
||||||
|
|
||||||
|
### Creating the PR
|
||||||
|
Open new PRs as **drafts** until they are ready for review. Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh pr create --repo microsoft/agent-framework --base main \
|
||||||
|
--head <your-fork-owner>:<branch> --draft \
|
||||||
|
--title "<concise title>" --body "<body following the template>"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Handling review comments
|
||||||
|
|
||||||
|
When a PR receives review comments, follow this sequence — **do not start editing
|
||||||
|
code before the user has reviewed the plan**:
|
||||||
|
|
||||||
|
1. **Review the comments.** Read every review comment and thread on the PR,
|
||||||
|
including inline code comments and general review summaries.
|
||||||
|
2. **Make a plan.** Produce a concrete plan describing how each comment will be
|
||||||
|
addressed (or why it should not be, with reasoning).
|
||||||
|
3. **Let the user review the plan.** Present the plan and wait for the user's
|
||||||
|
approval or adjustments before implementing anything.
|
||||||
|
4. **Implement.** Make the agreed changes.
|
||||||
|
5. **Reply to every comment.** Add a reply to **all** comments explaining how it
|
||||||
|
was addressed (or the agreed outcome) — leave none unanswered.
|
||||||
|
6. **Resolve resolved threads.** Mark a review thread as resolved only when the
|
||||||
|
comment has actually been addressed.
|
||||||
|
|
||||||
|
### Useful commands
|
||||||
|
|
||||||
|
List review comments and threads:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Inline review comments
|
||||||
|
gh api repos/{owner}/{repo}/pulls/{pr}/comments
|
||||||
|
|
||||||
|
# Review threads with resolution state (GraphQL)
|
||||||
|
gh api graphql -f query='
|
||||||
|
query($owner:String!,$repo:String!,$pr:Int!){
|
||||||
|
repository(owner:$owner,name:$repo){
|
||||||
|
pullRequest(number:$pr){
|
||||||
|
reviewThreads(first:100){
|
||||||
|
nodes{ id isResolved comments(first:50){ nodes{ id body author{login} } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' -F owner={owner} -F repo={repo} -F pr={pr}
|
||||||
|
```
|
||||||
|
|
||||||
|
Reply to an inline review comment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
|
||||||
|
-f body="Addressed in <commit>: <explanation>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolve a review thread (needs the thread node id from the GraphQL query above):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh api graphql -f query='
|
||||||
|
mutation($threadId:ID!){
|
||||||
|
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
|
||||||
|
}' -F threadId={thread_id}
|
||||||
|
```
|
||||||
@@ -48,6 +48,10 @@ jobs:
|
|||||||
filters: |
|
filters: |
|
||||||
dotnet:
|
dotnet:
|
||||||
- 'dotnet/**'
|
- 'dotnet/**'
|
||||||
|
- '!dotnet/AGENTS.md'
|
||||||
|
- '!dotnet/**/AGENTS.md'
|
||||||
|
- '!dotnet/.github/skills/*'
|
||||||
|
- '!dotnet/.github/skills/**'
|
||||||
cosmosdb:
|
cosmosdb:
|
||||||
- 'dotnet/src/Microsoft.Agents.AI.CosmosNoSql/**'
|
- 'dotnet/src/Microsoft.Agents.AI.CosmosNoSql/**'
|
||||||
# The Foundry hosted-agent IT is costly (builds a container, pushes to ACR,
|
# The Foundry hosted-agent IT is costly (builds a container, pushes to ACR,
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ on:
|
|||||||
branches: ["main", "feature*"]
|
branches: ["main", "feature*"]
|
||||||
paths:
|
paths:
|
||||||
- dotnet/**
|
- dotnet/**
|
||||||
|
- '!dotnet/AGENTS.md'
|
||||||
|
- '!dotnet/**/AGENTS.md'
|
||||||
|
- '!dotnet/.github/skills/*'
|
||||||
|
- '!dotnet/.github/skills/**'
|
||||||
- '.github/workflows/dotnet-format.yml'
|
- '.github/workflows/dotnet-format.yml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -6,16 +6,34 @@
|
|||||||
# https://github.com/actions/labeler
|
# https://github.com/actions/labeler
|
||||||
|
|
||||||
name: Label pull request
|
name: Label pull request
|
||||||
on: [pull_request_target]
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize, reopened, edited]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
add_label:
|
add_label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6
|
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}"
|
repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}"
|
||||||
|
|
||||||
|
- name: Checkout scripts
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
sparse-checkout: .github/scripts
|
||||||
|
fetch-depth: 1
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: "PR: add breaking change label from title"
|
||||||
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
|
||||||
|
script: |
|
||||||
|
const { syncBreakingChangeLabelFromTitle } = require('./.github/scripts/title_prefix.js');
|
||||||
|
await syncBreakingChangeLabelFromTitle({ github, context, core });
|
||||||
|
|||||||
@@ -15,58 +15,17 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout scripts
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
sparse-checkout: .github/scripts
|
||||||
|
fetch-depth: 1
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
name: "Issue/PR: update title"
|
name: "Issue/PR: update title"
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
let prefixLabels = {
|
const { updateTitleForAddedLabel } = require('./.github/scripts/title_prefix.js');
|
||||||
"python": "Python",
|
await updateTitleForAddedLabel({ github, context, core });
|
||||||
".NET": ".NET"
|
|
||||||
};
|
|
||||||
|
|
||||||
function addTitlePrefix(title, prefix)
|
|
||||||
{
|
|
||||||
// Update the title based on the label and prefix
|
|
||||||
// Check if the title starts with the prefix (case-sensitive)
|
|
||||||
if (!title.startsWith(prefix + ": ")) {
|
|
||||||
// If not, check if the first word is the label (case-insensitive)
|
|
||||||
if (title.match(new RegExp(`^${prefix}`, 'i'))) {
|
|
||||||
// If yes, replace it with the prefix (case-sensitive)
|
|
||||||
title = title.replace(new RegExp(`^${prefix}`, 'i'), prefix);
|
|
||||||
} else {
|
|
||||||
// If not, prepend the prefix to the title
|
|
||||||
title = prefix + ": " + title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
labelAdded = context.payload.label.name
|
|
||||||
|
|
||||||
// Check if the issue or PR has the label
|
|
||||||
if (labelAdded in prefixLabels) {
|
|
||||||
let prefix = prefixLabels[labelAdded];
|
|
||||||
switch(context.eventName) {
|
|
||||||
case 'issues':
|
|
||||||
github.rest.issues.update({
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
title: addTitlePrefix(context.payload.issue.title, prefix)
|
|
||||||
});
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'pull_request_target':
|
|
||||||
github.rest.pulls.update({
|
|
||||||
pull_number: context.issue.number,
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
title: addTitlePrefix(context.payload.pull_request.title, prefix)
|
|
||||||
});
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
core.setFailed('Unrecognited eventName: ' + context.eventName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ on:
|
|||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
paths:
|
paths:
|
||||||
- "python/**"
|
- "python/**"
|
||||||
|
- "!python/AGENTS.md"
|
||||||
|
- "!python/**/AGENTS.md"
|
||||||
|
- "!python/.github/skills/*"
|
||||||
|
- "!python/.github/skills/**"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Configure a constant location for the uv cache
|
# Configure a constant location for the uv cache
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ jobs:
|
|||||||
filters: |
|
filters: |
|
||||||
python:
|
python:
|
||||||
- 'python/**'
|
- 'python/**'
|
||||||
|
- '!python/AGENTS.md'
|
||||||
|
- '!python/**/AGENTS.md'
|
||||||
|
- '!python/.github/skills/*'
|
||||||
|
- '!python/.github/skills/**'
|
||||||
# run only if 'python' files were changed
|
# run only if 'python' files were changed
|
||||||
- name: python tests
|
- name: python tests
|
||||||
if: steps.filter.outputs.python == 'true'
|
if: steps.filter.outputs.python == 'true'
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ jobs:
|
|||||||
filters: |
|
filters: |
|
||||||
python:
|
python:
|
||||||
- 'python/**'
|
- 'python/**'
|
||||||
|
- '!python/AGENTS.md'
|
||||||
|
- '!python/**/AGENTS.md'
|
||||||
|
- '!python/.github/skills/*'
|
||||||
|
- '!python/.github/skills/**'
|
||||||
- '.github/actions/setup-local-mcp-server/**'
|
- '.github/actions/setup-local-mcp-server/**'
|
||||||
- '.github/workflows/python-merge-tests.yml'
|
- '.github/workflows/python-merge-tests.yml'
|
||||||
- '.github/workflows/python-integration-tests.yml'
|
- '.github/workflows/python-integration-tests.yml'
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ on:
|
|||||||
branches: ["main", "feature*"]
|
branches: ["main", "feature*"]
|
||||||
paths:
|
paths:
|
||||||
- "python/**"
|
- "python/**"
|
||||||
|
- "!python/AGENTS.md"
|
||||||
|
- "!python/**/AGENTS.md"
|
||||||
|
- "!python/.github/skills/*"
|
||||||
|
- "!python/.github/skills/**"
|
||||||
env:
|
env:
|
||||||
# Configure a constant location for the uv cache
|
# Configure a constant location for the uv cache
|
||||||
UV_CACHE_DIR: /tmp/.uv-cache
|
UV_CACHE_DIR: /tmp/.uv-cache
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../.github/skills/pull-requests
|
||||||
@@ -10,6 +10,10 @@ See `./.github/skills/build-and-test/SKILL.md` for detailed instructions on buil
|
|||||||
|
|
||||||
See `./.github/skills/project-structure/SKILL.md` for an overview of the project structure.
|
See `./.github/skills/project-structure/SKILL.md` for an overview of the project structure.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
See `./.github/skills/pull-requests/SKILL.md` for guidance on writing PR descriptions and handling/resolving PR review comments.
|
||||||
|
|
||||||
### Core types
|
### Core types
|
||||||
|
|
||||||
- `AIAgent`: The abstract base class that all agents derive from, providing common methods for interacting with an agent.
|
- `AIAgent`: The abstract base class that all agents derive from, providing common methods for interacting with an agent.
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../.github/skills/pull-requests
|
||||||
@@ -14,6 +14,7 @@ Instructions for AI coding agents working in the Python codebase.
|
|||||||
- `python-feature-lifecycle` — package vs feature lifecycle stages, decorators, enums, and promotion guidance
|
- `python-feature-lifecycle` — package vs feature lifecycle stages, decorators, enums, and promotion guidance
|
||||||
- `python-package-management` — monorepo structure, lazy loading, versioning, new packages
|
- `python-package-management` — monorepo structure, lazy loading, versioning, new packages
|
||||||
- `python-samples` — sample file structure, PEP 723, documentation guidelines
|
- `python-samples` — sample file structure, PEP 723, documentation guidelines
|
||||||
|
- `pull-requests` — writing PR descriptions (template) and handling/resolving PR review comments
|
||||||
|
|
||||||
## Maintaining Documentation
|
## Maintaining Documentation
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user