diff --git a/content/admin/data-residency/getting-started-with-data-residency-for-github-enterprise-cloud.md b/content/admin/data-residency/getting-started-with-data-residency-for-github-enterprise-cloud.md index 791be6b1386d..f2aa7e0a8dc9 100644 --- a/content/admin/data-residency/getting-started-with-data-residency-for-github-enterprise-cloud.md +++ b/content/admin/data-residency/getting-started-with-data-residency-for-github-enterprise-cloud.md @@ -57,7 +57,7 @@ To get started with {% data variables.enterprise.data_residency_short %}, you wi 1. Click **Create enterprise**. -Your enterprise can take a few hours to be provisioned. When it's ready, you'll receive a welcome email and an invitation to reset your password, delivered to the "Admin work email" address. +Your enterprise can take a few hours to be provisioned. You'll receive a welcome email and an invitation to reset your password, delivered to the "Admin work email" address. If opening the password reset link displays a certificate error (such as `net::ERR_CERT_COMMON_NAME_INVALID`), wait for provisioning to finish, then try the link again. ## 2. Add users to your enterprise diff --git a/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise.md b/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise.md index 3be236fa4050..89a867179ab7 100644 --- a/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise.md +++ b/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise.md @@ -213,7 +213,9 @@ From {% data variables.product.prodname_dotcom %}: ### Setting up streaming to Azure Event Hubs -> [!NOTE] Event Hubs instances in Azure Government are not supported. +> [!NOTE] +> - Event Hubs instances in Azure Government are not supported. +> - With **{% data variables.enterprise.data_residency %},** audit log streaming to Azure Event Hubs is not supported with IP Firewall rules enabled. Before setting up a stream in {% data variables.product.prodname_dotcom %}, you need: diff --git a/content/admin/upgrading-your-instance/performing-an-upgrade/upgrading-with-an-upgrade-package.md b/content/admin/upgrading-your-instance/performing-an-upgrade/upgrading-with-an-upgrade-package.md index 0872f1c48401..700e98dcab2e 100644 --- a/content/admin/upgrading-your-instance/performing-an-upgrade/upgrading-with-an-upgrade-package.md +++ b/content/admin/upgrading-your-instance/performing-an-upgrade/upgrading-with-an-upgrade-package.md @@ -111,7 +111,7 @@ Phased upgrade execution allows {% data variables.product.prodname_ghe_server %} 1. Run the upgrade phase ```shell - ghe-upgrade --phase pre-upgrade GITHUB-UPGRADE.pkg + ghe-upgrade --phase upgrade GITHUB-UPGRADE.pkg ``` 1. Optionally, after the upgrade, validate the upgrade by configuring an IP exception list to allow access to a specified list of IP addresses. See [AUTOTITLE](/admin/administering-your-instance/configuring-maintenance-mode/enabling-and-scheduling-maintenance-mode#validating-changes-in-maintenance-mode-using-the-ip-exception-list). diff --git a/content/code-security/concepts/code-quality/code-quality.md b/content/code-security/concepts/code-quality/code-quality.md index 69a0c51cfab7..cef2818f5a2d 100644 --- a/content/code-security/concepts/code-quality/code-quality.md +++ b/content/code-security/concepts/code-quality/code-quality.md @@ -20,10 +20,11 @@ category: {% data variables.product.prodname_code_quality %} analyzes your code for quality and coverage issues and delivers {% data variables.product.prodname_copilot_short %}-powered fixes you can apply in one click. It runs in two places: -* **On pull requests**, findings appear as inline comments before code is merged. If you upload a Cobertura XML coverage report, coverage metrics show whether a change maintains or reduces coverage. You can enforce quality and coverage thresholds with rulesets to block pull requests that don't meet your criteria, so new quality debt doesn't accumulate. -* **On the default branch**, scans identify existing quality debt across your codebase, with autofixes you can apply directly or assign to {% data variables.copilot.copilot_cloud_agent %} to resolve on your behalf. +* **On pull requests**, {% data variables.product.prodname_code_quality_short %} uses deterministic {% data variables.product.prodname_codeql %} rules to detect known anti-patterns and posts findings as inline comments before code is merged. If you upload a Cobertura XML coverage report, coverage metrics show whether a change maintains or reduces coverage. You can enforce quality and coverage thresholds with rulesets to block pull requests that don't meet your criteria, so new quality debt doesn't accumulate. +* **On the default branch**, rules-based scans identify existing quality debt across your codebase, with autofixes you can apply directly or assign to {% data variables.copilot.copilot_cloud_agent %} to resolve on your behalf. AI-powered analysis also runs on recently changed files, flagging issues that fall outside existing rule sets, including languages not yet covered by {% data variables.product.prodname_codeql %} queries. -Detection combines deterministic {% data variables.product.prodname_codeql %} rules for known anti-patterns with AI-powered analysis for issues that fall outside existing rule sets, including languages not yet covered by {% data variables.product.prodname_codeql %} queries. +> [!NOTE] +> On pull requests, {% data variables.product.prodname_code_quality_short %} posts rules-based {% data variables.product.prodname_codeql %} findings only. If you also want AI-powered reviews of your pull requests, you can enable {% data variables.copilot.copilot_code-review %} separately. See [AUTOTITLE](/copilot/concepts/agents/code-review). ## Use cases @@ -32,8 +33,8 @@ Here's what {% data variables.product.prodname_code_quality %} looks like in pra For developers and teams: * **A developer opens a pull request** that introduces a reliability or maintainability issue. {% data variables.product.prodname_code_quality_short %} posts a comment explaining the issue and offers a one-click fix before the code is merged. The developer also sees a report of coverage metrics, and can tell at a glance whether the pull request improves or reduces coverage compared to the default branch. -* **A team adopts AI coding assistants** and needs assurance that generated code meets the same bar as hand-written code. AI-powered analysis catches issues that rule-based queries weren't written for, while {% data variables.product.prodname_codeql %} rules cover well-defined anti-patterns. * **A team inherits a large codebase** with years of accumulated quality debt. {% data variables.product.prodname_code_quality_short %} scans the default branch, surfaces findings with autofixes on a dashboard, and the team assigns remediation work to {% data variables.copilot.copilot_cloud_agent %} to open fix pull requests automatically. +* **A team adopts AI coding assistants** and needs assurance that generated code meets the same bar as hand-written code. AI-powered analysis catches issues in recently changed files that rule-based queries weren't written for, while {% data variables.product.prodname_codeql %} rules cover well-defined anti-patterns. For administrators and leads: @@ -45,7 +46,7 @@ For administrators and leads: Usage costs are determined by: * A per-seat license fee based on active committers. -* AI-powered detections and {% data variables.product.prodname_copilot_short %}-powered autofixes, which consume {% data variables.product.prodname_ai_credits %} (no {% data variables.product.prodname_copilot_short %} license required). +* {% data variables.product.prodname_copilot_short %}-powered autofixes for findings in pull requests and on the default branch, alongside AI-powered detections on recently merged code, which consume {% data variables.product.prodname_ai_credits %} (no {% data variables.product.prodname_copilot_short %} license required). * {% data variables.product.prodname_actions %} minutes for deterministic {% data variables.product.prodname_codeql %} scans, if you don't use self-hosted runners. Optional features, such as delegating code quality remediation work to {% data variables.product.prodname_copilot_short %}, require a {% data variables.product.prodname_copilot_short %} license. @@ -58,10 +59,8 @@ For more information, see [AUTOTITLE](/billing/concepts/product-billing/github-c {% data reusables.code-quality.codeql-supported-languages %} -It also performs AI-powered analysis on pull requests and on your repository's recently changed code, including languages beyond those supported by rule-based queries. +{% data variables.product.prodname_code_quality_short %} also performs AI-powered analysis on your repository's recently changed code, including languages beyond those supported by rule-based queries. ## Next steps -* **For your enterprise:** Ensure repositories in your enterprise can enable {% data variables.product.prodname_code_quality_short %}. See [AUTOTITLE](/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/allow-github-code-quality-in-enterprise?utm_campaign=code-quality-ga-july-2026&utm_medium=docs&utm_source=docs-cq-intro-enable-cq-enterprise). -* **For your repository or organization:** Turn on {% data variables.product.prodname_code_quality_short %} to start generating results. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/enable-code-quality?utm_campaign=code-quality-ga-july-2026&utm_medium=docs&utm_source=docs-cq-intro-enable-cq-repo). -* **On your pull request:** Learn how to fix code quality findings on your pull request. See [AUTOTITLE](/code-security/tutorials/improve-code-quality/catch-issues-before-merge?utm_campaign=code-quality-ga-july-2026&utm_medium=docs&utm_source=docs-cq-intro-fix-on-pr). +* Learn how to fix code quality findings on your pull request. See [AUTOTITLE](/code-security/tutorials/improve-code-quality/catch-issues-before-merge?utm_campaign=code-quality-ga-july-2026&utm_medium=docs&utm_source=docs-cq-intro-fix-on-pr). diff --git a/content/code-security/how-tos/maintain-quality-code/enable-code-quality.md b/content/code-security/how-tos/maintain-quality-code/enable-code-quality.md index f4522451a298..fdd766bb1a29 100644 --- a/content/code-security/how-tos/maintain-quality-code/enable-code-quality.md +++ b/content/code-security/how-tos/maintain-quality-code/enable-code-quality.md @@ -56,8 +56,3 @@ For the available access options, and how filtering and enforcement work, see [A Your changes are saved automatically and begin to propagate immediately. In large organizations, it can take several minutes for the changes to apply across all repositories. If you're rolling out the feature across many teams, we recommend you pilot on a small group and tune your quality thresholds before you enable everywhere. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/roll-out-at-scale). - -## Next steps - -* **Add code coverage:** Upload reported code coverage from your test suite to see coverage results directly on pull requests. {% data reusables.code-quality.workflow-generation %} -* **For your organization:** Understand the code health of your repositories at a glance. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/explore-code-quality). diff --git a/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md b/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md index 50601523cb99..00067b3d9685 100644 --- a/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md +++ b/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md @@ -1,7 +1,7 @@ --- title: Exploring GitHub Code Quality results in your organization shortTitle: Explore code quality -intro: Understand your organization's code health at a glance with the organization-level dashboard for {% data variables.product.prodname_code_quality_short %}. +intro: Use the organization-level {% data variables.product.prodname_code_quality_short %} dashboard to spot repositories that need attention and track your organization's code health at a glance. product: '{% data reusables.gated-features.code-quality-availability %}' permissions: Organization members audience: @@ -52,7 +52,3 @@ You can sort the repository table in ascending or descending order for any colum 1. Scroll down to the repository table. By default, the table is sorted from most to least recent repository scan, helping you prioritize current quality issues. 1. Optionally, to prioritize repositories with the highest number of {% data variables.product.prodname_codeql %} findings, click **Standard Findings** twice. 1. To view the repository-level dashboard for a specific repository, click the repository's name. - -## Next steps - -To understand the code health information available on the repository-level dashboard, see [AUTOTITLE](/code-security/how-tos/maintain-quality-code/interpret-results). diff --git a/content/code-security/how-tos/maintain-quality-code/fix-findings-on-a-pr.md b/content/code-security/how-tos/maintain-quality-code/fix-findings-on-a-pr.md index 37977ea588ce..08d1b9f5591c 100644 --- a/content/code-security/how-tos/maintain-quality-code/fix-findings-on-a-pr.md +++ b/content/code-security/how-tos/maintain-quality-code/fix-findings-on-a-pr.md @@ -19,21 +19,15 @@ category: ## How {% data variables.product.prodname_code_quality_short %} works on pull requests -When you open a pull request, {% data variables.product.prodname_code_quality_short %} runs **two types of analysis** and posts findings as comments on the pull request. - -1. **`{% data variables.code-quality.pr_commenter %}` findings**: {% data variables.product.prodname_code_quality_short %} uses {% data variables.product.prodname_codeql %} to perform a rule-based scan of your changes. These findings are posted as comments by `{% data variables.code-quality.pr_commenter %}` and include a suggested autofix. Findings are labeled by severity (**Error**, **Warning**, **Note**), and administrators can set quality gates to block merges based on the severity of these findings. - -1. **{% data variables.product.prodname_copilot_short %} findings**: If your organization has {% data variables.product.prodname_copilot_short %} licenses and AI features are enabled for your enterprise, {% data variables.product.prodname_code_quality_short %} uses **{% data variables.copilot.copilot_code-review_short %}** to identify quality issues that rules-based analysis may not detect. These findings are posted as comments by **{% data variables.product.prodname_copilot_short %}**, and include a suggested autofix. See [AUTOTITLE](/copilot/concepts/agents/code-review). +When you open a pull request, {% data variables.product.prodname_code_quality_short %} uses {% data variables.product.prodname_codeql %} to perform a rule-based scan of your changes and posts findings as comments by `{% data variables.code-quality.pr_commenter %}`. Each finding includes a suggested autofix. Findings are labeled by severity (**Error**, **Warning**, **Note**), and administrators can set quality gates to block merges based on the severity of these findings. ## Resolving a finding 1. On {% data variables.product.github %}, navigate to your open pull request. -1. On the **Files Changed** tab, scroll to a comment left by **`{% data variables.code-quality.pr_commenter %}`** or **{% data variables.product.prodname_copilot_short %}**. +1. On the **Files Changed** tab, scroll to a comment left by **`{% data variables.code-quality.pr_commenter %}`**. {% data reusables.code-quality.review-comment-and-autofix %} {% data reusables.code-quality.apply-suggestion %} -1. Alternatively, if the finding isn't relevant or actionable, you can dismiss the finding. For example, you might dismiss a finding that is in legacy code no longer maintained, is a known exception to your team's coding standards, or is a false positive that doesn't pose a real quality risk. - * For comments left by `{% data variables.code-quality.pr_commenter %}`, click **Dismiss finding**. - * For comments left by {% data variables.product.prodname_copilot_short %}, click **Resolve**. +1. Alternatively, if the finding isn't relevant or actionable, you can dismiss it by clicking **Dismiss finding**. For example, you might dismiss a finding that is in legacy code no longer maintained, is a known exception to your team's coding standards, or is a false positive that doesn't pose a real quality risk. ## Delegating remediation work to {% data variables.product.prodname_copilot_short %} diff --git a/content/code-security/how-tos/maintain-quality-code/interpret-results.md b/content/code-security/how-tos/maintain-quality-code/interpret-results.md index c0f2fe871e6b..7021317af9b1 100644 --- a/content/code-security/how-tos/maintain-quality-code/interpret-results.md +++ b/content/code-security/how-tos/maintain-quality-code/interpret-results.md @@ -2,7 +2,7 @@ title: Interpreting the code quality results for your repository shortTitle: Interpret results allowTitleToDifferFromFilename: true -intro: Understand the maintainability and reliability of your codebase so you can prioritize where your teams focus remediation effort. +intro: Use {% data variables.product.prodname_code_quality_short %} results to assess the maintainability and reliability of your codebase, so your teams can focus remediation where it matters most. versions: feature: code-quality product: '{% data reusables.gated-features.code-quality-availability %}' @@ -53,8 +53,3 @@ Code quality results should always be interpreted in the context of your reposit * Large repositories with a lot of code in a fully supported language often have many results even if the majority of the code has good maintainability and reliability standards. To learn more about the metrics and how the scores are calculated, see [AUTOTITLE](/code-security/reference/code-quality/metrics-and-ratings). - -## Next steps - -* Remediate quality findings in your default branch and improve the maintainability and reliability score for your repository. See [AUTOTITLE](/code-security/tutorials/improve-code-quality/raise-your-quality-rating). -* Stop your repository from accumulating more code quality problems by setting a quality threshold for pull requests using rulesets. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/set-pr-thresholds). diff --git a/content/code-security/how-tos/maintain-quality-code/roll-out-at-scale.md b/content/code-security/how-tos/maintain-quality-code/roll-out-at-scale.md index a97ce4d08b7b..73926e351b52 100644 --- a/content/code-security/how-tos/maintain-quality-code/roll-out-at-scale.md +++ b/content/code-security/how-tos/maintain-quality-code/roll-out-at-scale.md @@ -75,7 +75,3 @@ If you need automation around your rollout, you can fetch {% data variables.prod Enabling {% data variables.product.prodname_code_quality_short %} does not automatically turn on code coverage. Coverage is opt-in per repository, and it starts reporting only after a workflow that uploads coverage data is added to the repository. This means teams can adopt {% data variables.product.prodname_code_quality_short %} first and add coverage later. To set up coverage for a repository, see [AUTOTITLE](/code-security/how-tos/maintain-quality-code/set-up-code-coverage). - -## Next steps - -Now that you've rolled out {% data variables.product.prodname_code_quality_short %} across your organization, you can assess the health of your repositories at a glance and decide where to focus. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/explore-code-quality). diff --git a/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md b/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md index 36b5c6351cd2..36ae5e2ca9b1 100644 --- a/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md +++ b/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md @@ -1,7 +1,7 @@ --- title: Setting code quality thresholds for pull requests shortTitle: Set quality thresholds -intro: Enforce your code quality standards automatically by blocking pull requests that fall below the thresholds you set, at the repository or organization level. +intro: Keep low-quality changes out of your codebase by using {% data variables.product.prodname_code_quality_short %} thresholds to block pull requests that don't meet your standards. allowTitleToDifferFromFilename: true versions: feature: code-quality @@ -51,7 +51,7 @@ The following steps create or update a ruleset at the repository level. To enfor 1. If you don't already have a ruleset to protect your default branch, expand **New ruleset** and click **New branch ruleset**. Alternatively, open your existing ruleset for the default branch and move to step 5. 1. If you are creating a new ruleset: * Define a name for the ruleset. - * Set the "Enforcement status" to "Active." + * Set the "Enforcement status" to "Evaluate" while you calibrate the threshold during a pilot. If you want to enforce the threshold immediately, select "Active." * Under "Target branches" add a target of "Include default branch." 1. Under "Branch rules", enable "Require code quality results". 1. Set "Severity" to define the lowest severity of code quality results that must be resolved before a pull request can be merged into the default branch. For example: @@ -61,10 +61,8 @@ The following steps create or update a ruleset at the repository level. To enfor * Set "All" to block pull requests with **any** unresolved code quality results being merged. 1. When you have finished defining or editing the ruleset, click **Create** or **Save changes**. +If you selected "Evaluate", review the ruleset insights to see which pull requests would have been blocked. When the results match the quality bar you want, change the enforcement status to "Active." + ## Setting a code coverage threshold You can also block pull requests that fall below a code coverage threshold. This uses a separate **Restrict code coverage** rule, not the **Require code quality results** rule used above, and your repository must upload code coverage data first. For the full procedure, see [AUTOTITLE](/code-security/how-tos/maintain-quality-code/restrict-code-coverage). - -## Next steps - -Learn how {% data variables.product.prodname_code_quality %} works on pull requests to prevent code quality issues from reaching your default branch. See [AUTOTITLE](/code-security/tutorials/improve-code-quality/catch-issues-before-merge). diff --git a/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md b/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md index 1f593e4c9e26..373634488e78 100644 --- a/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md +++ b/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md @@ -1,7 +1,7 @@ --- title: Setting up code coverage for your repository shortTitle: Set up code coverage -intro: 'Give your teams visibility into code coverage directly on pull requests, without paying for or maintaining a separate third-party coverage service.' +intro: 'Use built-in code coverage from {% data variables.product.prodname_code_quality_short %} to find untested code on pull requests, without paying for or maintaining a separate third-party service.' versions: feature: code-quality product: '{% data reusables.gated-features.code-quality-availability %}' @@ -163,8 +163,3 @@ jobs: 1. After the workflow completes, look for a comment from `{% data variables.code-quality.pr_commenter %}` on the pull request. The comment includes: * The aggregate coverage percentage for the pull request branch compared to the default branch. * A per-file breakdown showing which files gained or lost coverage. - -## Next steps - -* **Interpret results:** Understand coverage metrics and per-file breakdowns on your pull requests. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/interpret-results). -* **Enforce coverage thresholds:** Block pull requests that don't meet a minimum coverage percentage or that cause coverage to drop. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/restrict-code-coverage). diff --git a/content/code-security/how-tos/maintain-quality-code/view-and-manage-cost.md b/content/code-security/how-tos/maintain-quality-code/view-and-manage-cost.md index 3c0b89f32eca..ebefc9d1b463 100644 --- a/content/code-security/how-tos/maintain-quality-code/view-and-manage-cost.md +++ b/content/code-security/how-tos/maintain-quality-code/view-and-manage-cost.md @@ -74,8 +74,3 @@ Be aware of two limits so there are no surprises: * **You can't pre-estimate spend** before you enable {% data variables.product.prodname_code_quality_short %}. Usage depends on your committers, scan frequency, and findings, so plan to watch actuals after you turn it on rather than forecast them precisely. * **You can't turn off the in-pull-request AI features.** {% data variables.product.prodname_code_quality_short %} generates a fix for every detected finding, so {% data variables.product.prodname_ai_credits_short %} usage is inherent to running it. To stop that usage entirely, disable {% data variables.product.prodname_code_quality_short %} on the repository. - -## Next steps - -* **Roll out across your organization.** See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/roll-out-at-scale). -* **Assess health across your organization.** See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/explore-code-quality). diff --git a/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/allow-github-code-quality-in-enterprise.md b/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/allow-github-code-quality-in-enterprise.md index a35701e125e7..15df3a33f611 100644 --- a/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/allow-github-code-quality-in-enterprise.md +++ b/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/allow-github-code-quality-in-enterprise.md @@ -1,7 +1,7 @@ --- title: Allowing use of {% data variables.product.prodname_code_quality %} in your enterprise shortTitle: Allow {% data variables.product.prodname_code_quality_short %} -intro: Control {% data variables.product.prodname_code_quality_short %} enablement for your repositories by defining policies. +intro: Set enterprise policies for {% data variables.product.prodname_code_quality_short %} to give organizations access while keeping repository enablement under your control. versions: feature: code-quality product: '{% data reusables.gated-features.code-quality-availability %}' @@ -17,8 +17,7 @@ category: --- > [!NOTE] -> * {% data variables.product.prodname_code_quality %} is currently in {% data variables.release-phases.public_preview %} and subject to change. During {% data variables.release-phases.public_preview %}, {% data variables.product.prodname_code_quality_short %} will not be billed, although {% data variables.product.prodname_code_quality_short %} scans will consume {% data variables.product.prodname_actions %} minutes. -> * Previously, {% data variables.product.prodname_AS %} policies also controlled access to {% data variables.product.prodname_code_quality_short %}. Those existing policy settings are automatically applied to the standalone {% data variables.product.prodname_code_quality_short %} policies. +> Previously, {% data variables.product.prodname_AS %} policies also controlled access to {% data variables.product.prodname_code_quality_short %}. Those existing policy settings are automatically applied to the standalone {% data variables.product.prodname_code_quality_short %} policies. 1. Navigate to your enterprise. For example, from [https://github.com/settings/enterprises](https://github.com/settings/enterprises?ref_product=ghec&ref_type=engagement&ref_style=text). {% data reusables.enterprise-accounts.policies-tab %} diff --git a/content/code-security/index.md b/content/code-security/index.md index c360e6c143f2..35f07c8632ae 100644 --- a/content/code-security/index.md +++ b/content/code-security/index.md @@ -31,7 +31,7 @@ carousels: - /code-security/concepts/secret-security/secret-scanning - /code-security/concepts/code-scanning/code-scanning - /code-security/tutorials/secure-your-dependencies/dependabot-quickstart - - /code-security/concepts/code-quality/code-quality + - /code-security/tutorials/code-quality-adoption-path - /code-security/tutorials/secure-your-organization/prevent-data-leaks - /code-security/concepts/supply-chain-security/best-practices-for-maintaining-dependencies versions: diff --git a/content/code-security/tutorials/code-quality-adoption-path.md b/content/code-security/tutorials/code-quality-adoption-path.md new file mode 100644 index 000000000000..5861e89183ce --- /dev/null +++ b/content/code-security/tutorials/code-quality-adoption-path.md @@ -0,0 +1,58 @@ +--- +title: Improve code quality at scale with GitHub +shortTitle: Code quality +allowTitleToDifferFromFilename: true +intro: 'Inconsistent code quality slows every team down and adds risk you can''t easily see. {% data variables.product.prodname_code_quality %} catches issues before they merge and reports on code health across your organization. Follow this adoption path to evaluate the feature, run a pilot, prove its value, and roll it out at scale.' +layout: journey-landing +versions: + feature: code-quality +contentType: tutorials +audience: + - driver +category: + - Secure at scale + - Improve code quality +journeyArticlesHeading: 'All code quality articles' +journeyTracks: + - id: 'evaluate' + title: 'Phase 1: Evaluate and build your business case' + description: 'Evaluate {% data variables.product.prodname_code_quality_short %} alongside other code quality tools to determine whether it meets your organization''s needs and build a business case. Review its use cases, supported languages, quality findings, and code coverage reporting. Learn how usage is billed and which cost controls are available before you plan your budget.' + timeCommitment: '1-2 hours' + guides: + - href: '/code-security/concepts/code-quality/code-quality' + - href: '/billing/concepts/product-billing/github-code-quality' + - href: '/code-security/how-tos/maintain-quality-code/view-and-manage-cost' + - id: 'plan-pilot' + title: 'Phase 2: Plan your pilot' + description: 'Select a small, active group of repositories for your pilot and decide how you''ll measure success. Identify the teams and repository owners who will provide feedback, prepare them for the findings they''ll see on pull requests, and plan how you''ll communicate remediation options, gather feedback, and monitor costs during the pilot.' + timeCommitment: '2-4 hours' + guides: + - href: '/code-security/concepts/code-quality/enablement-at-scale' + - href: '/code-security/tutorials/improve-code-quality/catch-issues-before-merge' + - id: 'launch-pilot' + title: 'Phase 3: Launch your pilot' + description: 'Allow {% data variables.product.prodname_code_quality_short %} for your enterprise, then enable it for your pilot repositories. Configure thresholds for {% data variables.product.prodname_code_quality_short %} findings and code coverage. Start the ruleset in evaluate mode to see which pull requests would be blocked, adjust the thresholds based on the results, then enable enforcement.' + timeCommitment: '1-2 hours' + guides: + - href: '/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/allow-github-code-quality-in-enterprise' + - href: '/code-security/how-tos/maintain-quality-code/enable-code-quality' + - href: '/code-security/how-tos/maintain-quality-code/set-pr-thresholds' + - href: '/code-security/how-tos/maintain-quality-code/restrict-code-coverage' + - id: 'assess' + title: 'Phase 4: Assess the pilot and decide' + description: 'Compare the pilot results with the success measures you defined. Review code health at the repository and organization level, monitor costs, and gather feedback from developers. Use what you learn to decide whether to expand, adjust, or stop the rollout.' + timeCommitment: '1-2 weeks' + guides: + - href: '/code-security/how-tos/maintain-quality-code/explore-code-quality' + - href: '/code-security/how-tos/maintain-quality-code/interpret-results' + - href: '/code-security/how-tos/maintain-quality-code/view-and-manage-cost' + - href: '/code-security/how-tos/maintain-quality-code/disable-code-quality' + - id: 'scale' + title: 'Phase 5: Scale and optimize' + description: 'Expand {% data variables.product.prodname_code_quality_short %} to more repositories based on what you learned from the pilot. As you expand, adjust organization-level enablement and enforcement, and continue to monitor code health, license usage, {% data variables.product.prodname_actions %} minutes, and {% data variables.product.prodname_ai_credits %} usage.' + timeCommitment: 'Ongoing' + guides: + - href: '/code-security/how-tos/maintain-quality-code/roll-out-at-scale' + - href: '/code-security/how-tos/maintain-quality-code/explore-code-quality' + - href: '/code-security/how-tos/maintain-quality-code/view-and-manage-cost' +--- diff --git a/content/code-security/tutorials/improve-code-quality/catch-issues-before-merge.md b/content/code-security/tutorials/improve-code-quality/catch-issues-before-merge.md index 711a727ce7eb..c2017ca9ea15 100644 --- a/content/code-security/tutorials/improve-code-quality/catch-issues-before-merge.md +++ b/content/code-security/tutorials/improve-code-quality/catch-issues-before-merge.md @@ -21,7 +21,7 @@ category: In this tutorial, you'll follow a single pull request through {% data variables.product.prodname_code_quality_short %}'s analysis, from first comment to merge. You'll learn: -* How to read the {% data variables.product.prodname_code_quality_short %} comments on a pull request and tell the two types of finding apart. +* How to read the {% data variables.product.prodname_code_quality_short %} comments on your pull request. * How to use a finding's severity label to decide what to fix, what to dismiss, and in what order. * How the choices you make on a pull request shape your repository's scores, backlog, and merge gates. @@ -45,17 +45,13 @@ Resolving findings at the pull request stage means your team spends less time tr ## Step 1: Find the {% data variables.product.prodname_code_quality_short %} comments on your pull request -When you open a pull request, {% data variables.product.prodname_code_quality_short %} runs **two types of analysis** and posts findings as comments. Open the **Files changed** tab of your pull request and look at who left each comment—the author tells you which type of finding it is. +When you open a pull request, {% data variables.product.prodname_code_quality_short %} uses {% data variables.product.prodname_codeql %} to scan your changes against a set of rules and posts findings as comments by `{% data variables.code-quality.pr_commenter %}`. Each comment includes a suggested autofix. Open the **Files changed** tab of your pull request to review the findings. -1. **Rules-based findings** are posted by the **`{% data variables.code-quality.pr_commenter %}`**. {% data variables.product.prodname_code_quality_short %} uses {% data variables.product.prodname_codeql %} to scan your changes against a set of rules, and each comment includes a suggested autofix. - -1. **AI-powered findings** are posted by **{% data variables.product.prodname_copilot_short %}**. If your organization has {% data variables.product.prodname_copilot_short %} licenses and AI features are enabled for your enterprise, {% data variables.copilot.copilot_code-review_short %} looks for quality issues that rules-based analysis may miss. These comments also include a suggested autofix. - -In our example, we'll look at three comments that have come from `{% data variables.code-quality.pr_commenter %}`, so they're rules-based findings. On your own pull request you may see both types—note which is which before you go further, because severity labels (Step 2) apply only to the rules-based comments. +In our example, we'll look at three comments from `{% data variables.code-quality.pr_commenter %}`. Note the severity labels on each. Step 2 explains what they mean. ## Step 2: Read the severity label to decide what matters -Every rules-based finding from `{% data variables.code-quality.pr_commenter %}` carries a severity label—**Error**, **Warning**, or **Note**. Find the label on one of the comments and check it against this table. +Every finding from `{% data variables.code-quality.pr_commenter %}` carries a severity label—**Error**, **Warning**, or **Note**. Find the label on one of the comments and check it against this table. {% data reusables.code-quality.severity-levels-table %} @@ -78,7 +74,7 @@ For every finding, decide whether it applies to your code and, if it does, how t | Assessment | Recommended action | Notes | | --- | --- | --- | -| The finding is legitimate and the suggested fix looks correct | **Apply the autofix suggestion** | Clicking **Commit suggestion** doesn't consume {% data variables.product.prodname_ai_credits_short %}, and rules-based autofixes don't require a {% data variables.product.prodname_copilot_short %} license. | +| The finding is legitimate and the suggested fix looks correct | **Apply the autofix suggestion** | Clicking **Commit suggestion** doesn't consume {% data variables.product.prodname_ai_credits_short %}, and autofixes don't require a {% data variables.product.prodname_copilot_short %} license. | | The finding is real but you want to fix several at once, or the suggested fix needs adapting | **Delegate to {% data variables.product.prodname_copilot_short %}**—mention `@copilot` in a comment to hand the work to the cloud agent. {% data variables.product.prodname_copilot_short %} reacts with 👀, starts a new agent session, and pushes the necessary fixes to the pull request's branch | Requires a {% data variables.product.prodname_copilot_short %} license and consumes {% data variables.product.prodname_ai_credits_short %}. | | The finding doesn't apply, for example, it's test code, an intentional pattern, or a false positive | Click **Dismiss finding** and provide a reason | You'll be able to merge your pull request, but the finding will appear in the repository backlog, and in future pull requests. | @@ -92,26 +88,12 @@ In our example: ## Step 4: Confirm your pull request is unblocked (optional) -If you *do* have blocking findings, once you've fixed or dismissed the relevant findings, return to the **Checks** section at the bottom of the pull request. +If you *do* have blocking findings, once you've fixed or dismissed the relevant findings, return to the **Checks** section at the bottom of the pull request. In our example, with the **Error** and **Warning** findings resolved, the merge block banner disappears. Your pull request is now clear to merge. If the banner is still there, it means a finding at or above the blocking severity is still open. -## Step 5: Resolve the AI-powered findings from {% data variables.product.prodname_copilot_short %} - -If your organization has {% data variables.product.prodname_copilot_short %} licenses and AI features are enabled for your enterprise, you'll also see comments posted by {% data variables.product.prodname_copilot_short %}. These are the **AI-powered findings** introduced in Step 1, and they come from {% data variables.copilot.copilot_code-review_short %} rather than from `{% data variables.code-quality.pr_commenter %}`. - -Where the rules-based findings match your changes against a fixed set of {% data variables.product.prodname_codeql %} rules, {% data variables.copilot.copilot_code-review_short %} reasons about the intent of your code. It catches quality issues that don't map to a specific rule, so it's a useful complement to the rules-based comments rather than a replacement for them. - -These findings don't carry a severity label of "Error", "Warning", or "Note". Since the merge gate you saw in Step 2 counts only the severity of *rules-based findings*, AI-powered findings never block your pull request on their own. That doesn't make them optional, resolving them in context is still the optimal place to keep quality issues out of your default branch. - -You resolve an AI-powered finding with the same three choices you used in Step 3: - -* **Apply the autofix suggestion.** Each comment includes a suggested fix. If it's correct as-is, click **Commit suggestion**. Applying the autofix doesn't consume {% data variables.product.prodname_ai_credits %}. -* **Delegate to {% data variables.product.prodname_copilot_short %}**—mention `@copilot` in a comment to hand the work to the cloud agent. {% data variables.product.prodname_copilot_short %} reacts with 👀, starts a new agent session, and pushes the necessary fixes to the pull request's branch. This option requires a {% data variables.product.prodname_copilot_short %} license, and does consume {% data variables.product.prodname_ai_credits %}. -* **Resolve the comment.** If it doesn't apply to your code, click **Resolve**. - ## How this connects to the rest of your code health The pull request you just cleared is part of a bigger picture: @@ -125,7 +107,6 @@ The healthiest teams combine all three: deliberate triage and remediation at the ## Troubleshooting * **I don't see any {% data variables.product.prodname_code_quality_short %} comments.** The scan may still be running, your changes may not touch a supported language, or you don't have any findings. Confirm {% data variables.product.prodname_code_quality_short %} is enabled and give the check (called "{% data variables.code-quality.check_status_name %}") time to finish. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/enable-code-quality). -* **I only see comments from `{% data variables.code-quality.pr_commenter %}`, never from {% data variables.product.prodname_copilot_short %}.** AI-powered findings require {% data variables.product.prodname_copilot_short %} licenses and AI features enabled for your enterprise. Without them, you'll see only rules-based findings. * **I don't see autofixes for my code quality findings.** Autofix generation consumes {% data variables.product.prodname_ai_credits %}. Your organization may have depleted its monthly budget of {% data variables.product.prodname_ai_credits_short %}. * **The merge block banner won't clear.** At least one finding at or above the blocking severity is still open. If you don't see a severity level defined in the merge block banner, it means that your repository is using the most stringent code quality thresholds, which require *all* findings to be addressed before merging. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/unblock-your-pr). diff --git a/content/code-security/tutorials/index.md b/content/code-security/tutorials/index.md index 316e84466a65..57bd9340aea2 100644 --- a/content/code-security/tutorials/index.md +++ b/content/code-security/tutorials/index.md @@ -9,6 +9,7 @@ versions: contentType: tutorials children: - /secret-protection-adoption-path + - /code-quality-adoption-path - /trialing-github-advanced-security - /adopting-github-advanced-security-at-scale - /secure-your-organization diff --git a/content/copilot/concepts/agents/code-review.md b/content/copilot/concepts/agents/code-review.md index 690bdf2001d9..8210cdea749d 100644 --- a/content/copilot/concepts/agents/code-review.md +++ b/content/copilot/concepts/agents/code-review.md @@ -83,7 +83,7 @@ For more information, see [AUTOTITLE](/copilot/reference/review-excluded-files). * **Full project context gathering**. This provides more specific, accurate, and contextually aware code reviews. This capability analyzes your entire repository to better understand the context of code changes. * **The ability to pass suggestions to {% data variables.copilot.copilot_cloud_agent %}**. This automates creating a new pull request against your branch with the suggested fixes applied. Passing suggestions to {% data variables.copilot.copilot_cloud_agent %} is in public preview and subject to change. -These capabilities are enabled automatically for all plans that include {% data variables.copilot.copilot_code-review_short %}. See [Review effort level](#review-effort-level) later in this article for information about choosing between Low and Medium analysis levels. +These capabilities are enabled automatically for all plans that include {% data variables.copilot.copilot_code-review_short %}. See [Review effort level](#review-effort-level) later in this article for information about choosing between Lite and Balanced analysis levels. If {% data variables.product.prodname_actions %} is unavailable or if Actions workflows used by {% data variables.copilot.copilot_code-review_short %} fail, reviews will still be generated. However, they will not include the additional features provided by the agentic capabilities. @@ -104,18 +104,18 @@ You can view the {% data variables.product.prodname_actions %} minutes associate ## Review effort level -> [!NOTE] -> Medium review effort is in {% data variables.release-phases.public_preview %} and subject to change. The [AUTOTITLE](/free-pro-team@latest/site-policy/github-terms/github-pre-release-license-terms) apply to your use of preview features. - {% data variables.copilot.copilot_code-review_short %} supports multiple review effort levels, so you can choose the level of thoroughness that matches the criticality of your code. -* **Low**: Standard review. Provides fast, targeted feedback on common issues such as bugs, security vulnerabilities, and style inconsistencies (default). -* **Medium**: Routes pull requests to a higher-reasoning model for longer analysis of complex logic, security-sensitive code, and cross-service changes. Medium reviews use more {% data variables.product.prodname_ai_credits_short %} and {% data variables.product.prodname_actions %} minutes than Low reviews. For better performance with Medium reviews, consider configuring larger or self-hosted runners. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-runners). +* **Lite**: Standard review. Provides fast, targeted feedback on common issues such as bugs, security vulnerabilities, and style inconsistencies (default). +* **Balanced**: Routes pull requests to a higher-reasoning model for longer analysis of complex logic, security-sensitive code, and cross-service changes. Balanced reviews use more {% data variables.product.prodname_ai_credits_short %} and {% data variables.product.prodname_actions %} minutes than Lite reviews. For better performance with Balanced reviews, consider configuring larger or self-hosted runners. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-runners). + +Use Balanced for security-sensitive code, multi-service pull requests, or repositories with strict quality standards. Use Lite for routine changes where fast feedback is more important than exhaustive analysis. -Use Medium for security-sensitive code, multi-service pull requests, or repositories with strict quality standards. Use Low for routine changes where fast feedback is more important than exhaustive analysis. +Organization owners can set a default review effort level for automatic code reviews in their organization. Repository administrators can override the organization default for a specific repository. +After {% data variables.copilot.copilot_code-review_short %} reviews a pull request, the pull request overview comment shows the effort level used for each review run. -Repository administrators can set the default review effort level for automatic code reviews. For configuration steps, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-automatic-review). +For configuration steps, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-automatic-review). ## Code review usage @@ -236,7 +236,7 @@ For full instructions, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up {% data variables.product.prodname_code_quality %} complements {% data variables.copilot.copilot_code-review_short %} by adding: -* **Hybrid detection** that combines rules-based {% data variables.product.prodname_codeql %} analysis with AI-powered analysis, on pull requests and on your default branch. +* **Rules-based {% data variables.product.prodname_codeql %}-powered analysis** on pull requests and on your default branch. * **Test-coverage metrics** on pull requests, so you can see whether a change maintains or reduces coverage. * **One-click, {% data variables.product.prodname_copilot_short %}-powered fixes**, including delegating remediation to {% data variables.copilot.copilot_cloud_agent %}. * **Optional merge gating** with rulesets, so pull requests with unresolved rules-based findings (or that miss a coverage threshold) can be blocked from merging. diff --git a/content/copilot/concepts/context/repository-indexing.md b/content/copilot/concepts/context/repository-indexing.md index bdf9eec0b645..fea5beddbff5 100644 --- a/content/copilot/concepts/context/repository-indexing.md +++ b/content/copilot/concepts/context/repository-indexing.md @@ -51,7 +51,7 @@ Once an index has been created for a repository, it can be used by: {% data variables.product.prodname_copilot_short %} in {% data variables.product.prodname_vscode %} can use semantic indexing for workspace files from repositories hosted outside {% data variables.product.github %}, such as GitLab and local repositories. This feature uploads your data to {% data variables.product.github %} to make it searchable. -> [!NOTE] This feature is only available on {% data variables.product.prodname_dotcom_the_website %}. It is not available on {% data variables.product.prodname_ghe_server %}. +> [!NOTE] This feature is only available on {% data variables.product.prodname_dotcom_the_website %}. It is not available on {% data variables.enterprise.data_residency_site %} or {% data variables.product.prodname_ghe_server %}. This feature is controlled by policy and is disabled by default. For organizations and enterprises with {% data variables.copilot.copilot_business_short %} or {% data variables.copilot.copilot_enterprise_short %}, an enterprise owner or organization owner must explicitly set the `Semantic indexing for non-GitHub repositories` policy to **Enabled** before members can use it. If the policy remains **Unconfigured**, the feature stays unavailable. See: diff --git a/content/copilot/concepts/copilot-usage-metrics/copilot-metrics.md b/content/copilot/concepts/copilot-usage-metrics/copilot-metrics.md index 6ba71c160d08..e53444b0f787 100644 --- a/content/copilot/concepts/copilot-usage-metrics/copilot-metrics.md +++ b/content/copilot/concepts/copilot-usage-metrics/copilot-metrics.md @@ -36,7 +36,7 @@ Metrics are available through: ## Which usage is included? -{% data variables.product.prodname_copilot_short %} usage metrics are derived from telemetry across multiple {% data variables.product.prodname_copilot_short %} surfaces, including IDE and {% data variables.copilot.copilot_cli_short %} activity. Most metrics come from client-side IDE telemetry, and **end users need telemetry enabled in their IDE** for the richest data in these metrics. +{% data variables.product.prodname_copilot_short %} usage metrics are derived from telemetry across multiple {% data variables.product.prodname_copilot_short %} surfaces, including IDE, {% data variables.copilot.copilot_cli_short %}, and {% data variables.copilot.agent_apps %} activity. Most metrics come from client-side IDE telemetry, and **end users need telemetry enabled in their IDE** for the richest data in these metrics. In addition, {% data variables.product.prodname_copilot_short %} usage metrics incorporate **server-side telemetry** to identify active users that client-side telemetry alone may miss. Network conditions, proxy configurations, and client settings can prevent client telemetry from reaching {% data variables.product.github %}, so server-side signals ensure those users still appear in your reports. diff --git a/content/copilot/concepts/mcp-management.md b/content/copilot/concepts/mcp-management.md index 327b741197c8..933171efa45b 100644 --- a/content/copilot/concepts/mcp-management.md +++ b/content/copilot/concepts/mcp-management.md @@ -12,42 +12,35 @@ category: {% data reusables.copilot.mcp.intro %} -You can manage MCP server usage in your organization or enterprise by configuring a series of MCP policies on {% data variables.product.github %}. Through these policies, you can allow or block MCP server usage entirely, or restrict access to a list of servers that you define in an MCP registry. +You can manage MCP server usage in your organization or enterprise by configuring MCP policies on {% data variables.product.github %}. -## MCP policy settings +The **MCP servers in {% data variables.product.prodname_copilot_short %}** policy defines whether MCP servers can run at all across {% data variables.product.prodname_copilot_short %} clients. We recommend keeping this policy enabled and, if necessary, restricting the MCP servers that users can run to an approved list. -The following settings let you control how MCP servers are discovered and accessed in your organization or enterprise: +## MCP allowlists -* **MCP servers in {% data variables.product.prodname_copilot_short %}**: Manage the use of MCP servers for all users with {% data variables.product.prodname_copilot_short %} seats in your organization or enterprise. -* **MCP Registry URL**: Specify the URL of your MCP registry, allowing your developers to discover and use approved MCP servers in supported surfaces. -* **Restrict MCP access to registry servers**: Choose whether to allow all MCP servers or restrict access to only those listed in your configured registry. +The recommended method for creating an allowlist is to use your enterprise's `{% data variables.copilot.managed_setting_file %}` file. This allows you to apply settings across clients that users cannot override. -For an overview of which policies apply to which surfaces, see [AUTOTITLE](/copilot/reference/supported-surfaces-for-policies). +Alternatively, you can host your own MCP registry and restrict access to servers in the registry. However, this method has weaker enforcement than `{% data variables.copilot.managed_setting_file %}`. -## MCP registries +{% rowheaders %} -An MCP registry is a directory of MCP servers that acts like a catalog for IDEs and {% data variables.product.prodname_copilot_short %}. Each registry entry points to a server's manifest, which describes the tools, resources, and prompts that server provides. +| Method | Managed settings file | Custom registry | +| ------ | --------------------- | --------------- | +| Release phase | Generally available | {% data variables.release-phases.public_preview_caps %}, not prioritized for development | +| Ease of setup | You can host a configuration file on {% data variables.product.github %} that applies automatically to clients. | You must host your own registry that matches the MCP specification and serves HTTPS requests. | +| Enforcement level | Enterprise-wide settings, overridable for enterprise teams | Enterprise-wide or for individual organizations | +| Supported clients | Clients supported by the `{% data variables.copilot.managed_setting_file %}` file, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings). Planned to expand in the near future. | See [AUTOTITLE](/copilot/reference/mcp-allowlist-enforcement). | +| Server matching method | Secure matching based on name, URL, or `stdio` commands | Less secure matching, based on name or ID only. Users can bypass the restriction by editing configuration files. | -After you create your MCP registry, you can make it available to your company, allowing you to: -* Curate a catalog of MCP servers your developers can discover and use without context switching -* Restrict access to unapproved servers for increased security and compliance -* Provide clarity to developers when a server is blocked by policy +{% endrowheaders %} -To create your own MCP registry, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-registry). +## Next steps -### Supported surfaces +To configure an allowlist on {% data variables.product.github %}, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-enterprise-allowlist). -The following table lists where MCP registry features are supported in detail. +If you create your own MCP registry, see -| Surface | Registry display | Allowlist enforcement | -|---|:---:|:---:| -| {% data variables.copilot.copilot_cli_short %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v1.0.11+ | -| {% data variables.copilot.copilot_cloud_agent %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "x" aria-label="Not supported" %} | -| Eclipse | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v4.38+ | -| JetBrains | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v1.5.64+ | -| {% data variables.product.prodname_vs %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v18.4.0+ | -| {% data variables.product.prodname_vscode_shortname %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v1.109.3+ | -| Xcode | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v0.47.0+ | +## Further reading -> [!NOTE] -> For Eclipse, JetBrains, and Xcode, MCP management features are supported in the pre-release versions of {% data variables.product.prodname_copilot_short %}. +* [AUTOTITLE](/copilot/reference/supported-surfaces-for-policies) +* [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-registry) diff --git a/content/copilot/get-started/enterprise-ai-governance.md b/content/copilot/get-started/enterprise-ai-governance.md index 7c195840247b..151c8c0a22e3 100644 --- a/content/copilot/get-started/enterprise-ai-governance.md +++ b/content/copilot/get-started/enterprise-ai-governance.md @@ -32,7 +32,7 @@ journeyTracks: - href: '/copilot/tutorials/roll-out-at-scale/enable-developers/integrate-ai-agents' - href: '/copilot/concepts/agents/enterprise-management' - href: '/copilot/tutorials/cloud-agent/build-guardrails' - - href: '/copilot/concepts/mcp-management' + - href: '/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-enterprise-allowlist' - id: 'enable_agents' title: 'Enabling agents' description: 'Roll out and monitor agentic features.' diff --git a/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings.md b/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings.md index 2816e0f09f9a..c65f0984c472 100644 --- a/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings.md +++ b/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings.md @@ -58,7 +58,7 @@ There are additional considerations if you use a dedicated enterprise for {% dat For server-managed deployments, use `copilot/{% data variables.copilot.team_mappings_file %}` and the `copilot/{% data variables.copilot.team_settings_directory %}` directory when one or more enterprise teams should use settings that differ from your default `copilot/{% data variables.copilot.managed_setting_file %}` values. `enabledPlugins` and `extraKnownMarketplaces` work additively. The enterprise `{% data variables.copilot.managed_setting_file %}` sets a baseline, and an enterprise team file can add more plugins and marketplaces on top of it. -1. In your enterprise's `copilot/{% data variables.copilot.managed_setting_file %}` file, mark each key you want to make eligible for override using the `{ "overridable": }` syntax. The `json` files you map to teams can only send different values for keys you mark overridable. An `overridable` value you provide in `managed-settings.json` is the default when teams files do not declare a different value for a given key. +1. In your enterprise's `copilot/{% data variables.copilot.managed_setting_file %}` file, mark each key you want to make eligible for override using the `{ "overridable": }` syntax. The `json` files you map to teams can only send different values for keys you mark overridable. An `overridable` value you provide in `managed-settings.json` is the default when teams files do not declare a different value for a given key. For example, to defer both `model` and `disableBypassPermissionsMode`: ```json @@ -66,12 +66,17 @@ For example, to defer both `model` and `disableBypassPermissionsMode`: "model": { "overridable": "auto" }, "permissions": { "disableBypassPermissionsMode": { "overridable": "disable" } + }, + "allowedMcpServers": { + "overridable": [ + { "serverUrl": "https://mcp.company.com/*" } + ] } } ``` - + 1. In your enterprise's `.github-private` repository, create `copilot/{% data variables.copilot.team_mappings_file %}`. Map each team settings file to one or more enterprise team slugs. The key is the settings file name and the value is an array of team slugs, so you can apply one file across multiple teams. - + ```json { "devs.json": ["developers-all", "finops-dev"], @@ -79,7 +84,7 @@ For example, to defer both `model` and `disableBypassPermissionsMode`: "frontier.json": ["ai-pioneers"] } ``` - + 1. Create the team settings file under `copilot/{% data variables.copilot.team_settings_directory %}`. Include only the keys you marked as overridable. Every other key stays governed by your enterprise default. ```json @@ -87,7 +92,10 @@ For example, to defer both `model` and `disableBypassPermissionsMode`: "model": "unmanaged", "permissions": { "disableBypassPermissionsMode": "unmanaged" - } + }, + "allowedMcpServers": [ + { "serverUrl": "https://team-specific-mcp.company.com/*" } + ] } ``` diff --git a/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-enterprise-allowlist.md b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-enterprise-allowlist.md new file mode 100644 index 000000000000..019f0546c8fa --- /dev/null +++ b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-enterprise-allowlist.md @@ -0,0 +1,61 @@ +--- +title: Configuring an MCP server allowlist for your enterprise +intro: Define which MCP servers your users can and cannot use without the need for a private registry. +permissions: Enterprise owners +product: '{% data variables.copilot.copilot_enterprise_short %} or {% data variables.copilot.copilot_business_short %}' +versions: + feature: copilot +shortTitle: Configure enterprise allowlist +contentType: how-tos +category: + - Configure Copilot + - Manage Copilot for a team +--- + +## About allowlists + +You can define an allowlist and denylist to control which MCP servers users in your enterprise can run in {% data variables.product.prodname_copilot_short %} clients. These lists are defined in your enterprise's `{% data variables.copilot.managed_setting_file %}`, which you can store on {% data variables.product.github %}. + +For more information, see [AUTOTITLE](/copilot/concepts/mcp-management). + +## Prerequisites + +* For any MCP servers to run, the **MCP servers in {% data variables.product.prodname_copilot_short %}** policy must be enabled for your enterprise or for organizations where MCP servers should be allowed. +* If you currently restrict MCP servers to a custom registry, we recommend turning off this restriction to avoid conflicts with your new allowlist and maintain a single source of truth. Set the **Restrict MCP access to registry servers** policy to **Allow all**, and optionally clear the value for **MCP Registry URL**. + +You can find these settings in the {% octicon "mcp" aria-hidden="true" aria-label="MCP" %} **MCP** section of your {% data variables.product.prodname_copilot_short %} policies. See [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-enterprise-policies). + +## Defining an allowlist or denylist + +1. Create a `{% data variables.copilot.managed_setting_file %}` file for your enterprise. Most enterprises store this file in a `.github-private` repository. You can also install it directly on users' machines using mobile device management. See [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings#deploying-server-managed-settings). +1. Edit the file to define an allowlist and denylist for MCP servers. You can match by name, server URL, or specific commands. For syntax details, see [allowedMcpServers](/copilot/reference/enterprise-managed-settings-reference#allowedMcpServers), and [deniedMcpServers](/copilot/reference/enterprise-managed-settings-reference#deniedMcpServers) in "Enterprise managed settings reference." + + The following example allows servers that match any of the three allowlist entries. The server at `learn.microsoft.com` is always blocked, even if it also matches an allowlist entry. + + ```json copy + { + "allowedMcpServers": [ + { "serverUrl": "https://api.githubcopilot.com/*" }, + { "serverCommand": ["npx", "@playwright/mcp@latest"] }, + { "serverCommand": ["cmd", "/c", "uvx", "markitdown-mcp"] } + ], + "deniedMcpServers": [ + { "serverUrl": "https://learn.microsoft.com/*" } + ] + } + ``` + +## Evaluation rules + +{% data variables.product.prodname_copilot_short %} clients evaluate MCP servers in this order: + +1. Always allow built-in default servers, such as the built-in {% data variables.product.github %} MCP server. +1. Block the server if it matches any entry in `deniedMcpServers`. +1. If `allowedMcpServers` is present, block the server if it does not match an entry. +1. Block the server if its URL or command contains an unresolved variable, such as `${VARIABLE}` or `$VARIABLE`, because the client cannot verify the server. + +If a client receives settings from multiple `{% data variables.copilot.managed_setting_file %}` deployment methods, all the settings apply. A deny rule from any source blocks the server, and a server must match an allowlist entry at every layer that defines one. + +If an allowlist or denylist is malformed (for example, has invalid JSON), the client treats the policy as an empty `allowedMcpServers` list. This blocks all servers except built-in default servers. + +If the client cannot determine a policy layer because of a retrieval or device-discovery error, it retains the previously enforced policy. The effective policy can become more restrictive, but not less restrictive. diff --git a/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-registry.md b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-registry.md index 2740d1f79634..9940a155c1ea 100644 --- a/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-registry.md +++ b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-registry.md @@ -12,6 +12,8 @@ category: - Manage Copilot for a team --- +> [!IMPORTANT] {% data reusables.copilot.mcp.registry-alternative %} + ## Prerequisites Before you create your Model Context Protocol (MCP) registry, you should understand the functionality and benefits of MCP management for your company. See [AUTOTITLE](/copilot/concepts/mcp-management). diff --git a/content/copilot/how-tos/administer-copilot/manage-mcp-usage/index.md b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/index.md index 1c0723ac6a56..c68f90f1257f 100644 --- a/content/copilot/how-tos/administer-copilot/manage-mcp-usage/index.md +++ b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/index.md @@ -1,11 +1,13 @@ --- title: Managing MCP usage in your company shortTitle: Manage MCP usage -intro: 'Organization and enterprise owners can control the availability of MCP servers for their developers using MCP management features.' +intro: Organization and enterprise owners can control the availability of MCP servers for their developers using MCP management features. versions: feature: copilot children: + - /configure-enterprise-allowlist - /configure-mcp-registry - - /configure-mcp-server-access + - /restrict-based-on-registry contentType: how-tos --- + diff --git a/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-server-access.md b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/restrict-based-on-registry.md similarity index 92% rename from content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-server-access.md rename to content/copilot/how-tos/administer-copilot/manage-mcp-usage/restrict-based-on-registry.md index dc17e5cf82d8..e6add7d8789b 100644 --- a/content/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-server-access.md +++ b/content/copilot/how-tos/administer-copilot/manage-mcp-usage/restrict-based-on-registry.md @@ -1,21 +1,21 @@ --- -title: Configure MCP server access for your organization or enterprise +title: Restrict MCP server access to a custom registry intro: You can configure an MCP registry URL and access control policy to determine which MCP servers developers can discover and use in supported IDEs and {% data variables.copilot.copilot_cli_short %}. permissions: Enterprise owners and organization owners product: '{% data variables.copilot.copilot_enterprise_short %} or {% data variables.copilot.copilot_business_short %}' versions: feature: copilot -shortTitle: Configure MCP server access +shortTitle: Restrict based on registry redirect_from: - /copilot/how-tos/administer-copilot/configure-mcp-server-access - /copilot/how-tos/administer-copilot/manage-for-organization/set-extension-permissions + - /copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-server-access contentType: how-tos category: - Manage Copilot for a team --- -> [!NOTE] -> The MCP registry URL and allowlist are in {% data variables.release-phases.public_preview %} and subject to change. +> [!IMPORTANT] {% data reusables.copilot.mcp.registry-alternative %} ## Prerequisites diff --git a/content/copilot/how-tos/copilot-on-github/set-up-copilot/configure-automatic-review.md b/content/copilot/how-tos/copilot-on-github/set-up-copilot/configure-automatic-review.md index 368a570f5cb9..7ab1a1850532 100644 --- a/content/copilot/how-tos/copilot-on-github/set-up-copilot/configure-automatic-review.md +++ b/content/copilot/how-tos/copilot-on-github/set-up-copilot/configure-automatic-review.md @@ -61,18 +61,28 @@ You can configure {% data variables.copilot.copilot_code-review_short %} to revi {% data reusables.copilot.auto-code-review-steps %} -## Configuring review effort level for a repository +## Configuring review effort level for an organization -> [!NOTE] -> Medium review effort is in {% data variables.release-phases.public_preview %} and subject to change. +You can set the default review effort level that {% data variables.copilot.copilot_code-review_short %} uses for automatic reviews at both the organization and repository levels. Repository settings override organization defaults. For an overview of review effort levels, see [AUTOTITLE](/copilot/concepts/agents/code-review#review-effort-level). + +{% data reusables.profile.access_org %} +{% data reusables.profile.org_settings %} +1. {% data reusables.user-settings.code-planning-automation %} click **{% octicon "copilot" aria-hidden="true" aria-label="copilot" %} {% data variables.product.prodname_copilot_short %}**, then **Code review**. +1. Next to "Review effort level," select the default effort level for automatic reviews in repositories in your organization. + * **Lite**: Standard review (default). + * **Balanced**: Deeper analysis of complex logic, security-sensitive code, and cross-service changes. + +Balanced reviews use more {% data variables.product.prodname_actions %} minutes and {% data variables.product.prodname_ai_credits_short %}. If you use Balanced effort, consider configuring larger or self-hosted runners for better performance. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-runners). + +## Configuring review effort level for a repository -You can set the default review effort level that {% data variables.copilot.copilot_code-review_short %} uses for automatic reviews in a repository. For an overview of review effort levels, see [AUTOTITLE](/copilot/concepts/agents/code-review#review-effort-level). +You can override the organization default and set a different default review effort level for automatic reviews in a repository. {% data reusables.repositories.navigate-to-repo %} {% data reusables.repositories.sidebar-settings %} 1. {% data reusables.user-settings.code-planning-automation %} click **{% octicon "copilot" aria-hidden="true" aria-label="copilot" %} {% data variables.product.prodname_copilot_short %}**, then **Code review**. 1. Next to "Review effort level," select the effort level for automatic reviews in this repository. - * **Low**: Standard review (default). - * **Medium**: Deeper analysis of complex logic, security-sensitive code, and cross-service changes. + * **Lite**: Standard review (default). + * **Balanced**: Deeper analysis of complex logic, security-sensitive code, and cross-service changes. -Medium reviews use more {% data variables.product.prodname_actions %} minutes and {% data variables.product.prodname_ai_credits_short %}. If you use Medium effort, consider configuring larger or self-hosted runners for better performance. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-runners). +Balanced reviews use more {% data variables.product.prodname_actions %} minutes and {% data variables.product.prodname_ai_credits_short %}. If you use Balanced effort, consider configuring larger or self-hosted runners for better performance. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-runners). diff --git a/content/copilot/reference/ai-models/model-comparison.md b/content/copilot/reference/ai-models/model-comparison.md index aece1b4d4e8f..feec6ad112a1 100644 --- a/content/copilot/reference/ai-models/model-comparison.md +++ b/content/copilot/reference/ai-models/model-comparison.md @@ -28,7 +28,7 @@ Use this table to find a suitable model quickly, see more detail in the sections | Model | Task area | Excels at (primary use case) | Further reading | |----------------------------------------------------|-----------------------|------------------------------|-----------------------------| | {% for model in tables.copilot.model-comparison %} | -| {{ model.name }}{% if model.name == 'MAI-Code-1-Flash' %}[^mai-code-1-flash]{% endif %} | {{ model.task_area }} | {{ model.excels_at }} | {{ model.further_reading }} | +| {{ model.name }}{% if model.name == 'MAI-Code-1-Flash' %}[^mai-code-1-flash]{% elsif model.name == 'Kimi K3' %}[^kimi-k3]{% endif %} | {{ model.task_area }} | {{ model.excels_at }} | {{ model.further_reading }} | | {% endfor %} | ## Task: General-purpose coding and writing @@ -137,10 +137,20 @@ Use one of these models if you want to: If your task involves deep reasoning or large-scale refactoring, consider a model from [Deep reasoning and debugging](#task-deep-reasoning-and-debugging). For text-only tasks or simpler code edits, see [Fast help with simple or repetitive tasks](#task-fast-help-with-simple-or-repetitive-tasks). +## Model-specific considerations + +Some models have behaviors, limitations, or safeguards that are useful to understand before you choose them for a task. + +### {% data variables.copilot.copilot_kimi_k3 %} + +{% data variables.copilot.copilot_kimi_k3 %} is designed for long-context, multi-step coding and agentic workflows. In pre-release testing, the model exhibited elevated risk on certain higher-risk prompts and was less consistent than some other models in refusing requests involving sensitive topics. These behaviors may reflect differences in the model's safety post-training and alignment. We have deployed additional safeguards in {% data variables.product.prodname_copilot %} to help mitigate the identified risks. As with any model, enterprises should evaluate model capabilities, limitations, and safeguards in light of their particular use cases and requirements. + ## Next steps [^mai-code-1-flash]: {% data variables.copilot.copilot_mai_code_1_flash %} is a continuously improving model. Performance and behavior may evolve over time as new checkpoints are released. +[^kimi-k3]: For important information about {% data variables.copilot.copilot_kimi_k3 %} behavior and safeguards, see [Model-specific considerations](#kimi-k3). + Choosing the right model helps you get the most out of {% data variables.product.prodname_copilot_short %}. If you're not sure which model to use, start with a general-purpose option like {% data variables.copilot.copilot_gpt_5_mini %}, then adjust based on your needs. * For detailed model specs and pricing, see [AUTOTITLE](/copilot/reference/ai-models/supported-models). diff --git a/content/copilot/reference/ai-models/model-hosting.md b/content/copilot/reference/ai-models/model-hosting.md index 747e63f4df90..da6d93a31097 100644 --- a/content/copilot/reference/ai-models/model-hosting.md +++ b/content/copilot/reference/ai-models/model-hosting.md @@ -127,10 +127,13 @@ Open-weight models have publicly available weights. {% data reusables.copilot.op Used for: * {% data variables.copilot.copilot_kimi_k27_code %} +* {% data variables.copilot.copilot_kimi_k3 %} -{% data variables.copilot.copilot_kimi_k27_code %} was developed by Moonshot AI. It is an open-weight model. {% data variables.product.github %}'s content filtering applies, but you should review the [{% data variables.copilot.copilot_kimi_k27_code %} model card](https://huggingface.co/moonshotai/Kimi-K2.7-Code) and conduct your own evaluations before enabling it. +{% data variables.copilot.copilot_kimi_k27_code %} and {% data variables.copilot.copilot_kimi_k3 %} were developed by Moonshot AI. They are open-weight models that may be less aligned than other {% data variables.product.prodname_copilot_short %} models, with an elevated risk of geographic bias. Before you enable either model, review the [{% data variables.copilot.copilot_kimi_k27_code %} model card](https://huggingface.co/moonshotai/Kimi-K2.7-Code) or the [{% data variables.copilot.copilot_kimi_k3 %} model card](https://huggingface.co/moonshotai/Kimi-K3) and run your own evaluations before enabling. -When using {% data variables.copilot.copilot_kimi_k27_code %}, input prompts and output completions continue to run through {% data variables.product.prodname_copilot %}'s content filters for public code matching, when applied, along with those for harmful or offensive content. +{% data variables.copilot.copilot_kimi_k27_code %} is hosted on US-based Azure AI Foundry infrastructure managed by {% data variables.product.github %} and Microsoft. + +{% data variables.copilot.copilot_kimi_k3 %} is hosted by {% data variables.product.github %} on Fireworks AI. ## Inline suggestions diff --git a/content/copilot/reference/ai-models/supported-models.md b/content/copilot/reference/ai-models/supported-models.md index 5c28b3d96c59..5f8510fb60c3 100644 --- a/content/copilot/reference/ai-models/supported-models.md +++ b/content/copilot/reference/ai-models/supported-models.md @@ -92,6 +92,7 @@ Choosing a larger context window or higher reasoning will impact {% data variabl | {% data variables.copilot.copilot_gpt_56_luna %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | | {% data variables.copilot.copilot_gpt_56_sol %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | | {% data variables.copilot.copilot_gpt_56_terra %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | +| {% data variables.copilot.copilot_kimi_k3 %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% endrowheaders %} @@ -152,6 +153,7 @@ Some {% data variables.product.prodname_copilot_short %} models require minimum | {% data variables.copilot.copilot_claude_sonnet_5 %} | `v1.124` | `17.14.6` | TBD | TBD | TBD | | {% data variables.copilot.copilot_claude_fable_5 %} | `v1.124` | `17.14.6` | TBD | TBD | TBD | | {% data variables.copilot.copilot_kimi_k27_code %} | `v1.127` | `17.14.6` | `1.9.1-251` | TBD | TBD | +| {% data variables.copilot.copilot_kimi_k3 %} | `v1.131` | TBD | TBD | TBD | TBD | | {% data variables.copilot.copilot_mai_code_1_flash %} | `v1.121` | TBD | TBD | TBD | TBD | | {% data variables.copilot.copilot_grok_45 %} | TBD | `17.14.19` | TBD | TBD | TBD | diff --git a/content/copilot/reference/copilot-usage-metrics/copilot-usage-metrics.md b/content/copilot/reference/copilot-usage-metrics/copilot-usage-metrics.md index 5f5d98ae0f36..da659b7b5702 100644 --- a/content/copilot/reference/copilot-usage-metrics/copilot-usage-metrics.md +++ b/content/copilot/reference/copilot-usage-metrics/copilot-usage-metrics.md @@ -87,7 +87,7 @@ Reports come in different shapes depending on their scope and granularity, so th Organization-scope reports also include `organization_id` alongside `enterprise_id`. For example schemas of the data returned by the APIs, see [AUTOTITLE](/copilot/reference/copilot-usage-metrics/example-schema). -The **Type** column uses JSON Schema types: `string`, `integer`, `number`, `boolean`, `array`, and `object`. The **Nullable** column indicates whether a field's value can be `null` or absent from a record where it would otherwise apply. Arrays are always present but can be empty (`[]`), so they are not nullable. +The **Type** column uses JSON Schema types: `string`, `integer`, `number`, `boolean`, `array`, and `object`. The **Nullable** column indicates whether a field's value can be `null` or absent from a record where it would otherwise apply. Activity breakdown arrays are always present but can be empty (`[]`). Optional arrays are marked as nullable and can be absent. ### Report identification and partition fields @@ -128,6 +128,7 @@ Per-user reports contain one record per user for the reporting period. The 28-da | `ai_adoption_phase` | `object` | No | The user's AI adoption phase for the day. Always present; defaults to the "No Cohort" phase. See [AI adoption phase fields](#ai-adoption-phase-fields). | | `totals_by_cli` | `object` | Yes | CLI-specific metrics for the user. Omitted when the user had no {% data variables.copilot.copilot_cli_short %} usage that day. See [{% data variables.copilot.copilot_cli_short %} metrics fields](#copilot-cli-metrics-fields). | | `totals_by_copilot_app` | `object` | Yes | {% data variables.copilot.github_copilot_app_short %} metrics for the user. Omitted when the user had no {% data variables.copilot.github_copilot_app_short %} usage that day. See [{% data variables.copilot.github_copilot_app_short %} metrics fields](#copilot-app-metrics-fields). | +| `totals_by_3rd_party_agent` | `array` | Yes | Per-agent usage metrics for recognized {% data variables.copilot.agent_apps %}. Omitted when the user had no recognized {% data variables.copilot.agent_app %} activity during the reporting period. See [{% data variables.copilot.agent_apps_caps %} metrics fields](#agent-apps-metrics-fields). | | `totals_by_ide` | `array` | No | Per-IDE breakdown of the user's activity. See [Activity breakdown objects](#activity-breakdown-objects). | | `totals_by_feature` | `array` | No | Per-feature breakdown of the user's activity. See [Activity breakdown objects](#activity-breakdown-objects). | | `totals_by_language_feature` | `array` | No | Breakdown combining language and feature dimensions. See [Activity breakdown objects](#activity-breakdown-objects). | @@ -177,6 +178,7 @@ Activity totals and breakdowns: | `totals_by_model_feature` | `array` | No | Aggregated model-and-feature activity breakdown. See [Activity breakdown objects](#activity-breakdown-objects). | | `totals_by_cli` | `object` | Yes | Aggregated {% data variables.copilot.copilot_cli_short %} metrics for the day. Omitted when there is no CLI usage that day. Unlike the per-user form, it does not include `last_known_cli_version`. See [{% data variables.copilot.copilot_cli_short %} metrics fields](#copilot-cli-metrics-fields). | | `totals_by_copilot_app` | `object` | Yes | Aggregated {% data variables.copilot.github_copilot_app_short %} metrics for the day. Enterprise reports only. Null when the enterprise has no {% data variables.copilot.github_copilot_app_short %} activity that day. See [{% data variables.copilot.github_copilot_app_short %} metrics fields](#copilot-app-metrics-fields). | +| `totals_by_3rd_party_agent` | `array` | Yes | Aggregated per-agent usage metrics for recognized {% data variables.copilot.agent_apps %}. Omitted when the enterprise or organization had no recognized {% data variables.copilot.agent_app %} activity that day. See [{% data variables.copilot.agent_apps_caps %} metrics fields](#agent-apps-metrics-fields). | | `totals_by_ai_adoption_phase` | `array` | Yes | Per-phase aggregates of users and their average activity. Omitted when no adoption-phase data is available. See [AI adoption phase fields](#ai-adoption-phase-fields). | | `pull_requests` | `object` | No | Daily pull request activity for the enterprise or organization. See [Pull request activity fields](#pull-request-activity-fields). | @@ -277,6 +279,19 @@ The `totals_by_copilot_app` object contains the following nested fields when {% | `totals_by_copilot_app.token_usage.prompt_tokens_sum` | `integer` | No | Total prompt tokens sent across all {% data variables.copilot.github_copilot_app_short %} requests on this day. | | `totals_by_copilot_app.token_usage.avg_tokens_per_request` | `number` | Yes | Average of output and prompt tokens per {% data variables.copilot.github_copilot_app_short %} request, computed as `(output_tokens_sum + prompt_tokens_sum) ÷ request_count`. Null when there were no requests that day. | +### {% data variables.copilot.agent_apps_caps %} metrics fields + +The `totals_by_3rd_party_agent` array contains one entry for each recognized {% data variables.copilot.agent_app %} used during the reporting period. Entries are grouped by `agent_id`, and integrations that map to the same agent are combined. Use `agent_id` as the stable grouping key and `agent_name` for display. The array is omitted when no recognized {% data variables.copilot.agent_app %} activity is available during the reporting period. + +These metrics come from server-side job activity. The nested `user_initiated_interaction_count` counts {% data variables.copilot.agent_app %} job starts and is distinct from the top-level field with the same name, which counts explicit prompts from other supported telemetry. + +| Field | Type | Nullable | Description | +|:--|:--|:--|:--| +| `totals_by_3rd_party_agent[].agent_name` | `string` | No | Display name of the {% data variables.copilot.agent_app %}. The name can change, so use `agent_id` for grouping. | +| `totals_by_3rd_party_agent[].agent_id` | `string` | No | Stable identifier for the {% data variables.copilot.agent_app %}. | +| `totals_by_3rd_party_agent[].user_initiated_interaction_count` | `integer` | No | Number of user-initiated jobs started for the {% data variables.copilot.agent_app %} during the reporting period. Each job start increments the count once. | +| `totals_by_3rd_party_agent[].session_count` | `integer` | No | Sum of distinct, non-empty session counts for integrations mapped to the agent during the reporting period. Included only in aggregated enterprise and organization reports; omitted from per-user reports. | + ### Pull request activity fields > [!IMPORTANT] diff --git a/content/copilot/reference/copilot-usage-metrics/example-schema.md b/content/copilot/reference/copilot-usage-metrics/example-schema.md index ee5c49ee92f7..e3f34669b38c 100644 --- a/content/copilot/reference/copilot-usage-metrics/example-schema.md +++ b/content/copilot/reference/copilot-usage-metrics/example-schema.md @@ -52,6 +52,15 @@ The following are example schemas for the user-level, enterprise-level, and repo "prompt_tokens_sum": 5400 } }, + "totals_by_3rd_party_agent": [{ + "agent_id": "2246796", + "agent_name": "Claude (Anthropic)", + "user_initiated_interaction_count": 2 + }, { + "agent_id": "2248422", + "agent_name": "Codex (OpenAI)", + "user_initiated_interaction_count": 2 + }], "totals_by_feature": [{ "code_acceptance_activity_count": 1, "code_generation_activity_count": 1, @@ -124,6 +133,8 @@ The following are example schemas for the user-level, enterprise-level, and repo }] ``` +Organization per-user reports use the same `totals_by_3rd_party_agent` entry fields and also include `organization_id`. Per-user entries do not include `session_count`. + ## Enterprise-level schema example ```json copy @@ -159,6 +170,17 @@ The following are example schemas for the user-level, enterprise-level, and repo "total_reviewed_by_copilot" : 1, "total_suggestions" : 1 }, + "totals_by_3rd_party_agent" : [ { + "agent_id" : "2246796", + "agent_name" : "Claude (Anthropic)", + "session_count" : 1, + "user_initiated_interaction_count" : 2 + }, { + "agent_id" : "2248422", + "agent_name" : "Codex (OpenAI)", + "session_count" : 2, + "user_initiated_interaction_count" : 2 + } ], "totals_by_cli" : { "prompt_count" : 3, "request_count" : 3, @@ -327,6 +349,8 @@ The following are example schemas for the user-level, enterprise-level, and repo } ] ``` +Organization aggregated reports use the same `totals_by_3rd_party_agent` entry fields, including `session_count`, and also include `organization_id`. + The following user-teams report examples are returned by the `user-teams-1-day` endpoints and are intended to be joined with the per-user usage report on `user_id`, `day`, and the relevant entity id (`organization_id` for the organization scope, `enterprise_id` for the enterprise scope) to derive team-level metrics. For more guidance, see [AUTOTITLE](/copilot/reference/copilot-usage-metrics/team-level-metrics). > [!NOTE] diff --git a/content/copilot/reference/copilot-usage-metrics/reconciling-usage-metrics.md b/content/copilot/reference/copilot-usage-metrics/reconciling-usage-metrics.md index c8a2bf294bcd..be4cb564f078 100644 --- a/content/copilot/reference/copilot-usage-metrics/reconciling-usage-metrics.md +++ b/content/copilot/reference/copilot-usage-metrics/reconciling-usage-metrics.md @@ -31,6 +31,8 @@ If you notice missing users or unexpectedly low adoption numbers, verify IDE tel {% data variables.copilot.copilot_cli_short %} metrics (`daily_active_cli_users` and `totals_by_cli`) are collected and reported separately from IDE telemetry. CLI usage does **not** contribute to IDE-based active user counts or other IDE metrics. +Do not reconcile `totals_by_3rd_party_agent[].user_initiated_interaction_count`, which counts user-initiated {% data variables.copilot.agent_app %} jobs, with the top-level `user_initiated_interaction_count`, which counts explicit prompts from other supported telemetry. + ## Metric alignment The dashboard and APIs use shared definitions for key metrics: diff --git a/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md b/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md index 17d4d4fc1831..d2bd4d86befa 100644 --- a/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md +++ b/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md @@ -38,6 +38,8 @@ In {% data variables.copilot.copilot_cli_short %}, the `sandbox` key is an excep | `strictKnownMarketplaces` | Restricts plugin installation to explicitly listed marketplaces | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | | `telemetry` | Configures OpenTelemetry export, routing {% data variables.product.prodname_copilot_short %} usage data to a collector of your choice | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "x" aria-label="Not supported" %} | | `remoteControl` | Restricts whether sessions hosted on this device can be remotely controlled, based on the controlling client's SSO authorization status for the listed organizations. Doesn't affect the user's ability to remotely control sessions hosted on other devices | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | +| `allowedMcpServers` | Defines an allowlist of MCP servers permitted to run. Any server not matched is blocked. Omit to allow all servers, subject to any deny rules | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | +| `deniedMcpServers` | Defines MCP servers that are unconditionally blocked, even if they also match an entry in `allowedMcpServers` | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | | `sandbox` | Enforces minimum local sandbox restrictions for command execution, filesystem and network access, credentials, and local MCP and LSP servers | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "x" aria-label="Not supported" %} | {% endrowheaders %} @@ -46,8 +48,9 @@ In {% data variables.copilot.copilot_cli_short %}, the `sandbox` key is an excep For server-managed deployments, the enterprise can apply different governance to groups of users based on their enterprise team membership. The enterprise defines all settings—team membership only determines which users receive a given set of values. -To make a key eligible for team-specific values, mark it as overridable in `{% data variables.copilot.managed_setting_file %}` using the `{ "overridable": }` syntax. An overridable key uses the team's value when set, or falls back to your enterprise default when the team leaves it unset. The `{ "overridable": }` syntax applies to the governance keys `permissions.model` and `permissions.disableBypassPermissionsMode`. Keys not marked overridable remain an enterprise-level decision that teams can't modify. `enabledPlugins` and `extraKnownMarketplaces` work additively. The enterprise `{% data variables.copilot.managed_setting_file %}` sets a baseline, and an enterprise team file can add more plugins and marketplaces on top of it. For the full setup steps, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings#overriding-settings-for-specific-teams). - +To make a key eligible for team-specific values, mark it as overridable in `{% data variables.copilot.managed_setting_file %}` using the `{ "overridable": }` syntax. An overridable key uses the team's value when set, or falls back to your enterprise default when the team leaves it unset. The `{ "overridable": }` syntax applies to the `permissions.model`, `permissions.disableBypassPermissionsMode`, `allowedMcpServers`, and `deniedMcpServers` keys. Keys not marked overridable remain an enterprise-level decision that teams can't modify. + +`enabledPlugins` and `extraKnownMarketplaces` work additively. The enterprise `{% data variables.copilot.managed_setting_file %}` sets a baseline, and an enterprise team file can add more plugins and marketplaces on top of it. For the full setup steps, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings#overriding-settings-for-specific-teams). ## Example configuration @@ -94,6 +97,14 @@ The following example shows these keys in one managed settings file. "mode": "requireSSO", "githubDotComOrganizations": ["ORG-NAME"] }, + "allowedMcpServers": [ + { "serverUrl": "https://api.githubcopilot.com/*" }, + { "serverCommand": ["npx", "@playwright/mcp@latest"] }, + { "serverCommand": ["cmd", "/c", "uvx", "markitdown-mcp"] } + ], + "deniedMcpServers": [ + { "serverUrl": "https://learn.microsoft.com/*" } + ], "sandbox": { "enabled": true, "allowBypass": false, @@ -103,11 +114,11 @@ The following example shows these keys in one managed settings file. } ``` -## `enabledPlugins` +## enabledPlugins Defines plugins that are automatically installed or blocked for all enterprise users. Each entry uses the format `PLUGIN-NAME@MARKETPLACE-NAME` as the key, with a boolean value: `true` to require the plugin to be enabled, or `false` to require it to be disabled. See [AUTOTITLE](/copilot/concepts/agents/about-enterprise-plugin-standards). -## `extraKnownMarketplaces` +## extraKnownMarketplaces Defines additional plugin marketplaces available to users. Each entry is a named marketplace object containing a `source` property. The following source types are supported: @@ -117,7 +128,7 @@ Defines additional plugin marketplaces available to users. Each entry is a named See [AUTOTITLE](/copilot/concepts/agents/about-enterprise-plugin-standards). -## `strictKnownMarketplaces` +## strictKnownMarketplaces Restricts plugin installation to only the marketplaces explicitly defined by the enterprise. An empty array means complete lockdown. Each entry is a marketplace object with a `source` property indicating the source type. The following source types are supported: @@ -130,9 +141,9 @@ Restricts plugin installation to only the marketplaces explicitly defined by the * `"hostPattern"` — requires `hostPattern` (regex matching marketplace hosts) * `"pathPattern"` — requires `pathPattern` (regex matching marketplace paths) -## `permissions` +## permissions -### `disableBypassPermissionsMode` +### disableBypassPermissionsMode Prevents users from enabling bypass mode (also known as "YOLO mode"). Bypass mode lets an agent run commands, access files, and fetch URLs without asking for approval. @@ -143,14 +154,14 @@ When you set `disableBypassPermissionsMode` to `"disable"`, users cannot turn on * In the {% data variables.copilot.github_copilot_app %}, the "Allow all" setting for "Tool Permissions" is blocked in the sessions settings. * This key is overridable by enterprise team mapping. In your `{% data variables.copilot.managed_setting_file %}`, use the `{ "overridable": "disable" }` syntax to specialize the key's configuration on a per-team basis. You can then set `"disableBypassPermissionsMode": "unmanaged"` in a team settings file, providing a specialization that takes precedence over `{% data variables.copilot.managed_setting_file %}` for members of the subject team. -### `model` +### model Sets auto model selection as the default for new conversations. See [AUTOTITLE](/copilot/concepts/models/auto-model-selection). * When you set `permissions.model` to `"auto"`, new sessions use Auto model unless the user specifies a different model on a per-conversation basis. * This key is overridable by enterprise team mapping. In your `{% data variables.copilot.managed_setting_file %}`, use the `{ "overridable": "auto" }` syntax to specialize the key's configuration on a per-team basis. You can then set `"model": "unmanaged"` in a team settings file, providing a specialization that takes precedence over `{% data variables.copilot.managed_setting_file %}` for members of the subject team. -## `telemetry` +## telemetry Configures OpenTelemetry export, routing {% data variables.product.prodname_copilot_short %} usage data to a collector of your choice. @@ -167,13 +178,52 @@ When you set the `telemetry` property, {% data variables.product.prodname_copilo * `resourceAttributes`: An object of OpenTelemetry resource attributes to attach to all exported telemetry (for example, `{"deployment.environment": "production"}`). * `headers`: An object of HTTP headers to include with each telemetry request (for example, an `Authorization` header for your collector). -## `remoteControl` +## remoteControl Restricts whether {% data variables.product.prodname_copilot_short %} sessions hosted on a device can be remotely controlled. This doesn't affect a user's ability to remotely control their sessions hosted on other devices. * `mode`: Set to `"disabled"` to prevent remote control of sessions on the device, `"requireSSO"` to only allow remote control from a client that is SSO-authorized for the organizations listed in `githubDotComOrganizations`, or `"enabled"` to allow it unrestricted. * `githubDotComOrganizations`: An array of organization logins. Required when `mode` is `"requireSSO"`. +## allowedMcpServers + +Defines an allowlist of MCP servers permitted to run. When set, only servers matching at least one entry are allowed. Any server that is not matched is blocked. + +Omit this key entirely to allow all servers, subject to any entries in `deniedMcpServers`. Set it to an empty array to block all servers except built-in default servers. + +When multiple settings sources define `allowedMcpServers`, the effective allowlist is the intersection of all sources. A server must be permitted by every source to run. + +Each entry must contain exactly one matcher property. + +| Property | Matching behavior | Applicable servers | +| --- | --- | --- | +| `serverName` | Matches the user-assigned server label exactly. Wildcards are not supported. Because users choose server names, use `serverUrl` or `serverCommand` when you need to enforce the identity of a server. | Any server. In-memory servers can only use `serverName`. | +| `serverUrl` | Matches a remote server URL. Supports `*` wildcards for subdomains or path prefixes—for example, `https://mcp.example.com/*` or `https://*.internal.example.com/*`. | Remote servers that connect over HTTP or server-sent events (SSE). This property does not apply to local servers, even if they have a URL. | +| `serverCommand` | Matches the exact command and each argument for a local server—for example, `["npx", "-y", "my-mcp-server"]`. Wildcards and command-line expansion are not supported. | Local servers that use standard input and output (`stdio`). This property does not apply to remote servers, even if they have a command. | + +This key is overridable for enterprise teams. Wrap the matcher objects under `overridable` at the enterprise level, then use the regular syntax to define allowlists and denylists in each team's file. + +### URL canonicalization + +Before comparing a `serverUrl` pattern with a server URL, the client normalizes both values: + +* Converts the scheme and host to lowercase. +* Converts internationalized or Unicode host names to Punycode. +* Removes the default port, `:80` for HTTP or `:443` for HTTPS. +* Decodes percent-encoded host octets. For example, `%65vil` becomes `evil`. +* Removes URL fragments and trailing dots from DNS names. +* Prevents wildcards in the authority component from matching across the `/` boundary into the path. + +## deniedMcpServers + +Defines MCP servers that are unconditionally blocked. A server matching any entry is blocked even if it also matches an entry in `allowedMcpServers`. Deny rules always take precedence over allow rules. + +First-party {% data variables.product.prodname_copilot_short %} servers, such as the built-in {% data variables.product.github %} MCP server, are exempt from deny rules and cannot be blocked. + +When multiple settings sources define `deniedMcpServers`, the effective denylist is the union of all sources. A server blocked by any source is blocked for all. + +Each entry uses the same `serverName`, `serverUrl`, or `serverCommand` properties described in [`allowedMcpServers`](#allowedmcpservers). + ## `sandbox` Enforces minimum local sandbox restrictions for {% data variables.copilot.copilot_cli_short %}. Managed sandbox settings impose restrictions rather than defaults: diff --git a/content/copilot/reference/enterprise-administrators/index.md b/content/copilot/reference/enterprise-administrators/index.md index 978cbe1d56f8..e2591e80adea 100644 --- a/content/copilot/reference/enterprise-administrators/index.md +++ b/content/copilot/reference/enterprise-administrators/index.md @@ -7,7 +7,7 @@ children: - /agent-session-filters - /agentic-audit-log-events - /enterprise-managed-settings - - /mcp-allowlist-enforcement + - /mcp-private-registry-enforcement - /policy-conflicts contentType: reference --- diff --git a/content/copilot/reference/enterprise-administrators/mcp-allowlist-enforcement.md b/content/copilot/reference/enterprise-administrators/mcp-allowlist-enforcement.md deleted file mode 100644 index 5fe05658fe84..000000000000 --- a/content/copilot/reference/enterprise-administrators/mcp-allowlist-enforcement.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: MCP allowlist enforcement -intro: 'Understand the logic and limitations of MCP allowlist enforcement.' -versions: - feature: copilot -contentType: reference -redirect_from: - - /copilot/reference/mcp-allowlist-enforcement -category: - - Learn about Copilot ---- - -## Current enforcement limitations - -MCP allowlist enforcement currently has the following limitations: - -* Enforcement is based only on server name/ID matching, which can be bypassed by editing configuration files -* Strict enforcement that prevents installation of non-registry servers is not yet available - -For the highest level of security, you can **disable MCP servers in {% data variables.product.prodname_copilot_short %}** until strict enforcement is available. - -## Enforcement for local servers - -MCP allowlist enforcement applies to both remote and local MCP servers. When "Registry only" is configured, local servers must be included in your registry with the correct server ID, which must exactly match the installed server ID. A server's canonical ID is often defined in its documentation or manifest. - -## Policy resolution for users with multiple seats - -MCP allowlist enforcement is always tied to the organization or enterprise that assigns the {% data variables.product.prodname_copilot %} seat. If a user has multiple seats, {% data variables.product.github %} automatically resolves conflicts and applies a single active policy and registry. - -The resolution logic is: - -1. **Scope**: Policies set by a parent enterprise override those set by an organization. Enterprise policies trickle down to all organizations and members within that enterprise. -1. **Enforcement strictness**: Since `Registry only` is more restrictive than `Allow all`, it will always take precedence. -1. **Recency of registry upload**: If two policies have the same scope and strictness, the most recently uploaded registry will be applied. diff --git a/content/copilot/reference/enterprise-administrators/mcp-private-registry-enforcement.md b/content/copilot/reference/enterprise-administrators/mcp-private-registry-enforcement.md new file mode 100644 index 000000000000..6550a8923ad2 --- /dev/null +++ b/content/copilot/reference/enterprise-administrators/mcp-private-registry-enforcement.md @@ -0,0 +1,52 @@ +--- +title: MCP private registry enforcement +intro: Understand the logic and limitations of MCP private registry enforcement using a private registry server. +versions: + feature: copilot +contentType: reference +category: + - Learn about Copilot +redirect_from: + - /copilot/reference/mcp-allowlist-enforcement + - /copilot/reference/enterprise-administrators/mcp-allowlist-enforcement +--- + +> [!IMPORTANT] {% data reusables.copilot.mcp.registry-alternative %} + +## Supported surfaces + +The following table lists where MCP private registry features are supported. + +| Surface | Registry display | Allowlist enforcement | +|---|:---:|:---:| +| {% data variables.copilot.copilot_cli_short %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v1.0.11+ | +| {% data variables.copilot.copilot_cloud_agent %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "x" aria-label="Not supported" %} | +| Eclipse | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v4.38+ | +| JetBrains | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v1.5.64+ | +| {% data variables.product.prodname_vs %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v18.4.0+ | +| {% data variables.product.prodname_vscode_shortname %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v1.109.3+ | +| Xcode | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} v0.47.0+ | + +> [!NOTE] +> For Eclipse, JetBrains, and Xcode, MCP management features are supported in the pre-release versions of {% data variables.product.prodname_copilot_short %}. + +## Current enforcement limitations + +MCP private registry enforcement currently has the following limitations: + +* Enforcement is based only on server name/ID matching, which can be bypassed by editing configuration files. +* Strict enforcement that prevents installation of non-registry servers is not yet available. For stricter URL-based enforcement that users cannot override locally, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-enterprise-allowlist). + +## Enforcement for local servers + +MCP private registry enforcement applies to both remote and local MCP servers. When "Registry only" is configured, local servers must be included in your registry with the correct server ID, which must exactly match the installed server ID. A server's canonical ID is often defined in its documentation or manifest. + +## Policy resolution for users with multiple seats + +MCP private registry enforcement is always tied to the organization or enterprise that assigns the {% data variables.product.prodname_copilot %} seat. If a user has multiple seats, {% data variables.product.github %} automatically resolves conflicts and applies a single active policy and registry. + +The resolution logic is: + +1. **Scope**: Policies set by a parent enterprise override those set by an organization. Enterprise policies trickle down to all organizations and members within that enterprise. +1. **Enforcement strictness**: Since `Registry only` is more restrictive than `Allow all`, it will always take precedence. +1. **Recency of registry upload**: If two policies have the same scope and strictness, the most recently uploaded registry will be applied. diff --git a/content/copilot/reference/index.md b/content/copilot/reference/index.md index 0fb945a38498..32705f2dcf65 100644 --- a/content/copilot/reference/index.md +++ b/content/copilot/reference/index.md @@ -1,7 +1,7 @@ --- -title: 'Reference for {% data variables.product.prodname_copilot %}' +title: Reference for {% data variables.product.prodname_copilot %} shortTitle: Reference -intro: 'Find information to apply to your work with {% data variables.product.prodname_copilot %}.' +intro: Find information to apply to your work with {% data variables.product.prodname_copilot %}. versions: feature: copilot children: @@ -24,3 +24,4 @@ children: - /enterprise-administrators contentType: reference --- + diff --git a/content/copilot/tutorials/index.md b/content/copilot/tutorials/index.md index c47f0ba20593..a4a3087273e6 100644 --- a/content/copilot/tutorials/index.md +++ b/content/copilot/tutorials/index.md @@ -22,6 +22,7 @@ children: - /write-tests - /refactor-code - /optimize-ai-usage + - /optimize-chat-usage - /optimize-code-reviews - /reduce-technical-debt - /stack-ai-generated-code-in-pull-requests diff --git a/content/copilot/tutorials/optimize-chat-usage.md b/content/copilot/tutorials/optimize-chat-usage.md new file mode 100644 index 000000000000..33e0c112fadb --- /dev/null +++ b/content/copilot/tutorials/optimize-chat-usage.md @@ -0,0 +1,45 @@ +--- +title: Optimize GitHub Copilot Chat context to reduce AI credit usage +shortTitle: Optimize Chat usage +intro: Keep {% data variables.copilot.copilot_chat_short %} responses focused and reduce {% data variables.product.prodname_ai_credits_short %} usage by knowing when to continue a conversation and when to start a new one. +versions: + feature: copilot +contentType: tutorials +category: + - Author and optimize with Copilot +redirect_from: + - /copilot/how-tos/chat-with-copilot/manage-long-conversations + - /copilot/tutorials/manage-long-conversations +--- + +## Continue a conversation when the context is still relevant + +Continue the same conversation when your next prompt depends on earlier messages. For example: + +* You're refining code that {% data variables.product.prodname_copilot_short %} already generated. +* You're debugging the same error across multiple prompts. +* You're iterating on one design, test plan, or implementation approach. + +Keeping related prompts together helps {% data variables.product.prodname_copilot_short %} keep the right context and reduces repetition. + +## Start a new conversation when you switch tasks + +Start a new conversation when your next prompt is about a different problem. For example: + +* You finished one feature and are starting another. +* You're moving from coding work to documentation or release work. +* The existing thread contains context that no longer applies. + +Starting fresh helps {% data variables.product.prodname_copilot_short %} focus on your new goal. + +## Understand how conversation length affects {% data variables.product.prodname_ai_credits_short %} usage + +Each prompt in {% data variables.copilot.copilot_chat_short %} is processed with your new message and relevant context, such as conversation history, selected files, and tool results. + +Longer threads can require more context to be processed for each new prompt. More processed context can increase token usage, which can increase {% data variables.product.prodname_ai_credits_short %} usage. + + +## Further reading + +* [AUTOTITLE](/copilot/tutorials/optimize-ai-usage) +* [AUTOTITLE](/copilot/get-started/best-practices) diff --git a/content/copilot/tutorials/optimize-code-reviews.md b/content/copilot/tutorials/optimize-code-reviews.md index cf5f425485d4..a5cbf7b748ae 100644 --- a/content/copilot/tutorials/optimize-code-reviews.md +++ b/content/copilot/tutorials/optimize-code-reviews.md @@ -207,7 +207,7 @@ As you can see, identifying vulnerabilities automatically, along with suggestion ## 4. Add reliability, maintainability, and coverage checks -So far, {% data variables.copilot.copilot_code-review_short %} has provided per-pull request feedback on style and design, and {% data variables.copilot.copilot_autofix_short %} for {% data variables.product.prodname_code_scanning %} has flagged and fixed security vulnerabilities. To focus on the long-term health of your code, {% data variables.product.prodname_code_quality %} adds reliability, maintainability, and code coverage checks. It combines deterministic, rules-based {% data variables.product.prodname_codeql %} analysis for well-defined anti-patterns with {% data variables.product.prodname_copilot_short %}-powered analysis for issues that fall outside existing rules, so the two approaches complement each other on every change. +So far, {% data variables.copilot.copilot_code-review_short %} has provided per-pull request feedback on style and design, and {% data variables.copilot.copilot_autofix_short %} for {% data variables.product.prodname_code_scanning %} has flagged and fixed security vulnerabilities. To focus on the long-term health of your code, {% data variables.product.prodname_code_quality %} adds reliability, maintainability, and code coverage checks. It uses deterministic, rules-based {% data variables.product.prodname_codeql %} analysis to detect well-defined anti-patterns. When {% data variables.product.prodname_code_quality_short %} is enabled, it posts reliability and maintainability findings as inline comments on the pull request, each with a one-click, {% data variables.product.prodname_copilot_short %}-powered autofix you can apply directly. It also reports coverage metrics that show whether the change maintains or reduces reported code coverage from your test suite compared to the default branch. If you want to enforce these standards, rulesets can require rules-based findings to be resolved and coverage thresholds to be met before merge. diff --git a/data/reusables/copilot/mcp/registry-alternative.md b/data/reusables/copilot/mcp/registry-alternative.md new file mode 100644 index 000000000000..fa5b5f307382 --- /dev/null +++ b/data/reusables/copilot/mcp/registry-alternative.md @@ -0,0 +1 @@ +This feature is in {% data variables.release-phases.public_preview %} and is not the recommended method for restricting access to MCP servers. The more secure, generally available method is to define settings in your enterprise's `{% data variables.copilot.managed_setting_file %}` file. See [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-enterprise-allowlist). diff --git a/data/reusables/copilot/model-autoenablement-excluded-models.md b/data/reusables/copilot/model-autoenablement-excluded-models.md index f56f6afabb40..27b5e135a486 100644 --- a/data/reusables/copilot/model-autoenablement-excluded-models.md +++ b/data/reusables/copilot/model-autoenablement-excluded-models.md @@ -13,6 +13,6 @@ The following models are **not** eligible for default enablement, regardless of * Models that have been explicitly disabled * Pre-GA models -* Open weight models (DeepSeek, {% data variables.copilot.copilot_kimi_k27_code %}) +* Open weight models (DeepSeek, {% data variables.copilot.copilot_kimi_k27_code %}, {% data variables.copilot.copilot_kimi_k3 %}) * Models that are not covered by {% data variables.product.company_short %}'s data retention agreement ({% data variables.copilot.copilot_claude_fable_5 %}) * For enterprises that have restricted models to data-resident or FedRAMP-compliant models, any models that do not respect these policies diff --git a/data/reusables/copilot/open-weight-model-hosting.md b/data/reusables/copilot/open-weight-model-hosting.md index bf9626400e6e..ca3b9a6f7fcd 100644 --- a/data/reusables/copilot/open-weight-model-hosting.md +++ b/data/reusables/copilot/open-weight-model-hosting.md @@ -1 +1 @@ -These models are hosted on US-based Azure AI Foundry infrastructure managed by {% data variables.product.github %} and Microsoft. Customer prompts and responses are not sent to the original model developers. +Hosting differs by model and is described in the sections below. Customer prompts and responses are not sent to the original model developers. diff --git a/data/tables/copilot/model-comparison.yml b/data/tables/copilot/model-comparison.yml index fc836f343917..7b8a4debe6ae 100644 --- a/data/tables/copilot/model-comparison.yml +++ b/data/tables/copilot/model-comparison.yml @@ -138,3 +138,8 @@ task_area: General-purpose coding and agent tasks excels_at: Fast, reliable answers to lightweight coding questions further_reading: '[Kimi K2.7 Code model card](https://huggingface.co/moonshotai/Kimi-K2.7-Code)' + +- name: Kimi K3 + task_area: Agentic coding and long-context work + excels_at: Multi-step agent tasks across large codebases + further_reading: '[Kimi K3 model card](https://huggingface.co/moonshotai/Kimi-K3)' diff --git a/data/tables/copilot/model-release-status.yml b/data/tables/copilot/model-release-status.yml index e1823e4e26e3..8474e4d0b26c 100644 --- a/data/tables/copilot/model-release-status.yml +++ b/data/tables/copilot/model-release-status.yml @@ -126,6 +126,10 @@ provider: 'Moonshot AI' release_status: 'GA' +- name: 'Kimi K3' + provider: 'Moonshot AI' + release_status: 'GA' + # xAI models - name: 'Grok 4.5' provider: 'xAI' diff --git a/data/tables/copilot/model-supported-clients.yml b/data/tables/copilot/model-supported-clients.yml index a830a66aaa0a..74376d93b7b0 100644 --- a/data/tables/copilot/model-supported-clients.yml +++ b/data/tables/copilot/model-supported-clients.yml @@ -248,6 +248,15 @@ xcode: false jetbrains: true +- name: Kimi K3 + dotcom: true + cli: true + vscode: true + vs: true + eclipse: true + xcode: true + jetbrains: true + - name: Raptor mini dotcom: false cli: false diff --git a/data/tables/copilot/model-supported-plans.yml b/data/tables/copilot/model-supported-plans.yml index 66a691613372..88c6697eb1cc 100644 --- a/data/tables/copilot/model-supported-plans.yml +++ b/data/tables/copilot/model-supported-plans.yml @@ -187,6 +187,13 @@ business: true enterprise: true +- name: Kimi K3 + pro: true + pro_plus: true + max: true + business: true + enterprise: true + - name: MAI-Code-1-Flash pro: true pro_plus: true diff --git a/data/tables/copilot/models-and-pricing.yml b/data/tables/copilot/models-and-pricing.yml index 80bae6cc4d1a..92fc17363bee 100644 --- a/data/tables/copilot/models-and-pricing.yml +++ b/data/tables/copilot/models-and-pricing.yml @@ -372,3 +372,11 @@ input: $0.95 cached_input: $0.19 output: $4.00 + +- model: 'Kimi K3' + provider: moonshot_ai + release_status: GA + category: Powerful + input: $3.00 + cached_input: $0.30 + output: $15.00 diff --git a/data/ui.yml b/data/ui.yml index 5e1624d3d7f0..49c99e15e9ca 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -99,6 +99,8 @@ homepage: explore_by_product: Explore by product version_picker: Version description: Help for wherever you are on your GitHub journey. + search_placeholder: Search for anything... + all_docs: All docs toc: getting_started: Getting started diff --git a/data/variables/copilot.yml b/data/variables/copilot.yml index 29ef8e8bf24b..2ca237a8487e 100644 --- a/data/variables/copilot.yml +++ b/data/variables/copilot.yml @@ -243,6 +243,7 @@ copilot_mai_code_1_flash: 'MAI-Code-1-Flash' copilot_raptor_mini: 'Raptor mini' # Open-weight models: copilot_kimi_k27_code: 'Kimi K2.7 Code' +copilot_kimi_k3: 'Kimi K3' ## Current model used by Copilot cloud agent and Copilot CLI cca_current_model: '{% data variables.copilot.copilot_claude_opus_46 %}' diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml index 5e1624d3d7f0..49c99e15e9ca 100644 --- a/src/fixtures/fixtures/data/ui.yml +++ b/src/fixtures/fixtures/data/ui.yml @@ -99,6 +99,8 @@ homepage: explore_by_product: Explore by product version_picker: Version description: Help for wherever you are on your GitHub journey. + search_placeholder: Search for anything... + all_docs: All docs toc: getting_started: Getting started diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index 9e74522d9403..a45c9e2c610a 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -1212,9 +1212,12 @@ test('open search, Ask AI returns 400 error and shows general search results', a // Pressing enter should trigger Ask AI, get 400 error, and show general search results await page.keyboard.press('Enter') - // Wait for general search results to appear - await expect(page.getByRole('link', { name: 'Foo' })).toBeVisible() - await expect(page.getByRole('link', { name: 'Bar' })).toBeVisible() + // Wait for the general search results to appear inside the overlay's suggestions + // group. These render as ActionList items (buttons), so scope the lookup to the + // group rather than matching page-level links of the same name. + const generalSuggestions = page.getByTestId('general-autocomplete-suggestions') + await expect(generalSuggestions.getByRole('button', { name: 'Foo' })).toBeVisible() + await expect(generalSuggestions.getByRole('button', { name: 'Bar' })).toBeVisible() // Wait for the AI error message to appear // This is a canned response for the 400 error @@ -1244,15 +1247,14 @@ test.describe('LandingCarousel component', () => { const carousel = page.locator('[data-testid="landing-carousel"]') await expect(carousel).toBeVisible() - // Check that article cards are present + // Check that article cards are present. Brand Card renders each card's title + // as an

(Card.Heading) wrapping a stretched , so target the heading. const items = page.locator('[data-testid="carousel-items"]') - const cards = items.locator('a') - await expect(cards.first()).toBeVisible() + const cardHeadings = items.locator('h3') + await expect(cardHeadings.first()).toBeVisible() // Verify cards have real titles (not "Unknown Article" when article not found) - const firstCardTitle = cards.first().locator('h3') - await expect(firstCardTitle).toBeVisible() - await expect(firstCardTitle).not.toHaveText('Unknown Article') + await expect(cardHeadings.first()).not.toHaveText('Unknown Article') }) test('navigation works on desktop', async ({ page }) => { @@ -1534,10 +1536,12 @@ test.describe('LandingArticleGridWithFilter component', () => { await expect(articleCards.first()).toBeVisible() const firstCard = articleCards.first() - const titleLink = firstCard.locator('h3 span') + // Brand Card renders the title as an

(Card.Heading) wrapping a + // stretched , and the intro as a Card.Description

. + const titleLink = firstCard.locator('h3 a') await expect(titleLink).toBeVisible() - const intro = firstCard.locator('div').last() // cardIntro is the last div + const intro = firstCard.locator('p').last() await expect(intro).toBeVisible() const introText = await intro.textContent() expect(introText).toBeTruthy() @@ -1653,6 +1657,36 @@ test.describe('LandingArticleGridWithFilter component', () => { await expect(articleGrid).toBeVisible() }) + test('card is keyboard-navigable via Enter (client-side)', async ({ page }) => { + // The brand Card renders a native stretched anchor; a synthetic click from + // pressing Enter on that anchor must bubble to the card's onClick handler so + // keyboard users get the same client-side SPA navigation as mouse users. + // Guards against a regression if the click-intercept logic is refactored. + await page.goto('/get-started/article-grid-discovery') + + const articleGrid = page.getByTestId('article-grid') + await expect(articleGrid).toBeVisible() + + const firstCardLink = articleGrid.getByTestId('article-card').first().getByRole('link').first() + const href = await firstCardLink.getAttribute('href') + expect(href).toBeTruthy() + + // Mark the current document so we can prove navigation was client-side + // (no full page reload): a hard navigation would wipe this window property. + await page.evaluate(() => { + ;(window as unknown as { __spaMarker?: boolean }).__spaMarker = true + }) + + await firstCardLink.focus() + await page.keyboard.press('Enter') + + await expect(page).toHaveURL(new RegExp(href!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) + const stillClientSide = await page.evaluate( + () => (window as unknown as { __spaMarker?: boolean }).__spaMarker === true, + ) + expect(stillClientSide).toBe(true) + }) + test('bespoke landing page does not show duplicate articles', async ({ page }) => { // The bespoke fixture lists individual articles AND their parent group // as children, which would cause duplicates without deduplication. @@ -1671,7 +1705,7 @@ test.describe('LandingArticleGridWithFilter component', () => { const titles: string[] = [] const count = await articleCards.count() for (let i = 0; i < count; i++) { - const title = await articleCards.nth(i).locator('h3 span').textContent() + const title = await articleCards.nth(i).locator('h3').textContent() titles.push(title!) } const uniqueTitles = new Set(titles) diff --git a/src/graphql/data/fpt/schema-issues.json b/src/graphql/data/fpt/schema-issues.json index f48da692fa8f..3f409dadafb8 100644 --- a/src/graphql/data/fpt/schema-issues.json +++ b/src/graphql/data/fpt/schema-issues.json @@ -11734,8 +11734,7 @@ "description": "

The ID for the organization on which the issue type is created.

", "type": "ID!", "id": "id", - "href": "/graphql/reference/other#scalar-id", - "isDeprecated": false + "href": "/graphql/reference/other#scalar-id" } ], "category": "issues" diff --git a/src/graphql/data/fpt/schema.docs.graphql b/src/graphql/data/fpt/schema.docs.graphql index 41cb5ceb8e5f..e737ed48db83 100644 --- a/src/graphql/data/fpt/schema.docs.graphql +++ b/src/graphql/data/fpt/schema.docs.graphql @@ -8636,7 +8636,7 @@ input CreateIssueTypeInput { """ The ID for the organization on which the issue type is created """ - ownerId: ID! @possibleTypes(concreteTypes: ["Organization"]) + ownerId: ID! } """ diff --git a/src/graphql/data/ghec/schema-issues.json b/src/graphql/data/ghec/schema-issues.json index f48da692fa8f..3f409dadafb8 100644 --- a/src/graphql/data/ghec/schema-issues.json +++ b/src/graphql/data/ghec/schema-issues.json @@ -11734,8 +11734,7 @@ "description": "

The ID for the organization on which the issue type is created.

", "type": "ID!", "id": "id", - "href": "/graphql/reference/other#scalar-id", - "isDeprecated": false + "href": "/graphql/reference/other#scalar-id" } ], "category": "issues" diff --git a/src/graphql/data/ghec/schema.docs.graphql b/src/graphql/data/ghec/schema.docs.graphql index 41cb5ceb8e5f..e737ed48db83 100644 --- a/src/graphql/data/ghec/schema.docs.graphql +++ b/src/graphql/data/ghec/schema.docs.graphql @@ -8636,7 +8636,7 @@ input CreateIssueTypeInput { """ The ID for the organization on which the issue type is created """ - ownerId: ID! @possibleTypes(concreteTypes: ["Organization"]) + ownerId: ID! } """ diff --git a/src/landings/components/HomePageHero.module.scss b/src/landings/components/HomePageHero.module.scss index 7948ab860a63..7fe987e584d0 100644 --- a/src/landings/components/HomePageHero.module.scss +++ b/src/landings/components/HomePageHero.module.scss @@ -1,30 +1,73 @@ -// Applied to the brand Hero's root
. The hero background image is -// baked in here (not a Hero.Image visual slot), so these rules must stay. -.section { - height: 21rem; - // Brand Hero's root is display:flex/column but defaults to top alignment; - // restore the vertical centering the old `flex-justify-center` provided. +// Docs 2026 homepage hero: a left-aligned title block stacked above a muted +// search band, both inside bordered rails. No background image — the mountains +// graphic is a separate, out-of-scope band. +.hero { + border-bottom: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); +} + +// Bordered rails, centred with the same max width as the "All Docs" grid. +.rails { + max-width: 82rem; + margin: 0 auto; + border-inline: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); +} + +.titleRow { + padding: 4rem 1.5rem 1.5rem; +} + +.title { + margin: 0; +} + +.lede { + margin: 1rem 0 0; +} + +// Search entry that mimics an input but opens the shared SearchOverlay on +// activation. A full-width muted band with the placeholder on the left and a +// trailing "/" key hint, separated from the title by a top border. +.searchRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + width: 100%; + padding: 1.5rem; + border: 0; + border-top: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); + background-color: var(--color-canvas-subtle); + cursor: text; + text-align: left; + + &:hover { + background-color: var(--color-canvas-inset); + } + + &:focus-visible { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + } +} + +.searchPlaceholder { + color: var(--color-fg-muted); + font-size: 1.5rem; +} + +.searchHint { + display: inline-flex; + align-items: center; justify-content: center; - background-image: image-set( - url("/assets/images/banner-images/hero-home.webp") type("image/webp"), - url("/assets/images/banner-images/hero-home.png") type("image/png") - ); - background-size: cover; - background-position: center right; - background-repeat: no-repeat; -} - -.section h1 { - font-size: 4rem; - line-height: 1.2; -} - -// Frosted-glass panel keeping heading/lede readable over the background image. -.content { - backdrop-filter: blur(1rem); - background-color: color-mix( - in srgb, - var(--color-canvas-subtle) 70%, - transparent - ); + min-width: 2rem; + padding: 0.375rem 0.75rem; + border-radius: var(--brand-borderRadius-medium, 8px); + background-color: var(--brand-color-border-muted); + color: var(--color-fg-default); + font-family: inherit; + font-size: 1.25rem; + line-height: 1; } diff --git a/src/landings/components/HomePageHero.tsx b/src/landings/components/HomePageHero.tsx index d82eb034777c..53e6c61215c2 100644 --- a/src/landings/components/HomePageHero.tsx +++ b/src/landings/components/HomePageHero.tsx @@ -1,21 +1,37 @@ -import { Hero } from '@primer/react-brand' +import { Heading, Text } from '@primer/react-brand' + import { useTranslation } from '@/languages/components/useTranslation' +import { useSearchOverlayContext } from '@/search/components/context/SearchOverlayContext' import styles from './HomePageHero.module.scss' -import cx from 'classnames' export const HomePageHero = () => { const { t } = useTranslation(['header', 'homepage']) + const { setIsSearchOpen } = useSearchOverlayContext() return ( - -
- {t('github_docs')} - {t('description')} +
+
+
+ + {t('github_docs')} + + + {t('description')} + +
+ +
- +
) } diff --git a/src/landings/components/ProductSelectionCard.module.scss b/src/landings/components/ProductSelectionCard.module.scss index 06504a5ecb5b..1bfbcc9b9903 100644 --- a/src/landings/components/ProductSelectionCard.module.scss +++ b/src/landings/components/ProductSelectionCard.module.scss @@ -1,3 +1,85 @@ -.groupIcon { - height: 22px; +// A single "All Docs" grid cell: category heading at the top, product links +// bottom-aligned (space-between) so every cell in a row shares the tallest +// height — matching the Docs 2026 design. Internal dividers are the cell's left +// border (skipped on the first column of each row, per breakpoint, so they don't +// double the container rail) plus a bottom border for row dividers. The column +// count steps 1 -> 2 -> 3 -> 4, so each breakpoint re-applies the left border to +// every cell and then clears it on the new first-of-row. +.cell { + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 2.25rem; + padding: 2rem; + border-bottom: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); + + // Mobile: single column — no internal vertical dividers. + border-left: 0; + + @media (min-width: 34rem) { + // 2 columns. + border-left: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); + + &:nth-child(2n + 1) { + border-left: 0; + } + } + + @media (min-width: 63.25rem) { + // 3 columns. + &:nth-child(n) { + border-left: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); + } + + &:nth-child(3n + 1) { + border-left: 0; + } + } + + @media (min-width: 80rem) { + // 4 columns. + &:nth-child(n) { + border-left: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); + } + + &:nth-child(4n + 1) { + border-left: 0; + } + } +} + +.groupName { + margin: 0; + font-size: 1rem; + font-weight: 600; + line-height: 1.5; + color: var(--color-fg-default); +} + +.linkList { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin: 0; + padding: 0; + list-style: none; +} + +.linkList li { + min-height: 1rem; +} + +.link { + display: block; + overflow: hidden; + font-size: 1rem; + font-weight: 500; + line-height: 1rem; + letter-spacing: 0.01rem; + white-space: nowrap; + text-overflow: ellipsis; } diff --git a/src/landings/components/ProductSelectionCard.tsx b/src/landings/components/ProductSelectionCard.tsx index 0173d6ae7ad4..b052b97e68e4 100644 --- a/src/landings/components/ProductSelectionCard.tsx +++ b/src/landings/components/ProductSelectionCard.tsx @@ -1,41 +1,13 @@ import type { ProductGroupT } from '@/landings/components/ProductSelections' -import React from 'react' import { Link } from '@/frame/components/Link' +import { LinkExternalIcon } from '@primer/octicons-react' import styles from './ProductSelectionCard.module.scss' -import { - LinkExternalIcon, - RocketIcon, - CommentDiscussionIcon, - CopilotIcon, - GearIcon, - ShieldLockIcon, - DeviceMobileIcon, - ProjectIcon, - OrganizationIcon, - CodeSquareIcon, - GlobeIcon, - PencilIcon, -} from '@primer/octicons-react' type ProductSelectionCardProps = { group: ProductGroupT } -const octiconMap: { [name: string]: React.FunctionComponent } = { - LinkExternalIcon, - RocketIcon, - CommentDiscussionIcon, - CopilotIcon, - GearIcon, - ShieldLockIcon, - DeviceMobileIcon, - ProjectIcon, - OrganizationIcon, - CodeSquareIcon, - GlobeIcon, - PencilIcon, -} export const ProductSelectionCard = ({ group }: ProductSelectionCardProps) => { // Don't display the group if it has no children due to versioning @@ -43,58 +15,31 @@ export const ProductSelectionCard = ({ group }: ProductSelectionCardProps) => { return null } - function icon(productGroup: ProductGroupT) { - if (productGroup.icon) { - return ( -
- {productGroup.name} -
- ) - } else if (productGroup.octicon) { - const octicon: React.FunctionComponent = octiconMap[productGroup.octicon] - - if (!octicon) { - throw new Error(`Octicon ${productGroup.octicon} not found`) - } - - return ( -
- {React.createElement(octicon, { height: '22px' } as React.Attributes, null)} -
- ) - } - } - return ( -
-
-
- {icon(group)} - -
-

{group.name}

-
-
- -
-
    - {group.children.map((product) => { - return ( -
  • - - {product.name} - {product.external && ( - - - - )} - -
  • - ) - })} -
-
-
+
+

{group.name}

+ +
    + {group.children.map((product) => { + return ( +
  • + + {product.name} + {product.external && ( + + + + )} + +
  • + ) + })} +
) } diff --git a/src/landings/components/ProductSelections.module.scss b/src/landings/components/ProductSelections.module.scss new file mode 100644 index 000000000000..4ec64165ad96 --- /dev/null +++ b/src/landings/components/ProductSelections.module.scss @@ -0,0 +1,32 @@ +// "All Docs" section — bordered gridline layout mirroring the Docs 2026 design. +// The container supplies the outer left/right rails; internal dividers are drawn +// by each cell (left border, skipping the first column) and each row (bottom +// border). There is deliberately no divider directly under the "All Docs" +// heading. +.section { + max-width: 82rem; + margin: 0 auto; + border-inline: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); +} + +.heading { + padding: 3rem 2rem 1.5rem; +} + +.grid { + display: grid; + grid-template-columns: 1fr; + + @media (min-width: 34rem) { + grid-template-columns: repeat(2, 1fr); + } + + @media (min-width: 63.25rem) { + grid-template-columns: repeat(3, 1fr); + } + + @media (min-width: 80rem) { + grid-template-columns: repeat(4, 1fr); + } +} diff --git a/src/landings/components/ProductSelections.tsx b/src/landings/components/ProductSelections.tsx index 998fb5c9d622..6ca2a4b1d99c 100644 --- a/src/landings/components/ProductSelections.tsx +++ b/src/landings/components/ProductSelections.tsx @@ -1,5 +1,9 @@ import type { ProductT } from '@/frame/components/context/MainContext' +import { Heading } from '@primer/react-brand' + +import { useTranslation } from '@/languages/components/useTranslation' import { ProductSelectionCard } from './ProductSelectionCard' +import styles from './ProductSelections.module.scss' export type ProductGroupT = { name: string @@ -13,14 +17,19 @@ type Props = { } export const ProductSelections = ({ productGroups }: Props) => { + const { t } = useTranslation('homepage') + + const groups = productGroups.filter((group) => group.children && group.children.length > 0) + return ( -
-
-
- {productGroups.map((group) => { - return - })} -
+
+ + {t('all_docs')} + +
+ {groups.map((group) => ( + + ))}
) diff --git a/src/landings/components/SidebarProduct.tsx b/src/landings/components/SidebarProduct.tsx index 13d60f687e6a..7c5a96ffb532 100644 --- a/src/landings/components/SidebarProduct.tsx +++ b/src/landings/components/SidebarProduct.tsx @@ -180,6 +180,11 @@ export const SidebarProduct = () => { }, [router.events]) useEffect(() => { + // Skip all sidebar scroll adjustments when the URL carries landing-page + // article filters (search/category/page). Those are shallow same-page + // updates that must not move the reader (the article grid manages its own + // scroll position). + if (/[?&]articles-(filter|category|page)=/.test(router.asPath)) return // Brand NavList auto-expands the whole ancestor chain of the active item, so // scroll to the item marked aria-current="page" (the active article) rather // than the top-most expanded section. diff --git a/src/landings/components/bespoke/BespokeLanding.tsx b/src/landings/components/bespoke/BespokeLanding.tsx index b1d715c528a2..ba23ab1d57bc 100644 --- a/src/landings/components/bespoke/BespokeLanding.tsx +++ b/src/landings/components/bespoke/BespokeLanding.tsx @@ -4,6 +4,7 @@ import { LandingHero } from '@/landings/components/shared/LandingHero' import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter' import { UtmPreserver } from '@/frame/components/UtmPreserver' import { LandingCarousel } from '@/landings/components/shared/LandingCarousel' +import { LandingSection } from '@/landings/components/shared/LandingSection' import { useMultiQueryParams } from '@/search/components/hooks/useMultiQueryParams' export const BespokeLanding = () => { @@ -28,17 +29,15 @@ export const BespokeLanding = () => {
-
- {/* Render carousels */} - {carousels && - Object.entries(carousels).map(([carouselKey, articles]) => ( - - ))} + {/* Render carousels */} + {carousels && + Object.entries(carousels).map(([carouselKey, articles]) => ( + + + + ))} + { params={params} updateParams={updateParams} /> -
+
) diff --git a/src/landings/components/discovery/DiscoveryLanding.tsx b/src/landings/components/discovery/DiscoveryLanding.tsx index bb45b45c56be..4259f585bf10 100644 --- a/src/landings/components/discovery/DiscoveryLanding.tsx +++ b/src/landings/components/discovery/DiscoveryLanding.tsx @@ -5,6 +5,7 @@ import { useLandingContext } from '@/landings/context/LandingContext' import { LandingHero } from '@/landings/components/shared/LandingHero' import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter' import { LandingCarousel } from '@/landings/components/shared/LandingCarousel' +import { LandingSection } from '@/landings/components/shared/LandingSection' import { UtmPreserver } from '@/frame/components/UtmPreserver' import { RestRedirect } from '@/rest/components/RestRedirect' import { useMultiQueryParams } from '@/search/components/hooks/useMultiQueryParams' @@ -32,17 +33,15 @@ export const DiscoveryLanding = () => { {router.query.productId === 'rest' && }
-
- {/* Render carousels */} - {carousels && - Object.entries(carousels).map(([carouselKey, articles]) => ( - - ))} + {/* Render carousels */} + {carousels && + Object.entries(carousels).map(([carouselKey, articles]) => ( + + + + ))} + { params={params} updateParams={updateParams} /> -
+
) diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.module.scss b/src/landings/components/shared/LandingArticleGridWithFilter.module.scss index 7facfe23f6ac..3f100d6bd87d 100644 --- a/src/landings/components/shared/LandingArticleGridWithFilter.module.scss +++ b/src/landings/components/shared/LandingArticleGridWithFilter.module.scss @@ -1,6 +1,13 @@ @import "@primer/css/support/variables/layout.scss"; @import "@primer/css/support/mixins/layout.scss"; +// The section frame (four-sided border) is drawn by the wrapping LandingSection. +// This only owns the internal vertical padding. +.gridSection { + padding-top: 3rem; + padding-bottom: 3rem; +} + .headerTitleText { font-size: var(--h3-size, 1.25rem); font-weight: var(--base-text-weight-semibold, 600); @@ -16,100 +23,122 @@ color: var(--fgColor-muted, var(--color-fg-muted, #656d76)); } -.articleCardBox { - display: flex; - flex-direction: column; - padding: 1.5rem; - min-height: 7.5rem; - box-shadow: - 0 0.0625rem 0.1875rem 0 rgba(31, 35, 40, 0.08), - 0 0.0625rem 0 0 rgba(31, 35, 40, 0.06); - transition: all 0.2s ease-in-out; - cursor: pointer; - text-decoration: none !important; - color: inherit; +// Docs 2026 Articles grid (Figma): a seamless collapsed-border grid, NOT +// separate cards. Cells touch with shared 1px dividers; no per-card radius, +// shadow, or outer top/bottom/side borders. brand Card renders an outer +// wrapper div (Card__outer) that is the actual grid item — so the divider +// nth-child logic must live on `.articleGrid > *`, not on the inner .card +// (which is always :first-child of its own wrapper). +.card { + border-radius: 0 !important; + box-shadow: none !important; + padding: 32px !important; + // Top-align the card content so the tags/title sit at a consistent baseline + // across cards regardless of description length (brand Card otherwise + // distributes rows down the full cell height). + align-content: start !important; + // Green top border on hover, matching the brand NavList active indicator + // (--brand-color-accent-primary). Uses box-shadow (not border) so it overlays + // the seamless grid divider without shifting the 1px collapsed layout. + transition: box-shadow 0.1s ease-in-out; &:hover { - box-shadow: - 0 0.25rem 0.5rem 0 rgba(31, 35, 40, 0.12), - 0 0.125rem 0.25rem 0 rgba(31, 35, 40, 0.08); - transform: translateY(-2px); - background-color: var(--bgColor-muted, var(--color-canvas-subtle)); - text-decoration: none !important; + box-shadow: inset 0 2px 0 0 var(--brand-color-accent-primary) !important; } -} -.cardHeader { - display: flex; - flex-direction: column; -} + // Compress brand Card's generous inter-row margins so cards hug their content + // instead of ballooning. Targets the sub-element classes that merge onto + // Card.Tokens / Heading / Description. Figma tag→content gap is 36px for a + // single tag; shrink it when multiple tags are present (they can wrap and + // already add vertical bulk). + :global([class*="Card__tokens"]) { + margin-bottom: 36px !important; -.cardTitle { - margin: 0 0 0.5rem 0; - font-size: 1.1rem; - font-weight: var(--base-text-weight-semibold, 600); -} + &:has(> :nth-child(2)) { + margin-bottom: 16px !important; + } + } -.cardTitleLink { - color: var(--fgColor-accent); - text-decoration: none; -} + :global([class*="Card__heading"]) { + margin-block-end: 8px !important; + // Figma card title ("Subheading Medium"): 16px / 550, not brand Card's + // default 22px heading. + font-size: 1rem !important; + font-weight: 550 !important; + line-height: 1.5 !important; + } -.cardIntro { - margin: 0; - color: var(--fgColor-muted); - font-size: 0.9rem; - line-height: 1.4; - text-decoration: none !important; + :global([class*="Card__description"]) { + margin-block-end: 0 !important; + // Figma card intro ("Body/Small"): 14px, not brand Card's default 16px. + font-size: 0.875rem !important; + line-height: 1.5 !important; + } } -.tagsContainer { - margin-bottom: 0.5rem; +// Filled inset pill tags (Figma "Forms/Label"). className merges onto each +// Token span inside Card.Tokens. 12px / 500 weight; the Figma pill is 23px tall +// with 8px horizontal padding, centered. +.cardToken { + display: inline-flex !important; + align-items: center !important; + height: 23px !important; + background-color: var(--brand-color-canvas-inset) !important; + border-color: transparent !important; + border-radius: 4px !important; + color: var(--brand-color-text-default) !important; + padding: 0 8px !important; + font-size: 12px !important; + font-weight: 500 !important; + line-height: 150% !important; } .filterHeader { - display: flex; - flex-direction: column; - align-items: stretch; + // Mobile: 2-column grid. Row 1 = title (left) + category dropdown (right, on + // the same row as the heading). Row 2 = full-width search spanning both cols. + display: grid; + // Col 1 (title) takes what it needs; col 2 (category) gets the remaining + // space (1fr) and can shrink to 0 so a long category value ellipsises + // instead of overflowing the row. + grid-template-columns: auto minmax(0, 1fr); + align-items: center; + column-gap: 0.75rem; + row-gap: 1.25rem; border-bottom: 1px solid var(--borderColor-default); padding-bottom: 1rem; - margin-top: 3rem; margin-bottom: 1rem; - gap: 0.75rem; - // Medium screens: horizontal layout with tighter spacing + // Medium screens and up: single row — title on the left, controls on the + // right. @include breakpoint(md) { + display: flex; flex-direction: row; align-items: center; - gap: 0.75rem; - } - - // Large screens: horizontal layout with normal spacing - @include breakpoint(lg) { + justify-content: space-between; gap: 1rem; } } -.titleAndDropdownRow { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; +// `display: contents` on mobile so the category + search become direct grid +// items of .filterHeader (category shares row 1 with the title; search spans +// row 2). On md+ it's a normal right-aligned flex group. +.controls { + display: contents; - // Medium screens and up: maintain tight spacing, don't grow @include breakpoint(md) { + display: flex; + flex-direction: row; + align-items: center; gap: 1rem; - flex-shrink: 0; - flex-grow: 0; } } .headerTitle { flex-shrink: 0; margin: 0; - font-size: 1.5rem; + font-size: 1.25rem; font-weight: 600; + line-height: 2rem; text-align: left; width: auto; @@ -120,82 +149,176 @@ } } +// Category dropdown styled as a text control (Docs 2026 "Sort by: Newest" +// pattern) rather than a bordered button: muted "Category:" label + bold value, +// Action/Small type, transparent background. .categoryDropdown { - min-width: 12rem; - flex: 1; + // Mobile: sit at the right end of row 1 (next to the title) and allow the + // control to shrink so a long category value ellipsises rather than pushing + // the row wider. overflow: hidden clips any button content (e.g. the trailing + // caret) that would otherwise spill past the column edge. + justify-self: end; + min-width: 0; + max-width: 100%; + overflow: hidden; button { - width: fit-content; + max-width: 100%; + min-width: 0 !important; + padding: 0 !important; + border: 0 !important; + background: transparent !important; + box-shadow: none !important; text-align: left !important; + // Primer's ButtonBase sets min-width: max-content on the button and its + // inner content/label spans, preventing the label from shrinking. Force the + // whole track to shrink, and make the label a flex row so the value can + // take the remaining space and ellipsise. span { + min-width: 0 !important; + max-width: 100%; justify-content: start !important; } - // Medium screens: full width but constrained by container - @include breakpoint(md) { - width: 100%; + // The label span holds "Category:" (fixed) + the value (flexible). Make it + // a flex row so the value can take the remaining space and ellipsise. + // Target by class substring (Primer's hashed class is version-specific). + :global([class*="Button-Label"]) { + display: flex; + align-items: baseline; + gap: 0.25rem; + overflow: hidden; } + + // Primer's Button-Content is a grid [label][visual]; let the label track + // shrink so the trailing caret stays inside the button (not pushed past the + // edge by a long, ellipsised value). + :global([class*="Button-Content"]) { + grid-template-columns: minmax(0, auto) auto; + overflow: hidden; + } + } + + @include breakpoint(md) { + flex-shrink: 0; + justify-self: auto; } +} + +// Keep the "Category:" label whole; it should never be the thing that clips. +.categoryLabel { + flex-shrink: 0; + white-space: nowrap; + color: var(--brand-color-text-muted); + font-family: var(--brand-fontStack-sansSerif); + font-size: 0.8125rem; + font-weight: 500; + // Line height must leave room for descenders — a line-height equal to the + // font size clips them (the value span has overflow: hidden for the ellipsis). + line-height: 1.4; + letter-spacing: 0.008125rem; - // Medium screens: smaller min-width and constrained max-width @include breakpoint(md) { - min-width: 20rem; - max-width: 30%; + font-size: 0.875rem; + letter-spacing: 0.00875rem; } +} - // Large screens: larger sizing - @include breakpoint(lg) { - min-width: 20rem; - max-width: 40%; +.categoryValue { + display: block; + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--brand-color-text-default); + font-family: var(--brand-fontStack-sansSerif); + font-size: 0.8125rem; + font-weight: 500; + line-height: 1.4; + letter-spacing: 0.008125rem; + + @include breakpoint(md) { + font-size: 0.875rem; + letter-spacing: 0.00875rem; } } .searchContainer { margin-left: 0; width: auto; + // Mobile: span both grid columns on row 2 (below the title + category row). + grid-column: 1 / -1; input { font-size: 1rem; } - // Medium screens: flexible width with spacing @include breakpoint(md) { - flex: 0 1 25%; - min-width: 12.5rem; - margin-left: auto; - } - - // Large screens: larger width with auto margin - @include breakpoint(lg) { - width: 30%; - flex: 0 0 30%; - margin-left: auto; + width: 17rem; } } .articleGrid { display: grid; - gap: 1.5rem; + // Bleed out to the frame's vertical border rules (cancel the frame's content + // padding) so the card grid dividers reach the border with no gap. Each card's + // own 32px padding then re-insets the text to align with the section header. + margin-inline: -16px; + + @include breakpoint(md) { + margin-inline: -32px; + } // Mobile: 1 column grid-template-columns: 1fr; + // Shared 1px dividers via each grid item's top + left border. The first row's + // top and first column's left are removed per breakpoint so only internal + // dividers show (no outer top/bottom/side borders). + > * { + border-top: 1px solid var(--brand-color-border-default); + } + + // Mobile (1 col): horizontal dividers only; drop the first item's top border. + > *:first-child { + border-top: none; + } + // Tablet: 2 columns @include breakpoint(md) { grid-template-columns: repeat(2, 1fr); + + > * { + border-left: 1px solid var(--brand-color-border-default); + } + + > *:nth-child(2n + 1) { + border-left: none; + } + + > *:nth-child(-n + 2) { + border-top: none; + } } // Desktop: 3 columns @include breakpoint(lg) { grid-template-columns: repeat(3, 1fr); - } -} -.articleCard { - display: flex; - flex-direction: column; - height: 100%; + > *:nth-child(2n + 1) { + border-left: 1px solid var(--brand-color-border-default); + } + + > *:nth-child(3n + 1) { + border-left: none; + } + + > *:nth-child(-n + 3) { + border-top: none; + } + } } .paginationContainer { diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.tsx b/src/landings/components/shared/LandingArticleGridWithFilter.tsx index 2e65957e5cf6..ae03ba057704 100644 --- a/src/landings/components/shared/LandingArticleGridWithFilter.tsx +++ b/src/landings/components/shared/LandingArticleGridWithFilter.tsx @@ -1,11 +1,11 @@ import React, { useState, useRef, useEffect, useMemo } from 'react' -import { TextInput, ActionMenu, ActionList, Token } from '@primer/react' -import { Pagination } from '@primer/react-brand' +import { useRouter } from 'next/router' +import { ActionMenu, ActionList } from '@primer/react' +import { Card, Pagination, TextInput, Token } from '@primer/react-brand' import { SearchIcon } from '@primer/octicons-react' import { announce } from '@primer/live-region-element' import cx from 'classnames' -import { Link } from '@/frame/components/Link' import { useTranslation } from '@/languages/components/useTranslation' import { ChildTocItem, TocItem } from '@/landings/types' import { LandingType } from '@/landings/context/LandingContext' @@ -233,6 +233,32 @@ export const ArticleGrid = ({ prevPageRef.current = currentPage }, [currentPage]) + // Scroll the article grid into view whenever the filter query params change + // (typing in search, choosing a category, or landing on the page with those + // params already set). Debounced so fast typing scrolls once, after the last + // keystroke, rather than on every character. + const prevFilterRef = useRef({ searchQuery, selectedCategory }) + const anchorTimeoutRef = useRef | null>(null) + useEffect(() => { + const prev = prevFilterRef.current + const filtersChanged = + prev.searchQuery !== searchQuery || prev.selectedCategory !== selectedCategory + prevFilterRef.current = { searchQuery, selectedCategory } + if (!filtersChanged) return + + // Debounce: cancel any pending scroll from a prior change, schedule a fresh + // one. Do NOT clear on effect cleanup — cleanup runs on unrelated re-renders + // and would cancel the scroll before it fires. + if (anchorTimeoutRef.current) clearTimeout(anchorTimeoutRef.current) + anchorTimeoutRef.current = setTimeout(() => { + anchorTimeoutRef.current = null + const heading = headingRef.current + if (!heading) return + const offsetPosition = heading.getBoundingClientRect().top + window.scrollY - 140 + window.scrollTo({ top: Math.max(0, offsetPosition), behavior: 'smooth' }) + }, 250) // after the router debounce (100ms) + its scroll-restore + }, [searchQuery, selectedCategory]) + // Announce search/filter no-results to assistive technologies. // Uses @primer/live-region-element which renders a web component // with a shadow DOM on document.body — completely isolated from React's component @@ -253,24 +279,28 @@ export const ArticleGrid = ({ } }, [filteredResults.length, searchQuery, selectedCategory, noArticlesFoundMessage]) return ( -
+
{/* Filter and Search Controls */}
- {/* Title and Dropdown Row */} -
- {/* Title */} -

- {t('article_grid.heading')} -

- - {/* Category Dropdown */} + {/* Title */} +

+ {t('article_grid.heading')} +

+ + {/* Right-aligned controls: category dropdown + search (search last) */} +
+ {/* Category Dropdown — text-style control (Docs 2026 "Sort by" pattern) */}
- {t('article_grid.filter_by_category')}:{' '} - {categories[selectedCategoryIndex] === ALL_CATEGORIES - ? t('article_grid.all_categories') - : categories[selectedCategoryIndex]} + + {t('article_grid.filter_by_category')}: + {' '} + + {categories[selectedCategoryIndex] === ALL_CATEGORIES + ? t('article_grid.all_categories') + : categories[selectedCategoryIndex]} + @@ -287,23 +317,24 @@ export const ArticleGrid = ({
-
- {/* Search */} -
-
e.preventDefault()}> - ) => { - const query = e.target.value || '' - handleSearch(query) - }} - /> - + {/* Search */} +
+
e.preventDefault()}> + } + placeholder={t('article_grid.search_articles')} + aria-label={t('article_grid.search_articles')} + ref={inputRef} + autoComplete="off" + onChange={(e: React.ChangeEvent) => { + const query = e.target.value || '' + handleSearch(query) + }} + /> + +
@@ -356,6 +387,8 @@ type ArticleCardProps = { } const ArticleCard = ({ article, includedCategories }: ArticleCardProps) => { + const router = useRouter() + // Filter categories to only show those in includedCategories (if provided and not empty) const displayCategories = includedCategories && includedCategories.length > 0 && article.category @@ -364,28 +397,45 @@ const ArticleCard = ({ article, includedCategories }: ArticleCardProps) => { ) : article.category + // Brand Card renders its own native anchor (no `as` prop), so intercept plain + // left-clicks to preserve client-side SPA navigation. Modified clicks + // (cmd/ctrl/shift/middle) fall through to the real href for new-tab/right-click. + const handleClick = async (e: React.MouseEvent) => { + if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return + e.preventDefault() + try { + await router.push(article.fullPath) + } catch { + // If the client-side navigation is rejected/aborted, fall back to a hard + // navigation so the card never goes dead (we already suppressed the + // anchor's default). Matters most for keyboard users with no obvious retry. + window.location.href = article.fullPath + } + } + return ( - -
- {displayCategories && - displayCategories.map((cat) => )} -
+ {displayCategories && displayCategories.length > 0 && ( + + {displayCategories.map((cat) => ( + + {cat} + + ))} + + )} -

- {article.title} -

+ {article.title} - {article.intro &&
{article.intro}
} - + {article.intro && {article.intro}} + ) } diff --git a/src/landings/components/shared/LandingCarousel.module.scss b/src/landings/components/shared/LandingCarousel.module.scss index c7664d625608..238c363ecb1a 100644 --- a/src/landings/components/shared/LandingCarousel.module.scss +++ b/src/landings/components/shared/LandingCarousel.module.scss @@ -1,16 +1,6 @@ .carousel { - margin-top: 3rem; - padding-bottom: 3rem; - // Frame the section against the one below it (Docs 2026 "framed sections", - // #6681). Matches the hero band's divider — brand border-subtle adapts - // gray-2 light / gray-6 dark. - border-bottom: 1px solid var(--brand-color-border-subtle); + padding-top: 3rem; --carousel-transition-duration: 0.1s; - - // Subsequent carousels with headings get reduced margin - & + & { - margin-top: 1rem; - } } // Remove top margin for carousels without headings that come after another carousel @@ -38,8 +28,9 @@ } .heading { - font-size: 1.5rem; + font-size: 1.25rem; font-weight: 600; + line-height: 2rem; margin: 0; color: var(--fgColor-default); } @@ -54,30 +45,88 @@ } } +// Prev/next controls: plain 16x16 arrow icon buttons (Docs 2026). No longer the +// old `btn btn-sm` bordered buttons — just the muted arrow glyph in a square +// hit target that tints toward default on hover. .navButton { - min-width: 32px !important; - padding: 6px !important; - border-radius: 6px !important; + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--brand-color-text-muted); + cursor: pointer; + + &:hover:not(:disabled) { + color: var(--brand-color-text-default); + background-color: var(--brand-color-canvas-subtle); + } &:disabled { - cursor: not-allowed !important; - opacity: 0.5 !important; + cursor: not-allowed; + opacity: 0.4; } } .itemsGrid { display: grid; - gap: 1.5rem; grid-template-columns: 1fr; transition: opacity var(--carousel-transition-duration) ease-in-out; opacity: 1; + // Bleed out to the frame's vertical border rules (cancel the frame's content + // padding) so the card dividers reach the border with no gap. Each card's own + // 32px padding then re-insets the text to align with the section header. + margin-inline: -16px; + + @media (min-width: 768px) { + margin-inline: -32px; + } + + // Shared 1px dividers via each grid item's top + left border (matches the + // Articles grid). First row's top and first column's left are removed per + // breakpoint so only internal dividers show. + > * { + border-top: 1px solid var(--brand-color-border-default); + } + + > *:first-child { + border-top: none; + } @media (min-width: 768px) { grid-template-columns: repeat(2, 1fr); + + > * { + border-left: 1px solid var(--brand-color-border-default); + } + + > *:nth-child(2n + 1) { + border-left: none; + } + + > *:nth-child(-n + 2) { + border-top: none; + } } @media (min-width: 1012px) { grid-template-columns: repeat(3, 1fr); + + > *:nth-child(2n + 1) { + border-left: 1px solid var(--brand-color-border-default); + } + + > *:nth-child(3n + 1) { + border-left: none; + } + + > *:nth-child(-n + 3) { + border-top: none; + } } &.animating { @@ -85,45 +134,46 @@ } } -.articleCard { - display: flex; - flex-direction: column; - padding: 24px; - min-height: 120px; /* Ensures consistent card heights */ - box-shadow: - 0px 1px 3px 0px rgba(31, 35, 40, 0.08), - 0px 1px 0px 0px rgba(31, 35, 40, 0.06); - transition: all 0.2s ease-in-out; - cursor: pointer; - text-decoration: none !important; - color: inherit; +// Docs 2026 landing cards: seamless collapsed-border grid (dividers drawn by +// .itemsGrid above), NOT separate rounded cards. Flatten brand Card's radius/ +// shadow, reduce padding to 24px, and replace the stretched `1fr` description +// row with `auto` so cards hug their content instead of ballooning to ~365px. +.card { + border-radius: 0 !important; + box-shadow: none !important; + padding: 32px !important; + --Card-grid-template-rows: auto auto auto auto auto auto auto auto !important; + // Top-align the card content so the tag/title sit at a consistent baseline + // across cards regardless of description length (brand Card otherwise + // distributes rows down the full cell height). + align-content: start !important; + // Green top border on hover, matching the brand NavList active indicator + // (--brand-color-accent-primary). Uses box-shadow (not border) so it overlays + // the seamless grid divider without shifting the 1px collapsed layout. + transition: box-shadow 0.1s ease-in-out; &:hover { - box-shadow: - 0 0.25rem 0.5rem 0 rgba(31, 35, 40, 0.12), - 0 0.125rem 0.25rem 0 rgba(31, 35, 40, 0.08); - transform: translateY(-2px); - background-color: var(--bgColor-muted, var(--color-canvas-subtle)); - text-decoration: none !important; + box-shadow: inset 0 2px 0 0 var(--brand-color-accent-primary) !important; } -} -.articleTitle { - margin: 0 0 0.5rem 0; - font-size: 1.1rem; -} - -.articleLink { - color: var(--fgColor-accent); - text-decoration: none; -} + // Compress brand Card's generous inter-row margins so cards hug their content + // (~150px in Figma) instead of ballooning. Targets the sub-element classes + // that merge onto Card.Heading / Description. + :global([class*="Card__heading"]) { + margin-block-end: 8px !important; + // Figma card title ("Subheading Medium"): 16px / 550, not brand Card's + // default 22px heading. + font-size: 1rem !important; + font-weight: 550 !important; + line-height: 1.5 !important; + } -.articleDescription { - margin: 0; - color: var(--fgColor-muted); - font-size: 0.9rem; - line-height: 1.4; - text-decoration: none !important; + :global([class*="Card__description"]) { + margin-block-end: 0 !important; + // Figma card intro ("Body/Small"): 14px, not brand Card's default 16px. + font-size: 0.875rem !important; + line-height: 1.5 !important; + } } .pagination { diff --git a/src/landings/components/shared/LandingCarousel.tsx b/src/landings/components/shared/LandingCarousel.tsx index 62419e76d265..5ecddd8f5edd 100644 --- a/src/landings/components/shared/LandingCarousel.tsx +++ b/src/landings/components/shared/LandingCarousel.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useRef } from 'react' import { useRouter } from 'next/router' -import { ChevronLeftIcon, ChevronRightIcon } from '@primer/octicons-react' +import { ArrowLeftIcon, ArrowRightIcon } from '@primer/octicons-react' +import { Card } from '@primer/react-brand' import cx from 'classnames' import type { ResolvedArticle } from '@/types' import { useTranslation } from '@/languages/components/useTranslation' @@ -147,19 +148,19 @@ export const LandingCarousel = ({
)} @@ -170,16 +171,19 @@ export const LandingCarousel = ({ data-testid="carousel-items" > {visibleItems.map((article: ResolvedArticle, index) => ( -
-

- {article.title} -

- -
+ {article.title} + + + + ))}
diff --git a/src/landings/components/shared/LandingHero.module.scss b/src/landings/components/shared/LandingHero.module.scss index e2ecbf713ec7..5fb4b15fb017 100644 --- a/src/landings/components/shared/LandingHero.module.scss +++ b/src/landings/components/shared/LandingHero.module.scss @@ -1,33 +1,36 @@ -// Lightweight docs page-header (Docs 2026, #6681). A left-aligned bordered -// band — brand Heading + muted Text intro + a Button group — over the classic -// right-anchored hero banner art. The bottom border (brand border-subtle, which -// adapts gray-2 light / gray-6 dark) frames it against the section below, -// matching the Figma "framed sections" treatment. Horizontal inset comes from -// the wrapping container-xl so the header aligns with the sections below at -// every width. +// Docs 2026 landing hero (Figma node 341:117125). A content column — brand +// Heading + muted lede + a large Button group — with the right-anchored +// isometric banner art on desktop. On mobile the art drops below the content as +// a full-width band (matching the Figma mobile layout). The framing borders are +// drawn by the wrapping LandingSection. .landingHero { display: flex; flex-direction: column; - gap: 1rem; - align-items: flex-start; - padding: 2rem 0 2.5rem; width: 100%; - border-bottom: 1px solid var(--brand-color-border-subtle); - // Hero banner art is right-anchored isometric artwork on a transparent - // field. `auto 100%` scales it to the band's own (content-driven) height so - // the header keeps its lighter Docs 2026 proportions instead of growing to - // fit the art; pinned to the right edge, leaving the left clear for the - // heading/intro/actions stacked on top. + + // Desktop: right-anchored banner art behind the content column. `auto 100%` + // scales it to the band's own (content-driven) height; pinned to the right + // border box so the right-side text gutter insets only the text, not the art. background-size: auto 100%; background-position: center right; background-repeat: no-repeat; - // Anchor the art to the true right edge (the border box) so the right-side - // text gutter added below insets only the heading/intro, not the artwork. background-origin: border-box; } -// Brand `Heading size="2"` owns the type scale (40px desktop, per Figma); -// this only adds an optical max-width so long titles wrap before the edge. +// Content column: heading + lede + actions. Only vertical padding here — the +// horizontal inset comes from the wrapping LandingSection frame so the hero +// content aligns with the other sections' headers/cards. +.heroContent { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 1rem; + padding-block: 2rem; + width: 100%; +} + +// Brand `Heading size="2"` owns the type scale; this only adds an optical +// max-width so long titles wrap before the edge. .heroHeading { margin: 0; max-width: 48rem; @@ -41,13 +44,39 @@ max-width: 48rem; } +// Slight top gap above the actions (Figma "buttons + text" spacing). +.heroButtons { + margin-top: 0.25rem; +} + @media (max-width: 865px) { + // Drop the right-anchored background art; show the image as a band instead. .landingHero { - padding: 1.5rem 0 2rem; - // Drop the banner art on narrow viewports where it would collide with the - // text; the header falls back to the plain bordered band. background-image: none !important; } + + .heroContent { + padding-block: 1.5rem; + } + + // Stack the CTA buttons full-width (Figma mobile layout). Brand ButtonGroup + // renders a
laying children in a row; force a full-width column and + // make each Button fill it. + .heroButtons { + width: 100%; + + section { + display: flex; + flex-direction: column; + align-items: stretch; + width: 100%; + } + + a, + button { + width: 100%; + } + } } // Where the art IS shown, the intro can extend far enough right to overlap the diff --git a/src/landings/components/shared/LandingHero.tsx b/src/landings/components/shared/LandingHero.tsx index 37160993b776..4870022da520 100644 --- a/src/landings/components/shared/LandingHero.tsx +++ b/src/landings/components/shared/LandingHero.tsx @@ -1,6 +1,7 @@ import { LinkExternalIcon } from '@primer/octicons-react' import { Button, ButtonGroup, Heading, Text } from '@primer/react-brand' import styles from './LandingHero.module.scss' +import { LandingSection } from './LandingSection' import { useTranslation } from '@/languages/components/useTranslation' import { RenderedHTML } from '@/frame/components/ui/RenderedHTML/RenderedHTML' @@ -27,42 +28,45 @@ export const LandingHero = ({ title, intro, heroImage, introLinks }: LandingHero const linkEntries = introLinks ? Object.entries(introLinks) : [] return ( -
+
- - {title} - - {intro && ( - - - - )} - {linkEntries.length > 0 && ( - - {linkEntries.map(([label, href], i) => ( - - ))} - - )} +
+ + {title} + + {intro && ( + + + + )} + {linkEntries.length > 0 && ( +
+ + {linkEntries.map(([label, href], i) => ( + + ))} + +
+ )} +
-
+ ) } diff --git a/src/landings/components/shared/LandingSection.module.scss b/src/landings/components/shared/LandingSection.module.scss new file mode 100644 index 000000000000..7df4cffacdff --- /dev/null +++ b/src/landings/components/shared/LandingSection.module.scss @@ -0,0 +1,52 @@ +// Docs 2026 "framed section": horizontal rules span the full content column +// while the vertical side rules are inset by a gutter, framing the section on +// all four sides. brand border-subtle adapts gray-2 light / gray-6 dark. +@import "@primer/css/support/variables/layout.scss"; +@import "@primer/css/support/mixins/layout.scss"; + +// The band spans the full content column and draws the horizontal rules. It +// also holds the minimum horizontal gutter (as padding) so the framed content +// never touches the band edges, and the horizontal rules always extend past the +// vertical ones. Each section draws its own top + bottom, so consecutive +// sections separated by a gap show the paired-rule look from the Figma. +.band { + padding-inline: 16px; + border-top: 1px solid var(--brand-color-border-subtle); + border-bottom: 1px solid var(--brand-color-border-subtle); + + @include breakpoint(md) { + padding-inline: 24px; + } + + & + & { + margin-top: 1.5rem; + + @media (min-width: 768px) { + margin-top: 3rem; + } + } +} + +// The secondary bar above the first section already draws the top divider, so +// the hero band would otherwise double it. Drop the first section's top rule. +.band:first-child { + border-top: 0; +} + +// The frame draws the vertical side rules and holds the content. It is capped at +// container-xl and centered within the band, so on very wide viewports the +// content stays centered rather than hugging the left. Content padding (32px) +// sits inside the border and matches the cards' internal padding so section +// headers/pagination align with the card text; the card grid bleeds back out to +// the border with a negative margin so its dividers reach the vertical rules. +.frame { + max-width: $container-xl; + margin-inline: auto; + padding-inline: 16px; + border-left: 1px solid var(--brand-color-border-subtle); + border-right: 1px solid var(--brand-color-border-subtle); + + @include breakpoint(md) { + padding-inline: 32px; + } +} diff --git a/src/landings/components/shared/LandingSection.tsx b/src/landings/components/shared/LandingSection.tsx new file mode 100644 index 000000000000..ed67e532e14f --- /dev/null +++ b/src/landings/components/shared/LandingSection.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import cx from 'classnames' + +import styles from './LandingSection.module.scss' + +type LandingSectionProps = { + children: React.ReactNode + className?: string +} + +// A Docs 2026 "framed section". The outer band spans the full content column +// and draws the horizontal rules; the inner frame is inset by a gutter and +// draws the vertical side rules, so the horizontal rules always extend past the +// vertical ones. +export const LandingSection = ({ children, className }: LandingSectionProps) => { + return ( +
+
{children}
+
+ ) +} diff --git a/src/landings/pages/home.module.scss b/src/landings/pages/home.module.scss new file mode 100644 index 000000000000..2e385e593710 --- /dev/null +++ b/src/landings/pages/home.module.scss @@ -0,0 +1,25 @@ +// Full-bleed gap between the hero and the "All Docs" section. The hero supplies +// the top border (its own border-bottom); this band adds the responsive height +// and the bottom border, both spanning edge-to-edge. +.sectionGap { + height: 1.5rem; + border-bottom: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); + + @media (min-width: 34rem) { + height: 2rem; + } + + @media (min-width: 63.25rem) { + height: 4rem; + } +} + +// Full-bleed border closing off the bottom of the "All Docs" grid, mirroring the +// gap band at the top. The grid cells only draw rail-width borders, so this +// spans edge-to-edge before the footer. +.sectionEnd { + height: 4rem; + border-top: var(--brand-borderWidth-thin, 1px) solid + var(--brand-color-border-muted); +} diff --git a/src/landings/pages/home.tsx b/src/landings/pages/home.tsx index ac959006c688..d5d4ad0d1acb 100644 --- a/src/landings/pages/home.tsx +++ b/src/landings/pages/home.tsx @@ -9,12 +9,11 @@ import { } from '@/frame/components/context/MainContext' import { DefaultLayout } from '@/frame/components/DefaultLayout' -import { useTranslation } from '@/languages/components/useTranslation' -import { ArticleList } from '@/landings/components/ArticleList' import { HomePageHero } from '@/landings/components/HomePageHero' import type { ProductGroupT } from '@/landings/components/ProductSelections' import { ProductSelections } from '@/landings/components/ProductSelections' import type { ExtendedRequest, FeaturedLinkExpanded } from '@/types' +import styles from './home.module.scss' type FeaturedLink = { href: string @@ -24,56 +23,35 @@ type FeaturedLink = { type Props = { mainContext: MainContextT + // Retained in getServerSideProps so the "Getting started" / "Popular" lists + // can be restored later; the Docs 2026 homepage body is just the grid. popularLinks: Array gettingStartedLinks: Array productGroups: Array } -export default function MainHomePage({ - mainContext, - gettingStartedLinks, - popularLinks, - productGroups, -}: Props) { +export default function MainHomePage({ mainContext, productGroups }: Props) { return ( - + ) } type HomePageProps = { - popularLinks: Array - gettingStartedLinks: Array productGroups: Array } function HomePage(props: HomePageProps) { - const { gettingStartedLinks, popularLinks, productGroups } = props - const { t } = useTranslation(['toc']) + const { productGroups } = props return (
+
-
-
-
-
- -
- -
- -
-
-
-
+
) } diff --git a/src/landings/tests/curated-homepage-links.ts b/src/landings/tests/curated-homepage-links.ts deleted file mode 100644 index c469743bcd3f..000000000000 --- a/src/landings/tests/curated-homepage-links.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, expect, test, vi } from 'vitest' -import type { Element } from 'domhandler' - -import { getDOM } from '@/tests/helpers/e2etest' - -describe('curated homepage links', () => { - vi.setConfig({ testTimeout: 3 * 60 * 1000 }) - - test('English', async () => { - const $ = await getDOM('/en') - - // Update selector to find actual link elements within article list - const $links = $('[data-testid=article-list] a') - expect($links.length).toBeGreaterThanOrEqual(6) - - // Check that each link is localized and includes a title and intro - $links.each((i: number, el: Element) => { - const linkUrl = $(el).attr('href') as string - - expect(linkUrl.startsWith('/en/')).toBe(true) - expect( - $(el).find('[data-testid=link-with-intro-title]').text().trim().length, - `Did not find a title for the linked article ${linkUrl}`, - ).toBeGreaterThan(0) - expect( - $(el).find('[data-testid=link-with-intro-intro]').text().trim().length, - `Did not find an intro for the linked article ${linkUrl}`, - ).toBeGreaterThan(0) - - // ensure there's no unwanted nested HTML - expect($(el).find('p').length).toBe(1) - expect($(el).find('a').length).toBe(0) - expect($(el).find('p p').length).toBe(0) - }) - }) -}) diff --git a/src/landings/tests/featured-links.ts b/src/landings/tests/featured-links.ts index 75ee4203a7ba..bb05cff07f97 100644 --- a/src/landings/tests/featured-links.ts +++ b/src/landings/tests/featured-links.ts @@ -10,39 +10,30 @@ describe('featuredLinks', () => { expect($('[data-testid=article-list]')).toHaveLength(0) }) - test('landing page intro links have expected properties', async () => { - const $ = await getDOM('/en') - const $featuredLinks = $('[data-testid=article-list] a') - expect($featuredLinks).toHaveLength(7) - expect($featuredLinks.eq(0).attr('href')).toBe('/en/get-started/start-your-journey/hello-world') - expect($featuredLinks.eq(0).find('[data-testid=link-with-intro-title]').text()).toMatch( - 'Hello World', - ) - expect($featuredLinks.eq(0).find('[data-testid=link-with-intro-intro]').text()).toMatch( - /follow.+this.+hello.+world.+exercise/i, - ) - }) - test('Enterprise get-started landing renders', async () => { const $ = await getDOM('/en/enterprise-server@latest/get-started') // get-started uses discovery-landing, so it has hero/spotlight, not article-list. expect($('h1').text()).toMatch(/Getting started/) }) +}) + +describe('homepage', () => { + vi.setConfig({ testTimeout: 60 * 1000 }) + + test('renders the hero search entry that opens the overlay', async () => { + const $ = await getDOM('/en') + const $search = $('[data-testid=homepage-search]') + expect($search).toHaveLength(1) + // The redesigned homepage no longer renders the featured article lists. + expect($('[data-testid=article-list]')).toHaveLength(0) + }) - // This is an important test because one of the popular links, - // in the front matter of `src/fixtures/fixtures/content/index.md`, uses - // Liquid to conditionally include with `{% ifversion ghec %}`. - test.each(['', '/enterprise-cloud@latest'])( - 'never more than 4 links per category in %a', - async (version) => { - const $ = await getDOM(`/en${version}`) - const columns = $('[data-testid=article-list]') - expect(columns.length).toBe(2) - for (const column of columns) { - const $featuredLinks = $('a', column) - // See MAX_FEATURED_LINKS constant in featured-links.ts middleware - expect($featuredLinks.length).toBeLessThanOrEqual(4) - } - }, - ) + test('renders the "All docs" product grid with links', async () => { + const $ = await getDOM('/en') + const $grid = $('[data-testid=product]') + expect($grid).toHaveLength(1) + // Category group headings and their product links. + expect($grid.find('h3').length).toBeGreaterThan(0) + expect($grid.find('a').length).toBeGreaterThan(0) + }) }) diff --git a/src/secret-scanning/data/pattern-docs/fpt/public-docs.yml b/src/secret-scanning/data/pattern-docs/fpt/public-docs.yml index bb6a5301786b..82bedb517ff9 100644 --- a/src/secret-scanning/data/pattern-docs/fpt/public-docs.yml +++ b/src/secret-scanning/data/pattern-docs/fpt/public-docs.yml @@ -3261,7 +3261,7 @@ isPrivateWithGhas: true hasPushProtection: true hasValidityCheck: true - hasExtendedMetadata: false + hasExtendedMetadata: '{% ifversion ghes %}false{% else %}true{% endif %}' base64Supported: false isduplicate: false - provider: MongoDB diff --git a/src/secret-scanning/data/pattern-docs/ghec/public-docs.yml b/src/secret-scanning/data/pattern-docs/ghec/public-docs.yml index bb6a5301786b..82bedb517ff9 100644 --- a/src/secret-scanning/data/pattern-docs/ghec/public-docs.yml +++ b/src/secret-scanning/data/pattern-docs/ghec/public-docs.yml @@ -3261,7 +3261,7 @@ isPrivateWithGhas: true hasPushProtection: true hasValidityCheck: true - hasExtendedMetadata: false + hasExtendedMetadata: '{% ifversion ghes %}false{% else %}true{% endif %}' base64Supported: false isduplicate: false - provider: MongoDB