Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .agents/skills/assign-prs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Reviewer configuration

`reviewers.json` decides who receives review requests from the `/assign-prs` skill. Edit that file to
change assignment; never edit `SKILL.md`, which deliberately contains no names.

## It is an overlay, not a roster

Team membership is resolved at run time from the GitHub team API. This file only declares which of
those members are eligible for automatic assignment, and which areas each one owns. Both halves are
required: the API says who exists, the file says who to ask.

The `team` field names the team the roster is drawn from. It is deliberately independent of the
`--team` queues the skill reads: this repository grooms two queues but draws every reviewer from one
team, so a build-queue PR can be routed to a component reviewer.

The two are reconciled on every run and drift is reported in both directions:

- **In the file but no longer on the team** — a stale entry. They never receive a request.
- **On the team but absent from the file** — unconfigured. Under the default `exclude-and-report`
policy they receive nothing, and the run names them.

Every member of the team is listed, **including ineligible ones**, so that nobody is silently absent.
Removing an entry is not the same as marking it ineligible: an unlisted member is reported as
unconfigured on every run until someone decides.

## Fields

| Field | Required | Meaning |
| ------------------ | -------- | ---------------------------------------------------------------- |
| `login` | yes | GitHub login, matched against the live team roster |
| `eligible` | yes | Whether this person may receive automatic review requests |
| `primary` | yes | Areas this person owns; selection prefers them here |
| `secondary` | yes | Areas this person can cover when no primary owner is available |
| `fallbackEligible` | no | Defaults to `true`. Set `false` to opt out of unclassifiable PRs |

`fallbackEligible: false` means "only ever pick me for an area I actually declare." It exists because
a narrow specialist carries little load by definition, so lowest-load selection would otherwise hand
them every PR that matched no area — the opposite of what declaring a specialty means.

Read it with an explicit presence check, never with jq's `//` operator: jq treats `false` as empty, so
`.fallbackEligible // true` reads every opt-out back as opt-in.

## Areas

The `areas` map defines each area's `paths` and conventional-commit `scopes`. A PR's area is detected
from its changed file paths first, falling back to the commit scope in the title.

Every value in `primary` and `secondary` must be a key of `areas`. JSON Schema cannot express that
cross-reference, so it is checked at run time — a typo such as `ai-skill` for `ai-skills` is not a
validation error, it simply never matches anything.

## This file is public

`microsoft/fluentui` is a public repository and this config is committed to it. An entry carries a
login, an eligibility flag and areas — deliberately no free-text notes about anyone. The schema sets
`additionalProperties: false` so a commentary field cannot be reintroduced by accident.

When eligibility changes, record the reasoning in the pull request that changes the flag. Review
statistics and comparisons between people belong in that discussion, not in a permanent public file.

## Validating a change

```bash
jq empty reviewers.json
npx ajv-cli validate -s reviewers.schema.json -d reviewers.json --spec=draft2020
```
359 changes: 359 additions & 0 deletions .agents/skills/assign-prs/SKILL.md

Large diffs are not rendered by default.

134 changes: 134 additions & 0 deletions .agents/skills/assign-prs/reviewers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
{
"$schema": "./reviewers.schema.json",
"version": 1,

"team": {
"org": "microsoft",
"slug": "cxe-prg"
},

"settings": {
"reviewers": 1,
"areaMatch": "preference",
"unknownMemberPolicy": "exclude-and-report"
},

"areas": {
"headless": {
"label": "Headless components",
"scopes": ["react-headless-components-preview", "headless-*"],
"paths": ["packages/react-components/react-headless-components-preview/**", "apps/public-docsite-v9-headless/**"]
},
"components": {
"label": "v9 React components",
"scopes": ["react", "react-*"],
"paths": ["packages/react-components/**"]
},
"web-components": {
"label": "Web components",
"scopes": ["web-components"],
"paths": ["packages/web-components/**", "apps/vr-tests-web-components/**"]
},
"motion": {
"label": "Motion",
"scopes": ["react-motion*"],
"paths": ["packages/react-components/react-motion*/**"]
},
"build": {
"label": "Build, tooling and release",
"scopes": [
"eslint-*",
"workspace-plugin",
"tools",
"scripts*",
"ci",
"release",
"bundle-size",
"monosize",
"babel-preset*",
"react-conformance"
],
"paths": [
"tools/**",
"scripts/**",
".github/**",
".devops/**",
"packages/eslint-plugin/**",
"packages/react-conformance/**"
]
},
"dependencies": {
"label": "Dependencies",
"scopes": ["deps", "deps-dev", "dependabot"],
"paths": ["yarn.lock", "package.json", "**/package.json"]
},
"docs": {
"label": "Docs and docsite",
"scopes": ["docsite*", "api-docs", "react-docsite-components", "docs"],
"paths": ["apps/public-docsite*/**", "packages/api-docs/**"]
},
"a11y": {
"label": "Accessibility",
"scopes": ["a11y*", "axe"],
"paths": ["packages/a11y-testing/**"]
},
"ai-skills": {
"label": "Agentic Skills",
"scopes": ["skills"],
"paths": [".agents/skills/**", ".claude/skills/**", "AGENTS.md"]
}
},

"reviewers": [
{
"login": "Hotell",
"eligible": true,
"primary": ["build"],
"secondary": ["ai-skills", "dependencies", "docs", "headless"]
},
{
"login": "dmytrokirpa",
"eligible": true,
"primary": ["components", "headless"],
"secondary": ["a11y", "ai-skills", "build", "dependencies", "motion", "web-components"]
},
{
"login": "mainframev",
"eligible": true,
"primary": ["components", "headless"],
"secondary": ["a11y", "ai-skills", "build", "dependencies", "motion", "web-components"]
},
{
"login": "PaulGMardling",
"eligible": true,
"primary": ["components", "headless"],
"secondary": ["a11y", "ai-skills", "build", "dependencies", "motion", "web-components"]
},
{
"login": "tudorpopams",
"eligible": true,
"fallbackEligible": false,
"primary": ["ai-skills"],
"secondary": []
},
{
"login": "ValentinaKozlova",
"eligible": false,
"primary": [],
"secondary": []
},
{
"login": "paolo-aliprandi",
"eligible": true,
"fallbackEligible": false,
"primary": ["ai-skills"],
"secondary": []
},
{
"login": "gouttierre",
"eligible": false,
"primary": [],
"secondary": []
}
]
}
115 changes: 115 additions & 0 deletions .agents/skills/assign-prs/reviewers.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/microsoft/fluentui/.agents/skills/assign-prs/reviewers.schema.json",
"title": "assign-prs reviewer configuration",
"description": "Overlay declaring which members of the live GitHub team roster are eligible for automatic review assignment, and which areas each one owns. See README.md.",
"type": "object",
"additionalProperties": false,
"required": ["version", "team", "settings", "areas", "reviewers"],
"properties": {
"$schema": {
"type": "string",
"description": "Path to this schema, for editor validation."
},
"version": {
"type": "integer",
"minimum": 1,
"description": "Config format version."
},
"team": {
"type": "object",
"additionalProperties": false,
"required": ["org", "slug"],
"description": "GitHub team whose live membership is reconciled against this file.",
"properties": {
"org": { "type": "string", "minLength": 1 },
"slug": { "type": "string", "minLength": 1 }
}
},
"settings": {
"type": "object",
"additionalProperties": false,
"required": ["reviewers", "areaMatch", "unknownMemberPolicy"],
"properties": {
"reviewers": {
"type": "integer",
"minimum": 1,
"description": "Reviewers each PR should end up with. The skill requests only the shortfall."
},
"areaMatch": {
"enum": ["preference", "hard"],
"description": "preference degrades to the whole eligible pool when nobody owns an area; hard leaves the PR under-covered instead."
},
"unknownMemberPolicy": {
"enum": ["exclude-and-report"],
"description": "How to treat a live team member with no entry in this file."
}
}
},
"areas": {
"type": "object",
"minProperties": 1,
"description": "Area definitions. Every value in a reviewer's primary or secondary list must be a key of this map; that cross-reference is checked at run time, not by this schema.",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": ["label", "scopes", "paths"],
"properties": {
"label": {
"type": "string",
"minLength": 1,
"description": "Human-readable area name used in reports."
},
"scopes": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"description": "Conventional-commit scopes that identify this area, used only when paths are inconclusive."
},
"paths": {
"type": "array",
"minItems": 1,
"items": { "type": "string", "minLength": 1 },
"description": "Glob patterns identifying this area. Paths are authoritative over scopes."
}
}
}
},
"reviewers": {
"type": "array",
"minItems": 1,
"description": "Every member of the team, including ineligible ones, so nobody is silently absent.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["login", "eligible", "primary", "secondary"],
"properties": {
"login": {
"type": "string",
"pattern": "^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$",
"description": "GitHub login, matched against the live team roster."
},
"eligible": {
"type": "boolean",
"description": "Whether this person may receive automatic review requests."
},
"fallbackEligible": {
"type": "boolean",
"description": "Defaults to true. Set false to be selected only for declared areas, never for PRs that matched no area."
},
"primary": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Areas this person owns. Selection prefers a primary owner."
},
"secondary": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Areas this person can cover when no primary owner is available."
}
}
}
}
}
}
1 change: 1 addition & 0 deletions .claude/skills/assign-prs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@../../../.agents/skills/assign-prs/SKILL.md
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ state.root.className = mergeClasses(
| `review-pr` | `/review-pr #123` | Review a PR with confidence scoring and category checks |
| `triage-issues` | `/triage-issues` | Walk the Needs-Triage queue and recommend labels/assignee |
| `dependabot-rollup` | `/dependabot-rollup` | Dry-run and optionally roll up at most 11 Dependabot patch/minor PRs |
| `assign-prs` | `/assign-prs` | Assign reviewers to the team review queues by area and current load |

## Package Layout

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "feat: export toDataAttributeValue from the utils entry point",
"packageName": "@fluentui/react-headless-components-preview",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
"fixturesRoot": "./bundle-size",
"externals": ["react", "react-dom", "react/jsx-runtime", "react/compiler-runtime"],
"forbiddenPackages": ["tabster", "@griffel/*", "@fluentui/react-icons"],
"allowedViolations": {}
"fixtures": {
"AllComponents.fixture.js": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## API Report File for "@fluentui/react-headless-components-preview"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

// @public
export function toDataAttributeValue(value: boolean | string | number | undefined): string | undefined;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,16 @@
"default": "./lib-commonjs/tooltip.cjs"
}
},
"./utils": {
"import": {
"types": "./dist/utils.d.ts",
"default": "./lib/utils.js"
},
"require": {
"types": "./dist/utils.d.cts",
"default": "./lib-commonjs/utils.cjs"
}
},
"./package.json": "./package.json"
},
"beachball": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type * as React from 'react';
import { useAccordionHeaderBase_unstable, useAccordionHeaderContextValues_unstable } from '@fluentui/react-accordion';

import type { AccordionHeaderProps, AccordionHeaderState, AccordionHeaderContextValues } from './AccordionHeader.types';
import { stringifyDataAttribute } from '../../../utils';
import { toDataAttributeValue } from '../../../utils';

/**
* Returns the state for an AccordionHeader component, given its props and ref.
Expand All @@ -15,9 +15,9 @@ export const useAccordionHeader = (props: AccordionHeaderProps, ref: React.Ref<H

// Set data attributes for open, disabled, and expand icon position states to simplify styling.
// eslint-disable-next-line react-hooks/immutability
state.root['data-open'] = stringifyDataAttribute(state.open);
state.root['data-open'] = toDataAttributeValue(state.open);
// eslint-disable-next-line react-hooks/immutability
state.root['data-disabled'] = stringifyDataAttribute(state.disabled);
state.root['data-disabled'] = toDataAttributeValue(state.disabled);
// eslint-disable-next-line react-hooks/immutability
state.root['data-expand-icon-position'] = state.expandIconPosition;

Expand Down
Loading
Loading