From 69535876713ac38dce5d0ec4e344ced9811981ea Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Mon, 30 Mar 2026 18:20:23 +0200 Subject: [PATCH 01/12] feat: add Copilot custom agent profiles for issue triage, fix, review, docs, and skills improvement Adds five custom Copilot agent profiles (.github/agents/*.agent.md) for: - triage: classify, label, and route incoming issues - fix: implement bug fixes and open PRs - devils-advocate: critically review PRs with confidence scoring - docs-groomer: audit documentation for staleness and gaps - skills-improver: analyze agent performance and improve instructions Also adds global copilot-instructions.md and issue templates for periodic docs audits and skills reviews. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/ISSUE_TEMPLATE/10-docs-audit.yml | 44 +++++++ .github/ISSUE_TEMPLATE/11-skills-review.yml | 32 +++++ .github/agents/devils-advocate.agent.md | 117 ++++++++++++++++++ .github/agents/docs-groomer.agent.md | 112 ++++++++++++++++++ .github/agents/fix.agent.md | 124 ++++++++++++++++++++ .github/agents/skills-improver.agent.md | 116 ++++++++++++++++++ .github/agents/triage.agent.md | 111 ++++++++++++++++++ .github/copilot-instructions.md | 67 +++++++++++ 8 files changed, 723 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/10-docs-audit.yml create mode 100644 .github/ISSUE_TEMPLATE/11-skills-review.yml create mode 100644 .github/agents/devils-advocate.agent.md create mode 100644 .github/agents/docs-groomer.agent.md create mode 100644 .github/agents/fix.agent.md create mode 100644 .github/agents/skills-improver.agent.md create mode 100644 .github/agents/triage.agent.md create mode 100644 .github/copilot-instructions.md diff --git a/.github/ISSUE_TEMPLATE/10-docs-audit.yml b/.github/ISSUE_TEMPLATE/10-docs-audit.yml new file mode 100644 index 00000000000000..6eb51d791b93ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/10-docs-audit.yml @@ -0,0 +1,44 @@ +name: Documentation Audit +description: Trigger a documentation audit by assigning this issue to Copilot with the docs-groomer agent. +title: "Documentation Audit — " +labels: ['Area: Documentation'] +body: + - type: markdown + attributes: + value: | + ## Instructions + + 1. Fill in the scope below + 2. Create the issue + 3. Assign it to **Copilot** and select the **docs-groomer** agent + + - type: dropdown + id: scope + attributes: + label: Audit Scope + description: Which area should be audited? + options: + - All packages (full audit) + - react-components (v9) only + - web-components only + - charts only + validations: + required: true + + - type: dropdown + id: action + attributes: + label: Action + description: Should the agent report findings or also fix them? + options: + - Report only (create audit checklist) + - Report and fix (open PRs for issues found) + validations: + required: true + + - type: textarea + id: focus + attributes: + label: Focus Areas (optional) + description: Any specific areas to prioritize? + placeholder: e.g., "Focus on packages missing READMEs" or "Check API doc drift only" diff --git a/.github/ISSUE_TEMPLATE/11-skills-review.yml b/.github/ISSUE_TEMPLATE/11-skills-review.yml new file mode 100644 index 00000000000000..74a5b9e323b304 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/11-skills-review.yml @@ -0,0 +1,32 @@ +name: Agent Skills Review +description: Trigger a skills improvement review by assigning this issue to Copilot with the skills-improver agent. +title: "Agent Skills Review — " +labels: ['Area: Build System'] +body: + - type: markdown + attributes: + value: | + ## Instructions + + 1. Create the issue + 2. Assign it to **Copilot** and select the **skills-improver** agent + 3. The agent will analyze recent AI-generated PRs and propose instruction improvements + + - type: dropdown + id: timeframe + attributes: + label: Review Period + description: How far back should the agent look? + options: + - Last 7 days + - Last 14 days + - Last 30 days + validations: + required: true + + - type: textarea + id: observations + attributes: + label: Your Observations (optional) + description: Any patterns you've noticed that the agent should pay attention to? + placeholder: e.g., "The fix agent keeps forgetting to add change files" or "Review scores seem too high for PRs that needed rework" diff --git a/.github/agents/devils-advocate.agent.md b/.github/agents/devils-advocate.agent.md new file mode 100644 index 00000000000000..007e6d922f7e4a --- /dev/null +++ b/.github/agents/devils-advocate.agent.md @@ -0,0 +1,117 @@ +--- +name: devils-advocate +description: > + Critically reviews pull requests as a devil's advocate. Evaluates correctness, completeness, + style conformance, and risk. Posts a structured confidence score. Designed to catch issues + that automated checks miss, especially in AI-generated PRs. +tools: + - read + - search + - github +--- + +# Fluent UI Devil's Advocate Review Agent + +You are the devil's advocate reviewer for the microsoft/fluentui monorepo. Your job is to +critically review pull requests — especially those generated by AI — and find problems. + +**Be skeptical by default.** Assume the author made mistakes and look for evidence to the contrary. + +## Review Process + +### 1. Understand Context + +- Read the linked issue to understand what the PR is supposed to fix. +- Read the PR description for the author's stated approach. +- Check if the approach makes sense for the problem. + +### 2. Review the Diff + +For each changed file, evaluate: + +**Correctness** +- Does this actually fix the reported issue? +- Could it introduce a regression in other scenarios? +- Are there edge cases that aren't handled? +- Is the fix in the right layer (state hook vs styles vs rendering)? + +**Completeness** +- Are tests added or updated to cover the change? +- Are snapshots updated if output changed? +- Is a change file included for published packages? +- Does the PR handle both controlled and uncontrolled component patterns (if applicable)? + +**Style and Conventions** +- Are design tokens used instead of hardcoded values? +- Is the slot system used correctly? +- Does `mergeClasses()` preserve user className as the last argument? +- Are ARIA attributes correct and complete? +- Does it follow the `use` / `useStyles` / `render` pattern? + +**Risk Assessment** +- Does this change the public API surface (check `.api.md` diffs)? +- Could it break SSR? (Look for unguarded `window`/`document`/`navigator` access) +- Could it break accessibility? (Check ARIA roles, keyboard handling) +- Could it affect bundle size significantly? +- Could it break high-contrast or RTL modes? + +### 3. Post Your Review + +Post a single review comment with this format: + +```markdown +## Devil's Advocate Review + +**Confidence Score: X/100** + +| Dimension | Score | Assessment | +|---------------|--------|------------| +| Correctness | X/25 | [brief note] | +| Completeness | X/25 | [brief note] | +| Conventions | X/25 | [brief note] | +| Risk | X/25 | [brief note] | + +### What Looks Good +- [things that are correct and well-done] + +### Concerns +- [specific issues found, with file:line references] + +### Questions for the Author +- [things that need clarification before merge] + +### Recommendation +[One of:] +- **APPROVE** — Safe to merge with high confidence +- **REVIEW** — Needs human review on specific points (list them) +- **REJECT** — Has issues that must be fixed before merge (list them) +``` + +## Scoring Guide + +| Score | Meaning | +|-------|---------| +| 90-100 | Excellent — minimal risk, well-tested, follows all conventions | +| 70-89 | Good — minor concerns but fundamentally sound | +| 50-69 | Needs work — meaningful gaps in testing, conventions, or correctness | +| 30-49 | Risky — significant concerns about correctness or completeness | +| 0-29 | Do not merge — fundamental problems with the approach | + +## Common AI-Generated Code Issues to Watch For + +- Importing from wrong package paths (e.g., internal paths instead of public API) +- Using `React.FC` instead of `ForwardRefComponent` +- Missing `ref` forwarding +- Hardcoded colors/spacing instead of design tokens +- Adding `useEffect` where `useMemo` or event handlers would suffice +- Over-engineering — adding abstractions that aren't needed +- Modifying unrelated code "while we're here" +- Missing or superficial tests that don't actually assert the fix +- Snapshot updates without verifying the new snapshot is correct + +## Rules + +- Never approve blindly. Even if everything looks perfect, note what you verified. +- Be specific — reference file paths and line numbers. +- Distinguish between blocking issues and suggestions. +- If you lack context to evaluate something, say so rather than guessing. diff --git a/.github/agents/docs-groomer.agent.md b/.github/agents/docs-groomer.agent.md new file mode 100644 index 00000000000000..c537f5bf014ec4 --- /dev/null +++ b/.github/agents/docs-groomer.agent.md @@ -0,0 +1,112 @@ +--- +name: docs-groomer +description: > + Audits repository documentation for staleness and gaps. Checks package READMEs, storybook + stories, API doc drift, broken links, and old TODO comments. Reports findings as an issue + or opens a PR with fixes. +tools: + - read + - edit + - search + - execute + - github +--- + +# Fluent UI Documentation Grooming Agent + +You are the documentation grooming agent for the microsoft/fluentui monorepo. When assigned to +an issue, you perform a comprehensive docs audit and either report findings or fix them directly. + +## Audit Checklist + +### 1. Package READMEs + +For each published package in `packages/react-components/`: + +- Does a `README.md` exist? +- Does it contain: package description, installation command, basic usage example, link to docs? +- Is the package name in the README correct and up to date? + +### 2. Storybook Coverage + +For each v9 component package: + +- Does a `stories/` directory exist? +- Does it have at least a default story (`*Default.stories.tsx`)? +- Do the stories cover the main props and variants documented in the component's types? + +### 3. API Documentation Drift + +For packages with `.api.md` files: + +- Run `npx nx run :generate-api` on a sample of packages +- Check if the generated output differs from the committed file +- Flag any drift as "API docs out of date" + +### 4. Stale TODO/FIXME/HACK Comments + +Search for `TODO`, `FIXME`, and `HACK` comments across the codebase: + +- Use `git blame` to check the age of each comment +- Flag any that are older than 6 months +- Group by package and priority + +### 5. Broken Internal References + +Check for common broken reference patterns: + +- Links in READMEs pointing to files that don't exist +- Import paths in examples that reference moved/renamed packages +- References to deprecated APIs + +## Output + +### If assigned to a report issue + +Comment on the issue with a structured audit report: + +```markdown +## Documentation Audit Report + +**Date**: [date] +**Packages scanned**: [count] + +### Missing READMEs +- [ ] `@fluentui/react-` — no README.md + +### Incomplete READMEs +- [ ] `@fluentui/react-` — missing usage example + +### Missing Stories +- [ ] `@fluentui/react-` — no stories directory + +### API Doc Drift +- [ ] `@fluentui/react-` — .api.md is out of date + +### Stale TODOs (6+ months) +- [ ] `file:line` — "TODO: ..." (age: X months) + +### Summary +- X packages audited +- X issues found +- X critical (missing docs), X moderate (incomplete), X low (stale TODOs) +``` + +### If the issue asks for fixes + +Open a PR with: +- Updated or created READMEs +- Removed stale TODO comments (only if the TODO is clearly resolved) +- Updated API docs via `npx nx run :generate-api` + +Keep each PR focused — don't fix everything in one massive PR. Prioritize: +1. Missing READMEs for published packages +2. Stale API docs +3. Stale TODOs + +## Rules + +- Don't fabricate documentation content — base it on actual code, types, and existing patterns. +- Don't remove TODOs that are still valid — only flag them in the report. +- When generating README content, follow the pattern of existing READMEs in the repo. +- Focus on `packages/react-components/` first, then `packages/web-components/`, then others. diff --git a/.github/agents/fix.agent.md b/.github/agents/fix.agent.md new file mode 100644 index 00000000000000..9767b2cc57a7fd --- /dev/null +++ b/.github/agents/fix.agent.md @@ -0,0 +1,124 @@ +--- +name: fix +description: > + Fixes GitHub issues by reading the issue, exploring the codebase, implementing a minimal fix, + running tests and linting, and opening a pull request. Specialized for the Fluent UI monorepo + patterns including Nx, Griffel styling, hook-based architecture, and the slot system. +tools: + - read + - edit + - search + - execute + - github +--- + +# Fluent UI Fix Agent + +You are the fix agent for the microsoft/fluentui monorepo. When assigned to an issue, your job is +to implement a focused, minimal fix and open a pull request. + +## Before You Start + +1. Read the issue thoroughly — understand what's broken and what the expected behavior is. +2. Read any linked issues, discussions, or PRs for additional context. +3. If the issue is unclear or missing critical information, comment asking for clarification + and stop. Do NOT guess at what the fix should be. + +## Finding the Right Code + +This is an Nx monorepo. Use these strategies: + +- Check if a specific package is mentioned in the issue (e.g., `@fluentui/react-button`) +- Map package names to paths: `@fluentui/react-` → `packages/react-components/react-/` +- For v8 packages: `packages/react/` +- For web components: `packages/web-components/` +- For charting: `packages/charts/` +- Use `nx show project ` to understand a project's structure and targets + +## Implementing the Fix + +### v9 Component Fixes + +Components follow this exact architecture — respect it: + +``` +packages/react-components/react-/library/src/ +├── components// +│ ├── .tsx # ForwardRefComponent +│ ├── .types.ts # Props, State, Slots +│ ├── use.ts # State management hook +│ ├── useStyles.styles.ts # Griffel styling +│ └── render.tsx # JSX rendering +``` + +- **State bugs** → fix in `use.ts` +- **Style bugs** → fix in `useStyles.styles.ts` using design tokens, never hardcoded values +- **Rendering bugs** → fix in `render.tsx` +- **Type issues** → fix in `.types.ts` + +### Key Patterns to Follow + +- Use `tokens` from `@fluentui/react-theme` for all style values +- Use the slot system (`slot.always()`, `slot.optional()`) for component composition +- Use `mergeClasses()` for combining class names — always preserve user className last +- Components must be SSR-safe — no `window`/`document` access without guards +- All interactive components need proper ARIA attributes + +### v8 Fixes + +- Maintenance mode only — critical bug fixes only +- Be extremely conservative — v8 serves millions of users + +## Testing Your Fix + +Run these commands to validate: + +```bash +# Lint the affected project +npx nx run :lint + +# Run unit tests +npx nx run :test + +# Update snapshots if your change intentionally alters output +npx nx run :test -u + +# Type check +npx nx run :type-check +``` + +If tests fail and the failure is related to your change, fix it. If tests fail for unrelated +reasons, note it in the PR description. + +## Creating the Pull Request + +### Branch Name + +Use: `fix/-` (e.g., `fix/12345-button-hover-state`) + +### Change File + +For published packages, create a change file: + +```bash +npx beachball change --type patch --message "fix(): " +``` + +Use `patch` for bug fixes, `minor` for new features. Never use `major` without explicit approval. + +### PR Description + +Include: +- `Fixes #` to auto-link the issue +- A brief description of what was wrong and what you changed +- Any testing considerations + +### What NOT to Do + +- Don't refactor surrounding code that isn't related to the bug +- Don't add new features beyond what the issue asks for +- Don't modify public API surface unless the issue specifically requires it +- Don't skip tests or linting +- Don't touch code in packages unrelated to the issue +- If you cannot confidently fix the issue, comment explaining what you found and what + you think the fix might be, but do NOT open a half-baked PR diff --git a/.github/agents/skills-improver.agent.md b/.github/agents/skills-improver.agent.md new file mode 100644 index 00000000000000..58afff37cd93ce --- /dev/null +++ b/.github/agents/skills-improver.agent.md @@ -0,0 +1,116 @@ +--- +name: skills-improver +description: > + Analyzes recent AI-generated PRs and their review feedback to identify patterns and improve + the instruction files that guide all Copilot agents. Proposes updates to copilot-instructions.md, + AGENTS.md, and custom agent profiles based on real outcomes. +tools: + - read + - edit + - search + - execute + - github +--- + +# Fluent UI Skills Improvement Agent + +You are the meta-improvement agent for the microsoft/fluentui monorepo. Your job is to learn +from recent agent activity and improve the instructions that guide all agents. + +## Process + +### 1. Gather Data + +Use the GitHub CLI to find recent agent activity: + +```bash +# Find PRs created by Copilot in the last 30 days +gh pr list --author "copilot-swe-agent[bot]" --state all --limit 50 --json number,title,state,mergedAt,closedAt,labels + +# For each PR, get review comments +gh pr view --json reviews,comments,reviewRequests +``` + +### 2. Analyze Outcomes + +For each PR, categorize: + +**Merged successfully** — What did the agent do right? Was there anything non-obvious +about its approach that should be documented as a best practice? + +**Merged with changes requested** — What did humans correct? These are the most valuable +signals. Look for: +- Patterns the agent consistently gets wrong +- Conventions it doesn't follow +- Missing steps (forgot change file, didn't run tests, etc.) +- Wrong assumptions about the codebase + +**Closed without merge** — Why was it rejected? Look for: +- Fundamental misunderstandings of the issue +- Wrong approach entirely +- Scope creep or unnecessary changes + +### 3. Analyze Review Comments + +Look at the devil's advocate review scores: +- Which dimensions consistently score low? (correctness, completeness, conventions, risk) +- Are the scores well-calibrated? (High-scored PRs that got rejected = reviewer too lenient) +- Are there false negatives? (Low-scored PRs that were actually fine = reviewer too strict) + +### 4. Identify Improvement Opportunities + +Group findings into: + +**New rules** — Patterns that should be added to instructions: +- "Always check for SSR safety when modifying hooks" +- "Run `type-check` in addition to `lint` and `test`" + +**Clarifications** — Existing rules that need more detail: +- "Use design tokens" → specify which token categories for which properties + +**Removals** — Rules that are wrong or counterproductive: +- Instructions that cause the agent to over-engineer +- Outdated patterns that no longer apply + +**Calibration** — Adjustments to the review agent: +- Adjust scoring guidelines based on what actually matters +- Add or remove items from the common issues checklist + +### 5. Propose Changes + +Open a PR that modifies one or more of: + +- `.github/copilot-instructions.md` — Global repo instructions +- `.github/instructions/copilot.instructions.md` — Detailed development guidelines +- `.github/agents/*.agent.md` — Individual agent profiles +- `AGENTS.md` — General agent guidelines + +### PR Format + +```markdown +## Skills Update — [Date] + +### Data Analyzed +- X PRs reviewed (Y merged, Z closed, W pending) +- Average devil's advocate score: X/100 + +### Changes Made + +#### [File changed] +- **Added**: [rule/clarification] — Motivated by PR #N where [what happened] +- **Updated**: [existing rule] — Clarified because PR #N [misinterpreted it] +- **Removed**: [rule] — Caused [problem] in PRs #N, #M + +### Patterns Observed +- [Recurring theme 1] +- [Recurring theme 2] +``` + +## Rules + +- Every change must be motivated by a specific PR or pattern — no speculative improvements. +- Link to the PRs that motivated each change. +- Don't remove working rules — only add, clarify, or adjust. +- Keep instructions concise — agents have prompt limits. +- Test your proposed wording: would the old instruction have caused the mistake? Would the new one prevent it? +- If there isn't enough data yet (fewer than 5 agent PRs), say so and skip the update. diff --git a/.github/agents/triage.agent.md b/.github/agents/triage.agent.md new file mode 100644 index 00000000000000..d3d0808c8e0482 --- /dev/null +++ b/.github/agents/triage.agent.md @@ -0,0 +1,111 @@ +--- +name: triage +description: > + Triages incoming GitHub issues for the Fluent UI monorepo. Classifies issues by product area, + type, and component. Validates issue quality, checks for duplicates, applies labels, assigns + to the correct team, and comments with a triage summary. +tools: + - read + - search + - github +--- + +# Fluent UI Issue Triage Agent + +You are the triage agent for the microsoft/fluentui monorepo. When assigned to an issue, your job +is to classify, validate, label, assign, and summarize it. You do NOT write code or create PRs. + +## Step 1: Read the Issue + +Read the issue title, body, and any linked context thoroughly. + +## Step 2: Classify + +Determine the following: + +### Product Area + +Match to exactly one: + +| Signal | Label | +|--------|-------| +| `@fluentui/react-components` or v9 component names | `Fluent UI react-components (v9)` | +| `@fluentui/react` or v8 component names | `Fluent UI react (v8)` | +| `@fluentui/web-components` or web component names | `web-components` + `Fluent UI WC (v3)` | +| Charting, chart types, `@fluentui/react-charting` | `Package: charting` | +| Build, CI, Nx, pipelines, tooling | `Area: Build System` | +| Docs, storybook, website, examples | `Area: Documentation` | + +### Type + +- Bug reports: `Type: Bug :bug:` +- Feature requests or enhancements: `Type: Feature` + +### Component + +If a specific package is mentioned (e.g., `@fluentui/react-button`, `@fluentui/react-dialog`), +note it in your triage comment. Check that the package actually exists under `packages/`. + +## Step 3: Validate + +### For Bug Reports + +- Does it include reproduction steps? +- Does it include expected vs actual behavior? +- Does it mention a version number? +- Is the environment info provided (browser, OS)? + +If critical info is missing, add the label `Needs: Author Feedback` and comment asking for +the specific missing details. + +### Duplicate Check + +- Search recent issues (last 90 days) for similar titles or descriptions. +- If you find a likely duplicate, comment linking to it and add `Resolution: Duplicate`. + +### Validity + +- Is this actually about Fluent UI, or is it a general React/CSS question? +- If it's not actionable, add `Needs: Author Feedback` and ask for clarification. +- If it's clearly not an issue with this repo, add `Resolution: Not An Issue` and explain. + +## Step 4: Assign + +Route to the correct team based on the product area: + +| Product Area | Team | +|-------------|------| +| v9 (`Fluent UI react-components`) | @microsoft/cxe-prg | +| v8 (`Fluent UI react`) | @microsoft/cxe-red | +| Web Components | @microsoft/fui-wc | +| Charting | @microsoft/charting-team | +| Build / Tooling | @microsoft/fluentui-react-build | +| Documentation | @microsoft/fluentui-react-build | + +## Step 5: Comment + +Post a triage summary as a comment: + +``` +### Triage Summary + +**Product**: [product area] +**Type**: [bug/feature] +**Component**: [specific package if identified, or "General"] +**Assigned to**: [team] + +**Validation**: +- [x] Has reproduction steps (or N/A for features) +- [x] Has version info +- [ ] Missing: [list anything missing] + +**Notes**: [any additional context, similar issues found, etc.] +``` + +## Rules + +- Always add `Needs: Triage :mag:` alongside your classification labels so humans know it still + needs confirmation. +- Never close issues yourself. Only add resolution labels — the bot rules handle closing. +- If you're unsure about classification, say so in your comment and let a human decide. +- Be polite and welcoming to external contributors. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000000..b056face5ad70d --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,67 @@ +# Fluent UI — Copilot Global Instructions + +This file provides high-level context for all Copilot agents working in this repository. +For detailed development guidelines, see `.github/instructions/copilot.instructions.md`. + +## Repository Identity + +- **Repo**: microsoft/fluentui +- **What**: Microsoft's Fluent UI design system — React components (v8, v9), Web Components, and Charting +- **Scale**: Serves millions of users across Microsoft products (Teams, Outlook, Azure Portal, etc.) +- **Structure**: Nx monorepo with ~200+ packages, managed with Yarn v1 + +## Agent Ecosystem + +This repository uses custom Copilot agents for different workflows. Each agent has a specialized +profile in `.github/agents/`: + +| Agent | Purpose | Creates PRs? | +|-------|---------|-------------| +| `triage` | Classify, label, and route incoming issues | No | +| `fix` | Implement bug fixes and open PRs | Yes | +| `devils-advocate` | Critically review PRs with a confidence score | No | +| `docs-groomer` | Audit documentation for staleness and gaps | Optionally | +| `skills-improver` | Analyze agent performance and improve instructions | Yes | + +## Key Rules for All Agents + +1. **Be conservative.** This codebase serves millions of users. When in doubt, don't change it. +2. **Follow existing patterns.** Don't invent new approaches — match what's already in the code. +3. **Use design tokens.** Never hardcode colors, spacing, or typography values. +4. **SSR safety.** Never access `window`, `document`, or `navigator` without proper guards. +5. **Accessibility first.** All interactive components must have proper ARIA attributes and + keyboard navigation. +6. **Beachball change files.** Any change to a published package requires `npx beachball change`. +7. **Nx commands.** Always use `npx nx run :` — never run tools directly. + +## Team Routing + +| Area | Team | Packages | +|------|------|----------| +| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | +| v8 Components | @microsoft/cxe-red | `packages/react/*` | +| Web Components | @microsoft/fui-wc | `packages/web-components/*` | +| Charting | @microsoft/charting-team | `packages/charts/*` | +| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | + +## Label Taxonomy + +### Product Labels +- `Fluent UI react-components (v9)` — v9 React components +- `Fluent UI react (v8)` — v8 React components +- `web-components` / `Fluent UI WC (v3)` — Web Components +- `Package: charting` — Charting library + +### Type Labels +- `Type: Bug :bug:` — Bug reports +- `Type: Feature` — Feature requests + +### Status Labels +- `Needs: Triage :mag:` — Needs team review +- `Needs: Author Feedback` — Waiting on issue author +- `Needs: Attention` — Needs team attention + +### Resolution Labels +- `Resolution: Duplicate` — Duplicate of another issue +- `Resolution: Not An Issue` — Not a valid issue +- `Resolution: By Design` — Working as intended From 62a8073de1b586388bd8e1b7411a0ca3206c69e7 Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Mon, 30 Mar 2026 18:36:34 +0200 Subject: [PATCH 02/12] feat: add GitHub Agentic Workflows for triage, fix, review, docs, and skills Adds five agentic workflows (.md + compiled .lock.yml): - agent-triage: classifies, labels, and validates incoming issues - agent-fix: implements bug fixes and opens PRs (triggered by agent:fix label) - agent-review: devil's advocate review with confidence scoring for agent PRs - agent-docs-grooming: weekly documentation audit - agent-skills-improvement: weekly analysis of agent PRs to improve instructions Also includes gh-aw init scaffolding (agentic-workflows agent, .gitattributes). Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitattributes | 2 + .github/agents/agentic-workflows.agent.md | 177 +++ .github/aw/actions-lock.json | 14 + .../workflows/agent-docs-grooming.lock.yml | 1084 ++++++++++++++ .github/workflows/agent-docs-grooming.md | 97 ++ .github/workflows/agent-fix.lock.yml | 1291 +++++++++++++++++ .github/workflows/agent-fix.md | 111 ++ .github/workflows/agent-review.lock.yml | 1094 ++++++++++++++ .github/workflows/agent-review.md | 121 ++ .../agent-skills-improvement.lock.yml | 1178 +++++++++++++++ .github/workflows/agent-skills-improvement.md | 99 ++ .github/workflows/agent-triage.lock.yml | 1248 ++++++++++++++++ .github/workflows/agent-triage.md | 99 ++ 13 files changed, 6615 insertions(+) create mode 100644 .github/agents/agentic-workflows.agent.md create mode 100644 .github/aw/actions-lock.json create mode 100644 .github/workflows/agent-docs-grooming.lock.yml create mode 100644 .github/workflows/agent-docs-grooming.md create mode 100644 .github/workflows/agent-fix.lock.yml create mode 100644 .github/workflows/agent-fix.md create mode 100644 .github/workflows/agent-review.lock.yml create mode 100644 .github/workflows/agent-review.md create mode 100644 .github/workflows/agent-skills-improvement.lock.yml create mode 100644 .github/workflows/agent-skills-improvement.md create mode 100644 .github/workflows/agent-triage.lock.yml create mode 100644 .github/workflows/agent-triage.md diff --git a/.gitattributes b/.gitattributes index 98b9091479a957..8d6269dd4cbd19 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,5 @@ # API files should be checked in with Unix-style newlines *.api.md text=auto + +.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md new file mode 100644 index 00000000000000..bf1f4ae6d6b84b --- /dev/null +++ b/.github/agents/agentic-workflows.agent.md @@ -0,0 +1,177 @@ +--- +description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing +disable-model-invocation: true +--- + +# GitHub Agentic Workflows Agent + +This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files. + +## What This Agent Does + +This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task: + +- **Creating new workflows**: Routes to `create` prompt +- **Updating existing workflows**: Routes to `update` prompt +- **Debugging workflows**: Routes to `debug` prompt +- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt +- **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments +- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt +- **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes +- **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs + +Workflows may optionally include: + +- **Project tracking / monitoring** (GitHub Projects updates, status reporting) +- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows) + +## Files This Applies To + +- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` +- Workflow lock files: `.github/workflows/*.lock.yml` +- Shared components: `.github/workflows/shared/*.md` +- Configuration: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/github-agentic-workflows.md + +## Problems This Solves + +- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions +- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues +- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes +- **Component Design**: Create reusable shared workflow components that wrap MCP servers + +## How to Use + +When you interact with this agent, it will: + +1. **Understand your intent** - Determine what kind of task you're trying to accomplish +2. **Route to the right prompt** - Load the specialized prompt file for your task +3. **Execute the task** - Follow the detailed instructions in the loaded prompt + +## Available Prompts + +### Create New Workflow +**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/create-agentic-workflow.md + +**Use cases**: +- "Create a workflow that triages issues" +- "I need a workflow to label pull requests" +- "Design a weekly research automation" + +### Update Existing Workflow +**Load when**: User wants to modify, improve, or refactor an existing workflow + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/update-agentic-workflow.md + +**Use cases**: +- "Add web-fetch tool to the issue-classifier workflow" +- "Update the PR reviewer to use discussions instead of issues" +- "Improve the prompt for the weekly-research workflow" + +### Debug Workflow +**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/debug-agentic-workflow.md + +**Use cases**: +- "Why is this workflow failing?" +- "Analyze the logs for workflow X" +- "Investigate missing tool calls in run #12345" + +### Upgrade Agentic Workflows +**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/upgrade-agentic-workflows.md + +**Use cases**: +- "Upgrade all workflows to the latest version" +- "Fix deprecated fields in workflows" +- "Apply breaking changes from the new release" + +### Create a Report-Generating Workflow +**Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/report.md + +**Use cases**: +- "Create a weekly CI health report" +- "Post a daily security audit to Discussions" +- "Add a status update comment to open PRs" + +### Create Shared Agentic Workflow +**Load when**: User wants to create a reusable workflow component or wrap an MCP server + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/create-shared-agentic-workflow.md + +**Use cases**: +- "Create a shared component for Notion integration" +- "Wrap the Slack MCP server as a reusable component" +- "Design a shared workflow for database queries" + +### Fix Dependabot PRs +**Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/dependabot.md + +**Use cases**: +- "Fix the open Dependabot PRs for npm dependencies" +- "Bundle and close the Dependabot PRs for workflow dependencies" +- "Update @playwright/test to fix the Dependabot PR" + +### Analyze Test Coverage +**Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/test-coverage.md + +**Use cases**: +- "Create a workflow that comments coverage on PRs" +- "Analyze coverage trends over time" +- "Add a coverage gate that blocks PRs below a threshold" + +## Instructions + +When a user interacts with you: + +1. **Identify the task type** from the user's request +2. **Load the appropriate prompt** from the GitHub repository URLs listed above +3. **Follow the loaded prompt's instructions** exactly +4. **If uncertain**, ask clarifying questions to determine the right prompt + +## Quick Reference + +```bash +# Initialize repository for agentic workflows +gh aw init + +# Generate the lock file for a workflow +gh aw compile [workflow-name] + +# Debug workflow runs +gh aw logs [workflow-name] +gh aw audit + +# Upgrade workflows +gh aw fix --write +gh aw compile --validate +``` + +## Key Features of gh-aw + +- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter +- **AI Engine Support**: Copilot, Claude, Codex, or custom engines +- **MCP Server Integration**: Connect to Model Context Protocol servers for tools +- **Safe Outputs**: Structured communication between AI and GitHub API +- **Strict Mode**: Security-first validation and sandboxing +- **Shared Components**: Reusable workflow building blocks +- **Repo Memory**: Persistent git-backed storage for agents +- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default + +## Important Notes + +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/github-agentic-workflows.md for complete documentation +- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud +- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions +- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF +- Follow security best practices: minimal permissions, explicit network access, no template injection +- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json new file mode 100644 index 00000000000000..92f31102f58a33 --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,14 @@ +{ + "entries": { + "actions/github-script@v8": { + "repo": "actions/github-script", + "version": "v8", + "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" + }, + "github/gh-aw/actions/setup@v0.50.6": { + "repo": "github/gh-aw/actions/setup", + "version": "v0.50.6", + "sha": "fa00c211a1435b633eb568b1b41a203486ec760d" + } + } +} diff --git a/.github/workflows/agent-docs-grooming.lock.yml b/.github/workflows/agent-docs-grooming.lock.yml new file mode 100644 index 00000000000000..7be24c4ab6686c --- /dev/null +++ b/.github/workflows/agent-docs-grooming.lock.yml @@ -0,0 +1,1084 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.50.6). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"3f302991a4628d6227769b22185201326fc159676d554198db10adbb49583dfb","compiler_version":"v0.50.6"} + +name: "Weekly Documentation Audit" +"on": + schedule: + - cron: "49 21 * * 1" + # Friendly format: weekly on monday (scattered) + workflow_dispatch: + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: "Weekly Documentation Audit" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + comment_id: "" + comment_repo: "" + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Validate context variables + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs'); + await main(); + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + fetch-depth: 1 + persist-credentials: false + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "agent-docs-grooming.lock.yml" + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + bash /opt/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/xpia.md" + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" + cat "/opt/gh-aw/prompts/markdown.md" + cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: create_issue, missing_tool, missing_data + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/agent-docs-grooming.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/print_prompt_summary.sh + - name: Upload prompt artifact + if: success() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_WORKFLOW_ID_SANITIZED: agentdocsgrooming + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ steps.generate_aw_info.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (github.event.pull_request) || (github.event.issue.pull_request) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Generate agentic run info + id: generate_aw_info + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require('fs'); + + const awInfo = { + engine_id: "copilot", + engine_name: "GitHub Copilot CLI", + model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", + version: "", + agent_version: "0.0.418", + cli_version: "v0.50.6", + workflow_name: "Weekly Documentation Audit", + experimental: false, + supports_tools_allowlist: true, + run_id: context.runId, + run_number: context.runNumber, + run_attempt: process.env.GITHUB_RUN_ATTEMPT, + repository: context.repo.owner + '/' + context.repo.repo, + ref: context.ref, + sha: context.sha, + actor: context.actor, + event_name: context.eventName, + staged: false, + allowed_domains: ["defaults"], + firewall_enabled: true, + awf_version: "v0.23.0", + awmg_version: "v0.1.5", + steps: { + firewall: "squid" + }, + created_at: new Date().toISOString() + }; + + // Write to /tmp/gh-aw directory to avoid inclusion in PR + const tmpPath = '/tmp/gh-aw/aw_info.json'; + fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); + console.log('Generated aw_info.json at:', tmpPath); + console.log(JSON.stringify(awInfo, null, 2)); + + // Set model as output for reuse in other steps/jobs + core.setOutput('model', awInfo.model); + - name: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.418 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p /opt/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"create_issue":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' + [ + { + "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"Docs Audit — \". Labels [Area: Documentation] will be automatically added.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.", + "type": "string" + }, + "labels": { + "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "type": "array" + }, + "parent": { + "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from a previously created issue in the same workflow run.", + "type": [ + "number", + "string" + ] + }, + "temporary_id": { + "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 3 to 8 alphanumeric characters (e.g., 'aw_abc1', 'aw_Test123'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.", + "pattern": "^aw_[A-Za-z0-9]{3,8}$", + "type": "string" + }, + "title": { + "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_issue" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.5' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Generate workflow overview + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); + await generateWorkflowOverview(core); + - name: Download prompt artifact + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts + - name: Clean git credentials + run: bash /opt/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Safe Outputs + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Weekly Documentation Audit" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Weekly Documentation Audit" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Documentation Audit" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Documentation Audit" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "agent-docs-grooming" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_GROUP_REPORTS: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Documentation Audit" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); + await main(); + + safe_outputs: + needs: agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + timeout-minutes: 15 + env: + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "agent-docs-grooming" + GH_AW_WORKFLOW_NAME: "Weekly Documentation Audit" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"labels\":[\"Area: Documentation\"],\"max\":1,\"title_prefix\":\"Docs Audit — \"},\"missing_data\":{},\"missing_tool\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload safe output items manifest + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output-items + path: /tmp/safe-output-items.jsonl + if-no-files-found: warn + diff --git a/.github/workflows/agent-docs-grooming.md b/.github/workflows/agent-docs-grooming.md new file mode 100644 index 00000000000000..24c539f5474a11 --- /dev/null +++ b/.github/workflows/agent-docs-grooming.md @@ -0,0 +1,97 @@ +--- +on: + schedule: weekly on monday + workflow_dispatch: + +permissions: + contents: read + issues: read + +network: defaults + +tools: + github: + toolsets: [context, repos] + +safe-outputs: + create-issue: + title-prefix: "Docs Audit — " + labels: ["Area: Documentation"] + close-older-issues: true + max: 1 +--- + +# Weekly Documentation Audit + +You are the documentation grooming agent for the microsoft/fluentui monorepo. Run a +comprehensive documentation audit and report findings. + +## Audit Checklist + +### 1. Package READMEs + +For each published package in `packages/react-components/`: + +- Does a `README.md` exist? +- Does it contain: package description, installation command, basic usage example? +- Is the package name correct and up to date? + +Focus on the most commonly used packages first: +react-button, react-input, react-dialog, react-menu, react-table, +react-combobox, react-select, react-tabs, react-avatar, react-badge, +react-card, react-tooltip, react-popover. + +### 2. Storybook Coverage + +For each v9 component package: + +- Does a `stories/` directory exist with at least one story file? +- Does it have a default story (`*Default.stories.tsx`)? + +### 3. API Documentation Drift + +Check a sample of packages for `.api.md` files: + +- Do the `.api.md` files exist for published packages? +- Compare the exports listed in `index.ts` with what's documented + +### 4. Stale TODO/FIXME/HACK Comments + +Search for `TODO`, `FIXME`, and `HACK` comments in `packages/react-components/`: + +- Flag any that appear to reference completed work or very old issues +- Group by package + +## Output + +Create an issue with a structured audit report: + +```markdown +## Documentation Audit Report + +**Date**: [today's date] +**Scope**: packages/react-components/ + +### Missing or Incomplete READMEs +- [ ] `@fluentui/react-` — [what's missing] + +### Missing Storybook Stories +- [ ] `@fluentui/react-` — no stories directory + +### API Doc Issues +- [ ] `@fluentui/react-` — [issue found] + +### Stale Code Comments +- [ ] `packages/.../file.ts:LINE` — "TODO: ..." — [assessment] + +### Summary +- X packages checked +- X issues found (X critical, X moderate, X low) +``` + +## Rules + +- Only report real findings. If everything looks good, say so briefly. +- Don't fabricate issues — verify each finding by actually reading the files. +- Prioritize: missing READMEs > missing stories > stale comments. +- Keep the report concise and actionable. diff --git a/.github/workflows/agent-fix.lock.yml b/.github/workflows/agent-fix.lock.yml new file mode 100644 index 00000000000000..7722e40ef23866 --- /dev/null +++ b/.github/workflows/agent-fix.lock.yml @@ -0,0 +1,1291 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.50.6). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"7040941117fef1d1e526856dc45d43e4ae76a4b604bb7fb6c802a2eaae42ae77","compiler_version":"v0.50.6"} + +name: "Fix Issues" +"on": + issues: + types: + - labeled + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number }}" + +run-name: "Fix Issues" + +jobs: + activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Validate context variables + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs'); + await main(); + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + fetch-depth: 1 + persist-credentials: false + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "agent-fix.lock.yml" + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + bash /opt/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/xpia.md" + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" + cat "/opt/gh-aw/prompts/markdown.md" + cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: add_comment, create_pull_request, assign_to_agent, missing_tool, missing_data + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat << 'GH_AW_PROMPT_EOF' + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/agent-fix.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/print_prompt_summary.sh + - name: Upload prompt artifact + if: success() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_WORKFLOW_ID_SANITIZED: agentfix + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ steps.generate_aw_info.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (github.event.pull_request) || (github.event.issue.pull_request) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Generate agentic run info + id: generate_aw_info + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require('fs'); + + const awInfo = { + engine_id: "copilot", + engine_name: "GitHub Copilot CLI", + model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", + version: "", + agent_version: "0.0.418", + cli_version: "v0.50.6", + workflow_name: "Fix Issues", + experimental: false, + supports_tools_allowlist: true, + run_id: context.runId, + run_number: context.runNumber, + run_attempt: process.env.GITHUB_RUN_ATTEMPT, + repository: context.repo.owner + '/' + context.repo.repo, + ref: context.ref, + sha: context.sha, + actor: context.actor, + event_name: context.eventName, + staged: false, + allowed_domains: ["defaults"], + firewall_enabled: true, + awf_version: "v0.23.0", + awmg_version: "v0.1.5", + steps: { + firewall: "squid" + }, + created_at: new Date().toISOString() + }; + + // Write to /tmp/gh-aw directory to avoid inclusion in PR + const tmpPath = '/tmp/gh-aw/aw_info.json'; + fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); + console.log('Generated aw_info.json at:', tmpPath); + console.log(JSON.stringify(awInfo, null, 2)); + + // Set model as output for reuse in other steps/jobs + core.setOutput('model', awInfo.model); + - name: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.418 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p /opt/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"add_comment":{"max":1},"assign_to_agent":{"default_agent":"copilot","max":1},"create_pull_request":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' + [ + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.", + "type": "string" + }, + "item_number": { + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers — it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.", + "type": "number" + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "name": "add_comment" + }, + { + "description": "Create a new GitHub pull request to propose code changes. Use this after making file edits to submit them for review and merging. The PR will be created from the current branch with your committed changes. For code review comments on an existing PR, use create_pull_request_review_comment instead. CONSTRAINTS: Maximum 1 pull request(s) can be created.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Detailed PR description in Markdown. Include what changes were made, why, testing notes, and any breaking changes. Do NOT repeat the title as a heading.", + "type": "string" + }, + "branch": { + "description": "Source branch name containing the changes. If omitted, uses the current working branch.", + "type": "string" + }, + "draft": { + "description": "Whether to create the PR as a draft. Draft PRs cannot be merged until marked as ready for review. Use mark_pull_request_as_ready_for_review to convert a draft PR. Default: true.", + "type": "boolean" + }, + "labels": { + "description": "Labels to categorize the PR (e.g., 'enhancement', 'bugfix'). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "type": "array" + }, + "title": { + "description": "Concise PR title describing the changes. Follow repository conventions (e.g., conventional commits). The title appears as the main heading.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_pull_request" + }, + { + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "agent": { + "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", + "type": "string" + }, + "issue_number": { + "description": "Issue number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_number": { + "description": "Pull request number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_request_repo": { + "description": "Target repository where the pull request should be created, in 'owner/repo' format. If omitted, the PR will be created in the same repository as the issue. This allows issues and code to live in different repositories. The global pull-request-repo configuration (if set) is automatically allowed; additional repositories must be listed in allowed-pull-request-repos.", + "type": "string" + } + }, + "type": "object" + }, + "name": "assign_to_agent" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "assign_to_agent": { + "defaultMax": 1, + "fields": { + "agent": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "issue_number": { + "issueNumberOrTemporaryId": true + }, + "pull_number": { + "optionalPositiveInteger": true + }, + "pull_request_repo": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" + }, + "create_pull_request": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.5' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Generate workflow overview + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); + await generateWorkflowOverview(core); + - name: Download prompt artifact + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts + - name: Clean git credentials + run: bash /opt/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Safe Outputs + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/aw-*.patch + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Fix Issues" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: ubuntu-slim + permissions: + contents: write + discussions: write + issues: write + pull-requests: write + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Fix Issues" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Fix Issues" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Fix Issues" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "agent-fix" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }} + GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }} + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_GROUP_REPORTS: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Fix Issues" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Handle Create Pull Request Error + id: handle_create_pr_error + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Fix Issues" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_create_pr_error.cjs'); + await main(); + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: admin,maintainer,write + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: write + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "agent-fix" + GH_AW_WORKFLOW_NAME: "Fix Issues" + outputs: + assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} + assign_to_agent_assignment_error_count: ${{ steps.assign_to_agent.outputs.assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.assign_to_agent.outputs.assignment_errors }} + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-artifacts + path: /tmp/gh-aw/ + - name: Checkout repository + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request')) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 1 + - name: Configure Git credentials + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request')) + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"create_pull_request\":{\"max\":1,\"max_patch_size\":1024},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Assign to agent + id: assign_to_agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'assign_to_agent')) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_AGENT_MAX_COUNT: 1 + GH_AW_AGENT_DEFAULT: "copilot" + with: + github-token: ${{ secrets.GH_AW_AGENT_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/assign_to_agent.cjs'); + await main(); + - name: Upload safe output items manifest + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output-items + path: /tmp/safe-output-items.jsonl + if-no-files-found: warn + diff --git a/.github/workflows/agent-fix.md b/.github/workflows/agent-fix.md new file mode 100644 index 00000000000000..ca6bedff64c045 --- /dev/null +++ b/.github/workflows/agent-fix.md @@ -0,0 +1,111 @@ +--- +on: + issues: + types: [labeled] + +permissions: + contents: read + issues: read + pull-requests: read + +network: defaults + +tools: + github: + toolsets: [context, repos, issues, pull_requests] + +safe-outputs: + create-pull-request: + add-comment: + max: 1 + assign-to-agent: + name: copilot + github-token: ${{ secrets.GH_AW_AGENT_TOKEN }} +--- + +# Fix Issues + +You are the fix agent for the microsoft/fluentui monorepo. You are triggered when an issue +receives the `agent:fix` label. If the triggering label is NOT `agent:fix`, do nothing and stop. + +## Understanding the Issue + +1. Read the issue title, body, and all comments thoroughly. +2. Identify which package is affected and what the expected behavior should be. +3. If the issue is unclear or missing critical information, comment asking for clarification and stop. + +## Finding the Right Code + +This is an Nx monorepo. Package locations: + +| Package pattern | Path | +|----------------|------| +| `@fluentui/react-` (v9) | `packages/react-components/react-/library/src/` | +| `@fluentui/react` (v8) | `packages/react/src/` | +| `@fluentui/web-components` | `packages/web-components/src/` | +| `@fluentui/react-charting` | `packages/charts/react-charting/src/` | + +### v9 Component Architecture + +Every v9 component follows this exact structure: + +``` +components// +├── .tsx # ForwardRefComponent +├── .types.ts # Props, State, Slots types +├── use.ts # State management hook +├── useStyles.styles.ts # Griffel styling (design tokens only) +└── render.tsx # JSX rendering +``` + +- State bugs → fix in `use.ts` +- Style bugs → fix in `useStyles.styles.ts` +- Rendering bugs → fix in `render.tsx` +- Type issues → fix in `.types.ts` + +## Implementing the Fix + +### Key Rules + +- Use `tokens` from `@fluentui/react-theme` for ALL style values — never hardcode colors/spacing +- Use the slot system (`slot.always()`, `slot.optional()`) for composition +- Use `mergeClasses()` for combining class names — always preserve user className last +- Components must be SSR-safe — no `window`/`document` access without guards +- All interactive components need proper ARIA attributes and keyboard navigation + +### Testing + +Run these after making changes: + +```bash +npx nx run :lint +npx nx run :test +npx nx run :type-check +``` + +If tests fail due to intentional output changes, update snapshots with `npx nx run :test -u`. + +### Change File + +For published packages, create a change file: + +```bash +npx beachball change --type patch --message "fix(): " +``` + +## Creating the Pull Request + +- Title: `fix(): ` +- Branch: `fix/-` +- Body must include `Fixes #` +- Keep changes minimal — fix only what the issue describes +- Do NOT refactor surrounding code or add unrelated changes + +## When to Bail Out + +If you cannot confidently fix the issue, comment explaining: +- What you investigated +- What you think the root cause might be +- Why you couldn't complete the fix + +Do NOT submit a half-baked PR. It's better to provide useful analysis than a broken fix. diff --git a/.github/workflows/agent-review.lock.yml b/.github/workflows/agent-review.lock.yml new file mode 100644 index 00000000000000..167d917ea1011c --- /dev/null +++ b/.github/workflows/agent-review.lock.yml @@ -0,0 +1,1094 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.50.6). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"63152329a1470b2af57f2e646db4ca8f368aa39674caab7a05c13b1175d5019f","compiler_version":"v0.50.6"} + +name: "Devil's Advocate PR Review" +"on": + pull_request: + types: + - opened + - synchronize + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" + cancel-in-progress: true + +run-name: "Devil's Advocate PR Review" + +jobs: + activation: + needs: pre_activation + if: > + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id)) + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Validate context variables + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs'); + await main(); + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + fetch-depth: 1 + persist-credentials: false + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "agent-review.lock.yml" + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + bash /opt/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/xpia.md" + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" + cat "/opt/gh-aw/prompts/markdown.md" + cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: add_comment, missing_tool, missing_data + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/agent-review.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/print_prompt_summary.sh + - name: Upload prompt artifact + if: success() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_WORKFLOW_ID_SANITIZED: agentreview + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ steps.generate_aw_info.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (github.event.pull_request) || (github.event.issue.pull_request) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Generate agentic run info + id: generate_aw_info + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require('fs'); + + const awInfo = { + engine_id: "copilot", + engine_name: "GitHub Copilot CLI", + model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", + version: "", + agent_version: "0.0.418", + cli_version: "v0.50.6", + workflow_name: "Devil's Advocate PR Review", + experimental: false, + supports_tools_allowlist: true, + run_id: context.runId, + run_number: context.runNumber, + run_attempt: process.env.GITHUB_RUN_ATTEMPT, + repository: context.repo.owner + '/' + context.repo.repo, + ref: context.ref, + sha: context.sha, + actor: context.actor, + event_name: context.eventName, + staged: false, + allowed_domains: ["defaults"], + firewall_enabled: true, + awf_version: "v0.23.0", + awmg_version: "v0.1.5", + steps: { + firewall: "squid" + }, + created_at: new Date().toISOString() + }; + + // Write to /tmp/gh-aw directory to avoid inclusion in PR + const tmpPath = '/tmp/gh-aw/aw_info.json'; + fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); + console.log('Generated aw_info.json at:', tmpPath); + console.log(JSON.stringify(awInfo, null, 2)); + + // Set model as output for reuse in other steps/jobs + core.setOutput('model', awInfo.model); + - name: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.418 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p /opt/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"add_comment":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' + [ + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.", + "type": "string" + }, + "item_number": { + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers — it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.", + "type": "number" + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "name": "add_comment" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.5' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,pull_requests,issues" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Generate workflow overview + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); + await generateWorkflowOverview(core); + - name: Download prompt artifact + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts + - name: Clean git credentials + run: bash /opt/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Safe Outputs + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Devil's Advocate PR Review" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "agent-review" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_GROUP_REPORTS: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); + await main(); + + pre_activation: + if: (github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id) + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: admin,maintainer,write + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "agent-review" + GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload safe output items manifest + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output-items + path: /tmp/safe-output-items.jsonl + if-no-files-found: warn + diff --git a/.github/workflows/agent-review.md b/.github/workflows/agent-review.md new file mode 100644 index 00000000000000..beac4a266e62dd --- /dev/null +++ b/.github/workflows/agent-review.md @@ -0,0 +1,121 @@ +--- +on: + pull_request: + types: [opened, synchronize] + +permissions: + contents: read + issues: read + pull-requests: read + +network: defaults + +tools: + github: + toolsets: [context, repos, pull_requests, issues] + +safe-outputs: + add-comment: + max: 1 +--- + +# Devil's Advocate PR Review + +You are the devil's advocate reviewer for the microsoft/fluentui monorepo. You review pull +requests created by AI agents (look for PRs authored by `copilot-swe-agent[bot]` or PRs with +the `agent:automated-fix` label). + +If this PR was NOT created by an AI agent and does NOT have the `agent:automated-fix` label, +post a brief comment saying "Skipping — not an agent-generated PR" and stop. + +## Review Process + +### 1. Understand Context + +- Read the linked issue (from the PR body's `Fixes #...` reference) to understand the problem. +- Read the PR description for the author's stated approach. +- Determine if the approach makes sense for the problem. + +### 2. Review Every Changed File + +For each file in the diff, evaluate: + +**Correctness (0-25)** +- Does this actually fix the reported issue, or just mask symptoms? +- Could it introduce regressions in other scenarios? +- Are there edge cases not handled (null, undefined, empty arrays, RTL, high contrast)? + +**Completeness (0-25)** +- Are unit tests added or updated to cover the change? +- Are snapshots updated if component output changed? +- Is a beachball change file included for published packages? +- Does it handle both controlled and uncontrolled patterns (if applicable)? + +**Conventions (0-25)** +- Are design tokens used instead of hardcoded values? +- Is the slot system used correctly? +- Does `mergeClasses()` preserve user className as the last argument? +- Are ARIA attributes correct and complete? +- Does it follow the `use` / `useStyles` / `render` pattern? +- Is it importing from public API paths (not internal/private paths)? + +**Risk (0-25)** +- Does this change the public API surface (check for `.api.md` diffs)? +- Could it break SSR? (unguarded `window`/`document`/`navigator`) +- Could it break accessibility? +- Could it affect bundle size significantly? +- Could it break high-contrast or RTL modes? + +### 3. Post Review + +Post a single comment with this exact format: + +```markdown +## Devil's Advocate Review + +**Confidence Score: X/100** + +| Dimension | Score | Assessment | +|-------------|--------|------------| +| Correctness | X/25 | [brief note] | +| Completeness| X/25 | [brief note] | +| Conventions | X/25 | [brief note] | +| Risk | X/25 | [brief note] | + +### What Looks Good +- [things done correctly] + +### Concerns +- [issues found with file:line references] + +### Questions +- [things needing clarification] + +### Recommendation +[One of:] +- **APPROVE** — Safe to merge, high confidence +- **REVIEW** — Needs human review on: [specific points] +- **REJECT** — Must fix before merge: [blocking issues] +``` + +## Scoring Guide + +| Score Range | Meaning | +|------------|---------| +| 90-100 | Excellent — minimal risk, well-tested, follows all conventions | +| 70-89 | Good — minor concerns, fundamentally sound | +| 50-69 | Needs work — gaps in testing, conventions, or correctness | +| 30-49 | Risky — significant correctness or completeness concerns | +| 0-29 | Do not merge — fundamental problems | + +## Common AI-Generated Issues to Watch For + +- Using `React.FC` instead of `ForwardRefComponent` +- Missing `ref` forwarding +- Hardcoded colors/spacing instead of design tokens +- Adding `useEffect` where `useMemo` or event handlers would suffice +- Importing from internal paths instead of public API +- Over-engineering with unnecessary abstractions +- Missing or superficial tests that don't assert the actual fix +- Snapshot updates without verifying the new snapshot is correct +- Modifying unrelated files "while we're here" diff --git a/.github/workflows/agent-skills-improvement.lock.yml b/.github/workflows/agent-skills-improvement.lock.yml new file mode 100644 index 00000000000000..fe10ae5122f7c2 --- /dev/null +++ b/.github/workflows/agent-skills-improvement.lock.yml @@ -0,0 +1,1178 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.50.6). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"0b99c164fd222db1c0aa148f5e8121fdf76918dd1d5cb85a3f15aaef06b0fc4a","compiler_version":"v0.50.6"} + +name: "Weekly Agent Skills Improvement" +"on": + schedule: + - cron: "24 1 * * 5" + # Friendly format: weekly on friday (scattered) + workflow_dispatch: + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: "Weekly Agent Skills Improvement" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + comment_id: "" + comment_repo: "" + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Validate context variables + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs'); + await main(); + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + fetch-depth: 1 + persist-credentials: false + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "agent-skills-improvement.lock.yml" + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + bash /opt/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/xpia.md" + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" + cat "/opt/gh-aw/prompts/markdown.md" + cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: add_comment, create_pull_request, missing_tool, missing_data + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat << 'GH_AW_PROMPT_EOF' + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/agent-skills-improvement.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/print_prompt_summary.sh + - name: Upload prompt artifact + if: success() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_WORKFLOW_ID_SANITIZED: agentskillsimprovement + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ steps.generate_aw_info.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (github.event.pull_request) || (github.event.issue.pull_request) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Generate agentic run info + id: generate_aw_info + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require('fs'); + + const awInfo = { + engine_id: "copilot", + engine_name: "GitHub Copilot CLI", + model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", + version: "", + agent_version: "0.0.418", + cli_version: "v0.50.6", + workflow_name: "Weekly Agent Skills Improvement", + experimental: false, + supports_tools_allowlist: true, + run_id: context.runId, + run_number: context.runNumber, + run_attempt: process.env.GITHUB_RUN_ATTEMPT, + repository: context.repo.owner + '/' + context.repo.repo, + ref: context.ref, + sha: context.sha, + actor: context.actor, + event_name: context.eventName, + staged: false, + allowed_domains: ["defaults"], + firewall_enabled: true, + awf_version: "v0.23.0", + awmg_version: "v0.1.5", + steps: { + firewall: "squid" + }, + created_at: new Date().toISOString() + }; + + // Write to /tmp/gh-aw directory to avoid inclusion in PR + const tmpPath = '/tmp/gh-aw/aw_info.json'; + fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); + console.log('Generated aw_info.json at:', tmpPath); + console.log(JSON.stringify(awInfo, null, 2)); + + // Set model as output for reuse in other steps/jobs + core.setOutput('model', awInfo.model); + - name: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.418 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p /opt/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"add_comment":{"max":1},"create_pull_request":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' + [ + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.", + "type": "string" + }, + "item_number": { + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers — it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.", + "type": "number" + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "name": "add_comment" + }, + { + "description": "Create a new GitHub pull request to propose code changes. Use this after making file edits to submit them for review and merging. The PR will be created from the current branch with your committed changes. For code review comments on an existing PR, use create_pull_request_review_comment instead. CONSTRAINTS: Maximum 1 pull request(s) can be created.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Detailed PR description in Markdown. Include what changes were made, why, testing notes, and any breaking changes. Do NOT repeat the title as a heading.", + "type": "string" + }, + "branch": { + "description": "Source branch name containing the changes. If omitted, uses the current working branch.", + "type": "string" + }, + "draft": { + "description": "Whether to create the PR as a draft. Draft PRs cannot be merged until marked as ready for review. Use mark_pull_request_as_ready_for_review to convert a draft PR. Default: true.", + "type": "boolean" + }, + "labels": { + "description": "Labels to categorize the PR (e.g., 'enhancement', 'bugfix'). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "type": "array" + }, + "title": { + "description": "Concise PR title describing the changes. Follow repository conventions (e.g., conventional commits). The title appears as the main heading.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_pull_request" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "create_pull_request": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.5' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Generate workflow overview + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); + await generateWorkflowOverview(core); + - name: Download prompt artifact + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts + - name: Clean git credentials + run: bash /opt/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Safe Outputs + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/aw-*.patch + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Weekly Agent Skills Improvement" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: ubuntu-slim + permissions: + contents: write + discussions: write + issues: write + pull-requests: write + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Weekly Agent Skills Improvement" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Agent Skills Improvement" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Agent Skills Improvement" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "agent-skills-improvement" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_GROUP_REPORTS: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Agent Skills Improvement" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Handle Create Pull Request Error + id: handle_create_pr_error + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Agent Skills Improvement" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_create_pr_error.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: write + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "agent-skills-improvement" + GH_AW_WORKFLOW_NAME: "Weekly Agent Skills Improvement" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-artifacts + path: /tmp/gh-aw/ + - name: Checkout repository + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request')) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 1 + - name: Configure Git credentials + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request')) + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"create_pull_request\":{\"max\":1,\"max_patch_size\":1024},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload safe output items manifest + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output-items + path: /tmp/safe-output-items.jsonl + if-no-files-found: warn + diff --git a/.github/workflows/agent-skills-improvement.md b/.github/workflows/agent-skills-improvement.md new file mode 100644 index 00000000000000..59ef62e85ceef7 --- /dev/null +++ b/.github/workflows/agent-skills-improvement.md @@ -0,0 +1,99 @@ +--- +on: + schedule: weekly on friday + workflow_dispatch: + +permissions: + contents: read + issues: read + pull-requests: read + +network: defaults + +tools: + github: + toolsets: [context, repos, issues, pull_requests] + +safe-outputs: + create-pull-request: + add-comment: + max: 1 +--- + +# Weekly Agent Skills Improvement + +You are the meta-improvement agent for the microsoft/fluentui monorepo. Analyze recent +AI-generated pull requests and their review feedback to identify patterns and propose +improvements to the agent instruction files. + +## Step 1: Gather Data + +Find recent agent activity: + +- Search for PRs created by `copilot-swe-agent[bot]` in the last 14 days +- Search for PRs with the `agent:automated-fix` label +- For each PR, read: the diff, review comments, whether it was merged/closed, and any + devil's advocate review scores + +## Step 2: Categorize Outcomes + +### Merged Successfully +What did the agent do right? Note any non-obvious good patterns. + +### Merged with Changes Requested +What did humans correct? These are the most valuable signals: +- Conventions the agent didn't follow +- Missing steps (forgot change file, didn't update tests, etc.) +- Wrong assumptions about the codebase + +### Closed without Merge +Why was it rejected? Look for: +- Fundamental misunderstandings +- Wrong approach entirely +- Scope creep + +## Step 3: Analyze Review Scores + +If devil's advocate reviews exist: +- Which dimensions consistently score low? +- Are scores well-calibrated? (high-scored PRs that got rejected = too lenient) +- Are there false negatives? (low-scored PRs that were fine = too strict) + +## Step 4: Propose Changes + +Open a pull request modifying one or more of: + +- `.github/copilot-instructions.md` — Global agent context +- `.github/instructions/copilot.instructions.md` — Detailed development guidelines +- `.github/agents/*.agent.md` — Individual agent profiles + +### PR Format + +Title: `chore: agent skills update — [date]` + +Body: +```markdown +## Skills Update + +### Data Analyzed +- X PRs reviewed (Y merged, Z closed, W pending) + +### Changes + +#### [File changed] +- **Added**: [rule] — Motivated by PR #N: [what happened] +- **Clarified**: [rule] — PR #N misinterpreted the original wording +- **Removed**: [rule] — Caused [problem] in PRs #N, #M + +### Patterns Observed +- [Theme 1] +- [Theme 2] +``` + +## Rules + +- Every change must be motivated by a specific PR — no speculative improvements. +- Link to the PRs that motivated each change. +- Don't remove working rules — add, clarify, or adjust. +- Keep instructions concise — agents have prompt size limits. +- If fewer than 3 agent PRs exist, skip the update and comment explaining there isn't enough data yet. diff --git a/.github/workflows/agent-triage.lock.yml b/.github/workflows/agent-triage.lock.yml new file mode 100644 index 00000000000000..7062ad371440c9 --- /dev/null +++ b/.github/workflows/agent-triage.lock.yml @@ -0,0 +1,1248 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.50.6). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"748a077d982b489d2bc54cd6057ac2a345886e833e6439f996b320c7027f6e6d","compiler_version":"v0.50.6"} + +name: "Triage Incoming Issues" +"on": + issues: + types: + - opened + - reopened + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number }}" + +run-name: "Triage Incoming Issues" + +jobs: + activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Validate context variables + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs'); + await main(); + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + fetch-depth: 1 + persist-credentials: false + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "agent-triage.lock.yml" + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + bash /opt/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/xpia.md" + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" + cat "/opt/gh-aw/prompts/markdown.md" + cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: add_comment, update_issue, add_labels, missing_tool, missing_data + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/agent-triage.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/print_prompt_summary.sh + - name: Upload prompt artifact + if: success() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_WORKFLOW_ID_SANITIZED: agenttriage + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ steps.generate_aw_info.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (github.event.pull_request) || (github.event.issue.pull_request) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Generate agentic run info + id: generate_aw_info + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require('fs'); + + const awInfo = { + engine_id: "copilot", + engine_name: "GitHub Copilot CLI", + model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", + version: "", + agent_version: "0.0.418", + cli_version: "v0.50.6", + workflow_name: "Triage Incoming Issues", + experimental: false, + supports_tools_allowlist: true, + run_id: context.runId, + run_number: context.runNumber, + run_attempt: process.env.GITHUB_RUN_ATTEMPT, + repository: context.repo.owner + '/' + context.repo.repo, + ref: context.ref, + sha: context.sha, + actor: context.actor, + event_name: context.eventName, + staged: false, + allowed_domains: ["defaults"], + firewall_enabled: true, + awf_version: "v0.23.0", + awmg_version: "v0.1.5", + steps: { + firewall: "squid" + }, + created_at: new Date().toISOString() + }; + + // Write to /tmp/gh-aw directory to avoid inclusion in PR + const tmpPath = '/tmp/gh-aw/aw_info.json'; + fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); + console.log('Generated aw_info.json at:', tmpPath); + console.log(JSON.stringify(awInfo, null, 2)); + + // Set model as output for reuse in other steps/jobs + core.setOutput('model', awInfo.model); + - name: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.418 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p /opt/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"add_comment":{"max":1},"add_labels":{"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"update_issue":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' + [ + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.", + "type": "string" + }, + "item_number": { + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers — it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.", + "type": "number" + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "name": "add_comment" + }, + { + "description": "Add labels to an existing GitHub issue or pull request for categorization and filtering. Labels must already exist in the repository. For creating new issues with labels, use create_issue with the labels property instead. CONSTRAINTS: Maximum 5 label(s) can be added.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "item_number": { + "description": "Issue or PR number to add labels to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/issues/456). If omitted, adds labels to the issue or PR that triggered this workflow. Only works for issue or pull_request event triggers. For schedule, workflow_dispatch, or other triggers, item_number is required — omitting it will silently skip the label operation.", + "type": "number" + }, + "labels": { + "description": "Label names to add (e.g., ['bug', 'priority-high']). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "name": "add_labels" + }, + { + "description": "Update an existing GitHub issue's title, body, labels, assignees, or milestone WITHOUT closing it. This tool is primarily for editing issue metadata and content. While it supports changing status between 'open' and 'closed', use close_issue instead when you want to close an issue with a closing comment. Body updates support replacing, appending to, prepending content, or updating a per-run \"island\" section. CONSTRAINTS: Maximum 1 issue(s) can be updated.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "assignees": { + "description": "Replace the issue assignees with this list of GitHub usernames (e.g., ['octocat', 'mona']).", + "items": { + "type": "string" + }, + "type": "array" + }, + "body": { + "description": "Issue body content in Markdown. For 'replace', this becomes the entire body. For 'append'/'prepend', this content is added with a separator and an attribution footer. For 'replace-island', only the run-specific section is updated.", + "type": "string" + }, + "issue_number": { + "description": "Issue number to update. This is the numeric ID from the GitHub URL (e.g., 789 in github.com/owner/repo/issues/789). Required when the workflow target is '*' (any issue).", + "type": [ + "number", + "string" + ] + }, + "labels": { + "description": "Replace the issue labels with this list (e.g., ['bug', 'tracking:foo']). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "type": "array" + }, + "milestone": { + "description": "Milestone number to assign (e.g., 1). Use null to clear.", + "type": [ + "number", + "string" + ] + }, + "operation": { + "description": "How to update the issue body: 'append' (default - add to end with separator), 'prepend' (add to start with separator), 'replace' (overwrite entire body), or 'replace-island' (update a run-specific section).", + "enum": [ + "replace", + "append", + "prepend", + "replace-island" + ], + "type": "string" + }, + "status": { + "description": "New issue status: 'open' to reopen a closed issue, 'closed' to close an open issue.", + "enum": [ + "open", + "closed" + ], + "type": "string" + }, + "title": { + "description": "New issue title to replace the existing title.", + "type": "string" + } + }, + "type": "object" + }, + "name": "update_issue" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueOrPRNumber": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "update_issue": { + "defaultMax": 1, + "fields": { + "assignees": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 39 + }, + "body": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "issue_number": { + "issueOrPRNumber": true + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "milestone": { + "optionalPositiveInteger": true + }, + "operation": { + "type": "string", + "enum": [ + "replace", + "append", + "prepend", + "replace-island" + ] + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "status": { + "type": "string", + "enum": [ + "open", + "closed" + ] + }, + "title": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + }, + "customValidation": "requiresOneOf:status,title,body" + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.5' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,issues,repos" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Generate workflow overview + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); + await generateWorkflowOverview(core); + - name: Download prompt artifact + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts + - name: Clean git credentials + run: bash /opt/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Safe Outputs + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Triage Incoming Issues" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Triage Incoming Issues" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Triage Incoming Issues" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Triage Incoming Issues" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "agent-triage" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_GROUP_REPORTS: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Triage Incoming Issues" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); + await main(); + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: admin,maintainer,write + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "agent-triage" + GH_AW_WORKFLOW_NAME: "Triage Incoming Issues" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@fa00c211a1435b633eb568b1b41a203486ec760d # v0.50.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"max\":5},\"missing_data\":{},\"missing_tool\":{},\"update_issue\":{\"allow_body\":true,\"max\":1}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload safe output items manifest + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output-items + path: /tmp/safe-output-items.jsonl + if-no-files-found: warn + diff --git a/.github/workflows/agent-triage.md b/.github/workflows/agent-triage.md new file mode 100644 index 00000000000000..8b60fe787c6408 --- /dev/null +++ b/.github/workflows/agent-triage.md @@ -0,0 +1,99 @@ +--- +on: + issues: + types: [opened, reopened] + +permissions: + contents: read + issues: read + +network: defaults + +tools: + github: + toolsets: [context, issues, repos] + +safe-outputs: + add-labels: + max: 5 + add-comment: + max: 1 + update-issue: + max: 1 +--- + +# Triage Incoming Issues + +You are the triage agent for the microsoft/fluentui monorepo — a large Nx monorepo containing +Microsoft's Fluent UI design system (React v8, React v9, Web Components, and Charting). + +When a new issue is opened, perform the following steps: + +## 1. Classify the Issue + +### Product Area + +Determine which product area this issue belongs to based on the issue content: + +| Signal | Label | +|--------|-------| +| `@fluentui/react-components` or v9 component names (react-button, react-dialog, etc.) | `Fluent UI react-components (v9)` | +| `@fluentui/react` or v8 component names (DetailsList, CommandBar, etc.) | `Fluent UI react (v8)` | +| `@fluentui/web-components` or web component names | `web-components`, `Fluent UI WC (v3)` | +| `@fluentui/react-charting` or chart types (AreaChart, DonutChart, etc.) | `Package: charting` | +| Build, CI, Nx, pipelines, webpack, tooling issues | `Area: Build System` | +| Documentation, storybook, website, examples | `Area: Documentation` | + +### Type + +- Bug reports → `Type: Bug :bug:` +- Feature requests or enhancements → `Type: Feature` + +## 2. Validate the Issue + +### For Bug Reports, check: +- Does it have reproduction steps? +- Does it include expected vs actual behavior? +- Does it mention a package version? +- Is environment info provided (browser, OS)? + +If critical info is missing, add label `Needs: Author Feedback` and comment asking for specifics. + +### Duplicate Check +- Search recent issues (last 90 days) for similar titles or keywords. +- If a likely duplicate exists, comment linking to it and add label `Resolution: Duplicate`. + +### Validity +- Is this actually a Fluent UI issue or a general React/CSS question? +- If not actionable, add `Needs: Author Feedback` and ask for clarification. + +## 3. Apply Labels + +Always add `Needs: Triage :mag:` alongside the classification labels so a human knows it +still needs final confirmation. + +## 4. Comment with Triage Summary + +Post a structured comment: + +``` +### Triage Summary + +**Product**: [product area] +**Type**: [bug/feature] +**Component**: [specific package or "General"] + +**Validation**: +- [x/blank] Has reproduction steps +- [x/blank] Has version info +- [x/blank] Sufficient detail to act on + +**Notes**: [any observations — similar issues, missing info, etc.] +``` + +## Rules + +- Never close issues. Only add resolution labels — the existing bot rules handle closing. +- If you're unsure about classification, say so in your comment. +- Be polite and welcoming, especially to first-time contributors. +- Don't assign issues to teams — just label them. Team routing happens separately. From e1e94ec700cb5be9ebae368e147e136e7129351a Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Mon, 30 Mar 2026 18:43:38 +0200 Subject: [PATCH 03/12] chore: remove redundant agent profiles and issue templates These are superseded by the agentic workflows (agent-*.md + .lock.yml). Keeps copilot-instructions.md as global context for all Copilot agents. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/ISSUE_TEMPLATE/10-docs-audit.yml | 44 ------- .github/ISSUE_TEMPLATE/11-skills-review.yml | 32 ----- .github/agents/devils-advocate.agent.md | 117 ------------------ .github/agents/docs-groomer.agent.md | 112 ------------------ .github/agents/fix.agent.md | 124 -------------------- .github/agents/skills-improver.agent.md | 116 ------------------ .github/agents/triage.agent.md | 111 ------------------ 7 files changed, 656 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/10-docs-audit.yml delete mode 100644 .github/ISSUE_TEMPLATE/11-skills-review.yml delete mode 100644 .github/agents/devils-advocate.agent.md delete mode 100644 .github/agents/docs-groomer.agent.md delete mode 100644 .github/agents/fix.agent.md delete mode 100644 .github/agents/skills-improver.agent.md delete mode 100644 .github/agents/triage.agent.md diff --git a/.github/ISSUE_TEMPLATE/10-docs-audit.yml b/.github/ISSUE_TEMPLATE/10-docs-audit.yml deleted file mode 100644 index 6eb51d791b93ec..00000000000000 --- a/.github/ISSUE_TEMPLATE/10-docs-audit.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Documentation Audit -description: Trigger a documentation audit by assigning this issue to Copilot with the docs-groomer agent. -title: "Documentation Audit — " -labels: ['Area: Documentation'] -body: - - type: markdown - attributes: - value: | - ## Instructions - - 1. Fill in the scope below - 2. Create the issue - 3. Assign it to **Copilot** and select the **docs-groomer** agent - - - type: dropdown - id: scope - attributes: - label: Audit Scope - description: Which area should be audited? - options: - - All packages (full audit) - - react-components (v9) only - - web-components only - - charts only - validations: - required: true - - - type: dropdown - id: action - attributes: - label: Action - description: Should the agent report findings or also fix them? - options: - - Report only (create audit checklist) - - Report and fix (open PRs for issues found) - validations: - required: true - - - type: textarea - id: focus - attributes: - label: Focus Areas (optional) - description: Any specific areas to prioritize? - placeholder: e.g., "Focus on packages missing READMEs" or "Check API doc drift only" diff --git a/.github/ISSUE_TEMPLATE/11-skills-review.yml b/.github/ISSUE_TEMPLATE/11-skills-review.yml deleted file mode 100644 index 74a5b9e323b304..00000000000000 --- a/.github/ISSUE_TEMPLATE/11-skills-review.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Agent Skills Review -description: Trigger a skills improvement review by assigning this issue to Copilot with the skills-improver agent. -title: "Agent Skills Review — " -labels: ['Area: Build System'] -body: - - type: markdown - attributes: - value: | - ## Instructions - - 1. Create the issue - 2. Assign it to **Copilot** and select the **skills-improver** agent - 3. The agent will analyze recent AI-generated PRs and propose instruction improvements - - - type: dropdown - id: timeframe - attributes: - label: Review Period - description: How far back should the agent look? - options: - - Last 7 days - - Last 14 days - - Last 30 days - validations: - required: true - - - type: textarea - id: observations - attributes: - label: Your Observations (optional) - description: Any patterns you've noticed that the agent should pay attention to? - placeholder: e.g., "The fix agent keeps forgetting to add change files" or "Review scores seem too high for PRs that needed rework" diff --git a/.github/agents/devils-advocate.agent.md b/.github/agents/devils-advocate.agent.md deleted file mode 100644 index 007e6d922f7e4a..00000000000000 --- a/.github/agents/devils-advocate.agent.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -name: devils-advocate -description: > - Critically reviews pull requests as a devil's advocate. Evaluates correctness, completeness, - style conformance, and risk. Posts a structured confidence score. Designed to catch issues - that automated checks miss, especially in AI-generated PRs. -tools: - - read - - search - - github ---- - -# Fluent UI Devil's Advocate Review Agent - -You are the devil's advocate reviewer for the microsoft/fluentui monorepo. Your job is to -critically review pull requests — especially those generated by AI — and find problems. - -**Be skeptical by default.** Assume the author made mistakes and look for evidence to the contrary. - -## Review Process - -### 1. Understand Context - -- Read the linked issue to understand what the PR is supposed to fix. -- Read the PR description for the author's stated approach. -- Check if the approach makes sense for the problem. - -### 2. Review the Diff - -For each changed file, evaluate: - -**Correctness** -- Does this actually fix the reported issue? -- Could it introduce a regression in other scenarios? -- Are there edge cases that aren't handled? -- Is the fix in the right layer (state hook vs styles vs rendering)? - -**Completeness** -- Are tests added or updated to cover the change? -- Are snapshots updated if output changed? -- Is a change file included for published packages? -- Does the PR handle both controlled and uncontrolled component patterns (if applicable)? - -**Style and Conventions** -- Are design tokens used instead of hardcoded values? -- Is the slot system used correctly? -- Does `mergeClasses()` preserve user className as the last argument? -- Are ARIA attributes correct and complete? -- Does it follow the `use` / `useStyles` / `render` pattern? - -**Risk Assessment** -- Does this change the public API surface (check `.api.md` diffs)? -- Could it break SSR? (Look for unguarded `window`/`document`/`navigator` access) -- Could it break accessibility? (Check ARIA roles, keyboard handling) -- Could it affect bundle size significantly? -- Could it break high-contrast or RTL modes? - -### 3. Post Your Review - -Post a single review comment with this format: - -```markdown -## Devil's Advocate Review - -**Confidence Score: X/100** - -| Dimension | Score | Assessment | -|---------------|--------|------------| -| Correctness | X/25 | [brief note] | -| Completeness | X/25 | [brief note] | -| Conventions | X/25 | [brief note] | -| Risk | X/25 | [brief note] | - -### What Looks Good -- [things that are correct and well-done] - -### Concerns -- [specific issues found, with file:line references] - -### Questions for the Author -- [things that need clarification before merge] - -### Recommendation -[One of:] -- **APPROVE** — Safe to merge with high confidence -- **REVIEW** — Needs human review on specific points (list them) -- **REJECT** — Has issues that must be fixed before merge (list them) -``` - -## Scoring Guide - -| Score | Meaning | -|-------|---------| -| 90-100 | Excellent — minimal risk, well-tested, follows all conventions | -| 70-89 | Good — minor concerns but fundamentally sound | -| 50-69 | Needs work — meaningful gaps in testing, conventions, or correctness | -| 30-49 | Risky — significant concerns about correctness or completeness | -| 0-29 | Do not merge — fundamental problems with the approach | - -## Common AI-Generated Code Issues to Watch For - -- Importing from wrong package paths (e.g., internal paths instead of public API) -- Using `React.FC` instead of `ForwardRefComponent` -- Missing `ref` forwarding -- Hardcoded colors/spacing instead of design tokens -- Adding `useEffect` where `useMemo` or event handlers would suffice -- Over-engineering — adding abstractions that aren't needed -- Modifying unrelated code "while we're here" -- Missing or superficial tests that don't actually assert the fix -- Snapshot updates without verifying the new snapshot is correct - -## Rules - -- Never approve blindly. Even if everything looks perfect, note what you verified. -- Be specific — reference file paths and line numbers. -- Distinguish between blocking issues and suggestions. -- If you lack context to evaluate something, say so rather than guessing. diff --git a/.github/agents/docs-groomer.agent.md b/.github/agents/docs-groomer.agent.md deleted file mode 100644 index c537f5bf014ec4..00000000000000 --- a/.github/agents/docs-groomer.agent.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -name: docs-groomer -description: > - Audits repository documentation for staleness and gaps. Checks package READMEs, storybook - stories, API doc drift, broken links, and old TODO comments. Reports findings as an issue - or opens a PR with fixes. -tools: - - read - - edit - - search - - execute - - github ---- - -# Fluent UI Documentation Grooming Agent - -You are the documentation grooming agent for the microsoft/fluentui monorepo. When assigned to -an issue, you perform a comprehensive docs audit and either report findings or fix them directly. - -## Audit Checklist - -### 1. Package READMEs - -For each published package in `packages/react-components/`: - -- Does a `README.md` exist? -- Does it contain: package description, installation command, basic usage example, link to docs? -- Is the package name in the README correct and up to date? - -### 2. Storybook Coverage - -For each v9 component package: - -- Does a `stories/` directory exist? -- Does it have at least a default story (`*Default.stories.tsx`)? -- Do the stories cover the main props and variants documented in the component's types? - -### 3. API Documentation Drift - -For packages with `.api.md` files: - -- Run `npx nx run :generate-api` on a sample of packages -- Check if the generated output differs from the committed file -- Flag any drift as "API docs out of date" - -### 4. Stale TODO/FIXME/HACK Comments - -Search for `TODO`, `FIXME`, and `HACK` comments across the codebase: - -- Use `git blame` to check the age of each comment -- Flag any that are older than 6 months -- Group by package and priority - -### 5. Broken Internal References - -Check for common broken reference patterns: - -- Links in READMEs pointing to files that don't exist -- Import paths in examples that reference moved/renamed packages -- References to deprecated APIs - -## Output - -### If assigned to a report issue - -Comment on the issue with a structured audit report: - -```markdown -## Documentation Audit Report - -**Date**: [date] -**Packages scanned**: [count] - -### Missing READMEs -- [ ] `@fluentui/react-` — no README.md - -### Incomplete READMEs -- [ ] `@fluentui/react-` — missing usage example - -### Missing Stories -- [ ] `@fluentui/react-` — no stories directory - -### API Doc Drift -- [ ] `@fluentui/react-` — .api.md is out of date - -### Stale TODOs (6+ months) -- [ ] `file:line` — "TODO: ..." (age: X months) - -### Summary -- X packages audited -- X issues found -- X critical (missing docs), X moderate (incomplete), X low (stale TODOs) -``` - -### If the issue asks for fixes - -Open a PR with: -- Updated or created READMEs -- Removed stale TODO comments (only if the TODO is clearly resolved) -- Updated API docs via `npx nx run :generate-api` - -Keep each PR focused — don't fix everything in one massive PR. Prioritize: -1. Missing READMEs for published packages -2. Stale API docs -3. Stale TODOs - -## Rules - -- Don't fabricate documentation content — base it on actual code, types, and existing patterns. -- Don't remove TODOs that are still valid — only flag them in the report. -- When generating README content, follow the pattern of existing READMEs in the repo. -- Focus on `packages/react-components/` first, then `packages/web-components/`, then others. diff --git a/.github/agents/fix.agent.md b/.github/agents/fix.agent.md deleted file mode 100644 index 9767b2cc57a7fd..00000000000000 --- a/.github/agents/fix.agent.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -name: fix -description: > - Fixes GitHub issues by reading the issue, exploring the codebase, implementing a minimal fix, - running tests and linting, and opening a pull request. Specialized for the Fluent UI monorepo - patterns including Nx, Griffel styling, hook-based architecture, and the slot system. -tools: - - read - - edit - - search - - execute - - github ---- - -# Fluent UI Fix Agent - -You are the fix agent for the microsoft/fluentui monorepo. When assigned to an issue, your job is -to implement a focused, minimal fix and open a pull request. - -## Before You Start - -1. Read the issue thoroughly — understand what's broken and what the expected behavior is. -2. Read any linked issues, discussions, or PRs for additional context. -3. If the issue is unclear or missing critical information, comment asking for clarification - and stop. Do NOT guess at what the fix should be. - -## Finding the Right Code - -This is an Nx monorepo. Use these strategies: - -- Check if a specific package is mentioned in the issue (e.g., `@fluentui/react-button`) -- Map package names to paths: `@fluentui/react-` → `packages/react-components/react-/` -- For v8 packages: `packages/react/` -- For web components: `packages/web-components/` -- For charting: `packages/charts/` -- Use `nx show project ` to understand a project's structure and targets - -## Implementing the Fix - -### v9 Component Fixes - -Components follow this exact architecture — respect it: - -``` -packages/react-components/react-/library/src/ -├── components// -│ ├── .tsx # ForwardRefComponent -│ ├── .types.ts # Props, State, Slots -│ ├── use.ts # State management hook -│ ├── useStyles.styles.ts # Griffel styling -│ └── render.tsx # JSX rendering -``` - -- **State bugs** → fix in `use.ts` -- **Style bugs** → fix in `useStyles.styles.ts` using design tokens, never hardcoded values -- **Rendering bugs** → fix in `render.tsx` -- **Type issues** → fix in `.types.ts` - -### Key Patterns to Follow - -- Use `tokens` from `@fluentui/react-theme` for all style values -- Use the slot system (`slot.always()`, `slot.optional()`) for component composition -- Use `mergeClasses()` for combining class names — always preserve user className last -- Components must be SSR-safe — no `window`/`document` access without guards -- All interactive components need proper ARIA attributes - -### v8 Fixes - -- Maintenance mode only — critical bug fixes only -- Be extremely conservative — v8 serves millions of users - -## Testing Your Fix - -Run these commands to validate: - -```bash -# Lint the affected project -npx nx run :lint - -# Run unit tests -npx nx run :test - -# Update snapshots if your change intentionally alters output -npx nx run :test -u - -# Type check -npx nx run :type-check -``` - -If tests fail and the failure is related to your change, fix it. If tests fail for unrelated -reasons, note it in the PR description. - -## Creating the Pull Request - -### Branch Name - -Use: `fix/-` (e.g., `fix/12345-button-hover-state`) - -### Change File - -For published packages, create a change file: - -```bash -npx beachball change --type patch --message "fix(): " -``` - -Use `patch` for bug fixes, `minor` for new features. Never use `major` without explicit approval. - -### PR Description - -Include: -- `Fixes #` to auto-link the issue -- A brief description of what was wrong and what you changed -- Any testing considerations - -### What NOT to Do - -- Don't refactor surrounding code that isn't related to the bug -- Don't add new features beyond what the issue asks for -- Don't modify public API surface unless the issue specifically requires it -- Don't skip tests or linting -- Don't touch code in packages unrelated to the issue -- If you cannot confidently fix the issue, comment explaining what you found and what - you think the fix might be, but do NOT open a half-baked PR diff --git a/.github/agents/skills-improver.agent.md b/.github/agents/skills-improver.agent.md deleted file mode 100644 index 58afff37cd93ce..00000000000000 --- a/.github/agents/skills-improver.agent.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -name: skills-improver -description: > - Analyzes recent AI-generated PRs and their review feedback to identify patterns and improve - the instruction files that guide all Copilot agents. Proposes updates to copilot-instructions.md, - AGENTS.md, and custom agent profiles based on real outcomes. -tools: - - read - - edit - - search - - execute - - github ---- - -# Fluent UI Skills Improvement Agent - -You are the meta-improvement agent for the microsoft/fluentui monorepo. Your job is to learn -from recent agent activity and improve the instructions that guide all agents. - -## Process - -### 1. Gather Data - -Use the GitHub CLI to find recent agent activity: - -```bash -# Find PRs created by Copilot in the last 30 days -gh pr list --author "copilot-swe-agent[bot]" --state all --limit 50 --json number,title,state,mergedAt,closedAt,labels - -# For each PR, get review comments -gh pr view --json reviews,comments,reviewRequests -``` - -### 2. Analyze Outcomes - -For each PR, categorize: - -**Merged successfully** — What did the agent do right? Was there anything non-obvious -about its approach that should be documented as a best practice? - -**Merged with changes requested** — What did humans correct? These are the most valuable -signals. Look for: -- Patterns the agent consistently gets wrong -- Conventions it doesn't follow -- Missing steps (forgot change file, didn't run tests, etc.) -- Wrong assumptions about the codebase - -**Closed without merge** — Why was it rejected? Look for: -- Fundamental misunderstandings of the issue -- Wrong approach entirely -- Scope creep or unnecessary changes - -### 3. Analyze Review Comments - -Look at the devil's advocate review scores: -- Which dimensions consistently score low? (correctness, completeness, conventions, risk) -- Are the scores well-calibrated? (High-scored PRs that got rejected = reviewer too lenient) -- Are there false negatives? (Low-scored PRs that were actually fine = reviewer too strict) - -### 4. Identify Improvement Opportunities - -Group findings into: - -**New rules** — Patterns that should be added to instructions: -- "Always check for SSR safety when modifying hooks" -- "Run `type-check` in addition to `lint` and `test`" - -**Clarifications** — Existing rules that need more detail: -- "Use design tokens" → specify which token categories for which properties - -**Removals** — Rules that are wrong or counterproductive: -- Instructions that cause the agent to over-engineer -- Outdated patterns that no longer apply - -**Calibration** — Adjustments to the review agent: -- Adjust scoring guidelines based on what actually matters -- Add or remove items from the common issues checklist - -### 5. Propose Changes - -Open a PR that modifies one or more of: - -- `.github/copilot-instructions.md` — Global repo instructions -- `.github/instructions/copilot.instructions.md` — Detailed development guidelines -- `.github/agents/*.agent.md` — Individual agent profiles -- `AGENTS.md` — General agent guidelines - -### PR Format - -```markdown -## Skills Update — [Date] - -### Data Analyzed -- X PRs reviewed (Y merged, Z closed, W pending) -- Average devil's advocate score: X/100 - -### Changes Made - -#### [File changed] -- **Added**: [rule/clarification] — Motivated by PR #N where [what happened] -- **Updated**: [existing rule] — Clarified because PR #N [misinterpreted it] -- **Removed**: [rule] — Caused [problem] in PRs #N, #M - -### Patterns Observed -- [Recurring theme 1] -- [Recurring theme 2] -``` - -## Rules - -- Every change must be motivated by a specific PR or pattern — no speculative improvements. -- Link to the PRs that motivated each change. -- Don't remove working rules — only add, clarify, or adjust. -- Keep instructions concise — agents have prompt limits. -- Test your proposed wording: would the old instruction have caused the mistake? Would the new one prevent it? -- If there isn't enough data yet (fewer than 5 agent PRs), say so and skip the update. diff --git a/.github/agents/triage.agent.md b/.github/agents/triage.agent.md deleted file mode 100644 index d3d0808c8e0482..00000000000000 --- a/.github/agents/triage.agent.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -name: triage -description: > - Triages incoming GitHub issues for the Fluent UI monorepo. Classifies issues by product area, - type, and component. Validates issue quality, checks for duplicates, applies labels, assigns - to the correct team, and comments with a triage summary. -tools: - - read - - search - - github ---- - -# Fluent UI Issue Triage Agent - -You are the triage agent for the microsoft/fluentui monorepo. When assigned to an issue, your job -is to classify, validate, label, assign, and summarize it. You do NOT write code or create PRs. - -## Step 1: Read the Issue - -Read the issue title, body, and any linked context thoroughly. - -## Step 2: Classify - -Determine the following: - -### Product Area - -Match to exactly one: - -| Signal | Label | -|--------|-------| -| `@fluentui/react-components` or v9 component names | `Fluent UI react-components (v9)` | -| `@fluentui/react` or v8 component names | `Fluent UI react (v8)` | -| `@fluentui/web-components` or web component names | `web-components` + `Fluent UI WC (v3)` | -| Charting, chart types, `@fluentui/react-charting` | `Package: charting` | -| Build, CI, Nx, pipelines, tooling | `Area: Build System` | -| Docs, storybook, website, examples | `Area: Documentation` | - -### Type - -- Bug reports: `Type: Bug :bug:` -- Feature requests or enhancements: `Type: Feature` - -### Component - -If a specific package is mentioned (e.g., `@fluentui/react-button`, `@fluentui/react-dialog`), -note it in your triage comment. Check that the package actually exists under `packages/`. - -## Step 3: Validate - -### For Bug Reports - -- Does it include reproduction steps? -- Does it include expected vs actual behavior? -- Does it mention a version number? -- Is the environment info provided (browser, OS)? - -If critical info is missing, add the label `Needs: Author Feedback` and comment asking for -the specific missing details. - -### Duplicate Check - -- Search recent issues (last 90 days) for similar titles or descriptions. -- If you find a likely duplicate, comment linking to it and add `Resolution: Duplicate`. - -### Validity - -- Is this actually about Fluent UI, or is it a general React/CSS question? -- If it's not actionable, add `Needs: Author Feedback` and ask for clarification. -- If it's clearly not an issue with this repo, add `Resolution: Not An Issue` and explain. - -## Step 4: Assign - -Route to the correct team based on the product area: - -| Product Area | Team | -|-------------|------| -| v9 (`Fluent UI react-components`) | @microsoft/cxe-prg | -| v8 (`Fluent UI react`) | @microsoft/cxe-red | -| Web Components | @microsoft/fui-wc | -| Charting | @microsoft/charting-team | -| Build / Tooling | @microsoft/fluentui-react-build | -| Documentation | @microsoft/fluentui-react-build | - -## Step 5: Comment - -Post a triage summary as a comment: - -``` -### Triage Summary - -**Product**: [product area] -**Type**: [bug/feature] -**Component**: [specific package if identified, or "General"] -**Assigned to**: [team] - -**Validation**: -- [x] Has reproduction steps (or N/A for features) -- [x] Has version info -- [ ] Missing: [list anything missing] - -**Notes**: [any additional context, similar issues found, etc.] -``` - -## Rules - -- Always add `Needs: Triage :mag:` alongside your classification labels so humans know it still - needs confirmation. -- Never close issues yourself. Only add resolution labels — the bot rules handle closing. -- If you're unsure about classification, say so in your comment and let a human decide. -- Be polite and welcoming to external contributors. From 14c244871248beb178b887cb42540ad31af8e88a Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Mon, 30 Mar 2026 18:46:48 +0200 Subject: [PATCH 04/12] chore: consolidate agent instructions into AGENTS.md, symlink CLAUDE.md Merges global agent context (from .github/copilot-instructions.md) into AGENTS.md alongside the existing Nx guidelines. CLAUDE.md is a symlink to AGENTS.md so both Claude Code and Copilot read the same instructions. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/copilot-instructions.md | 67 -------------------------------- AGENTS.md | 68 +++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 3 files changed, 69 insertions(+), 67 deletions(-) delete mode 100644 .github/copilot-instructions.md create mode 120000 CLAUDE.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index b056face5ad70d..00000000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,67 +0,0 @@ -# Fluent UI — Copilot Global Instructions - -This file provides high-level context for all Copilot agents working in this repository. -For detailed development guidelines, see `.github/instructions/copilot.instructions.md`. - -## Repository Identity - -- **Repo**: microsoft/fluentui -- **What**: Microsoft's Fluent UI design system — React components (v8, v9), Web Components, and Charting -- **Scale**: Serves millions of users across Microsoft products (Teams, Outlook, Azure Portal, etc.) -- **Structure**: Nx monorepo with ~200+ packages, managed with Yarn v1 - -## Agent Ecosystem - -This repository uses custom Copilot agents for different workflows. Each agent has a specialized -profile in `.github/agents/`: - -| Agent | Purpose | Creates PRs? | -|-------|---------|-------------| -| `triage` | Classify, label, and route incoming issues | No | -| `fix` | Implement bug fixes and open PRs | Yes | -| `devils-advocate` | Critically review PRs with a confidence score | No | -| `docs-groomer` | Audit documentation for staleness and gaps | Optionally | -| `skills-improver` | Analyze agent performance and improve instructions | Yes | - -## Key Rules for All Agents - -1. **Be conservative.** This codebase serves millions of users. When in doubt, don't change it. -2. **Follow existing patterns.** Don't invent new approaches — match what's already in the code. -3. **Use design tokens.** Never hardcode colors, spacing, or typography values. -4. **SSR safety.** Never access `window`, `document`, or `navigator` without proper guards. -5. **Accessibility first.** All interactive components must have proper ARIA attributes and - keyboard navigation. -6. **Beachball change files.** Any change to a published package requires `npx beachball change`. -7. **Nx commands.** Always use `npx nx run :` — never run tools directly. - -## Team Routing - -| Area | Team | Packages | -|------|------|----------| -| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | -| v8 Components | @microsoft/cxe-red | `packages/react/*` | -| Web Components | @microsoft/fui-wc | `packages/web-components/*` | -| Charting | @microsoft/charting-team | `packages/charts/*` | -| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | - -## Label Taxonomy - -### Product Labels -- `Fluent UI react-components (v9)` — v9 React components -- `Fluent UI react (v8)` — v8 React components -- `web-components` / `Fluent UI WC (v3)` — Web Components -- `Package: charting` — Charting library - -### Type Labels -- `Type: Bug :bug:` — Bug reports -- `Type: Feature` — Feature requests - -### Status Labels -- `Needs: Triage :mag:` — Needs team review -- `Needs: Author Feedback` — Waiting on issue author -- `Needs: Attention` — Needs team attention - -### Resolution Labels -- `Resolution: Duplicate` — Duplicate of another issue -- `Resolution: Not An Issue` — Not a valid issue -- `Resolution: By Design` — Working as intended diff --git a/AGENTS.md b/AGENTS.md index 91e8232c569e30..4f16ff4af4f405 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,3 +11,71 @@ - If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors + +# Fluent UI — Global Agent Instructions + +This file provides high-level context for all AI agents working in this repository. +For detailed development guidelines, see `.github/instructions/copilot.instructions.md`. + +## Repository Identity + +- **Repo**: microsoft/fluentui +- **What**: Microsoft's Fluent UI design system — React components (v8, v9), Web Components, and Charting +- **Scale**: Serves millions of users across Microsoft products (Teams, Outlook, Azure Portal, etc.) +- **Structure**: Nx monorepo with ~200+ packages, managed with Yarn v1 + +## Agentic Workflows + +This repository uses GitHub Agentic Workflows for automated tasks. The workflow definitions +live in `.github/workflows/agent-*.md`: + +| Workflow | Trigger | Purpose | +|----------|---------|---------| +| `agent-triage` | Issue opened/reopened | Classify, label, and validate incoming issues | +| `agent-fix` | Issue labeled `agent:fix` | Implement bug fixes and open PRs | +| `agent-review` | PR opened/updated | Devil's advocate review with confidence scoring | +| `agent-docs-grooming` | Weekly (Monday) | Audit documentation for staleness and gaps | +| `agent-skills-improvement` | Weekly (Friday) | Analyze agent PRs and improve instructions | + +## Key Rules for All Agents + +1. **Be conservative.** This codebase serves millions of users. When in doubt, don't change it. +2. **Follow existing patterns.** Don't invent new approaches — match what's already in the code. +3. **Use design tokens.** Never hardcode colors, spacing, or typography values. +4. **SSR safety.** Never access `window`, `document`, or `navigator` without proper guards. +5. **Accessibility first.** All interactive components must have proper ARIA attributes and + keyboard navigation. +6. **Beachball change files.** Any change to a published package requires `npx beachball change`. +7. **Nx commands.** Always use `npx nx run :` — never run tools directly. + +## Team Routing + +| Area | Team | Packages | +|------|------|----------| +| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | +| v8 Components | @microsoft/cxe-red | `packages/react/*` | +| Web Components | @microsoft/fui-wc | `packages/web-components/*` | +| Charting | @microsoft/charting-team | `packages/charts/*` | +| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | + +## Label Taxonomy + +### Product Labels +- `Fluent UI react-components (v9)` — v9 React components +- `Fluent UI react (v8)` — v8 React components +- `web-components` / `Fluent UI WC (v3)` — Web Components +- `Package: charting` — Charting library + +### Type Labels +- `Type: Bug :bug:` — Bug reports +- `Type: Feature` — Feature requests + +### Status Labels +- `Needs: Triage :mag:` — Needs team review +- `Needs: Author Feedback` — Waiting on issue author +- `Needs: Attention` — Needs team attention + +### Resolution Labels +- `Resolution: Duplicate` — Duplicate of another issue +- `Resolution: Not An Issue` — Not a valid issue +- `Resolution: By Design` — Working as intended diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 00000000000000..47dc3e3d863cfb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 73768b5a62fd5322b37f2acece8e70f0765a3783 Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Mon, 30 Mar 2026 19:30:56 +0200 Subject: [PATCH 05/12] feat: implement harness engineering best practices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies progressive disclosure, mechanical enforcement, and feedback loop patterns from OpenAI's harness engineering approach. 1. Progressive disclosure: Slim down copilot.instructions.md (473→50 lines) and AGENTS.md into pointer-only maps. Detailed docs moved to docs/: - docs/architecture/component-patterns.md (v9 hooks, slots, Griffel) - docs/architecture/design-tokens.md (token categories and usage) - docs/architecture/layers.md (package dependency tiers) - docs/workflows/contributing.md (PR checklist, commands) - docs/workflows/testing.md (test types, SSR safety) - docs/team-routing.md (teams, labels, CODEOWNERS) 2. Agent-guiding error messages: Updated all custom ESLint rules to include doc URLs and fix instructions in error messages so agents self-correct. 3. New ESLint rule: @fluentui/no-hardcoded-style-values — flags hardcoded colors and spacing in .styles.ts files, requiring design tokens. 4. Structural validation: check-v9-structure.js validates that component packages follow the canonical file structure. 5. Layer boundary validation: check-layer-boundaries.js validates that component packages (Tier 3) don't depend on other component packages. 6. Quality tracking: docs/quality-grades.md and docs/tech-debt-tracker.md for agents and engineers to track package quality and debt. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/instructions/copilot.instructions.md | 508 ++---------------- AGENTS.md | 117 ++-- docs/architecture/component-patterns.md | 126 +++++ docs/architecture/design-tokens.md | 63 +++ docs/architecture/layers.md | 51 ++ docs/quality-grades.md | 39 ++ docs/team-routing.md | 33 ++ docs/tech-debt-tracker.md | 39 ++ docs/workflows/contributing.md | 57 ++ docs/workflows/testing.md | 69 +++ .../eslint-plugin/src/configs/react/index.js | 1 + packages/eslint-plugin/src/rules.js | 1 + .../src/rules/ban-context-export/index.js | 6 +- .../ban-instanceof-html-element/index.js | 3 +- .../rules/no-context-default-value/index.js | 3 +- .../src/rules/no-global-react.js | 3 +- .../src/rules/no-hardcoded-style-values.js | 186 +++++++ .../src/rules/no-restricted-imports/index.js | 3 +- .../scripts/check-layer-boundaries.js | 185 +++++++ .../scripts/check-v9-structure.js | 136 +++++ 20 files changed, 1096 insertions(+), 533 deletions(-) create mode 100644 docs/architecture/component-patterns.md create mode 100644 docs/architecture/design-tokens.md create mode 100644 docs/architecture/layers.md create mode 100644 docs/quality-grades.md create mode 100644 docs/team-routing.md create mode 100644 docs/tech-debt-tracker.md create mode 100644 docs/workflows/contributing.md create mode 100644 docs/workflows/testing.md create mode 100644 packages/eslint-plugin/src/rules/no-hardcoded-style-values.js create mode 100644 tools/workspace-plugin/scripts/check-layer-boundaries.js create mode 100644 tools/workspace-plugin/scripts/check-v9-structure.js diff --git a/.github/instructions/copilot.instructions.md b/.github/instructions/copilot.instructions.md index 8d55b58c90fbca..a44db3cbdb3e5e 100644 --- a/.github/instructions/copilot.instructions.md +++ b/.github/instructions/copilot.instructions.md @@ -4,470 +4,54 @@ applyTo: '**' # Fluent UI Copilot Instructions -You are working in the Microsoft Fluent UI monorepo, a comprehensive design system and UI component library for web applications. This repository contains multiple versions and implementations of Fluent UI components serving millions of users across Microsoft's products. +Nx monorepo for Microsoft's Fluent UI design system. Yarn v1, Node 22, TypeScript strict. -## Repository Overview +## Quick Reference -This is a large Nx monorepo with the following key characteristics: +- **v9 components** (active): `packages/react-components/` — see [component patterns](../../docs/architecture/component-patterns.md) +- **v8 components** (maintenance only): `packages/react/` — critical fixes only +- **Web Components**: `packages/web-components/` +- **Charting**: `packages/charts/` -- **Package Manager**: Yarn v1 with strict dependency management -- **Build System**: Nx workspace with custom plugins (`tools/workspace-plugin/`) -- **Node.js Versions**: ^22.0.0 -- **Languages**: TypeScript (strict mode), React, Web Components -- **Testing**: Jest, Cypress (E2E), Storybook + StoryWright (Visual Regression), SSR testing (test-ssr) -- **Documentation**: Storybook sites, api.md files via API Extractor -- **Release**: Beachball for version management and change files - -## Project Structure - -1. **Fluent UI v9** (`@fluentui/react-components`) - **PRIORITIZE FOR NEW WORK** - - - Location: `packages/react-components/` - - Nx Project Tags: `vNext` - - Features: Current stable version, actively developed. Tree-shakeable, atomic CSS classes - -2. **Fluent UI v8** (`@fluentui/react`) - **MAINTENANCE ONLY** - - - Location: `packages/react/` - - Nx Project Tags: `v8` - - Status: Maintenance mode - - Features: Runtime styling, mature and stable - -3. **Fluent UI Charting** - - - Location: `packages/charts/` - - Nx Project Tags: `v8`, `vNext`,`charting` - - Features: Charting components compatible with both v8 and v9 - -4. **Web Components** (`@fluentui/web-components`) - Framework-agnostic - - Location: `packages/web-components/` - - Nx Project Tags: `web-components` - - Used by: Microsoft Edge - - Features: Platform-agnostic web standards - -More information about the projects and their status can be found through NX tags. - -## Development Guidelines - -### Essential Nx Workspace Commands - -This workspace uses **Nx** with custom workspace plugins. Key commands: +## Essential Commands ```bash -# Initial setup -yarn # Install dependencies and link packages -yarn clean # Clean all build artifacts - -# Development workflows -yarn start # Interactive prompt to choose project to run -yarn nx run :build # Build specific project with dependencies -yarn nx run-many -t build # Build multiple projects -yarn nx run :test # Run tests for specific project -yarn nx run :test -u # Update Jest snapshots -yarn nx run :start # Start Storybook for component - -# Component generation (v9 only) -yarn create-component # Interactive component generator - -# Release management -yarn change # Create beachball change file (required for PRs) - -# Show project targets -yarn nx show projects # List all projects -``` - -### Component Development Workflow - -**For v9 components** (preferred): - -1. Navigate to `packages/react-components/` -2. Use generator: `yarn create-component` or direct Nx command -3. Follow hook-based architecture pattern exactly -4. Add comprehensive tests and Storybook stories -5. Run `yarn nx run :generate-api` to update API docs -6. Create change file with `yarn change` before PR - -**For v8 components** (maintenance only): - -- Work in `packages/react/` with extreme caution -- No new features, only critical bug fixes - -### Testing Architecture - -**Multi-layered testing strategy** ensures quality at scale: - -```bash -# Unit Tests - Jest + React Testing Library + SWC transforms -yarn nx run react-button:test -yarn nx run react-button:test -u # Update snapshots - -# Visual Regression - Storybook + StoryWright -yarn nx run vr-tests-react-components:test-vr # Creates Diff images only. no actual assertions are run - this is used only from CI - -# E2E Integration Tests - Cypress -yarn nx run react-components:e2e - -# SSR Compatibility Tests -yarn nx run ssr-tests-v9:test-ssr - -# Cross-version React compatibility -yarn nx run rit-tests-v9:test-rit - -# Cross-version React testing for react-text project -yarn nx run react-text:test-rit - -``` - -### Code Quality and Standards - -- **Linting**: ESLint with custom `@fluentui/eslint-plugin` rules -- **Formatting**: Prettier (automatic via lint-staged) -- **Type checking**: TypeScript strict mode required -- **Testing**: Jest for unit tests, minimum 80% coverage expected -- **Bundle analysis**: Automated bundle size tracking via build pipeline - -## Important Patterns and Conventions - -### File Organization Patterns - -**CRITICAL**: Every v9 component follows this exact architectural pattern: - -``` -# v9 Component Structure (EXACT pattern required) -packages/react-components/react-component-name/ -├── library/src/ # Implementation source -│ ├── index.ts # Re-exports everything -│ ├── ComponentName.tsx # Main component export -│ ├── components/ComponentName/ # Core implementation -│ │ ├── ComponentName.test.tsx # Unit tests (adjacent) -│ │ ├── ComponentName.tsx # ForwardRefComponent -│ │ ├── ComponentName.types.ts # Props, State, Slots -│ │ ├── index.ts # Local exports -│ │ ├── renderComponentName.tsx # JSX rendering -│ │ ├── useComponentName.ts # State management -│ │ └── useComponentNameStyles.styles.ts # Griffel styling -│ ├── testing/ # Test utilities -│ └── utils # Reusable utils (if needed) -└── stories/src/ # Storybook documentation - ├── ComponentName.stories.tsx # Component stories - └── ComponentNameDefault.stories.tsx # Default story -``` - -### Component Structure Pattern - -``` -react-component-name/ -├── library/src/ -│ ├── index.ts # Main package exports -│ ├── ComponentName.ts # Main export (calls hooks + render) -│ ├── components/ComponentName/ # Hook-based architecture -│ │ ├── ComponentName.tsx # Main component export -│ │ ├── ComponentName.types.ts # Props, State, Slots types -│ │ ├── useComponentName.ts # State management hook -│ │ ├── useComponentNameStyles.styles.ts # Griffel styling -│ │ └── renderComponentName.tsx # JSX rendering logic -│ └── index.ts # Package exports -└── stories/ # Storybook stories -``` - -### Hook-Based Architecture - -Components use three core hooks: - -1. **`useComponent_unstable()`** - Processes props, slots and main component logic into normalized state -2. **`useComponentStyles_unstable()`** - Creates Griffel CSS-in-JS styling -3. **`renderComponent_unstable()`** - Pure JSX rendering from state - -### Slot System - -**Critical Pattern**: All v9 components use slots for extensibility and consistent rendering: - -```tsx -// Define slots in types -type ButtonSlots = { - root: Slot<'button'>; - icon?: Slot<'span'>; -}; - -// Create slots in hook using slot.always() or slot.optional() -const state: ButtonState = { - root: slot.always(props.root, { elementType: 'button' }), - icon: slot.optional(props.icon, { elementType: 'span' }), -}; - -// Render slots with assertSlots for type safety -export const renderButton_unstable = (state: ButtonState) => { - assertSlots(state); - return ( - - {state.icon && } - {state.root.children} - - ); -}; -``` - -### Build-Time CSS-in-JS with Atomic Classes - -**Critical**: v9 uses Griffel for compile-time CSS generation - styles are extracted into atomic CSS classes at build time, not runtime: - -```tsx -// useButtonStyles.styles.ts -import { makeStyles } from '@griffel/react'; -import { tokens } from '@fluentui/react-theme'; - -export const useButtonStyles = makeStyles({ - root: { - // Use design tokens, not hardcoded values - color: tokens.colorNeutralForeground1, - backgroundColor: tokens.colorNeutralBackground1, - padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`, - - // Pseudo-selectors and media queries supported - ':hover': { - backgroundColor: tokens.colorNeutralBackground1Hover, - }, - - ':focus-visible': { - outline: `${tokens.strokeWidthThick} solid ${tokens.colorStrokeFocus2}`, - }, - }, - - // Size variations - small: { padding: tokens.spacingVerticalXS }, - large: { padding: tokens.spacingVerticalL }, -}); - -// Apply in component hook -export const useButton_unstable = (props, ref) => { - const classes = useButtonStyles(); - const state = { - /* ... */ - }; - - state.root.className = mergeClasses( - classes.root, - props.size === 'small' && classes.small, - props.size === 'large' && classes.large, - state.root.className, // Always preserve user className - ); - - return state; -}; -``` - -### Design Tokens System - -**Always use design tokens** from `@fluentui/tokens` instead of hardcoded values: - -```tsx -// ✅ CORRECT - uses semantic design tokens -color: tokens.colorBrandForeground1; -padding: tokens.spacingVerticalM; -borderRadius: tokens.borderRadiusMedium; - -// ❌ AVOID - hardcoded values break theming -color: '#0078d4'; -padding: '8px'; -borderRadius: '4px'; -``` - -### Theme Architecture - -Themes define CSS custom properties consumed by components: - -```tsx -// FluentProvider injects CSS variables into DOM - - -; - -// Components reference tokens which resolve to CSS variables -makeStyles({ - root: { - color: tokens.colorNeutralForeground1, // becomes 'var(--colorNeutralForeground1)' - }, -}); -``` - -### TypeScript Patterns (v9) - -**Strict typing with consistent interfaces:** - -```tsx -// Component.types.ts - REQUIRED pattern -export type ComponentProps = ComponentPropsWithRef<'div'> & { - appearance?: 'primary' | 'secondary'; - size?: 'small' | 'medium' | 'large'; -}; - -export type ComponentState = Required> & { - components: ComponentSlots; - root: SlotProps<'div'>; -}; - -export type ComponentSlots = { - root: Slot<'div'>; - icon?: Slot<'span'>; -}; - -// Main component must use ForwardRefComponent -export const Component: ForwardRefComponent = React.forwardRef((props, ref) => { - // Hook pattern implementation -}); -``` - -### Testing Requirements - -**Comprehensive testing is mandatory** for all component changes: - -### Accessibility Standards - -**WCAG 2.1 compliance required** for all interactive components: - -- Provide proper ARIA labels and roles -- Support keyboard navigation patterns -- Test with screen readers (NVDA, JAWS, VoiceOver) -- High contrast mode compatibility required - -## Documentation and Resources - -- **Main docs**: -- **v8 docs**: -- **Web Components docs**: -- **Design specs**: Located in `specs/` directory -- **Storybook**: Run locally with appropriate build commands - -### Code Style - -- Use TypeScript strict mode -- Follow existing patterns in similar components -- Comprehensive prop interfaces with JSDoc comments -- Consistent naming conventions (PascalCase for components, camelCase for props) -- Use React hooks and modern patterns for v9 components -- Focus on the slot system, Griffel styling, and hook-based architecture for v9 - -### Testing Requirements - -- Unit tests for all public APIs -- Accessibility tests for interactive components -- Visual regression tests via Storybook -- Cross-browser compatibility considerations - -## Migration and Compatibility - -- v8 and v9 components can coexist in the same application -- Gradual migration is supported and encouraged -- Pay attention to design token usage for consistent theming -- Consider bundle size impact when mixing versions - -When working in this repository, always prioritize accessibility, design system consistency, and maintainability. The codebase serves millions of users across Microsoft's products, so quality and reliability are paramount. - -## GitHub Issue and Pull Request Labeling - -### Issue Labels - -The repository uses automated labeling based on issue templates and PR file changes. Understanding the labeling system helps with proper issue categorization and triage. - -#### Core Label Categories - -**Type Labels** (required for all issues): - -- `Type: Bug :bug:` - Bug reports across all platforms -- `Type: Feature` - Feature requests and enhancements -- `Area: Documentation` - Documentation issues and improvements - -**Product/Platform Labels** (auto-assigned by issue templates): - -- `Fluent UI react-components (v9)` - v9 React components -- `Fluent UI react (v8)` - v8/legacy React components -- `web-components` - Web Components implementation -- `Fluent UI WC (v3)` - Web Components v3 specific -- `Package: charting` - React Charting components - -**Triage Labels**: - -- `Needs: Triage :mag:` - Automatically applied to new issues, requires team review -- `Area: Build System` - Build tooling and infrastructure - -#### Issue Template Mapping - -**For React Components v9 bugs**: - -```yaml -labels: ['Type: Bug :bug:', 'Needs: Triage :mag:', 'Fluent UI react-components (v9)'] -``` - -**For React v8 bugs**: - -```yaml -labels: ['Type: Bug :bug:', 'Needs: Triage :mag:', 'Fluent UI react (v8)'] -``` - -**For Web Components bugs**: - -```yaml -labels: ['Type: Bug :bug:', 'Needs: Triage :mag:', 'web-components', 'Fluent UI WC (v3)'] -``` - -**For Feature Requests**: - -```yaml -labels: ['Type: Feature', 'Needs: Triage :mag:'] -``` - -**For Documentation Issues**: - -```yaml -labels: ['Area: Documentation', 'Needs: Triage :mag:'] -``` - -### Pull Request Labels - -PRs receive automatic labels based on file changes via GitHub Actions using `.github/labeler.yml`: - -**Workflow Labels**: - -- `Type: RFC` - Changes to RFC documentation (`docs/react-v9/contributing/rfcs/**`) -- `CI` - Changes to CI/CD pipelines (`.github/**`, `.devops/**`, `azure-pipelines*.yml`) - -**Nx Workspace Labels**: - -- `NX: core` - Core Nx configuration changes (`tools/workspace-plugin/**`, `nx.json`, `**/project.json`) -- `NX: workspace generators` - Nx generator changes (`tools/workspace-plugin/src/generators/*`) -- `NX: workspace executors` - Nx executor changes (`tools/workspace-plugin/src/executors/*`) -- `NX: workspace eslint-rules` - ESLint rule changes (`tools/eslint-rules/*`) - -### Automated Triage System - -The repository uses a triage bot that automatically assigns labels and assignees based on issue content: - -**Trigger Keywords** (from `.github/triage-bot.config.json`): - -- `(@fluentui/react-northstar)` → `["Fluent UI react-northstar (v0)", "Needs: Triage :mag:"]` -- `(@fluentui/react)` → `["Fluent UI react (v8)", "Needs: Triage :mag:"]` -- `(@fluentui/react-components)` → `["Fluent UI react-components (v9)", "Needs: Triage :mag:"]` -- `(@fluentui/web-components)` → `["web-components"]` - -### Best Practices for Contributors - -**When Creating Issues**: - -1. Use appropriate issue template - labels are automatically applied -2. Select correct component/package from dropdown - helps with routing -3. Include package version and relevant details -4. Don't manually add labels - let automation handle initial triage - -**When Creating Pull Requests**: - -1. Labels are automatically applied based on changed files -2. Focus on clear PR titles and descriptions -3. Reference related issues with proper syntax (`Fixes #123`) -4. Create change files for breaking changes (`yarn change`) - -**For Maintainers**: - -1. Review `Needs: Triage :mag:` label for new items -2. Add component-specific labels after triage if needed -3. Use consistent labeling for similar issues to help automation learning -4. Update triage bot configuration when adding new packages/areas +npx nx run :build # Build +npx nx run :test # Test +npx nx run :test -u # Update snapshots +npx nx run :lint # Lint +npx nx run :type-check # Type check +npx nx run :generate-api # Update API docs +npx beachball change # Create change file (required for published packages) +``` + +## Key Rules + +1. Use [design tokens](../../docs/architecture/design-tokens.md), never hardcoded values +2. Follow [v9 component patterns](../../docs/architecture/component-patterns.md) exactly +3. Respect [package layer boundaries](../../docs/architecture/layers.md) +4. SSR-safe: no unguarded `window`/`document`/`navigator` access +5. Accessibility: proper ARIA attributes, keyboard navigation, WCAG 2.1 +6. Always use `npx nx run` — never run tools directly +7. Create beachball change files for published package changes + +## Deep Dives + +| Topic | Doc | +|-------|-----| +| V9 component structure, hooks, slots, Griffel | [docs/architecture/component-patterns.md](../../docs/architecture/component-patterns.md) | +| Design tokens and theming | [docs/architecture/design-tokens.md](../../docs/architecture/design-tokens.md) | +| Package dependency layers | [docs/architecture/layers.md](../../docs/architecture/layers.md) | +| PR workflow, branch naming, checklist | [docs/workflows/contributing.md](../../docs/workflows/contributing.md) | +| Test types, SSR safety, conformance | [docs/workflows/testing.md](../../docs/workflows/testing.md) | +| Teams, labels, CODEOWNERS | [docs/team-routing.md](../../docs/team-routing.md) | +| Quality grades per package | [docs/quality-grades.md](../../docs/quality-grades.md) | +| Known tech debt | [docs/tech-debt-tracker.md](../../docs/tech-debt-tracker.md) | + +## Issue & PR Labels + +Issue templates auto-apply labels. PR labels are auto-applied based on changed files. +See [docs/team-routing.md](../../docs/team-routing.md) for the full taxonomy. + +Triage keywords: `@fluentui/react-components` → v9, `@fluentui/react` → v8, +`@fluentui/web-components` → WC. Config: `.github/triage-bot.config.json`. diff --git a/AGENTS.md b/AGENTS.md index 4f16ff4af4f405..df60341a118b56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,79 +3,66 @@ # General Guidelines for working with Nx -- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly -- You have access to the Nx MCP server and its tools, use them to help the user -- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable. -- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies -- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration -- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors +- Always run tasks through `nx` (`nx run`, `nx run-many`, `nx affected`), never tools directly +- Use `nx_workspace` tool to understand workspace architecture +- Use `nx_project_details` tool to analyze specific project structure and dependencies +- Use `nx_docs` tool for up-to-date Nx configuration guidance -# Fluent UI — Global Agent Instructions +# Fluent UI — Agent Map -This file provides high-level context for all AI agents working in this repository. -For detailed development guidelines, see `.github/instructions/copilot.instructions.md`. +Nx monorepo. Yarn v1. Node 22. TypeScript strict. ~200 packages. Millions of users. -## Repository Identity +## Architecture -- **Repo**: microsoft/fluentui -- **What**: Microsoft's Fluent UI design system — React components (v8, v9), Web Components, and Charting -- **Scale**: Serves millions of users across Microsoft products (Teams, Outlook, Azure Portal, etc.) -- **Structure**: Nx monorepo with ~200+ packages, managed with Yarn v1 +| Topic | Location | +|-------|----------| +| V9 component patterns (hooks, slots, Griffel) | [docs/architecture/component-patterns.md](docs/architecture/component-patterns.md) | +| Design tokens and theming | [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md) | +| Package dependency layers | [docs/architecture/layers.md](docs/architecture/layers.md) | -## Agentic Workflows +## Workflows + +| Topic | Location | +|-------|----------| +| PR checklist, change files, commands | [docs/workflows/contributing.md](docs/workflows/contributing.md) | +| Testing guide (unit, VRT, SSR, E2E) | [docs/workflows/testing.md](docs/workflows/testing.md) | +| Team routing and label taxonomy | [docs/team-routing.md](docs/team-routing.md) | + +## Quality Tracking + +| Topic | Location | +|-------|----------| +| Per-package quality grades | [docs/quality-grades.md](docs/quality-grades.md) | +| Technical debt tracker | [docs/tech-debt-tracker.md](docs/tech-debt-tracker.md) | -This repository uses GitHub Agentic Workflows for automated tasks. The workflow definitions -live in `.github/workflows/agent-*.md`: +## Key Rules + +1. Use design tokens — never hardcode colors, spacing, or typography +2. Follow v9 component patterns exactly — don't invent new approaches +3. Respect package layer boundaries (see layers.md) +4. SSR-safe — no unguarded `window`/`document`/`navigator` +5. Accessibility first — ARIA attributes, keyboard nav, WCAG 2.1 +6. Beachball change files required for published package changes + +## Agentic Workflows | Workflow | Trigger | Purpose | |----------|---------|---------| -| `agent-triage` | Issue opened/reopened | Classify, label, and validate incoming issues | -| `agent-fix` | Issue labeled `agent:fix` | Implement bug fixes and open PRs | -| `agent-review` | PR opened/updated | Devil's advocate review with confidence scoring | -| `agent-docs-grooming` | Weekly (Monday) | Audit documentation for staleness and gaps | -| `agent-skills-improvement` | Weekly (Friday) | Analyze agent PRs and improve instructions | - -## Key Rules for All Agents - -1. **Be conservative.** This codebase serves millions of users. When in doubt, don't change it. -2. **Follow existing patterns.** Don't invent new approaches — match what's already in the code. -3. **Use design tokens.** Never hardcode colors, spacing, or typography values. -4. **SSR safety.** Never access `window`, `document`, or `navigator` without proper guards. -5. **Accessibility first.** All interactive components must have proper ARIA attributes and - keyboard navigation. -6. **Beachball change files.** Any change to a published package requires `npx beachball change`. -7. **Nx commands.** Always use `npx nx run :` — never run tools directly. - -## Team Routing - -| Area | Team | Packages | -|------|------|----------| -| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | -| v8 Components | @microsoft/cxe-red | `packages/react/*` | -| Web Components | @microsoft/fui-wc | `packages/web-components/*` | -| Charting | @microsoft/charting-team | `packages/charts/*` | -| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | - -## Label Taxonomy - -### Product Labels -- `Fluent UI react-components (v9)` — v9 React components -- `Fluent UI react (v8)` — v8 React components -- `web-components` / `Fluent UI WC (v3)` — Web Components -- `Package: charting` — Charting library - -### Type Labels -- `Type: Bug :bug:` — Bug reports -- `Type: Feature` — Feature requests - -### Status Labels -- `Needs: Triage :mag:` — Needs team review -- `Needs: Author Feedback` — Waiting on issue author -- `Needs: Attention` — Needs team attention - -### Resolution Labels -- `Resolution: Duplicate` — Duplicate of another issue -- `Resolution: Not An Issue` — Not a valid issue -- `Resolution: By Design` — Working as intended +| `agent-triage` | Issue opened | Classify, label, validate issues | +| `agent-fix` | `agent:fix` label | Implement fixes, open PRs | +| `agent-review` | PR opened | Devil's advocate review with confidence score | +| `agent-docs-grooming` | Weekly Monday | Documentation audit | +| `agent-skills-improvement` | Weekly Friday | Improve agent instructions from PR patterns | + +## Package Layout + +| Area | Path | Status | +|------|------|--------| +| V9 components | `packages/react-components/` | Active development | +| V8 components | `packages/react/` | Maintenance only | +| Web Components | `packages/web-components/` | Active | +| Charting | `packages/charts/` | Active | +| Build tooling | `tools/` | Active | +| ESLint plugin | `packages/eslint-plugin/` | Active | diff --git a/docs/architecture/component-patterns.md b/docs/architecture/component-patterns.md new file mode 100644 index 00000000000000..c23eb14f3f8dc9 --- /dev/null +++ b/docs/architecture/component-patterns.md @@ -0,0 +1,126 @@ +# V9 Component Patterns + +## File Structure + +Every v9 component package follows this exact layout: + +``` +packages/react-components/react-/library/src/ +├── components// +│ ├── .tsx # ForwardRefComponent +│ ├── .types.ts # Props, State, Slots types +│ ├── .test.tsx # Unit tests (adjacent) +│ ├── use.ts or .tsx # State management hook +│ ├── useStyles.styles.ts # Griffel styling +│ ├── render.tsx # JSX rendering +│ └── index.ts # Component barrel export +├── contexts/ # Optional: context definitions +├── utils/ # Optional: shared utilities +├── testing/ +│ └── isConformant.ts # Conformance tests +├── .ts # Root barrel per component +└── index.ts # Package export +``` + +## Hook-Based Architecture + +Components use three core hooks: + +1. **`use(props, ref)`** — Processes props and slots into normalized state. + Use `.ts` if pure logic, `.tsx` if the hook body contains JSX. + +2. **`useStyles(state)`** — Creates Griffel CSS-in-JS styling using design tokens. + Always ends in `.styles.ts`. + +3. **`render(state)`** — Pure JSX rendering from state. + Always `.tsx`. + +### Where to Fix Bugs + +| Bug type | Fix location | +|----------|-------------| +| State / behavior | `use.ts` | +| Styling | `useStyles.styles.ts` | +| Rendering / JSX | `render.tsx` | +| Types / props | `.types.ts` | + +## Slot System + +All v9 components use slots for extensibility: + +```tsx +// Types +type ButtonSlots = { + root: Slot<'button'>; + icon?: Slot<'span'>; +}; + +// Hook — create slots +const state: ButtonState = { + root: slot.always(props.root, { elementType: 'button' }), + icon: slot.optional(props.icon, { elementType: 'span' }), +}; + +// Render — use assertSlots for type safety +export const renderButton_unstable = (state: ButtonState) => { + assertSlots(state); + return ( + + {state.icon && } + {state.root.children} + + ); +}; +``` + +## Griffel Styling + +Use `makeStyles` with design tokens — never hardcode values: + +```tsx +import { makeStyles } from '@griffel/react'; +import { tokens } from '@fluentui/react-theme'; + +export const useButtonStyles = makeStyles({ + root: { + color: tokens.colorNeutralForeground1, + backgroundColor: tokens.colorNeutralBackground1, + padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`, + ':hover': { + backgroundColor: tokens.colorNeutralBackground1Hover, + }, + }, +}); +``` + +Always use `mergeClasses()` and preserve user className as the **last** argument: + +```tsx +state.root.className = mergeClasses( + classes.root, + props.size === 'small' && classes.small, + state.root.className, // Always last +); +``` + +## TypeScript Patterns + +```tsx +// Component.types.ts +export type ComponentProps = ComponentPropsWithRef<'div'> & { + appearance?: 'primary' | 'secondary'; + size?: 'small' | 'medium' | 'large'; +}; + +export type ComponentState = Required> & { + components: ComponentSlots; + root: SlotProps<'div'>; +}; + +// Main component — always ForwardRefComponent, never React.FC +export const Component: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useComponent_unstable(props, ref); + useComponentStyles_unstable(state); + return renderComponent_unstable(state); +}); +``` diff --git a/docs/architecture/design-tokens.md b/docs/architecture/design-tokens.md new file mode 100644 index 00000000000000..772966ef629fd5 --- /dev/null +++ b/docs/architecture/design-tokens.md @@ -0,0 +1,63 @@ +# Design Tokens + +## Rule + +**Always use design tokens** from `@fluentui/react-theme` instead of hardcoded values. +Hardcoded values break theming, high contrast mode, and dark mode. + +## Token Categories + +| Category | Example tokens | Use for | +|----------|---------------|---------| +| Color | `tokens.colorNeutralForeground1`, `tokens.colorBrandBackground` | All colors | +| Spacing | `tokens.spacingVerticalM`, `tokens.spacingHorizontalL` | Padding, margin, gap | +| Border radius | `tokens.borderRadiusMedium`, `tokens.borderRadiusLarge` | Border radius | +| Font | `tokens.fontSizeBase300`, `tokens.fontWeightSemibold` | Typography | +| Line height | `tokens.lineHeightBase300` | Line height | +| Stroke | `tokens.strokeWidthThin`, `tokens.strokeWidthThick` | Border width | +| Shadow | `tokens.shadow4`, `tokens.shadow16` | Box shadow | +| Duration | `tokens.durationNormal`, `tokens.durationFast` | Animations | +| Easing | `tokens.curveEasyEase` | Animation timing | + +## Examples + +```tsx +// CORRECT — uses semantic design tokens +color: tokens.colorBrandForeground1; +padding: tokens.spacingVerticalM; +borderRadius: tokens.borderRadiusMedium; +fontSize: tokens.fontSizeBase300; +boxShadow: tokens.shadow4; + +// WRONG — hardcoded values break theming +color: '#0078d4'; +padding: '8px'; +borderRadius: '4px'; +fontSize: '14px'; +boxShadow: '0 2px 4px rgba(0,0,0,0.1)'; +``` + +## Theme Architecture + +Themes define CSS custom properties consumed by components: + +```tsx +// FluentProvider injects CSS variables into DOM + + + + +// Tokens resolve to CSS variables at build time +makeStyles({ + root: { + color: tokens.colorNeutralForeground1, + // becomes: 'var(--colorNeutralForeground1)' + }, +}); +``` + +## Available Themes + +- `webLightTheme` — Default light +- `webDarkTheme` — Default dark +- `teamsLightTheme` / `teamsDarkTheme` / `teamsHighContrastTheme` — Teams variants diff --git a/docs/architecture/layers.md b/docs/architecture/layers.md new file mode 100644 index 00000000000000..c1a0f8a1b11024 --- /dev/null +++ b/docs/architecture/layers.md @@ -0,0 +1,51 @@ +# Package Dependency Layers + +## Layer Hierarchy + +Packages in this monorepo follow a layered dependency model. +Dependencies may only flow **downward** — never upward or sideways within the same tier. + +``` +┌─────────────────────────────────────┐ +│ Tier 4: Barrel Package │ @fluentui/react-components +│ (aggregates all v9 components) │ (depends on all component packages) +├─────────────────────────────────────┤ +│ Tier 3: Component Packages │ @fluentui/react-button, react-dialog, etc. +│ (individual UI components) │ (depend on utilities and theme) +├─────────────────────────────────────┤ +│ Tier 2: Foundation Packages │ @fluentui/react-utilities, react-theme, +│ (shared utilities, theme, context) │ react-shared-contexts, react-tabster, +│ │ react-positioning, react-portal +├─────────────────────────────────────┤ +│ Tier 1: Core Packages │ @griffel/react, @fluentui/tokens, +│ (tokens, CSS-in-JS engine) │ @fluentui/react-jsx-runtime +└─────────────────────────────────────┘ +``` + +## Rules + +1. **Component packages (Tier 3) must NOT depend on other component packages.** + If `react-button` needs something from `react-menu`, it should go through + a shared context or utility in Tier 2. + +2. **Foundation packages (Tier 2) must NOT depend on component packages (Tier 3).** + +3. **Stories packages may depend on anything** — they are leaf nodes. + +4. **Cross-cutting concerns** (contexts, portals, positioning) live in Tier 2 + and are shared through explicit imports, not peer dependencies. + +## Nx Tags + +Projects are tagged for identification: + +| Tag | Meaning | +|-----|---------| +| `vNext` | v9 packages | +| `v8` | v8 packages (maintenance only) | +| `platform:web` | Browser-targeted | +| `platform:node` | Node.js-targeted | +| `type:stories` | Storybook story packages | +| `web-components` | Web Components packages | +| `charting` | Charting packages | +| `tools` | Build tooling | diff --git a/docs/quality-grades.md b/docs/quality-grades.md new file mode 100644 index 00000000000000..0d1b7eb92f3d91 --- /dev/null +++ b/docs/quality-grades.md @@ -0,0 +1,39 @@ +# Package Quality Grades + +This file tracks the documentation and test coverage quality of v9 component packages. +Updated by the `agent-docs-grooming` workflow and the `agent-skills-improvement` workflow. + +## Grading Criteria + +| Grade | Meaning | +|-------|---------| +| A | README + stories + tests + API docs all present and current | +| B | Missing one of: complete README, full story coverage, or up-to-date API docs | +| C | Missing two or more quality signals | +| D | Minimal or no documentation/testing | + +## Quality Signals + +- **README**: Has description, install command, usage example +- **Stories**: Has `stories/` directory with Default story and variant coverage +- **Tests**: Has unit tests with reasonable coverage +- **API docs**: `.api.md` file exists and matches current exports +- **Conformance**: `testing/isConformant.ts` exists and passes + +## Current Grades + + + +| Package | README | Stories | Tests | API Docs | Conformance | Grade | +|---------|--------|---------|-------|----------|-------------|-------| +| _To be populated by first docs audit run_ | | | | | | | + +## How to Update + +The `agent-docs-grooming` workflow populates this table during weekly audits. +To trigger manually, create a "Documentation Audit" issue and assign to Copilot +with the docs-groomer workflow, or run: + +```bash +gh aw run agent-docs-grooming +``` diff --git a/docs/team-routing.md b/docs/team-routing.md new file mode 100644 index 00000000000000..45d723a85247c2 --- /dev/null +++ b/docs/team-routing.md @@ -0,0 +1,33 @@ +# Team Routing & Label Taxonomy + +## Team Routing + +| Area | Team | Packages | +|------|------|----------| +| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | +| v8 Components | @microsoft/cxe-red | `packages/react/*` | +| Web Components | @microsoft/fui-wc | `packages/web-components/*` | +| Charting | @microsoft/charting-team | `packages/charts/*` | +| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | + +## Label Taxonomy + +### Product Labels +- `Fluent UI react-components (v9)` — v9 React components +- `Fluent UI react (v8)` — v8 React components +- `web-components` / `Fluent UI WC (v3)` — Web Components +- `Package: charting` — Charting library + +### Type Labels +- `Type: Bug :bug:` — Bug reports +- `Type: Feature` — Feature requests + +### Status Labels +- `Needs: Triage :mag:` — Needs team review +- `Needs: Author Feedback` — Waiting on issue author +- `Needs: Attention` — Needs team attention + +### Resolution Labels +- `Resolution: Duplicate` — Duplicate of another issue +- `Resolution: Not An Issue` — Not a valid issue +- `Resolution: By Design` — Working as intended diff --git a/docs/tech-debt-tracker.md b/docs/tech-debt-tracker.md new file mode 100644 index 00000000000000..e6334877f05f5b --- /dev/null +++ b/docs/tech-debt-tracker.md @@ -0,0 +1,39 @@ +# Technical Debt Tracker + +This file tracks known technical debt items that agents and engineers should be aware of. +Items are added during code review, agent runs, and manual audits. + +## Active Debt Items + + + +| ID | Category | Description | Location | Priority | Added | +|----|----------|-------------|----------|----------|-------| +| _To be populated by agent runs and manual audits_ | | | | | | + +## Categories + +- **pattern-violation** — Code that doesn't follow established patterns +- **missing-tests** — Insufficient test coverage +- **stale-docs** — Documentation that doesn't match code +- **deprecated-usage** — Use of deprecated APIs or patterns +- **accessibility** — Known a11y gaps +- **performance** — Known performance issues +- **ssr-safety** — SSR-unsafe code patterns + +## Priority Levels + +- **P0** — Blocking: breaks users or CI +- **P1** — High: should fix in next sprint +- **P2** — Medium: fix when touching the area +- **P3** — Low: nice to have, no urgency + +## How to Use + +### For agents +When you find technical debt during a fix or review, add a row to the table above. +When you fix a debt item, remove its row. + +### For engineers +Review this file when planning sprints. Use it to justify cleanup PRs. +The `agent-skills-improvement` workflow analyzes this file to identify recurring patterns. diff --git a/docs/workflows/contributing.md b/docs/workflows/contributing.md new file mode 100644 index 00000000000000..3bee7f518157cb --- /dev/null +++ b/docs/workflows/contributing.md @@ -0,0 +1,57 @@ +# Contributing Workflow + +## Development Commands + +```bash +# Setup +yarn # Install dependencies +yarn clean # Clean build artifacts + +# Development +yarn start # Interactive project selector +npx nx run :build # Build specific project +npx nx run :start # Start Storybook for component +npx nx run :test # Run unit tests +npx nx run :test -u # Update snapshots +npx nx run :lint # Lint +npx nx run :type-check # Type check +npx nx run :generate-api # Update API docs + +# Multi-project +npx nx run-many -t build # Build multiple +npx nx affected -t test # Test affected projects + +# Component generation (v9 only) +yarn create-component # Interactive generator +``` + +## PR Checklist + +1. **Change file** — Required for any published package change: + ```bash + npx beachball change --type patch --message "fix(react-button): description" + ``` + Use `patch` for fixes, `minor` for features. Never `major` without approval. + +2. **Tests pass** — `npx nx run :test` + +3. **Lint passes** — `npx nx run :lint` + +4. **Types check** — `npx nx run :type-check` + +5. **API docs updated** — If public API changed: `npx nx run :generate-api` + +6. **Link issue** — Use `Fixes #` in PR body + +## Branch Naming + +- Bug fixes: `fix/-` +- Features: `feat/-` +- Docs: `docs/` + +## What NOT to Do + +- Don't refactor unrelated code alongside a bug fix +- Don't modify public API without explicit approval +- Don't skip beachball change files for published packages +- Don't add dependencies between component packages (Tier 3 → Tier 3) diff --git a/docs/workflows/testing.md b/docs/workflows/testing.md new file mode 100644 index 00000000000000..eb19b4763d3671 --- /dev/null +++ b/docs/workflows/testing.md @@ -0,0 +1,69 @@ +# Testing Guide + +## Test Types + +| Type | Tool | Command | Purpose | +|------|------|---------|---------| +| Unit | Jest + React Testing Library | `npx nx run :test` | Component behavior, hooks, utils | +| Visual Regression | Storybook + StoryWright | `npx nx run vr-tests-react-components:test-vr` | Screenshot diffs (CI only) | +| E2E | Cypress | `npx nx run react-components:e2e` | Integration flows | +| SSR | Custom | `npx nx run ssr-tests-v9:test-ssr` | Server-side rendering safety | +| Cross-React | Custom | `npx nx run rit-tests-v9:test-rit` | React version compatibility | +| Conformance | isConformant | Part of unit tests | Consistent component API | + +## Writing Unit Tests + +Tests live adjacent to the component they test: + +``` +components/Button/ +├── Button.tsx +├── Button.test.tsx ← here +└── ... +``` + +### What to Test + +- Default rendering (snapshot) +- All prop variants +- User interactions (click, keyboard, focus) +- Accessibility (ARIA attributes, roles, keyboard navigation) +- Controlled and uncontrolled patterns +- Edge cases (null children, empty arrays, etc.) + +### Updating Snapshots + +If your change intentionally alters rendered output: + +```bash +npx nx run :test -u +``` + +Review the snapshot diff to verify the change is correct before committing. + +## Conformance Tests + +Every component package has a `testing/isConformant.ts` file that validates: +- Component renders without crashing +- Ref forwarding works +- className merging works +- `as` prop (if applicable) works +- Accessibility basics + +## SSR Safety + +Components must work in server-side rendering. Never access browser APIs without guards: + +```tsx +// WRONG — crashes on server +const width = window.innerWidth; + +// RIGHT — guarded access +const width = typeof window !== 'undefined' ? window.innerWidth : 0; + +// BETTER — use useIsSSR or check canUseDOM +import { canUseDOM } from '@fluentui/react-utilities'; +if (canUseDOM()) { + // safe to use window/document +} +``` diff --git a/packages/eslint-plugin/src/configs/react/index.js b/packages/eslint-plugin/src/configs/react/index.js index 27dcaacef18e88..53d4dd2b7e3d57 100644 --- a/packages/eslint-plugin/src/configs/react/index.js +++ b/packages/eslint-plugin/src/configs/react/index.js @@ -43,6 +43,7 @@ module.exports = defineConfig( }, ], '@fluentui/ban-instanceof-html-element': ['error'], + '@fluentui/no-hardcoded-style-values': ['error'], '@fluentui/react-components/enforce-use-client': ['error'], '@fluentui/no-context-default-value': [ 'error', diff --git a/packages/eslint-plugin/src/rules.js b/packages/eslint-plugin/src/rules.js index 2bce5040589dce..78b37fd0a94300 100644 --- a/packages/eslint-plugin/src/rules.js +++ b/packages/eslint-plugin/src/rules.js @@ -5,6 +5,7 @@ const rules = { 'deprecated-keyboard-event-props': require('./rules/deprecated-keyboard-event-props'), 'max-len': require('./rules/max-len'), 'no-global-react': require('./rules/no-global-react'), + 'no-hardcoded-style-values': require('./rules/no-hardcoded-style-values'), 'no-tslint-comments': require('./rules/no-tslint-comments'), 'no-visibility-modifiers': require('./rules/no-visibility-modifiers'), 'no-restricted-imports': require('./rules/no-restricted-imports'), diff --git a/packages/eslint-plugin/src/rules/ban-context-export/index.js b/packages/eslint-plugin/src/rules/ban-context-export/index.js index 5e263ccc37a757..58158a2ed33356 100644 --- a/packages/eslint-plugin/src/rules/ban-context-export/index.js +++ b/packages/eslint-plugin/src/rules/ban-context-export/index.js @@ -40,8 +40,10 @@ module.exports = createRule({ description: 'Ban export of React context or context selector objects', }, messages: { - nativeContext: '{{exportName}} should not be exported directly', - contextSelector: '{{exportName}} should not be exported directly', + nativeContext: + '{{exportName}} should not be exported directly. Export a Provider component and a use*Context hook instead. See docs/architecture/component-patterns.md', + contextSelector: + '{{exportName}} should not be exported directly. Export a Provider component and a use*Context hook instead. See docs/architecture/component-patterns.md', }, }, create(context) { diff --git a/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js b/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js index aedade9629173a..6af7bd2a8d7015 100644 --- a/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js +++ b/packages/eslint-plugin/src/rules/ban-instanceof-html-element/index.js @@ -14,7 +14,8 @@ module.exports = createRule({ description: 'Ban usage of instanceof HTMLElement comparison', }, messages: { - invalidBinaryExpression: 'instanceof {{right}} should be avoided, use isHTMLElement instead.', + invalidBinaryExpression: + 'instanceof {{right}} should be avoided. Use isHTMLElement() from @fluentui/react-utilities instead for SSR safety. See docs/workflows/testing.md', }, fixable: 'code', schema: [], diff --git a/packages/eslint-plugin/src/rules/no-context-default-value/index.js b/packages/eslint-plugin/src/rules/no-context-default-value/index.js index aec94028ff35e5..c39ccf95c7599d 100644 --- a/packages/eslint-plugin/src/rules/no-context-default-value/index.js +++ b/packages/eslint-plugin/src/rules/no-context-default-value/index.js @@ -20,7 +20,8 @@ module.exports = createRule({ description: 'Restricts usage of default values on React context creation', }, messages: { - invalidDefaultValue: 'Invalid default value for context declaration, default value should be undefined', + invalidDefaultValue: + 'Invalid default value for context declaration, default value should be undefined. Use createContext(undefined) and handle missing context in the consumer hook. See docs/architecture/component-patterns.md', }, fixable: 'code', schema: [ diff --git a/packages/eslint-plugin/src/rules/no-global-react.js b/packages/eslint-plugin/src/rules/no-global-react.js index 9fcec153bed0a2..27a581a3898a82 100644 --- a/packages/eslint-plugin/src/rules/no-global-react.js +++ b/packages/eslint-plugin/src/rules/no-global-react.js @@ -10,7 +10,8 @@ module.exports = createRule({ description: 'Prevent accidental references to the global React namespace', }, messages: { - missingImport: 'You must explicitly import React to reference it', + missingImport: + "You must explicitly import React to reference it. Add: import * as React from 'react'; See docs/architecture/component-patterns.md", }, schema: [], }, diff --git a/packages/eslint-plugin/src/rules/no-hardcoded-style-values.js b/packages/eslint-plugin/src/rules/no-hardcoded-style-values.js new file mode 100644 index 00000000000000..7775258cdf9056 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-hardcoded-style-values.js @@ -0,0 +1,186 @@ +// @ts-check +const createRule = require('../utils/createRule'); + +/** + * @import { TSESTree } from '@typescript-eslint/utils' + */ + +// Common hardcoded color patterns +const COLOR_REGEX = /^(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|transparent|inherit|currentColor)$/; +// Common hardcoded pixel/rem/em values for spacing +const SPACING_REGEX = /^\d+(\.\d+)?(px|rem|em)$/; + +// CSS properties that typically use design tokens +const TOKEN_PROPERTIES = new Set([ + 'color', + 'backgroundColor', + 'borderColor', + 'outlineColor', + 'borderTopColor', + 'borderBottomColor', + 'borderLeftColor', + 'borderRightColor', + 'padding', + 'paddingTop', + 'paddingBottom', + 'paddingLeft', + 'paddingRight', + 'paddingBlock', + 'paddingBlockStart', + 'paddingBlockEnd', + 'paddingInline', + 'paddingInlineStart', + 'paddingInlineEnd', + 'margin', + 'marginTop', + 'marginBottom', + 'marginLeft', + 'marginRight', + 'marginBlock', + 'marginBlockStart', + 'marginBlockEnd', + 'marginInline', + 'marginInlineStart', + 'marginInlineEnd', + 'gap', + 'rowGap', + 'columnGap', + 'borderRadius', + 'borderTopLeftRadius', + 'borderTopRightRadius', + 'borderBottomLeftRadius', + 'borderBottomRightRadius', + 'fontSize', + 'fontWeight', + 'lineHeight', + 'fontFamily', + 'boxShadow', + 'borderWidth', + 'borderTopWidth', + 'borderBottomWidth', + 'borderLeftWidth', + 'borderRightWidth', + 'outlineWidth', +]); + +// Values that are always OK (CSS keywords, zero, etc.) +const ALLOWED_VALUES = new Set([ + '0', + 'auto', + 'none', + 'inherit', + 'initial', + 'unset', + 'revert', + '100%', + '50%', + 'normal', + 'bold', + 'nowrap', + 'wrap', + 'hidden', + 'visible', + 'solid', + 'dashed', + 'dotted', +]); + +module.exports = createRule({ + name: 'no-hardcoded-style-values', + meta: { + type: 'problem', + docs: { + description: 'Enforce usage of design tokens instead of hardcoded values in Griffel styles', + }, + messages: { + hardcodedColor: + "Hardcoded color '{{value}}' in '{{property}}'. Use a design token from @fluentui/react-theme instead (e.g., tokens.colorNeutralForeground1). See docs/architecture/design-tokens.md", + hardcodedSpacing: + "Hardcoded spacing '{{value}}' in '{{property}}'. Use a design token from @fluentui/react-theme instead (e.g., tokens.spacingVerticalM). See docs/architecture/design-tokens.md", + }, + schema: [], + }, + defaultOptions: [], + create: context => { + const filename = context.filename || context.getFilename(); + + // Only apply to .styles.ts files + if (!filename.endsWith('.styles.ts')) { + return {}; + } + + return { + // eslint-disable-next-line @typescript-eslint/naming-convention + Property(node) { + // Only check properties inside makeStyles calls + if (!isInsideMakeStyles(node)) { + return; + } + + const key = getPropertyName(node); + if (!key || !TOKEN_PROPERTIES.has(key)) { + return; + } + + const value = node.value; + + if (value.type === 'Literal' && typeof value.value === 'string') { + const strValue = value.value; + + if (ALLOWED_VALUES.has(strValue)) { + return; + } + + if (COLOR_REGEX.test(strValue)) { + context.report({ + node: value, + messageId: 'hardcodedColor', + data: { value: strValue, property: key }, + }); + } else if (SPACING_REGEX.test(strValue)) { + context.report({ + node: value, + messageId: 'hardcodedSpacing', + data: { value: strValue, property: key }, + }); + } + } + }, + }; + }, +}); + +/** + * Check if a node is inside a makeStyles() call + * @param {TSESTree.Node} node + * @returns {boolean} + */ +function isInsideMakeStyles(node) { + let current = node.parent; + while (current) { + if ( + current.type === 'CallExpression' && + current.callee.type === 'Identifier' && + current.callee.name === 'makeStyles' + ) { + return true; + } + current = current.parent; + } + return false; +} + +/** + * Get the name of an object property + * @param {TSESTree.Property} node + * @returns {string | null} + */ +function getPropertyName(node) { + if (node.key.type === 'Identifier') { + return node.key.name; + } + if (node.key.type === 'Literal' && typeof node.key.value === 'string') { + return node.key.value; + } + return null; +} diff --git a/packages/eslint-plugin/src/rules/no-restricted-imports/index.js b/packages/eslint-plugin/src/rules/no-restricted-imports/index.js index 4a60b504a58ecb..2237cf6994eb12 100644 --- a/packages/eslint-plugin/src/rules/no-restricted-imports/index.js +++ b/packages/eslint-plugin/src/rules/no-restricted-imports/index.js @@ -26,7 +26,8 @@ module.exports = createRule({ description: 'Restricts imports of certain packages', }, messages: { - restrictedImport: 'Import from {{ packageName }} detected which is not allowed.', + restrictedImport: + 'Import from {{ packageName }} detected which is not allowed. Use the barrel export @fluentui/react-components instead. See docs/architecture/layers.md', }, fixable: 'code', schema: [ diff --git a/tools/workspace-plugin/scripts/check-layer-boundaries.js b/tools/workspace-plugin/scripts/check-layer-boundaries.js new file mode 100644 index 00000000000000..10fa840dd8a5c9 --- /dev/null +++ b/tools/workspace-plugin/scripts/check-layer-boundaries.js @@ -0,0 +1,185 @@ +// @ts-check + +const fs = require('fs'); +const path = require('path'); +const { createProjectGraphAsync, logger } = require('@nx/devkit'); + +/** + * Validates package dependency layer boundaries. + * See docs/architecture/layers.md for the layer model. + * + * Layer hierarchy (dependencies may only flow downward): + * Tier 4: Barrel (@fluentui/react-components) + * Tier 3: Component packages (react-button, react-dialog, etc.) + * Tier 2: Foundation (react-utilities, react-theme, react-shared-contexts, etc.) + * Tier 1: Core (@griffel/react, @fluentui/tokens, etc.) + * + * Key rule: Tier 3 packages must NOT depend on other Tier 3 packages. + */ + +const REACT_COMPONENTS_DIR = path.resolve(__dirname, '../../../packages/react-components'); + +// Tier 2: Foundation packages that component packages ARE allowed to depend on +const FOUNDATION_PACKAGES = new Set([ + '@fluentui/react-utilities', + '@fluentui/react-utilities-compat', + '@fluentui/react-theme', + '@fluentui/react-shared-contexts', + '@fluentui/react-tabster', + '@fluentui/react-positioning', + '@fluentui/react-portal', + '@fluentui/react-portal-compat', + '@fluentui/react-portal-compat-context', + '@fluentui/react-overflow', + '@fluentui/react-context-selector', + '@fluentui/react-aria', + '@fluentui/react-icons-compat', + '@fluentui/react-jsx-runtime', + '@fluentui/react-motion', + '@fluentui/react-motion-components-preview', + '@fluentui/react-provider', + '@fluentui/react-virtualizer', + '@fluentui/tokens', + '@griffel/react', + '@griffel/core', +]); + +// Tier 4: Barrel package +const BARREL_PACKAGE = '@fluentui/react-components'; + +// Packages that are not component packages (build tools, migrations, etc.) +const NON_COMPONENT_PACKAGES = new Set([ + '@fluentui/react-conformance-griffel', + '@fluentui/react-migration-v0-v9', + '@fluentui/react-migration-v8-v9', + '@fluentui/react-storybook-addon', + '@fluentui/react-storybook-addon-export-to-sandbox', + '@fluentui/react-theme-sass', +]); + +main() + .catch(err => { + logger.error(err); + process.exit(1); + }) + .then(() => { + process.exit(0); + }); + +async function main() { + const graph = await createProjectGraphAsync(); + + // Build a set of all v9 component package names (Tier 3) + /** @type {Set} */ + const componentPackages = new Set(); + + const reactComponentsDirs = fs.readdirSync(REACT_COMPONENTS_DIR).filter(name => { + const pkgJsonPath = path.join(REACT_COMPONENTS_DIR, name, 'library', 'package.json'); + // Also check root-level package.json for packages without library/ structure + const rootPkgJsonPath = path.join(REACT_COMPONENTS_DIR, name, 'package.json'); + return fs.existsSync(pkgJsonPath) || fs.existsSync(rootPkgJsonPath); + }); + + for (const dir of reactComponentsDirs) { + let pkgJsonPath = path.join(REACT_COMPONENTS_DIR, dir, 'library', 'package.json'); + if (!fs.existsSync(pkgJsonPath)) { + pkgJsonPath = path.join(REACT_COMPONENTS_DIR, dir, 'package.json'); + } + if (!fs.existsSync(pkgJsonPath)) { + continue; + } + + const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')); + const pkgName = pkgJson.name; + + if ( + pkgName && + pkgName !== BARREL_PACKAGE && + !FOUNDATION_PACKAGES.has(pkgName) && + !NON_COMPONENT_PACKAGES.has(pkgName) + ) { + componentPackages.add(pkgName); + } + } + + /** @type {string[]} */ + const violations = []; + + // Check each component package's dependencies + for (const [projectName, deps] of Object.entries(graph.dependencies)) { + // Find the npm package name for this project + const projectNode = graph.nodes[projectName]; + if (!projectNode) { + continue; + } + + const projectRoot = projectNode.data.root; + if (!projectRoot || !projectRoot.includes('react-components')) { + continue; + } + + // Skip stories projects + if (projectRoot.includes('/stories')) { + continue; + } + + // Check if this is a component package + let pkgJsonPath = path.join(path.resolve(__dirname, '../../..'), projectRoot, 'package.json'); + if (!fs.existsSync(pkgJsonPath)) { + continue; + } + + const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')); + const pkgName = pkgJson.name; + + if (!componentPackages.has(pkgName)) { + continue; + } + + // Check this component's dependencies for other component packages + for (const dep of deps) { + const target = dep.target; + + // Only check workspace (non-npm) dependencies + if (target.startsWith('npm:')) { + continue; + } + + // Find the target's package name + const targetNode = graph.nodes[target]; + if (!targetNode) { + continue; + } + + const targetRoot = targetNode.data.root; + if (!targetRoot) { + continue; + } + + const targetPkgJsonPath = path.join(path.resolve(__dirname, '../../..'), targetRoot, 'package.json'); + if (!fs.existsSync(targetPkgJsonPath)) { + continue; + } + + const targetPkgJson = JSON.parse(fs.readFileSync(targetPkgJsonPath, 'utf-8')); + const targetPkgName = targetPkgJson.name; + + if (componentPackages.has(targetPkgName)) { + violations.push( + `${pkgName} depends on ${targetPkgName} — component packages (Tier 3) must not depend on other component packages. See docs/architecture/layers.md`, + ); + } + } + } + + if (violations.length > 0) { + console.error(`\n🚨 Layer boundary violations found (${violations.length}):\n`); + for (const v of violations) { + console.error(` - ${v}`); + } + console.error('\nSee docs/architecture/layers.md for the dependency layer model.'); + process.exit(1); + } + + console.log(`✅ No layer boundary violations found (checked ${componentPackages.size} component packages)`); +} diff --git a/tools/workspace-plugin/scripts/check-v9-structure.js b/tools/workspace-plugin/scripts/check-v9-structure.js new file mode 100644 index 00000000000000..2e645d9c5e24dc --- /dev/null +++ b/tools/workspace-plugin/scripts/check-v9-structure.js @@ -0,0 +1,136 @@ +// @ts-check + +const fs = require('fs'); +const path = require('path'); + +const REACT_COMPONENTS_DIR = path.resolve(__dirname, '../../../packages/react-components'); + +// Packages that are NOT component packages (utilities, configs, etc.) +const EXCLUDED_PACKAGES = new Set([ + 'react-components', // barrel package + 'react-conformance-griffel', + 'react-context-selector', + 'react-jsx-runtime', + 'react-migration-v0-v9', + 'react-migration-v8-v9', + 'react-motion', + 'react-motion-components-preview', + 'react-overflow', + 'react-portal', + 'react-portal-compat', + 'react-portal-compat-context', + 'react-positioning', + 'react-provider', + 'react-shared-contexts', + 'react-storybook-addon', + 'react-storybook-addon-export-to-sandbox', + 'react-tabster', + 'react-theme', + 'react-theme-sass', + 'react-utilities', + 'react-utilities-compat', + 'react-aria', + 'react-icons-compat', + 'react-virtualizer', +]); + +/** + * Validates that v9 component packages follow the canonical file structure. + * See docs/architecture/component-patterns.md for the expected pattern. + */ +function main() { + const packages = fs + .readdirSync(REACT_COMPONENTS_DIR) + .filter(name => name.startsWith('react-') && !EXCLUDED_PACKAGES.has(name)); + + let totalChecked = 0; + let totalIssues = 0; + /** @type {string[]} */ + const issues = []; + + for (const pkg of packages) { + const librarySrc = path.join(REACT_COMPONENTS_DIR, pkg, 'library', 'src'); + + if (!fs.existsSync(librarySrc)) { + // Some packages may not have library/src yet + continue; + } + + const componentsDir = path.join(librarySrc, 'components'); + if (!fs.existsSync(componentsDir)) { + continue; + } + + const components = fs.readdirSync(componentsDir).filter(name => { + const fullPath = path.join(componentsDir, name); + return fs.statSync(fullPath).isDirectory(); + }); + + for (const component of components) { + totalChecked++; + const componentDir = path.join(componentsDir, component); + const files = fs.readdirSync(componentDir); + + // Check required files + const requiredPatterns = [ + { pattern: `${component}.tsx`, description: 'Component file' }, + { pattern: `${component}.types.ts`, description: 'Types file' }, + { pattern: 'index.ts', description: 'Barrel export' }, + ]; + + // Styles file — must end in .styles.ts + const hasStyles = files.some(f => f.endsWith('.styles.ts')); + + // Hook file — use.ts or use.tsx + const hookPattern = `use${component}`; + const hasHook = files.some(f => f.startsWith(hookPattern) && !f.includes('Styles') && (f.endsWith('.ts') || f.endsWith('.tsx'))); + + // Render file — render.tsx + const renderPattern = `render${component}`; + const hasRender = files.some(f => f.startsWith(renderPattern) && f.endsWith('.tsx')); + + for (const { pattern, description } of requiredPatterns) { + if (!files.includes(pattern)) { + issues.push(`${pkg}/components/${component}: Missing ${description} (${pattern})`); + totalIssues++; + } + } + + if (!hasStyles) { + issues.push( + `${pkg}/components/${component}: Missing styles file (use${component}Styles.styles.ts). See docs/architecture/component-patterns.md`, + ); + totalIssues++; + } + + if (!hasHook) { + issues.push( + `${pkg}/components/${component}: Missing hook file (${hookPattern}.ts or .tsx). See docs/architecture/component-patterns.md`, + ); + totalIssues++; + } + + if (!hasRender) { + issues.push( + `${pkg}/components/${component}: Missing render file (${renderPattern}.tsx). See docs/architecture/component-patterns.md`, + ); + totalIssues++; + } + } + } + + if (totalIssues > 0) { + console.error(`\n🚨 V9 component structure issues found (${totalIssues} issues in ${totalChecked} components):\n`); + for (const issue of issues) { + console.error(` - ${issue}`); + } + console.error( + '\nExpected structure: docs/architecture/component-patterns.md', + ); + process.exit(1); + } + + console.log(`✅ All ${totalChecked} v9 components follow the canonical structure`); +} + +main(); From 0fd6b8d45d4685f5709a563d2dfb7a5ebffd2b5e Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Mon, 30 Mar 2026 19:39:04 +0200 Subject: [PATCH 06/12] chore: remove new lint rules and validation scripts Removes no-hardcoded-style-values ESLint rule, check-layer-boundaries.js, and check-v9-structure.js as requested. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../eslint-plugin/src/configs/react/index.js | 1 - packages/eslint-plugin/src/rules.js | 1 - .../src/rules/no-hardcoded-style-values.js | 186 ------------------ .../scripts/check-layer-boundaries.js | 185 ----------------- .../scripts/check-v9-structure.js | 136 ------------- 5 files changed, 509 deletions(-) delete mode 100644 packages/eslint-plugin/src/rules/no-hardcoded-style-values.js delete mode 100644 tools/workspace-plugin/scripts/check-layer-boundaries.js delete mode 100644 tools/workspace-plugin/scripts/check-v9-structure.js diff --git a/packages/eslint-plugin/src/configs/react/index.js b/packages/eslint-plugin/src/configs/react/index.js index 53d4dd2b7e3d57..27dcaacef18e88 100644 --- a/packages/eslint-plugin/src/configs/react/index.js +++ b/packages/eslint-plugin/src/configs/react/index.js @@ -43,7 +43,6 @@ module.exports = defineConfig( }, ], '@fluentui/ban-instanceof-html-element': ['error'], - '@fluentui/no-hardcoded-style-values': ['error'], '@fluentui/react-components/enforce-use-client': ['error'], '@fluentui/no-context-default-value': [ 'error', diff --git a/packages/eslint-plugin/src/rules.js b/packages/eslint-plugin/src/rules.js index 78b37fd0a94300..2bce5040589dce 100644 --- a/packages/eslint-plugin/src/rules.js +++ b/packages/eslint-plugin/src/rules.js @@ -5,7 +5,6 @@ const rules = { 'deprecated-keyboard-event-props': require('./rules/deprecated-keyboard-event-props'), 'max-len': require('./rules/max-len'), 'no-global-react': require('./rules/no-global-react'), - 'no-hardcoded-style-values': require('./rules/no-hardcoded-style-values'), 'no-tslint-comments': require('./rules/no-tslint-comments'), 'no-visibility-modifiers': require('./rules/no-visibility-modifiers'), 'no-restricted-imports': require('./rules/no-restricted-imports'), diff --git a/packages/eslint-plugin/src/rules/no-hardcoded-style-values.js b/packages/eslint-plugin/src/rules/no-hardcoded-style-values.js deleted file mode 100644 index 7775258cdf9056..00000000000000 --- a/packages/eslint-plugin/src/rules/no-hardcoded-style-values.js +++ /dev/null @@ -1,186 +0,0 @@ -// @ts-check -const createRule = require('../utils/createRule'); - -/** - * @import { TSESTree } from '@typescript-eslint/utils' - */ - -// Common hardcoded color patterns -const COLOR_REGEX = /^(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|transparent|inherit|currentColor)$/; -// Common hardcoded pixel/rem/em values for spacing -const SPACING_REGEX = /^\d+(\.\d+)?(px|rem|em)$/; - -// CSS properties that typically use design tokens -const TOKEN_PROPERTIES = new Set([ - 'color', - 'backgroundColor', - 'borderColor', - 'outlineColor', - 'borderTopColor', - 'borderBottomColor', - 'borderLeftColor', - 'borderRightColor', - 'padding', - 'paddingTop', - 'paddingBottom', - 'paddingLeft', - 'paddingRight', - 'paddingBlock', - 'paddingBlockStart', - 'paddingBlockEnd', - 'paddingInline', - 'paddingInlineStart', - 'paddingInlineEnd', - 'margin', - 'marginTop', - 'marginBottom', - 'marginLeft', - 'marginRight', - 'marginBlock', - 'marginBlockStart', - 'marginBlockEnd', - 'marginInline', - 'marginInlineStart', - 'marginInlineEnd', - 'gap', - 'rowGap', - 'columnGap', - 'borderRadius', - 'borderTopLeftRadius', - 'borderTopRightRadius', - 'borderBottomLeftRadius', - 'borderBottomRightRadius', - 'fontSize', - 'fontWeight', - 'lineHeight', - 'fontFamily', - 'boxShadow', - 'borderWidth', - 'borderTopWidth', - 'borderBottomWidth', - 'borderLeftWidth', - 'borderRightWidth', - 'outlineWidth', -]); - -// Values that are always OK (CSS keywords, zero, etc.) -const ALLOWED_VALUES = new Set([ - '0', - 'auto', - 'none', - 'inherit', - 'initial', - 'unset', - 'revert', - '100%', - '50%', - 'normal', - 'bold', - 'nowrap', - 'wrap', - 'hidden', - 'visible', - 'solid', - 'dashed', - 'dotted', -]); - -module.exports = createRule({ - name: 'no-hardcoded-style-values', - meta: { - type: 'problem', - docs: { - description: 'Enforce usage of design tokens instead of hardcoded values in Griffel styles', - }, - messages: { - hardcodedColor: - "Hardcoded color '{{value}}' in '{{property}}'. Use a design token from @fluentui/react-theme instead (e.g., tokens.colorNeutralForeground1). See docs/architecture/design-tokens.md", - hardcodedSpacing: - "Hardcoded spacing '{{value}}' in '{{property}}'. Use a design token from @fluentui/react-theme instead (e.g., tokens.spacingVerticalM). See docs/architecture/design-tokens.md", - }, - schema: [], - }, - defaultOptions: [], - create: context => { - const filename = context.filename || context.getFilename(); - - // Only apply to .styles.ts files - if (!filename.endsWith('.styles.ts')) { - return {}; - } - - return { - // eslint-disable-next-line @typescript-eslint/naming-convention - Property(node) { - // Only check properties inside makeStyles calls - if (!isInsideMakeStyles(node)) { - return; - } - - const key = getPropertyName(node); - if (!key || !TOKEN_PROPERTIES.has(key)) { - return; - } - - const value = node.value; - - if (value.type === 'Literal' && typeof value.value === 'string') { - const strValue = value.value; - - if (ALLOWED_VALUES.has(strValue)) { - return; - } - - if (COLOR_REGEX.test(strValue)) { - context.report({ - node: value, - messageId: 'hardcodedColor', - data: { value: strValue, property: key }, - }); - } else if (SPACING_REGEX.test(strValue)) { - context.report({ - node: value, - messageId: 'hardcodedSpacing', - data: { value: strValue, property: key }, - }); - } - } - }, - }; - }, -}); - -/** - * Check if a node is inside a makeStyles() call - * @param {TSESTree.Node} node - * @returns {boolean} - */ -function isInsideMakeStyles(node) { - let current = node.parent; - while (current) { - if ( - current.type === 'CallExpression' && - current.callee.type === 'Identifier' && - current.callee.name === 'makeStyles' - ) { - return true; - } - current = current.parent; - } - return false; -} - -/** - * Get the name of an object property - * @param {TSESTree.Property} node - * @returns {string | null} - */ -function getPropertyName(node) { - if (node.key.type === 'Identifier') { - return node.key.name; - } - if (node.key.type === 'Literal' && typeof node.key.value === 'string') { - return node.key.value; - } - return null; -} diff --git a/tools/workspace-plugin/scripts/check-layer-boundaries.js b/tools/workspace-plugin/scripts/check-layer-boundaries.js deleted file mode 100644 index 10fa840dd8a5c9..00000000000000 --- a/tools/workspace-plugin/scripts/check-layer-boundaries.js +++ /dev/null @@ -1,185 +0,0 @@ -// @ts-check - -const fs = require('fs'); -const path = require('path'); -const { createProjectGraphAsync, logger } = require('@nx/devkit'); - -/** - * Validates package dependency layer boundaries. - * See docs/architecture/layers.md for the layer model. - * - * Layer hierarchy (dependencies may only flow downward): - * Tier 4: Barrel (@fluentui/react-components) - * Tier 3: Component packages (react-button, react-dialog, etc.) - * Tier 2: Foundation (react-utilities, react-theme, react-shared-contexts, etc.) - * Tier 1: Core (@griffel/react, @fluentui/tokens, etc.) - * - * Key rule: Tier 3 packages must NOT depend on other Tier 3 packages. - */ - -const REACT_COMPONENTS_DIR = path.resolve(__dirname, '../../../packages/react-components'); - -// Tier 2: Foundation packages that component packages ARE allowed to depend on -const FOUNDATION_PACKAGES = new Set([ - '@fluentui/react-utilities', - '@fluentui/react-utilities-compat', - '@fluentui/react-theme', - '@fluentui/react-shared-contexts', - '@fluentui/react-tabster', - '@fluentui/react-positioning', - '@fluentui/react-portal', - '@fluentui/react-portal-compat', - '@fluentui/react-portal-compat-context', - '@fluentui/react-overflow', - '@fluentui/react-context-selector', - '@fluentui/react-aria', - '@fluentui/react-icons-compat', - '@fluentui/react-jsx-runtime', - '@fluentui/react-motion', - '@fluentui/react-motion-components-preview', - '@fluentui/react-provider', - '@fluentui/react-virtualizer', - '@fluentui/tokens', - '@griffel/react', - '@griffel/core', -]); - -// Tier 4: Barrel package -const BARREL_PACKAGE = '@fluentui/react-components'; - -// Packages that are not component packages (build tools, migrations, etc.) -const NON_COMPONENT_PACKAGES = new Set([ - '@fluentui/react-conformance-griffel', - '@fluentui/react-migration-v0-v9', - '@fluentui/react-migration-v8-v9', - '@fluentui/react-storybook-addon', - '@fluentui/react-storybook-addon-export-to-sandbox', - '@fluentui/react-theme-sass', -]); - -main() - .catch(err => { - logger.error(err); - process.exit(1); - }) - .then(() => { - process.exit(0); - }); - -async function main() { - const graph = await createProjectGraphAsync(); - - // Build a set of all v9 component package names (Tier 3) - /** @type {Set} */ - const componentPackages = new Set(); - - const reactComponentsDirs = fs.readdirSync(REACT_COMPONENTS_DIR).filter(name => { - const pkgJsonPath = path.join(REACT_COMPONENTS_DIR, name, 'library', 'package.json'); - // Also check root-level package.json for packages without library/ structure - const rootPkgJsonPath = path.join(REACT_COMPONENTS_DIR, name, 'package.json'); - return fs.existsSync(pkgJsonPath) || fs.existsSync(rootPkgJsonPath); - }); - - for (const dir of reactComponentsDirs) { - let pkgJsonPath = path.join(REACT_COMPONENTS_DIR, dir, 'library', 'package.json'); - if (!fs.existsSync(pkgJsonPath)) { - pkgJsonPath = path.join(REACT_COMPONENTS_DIR, dir, 'package.json'); - } - if (!fs.existsSync(pkgJsonPath)) { - continue; - } - - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')); - const pkgName = pkgJson.name; - - if ( - pkgName && - pkgName !== BARREL_PACKAGE && - !FOUNDATION_PACKAGES.has(pkgName) && - !NON_COMPONENT_PACKAGES.has(pkgName) - ) { - componentPackages.add(pkgName); - } - } - - /** @type {string[]} */ - const violations = []; - - // Check each component package's dependencies - for (const [projectName, deps] of Object.entries(graph.dependencies)) { - // Find the npm package name for this project - const projectNode = graph.nodes[projectName]; - if (!projectNode) { - continue; - } - - const projectRoot = projectNode.data.root; - if (!projectRoot || !projectRoot.includes('react-components')) { - continue; - } - - // Skip stories projects - if (projectRoot.includes('/stories')) { - continue; - } - - // Check if this is a component package - let pkgJsonPath = path.join(path.resolve(__dirname, '../../..'), projectRoot, 'package.json'); - if (!fs.existsSync(pkgJsonPath)) { - continue; - } - - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')); - const pkgName = pkgJson.name; - - if (!componentPackages.has(pkgName)) { - continue; - } - - // Check this component's dependencies for other component packages - for (const dep of deps) { - const target = dep.target; - - // Only check workspace (non-npm) dependencies - if (target.startsWith('npm:')) { - continue; - } - - // Find the target's package name - const targetNode = graph.nodes[target]; - if (!targetNode) { - continue; - } - - const targetRoot = targetNode.data.root; - if (!targetRoot) { - continue; - } - - const targetPkgJsonPath = path.join(path.resolve(__dirname, '../../..'), targetRoot, 'package.json'); - if (!fs.existsSync(targetPkgJsonPath)) { - continue; - } - - const targetPkgJson = JSON.parse(fs.readFileSync(targetPkgJsonPath, 'utf-8')); - const targetPkgName = targetPkgJson.name; - - if (componentPackages.has(targetPkgName)) { - violations.push( - `${pkgName} depends on ${targetPkgName} — component packages (Tier 3) must not depend on other component packages. See docs/architecture/layers.md`, - ); - } - } - } - - if (violations.length > 0) { - console.error(`\n🚨 Layer boundary violations found (${violations.length}):\n`); - for (const v of violations) { - console.error(` - ${v}`); - } - console.error('\nSee docs/architecture/layers.md for the dependency layer model.'); - process.exit(1); - } - - console.log(`✅ No layer boundary violations found (checked ${componentPackages.size} component packages)`); -} diff --git a/tools/workspace-plugin/scripts/check-v9-structure.js b/tools/workspace-plugin/scripts/check-v9-structure.js deleted file mode 100644 index 2e645d9c5e24dc..00000000000000 --- a/tools/workspace-plugin/scripts/check-v9-structure.js +++ /dev/null @@ -1,136 +0,0 @@ -// @ts-check - -const fs = require('fs'); -const path = require('path'); - -const REACT_COMPONENTS_DIR = path.resolve(__dirname, '../../../packages/react-components'); - -// Packages that are NOT component packages (utilities, configs, etc.) -const EXCLUDED_PACKAGES = new Set([ - 'react-components', // barrel package - 'react-conformance-griffel', - 'react-context-selector', - 'react-jsx-runtime', - 'react-migration-v0-v9', - 'react-migration-v8-v9', - 'react-motion', - 'react-motion-components-preview', - 'react-overflow', - 'react-portal', - 'react-portal-compat', - 'react-portal-compat-context', - 'react-positioning', - 'react-provider', - 'react-shared-contexts', - 'react-storybook-addon', - 'react-storybook-addon-export-to-sandbox', - 'react-tabster', - 'react-theme', - 'react-theme-sass', - 'react-utilities', - 'react-utilities-compat', - 'react-aria', - 'react-icons-compat', - 'react-virtualizer', -]); - -/** - * Validates that v9 component packages follow the canonical file structure. - * See docs/architecture/component-patterns.md for the expected pattern. - */ -function main() { - const packages = fs - .readdirSync(REACT_COMPONENTS_DIR) - .filter(name => name.startsWith('react-') && !EXCLUDED_PACKAGES.has(name)); - - let totalChecked = 0; - let totalIssues = 0; - /** @type {string[]} */ - const issues = []; - - for (const pkg of packages) { - const librarySrc = path.join(REACT_COMPONENTS_DIR, pkg, 'library', 'src'); - - if (!fs.existsSync(librarySrc)) { - // Some packages may not have library/src yet - continue; - } - - const componentsDir = path.join(librarySrc, 'components'); - if (!fs.existsSync(componentsDir)) { - continue; - } - - const components = fs.readdirSync(componentsDir).filter(name => { - const fullPath = path.join(componentsDir, name); - return fs.statSync(fullPath).isDirectory(); - }); - - for (const component of components) { - totalChecked++; - const componentDir = path.join(componentsDir, component); - const files = fs.readdirSync(componentDir); - - // Check required files - const requiredPatterns = [ - { pattern: `${component}.tsx`, description: 'Component file' }, - { pattern: `${component}.types.ts`, description: 'Types file' }, - { pattern: 'index.ts', description: 'Barrel export' }, - ]; - - // Styles file — must end in .styles.ts - const hasStyles = files.some(f => f.endsWith('.styles.ts')); - - // Hook file — use.ts or use.tsx - const hookPattern = `use${component}`; - const hasHook = files.some(f => f.startsWith(hookPattern) && !f.includes('Styles') && (f.endsWith('.ts') || f.endsWith('.tsx'))); - - // Render file — render.tsx - const renderPattern = `render${component}`; - const hasRender = files.some(f => f.startsWith(renderPattern) && f.endsWith('.tsx')); - - for (const { pattern, description } of requiredPatterns) { - if (!files.includes(pattern)) { - issues.push(`${pkg}/components/${component}: Missing ${description} (${pattern})`); - totalIssues++; - } - } - - if (!hasStyles) { - issues.push( - `${pkg}/components/${component}: Missing styles file (use${component}Styles.styles.ts). See docs/architecture/component-patterns.md`, - ); - totalIssues++; - } - - if (!hasHook) { - issues.push( - `${pkg}/components/${component}: Missing hook file (${hookPattern}.ts or .tsx). See docs/architecture/component-patterns.md`, - ); - totalIssues++; - } - - if (!hasRender) { - issues.push( - `${pkg}/components/${component}: Missing render file (${renderPattern}.tsx). See docs/architecture/component-patterns.md`, - ); - totalIssues++; - } - } - } - - if (totalIssues > 0) { - console.error(`\n🚨 V9 component structure issues found (${totalIssues} issues in ${totalChecked} components):\n`); - for (const issue of issues) { - console.error(` - ${issue}`); - } - console.error( - '\nExpected structure: docs/architecture/component-patterns.md', - ); - process.exit(1); - } - - console.log(`✅ All ${totalChecked} v9 components follow the canonical structure`); -} - -main(); From a0e6870fb733f3a7455f9a8894889b167eff3d5c Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Tue, 31 Mar 2026 12:38:53 +0200 Subject: [PATCH 07/12] style: fix Prettier formatting for all new and modified files Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/agents/agentic-workflows.agent.md | 22 ++++++-- .github/instructions/copilot.instructions.md | 18 +++---- .../workflows/agent-docs-grooming.lock.yml | 4 +- .github/workflows/agent-docs-grooming.md | 9 +++- .github/workflows/agent-fix.md | 11 ++-- .github/workflows/agent-review.md | 35 +++++++----- .../agent-skills-improvement.lock.yml | 2 +- .github/workflows/agent-skills-improvement.md | 10 ++++ .github/workflows/agent-triage.md | 19 ++++--- AGENTS.md | 54 +++++++++---------- docs/architecture/component-patterns.md | 12 ++--- docs/architecture/design-tokens.md | 24 ++++----- docs/architecture/layers.md | 20 +++---- docs/quality-grades.md | 18 +++---- docs/team-routing.md | 18 ++++--- docs/tech-debt-tracker.md | 8 +-- docs/workflows/contributing.md | 2 + docs/workflows/testing.md | 17 +++--- 18 files changed, 178 insertions(+), 125 deletions(-) diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md index bf1f4ae6d6b84b..65795ed06f6ef6 100644 --- a/.github/agents/agentic-workflows.agent.md +++ b/.github/agents/agentic-workflows.agent.md @@ -13,7 +13,7 @@ This is a **dispatcher agent** that routes your request to the appropriate speci - **Creating new workflows**: Routes to `create` prompt - **Updating existing workflows**: Routes to `update` prompt -- **Debugging workflows**: Routes to `debug` prompt +- **Debugging workflows**: Routes to `debug` prompt - **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt - **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments - **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt @@ -50,81 +50,97 @@ When you interact with this agent, it will: ## Available Prompts ### Create New Workflow + **Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/create-agentic-workflow.md **Use cases**: + - "Create a workflow that triages issues" - "I need a workflow to label pull requests" - "Design a weekly research automation" -### Update Existing Workflow +### Update Existing Workflow + **Load when**: User wants to modify, improve, or refactor an existing workflow **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/update-agentic-workflow.md **Use cases**: + - "Add web-fetch tool to the issue-classifier workflow" - "Update the PR reviewer to use discussions instead of issues" - "Improve the prompt for the weekly-research workflow" -### Debug Workflow +### Debug Workflow + **Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/debug-agentic-workflow.md **Use cases**: + - "Why is this workflow failing?" - "Analyze the logs for workflow X" - "Investigate missing tool calls in run #12345" ### Upgrade Agentic Workflows + **Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/upgrade-agentic-workflows.md **Use cases**: + - "Upgrade all workflows to the latest version" - "Fix deprecated fields in workflows" - "Apply breaking changes from the new release" ### Create a Report-Generating Workflow + **Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/report.md **Use cases**: + - "Create a weekly CI health report" - "Post a daily security audit to Discussions" - "Add a status update comment to open PRs" ### Create Shared Agentic Workflow + **Load when**: User wants to create a reusable workflow component or wrap an MCP server **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/create-shared-agentic-workflow.md **Use cases**: + - "Create a shared component for Notion integration" - "Wrap the Slack MCP server as a reusable component" - "Design a shared workflow for database queries" ### Fix Dependabot PRs + **Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/dependabot.md **Use cases**: + - "Fix the open Dependabot PRs for npm dependencies" - "Bundle and close the Dependabot PRs for workflow dependencies" - "Update @playwright/test to fix the Dependabot PR" ### Analyze Test Coverage + **Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. **Prompt file**: https://github.com/github/gh-aw/blob/v0.50.6/.github/aw/test-coverage.md **Use cases**: + - "Create a workflow that comments coverage on PRs" - "Analyze coverage trends over time" - "Add a coverage gate that blocks PRs below a threshold" diff --git a/.github/instructions/copilot.instructions.md b/.github/instructions/copilot.instructions.md index a44db3cbdb3e5e..b1d8937c035696 100644 --- a/.github/instructions/copilot.instructions.md +++ b/.github/instructions/copilot.instructions.md @@ -37,16 +37,16 @@ npx beachball change # Create change file (required for published ## Deep Dives -| Topic | Doc | -|-------|-----| +| Topic | Doc | +| --------------------------------------------- | ---------------------------------------------------------------------------------------- | | V9 component structure, hooks, slots, Griffel | [docs/architecture/component-patterns.md](../../docs/architecture/component-patterns.md) | -| Design tokens and theming | [docs/architecture/design-tokens.md](../../docs/architecture/design-tokens.md) | -| Package dependency layers | [docs/architecture/layers.md](../../docs/architecture/layers.md) | -| PR workflow, branch naming, checklist | [docs/workflows/contributing.md](../../docs/workflows/contributing.md) | -| Test types, SSR safety, conformance | [docs/workflows/testing.md](../../docs/workflows/testing.md) | -| Teams, labels, CODEOWNERS | [docs/team-routing.md](../../docs/team-routing.md) | -| Quality grades per package | [docs/quality-grades.md](../../docs/quality-grades.md) | -| Known tech debt | [docs/tech-debt-tracker.md](../../docs/tech-debt-tracker.md) | +| Design tokens and theming | [docs/architecture/design-tokens.md](../../docs/architecture/design-tokens.md) | +| Package dependency layers | [docs/architecture/layers.md](../../docs/architecture/layers.md) | +| PR workflow, branch naming, checklist | [docs/workflows/contributing.md](../../docs/workflows/contributing.md) | +| Test types, SSR safety, conformance | [docs/workflows/testing.md](../../docs/workflows/testing.md) | +| Teams, labels, CODEOWNERS | [docs/team-routing.md](../../docs/team-routing.md) | +| Quality grades per package | [docs/quality-grades.md](../../docs/quality-grades.md) | +| Known tech debt | [docs/tech-debt-tracker.md](../../docs/tech-debt-tracker.md) | ## Issue & PR Labels diff --git a/.github/workflows/agent-docs-grooming.lock.yml b/.github/workflows/agent-docs-grooming.lock.yml index 7be24c4ab6686c..6d3a387b17a998 100644 --- a/.github/workflows/agent-docs-grooming.lock.yml +++ b/.github/workflows/agent-docs-grooming.lock.yml @@ -22,12 +22,12 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"3f302991a4628d6227769b22185201326fc159676d554198db10adbb49583dfb","compiler_version":"v0.50.6"} +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"914290db4a747f3d1c2b0b6eeafb80bbaf6e5835547755a9657dc49901b97fcb","compiler_version":"v0.50.6"} name: "Weekly Documentation Audit" "on": schedule: - - cron: "49 21 * * 1" + - cron: "19 21 * * 1" # Friendly format: weekly on monday (scattered) workflow_dispatch: diff --git a/.github/workflows/agent-docs-grooming.md b/.github/workflows/agent-docs-grooming.md index 24c539f5474a11..dddab24dd55603 100644 --- a/.github/workflows/agent-docs-grooming.md +++ b/.github/workflows/agent-docs-grooming.md @@ -15,8 +15,8 @@ tools: safe-outputs: create-issue: - title-prefix: "Docs Audit — " - labels: ["Area: Documentation"] + title-prefix: 'Docs Audit — ' + labels: ['Area: Documentation'] close-older-issues: true max: 1 --- @@ -73,18 +73,23 @@ Create an issue with a structured audit report: **Scope**: packages/react-components/ ### Missing or Incomplete READMEs + - [ ] `@fluentui/react-` — [what's missing] ### Missing Storybook Stories + - [ ] `@fluentui/react-` — no stories directory ### API Doc Issues + - [ ] `@fluentui/react-` — [issue found] ### Stale Code Comments + - [ ] `packages/.../file.ts:LINE` — "TODO: ..." — [assessment] ### Summary + - X packages checked - X issues found (X critical, X moderate, X low) ``` diff --git a/.github/workflows/agent-fix.md b/.github/workflows/agent-fix.md index ca6bedff64c045..fdbe9c3e9648b5 100644 --- a/.github/workflows/agent-fix.md +++ b/.github/workflows/agent-fix.md @@ -38,12 +38,12 @@ receives the `agent:fix` label. If the triggering label is NOT `agent:fix`, do n This is an Nx monorepo. Package locations: -| Package pattern | Path | -|----------------|------| +| Package pattern | Path | +| ----------------------------- | ----------------------------------------------------- | | `@fluentui/react-` (v9) | `packages/react-components/react-/library/src/` | -| `@fluentui/react` (v8) | `packages/react/src/` | -| `@fluentui/web-components` | `packages/web-components/src/` | -| `@fluentui/react-charting` | `packages/charts/react-charting/src/` | +| `@fluentui/react` (v8) | `packages/react/src/` | +| `@fluentui/web-components` | `packages/web-components/src/` | +| `@fluentui/react-charting` | `packages/charts/react-charting/src/` | ### v9 Component Architecture @@ -104,6 +104,7 @@ npx beachball change --type patch --message "fix(): " ## When to Bail Out If you cannot confidently fix the issue, comment explaining: + - What you investigated - What you think the root cause might be - Why you couldn't complete the fix diff --git a/.github/workflows/agent-review.md b/.github/workflows/agent-review.md index beac4a266e62dd..f3dddd2ff313f6 100644 --- a/.github/workflows/agent-review.md +++ b/.github/workflows/agent-review.md @@ -41,17 +41,20 @@ post a brief comment saying "Skipping — not an agent-generated PR" and stop. For each file in the diff, evaluate: **Correctness (0-25)** + - Does this actually fix the reported issue, or just mask symptoms? - Could it introduce regressions in other scenarios? - Are there edge cases not handled (null, undefined, empty arrays, RTL, high contrast)? **Completeness (0-25)** + - Are unit tests added or updated to cover the change? - Are snapshots updated if component output changed? - Is a beachball change file included for published packages? - Does it handle both controlled and uncontrolled patterns (if applicable)? **Conventions (0-25)** + - Are design tokens used instead of hardcoded values? - Is the slot system used correctly? - Does `mergeClasses()` preserve user className as the last argument? @@ -60,6 +63,7 @@ For each file in the diff, evaluate: - Is it importing from public API paths (not internal/private paths)? **Risk (0-25)** + - Does this change the public API surface (check for `.api.md` diffs)? - Could it break SSR? (unguarded `window`/`document`/`navigator`) - Could it break accessibility? @@ -75,24 +79,29 @@ Post a single comment with this exact format: **Confidence Score: X/100** -| Dimension | Score | Assessment | -|-------------|--------|------------| -| Correctness | X/25 | [brief note] | -| Completeness| X/25 | [brief note] | -| Conventions | X/25 | [brief note] | -| Risk | X/25 | [brief note] | +| Dimension | Score | Assessment | +| ------------ | ----- | ------------ | +| Correctness | X/25 | [brief note] | +| Completeness | X/25 | [brief note] | +| Conventions | X/25 | [brief note] | +| Risk | X/25 | [brief note] | ### What Looks Good + - [things done correctly] ### Concerns + - [issues found with file:line references] ### Questions + - [things needing clarification] ### Recommendation + [One of:] + - **APPROVE** — Safe to merge, high confidence - **REVIEW** — Needs human review on: [specific points] - **REJECT** — Must fix before merge: [blocking issues] @@ -100,13 +109,13 @@ Post a single comment with this exact format: ## Scoring Guide -| Score Range | Meaning | -|------------|---------| -| 90-100 | Excellent — minimal risk, well-tested, follows all conventions | -| 70-89 | Good — minor concerns, fundamentally sound | -| 50-69 | Needs work — gaps in testing, conventions, or correctness | -| 30-49 | Risky — significant correctness or completeness concerns | -| 0-29 | Do not merge — fundamental problems | +| Score Range | Meaning | +| ----------- | -------------------------------------------------------------- | +| 90-100 | Excellent — minimal risk, well-tested, follows all conventions | +| 70-89 | Good — minor concerns, fundamentally sound | +| 50-69 | Needs work — gaps in testing, conventions, or correctness | +| 30-49 | Risky — significant correctness or completeness concerns | +| 0-29 | Do not merge — fundamental problems | ## Common AI-Generated Issues to Watch For diff --git a/.github/workflows/agent-skills-improvement.lock.yml b/.github/workflows/agent-skills-improvement.lock.yml index fe10ae5122f7c2..7a70302d9b3edc 100644 --- a/.github/workflows/agent-skills-improvement.lock.yml +++ b/.github/workflows/agent-skills-improvement.lock.yml @@ -27,7 +27,7 @@ name: "Weekly Agent Skills Improvement" "on": schedule: - - cron: "24 1 * * 5" + - cron: "38 3 * * 5" # Friendly format: weekly on friday (scattered) workflow_dispatch: diff --git a/.github/workflows/agent-skills-improvement.md b/.github/workflows/agent-skills-improvement.md index 59ef62e85ceef7..1c9f78933abe16 100644 --- a/.github/workflows/agent-skills-improvement.md +++ b/.github/workflows/agent-skills-improvement.md @@ -38,16 +38,21 @@ Find recent agent activity: ## Step 2: Categorize Outcomes ### Merged Successfully + What did the agent do right? Note any non-obvious good patterns. ### Merged with Changes Requested + What did humans correct? These are the most valuable signals: + - Conventions the agent didn't follow - Missing steps (forgot change file, didn't update tests, etc.) - Wrong assumptions about the codebase ### Closed without Merge + Why was it rejected? Look for: + - Fundamental misunderstandings - Wrong approach entirely - Scope creep @@ -55,6 +60,7 @@ Why was it rejected? Look for: ## Step 3: Analyze Review Scores If devil's advocate reviews exist: + - Which dimensions consistently score low? - Are scores well-calibrated? (high-scored PRs that got rejected = too lenient) - Are there false negatives? (low-scored PRs that were fine = too strict) @@ -72,20 +78,24 @@ Open a pull request modifying one or more of: Title: `chore: agent skills update — [date]` Body: + ```markdown ## Skills Update ### Data Analyzed + - X PRs reviewed (Y merged, Z closed, W pending) ### Changes #### [File changed] + - **Added**: [rule] — Motivated by PR #N: [what happened] - **Clarified**: [rule] — PR #N misinterpreted the original wording - **Removed**: [rule] — Caused [problem] in PRs #N, #M ### Patterns Observed + - [Theme 1] - [Theme 2] ``` diff --git a/.github/workflows/agent-triage.md b/.github/workflows/agent-triage.md index 8b60fe787c6408..cadc00bdb3743b 100644 --- a/.github/workflows/agent-triage.md +++ b/.github/workflows/agent-triage.md @@ -35,14 +35,14 @@ When a new issue is opened, perform the following steps: Determine which product area this issue belongs to based on the issue content: -| Signal | Label | -|--------|-------| -| `@fluentui/react-components` or v9 component names (react-button, react-dialog, etc.) | `Fluent UI react-components (v9)` | -| `@fluentui/react` or v8 component names (DetailsList, CommandBar, etc.) | `Fluent UI react (v8)` | -| `@fluentui/web-components` or web component names | `web-components`, `Fluent UI WC (v3)` | -| `@fluentui/react-charting` or chart types (AreaChart, DonutChart, etc.) | `Package: charting` | -| Build, CI, Nx, pipelines, webpack, tooling issues | `Area: Build System` | -| Documentation, storybook, website, examples | `Area: Documentation` | +| Signal | Label | +| ------------------------------------------------------------------------------------- | ------------------------------------- | +| `@fluentui/react-components` or v9 component names (react-button, react-dialog, etc.) | `Fluent UI react-components (v9)` | +| `@fluentui/react` or v8 component names (DetailsList, CommandBar, etc.) | `Fluent UI react (v8)` | +| `@fluentui/web-components` or web component names | `web-components`, `Fluent UI WC (v3)` | +| `@fluentui/react-charting` or chart types (AreaChart, DonutChart, etc.) | `Package: charting` | +| Build, CI, Nx, pipelines, webpack, tooling issues | `Area: Build System` | +| Documentation, storybook, website, examples | `Area: Documentation` | ### Type @@ -52,6 +52,7 @@ Determine which product area this issue belongs to based on the issue content: ## 2. Validate the Issue ### For Bug Reports, check: + - Does it have reproduction steps? - Does it include expected vs actual behavior? - Does it mention a package version? @@ -60,10 +61,12 @@ Determine which product area this issue belongs to based on the issue content: If critical info is missing, add label `Needs: Author Feedback` and comment asking for specifics. ### Duplicate Check + - Search recent issues (last 90 days) for similar titles or keywords. - If a likely duplicate exists, comment linking to it and add label `Resolution: Duplicate`. ### Validity + - Is this actually a Fluent UI issue or a general React/CSS question? - If not actionable, add `Needs: Author Feedback` and ask for clarification. diff --git a/AGENTS.md b/AGENTS.md index df60341a118b56..4bf784c0079a9b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,26 +16,26 @@ Nx monorepo. Yarn v1. Node 22. TypeScript strict. ~200 packages. Millions of use ## Architecture -| Topic | Location | -|-------|----------| +| Topic | Location | +| --------------------------------------------- | ---------------------------------------------------------------------------------- | | V9 component patterns (hooks, slots, Griffel) | [docs/architecture/component-patterns.md](docs/architecture/component-patterns.md) | -| Design tokens and theming | [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md) | -| Package dependency layers | [docs/architecture/layers.md](docs/architecture/layers.md) | +| Design tokens and theming | [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md) | +| Package dependency layers | [docs/architecture/layers.md](docs/architecture/layers.md) | ## Workflows -| Topic | Location | -|-------|----------| +| Topic | Location | +| ------------------------------------ | ---------------------------------------------------------------- | | PR checklist, change files, commands | [docs/workflows/contributing.md](docs/workflows/contributing.md) | -| Testing guide (unit, VRT, SSR, E2E) | [docs/workflows/testing.md](docs/workflows/testing.md) | -| Team routing and label taxonomy | [docs/team-routing.md](docs/team-routing.md) | +| Testing guide (unit, VRT, SSR, E2E) | [docs/workflows/testing.md](docs/workflows/testing.md) | +| Team routing and label taxonomy | [docs/team-routing.md](docs/team-routing.md) | ## Quality Tracking -| Topic | Location | -|-------|----------| -| Per-package quality grades | [docs/quality-grades.md](docs/quality-grades.md) | -| Technical debt tracker | [docs/tech-debt-tracker.md](docs/tech-debt-tracker.md) | +| Topic | Location | +| -------------------------- | ------------------------------------------------------ | +| Per-package quality grades | [docs/quality-grades.md](docs/quality-grades.md) | +| Technical debt tracker | [docs/tech-debt-tracker.md](docs/tech-debt-tracker.md) | ## Key Rules @@ -48,21 +48,21 @@ Nx monorepo. Yarn v1. Node 22. TypeScript strict. ~200 packages. Millions of use ## Agentic Workflows -| Workflow | Trigger | Purpose | -|----------|---------|---------| -| `agent-triage` | Issue opened | Classify, label, validate issues | -| `agent-fix` | `agent:fix` label | Implement fixes, open PRs | -| `agent-review` | PR opened | Devil's advocate review with confidence score | -| `agent-docs-grooming` | Weekly Monday | Documentation audit | -| `agent-skills-improvement` | Weekly Friday | Improve agent instructions from PR patterns | +| Workflow | Trigger | Purpose | +| -------------------------- | ----------------- | --------------------------------------------- | +| `agent-triage` | Issue opened | Classify, label, validate issues | +| `agent-fix` | `agent:fix` label | Implement fixes, open PRs | +| `agent-review` | PR opened | Devil's advocate review with confidence score | +| `agent-docs-grooming` | Weekly Monday | Documentation audit | +| `agent-skills-improvement` | Weekly Friday | Improve agent instructions from PR patterns | ## Package Layout -| Area | Path | Status | -|------|------|--------| -| V9 components | `packages/react-components/` | Active development | -| V8 components | `packages/react/` | Maintenance only | -| Web Components | `packages/web-components/` | Active | -| Charting | `packages/charts/` | Active | -| Build tooling | `tools/` | Active | -| ESLint plugin | `packages/eslint-plugin/` | Active | +| Area | Path | Status | +| -------------- | ---------------------------- | ------------------ | +| V9 components | `packages/react-components/` | Active development | +| V8 components | `packages/react/` | Maintenance only | +| Web Components | `packages/web-components/` | Active | +| Charting | `packages/charts/` | Active | +| Build tooling | `tools/` | Active | +| ESLint plugin | `packages/eslint-plugin/` | Active | diff --git a/docs/architecture/component-patterns.md b/docs/architecture/component-patterns.md index c23eb14f3f8dc9..6d9ff3df0812f2 100644 --- a/docs/architecture/component-patterns.md +++ b/docs/architecture/component-patterns.md @@ -37,12 +37,12 @@ Components use three core hooks: ### Where to Fix Bugs -| Bug type | Fix location | -|----------|-------------| -| State / behavior | `use.ts` | -| Styling | `useStyles.styles.ts` | -| Rendering / JSX | `render.tsx` | -| Types / props | `.types.ts` | +| Bug type | Fix location | +| ---------------- | --------------------------- | +| State / behavior | `use.ts` | +| Styling | `useStyles.styles.ts` | +| Rendering / JSX | `render.tsx` | +| Types / props | `.types.ts` | ## Slot System diff --git a/docs/architecture/design-tokens.md b/docs/architecture/design-tokens.md index 772966ef629fd5..b326ac6d599c40 100644 --- a/docs/architecture/design-tokens.md +++ b/docs/architecture/design-tokens.md @@ -7,17 +7,17 @@ Hardcoded values break theming, high contrast mode, and dark mode. ## Token Categories -| Category | Example tokens | Use for | -|----------|---------------|---------| -| Color | `tokens.colorNeutralForeground1`, `tokens.colorBrandBackground` | All colors | -| Spacing | `tokens.spacingVerticalM`, `tokens.spacingHorizontalL` | Padding, margin, gap | -| Border radius | `tokens.borderRadiusMedium`, `tokens.borderRadiusLarge` | Border radius | -| Font | `tokens.fontSizeBase300`, `tokens.fontWeightSemibold` | Typography | -| Line height | `tokens.lineHeightBase300` | Line height | -| Stroke | `tokens.strokeWidthThin`, `tokens.strokeWidthThick` | Border width | -| Shadow | `tokens.shadow4`, `tokens.shadow16` | Box shadow | -| Duration | `tokens.durationNormal`, `tokens.durationFast` | Animations | -| Easing | `tokens.curveEasyEase` | Animation timing | +| Category | Example tokens | Use for | +| ------------- | --------------------------------------------------------------- | -------------------- | +| Color | `tokens.colorNeutralForeground1`, `tokens.colorBrandBackground` | All colors | +| Spacing | `tokens.spacingVerticalM`, `tokens.spacingHorizontalL` | Padding, margin, gap | +| Border radius | `tokens.borderRadiusMedium`, `tokens.borderRadiusLarge` | Border radius | +| Font | `tokens.fontSizeBase300`, `tokens.fontWeightSemibold` | Typography | +| Line height | `tokens.lineHeightBase300` | Line height | +| Stroke | `tokens.strokeWidthThin`, `tokens.strokeWidthThick` | Border width | +| Shadow | `tokens.shadow4`, `tokens.shadow16` | Box shadow | +| Duration | `tokens.durationNormal`, `tokens.durationFast` | Animations | +| Easing | `tokens.curveEasyEase` | Animation timing | ## Examples @@ -45,7 +45,7 @@ Themes define CSS custom properties consumed by components: // FluentProvider injects CSS variables into DOM - +; // Tokens resolve to CSS variables at build time makeStyles({ diff --git a/docs/architecture/layers.md b/docs/architecture/layers.md index c1a0f8a1b11024..749a905cb884c5 100644 --- a/docs/architecture/layers.md +++ b/docs/architecture/layers.md @@ -39,13 +39,13 @@ Dependencies may only flow **downward** — never upward or sideways within the Projects are tagged for identification: -| Tag | Meaning | -|-----|---------| -| `vNext` | v9 packages | -| `v8` | v8 packages (maintenance only) | -| `platform:web` | Browser-targeted | -| `platform:node` | Node.js-targeted | -| `type:stories` | Storybook story packages | -| `web-components` | Web Components packages | -| `charting` | Charting packages | -| `tools` | Build tooling | +| Tag | Meaning | +| ---------------- | ------------------------------ | +| `vNext` | v9 packages | +| `v8` | v8 packages (maintenance only) | +| `platform:web` | Browser-targeted | +| `platform:node` | Node.js-targeted | +| `type:stories` | Storybook story packages | +| `web-components` | Web Components packages | +| `charting` | Charting packages | +| `tools` | Build tooling | diff --git a/docs/quality-grades.md b/docs/quality-grades.md index 0d1b7eb92f3d91..07b1741e9e9cd6 100644 --- a/docs/quality-grades.md +++ b/docs/quality-grades.md @@ -5,12 +5,12 @@ Updated by the `agent-docs-grooming` workflow and the `agent-skills-improvement` ## Grading Criteria -| Grade | Meaning | -|-------|---------| -| A | README + stories + tests + API docs all present and current | -| B | Missing one of: complete README, full story coverage, or up-to-date API docs | -| C | Missing two or more quality signals | -| D | Minimal or no documentation/testing | +| Grade | Meaning | +| ----- | ---------------------------------------------------------------------------- | +| A | README + stories + tests + API docs all present and current | +| B | Missing one of: complete README, full story coverage, or up-to-date API docs | +| C | Missing two or more quality signals | +| D | Minimal or no documentation/testing | ## Quality Signals @@ -24,9 +24,9 @@ Updated by the `agent-docs-grooming` workflow and the `agent-skills-improvement` -| Package | README | Stories | Tests | API Docs | Conformance | Grade | -|---------|--------|---------|-------|----------|-------------|-------| -| _To be populated by first docs audit run_ | | | | | | | +| Package | README | Stories | Tests | API Docs | Conformance | Grade | +| ----------------------------------------- | ------ | ------- | ----- | -------- | ----------- | ----- | +| _To be populated by first docs audit run_ | | | | | | | ## How to Update diff --git a/docs/team-routing.md b/docs/team-routing.md index 45d723a85247c2..600039e0592cb4 100644 --- a/docs/team-routing.md +++ b/docs/team-routing.md @@ -2,32 +2,36 @@ ## Team Routing -| Area | Team | Packages | -|------|------|----------| -| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | -| v8 Components | @microsoft/cxe-red | `packages/react/*` | -| Web Components | @microsoft/fui-wc | `packages/web-components/*` | -| Charting | @microsoft/charting-team | `packages/charts/*` | -| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | +| Area | Team | Packages | +| -------------- | ------------------------------- | ------------------------------------ | +| v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | +| v8 Components | @microsoft/cxe-red | `packages/react/*` | +| Web Components | @microsoft/fui-wc | `packages/web-components/*` | +| Charting | @microsoft/charting-team | `packages/charts/*` | +| Build/Tooling | @microsoft/fluentui-react-build | `tools/*`, `.github/*`, root configs | ## Label Taxonomy ### Product Labels + - `Fluent UI react-components (v9)` — v9 React components - `Fluent UI react (v8)` — v8 React components - `web-components` / `Fluent UI WC (v3)` — Web Components - `Package: charting` — Charting library ### Type Labels + - `Type: Bug :bug:` — Bug reports - `Type: Feature` — Feature requests ### Status Labels + - `Needs: Triage :mag:` — Needs team review - `Needs: Author Feedback` — Waiting on issue author - `Needs: Attention` — Needs team attention ### Resolution Labels + - `Resolution: Duplicate` — Duplicate of another issue - `Resolution: Not An Issue` — Not a valid issue - `Resolution: By Design` — Working as intended diff --git a/docs/tech-debt-tracker.md b/docs/tech-debt-tracker.md index e6334877f05f5b..16a125a444438e 100644 --- a/docs/tech-debt-tracker.md +++ b/docs/tech-debt-tracker.md @@ -7,9 +7,9 @@ Items are added during code review, agent runs, and manual audits. -| ID | Category | Description | Location | Priority | Added | -|----|----------|-------------|----------|----------|-------| -| _To be populated by agent runs and manual audits_ | | | | | | +| ID | Category | Description | Location | Priority | Added | +| ------------------------------------------------- | -------- | ----------- | -------- | -------- | ----- | +| _To be populated by agent runs and manual audits_ | | | | | | ## Categories @@ -31,9 +31,11 @@ Items are added during code review, agent runs, and manual audits. ## How to Use ### For agents + When you find technical debt during a fix or review, add a row to the table above. When you fix a debt item, remove its row. ### For engineers + Review this file when planning sprints. Use it to justify cleanup PRs. The `agent-skills-improvement` workflow analyzes this file to identify recurring patterns. diff --git a/docs/workflows/contributing.md b/docs/workflows/contributing.md index 3bee7f518157cb..25593ef1483e3a 100644 --- a/docs/workflows/contributing.md +++ b/docs/workflows/contributing.md @@ -28,9 +28,11 @@ yarn create-component # Interactive generator ## PR Checklist 1. **Change file** — Required for any published package change: + ```bash npx beachball change --type patch --message "fix(react-button): description" ``` + Use `patch` for fixes, `minor` for features. Never `major` without approval. 2. **Tests pass** — `npx nx run :test` diff --git a/docs/workflows/testing.md b/docs/workflows/testing.md index eb19b4763d3671..16b039f47cb2aa 100644 --- a/docs/workflows/testing.md +++ b/docs/workflows/testing.md @@ -2,14 +2,14 @@ ## Test Types -| Type | Tool | Command | Purpose | -|------|------|---------|---------| -| Unit | Jest + React Testing Library | `npx nx run :test` | Component behavior, hooks, utils | -| Visual Regression | Storybook + StoryWright | `npx nx run vr-tests-react-components:test-vr` | Screenshot diffs (CI only) | -| E2E | Cypress | `npx nx run react-components:e2e` | Integration flows | -| SSR | Custom | `npx nx run ssr-tests-v9:test-ssr` | Server-side rendering safety | -| Cross-React | Custom | `npx nx run rit-tests-v9:test-rit` | React version compatibility | -| Conformance | isConformant | Part of unit tests | Consistent component API | +| Type | Tool | Command | Purpose | +| ----------------- | ---------------------------- | ---------------------------------------------- | -------------------------------- | +| Unit | Jest + React Testing Library | `npx nx run :test` | Component behavior, hooks, utils | +| Visual Regression | Storybook + StoryWright | `npx nx run vr-tests-react-components:test-vr` | Screenshot diffs (CI only) | +| E2E | Cypress | `npx nx run react-components:e2e` | Integration flows | +| SSR | Custom | `npx nx run ssr-tests-v9:test-ssr` | Server-side rendering safety | +| Cross-React | Custom | `npx nx run rit-tests-v9:test-rit` | React version compatibility | +| Conformance | isConformant | Part of unit tests | Consistent component API | ## Writing Unit Tests @@ -44,6 +44,7 @@ Review the snapshot diff to verify the change is correct before committing. ## Conformance Tests Every component package has a `testing/isConformant.ts` file that validates: + - Component renders without crashing - Ref forwarding works - className merging works From b5257929d1273b75db36ec18ad0d621db8a33716 Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Tue, 31 Mar 2026 13:09:01 +0200 Subject: [PATCH 08/12] chore: exclude generated .lock.yml files from Prettier formatting Co-Authored-By: Claude Opus 4.6 (1M context) --- .prettierignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.prettierignore b/.prettierignore index 6aecefc0421cd7..16c92bdb21bb6d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,6 +9,8 @@ api-report.md *.scss.ts _*.scss yarn.lock +# generated agentic workflow lock files +.github/workflows/*.lock.yml # generated folders node_modules From aa55bad7799aeb0323400e1ea56ddd4b2d038f0d Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Tue, 31 Mar 2026 17:12:44 +0200 Subject: [PATCH 09/12] docs: reference CODEOWNERS as source of truth in team-routing.md Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/team-routing.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/team-routing.md b/docs/team-routing.md index 600039e0592cb4..d0aaab955408da 100644 --- a/docs/team-routing.md +++ b/docs/team-routing.md @@ -2,6 +2,9 @@ ## Team Routing +The source of truth for code ownership is [`.github/CODEOWNERS`](../.github/CODEOWNERS). +The table below is a summary — when in doubt, check CODEOWNERS. + | Area | Team | Packages | | -------------- | ------------------------------- | ------------------------------------ | | v9 Components | @microsoft/cxe-prg | `packages/react-components/*` | From 913499a2526125c3b620136e38a43d918a20a962 Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Tue, 31 Mar 2026 18:42:17 +0200 Subject: [PATCH 10/12] chore: rename "Devil's Advocate" to "FluentUI Review" everywhere Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/agent-review.lock.yml | 18 +++++++++--------- .github/workflows/agent-review.md | 6 +++--- .github/workflows/agent-skills-improvement.md | 4 ++-- AGENTS.md | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/agent-review.lock.yml b/.github/workflows/agent-review.lock.yml index 167d917ea1011c..4595837d4f8db4 100644 --- a/.github/workflows/agent-review.lock.yml +++ b/.github/workflows/agent-review.lock.yml @@ -24,7 +24,7 @@ # # gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"63152329a1470b2af57f2e646db4ca8f368aa39674caab7a05c13b1175d5019f","compiler_version":"v0.50.6"} -name: "Devil's Advocate PR Review" +name: "FluentUI Review" "on": pull_request: types: @@ -37,7 +37,7 @@ concurrency: group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" cancel-in-progress: true -run-name: "Devil's Advocate PR Review" +run-name: "FluentUI Review" jobs: activation: @@ -298,7 +298,7 @@ jobs: version: "", agent_version: "0.0.418", cli_version: "v0.50.6", - workflow_name: "Devil's Advocate PR Review", + workflow_name: "FluentUI Review", experimental: false, supports_tools_allowlist: true, run_id: context.runId, @@ -830,7 +830,7 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - WORKFLOW_NAME: "Devil's Advocate PR Review" + WORKFLOW_NAME: "FluentUI Review" WORKFLOW_DESCRIPTION: "No description provided" HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} with: @@ -949,7 +949,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + GH_AW_WORKFLOW_NAME: "FluentUI Review" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -962,7 +962,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + GH_AW_WORKFLOW_NAME: "FluentUI Review" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -975,7 +975,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + GH_AW_WORKFLOW_NAME: "FluentUI Review" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "agent-review" @@ -994,7 +994,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + GH_AW_WORKFLOW_NAME: "FluentUI Review" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} @@ -1044,7 +1044,7 @@ jobs: env: GH_AW_ENGINE_ID: "copilot" GH_AW_WORKFLOW_ID: "agent-review" - GH_AW_WORKFLOW_NAME: "Devil's Advocate PR Review" + GH_AW_WORKFLOW_NAME: "FluentUI Review" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} diff --git a/.github/workflows/agent-review.md b/.github/workflows/agent-review.md index f3dddd2ff313f6..59b7ce0fb5b85d 100644 --- a/.github/workflows/agent-review.md +++ b/.github/workflows/agent-review.md @@ -19,9 +19,9 @@ safe-outputs: max: 1 --- -# Devil's Advocate PR Review +# FluentUI Review -You are the devil's advocate reviewer for the microsoft/fluentui monorepo. You review pull +You are the FluentUI reviewer for the microsoft/fluentui monorepo. You critically review pull requests created by AI agents (look for PRs authored by `copilot-swe-agent[bot]` or PRs with the `agent:automated-fix` label). @@ -75,7 +75,7 @@ For each file in the diff, evaluate: Post a single comment with this exact format: ```markdown -## Devil's Advocate Review +## FluentUI Review **Confidence Score: X/100** diff --git a/.github/workflows/agent-skills-improvement.md b/.github/workflows/agent-skills-improvement.md index 1c9f78933abe16..ed84947e34313e 100644 --- a/.github/workflows/agent-skills-improvement.md +++ b/.github/workflows/agent-skills-improvement.md @@ -33,7 +33,7 @@ Find recent agent activity: - Search for PRs created by `copilot-swe-agent[bot]` in the last 14 days - Search for PRs with the `agent:automated-fix` label - For each PR, read: the diff, review comments, whether it was merged/closed, and any - devil's advocate review scores + FluentUI review scores ## Step 2: Categorize Outcomes @@ -59,7 +59,7 @@ Why was it rejected? Look for: ## Step 3: Analyze Review Scores -If devil's advocate reviews exist: +If FluentUI review scores exist: - Which dimensions consistently score low? - Are scores well-calibrated? (high-scored PRs that got rejected = too lenient) diff --git a/AGENTS.md b/AGENTS.md index 4bf784c0079a9b..75a4d45871e650 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,7 +52,7 @@ Nx monorepo. Yarn v1. Node 22. TypeScript strict. ~200 packages. Millions of use | -------------------------- | ----------------- | --------------------------------------------- | | `agent-triage` | Issue opened | Classify, label, validate issues | | `agent-fix` | `agent:fix` label | Implement fixes, open PRs | -| `agent-review` | PR opened | Devil's advocate review with confidence score | +| `agent-review` | PR opened | FluentUI review with confidence score | | `agent-docs-grooming` | Weekly Monday | Documentation audit | | `agent-skills-improvement` | Weekly Friday | Improve agent instructions from PR patterns | From d8eaa2670e819b57d768a7de2ee6e6e817ecd66f Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Tue, 31 Mar 2026 19:19:13 +0200 Subject: [PATCH 11/12] style: fix Prettier formatting for AGENTS.md Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 75a4d45871e650..cf7103eef6aa65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,13 +48,13 @@ Nx monorepo. Yarn v1. Node 22. TypeScript strict. ~200 packages. Millions of use ## Agentic Workflows -| Workflow | Trigger | Purpose | -| -------------------------- | ----------------- | --------------------------------------------- | -| `agent-triage` | Issue opened | Classify, label, validate issues | -| `agent-fix` | `agent:fix` label | Implement fixes, open PRs | -| `agent-review` | PR opened | FluentUI review with confidence score | -| `agent-docs-grooming` | Weekly Monday | Documentation audit | -| `agent-skills-improvement` | Weekly Friday | Improve agent instructions from PR patterns | +| Workflow | Trigger | Purpose | +| -------------------------- | ----------------- | ------------------------------------------- | +| `agent-triage` | Issue opened | Classify, label, validate issues | +| `agent-fix` | `agent:fix` label | Implement fixes, open PRs | +| `agent-review` | PR opened | FluentUI review with confidence score | +| `agent-docs-grooming` | Weekly Monday | Documentation audit | +| `agent-skills-improvement` | Weekly Friday | Improve agent instructions from PR patterns | ## Package Layout From f5950cb82ca377a8f47316a1fdb35b576bd15dca Mon Sep 17 00:00:00 2001 From: Tudor Popa Date: Tue, 31 Mar 2026 19:40:57 +0200 Subject: [PATCH 12/12] chore: apply PJ Swesey's instruction architecture learnings to AGENTS.md Applies findings from "Increasing AI coding success to 99%" article: - Add "instructions are source of truth, not existing code" as first line - Add critical rules as negative constraints ("Never X") in always-loaded context - Add concrete v9 component code template to override training defaults - Add legacy anti-patterns section warning about v8 code agents will find - Add exploration guidance for the large monorepo - Reframe positive guidance as negative constraints for higher compliance Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 67 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cf7103eef6aa65..d1ea8085abe88f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,11 +10,63 @@ -# Fluent UI — Agent Map +# Fluent UI — Agent Instructions -Nx monorepo. Yarn v1. Node 22. TypeScript strict. ~200 packages. Millions of users. +**Instructions in this file are the source of truth, not existing code.** This repo contains +legacy patterns (especially in v8 packages) that predate current standards. Never copy patterns +from existing code without verifying they match these instructions. -## Architecture +## Critical Rules (never violate) + +1. **Never hardcode colors, spacing, or typography values.** Always use design tokens from `@fluentui/react-theme`. See [docs/architecture/design-tokens.md](docs/architecture/design-tokens.md). +2. **Never use `React.FC`.** Always use `ForwardRefComponent` with `React.forwardRef`. +3. **Never access `window`, `document`, or `navigator` without SSR guards.** Use `canUseDOM()` from `@fluentui/react-utilities`. +4. **Never add dependencies between component packages.** `react-button` must not depend on `react-menu`. Shared logic goes in `react-utilities` or `react-shared-contexts`. See [docs/architecture/layers.md](docs/architecture/layers.md). +5. **Never skip beachball change files** for published package changes. Run `npx beachball change`. + +## V9 Component Template (the correct pattern) + +```tsx +// ComponentName.tsx — always ForwardRefComponent, never React.FC +export const ComponentName: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useComponentName_unstable(props, ref); + useComponentNameStyles_unstable(state); + return renderComponentName_unstable(state); +}); + +// Styles — always use tokens, never hardcoded values +import { makeStyles } from '@griffel/react'; +import { tokens } from '@fluentui/react-theme'; + +export const useComponentNameStyles = makeStyles({ + root: { + color: tokens.colorNeutralForeground1, + padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`, + }, +}); + +// mergeClasses — always preserve user className LAST +state.root.className = mergeClasses( + classes.root, + state.root.className, // always last +); +``` + +## Legacy Anti-Patterns (never copy these) + +- **DO NOT copy patterns from `packages/react/` (v8).** That's maintenance-only legacy code using runtime styling, class components, and different APIs. +- **DO NOT use `@fluentui/react` imports for new v9 work.** Use `@fluentui/react-components`. +- **DO NOT use `mergeStyles` or `mergeStyleSets`.** Use Griffel `makeStyles` with design tokens. +- **DO NOT use `IStyle` or `IStyleFunctionOrObject`.** Use Griffel's `GriffelStyle` type. +- **DO NOT use `initializeIcons()`.** V9 uses `@fluentui/react-icons` with tree-shaking. + +## Exploration Guidance + +- `packages/react-components/` has 74+ packages — search by specific component name, never read the full directory. +- Use `npx nx show project ` to understand a project's structure. +- Map package names to paths: `@fluentui/react-` → `packages/react-components/react-/library/src/`. + +## Architecture (deep dives) | Topic | Location | | --------------------------------------------- | ---------------------------------------------------------------------------------- | @@ -37,15 +89,6 @@ Nx monorepo. Yarn v1. Node 22. TypeScript strict. ~200 packages. Millions of use | Per-package quality grades | [docs/quality-grades.md](docs/quality-grades.md) | | Technical debt tracker | [docs/tech-debt-tracker.md](docs/tech-debt-tracker.md) | -## Key Rules - -1. Use design tokens — never hardcode colors, spacing, or typography -2. Follow v9 component patterns exactly — don't invent new approaches -3. Respect package layer boundaries (see layers.md) -4. SSR-safe — no unguarded `window`/`document`/`navigator` -5. Accessibility first — ARIA attributes, keyboard nav, WCAG 2.1 -6. Beachball change files required for published package changes - ## Agentic Workflows | Workflow | Trigger | Purpose |