From 5001210b77d201d18d352d51fc68bfaf12cdf00d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:56:26 +0000 Subject: [PATCH 01/29] chore: start workshop paths restructure Agent-Logs-Url: https://github.com/github-samples/agents-in-sdlc/sessions/fe23a7c4-299e-4150-9215-ce55e8e0682e Co-authored-by: GeekTrainer <6109729+GeekTrainer@users.noreply.github.com> --- client/package-lock.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 6011fa3d..1bb87027 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1633,7 +1633,6 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", @@ -2172,7 +2171,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2325,7 +2323,6 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.5.tgz", "integrity": "sha512-A56u4H6gFHEb0yRHcGTOADBb7jmEwfDjQpkqVV/Z+ZWlu6mYuwCrIcOUtZjNno0chrRKmOeZWDofW23ql18y3w==", "license": "MIT", - "peer": true, "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.7.4", @@ -2561,7 +2558,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5291,7 +5287,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5669,7 +5664,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.1.tgz", "integrity": "sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -5935,7 +5929,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.5.tgz", "integrity": "sha512-HQoZArIewxQVNedseDsgMgnRSC4XOXczxXLF9rOJaPIJkg58INOPUiL8aEtzqZIXNSZJyw8NmqObwg/voajiHQ==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -6108,7 +6101,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6497,7 +6489,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -7109,7 +7100,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 4b7be424d1d29ca413b6376f7a451b153c31d856 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:09:24 +0000 Subject: [PATCH 02/29] feat: restructure workshop-content into multi-path layout (CLI, VS Code, Cloud, Complete) Agent-Logs-Url: https://github.com/github-samples/agents-in-sdlc/sessions/fe23a7c4-299e-4150-9215-ce55e8e0682e Co-authored-by: GeekTrainer <6109729+GeekTrainer@users.noreply.github.com> --- workshop-content/README.md | 91 +++++-- workshop-content/cli/2-install-copilot-cli.md | 134 ++++++++++ workshop-content/cli/3-mcp.md | 124 ++++++++++ workshop-content/cli/4-generating-code.md | 84 +++++++ workshop-content/cli/5-agent-skills.md | 113 +++++++++ workshop-content/cli/6-custom-agents.md | 112 +++++++++ workshop-content/cli/7-slash-commands.md | 137 +++++++++++ workshop-content/cli/8-review.md | 72 ++++++ workshop-content/cli/README.md | 44 ++++ workshop-content/cloud/2-coding-agent.md | 205 ++++++++++++++++ workshop-content/cloud/3-custom-agents.md | 94 +++++++ workshop-content/cloud/4-managing-agents.md | 92 +++++++ workshop-content/cloud/5-iterating.md | 173 +++++++++++++ workshop-content/cloud/README.md | 37 +++ workshop-content/shared/0-prereqs.md | 68 ++++++ .../shared/1-custom-instructions.md | 193 +++++++++++++++ workshop-content/vscode/2-mcp.md | 230 ++++++++++++++++++ workshop-content/vscode/3-agent-mode.md | 224 +++++++++++++++++ workshop-content/vscode/4-coding-agent.md | 205 ++++++++++++++++ workshop-content/vscode/5-custom-agents.md | 94 +++++++ workshop-content/vscode/6-managing-agents.md | 92 +++++++ workshop-content/vscode/7-iterating.md | 173 +++++++++++++ workshop-content/vscode/README.md | 41 ++++ 23 files changed, 2817 insertions(+), 15 deletions(-) create mode 100644 workshop-content/cli/2-install-copilot-cli.md create mode 100644 workshop-content/cli/3-mcp.md create mode 100644 workshop-content/cli/4-generating-code.md create mode 100644 workshop-content/cli/5-agent-skills.md create mode 100644 workshop-content/cli/6-custom-agents.md create mode 100644 workshop-content/cli/7-slash-commands.md create mode 100644 workshop-content/cli/8-review.md create mode 100644 workshop-content/cli/README.md create mode 100644 workshop-content/cloud/2-coding-agent.md create mode 100644 workshop-content/cloud/3-custom-agents.md create mode 100644 workshop-content/cloud/4-managing-agents.md create mode 100644 workshop-content/cloud/5-iterating.md create mode 100644 workshop-content/cloud/README.md create mode 100644 workshop-content/shared/0-prereqs.md create mode 100644 workshop-content/shared/1-custom-instructions.md create mode 100644 workshop-content/vscode/2-mcp.md create mode 100644 workshop-content/vscode/3-agent-mode.md create mode 100644 workshop-content/vscode/4-coding-agent.md create mode 100644 workshop-content/vscode/5-custom-agents.md create mode 100644 workshop-content/vscode/6-managing-agents.md create mode 100644 workshop-content/vscode/7-iterating.md create mode 100644 workshop-content/vscode/README.md diff --git a/workshop-content/README.md b/workshop-content/README.md index 480e4d92..e4acfa98 100644 --- a/workshop-content/README.md +++ b/workshop-content/README.md @@ -2,30 +2,91 @@ The recent additions to the capabilities of GitHub Copilot provide powerful tools to the developer across the entire software development lifecycle (SDLC). This includes working with issues and pull requests on GitHub, interacting with external services, and of course code creation. This lab explores the functionality, providing real-world use cases and tips on how to get the most out of the tools. -## Lab overview - > [!IMPORTANT] -> Because GitHub Copilot, and generative AI at large, is probabilistic rather than deterministic, the exact code, files changed, etc., may vary. As a result, you may notice slight difference between screenshots and code snippets in the lab and your experience. This is to be expected, and is just the nature of working with this class of tools. +> Because GitHub Copilot, and generative AI at large, is probabilistic rather than deterministic, the exact code, files changed, etc., may vary. As a result, you may notice slight differences between screenshots and code snippets in the lab and your experience. This is to be expected, and is just the nature of working with this class of tools. > > If something appears broken or isn't running correctly, please ask a mentor! -These labs will walk you through the most common workloads with the agent capabilities of GitHub Copilot. +## Choose your learning path + +This workshop offers multiple paths depending on how you want to interact with GitHub Copilot. All paths begin with the same shared setup and custom instructions exercises before branching into tool-specific content. + +### πŸ–₯️ [VS Code Path](./vscode/README.md) + +Focused on GitHub Copilot features within **Visual Studio Code** and GitHub Codespaces. Covers Copilot Chat agent mode, MCP integration, the Copilot coding agent, and custom agents β€” all from your IDE. + +**Best for**: Developers who prefer IDE-based workflows. + +| # | Exercise | +|---|---------| +| 0 | [Prerequisites](./shared/0-prereqs.md) | +| 1 | [Custom instructions](./shared/1-custom-instructions.md) | +| 2 | [MCP with VS Code](./vscode/2-mcp.md) | +| 3 | [Agent mode](./vscode/3-agent-mode.md) | +| 4 | [Coding agent](./vscode/4-coding-agent.md) | +| 5 | [Custom agents](./vscode/5-custom-agents.md) | +| 6 | [Managing agents](./vscode/6-managing-agents.md) | +| 7 | [Iterating on Copilot's work](./vscode/7-iterating.md) | + +--- + +### πŸ’» [CLI Path](./cli/README.md) + +Focused on **GitHub Copilot CLI** β€” a powerful agentic assistant that runs in your terminal. Covers installation, MCP, code generation with plan mode, agent skills, custom agents, and slash commands. + +**Best for**: Developers who live in the terminal and want AI assistance without leaving the command line. + +| # | Exercise | +|---|---------| +| 0 | [Prerequisites](./shared/0-prereqs.md) | +| 1 | [Custom instructions](./shared/1-custom-instructions.md) | +| 2 | [Install Copilot CLI](./cli/2-install-copilot-cli.md) | +| 3 | [MCP with CLI](./cli/3-mcp.md) | +| 4 | [Generating code](./cli/4-generating-code.md) | +| 5 | [Agent skills](./cli/5-agent-skills.md) | +| 6 | [Custom agents](./cli/6-custom-agents.md) | +| 7 | [Slash commands](./cli/7-slash-commands.md) | +| 8 | [Review](./cli/8-review.md) | + +--- + +### ☁️ [Cloud / Coding Agent Path](./cloud/README.md) + +Focused on **Copilot coding agent** β€” the asynchronous peer programmer that works on GitHub issues in the background. Covers assigning issues, custom agents, monitoring with the agents dashboard, and reviewing generated work. + +**Best for**: Developers who want to offload tasks and let Copilot work asynchronously. + +> [!NOTE] +> Requires **Copilot Pro+, Business, or Enterprise** with coding agent enabled. + +| # | Exercise | +|---|---------| +| 0 | [Prerequisites](./shared/0-prereqs.md) | +| 1 | [Custom instructions](./shared/1-custom-instructions.md) | +| 2 | [Copilot coding agent](./cloud/2-coding-agent.md) | +| 3 | [Custom agents](./cloud/3-custom-agents.md) | +| 4 | [Managing agents](./cloud/4-managing-agents.md) | +| 5 | [Iterating on Copilot's work](./cloud/5-iterating.md) | + +--- + +### πŸ—ΊοΈ Complete Path + +Want to explore everything? Work through all three paths to experience the full breadth of GitHub Copilot's agent capabilities. + +**Recommended order:** -0. [Setup the environment](./0-prereqs.md). -1. [Configure and interact with external services](./1-mcp.md) through Model Context Protocol (MCP). -2. [Provide context to Copilot](./2-custom-instructions.md) through the use of custom instructions, prompt files, and chat participants. -3. [Complete a site-wide update](./3-copilot-agent-mode-vscode.md) with the help of Copilot agent mode. -4. [Assign issues to GitHub Copilot coding agent](./4-copilot-coding-agent.md) to allow Copilot to work on tasks asynchronously. -5. [Create and use custom agents](./5-custom-agents.md) to provide specialized guidance for specific tasks. -6. [Manage agents](./6-managing-agents.md) to control who has access to your agents and how they're used. -7. [Iterate on Copilot's work](./7-iterating-copilot-work.md) to refine and improve the generated code. +1. **Shared setup**: [Prerequisites](./shared/0-prereqs.md) β†’ [Custom instructions](./shared/1-custom-instructions.md) +2. **VS Code**: [MCP](./vscode/2-mcp.md) β†’ [Agent mode](./vscode/3-agent-mode.md) +3. **CLI**: [Install CLI](./cli/2-install-copilot-cli.md) β†’ [MCP](./cli/3-mcp.md) β†’ [Generating code](./cli/4-generating-code.md) β†’ [Agent skills](./cli/5-agent-skills.md) β†’ [Slash commands](./cli/7-slash-commands.md) +4. **Cloud**: [Coding agent](./cloud/2-coding-agent.md) β†’ [Custom agents](./cloud/3-custom-agents.md) β†’ [Managing agents](./cloud/4-managing-agents.md) β†’ [Iterating](./cloud/5-iterating.md) -## Conclusion +--- ## Scenario -You are a new developer for Tailspin Toys, a fictional company who provides crowdfunding for boardgames with a DevOps theme - a huge market! You are tasked with creating issues to document the desired updates to the application and DevOps flow, then implementing the ability to filter games by both category and publisher. You'll work iteratively, exploring both the site and Copilot's capabilities, to complete the tasks. +You are a new developer for Tailspin Toys, a fictional company who provides crowdfunding for board games with a developer theme - a huge market! You are tasked with creating issues to document the desired updates to the application and implementing the ability to filter games by both category and publisher. You'll work iteratively, exploring both the site and Copilot's capabilities, to complete the tasks. ## Get started -OK, let's [get going by starting with the setup](./0-prereqs.md)! +Choose your path above and start with [Exercise 0: Prerequisites](./shared/0-prereqs.md)! diff --git a/workshop-content/cli/2-install-copilot-cli.md b/workshop-content/cli/2-install-copilot-cli.md new file mode 100644 index 00000000..8c734164 --- /dev/null +++ b/workshop-content/cli/2-install-copilot-cli.md @@ -0,0 +1,134 @@ +# Exercise 2 - Installing GitHub Copilot CLI + +| [← Previous lesson: Custom Instructions][previous-lesson] | [Next lesson: MCP Servers β†’][next-lesson] | +|:--|--:| + +[GitHub Copilot CLI][about-copilot-cli] is a powerful agentic coding assistant that runs in your terminal, enabling you to explore codebases, generate code, run commands, and interact with external tools - all from the command line. + +## Scenario + +Tailspin Toys is a nascent organization with a website that's lacking in many features. Their backlog is continuing to grow, and there's a strong demand to grow. To aid the developers, they want to begin utilizing AI agents through Copilot CLI. This will allow developers to be more productive, as they can focus on the bigger picture while moving faster. The first step to doing this is, of course, to install Copilot CLI! + +In this exercise, you will learn how to: + +- install GitHub Copilot CLI using npm. +- authenticate with your GitHub account. +- verify the installation. + +## Open a terminal in your codespace + +Before installing Copilot CLI, you need to open a terminal window in VS Code. + +1. Return to your codespace if you're not already there. +2. Open a terminal window by pressing Ctrl+\`. +3. You should see a terminal panel appear at the bottom of your VS Code window. + +## Install Copilot CLI + +GitHub Codespaces come with Node.js pre-installed, so you can use npm to install Copilot CLI globally. + +1. In the terminal, verify Node.js is installed and meets the version requirement: + + ```bash + node --version + ``` + + You should see version 22 or higher (e.g., `v22.x.x`). + +2. Install Copilot CLI globally using npm: + + ```bash + npm install -g @github/copilot + ``` + +3. Verify the installation by checking the version: + + ```bash + copilot --version + ``` + + You should see the version number displayed (e.g., `v0.0.393`). + +> [!TIP] +> If you encounter permission errors, you may need to use `sudo npm install -g @github/copilot` on some systems. However, this shouldn't be necessary in GitHub Codespaces. + +## Authenticate with GitHub + +On first launch, Copilot CLI will prompt you to authenticate with your GitHub account. + +1. Start Copilot CLI: + + ```bash + copilot + ``` + +2. If you're not currently logged in, you'll see a prompt to authenticate. Copilot CLI will display a device code and ask you to visit a URL. +3. Follow the on-screen instructions: + - Open the provided URL in your browser + - Enter the device code when prompted + - Authorize Copilot CLI to access your GitHub account +4. Once authenticated, you'll see the Copilot CLI prompt, ready to accept your questions and commands. + +> [!NOTE] +> In a codespace, you may already be authenticated through your GitHub session. If Copilot CLI starts without prompting for authentication, you're good to go! + +## Trust the directory + +When you first use Copilot CLI in a directory, it will ask you to confirm that you trust the files in that folder. This is a security feature to prevent Copilot from accidentally working with untrusted code. + +1. When prompted, you'll see three options: + - **Yes, proceed**: Trust for this session only + - **Yes, and remember this folder for future sessions**: Trust permanently + - **No, exit (Esc)**: Don't allow file access +2. For this workshop, select **Yes, and remember this folder for future sessions** since you'll be working in this repository throughout. + +## Verify everything is working + +Let's make sure Copilot CLI is properly installed and connected. + +1. If you exited Copilot CLI, start it again: + + ```bash + copilot + ``` + +2. Ask Copilot a simple question to verify it's working: + + ``` + What files are in this project? + ``` + +3. Copilot should explore the repository and provide a summary of the project structure. +4. Try the `/help` command to see available slash commands: + + ``` + /help + ``` + +## Summary and next steps + +Congratulations! You've successfully installed and authenticated GitHub Copilot CLI. You learned how to: + +- install Copilot CLI using npm. +- authenticate with your GitHub account. +- trust a directory for Copilot CLI to work with. +- verify the installation is working correctly. + +Now that Copilot CLI is installed, let's start using it with external services through MCP! Continue to [Exercise 3 - MCP Servers][next-lesson]. + +## Resources + +- [Installing GitHub Copilot CLI][install-copilot-cli] +- [About Copilot CLI][about-copilot-cli] +- [Using Copilot CLI][using-copilot-cli] + +--- + +| [← Previous lesson: Custom Instructions][previous-lesson] | [Next lesson: MCP Servers β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ../shared/1-custom-instructions.md +[next-lesson]: ./3-mcp.md +[install-copilot-cli]: https://docs.github.com/copilot/how-tos/set-up/install-copilot-cli +[about-copilot-cli]: https://docs.github.com/copilot/concepts/agents/about-copilot-cli +[using-copilot-cli]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli diff --git a/workshop-content/cli/3-mcp.md b/workshop-content/cli/3-mcp.md new file mode 100644 index 00000000..0ec13894 --- /dev/null +++ b/workshop-content/cli/3-mcp.md @@ -0,0 +1,124 @@ +# Exercise 3 - MCP Servers with GitHub Copilot CLI + +| [← Previous lesson: Installing Copilot CLI][previous-lesson] | [Next lesson: Generating Code β†’][next-lesson] | +|:--|--:| + +There's more to writing code than just writing code. Issues need to be filed, external services need to be called, and information needs to be gathered. Typically this involves interacting with external tools, which can break a developer's flow. Through the power of Model Context Protocol (MCP), you can access all of this functionality right from Copilot CLI! + +## Scenario + +You are a part-time developer for Tailspin Toys - a crowdfunding platform for board games with a developer theme. You've been assigned various tasks to introduce new functionality to the website. Being a good team member, you want to file issues to track your work. To help future you, you've decided to enlist the help of Copilot. You will set up your backlog of work for the rest of the lab, using GitHub Copilot CLI and the GitHub Model Context Protocol (MCP) server to create the issues for you. + +In this exercise, you will: + +- understand what Model Context Protocol (MCP) is and how it works with Copilot CLI. +- set up the GitHub MCP server in your repository. +- use GitHub Copilot CLI to create issues in your repository. + +By the end of this exercise, you will have created a backlog of GitHub issues for use throughout the remainder of the lab. + +## What is Model Context Protocol (MCP)? + +[Model Context Protocol (MCP)][mcp-blog-post] provides AI agents with a way to communicate with external tools and services. By using MCP, AI agents can communicate with external tools and services in real-time. This allows them to access up-to-date information (using resources) and perform actions on your behalf (using tools). + +These tools and resources are accessed through an MCP server, which acts as a bridge between the AI agent and the external tools and services. The MCP server is responsible for managing the communication between the AI agent and the external tools (such as existing APIs or local tools like NPM packages). Each MCP server represents a different set of tools and resources that the AI agent can access. + +A couple of popular existing MCP servers are: + +- **[GitHub MCP Server][github-mcp-server]**: This server provides access to a set of APIs for managing your GitHub repositories. It allows the AI agent to perform actions such as creating new repositories, updating existing ones, and managing issues and pull requests. +- **[Playwright MCP Server][playwright-mcp-server]**: This server provides browser automation capabilities using Playwright. It allows the AI agent to perform actions such as navigating to web pages, filling out forms, and clicking buttons. + +There are many other MCP servers available that provide access to different tools and resources. GitHub hosts an [MCP registry][mcp-registry] to enhance discoverability and contributions to the ecosystem. + +> [!IMPORTANT] +> With regard to security, treat MCP servers as you would any other dependency in your project. Before using an MCP server, carefully review its source code, verify the publisher, and consider the security implications. Only use MCP servers that you trust and be cautious about granting access to sensitive resources or operations. + +## Setting up the GitHub MCP server in Copilot CLI + +Copilot CLI supports MCP servers via a configuration file at **~/.copilot/mcp.json**. Additionally, in your project the **.vscode/mcp.json** file can configure MCP servers for VS Code-based workflows, and Copilot CLI also reads from this file. + +1. Return to your codespace. +2. Open a terminal window by pressing Ctrl+\`. +3. Start Copilot CLI: + + ```bash + copilot + ``` + +4. Register the GitHub MCP server in Copilot CLI using the `/mcp` command: + + ``` + /mcp add github https://api.githubcopilot.com/mcp/ + ``` + +5. Copilot CLI will prompt you to authenticate with GitHub. Follow the on-screen instructions to authorize. +6. Once authentication is complete, confirm the server is registered: + + ``` + /mcp list + ``` + +You should see the GitHub MCP server listed and marked as active. + +## Creating a backlog of tasks + +Now that you have set up the GitHub MCP server, you can use Copilot CLI to create a backlog of tasks for use in the rest of the lab. + +1. In the Copilot CLI prompt, type or paste the following prompt to create the issues you'll be working on in the lab: + + ```markdown + In my GitHub repo, create GitHub issues for our Tailspin Toys backlog. Each issue should include: + - A clear title + - A brief description of the task and why it is important to the project + - A checkbox list of acceptance criteria + + From our recent planning meeting, the upcoming backlog includes the following tasks: + + 1. Allow users to filter games by category and publisher + 2. Update our repository coding standards (including rules about Python formatting and docstrings) in a custom instructions file + 3. Stretch Goal: Implement pagination on the game list page + ``` + +2. Press Enter to send the prompt to Copilot. +3. Copilot will ask you to confirm any write operations before proceeding. Review the issue details and confirm each one. + +> [!IMPORTANT] +> Remember, AI can make mistakes, so make sure to review the issues before confirming. + +4. In a separate browser tab, navigate to your GitHub repository and select the **Issues** tab. +5. You should see a list of issues that have been created by Copilot. Each issue should include a clear title and a checkbox list of acceptance criteria. + +## Summary and next steps + +Congratulations, you have created issues on GitHub using Copilot CLI and MCP! + +To recap, in this exercise you: + +- understood what Model Context Protocol (MCP) is and how it works with Copilot CLI. +- set up the GitHub MCP server in your repository. +- used GitHub Copilot CLI to create issues in your repository. + +You can now continue to the next exercise, where you will [add project features with GitHub Copilot CLI][next-lesson]. + +### Optional exploration exercise – Set up the Microsoft Playwright MCP server + +If you are feeling adventurous, you can try installing and configuring another MCP server, such as the [Microsoft Playwright MCP server][playwright-mcp-server]. This will allow you to use Copilot CLI to perform browser automation tasks, such as navigating to web pages, filling out forms, and clicking buttons. + +## Resources + +- [What the heck is MCP and why is everyone talking about it?][mcp-blog-post] +- [GitHub MCP Server][github-mcp-server] +- [Microsoft Playwright MCP Server][playwright-mcp-server] +- [GitHub MCP Registry][mcp-registry] + +--- + +| [← Previous lesson: Installing Copilot CLI][previous-lesson] | [Next lesson: Generating Code β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ./2-install-copilot-cli.md +[next-lesson]: ./4-generating-code.md +[mcp-blog-post]: https://github.blog/ai-and-ml/llms/what-the-heck-is-mcp-and-why-is-everyone-talking-about-it/ +[github-mcp-server]: https://github.com/github/github-mcp-server +[playwright-mcp-server]: https://github.com/microsoft/playwright-mcp +[mcp-registry]: https://github.com/mcp diff --git a/workshop-content/cli/4-generating-code.md b/workshop-content/cli/4-generating-code.md new file mode 100644 index 00000000..ec0fcfa3 --- /dev/null +++ b/workshop-content/cli/4-generating-code.md @@ -0,0 +1,84 @@ +# Exercise 4 - Adding project features with GitHub Copilot CLI + +| [← Previous lesson: MCP Servers][previous-lesson] | [Next lesson: Agent Skills β†’][next-lesson] | +|:--|--:| + +As you might expect, the core tasks you'll perform with GitHub Copilot CLI is to add features, functionality, and code to a project. As you've already explored, you can add instructions files and MCP servers to help guide Copilot and ensure you're getting the code you expect, following the best practices laid out by the team and community. Let's take one of the issues we generated previously and ask Copilot to help us implement it. + +## Scenario + +The time has come to finally implement filtering in the project. You've already got the issue in GitHub. Let's have Copilot retrieve the details from the issue and put together a plan to implement it. Then we'll get Copilot on the job to create the code and run the tests. + +In this exercise, you will: + +- utilize plan mode to generate a plan for implementing the filtering functionality. +- generate the code necessary to add filtering to the website with Copilot. + +By the end of this exercise, you will have added new functionality to the project. + +## Utilize plan mode + +One of the best uses of AI is planning. Oftentimes you'll have a good concept of what you want to build, but just need to bounce some ideas off of something. AI tools can help you crystalize your thoughts by asking you follow up questions and working through different pitfalls or missing components. To support this process, Copilot CLI offers a plan mode. + +You'll start the process of creating the new functionality by utilizing plan mode in Copilot CLI. + +1. Return to your codespace. +2. If not already open, open a terminal window by utilizing Ctrl+\`. +3. If not already running, start Copilot CLI by issuing the following command in the terminal window: + + ```bash + copilot --allow-all-tools + ``` + +4. If already running, clear Copilot's context by sending the `/clear` command in the prompt. +5. Switch Copilot CLI into plan mode by selecting Shift+Tab until you see **Plan mode** just below the prompt window. +6. Enter the following prompt into Copilot CLI to have it retrieve the issue from your repository and put forth a plan for implementing the functionality: + + ``` + Retrieve the issue on the repository related to adding filtering. Help me build a good plan to implement this functionality. + ``` + +7. Copilot may ask follow-up questions as it builds out its plan. As those arise, answer them based on how you'd build out the functionality. +8. Once the plan is generated, review the blueprint. You should notice it recommends changes to the backend and frontend, as well as generating tests. You can utilize Ctrl+Y to view the full details as a markdown file in VS Code. +9. If you wish to make any suggestions to the plan Copilot generated, feel free to do so! +10. Once you're satisfied, switch out of plan mode by selecting Shift+Tab. +11. Tell Copilot to start the work by sending a `start` prompt (or another similar phrase like "Let's do it!") to Copilot. +12. Copilot will get to work generating the files! + +> [!NOTE] +> This operation will likely take several minutes. You will see Copilot edit and create files, update and generate tests, and run all of the tests to ensure everything succeeds. Now's a good time to reflect on what you've explored thus far, or to enjoy a beverage. + +## Review the code + +All AI code needs to be reviewed before being merged into production. Let's take the time now to explore the files Copilot created and modified in implementing the new feature. + +1. Hide the terminal window in your codespace by selecting Ctrl+\`. +2. Select **Source Control** in your codespace. +3. Note the files changed. You should see updates to files such as **games.py**, the Games API, and **test_games.py**, the tests for that API. You should also see new files created, such as Svelte components for the new filter functionality, and Playwright tests to validate the frontend. +4. Open the files and explore the changes. In particular, notice the comment sections which have been added. All of this comes from the instructions files you worked on previously in this workshop. + +## Summary and next steps + +You've now added filtering functionality to the website with the help of Copilot CLI! Specifically, you: + +- utilized plan mode to generate a plan for implementing the filtering functionality. +- generated the code necessary to add filtering to the website with Copilot. + +Of course, the next step from here is to [create the PR][next-lesson], which we'll do with the help of a skill. + +## Resources + +- [Using Copilot CLI][using-copilot-cli] +- [About Copilot CLI][about-copilot-cli] +- [Context management in Copilot CLI][context-management] + +--- + +| [← Previous lesson: MCP Servers][previous-lesson] | [Next lesson: Agent Skills β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ./3-mcp.md +[next-lesson]: ./5-agent-skills.md +[using-copilot-cli]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli +[about-copilot-cli]: https://docs.github.com/copilot/concepts/agents/about-copilot-cli +[context-management]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#context-management diff --git a/workshop-content/cli/5-agent-skills.md b/workshop-content/cli/5-agent-skills.md new file mode 100644 index 00000000..f92674d2 --- /dev/null +++ b/workshop-content/cli/5-agent-skills.md @@ -0,0 +1,113 @@ +# Exercise 5 - Using agent skills + +| [← Previous lesson: Generating Code][previous-lesson] | [Next lesson: Custom Agents β†’][next-lesson] | +|:--|--:| + +Agent Skills are folders of instructions, scripts, and resources that Copilot can load when relevant to improve its performance in specialized tasks. [Agent Skills is an open standard][agent-skills-repo], used by a range of different agents. Agent skills works in Copilot Chat in Agent Mode, Copilot Coding Agent (CCA), and Copilot CLI. + +Let's explore skills, how they're utilized by AI agents, and how we can use a skill to ensure actions are performed according to the specifications set forth by our team. + +## Scenario + +You are a part-time developer for Tailspin Toys - a crowdfunding platform for board games with a developer theme. The team has a set of requirements for pull requests (PR): + +- clear commit messages, with files grouped logically. +- all tests must pass before a PR is created. +- each PR must contain the following sections: + - a description of why the changes were made. + - an overview of the files changed. + - snippets of important code blocks. + - details of the changes made grouped together. + +As the team is using Copilot to generate code and PRs, it wants to ensure the AI tools follow these requirements. + +In this exercise you will: + +- explore an existing skill for creating pull requests. +- learn how skills are utilized by the AI agent. +- create a PR which matches the guidelines with the help of the skill. + +## Agent skills + +Skills allow you to tell Copilot and other AI agents how to perform specific tasks. This might include how to run tests, deploy projects, or create a PR. Skills are included in the project in the **.github/skills** folder, or globally in **~/.copilot/skills**. + +Each skill is defined as a folder with a name. Each folder then contains a **SKILL.md** file, which defines the skill. The **SKILL.md** file must have YAML frontmatter with a name and description. + +```yaml +--- +name: branching-commits-prs +description: All changes to the repository must be done through a pull request (PR). A branch must always be created, and commits grouped together logically. Whenever asked to create commit messages, to push code, or create a pull request (PR), use this skill so everything is done correctly. +--- +``` + +> [!IMPORTANT] +> Skills are loaded dynamically by the agent when the agent determines they're needed. It does so by utilizing the description. Having clear descriptions which defines when they should be used is key to success. + +Skills can also have subfolders with scripts, assets and references to provide additional information and capabilities. You can explore the full [specification][agent-skills-spec] to learn more about how skills are defined. + +## Executing skills + +Skills are loaded dynamically when the agent determines they're necessary. The decision of what skills to use is driven by the description in the **SKILL.md** file. As such, it's important to have clear descriptions which define the use case for the skill. + +## Exploring the PR skill + +Because Tailspin Toys has a set of requirements for creating PRs, they created a skill to help AI tools be able to generate PRs which follow these guidelines. Let's explore the skill to understand what it'll do. + +1. Return to your codespace. +2. Open **.github/skills/branches-commits-prs/SKILL.md**. +3. Note the name and description. Notice how the description highlights the scenario in which it should be used, which is whenever a request is made to create a pull request or committing code. +4. Read through the skill. Notice the rules are defined about how branches should be created, commits generated, and the contents of the pull request. + +## Using the skill + +As highlighted previously, skills are automatically invoked by Copilot CLI. As a result, all we need to do is ask Copilot to create a PR! + +1. Return to your codespace. +2. If not already open, open a terminal window by utilizing Ctrl+\`. +3. If not already running, start Copilot CLI by issuing the following command in the terminal window: + + ```bash + copilot --allow-all-tools + ``` + +4. Ask Copilot to create a PR by using the following prompt: + + ``` + Can you please create a pull request for me! + ``` + +5. Copilot will acknowledge the request. After a few moments, you'll notice Copilot will indicate it's utilizing the **branches-commits-prs** skill. + + ![Screenshot of the agent skill being called by Copilot CLI](https://raw.githubusercontent.com/GeekTrainer/tailspin-toys-workshop/main/workshop/images/5-agent-skill.png) + +6. Copilot will then follow the instructions in the skill. It will start by running the tests, then create a branch, commits, and eventually the PR. +7. Once the PR is created, return to your repository and open the PR. Note the sections follow the guidelines set forth in the skill, matching the requirements the team put forth. + +## Summary and next steps + +With the help of an agent skill, you created a new PR which matches documented requirements! You: + +- explored an existing skill for creating pull requests. +- learned how skills are utilized by the AI agent. +- created a PR which matches the guidelines with the help of the skill. + +Skills are perfect for tasks, but for more robust operations we want to take advantage of [custom agents][next-lesson], which we'll explore next! + +## Resources + +- [About Agent Skills][about-agent-skills] +- [Agent Skills Specification][agent-skills-spec] +- [Agent Skills Repository][agent-skills-repo] +- [Agent Skills on awesome-copilot][awesome-copilot-skills] + +--- + +| [← Previous lesson: Generating Code][previous-lesson] | [Next lesson: Custom Agents β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ./4-generating-code.md +[next-lesson]: ./6-custom-agents.md +[agent-skills-repo]: https://github.com/agentskills/agentskills +[agent-skills-spec]: https://agentskills.io/specification +[about-agent-skills]: https://docs.github.com/copilot/concepts/agents/about-agent-skills +[awesome-copilot-skills]: https://github.com/github/awesome-copilot/tree/main/skills diff --git a/workshop-content/cli/6-custom-agents.md b/workshop-content/cli/6-custom-agents.md new file mode 100644 index 00000000..3452119c --- /dev/null +++ b/workshop-content/cli/6-custom-agents.md @@ -0,0 +1,112 @@ +# Exercise 6 - Custom agents with GitHub Copilot CLI + +| [← Previous lesson: Agent Skills][previous-lesson] | [Next lesson: Slash Commands β†’][next-lesson] | +|:--|--:| + +[Custom agents][custom-agents] in GitHub Copilot allow you to create specialized AI assistants tailored to specific tasks or domains within your development workflow. By defining agents through markdown files in the `.github/agents` folder of your repository, you can provide Copilot with focused instructions, best practices, coding patterns, and domain-specific knowledge that guide it to perform particular types of work more effectively. This allows teams to codify their expertise and standards into reusable agents. You might create an accessibility agent that ensures [WCAG][wcag] compliance, a security agent that follows secure coding practices, or a testing agent that maintains consistent test patternsβ€”enabling developers to leverage these specialized capabilities on-demand for faster, more consistent implementations. + +You'll explore the following with custom agents: + +- how to create a custom agent. +- using a custom agent in Copilot CLI. + +## Scenario + +Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. + +Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. + +In this exercise, you will: + +- explore custom agents. +- enable a custom agent and assign it a task using Copilot CLI. + +## Custom agents + +Custom agents are defined by markdown files in the **.github/agents** folder of your project, or globally in **~/.copilot/agents**. The markdown files will contain guidance for Copilot on how best to perform at task. + +## Custom agents vs skills + +There is some logical overlap between custom agents and skills. Both are primarily defined with markdown files, and provide an AI guidance on how to perform operations. The best way to breakdown the difference is to think of a custom agent as the worker, and skills as tools. + +Custom agents have their own context window, and are built to use skills as part of their orchestration. In our example, we have an accessibility custom agent, which is designed to review and make updates to the site based on defined accessibility guidelines. As part of its work, it could then call skills such as the pull request skill we saw previously, or one to run and manage tests. + +Custom agents are launched by using the `/agent` command in Copilot CLI. Skills are launched dynamically, thus can be reused across multiple agents. + +## Reviewing the accessibility custom agent + +A custom agent has already been created for you for accessibility. Let's review the contents to understand how it will guide Copilot. + +1. Return to your codespace. +2. Open **.github/agents/accessibility.md**. +3. Note the header section with the name and description of the agent. + +> [!IMPORTANT] +> This section is required for custom agents. + +4. From there, scan and review the next sections which highlight: + - Core responsibilities when generating code for an accessible website. + - Best practices for accessibility. + - Code examples for HTML, CSS and JavaScript. + - A list of common pitfalls and mistakes. + +> [!NOTE] +> There is no "best markdown" for a custom agent. As with anything in AI, you will want to test and explore to determine what works best for your environments and scenarios. + +## Using a custom agent in Copilot CLI + +You can start a custom agent in Copilot CLI by using the `/agent` command. Let's perform an accessibility pass on our website. + +1. Return to your codespace. +2. If not already open, open a terminal window by utilizing Ctrl+\`. +3. If not already running, start Copilot CLI by issuing the following command in the terminal window: + + ```bash + copilot --allow-all-tools + ``` + +4. Bring up the list of agents by typing `/agent` in the prompt window in Copilot CLI and selecting Enter. +5. Select the **Accessibility agent** from the list of available agents. +6. Use the following prompt to ask the accessibility agent to perform a review and generate fixes for one particular class of possible errors related to HTML headers: + + ``` + Perform an accessibility review of the site. Pull the related issue down from the repository for details. We're going to build in stages, so for now focus on headers, ensuring we're following good guidelines. Ensure there are e2e tests for any updates made to the project. Then create a PR with the updates. + ``` + +7. Copilot gets to work on the task! It will start by retrieving the issue, then performing the review, generating updates, and finally creating the PR. You should also notice when it creates the PR it utilizes the skill focused on PRs for the project. + +> [!NOTE] +> This process will likely take a few minutes. It's a good time to reflect on everything you've learned, enjoy a beverage, or sneak ahead to the next module which talks about some additional commands available to you in Copilot CLI. + +## Summary and next steps + +This lesson explored [custom agents][custom-agents] in GitHub Copilot, specialized AI assistants tailored to specific tasks and domains. With custom agents you can codify your team's expertise and standards into reusable agents that guide Copilot to perform particular types of work more effectively. + +You explored these concepts: + +- how to create a custom agent. +- using a custom agent in Copilot CLI. + +Next up, let's explore [some slash commands][next-lesson] to learn some additional tricks with Copilot CLI. + +## Resources + +- [Custom agents][custom-agents] +- [Creating custom agents for a repository][creating-custom-agents] +- [Custom agents on awesome-copilot][awesome-copilot-agents] +- [Preparing to use custom agents in your organization][org-custom-agents] +- [Preparing to use custom agents in your enterprise][enterprise-custom-agents] + +--- + +| [← Previous lesson: Agent Skills][previous-lesson] | [Next lesson: Slash Commands β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ./5-agent-skills.md +[next-lesson]: ./7-slash-commands.md +[custom-agents]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#use-custom-agents +[creating-custom-agents]: https://docs.github.com/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents +[awesome-copilot-agents]: https://github.com/github/awesome-copilot/tree/main/agents +[wcag]: https://www.w3.org/WAI/standards-guidelines/wcag/ +[org-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-organization/prepare-for-custom-agents +[enterprise-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/prepare-for-custom-agents diff --git a/workshop-content/cli/7-slash-commands.md b/workshop-content/cli/7-slash-commands.md new file mode 100644 index 00000000..c13c690e --- /dev/null +++ b/workshop-content/cli/7-slash-commands.md @@ -0,0 +1,137 @@ +# Exercise 7 - Slash commands in GitHub Copilot CLI + +| [← Previous lesson: Custom Agents][previous-lesson] | [Next lesson: Review β†’][next-lesson] | +|:--|--:| + +Like any good CLI tool, GitHub Copilot CLI includes many slash commands to interact with it. These commands expose advanced functionality, "behind-the-scenes" information, or additional configuration options. You've already explored a couple with `/clear` to clear context and `/mcp` to register MCP servers. Let's explore a couple of other powerful ones, including `/context`, `/models` and `/share`. + +## Scenario + +As a part-time developer for Tailspin Toys, you want to share your experience with Copilot CLI with the rest of the team. You also want to explore different options inside the tool to ensure you're getting the most out of it. + +In this exercise you will use: + +- `/share` to create a GitHub gist to share your session with the team. +- `/context` to see the context Copilot CLI is currently using. +- `/models` to explore the list of available models and select a new one if you so desire. + +## Sharing a session + +Using any tool, including an AI tool, is a skill. Working together as a team, sharing learnings with each other, is the best way to help improve everyone's experience and generate higher quality code. To support this, Copilot CLI provides a `/share` command. The `/share` command can generate a markdown file or GitHub gist with the details of the session, including the prompts used and logic Copilot followed. + +Let's create a GitHub gist we could share with our team. + +1. Return to your codespace. +2. If hidden, show the terminal window by selecting Ctrl+\`. +3. In the prompt window for Copilot CLI, send the following command: + + ``` + /share gist + ``` + +4. In just a couple of moments, Copilot will create a gist and display the link. +5. Copy the link text. +6. In a new browser tab, paste the link to explore the gist. Note how the gist highlights the prompts sent, skills and agents used, Copilot's thought process, and even the code and results from locally run commands. + +The gists and markdown files generated by `/share` can be used for documentation purposes of how code was generated, or to share with your team about how certain actions were performed that generated the desired results from Copilot. + +## Exploring Copilot CLI's context + +When working on larger or more complex tasks you may bump into the maximum context window for the model. The exact size of the window will vary based on the model being used and the version of Copilot CLI. When the context window is maxed out, Copilot CLI will automatically compact it, summarizing information and removing anything it deems isn't relevant to the current task. You can both see the current state of the context and manually compact the context by using slash commands. Let's explore the context window. + +1. Return to your codespace. +2. If hidden, show the terminal window by selecting Ctrl+\`. +3. In the prompt window for Copilot CLI, send the following command: + + ``` + /context + ``` + +4. In just a couple of moments, Copilot CLI will generate a visual representation of its current context: + + ![Screenshot of context window from Copilot CLI](https://raw.githubusercontent.com/GeekTrainer/tailspin-toys-workshop/main/workshop/images/7-context-window.png) + +5. Note the model displayed (which may be different than the one in the image), and the current percentage of tokens used. The rest of the information highlights: + + | Title | Description | + | ------------ | ------------------------------------------------------ | + | System/Tools | Instructions files, file contents and tool definitions | + | Messages | Conversation history between you and Copilot | + | Buffer | Reserved space by Copilot CLI for generating responses | + | Free space | Remaining free space | + +6. Compact the conversation history by sending the following slash command to Copilot CLI: + + ``` + /compact + ``` + +7. Once completed, send the following command to display the current context stats again: + + ``` + /context + ``` + +8. Note the change in context. There might not be a drastic change as the context window is likely relatively small at the moment. + +> [!NOTE] +> Copilot CLI will automatically compact when it becomes full. As it approaches 100% capacity it will display the percentage just above the prompt window. Normally it will compact asynchronously, allowing you to continue interacting with Copilot while it does its work. It may however block a running operation for several seconds while performing its work. + +### Best practices with context + +In most sessions with Copilot context will be managed efficiently by Copilot itself without any specific guidance. However, there may be instances when you decide to manually instruct Copilot to either clear or compact its history: + +- If you are changing to a different part of the application, or to an unrelated task, you can use `/clear` to start new to avoid confusing Copilot with older, unrelated context. +- If you are approaching the maximum context window, you can manually `/compact` your context to control when it happens. + +> [!IMPORTANT] +> Again, the majority of the time, Copilot will manage its context without direct interaction from you. If you notice Copilot is a bit confused by older information, or are about to switch to an unrelated task, then you might consider using the manual commands. + +## Choosing your model + +Different models have different strengths, and different developers have different preferences. Copilot CLI allows you to list and select the model you wish to use! + +1. Return to your codespace. +2. If hidden, show the terminal window by selecting Ctrl+\`. +3. Display the list of models by sending the following slash command to Copilot CLI: + + ``` + /models + ``` + +4. Note the list of models. Each model will have both its name and cost-per-request modifier listed next to it. +5. If you wish, select a new model! Or select Esc to exit the model list. + +> [!IMPORTANT] +> Model selection persists in Copilot CLI. + +## Summary and next steps + +Using slash commands in Copilot CLI allows you to configure it, share sessions, and get internal information about how Copilot's working. In this lesson you used: + +- `/share` to create a GitHub gist to share your session with the team. +- `/context` to see the context Copilot CLI is currently using. +- `/models` to explore the list of available models and select a new one if you so desire. + +There are of course more slash commands available, and more to explore with Copilot CLI! Let's close out our journey by [reviewing what we've learned][next-lesson] and some next steps to continue learning. + +## Resources + +- [Using Copilot CLI][using-copilot-cli] +- [About Copilot CLI][about-copilot-cli] +- [Context Management in Copilot CLI][context-management] +- [Share Sessions with Copilot CLI][share-sessions] +- [Selecting Models in Copilot CLI][selecting-models] + +--- + +| [← Previous lesson: Custom Agents][previous-lesson] | [Next lesson: Review β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ./6-custom-agents.md +[next-lesson]: ./8-review.md +[using-copilot-cli]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli +[about-copilot-cli]: https://docs.github.com/copilot/concepts/agents/about-copilot-cli +[context-management]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#context-management +[share-sessions]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#share-sessions +[selecting-models]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#select-an-llm diff --git a/workshop-content/cli/8-review.md b/workshop-content/cli/8-review.md new file mode 100644 index 00000000..83525a01 --- /dev/null +++ b/workshop-content/cli/8-review.md @@ -0,0 +1,72 @@ +# Exercise 8 - Review and Next Steps + +| [← Previous lesson: Slash Commands][previous-lesson] | | +|:--|--:| + +Over the last several exercises, you explored some of the most common uses cases for GitHub Copilot CLI, including: + +- interacting with GitHub and other MCP servers. +- using instructions files to guide code generation. +- implementing skills to add tools to the Copilot CLI toolbox. +- calling custom agents for advanced and more complex tasks. + +Let's talk about some slash commands, best practices, and next steps. + +## Slash commands + +Copilot CLI has a series of slash commands available to interact with it, including ones which allow you to configure it or see what's going on behind the scenes. You've already used `/clear` to start a new chat which clears the current context, and `/mcp` to register MCP servers. Some additional ones you might find helpful are: + +| Command | Description | +| ------------------ | ------------------------------------------------------------- | +| `/add-dir` | Add a directory to the trusted list for Copilot | +| `/clear`, `/new` | Clear the conversation history and start fresh | +| `/compact` | Summarize conversation history to reduce context window usage | +| `/context` | Show context window token usage and visualization | +| `/diff` | Review the changes made in the current directory | +| `/model` | Select AI model to use (Claude Sonnet, GPT-5, etc.) | +| `/plan ` | Create an implementation plan before coding | +| `/review ` | Run code review agent to analyze changes | +| `/delegate` | Delegate task to Copilot coding agent for async processing | +| `/session` | Show session info and workspace summary | +| `/share` | Share session to markdown file or GitHub gist | +| `/skills` | Manage skills for enhanced capabilities | +| `/usage` | Display session usage metrics and statistics | + +> [!TIP] +> Use `/help` to see the full list of available commands and keyboard shortcuts. + +## Best practices + +When using any AI tool, investing in the underlying infrastructure will drive better code and a better experience. These include having robust instructions files, custom agents, and agent skills. You explored each of these in the workshop. A great resource for templates is [awesome-copilot][awesome-copilot]. You can also ask Copilot to explore your project and create these for you as a starting point! + +Always remember that context is key, both in life and when working with AI tools. A good infrastructure certainly goes a long way in helping generate the highest quality code, but prompts also have a large impact. Clearly describing what you want built, why you want it built, and how you want it built will help Copilot. Basically, if there's a piece of information that Copilot would benefit from having, ensure you pass that along! + +## Next steps + +The best way to improve your skills with any tool is to keep using the tool! Use it for production code, for hobby code, for the little app you've had in your mind for years but never got around to building. Share your learnings with your team, and learn from your team. And, as always, explore the documentation. + +If you'd like to explore more of the GitHub Copilot ecosystem, check out the [VS Code path](../vscode/README.md) or the [Cloud/Coding Agent path](../cloud/README.md). + +## Resources + +- [About Copilot CLI][about-copilot-cli] +- [Using Copilot CLI][using-copilot-cli] +- [Awesome Copilot Repository][awesome-copilot] +- [Custom Instructions Guide][repo-instructions] +- [Agent Skills Documentation][agent-skills] +- [Custom Agents Documentation][custom-agents] +- [MCP Specification][mcp-spec] + +--- + +| [← Previous lesson: Slash Commands][previous-lesson] | | +|:--|--:| + +[previous-lesson]: ./7-slash-commands.md +[about-copilot-cli]: https://docs.github.com/copilot/concepts/agents/about-copilot-cli +[using-copilot-cli]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli +[awesome-copilot]: https://github.com/github/awesome-copilot +[repo-instructions]: https://docs.github.com/copilot/how-tos/configure-custom-instructions/add-repository-instructions +[agent-skills]: https://docs.github.com/copilot/concepts/agents/about-agent-skills +[custom-agents]: https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#use-custom-agents +[mcp-spec]: https://modelcontextprotocol.io/ diff --git a/workshop-content/cli/README.md b/workshop-content/cli/README.md new file mode 100644 index 00000000..7182ed4c --- /dev/null +++ b/workshop-content/cli/README.md @@ -0,0 +1,44 @@ +# GitHub Copilot CLI Learning Path + +Welcome to the **Copilot CLI** learning path! This path focuses on GitHub Copilot CLI β€” a powerful agentic coding assistant that runs directly in your terminal, enabling you to explore codebases, generate code, run commands, and interact with external tools β€” all from the command line. + +## Path Overview + +| Exercise | Topic | Description | +|----------|-------|-------------| +| [0. Prerequisites][ex0] | Setup | Create your repository and codespace | +| [1. Custom Instructions][ex1] | Context | Learn how instruction files guide Copilot | +| [2. Installing Copilot CLI][ex2] | Installation | Install and authenticate Copilot CLI | +| [3. MCP Servers][ex3] | External Tools | Connect to GitHub and other services via MCP | +| [4. Generating Code][ex4] | Code Generation | Use plan mode and generate features | +| [5. Agent Skills][ex5] | Skills | Enhance Copilot with specialized skills | +| [6. Custom Agents][ex6] | Agents | Create and use custom agents | +| [7. Slash Commands][ex7] | CLI Features | Explore context, models, and sharing | +| [8. Review][ex8] | Summary | Review key concepts and next steps | + +## Prerequisites + +Before attending this workshop, please ensure you have: + +- [ ] A GitHub account with an active **Copilot Pro, Pro+, Business, or Enterprise** subscription +- [ ] Basic familiarity with terminal/command line operations +- [ ] Git installed and configured + +> [!NOTE] +> If you are using Copilot Business or Copilot Enterprise, ensure your admin has enabled Copilot CLI for use. + +## Get Started + +**[Start with Exercise 0: Prerequisites β†’][ex0]** + +--- + +[ex0]: ../shared/0-prereqs.md +[ex1]: ../shared/1-custom-instructions.md +[ex2]: ./2-install-copilot-cli.md +[ex3]: ./3-mcp.md +[ex4]: ./4-generating-code.md +[ex5]: ./5-agent-skills.md +[ex6]: ./6-custom-agents.md +[ex7]: ./7-slash-commands.md +[ex8]: ./8-review.md diff --git a/workshop-content/cloud/2-coding-agent.md b/workshop-content/cloud/2-coding-agent.md new file mode 100644 index 00000000..7d81ac31 --- /dev/null +++ b/workshop-content/cloud/2-coding-agent.md @@ -0,0 +1,205 @@ +# Exercise 4 - GitHub Copilot coding agent + +| [← Previous lesson: Custom instructions][previous-lesson] | [Next lesson: Custom agents β†’][next-lesson] | +|:--|--:| + +There are likely very few, if any, organizations who don't struggle with tech debt. This could be unresolved security issues, legacy code requiring updates, or feature requests which have languished on the backlog because there just wasn't the time to implement them. GitHub Copilot's coding agent is built to perform tasks such as updating code and adding functionality, all in an autonomous fashion. Once the agent completes its work, it generates a draft PR ready for a human developer to review. This allows offloading of tedious tasks and an acceleration of the development process, and frees developers to focus on larger picture items. + +You'll explore the following with Copilot coding agent: + +- customizing the environment for generating code. +- ensuring operations are performed securely. +- the importance of clearly scoped issues. +- assigning issues to Copilot. + +## Scenarios + +Tailspin Toys has some tech debt they'd like to address. The contractors initially hired to create the first version of the site left the documentation in an unideal state - and by that you'll notice it's completely lacking. As a first step, they'd like to see docstrings or the equivalent added to all functions in the application. + +Additionally, the design team is ready to get to work on building the UX for managing games. They don't need a full implementation yet, but they at least need some endpoints they can use for testing. Specifically, they need endpoints for the games API which will allow them to create, update and delete games. This is currently a blocker, but there are other issues which are of higher priority at the moment. + +These are both examples of tasks which can quickly find themselves deprioritized, and are great to assign to Copilot coding agent. Copilot coding agent can then work on them asynchronously, allowing the developer to focus on other tasks, then return to review Copilot's work and ensure everything is as expected. + +## Introducing GitHub Copilot coding agent + +[GitHub Copilot coding agent][coding-agent-overview] can perform tasks in the background, much in the same way a human developer would. And, just like with working with a human developer, this can be done in multiple ways, including [assigning a GitHub issue to Copilot][assign-issue]. Once assigned, Copilot will create a draft pull request to track its progress, setup an environment, and begin working on the task. You can dig into Copilot's session while it's still in flight or after its completed. Once its ready for you to review the proposed solution, it'll tag you in the pull request! + +## The importance of well-scoped instructions + +While it can often feel like it, there is no magic in GitHub Copilot. There are no magic solutions available, where you can with just a couple of sentences snap your fingers and let AI perform the entire task for you. In fact, even seemingly straight-forward operations can often have fair amount of complexity when you peel back the layers. + +As a result, you want to [be mindful about how you approach assigning tasks to Copilot coding agent][coding-agent-best-practices]. Working with Copilot as an AI pair programmer is typically the best approach. Approach tasks, big and small, following the same strategy you would without Copilot - work in stages, learn, experiment, and adapt accordingly. + +As always, the fundamentals of software development do not change with the addition of generative AI. + +## Setting up the dev environment for the Copilot coding agent + +Creating code, regardless of who's involved, typically requires a specific environment and some setup scripts to be run to ensure everything is in a good state. This holds true when assigning tasks to Copilot, which is performing tasks in a similar fashion to a SWE. + +Coding agent uses [GitHub Actions][github-actions] for its environment when doing its work. You can customize this environment by creating a [special setup workflow][setup-workflow], configured in the **.github/workflows/copilot-setup-steps.yml** file, to run before it gets to work. This enables it to have access to the required development tools and dependencies. This has been pre-configured ahead of the lab to help the lab flow and allow this learning opportunity. It makes sure that Copilot had access to Python, Node.JS, and the required dependencies for the client and server: + +```yaml +name: "Copilot Setup Steps" + +# Allows you to test the setup steps from your repository's "Actions" tab +on: workflow_dispatch + +jobs: + copilot-setup-steps: + runs-on: ubuntu-latest + # Set the permissions to the lowest permissions possible needed for *your steps*. Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v5 + + # Backend setup - Python + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + cache: "pip" + + - name: Install Python dependencies + run: ./scripts/setup-env.sh + + # Frontend setup - Node.js + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + cache: "npm" + cache-dependency-path: "./client/package.json" + + - name: Install JavaScript dependencies + working-directory: ./client + run: npm ci + + - name: Install Playwright + working-directory: ./client + run: npx playwright install +``` + +It looks like any other GitHub workflow file, but it has a few key points: + +- It contains a single job called **copilot-setup-steps**. This job is executed in GitHub Actions before Copilot starts working on the pull request. +- Notice the **workflow_dispatch** trigger, which allows you to run the workflow manually from the Actions tab of your repository. This is useful for testing that the workflow runs successfully instead of waiting for Copilot to run it. + +## Adding documentation + +While everyone understands the importance of documentation, most projects have either outdated information or lack it altogether. This is the type of tech debt which often goes unaddressed, slowing productivity and making it more difficult to maintain the codebase or bring new developers into the team. Fortunately, Copilot shines at creating documentation, and this is a perfect issue to assign to Copilot coding agent. It'll work in the background to generate the necessary documentation. In a future exercise you'll return to review its work. + +1. Navigate to your repository on github.com in a new browser tab. +2. Select the **Issues** tab. +3. Select **New issue** to open the new issue dialog. +4. Select **Blank issue** to create the new issue. +5. Set the **Title** to `Code lacks documentation`. +6. Set the **Description** to: + + ```plaintext + Our organization has a requirement that all functions have docstrings or the language equivalent. Unfortunately, recent updates haven't followed this standard. We need to update the existing code to ensure docstrings (or the equivalent) are included with every function or method. + ``` + +7. Select **Create** to create the issue. +8. On the right side, select **Assign to Copilot** to open the assignment dialog. + + ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + +9. Select **Assign**. + + ![Copilot assignment details]((../images/)ex4-assign-copilot-details.png) + +10. Select the **Pull Requests** tab. +11. Open the newly generated pull request (PR), which will be titled something similar to **[WIP]: Code lacks documentation**. If a new PR doesn't appear on the list, wait for a moment or two and refresh the browser window. +12. After a few minutes, you should see that Copilot has created a todo list. + +> [!NOTE] +> It make take several minutes for the todo list from Copilot to appear in the PR. Copilot is creating its environment (running the workflow highlighted previously), analyzing the project, and determining the best approach to tackling the problem. + +13. Review the list and the tasks it's going to complete. +14. Scroll down the pull request timeline, and you should see an update that Copilot has started working on the issue. +15. Select the **View session** button. + + ![Copilot session view]((../images/)ex4-view-session.png) + +> [!IMPORTANT] +> You may need to refresh the window to see the updated indicator. + +16. Notice that you can scroll through the live session, and how Copilot is solving the problem. That includes exploring the code and understanding the state, how Copilot pauses to think and decide on the appropriate plan and also creating code. + +This will likely take several minutes. One of the primary goals of Copilot coding agent is to allow it to perform tasks asynchronously, freeing us to focus on other tasks. We're going to take advantage of that very feature by both assigning another task to Copilot coding agent, then turning our attention to writing some code to add features to our application. + +## Create new endpoints to modify games + +As has been highlighted, one of the great advantages of GitHub Copilot coding agent is the ability to divide work, where you can focus on one set of tasks while it focuses on another. While creating the endpoints for modifying games for the design team might not necessarily take a long time, it's still time which could be used for other tasks. Let's assign it to Copilot coding agent! + +1. Return to your repository on github.com. +2. Select the **Issues** tab. +3. Select **New issue** to open the new issue dialogue. +4. Select **Blank issue** to use the blank template. +5. Set the **Title** to: `Add endpoints to create and edit games` +6. Set the **Description** to: + + ```markdown + We're going to be creating functionality in the future to allow for the submission (and editing) of games. For now we just want the endpoints so we can explore how we want to create the UX and do some acceptance testing. Our requirements are: + + - Add new endpoints to the Games API to support creating, updating and deleting games + - There should be appropriate error handling for all new endpoints + - There should be unit tests created for all new endpoints + - Before creating the PR, ensure all tests pass + ``` + +7. Select **Create** to create the issue. +8. On the right side, select **Assign to Copilot** to open the assignment dialog. + + ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + +9. Select **Assign**. + +Shortly after, you should see a set of πŸ‘€ on the first comment in the issue, indicating Copilot is on the job! + +![Copilot uses the eyes emoji to indicate it's working on the issue]((../images/)ex4-issue-eyes-emoji.png) + +9. Select **Assign** to assign the issue to Copilot coding agent. + +Copilot is now diligently working on your second request! Copilot coding agent works in a similar fashion to a SWE, so you don't need to actively monitor it, but instead review once it's completed. Let's turn your attention to writing code and adding other features. + +## Summary and next steps + +With coding agent working diligently in the background, you can now turn your attention to your next lesson, [creating and using custom agents][next-lesson]. [Copilot coding agent can also use MCP servers][coding-agent-mcp], and has custom instructions available to it, which you explored in earlier modules. + +## Summary and next steps + +This lesson explored [GitHub Copilot coding agent][copilot-agents], your AI peer programmer. With coding agent you can assign issues to Copilot to perform asynchronously. You can use Copilot to address tech debt, create new features, or aid in migrating code from one framework to another. + +You explored these concepts: + +- customizing the environment for generating code. +- ensuring operations are performed securely. +- the importance of clearly scoped issues. +- assigning issues to Copilot. + +With coding agent working diligently in the background, we can now turn our attention to our next lesson, [creating and using custom agents][next-lesson]. [Copilot coding agent can also use MCP servers][coding-agent-mcp], and has custom instructions available to it, which we explored in earlier modules. + +## Resources + +- [About Copilot coding agent][copilot-agents] +- [Assigning GitHub issues to Copilot][assign-issue] +- [Copilot coding agent setup workflow best practices][coding-agent-best-practices] + +--- + +| [← Previous lesson: Custom instructions][previous-lesson] | [Next lesson: Custom agents β†’][next-lesson] | +|:--|--:| + +[coding-agent-overview]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot#overview-of-copilot-coding-agent +[coding-agent-mcp]: https://docs.github.com/copilot/how-tos/agents/copilot-coding-agent/extending-copilot-coding-agent-with-mcp +[assign-issue]: https://docs.github.com/copilot/using-github-copilot/coding-agent/using-copilot-to-work-on-an-issue +[setup-workflow]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#pre-installing-dependencies-in-github-copilots-environment +[copilot-agents]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot +[coding-agent-best-practices]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks +[github-actions]: https://docs.github.com/actions +[next-lesson]: ./3-custom-agents.md +[previous-lesson]: ../shared/1-custom-instructions.md diff --git a/workshop-content/cloud/3-custom-agents.md b/workshop-content/cloud/3-custom-agents.md new file mode 100644 index 00000000..2b3f0dd2 --- /dev/null +++ b/workshop-content/cloud/3-custom-agents.md @@ -0,0 +1,94 @@ +# Exercise 5 - Custom agents + +| [← Previous lesson: Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | +|:--|--:| + +[Custom agents][custom-agents] in GitHub Copilot allow you to create specialized AI assistants tailored to specific tasks or domains within your development workflow. By defining agents through markdown files in the `.github/agents` folder of your repository, you can provide Copilot with focused instructions, best practices, coding patterns, and domain-specific knowledge that guide it to perform particular types of work more effectively. This allows teams to codify their expertise and standards into reusable agents. You might create an accessibility agent that ensures [WCAG][wcag] compliance, a security agent that follows secure coding practices, or a testing agent that maintains consistent test patternsβ€”enabling developers to leverage these specialized capabilities on-demand for faster, more consistent implementations. + +You'll explore the following with custom agents: + +- how to create a custom agent. +- assigning a task to a custom agent. + +## Scenario + +Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. + +Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. + +## Custom agents + +Custom agents are defined by markdown files in the **.github/agents** folder of your project. The markdown files will contain guidance for Copilot on how best to perform at task. + +## Reviewing the accessibility custom agent + +A custom agent has already been created for you for accessibility. Let's review the contents to understand how it will guide Copilot. + +1. Return to your codespace. +2. Open **.github/agents/accessibility.md**. +3. Note the header section with the name and description of the agent. + +> [!IMPORTANT] +> This section is required for custom agents. + +4. From there, scan and review the next sections which highlight: + - Core responsibilities when generating code for an accessible website. + - Best practices for accessibility. + - Code examples for HTML, CSS and JavaScript. + - A list of common pitfalls and mistakes. + +> [!NOTE] +> There is no "best markdown" for a custom agent. As with anything in AI, you will want to test and explore to determine what works best for your environments and scenarios. + +## Create and assign an issue + +Mission control is the central location for working with all agents for your environment. You can assign tasks to Copilot coding agent, monitor tasks, and even redirect and provide additional guidance. Let's start by assigning a task to create the high contrast mode to Copilot. + +1. Navigate to your repository. +2. Select the issues tab. +3. Select **New issue** to open the new issue dialog. +4. Select **Blank issue** to create the new issue. +5. Set the **Title** to `Add high contrast mode to website`. +6. Set the **Description** to: + + ```plaintext + We need a high contrast mode for the site. There should be a toggle for high contrast which the user can set. It should store the setting in local storage on the browser. + ``` + +7. Select **Create** to create the issue. +8. On the right side, select **Assign to Copilot** to open the assignment dialog. +9. Select **Accessibility agent** from the list of custom agents. + + ![Screenshot of coding agent assignment, with custom agent and accessibility highlighted](./(../images/)ex5-select-custom-agent.png) + +10. Select **Assign**. +11. Copilot gets to work on the task in the background! + +## Summary and next steps + +This lesson explored [custom agents][custom-agents] in GitHub Copilot, specialized AI assistants tailored to specific tasks and domains. With custom agents you can codify your team's expertise and standards into reusable agents that guide Copilot to perform particular types of work more effectively. + +You explored these concepts: + +- how to create a custom agent. +- assigning a task to a custom agent. + +With Copilot working on implementing the high contrast mode, we can now turn our attention to our next lesson, [using Copilot HQ to monitor and guide agent sessions][next-lesson]. Custom agents help ensure that Copilot follows your organization's best practices and domain-specific requirements, enabling faster and more consistent implementations across your team. + +## Resources + +- [Custom agents][custom-agents] +- [Preparing to use custom agents in your organization][org-custom-agents] +- [Preparing to use custom agents in your enterprise][enterprise-custom-agents] + +--- + +| [← Previous lesson: Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | +|:--|--:| + +[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents +[wcag]: https://www.w3.org/WAI/standards-guidelines/wcag/ +[org-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-organization/prepare-for-custom-agents +[enterprise-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/prepare-for-custom-agents +[next-lesson]: ./4-managing-agents.md +[previous-lesson]: ./2-coding-agent.md diff --git a/workshop-content/cloud/4-managing-agents.md b/workshop-content/cloud/4-managing-agents.md new file mode 100644 index 00000000..a373477a --- /dev/null +++ b/workshop-content/cloud/4-managing-agents.md @@ -0,0 +1,92 @@ +# Exercise 6 - Monitoring and managing agents + +| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on Copilot's work β†’][next-lesson] | +|:--|--:| + +In the last couple of exercises you asked Copilot coding agent to take on three separate tasks focused on improving the user experience and adding functionality. While coding agent is built to operate asynchronously and autonomously, the ability to monitor these tasks is still important. + +There are numerous tools available to you to manage tasks assigned to coding agent, including [the agents page][agents-page] on GitHub.com. From this mission control you can see all agent tasks with open pull requests (PRs). You can explore the operations performed, and even steer an in-progress session to help guide it. + +In this lesson you will: + +- explore the agents page to monitor coding agent tasks. +- steer an in-flight session to request additional functionality. + +## Scenario + +After assigning the agent to create a high-contrast mode, the team realized it would be a good time to add a light mode as well. Since work was already being done to update the style of the site and add toggle functionality, it seemed logical to include this functionality. You want to steer the agent's work to ensure it adds a light mode as well as high contrast. + +## Review Copilot coding agent tasks + +Let's see the current status of all tasks assigned to Copilot coding agent. + +1. Navigate to agents page at [https://github.com/copilot/agents](https://github.com/copilot/agents). +2. Note the list of tasks, both on the main pane and on the left pane. You should see the list of the tasks you've assigned to Copilot, including: + - Updating documentation for your codebase. + - Generating APIs for modifying products. + - Adding a high contrast mode for the website. +3. Select one of the running tasks. Review the tasks which have been performed by Copilot. These can include: + - Checking out the code from the repository. + - Creating the environment for Copilot to work. + - Setting up MCP servers. + - Performing various steps to complete the assigned task. + +> [!NOTE] +> The exact steps listed will vary depending on the state of Copilot's work and the approach it took. + +4. Also note the pull request (PR) pane which appears on the right side. This allows you to see the PR and files changed for additional monitoring. + +## Steering coding agent + +Now that you've seen the tasks which are active, let's request Copilot include the light mode toggle while it works on the high-contrast mode. + +1. Select the session which refers to adding a high contrast mode. The exact title will vary depending on the name Copilot uses and the current state of work. + + ![Accessibility session in mission control]((../images/)ex6-accessibility-session.png) + +2. Watch the session for a few of minutes, until it indicates it's completed the setup and begun its work. You'll know this has happened when you start seeing messages similar to the ones below. +3. In the **Steer active session while Copilot is working** dialog, add the following prompt: + + ``` + While we are working on a high contrast mode, let's also add a light mode. There should be a switch for this mode as well where users can select their desired display mode. + ``` + + ![Screenshot of the coding agent task in the agents page with the steer active session while copilot is working dialogue highlighted](./(../images/)ex6-steer-coding-agent-task.png) + +4. Press Enter to send the prompt. +5. Notice how Copilot acknowledges the prompt and includes it in its flow. + +## Let Copilot do its work + +Just like before, Copilot will get to work on the updated task! It will incorporate the new request into its flow after it completes the particular step it's working on when you sent the message. + +As before, this will take several minutes, so it's a good time to pause and reflect on everything you've learned and explored thus far. + +## Summary and next steps + +This lesson explored the Copilot agents page, your central hub for monitoring and guiding GitHub Copilot coding agent tasks. With this mission control you can track all active and completed tasks, review the work being performed, and even redirect in-flight tasks to adjust scope or provide additional guidance. + +You explored these concepts: + +- explored Copilot HQ and the agents page to monitor coding agent tasks. +- redirected an in-flight session to request additional functionality. + +With Copilot completing its work on the accessibility features, we can now turn our attention to our next lesson, [iterating on the pull requests Copilot created][next-lesson]. Mission control provides visibility into agent work and enables dynamic collaboration with coding agents as they work on tasks. + +## Resources + +- [Copilot HQ agents page][agents-page] +- [Custom agents][custom-agents] + +--- + +| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on coding agent's work β†’][next-lesson] | +|:--|--:| + +[agents-page]: https://github.blog/changelog/2025-10-28-a-agents-page-to-assign-steer-and-track-copilot-coding-agent-tasks +[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents +[next-lesson]: +--- + +[next-lesson]: ./5-iterating.md +[previous-lesson]: ./3-custom-agents.md diff --git a/workshop-content/cloud/5-iterating.md b/workshop-content/cloud/5-iterating.md new file mode 100644 index 00000000..6032f812 --- /dev/null +++ b/workshop-content/cloud/5-iterating.md @@ -0,0 +1,173 @@ +# Exercise 7: Iterating on GitHub Copilot's work + +| [← Previous lesson: Managing agents][previous-lesson] | +|:--| + +Throughout this lab you've assigned several issues to GitHub Copilot coding agent. You asked it to add documentation to your code, generate endpoints for the design team to iterate on, and implement accessibility features including high-contrast and light mode toggles. Let's explore the code changes it suggested and, if necessary, provide feedback to Copilot to improve its work. + +## Scenario + +As has been highlighted numerous times, the fundamentals of software design and DevOps do not change with the addition of generative AI. We always want to review the code generated, and work through our normal DevOps process. With that in mind, let's review the suggestions from GitHub Copilot for creating the documentation, new endpoints, and accessibility features before we turn on review for the rest of our team. + +## Security and GitHub Copilot coding agent + +Because Copilot coding agent performs its tasks asynchronously and without supervision, certain security constraints have been put in place to ensure everything remains safe. These include: + +- Copilot only has read access to your repository and write access **only** to the branch it will use for its code. +- Coding agent runs inside of GitHub Actions, where it will create a separate, ephemeral environment in which to work. +- Any GitHub Actions workflows require approval from a human before they can be run. +- [Access to external resources is limited by default][agent-firewall], including MCP servers. + +## Reviewing the generated documentation + +Let's start by exploring the first pull request (PR) generated by GitHub Copilot coding agent - adding documentation to your code. You'll perform this task by utilizing the standard PR interface in GitHub.com. + +> [!NOTE] +> When you explore the PR you may notice a warning about GitHub Copilot being blocked by a firewall. This **is expected**, as Copilot has limited access to external resources by default, including calls to external MCP servers. If you wish, you can [customize or disable the firewall for Copilot coding agent][agent-firewall]. + +1. Return to your repository on github.com. +2. Select **Pull Requests** to open the list of pull requests. +3. Open issue titled something similar to **Add missing documentation** or something more robust. + +> [!NOTE] +> If Copilot is still working on the task, the issue will contain the **[WIP]** flag. If so, wait for Copilot to complete the work. This may take a few minutes, so feel free to take a break, or reflect on everything you've learned so far. + +4. Once the pull request is ready, select the **Files changed** tab and review the changes. + + ![Files changed tab]((../images/)shared-pr-files-changed.png) + +5. Explore the newly updated code, which includes the newly created docstrings and other documentation. The exact changes will vary. +6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. +7. You should see an indicator that some workflows are waiting for approval. +8. Click on the **Approve and run workflows** button to allow the workflows to run. + + ![Approve and run workflows]((../images/)shared-approve-workflows.png) + +9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. + +## Requesting changes from GitHub Copilot + +Working with Copilot on a pull request is not just a one-way street. You can also tag Copilot in comments - like you would other members of your team - in the pull request, or inline comments of the code. Copilot will see these comments, and trigger another session to address them. Due to the non-deterministic results, we can't give prescriptive text of what to ask for. Some ideas of what to ask Copilot to update include: + +- Add comment headers to the top of each code file with a brief description of what they do. +- Add docstrings to TypeScript and Svelte files. +- Create a README in both the server and client folders with descriptions of the codebase of each. + +1. Add a comment requesting a change to the generated documentation, tagging **@copilot** like you would any user. Use one of the ideas above, or another suggestion for Copilot around documentation you'd like to see in the codebase. +2. Select **View Session** to watch Copilot perform its work. Notice how Copilot starts a new session to make the updates. +3. You can select **Back to pull request** to return to the pull request. + + ![Back to pull request]((../images/)ex7-back-to-pr.png) + +4. Once Copilot has completed the changes, you should see a new commit in the pull request. +5. Select the **Files changed** tab to review the changes. + +Feel free to continue iterating until you are happy. Once happy, you can convert the PR to ready from a draft, and merge it into the main branch. + +![Convert PR to ready]((../images/)ex7-ready-for-review.png) + +## Review the new endpoints + +Let's return to the PR Copilot generated for resolving our issue about adding endpoints to the games API for creating, updating and deleting games. + +1. Return to your repository in GitHub.com. +2. Select the **Pull Requests** tab. +3. Select the PR which has a title similar to **Add CRUD endpoints for games API** or something more robust. +4. Select the **Files changed** tab to review the code it generated. +5. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. +6. You should see an indicator that some workflows are waiting for approval. +7. Click on the **Approve and run workflows** button to allow the workflows to run. + + ![Approve and run workflows]((../images/)shared-approve-workflows.png) + +8. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. +9. **Optional:** You could even switch to this branch in your Codespace to perform a manual test of the new endpoints. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created, e.g. **copilot/fix-8**.): + + ```bash + git fetch origin + git checkout + ``` + +Copilot has created the new endpoints! Just as before, you can work iteratively with Copilot coding agent to request updates. For example, you might want to request Copilot centralizes the error handling to reduce duplication, or ensuring comments and docstrings are added (remember - this was assigned **before** you made the updates to your custom instructions!) Just like before, you can make these requests by adding a new comment on the **Conversation** tab, which Copilot will see and kickoff a new session. + +## Review the accessibility features + +Finally, let's review the accessibility features that were implemented using the custom accessibility agent. This PR should include both the high-contrast mode you assigned in Exercise 5, and the light mode that was requested in mission control in Exercise 6. + +1. Return to your repository in GitHub.com. +2. Select the **Pull Requests** tab. +3. Select the PR which has a title similar to **Add high contrast mode to website** or something more robust. + +> [!NOTE] +> If Copilot is still working on the task, the issue will contain the **[WIP]** flag. If so, wait for Copilot to complete the work. This may take a few minutes. + +4. Select the **Files changed** tab to review the code it generated. +5. Review the implementation, paying particular attention to: + - The toggle UI components for switching between modes + - The use of local storage to persist user preferences + - The CSS or styling changes for high-contrast and light modes + - The accessibility attributes (ARIA labels, keyboard navigation, etc.) + - Any JavaScript/TypeScript code that manages the mode switching + +6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. +7. You should see an indicator that some workflows are waiting for approval. +8. Click on the **Approve and run workflows** button to allow the workflows to run. + + ![Approve and run workflows]((../images/)shared-approve-workflows.png) + +9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. +10. **Optional:** You could switch to this branch in your Codespace to manually test the accessibility features. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created): + + ```bash + git fetch origin + git checkout + ``` + + Then start the application and test the high-contrast and light mode toggles in your browser to ensure they work as expected and persist across page reloads. + +Notice how the custom accessibility agent helped guide Copilot to implement these features following accessibility best practices. If you see any accessibility concerns or improvements, you can tag **@copilot** in a comment to request updates, just like you did with the previous PRs. + +## Optional Exercise - Explore the agent's capabilities with more issues + +Having access to a peer programmer who is able to explore our codebase and make changes asynchronously is powerful, allowing us to reach a first iteration across tasks quickly, allowing us to review and guide, or take over and continue coding in the editor. + +You have made great progress through the lab, and you're approaching the end. However, you're encouraged to create some additional issues in your GitHub repository and use Copilot to solve those. Some ideas include: + +- Create a backer interest form on the game details page +- Implement pagination on the game listing endpoint +- Add input validation and error handling to the Flask API +- Something else? What else might you consider? + +## Summary + +Congratulations! You completed the lab! You worked through several features available to you with GitHub Copilot, from the IDE to the repository. In particular you: + +- **Learned how to use GitHub Copilot and the Model Context Protocol (MCP) to streamline software development**. You set up the GitHub MCP server to enable Copilot to interact with your repository, created a detailed backlog using Copilot Agent Mode. +- **Explored how custom instructions and prompt files can guide Copilot to follow your project's coding standards.** You created a custom instructions file to provide context for Copilot, ensuring it generates code that adheres to your project's guidelines and used prompt files to provide guidance for repetitive tasks and established practices. +- **Used Copilot Agent Mode to implement new features, coordinate changes across backend and frontend code, and automate repetitive tasks.** You used GitHub Copilot to implement a new category and publisher filter for the game listing page, making changes across the client, backend, and the resulting tests. +- **Experienced Copilot as a peer programmer, being assigned issues and working collaboratively on pull requests.** You assigned Copilot to issues in your backlog, allowing it to create a pull request, build a plan, implement changes, and iterate further as you provided feedback. + +This is just the beginning, and we can't wait to see how you use Copilot to help you with your own projects. We hope you enjoyed the lab, and we look forward to seeing you in the next one! Happy coding! + +## Resources + +- [GitHub Copilot][github-copilot] +- [About Copilot agents][copilot-agents] +- [Assigning GitHub issues to Copilot][assign-issue] +- [Copilot coding agent setup workflow best practices][coding-agent-best-practices] +- [Configuring Copilot coding agent firewall][agent-firewall] + +--- + +| [← Previous lesson: Managing agents][previous-lesson] | +|:--| + +[github-copilot]: https://github.com/features/copilot +[coding-agent-overview]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot#overview-of-copilot-coding-agent +[assign-issue]: https://docs.github.com/copilot/using-github-copilot/coding-agent/using-copilot-to-work-on-an-issue +[setup-workflow]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#pre-installing-dependencies-in-github-copilots-environment +[copilot-agents]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot +[coding-agent-best-practices]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks +[agent-firewall]: https://docs.github.com/copilot/customizing-copilot/customizing-or-disabling-the-firewall-for-copilot-coding-agent + +[previous-lesson]: ./4-managing-agents.md diff --git a/workshop-content/cloud/README.md b/workshop-content/cloud/README.md new file mode 100644 index 00000000..1a0be1c5 --- /dev/null +++ b/workshop-content/cloud/README.md @@ -0,0 +1,37 @@ +# Cloud & Coding Agent Learning Path + +Welcome to the **Cloud & Coding Agent** learning path! This path focuses on GitHub Copilot's cloud-based capabilities β€” specifically the **Copilot coding agent**, which works asynchronously like a peer programmer on your GitHub issues, and the tools for managing and guiding agent sessions. + +## Path Overview + +| Exercise | Topic | Description | +|----------|-------|-------------| +| [0. Prerequisites][ex0] | Setup | Create your repository and codespace | +| [1. Custom Instructions][ex1] | Context | Learn how instruction files guide Copilot | +| [2. Coding Agent][ex2] | Async Agent | Assign issues to Copilot coding agent | +| [3. Custom Agents][ex3] | Specialized Agents | Create and use custom agents | +| [4. Managing Agents][ex4] | Monitoring | Monitor and steer agent sessions | +| [5. Iterating][ex5] | Review | Iterate on Copilot's generated work | + +## Prerequisites + +Before attending this workshop, please ensure you have: + +- [ ] A GitHub account with an active **Copilot Pro+, Business, or Enterprise** subscription +- [ ] Copilot coding agent enabled for your account or organization + +> [!NOTE] +> Copilot coding agent requires **Copilot Pro+, Business, or Enterprise**. Check with your administrator if you are unsure whether this feature is enabled. + +## Get Started + +**[Start with Exercise 0: Prerequisites β†’][ex0]** + +--- + +[ex0]: ../shared/0-prereqs.md +[ex1]: ../shared/1-custom-instructions.md +[ex2]: ./2-coding-agent.md +[ex3]: ./3-custom-agents.md +[ex4]: ./4-managing-agents.md +[ex5]: ./5-iterating.md diff --git a/workshop-content/shared/0-prereqs.md b/workshop-content/shared/0-prereqs.md new file mode 100644 index 00000000..aff139f6 --- /dev/null +++ b/workshop-content/shared/0-prereqs.md @@ -0,0 +1,68 @@ +# Exercise 0: Prerequisites + +| | [Next lesson: Custom Instructions β†’][next-lesson] | +|:--|--:| + +Before you get started on the lab, there's a few tasks you need to complete to get everything ready. You need to get a copy of the repository which includes the code, then spin up a [codespace][codespaces] to use to create your code. + +## Setting up the Lab Repository + +To create a copy of the repository for the code you'll create an instance from the [template][template-repository]. The new instance will contain all of the necessary files for the lab, and you'll use it as you work through the exercises. + +1. In a new browser window, navigate to the GitHub repository for this lab: `https://github.com/github-samples/agents-in-sdlc`. +2. Create your own copy of the repository by selecting the **Use this template** button on the lab repository page. Then select **Create a new repository**. + + ![Use this template button](../images/ex0-use-template.png) + +3. If you are completing the workshop as part of an event being led by GitHub or Microsoft, follow the instructions provided by the mentors. Otherwise, you can create the new repository in an organization where you have access to GitHub Copilot. + + ![Input the repository template settings](../images/ex0-repository-settings.png) + +4. Make a note of the repository path you created (**organization-or-user-name/repository-name**), as you will be referring to this later in the lab. + +## Creating a codespace + +Next up, you'll be using a codespace to complete the lab exercises. [GitHub Codespaces][codespaces] are a cloud-based development environment that allows you to write, run, and debug code directly in your browser. It provides a fully-featured IDE with support for multiple programming languages, extensions, and tools. + +1. Navigate to your newly created repository. +2. Select the green **Code** button. + + ![Select the Code button](../images/ex0-code-button.png) + +3. Select the **Codespaces** tab and select the **+** button to create a new Codespace. + + ![Create a new codespace](../images/ex0-create-codespace.png) + +The creation of the codespace will take several minutes, although it's still far quicker than having to manually install all the services! That said, you can use this time to explore other features of GitHub Copilot, which we'll turn your attention to next! + +> [!IMPORTANT] +> You'll return to the codespace in a future exercise. For the time being, leave it open in a tab in your browser. + +> [!NOTE] +> This workshop is built to run inside of a codespace or container. This ensures the environment you're working in has all of the necessary prerequisites installed and you'll have a smooth experience. If you wish to run the workshop locally on your system, you will need recent versions of Node.js and Python installed, as well as Visual Studio Code. +> +> If you are running the workshop locally, rather than creating a codespace, clone the newly created repository. Then open the cloned repository in VS Code, which you will use in place of Codespaces. + +## Summary + +Congratulations, you have created a copy of the lab repository! You also began the creation process of your codespace, which you'll use when you begin writing code. + +## Next step + +Let's explore how you can provide context to Copilot using instruction files! Continue to [Exercise 1 - Custom Instructions][next-lesson]. + +## Resources + +- [GitHub Codespaces overview][codespaces] +- [Creating a repository from a template][template-repository] +- [Getting started with Codespaces][codespaces-quickstart] + +--- + +| | [Next lesson: Custom Instructions β†’][next-lesson] | +|:--|--:| + +[codespaces]: https://github.com/features/codespaces +[template-repository]: https://docs.github.com/repositories/creating-and-managing-repositories/creating-a-template-repository +[codespaces-quickstart]: https://docs.github.com/codespaces/getting-started/quickstart +[next-lesson]: ./1-custom-instructions.md diff --git a/workshop-content/shared/1-custom-instructions.md b/workshop-content/shared/1-custom-instructions.md new file mode 100644 index 00000000..ce9eb872 --- /dev/null +++ b/workshop-content/shared/1-custom-instructions.md @@ -0,0 +1,193 @@ +# Exercise 1 - Providing context to Copilot with instruction files + +| [← Previous lesson: Prerequisites][previous-lesson] | [Next lesson β†’][next-lesson] | +|:--|--:| + +Context is key across many aspects of life, and when working with generative AI. If you're performing a task which needs to be completed a particular way, or if a piece of background information is important, you want to ensure Copilot has access to that information. You can use [instruction files][instruction-files] to provide guidance so that Copilot not only understands what you want it to do but also how you want it to be done. + +In this exercise, you will learn how to: + +- provide Copilot with project-specific context, coding guidelines and documentation standards using [repository custom instructions][repository-custom-instructions] **.github/copilot-instructions.md**. +- provide path instruction files to guide Copilot for repetitive or templated tasks on specific types of files. +- implement both repository-wide instructions and task-specific instructions. + +> [!IMPORTANT] +> Note that the code generated may diverge from some of the standards you set. AI tools like Copilot are non-deterministic, and may not always provide the same result. The other files in the codebase do not contain docstrings or comment headers, which could lead Copilot in another direction. Consistency is key, so making sure that your code follows the established patterns is important. You can always follow-up in chat and ask Copilot to follow your coding standards, which will help guide it in the right direction. + +## Scenario + +As any good dev shop, Tailspin Toys has a set of guidelines and requirements for development practices. These include: + +- API always needs unit tests. +- UI should be in dark mode and have a modern feel. +- Documentation should be added to code in the form of docstrings. +- A block of comments should be added to the head of each file describing what the file does. + +Through the use of instruction files you'll ensure Copilot has the right information to perform the tasks in alignment with the practices highlighted. + +## Custom instructions + +Custom instructions allow you to provide context and preferences to Copilot, so that it can better understand your coding style and requirements. This is a powerful feature that can help you steer Copilot to get more relevant suggestions and code snippets. You can specify your preferred coding conventions, libraries, and even the types of comments you like to include in your code. You can create instructions for your entire repository, or for specific types of files for task-level context. + +There are two types of instructions files: + +- **.github/copilot-instructions.md**, a single instruction file sent to Copilot for **every** chat prompt for the repository. This file should contain project-level information, context which is relevant for most chat requests sent to Copilot. This could include the tech stack being used, an overview of what's being built and best practices, and other global guidance for Copilot. +- **\*.instructions.md** files can be created for specific tasks or file types. You can use **\*.instructions.md** files to provide guidelines for particular languages (like Python or TypeScript), or for tasks like creating a React component or a new set of unit tests. + +> [!NOTE] +> When working in your IDE, instructions files are only used for code generation in Copilot Chat, and not used for code completions or next edit suggestions. +> +> Copilot coding agent and Copilot CLI will utilize both repository level and `*.instructions` files with `applyTo` header matter when generating code. + +## Best practices for managing instructions files + +A full conversation about creating instructions files is beyond the scope of the workshop. However, the examples provided in the sample project provide a representative example of how to approach their management. At a high level: + +- Keep instructions in **copilot-instructions.md** focused on project-level guidance, such as a description of what's being built, the structure of the project, and global coding standards. +- Use **\*.instructions.md** files to provide specific instructions for file types (unit tests, React components, API endpoints), or for specific tasks. +- Use natural language in your instructions files. Keep guidance clear. Provide examples of how code should (and shouldn't) look. + +There isn't one specific way to create instructions files, just as there isn't one specific way to use AI. You will find through experimentation what works best for your project. The guidance provided here and the [resources](#resources) below should help you get started. + +> [!TIP] +> Every project using GitHub Copilot should have a robust collection of instructions files to provide context and best guide code generation. As you explore the instructions files in the project, you may notice there are ones for numerous types of files and tasks, including [UI updates][ui-instructions] and [Astro][astro-instructions]. The investment made in instructions files will greatly enhance the quality of code suggestion from Copilot, ensuring it better matches the style and requirements your organization has. +> +> You can even have Copilot aid in generating instructions files by selecting the gear icon for **Configure Chat** in Copilot chat and selecting **Generate Agent Instructions**. Or if using Copilot CLI, use the `/agent` command to engage an agent specialized for generating instruction files. +> +> And, if you're looking for templates or a starting point for instructions files, you can explore [awesome-copilot][awesome-copilot], a repository full of instructions files, custom agents, and other resources to help you out! + +## Ensure your codespace is ready + +In a [prior exercise][prereqs-lesson] you launched the codespace you'll use for the remainder of the coding exercises in this lab. Let's put the final touches on it before you begin using it. + +The setup process for the codespace installed and set up many [VS Code extensions][vscode-extensions]. As with any software, updates may be needed. When your codespace is created you'll need to ensure everything is up-to-date. + +1. Return to the tab where you started your codespace. If you closed the tab, return to your repository, select **Code** > **Codespaces** and then the name of the codespace. +2. Select **Extensions** on the workbench on the left side of your codespace. + + ![Screenshot of the extensions window with multiple extensions showing either Update or Reload Window buttons](../images/ex1-extensions-updates.png) + +3. Select **Update** on any extensions with an **Update** button. Repeat as necessary. +4. Select **Reload Window** on any extensions with a **Reload Window** button to reload the codespace. +5. When prompted by a dialog, select **Reload** to reload the window. This will ensure the latest version is being used. + +## Explore the custom instructions files + +Let's start by exploring the instructions files created for this project. You'll notice there's one core **copilot-instructions.md** file, and a collection of **.instructions** files for various tasks. + +1. Return to your codespace. +2. Open **.github/copilot-instructions.md**. +3. Explore the file, noting the brief description of the project and sections for **Code standards**, **Scripts** and **GitHub Actions Workflows**. These are applicable to any interactions you'd have with Copilot, are robust, and provide clear guidance on what you're doing and how you want to accomplish it. +4. Open **.github/instructions**, and explore the files contained inside it. Note there are instructions for Astro files, Svelte files, the various tests, and others. +5. Open **.github/instructions/python-tests.instructions.md**. Make note of the `applyTo` section. This sets the path, relative to the root of the project, which determines which files the instructions apply to. In this case, any Python files in the **server/tests** folder with a name that starts with **test_** will match the slug. +6. Note the instructions specific to creating Python tests for this project. +7. Finally, open **.github/instructions/flask-endpoint.instructions.md**, and scroll to the bottom of the file. Note the links to other instructions files and existing files in the project. This allows you to both break down larger instruction sets into smaller, reusable files, and to point to examples Copilot should consider when generating code. Note these paths are relative to the instructions file rather than the root of the project. + +## Examine the impact of custom instructions + +To see the impact of custom instructions, you'll start by sending a prompt with the current version of the files, and see how Copilot pulls those files into context. Then you'll make some updates, send the same prompt again, and note the difference. + +1. Return to your codespace. +2. Close any files open in the codespace. +3. Open `server/routes/publishers.py`, an empty file. +4. If **Copilot chat** is not already open, open it by selecting the Copilot icon towards the top of your codespace. +5. Create a new chat session by typing `/clear` into the chat window and selecting Enter (or return on a Mac). +6. Select **Ask** from the modes dropdown. +7. Send the following prompt to create a new endpoint to return all publishers: + + ```plaintext + Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. + ``` + +8. Copilot explores the project to learn how best to implement the code, and generates a list of suggestions, which may include code for `publishers.py`, `app.py`, and tests to ensure the new code runs correctly. +9. Explore the code, noticing the generated code includes [type hints][python-type-hints] because, as you'll see, the custom instructions includes the directive to include them. +10. Notice the generated code **is missing** either a docstring or a comment header - or both! + +> [!IMPORTANT] +> As highlighted previously, GitHub Copilot and LLM tools are probabilistic, not deterministic. As a result, the exact code generated may vary, and there's even a chance it'll abide by your rules without you spelling it out! But to aid consistency in code you should always document anything you want to ensure Copilot should understand about how you want your code generated. + +## Add new repository standards to copilot-instructions.md + +As highlighted previously, `copilot-instructions.md` is designed to provide project-level information to Copilot. Let's ensure repository coding standards are documented to improve code suggestions from Copilot. + +1. Return to your codespace. +2. Open `.github/copilot-instructions.md`. +3. Locate the **Code formatting requirements** section. Note how it contains a note to use type hints. That's why you saw those in the code generated previously. +4. Add the following lines of markdown right below the note about type hints to instruct Copilot to add comment headers to files and docstrings: + + ```markdown + - Every function should have docstrings or the language equivalent. + - Before imports or any code, add a comment block to the file that explains its purpose. + ``` + +5. Close **copilot-instructions.md**. +6. Select **New Chat** in Copilot chat to clear the buffer and start a new conversation. +7. Return to **server/routes/publishers.py** to ensure focus is set correctly. +8. Send the same prompt as before to create the endpoint. + + ```plaintext + Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. + ``` + +9. Notice how the newly generated code includes a comment header at the top of the file which resembles the following: + + ```python + """ + Publisher API routes for the Tailspin Toys Crowd Funding platform. + This module provides endpoints to retrieve publisher information. + """ + ``` + +10. Notice how the newly generated code includes a docstring inside the function which resembles the following: + + ```python + """ + Returns a list of all publishers with their id and name. + + Returns: + Response: JSON response containing an array of publisher objects + """ + ``` + +11. Notice the generated code now includes a docstring as well as a comment block at the top! +12. Also note how the existing code isn't updated, but of course you could ask Copilot to perform that operation if you so desired! +13. **Don't implement the suggested changes**, as you'll be doing that in a later exercise. + +> [!NOTE] +> If you accepted the changes, you can always select the **Undo** button towards the top right of the Copilot chat window. + +## Summary and next steps + +Congratulations! You explored how to ensure Copilot has the right context to generate code following the practices your organization has set forth. This can be done at a repository level with the **.github/copilot-instructions.md** file, or on a task basis with instruction files. You explored how to: + +- provide Copilot with project-specific context, coding guidelines and documentation standards using custom instructions (.github/copilot-instructions.md). +- use instruction files to guide Copilot for repetitive or templated tasks. +- implement both repository-wide instructions and task-specific instructions. + +Next, continue to the exercise for your chosen path! + +## Resources + +- [Instruction files for GitHub Copilot customization][instruction-files] +- [5 tips for writing better custom instructions for Copilot][copilot-instructions-five-tips] +- [Best practices for creating custom instructions][instructions-best-practices] +- [Personal custom instructions for GitHub Copilot][personal-instructions] +- [Awesome Copilot - a collection of instructions files and other resources][awesome-copilot] + +--- + +| [← Previous lesson: Prerequisites][previous-lesson] | [Next lesson β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ./0-prereqs.md +[next-lesson]: ../README.md +[instruction-files]: https://code.visualstudio.com/docs/copilot/copilot-customization +[repository-custom-instructions]: https://docs.github.com/copilot/how-tos/configure-custom-instructions/add-repository-instructions +[python-type-hints]: https://docs.python.org/3/library/typing.html +[instructions-best-practices]: https://docs.github.com/enterprise-cloud@latest/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#adding-custom-instructions-to-your-repository +[personal-instructions]: https://docs.github.com/copilot/customizing-copilot/adding-personal-custom-instructions-for-github-copilot +[copilot-instructions-five-tips]: https://github.blog/ai-and-ml/github-copilot/5-tips-for-writing-better-custom-instructions-for-copilot/ +[awesome-copilot]: https://github.com/github/awesome-copilot +[ui-instructions]: ../../.github/instructions/ui.instructions.md +[astro-instructions]: ../../.github/instructions/astro.instructions.md +[vscode-extensions]: https://code.visualstudio.com/docs/configure/extensions/extension-marketplace diff --git a/workshop-content/vscode/2-mcp.md b/workshop-content/vscode/2-mcp.md new file mode 100644 index 00000000..2704adc4 --- /dev/null +++ b/workshop-content/vscode/2-mcp.md @@ -0,0 +1,230 @@ +# Exercise 1 - Setting up the backlog with Copilot agent mode and GitHub's MCP Server + +| [← Custom instructions][previous-lesson] | [Next lesson: Agent mode β†’][next-lesson] | +|:--|--:| + +There's more to writing code than just writing code. Issues need to be filed, external services need to be called, and information needs to be gathered. Typically this involves interacting with external tools, which can break a developer's flow. Through the power of Model Context Protocol (MCP), you can access all of this functionality right from Copilot! + +## Scenario + +You are a part-time developer for Tailspin Toys - a crowdfunding platform for board games with a developer theme. You've been assigned various tasks to introduce new functionality to the website. Being a good team member, you want to file issues to track your work. To help future you, you've decided to enlist the help of Copilot. You will set up your backlog of work for the rest of the lab, using GitHub Copilot Chat agent mode and the GitHub Model Context Protocol (MCP) server to create the issues for you. + +In this exercise, you will: + +- use Model Context Protocol (MCP), which provides access to external tools and capabilities. +- set up the GitHub MCP server in your repository. +- use GitHub Copilot Chat agent mode to create issues in your repository. + +By the end of this exercise, you will have created a backlog of GitHub issues for use throughout the remainder of the lab. + +## What is agent mode and Model Context Protocol (MCP)? + +Agent mode in GitHub Copilot Chat transforms Copilot into an AI agent that can perform actions on your behalf. This mode allows you to interact with Copilot in a more dynamic way, enabling it to use tools and execute tasks, like running tests or terminal commands, reading problems from the editor, and using those insights to update your code. This allows for a more interactive and collaborative workflow, enabling you to leverage the capabilities of AI in your development process. + +[Model Context Protocol (MCP)][mcp-blog-post] provides AI agents with a way to communicate with external tools and services. By using MCP, AI agents can communicate with external tools and services in real-time. This allows them to access up-to-date information (using resources) and perform actions on your behalf (using tools). + +These tools and resources are accessed through an MCP server, which acts as a bridge between the AI agent and the external tools and services. The MCP server is responsible for managing the communication between the AI agent and the external tools (such as existing APIs or local tools like NPM packages). Each MCP server represents a different set of tools and resources that the AI agent can access. + +![Diagram showing the inner works of agent mode and how it interacts with context, LLM and tools - including tools contributed by MCP servers and VS Code extensions]((../images/)ex1-mcp-diagram.png) + +A couple of popular existing MCP servers are: + +- **[GitHub MCP Server][github-mcp-server]**: This server provides access to a set of APIs for managing your GitHub repositories. It allows the AI agent to perform actions such as creating new repositories, updating existing ones, and managing issues and pull requests. +- **[Playwright MCP Server][playwright-mcp-server]**: This server provides browser automation capabilities using Playwright. It allows the AI agent to perform actions such as navigating to web pages, filling out forms, and clicking buttons. + +There are many other MCP servers available that provide access to different tools and resources. GitHub hosts an [MCP registry][mcp-registry] to enhance discoverability and contributions to the ecosystem. + +> [!IMPORTANT] +> With regard to security, treat MCP servers as you would any other dependency in your project. Before using an MCP server, carefully review its source code, verify the publisher, and consider the security implications. Only use MCP servers that you trust and be cautious about granting access to sensitive resources or operations. + +## Ensure your codespace is ready + +In a [prior exercise][prereqs-lesson] you launched the codespace you'll use for the remainder of the coding exercises in this lab. Let's put the final touches on it before you begin using it. + +The setup process for the codespace installed and setup many [VS Code extensions][vscode-extensions]. As with any software, updates may be needed. When your codespace is created you'll need to ensure everything is up-to-date. + +1. Return to the tab where you started your codespace. If you closed the tab, return to your repository, select **Code** > **Codespaces** and then the name of the codespace. +2. Select **Extensions** on the workbench on the left side of your codespace. + + ![Screenshot of the extensions window with multiple extensions showing either Update or Reload Window buttons]((../images/)ex1-extensions-updates.png) + +3. Select **Update** on any extensions with an **Update** button. Repeat as necessary. +4. Select **Reload Window** on any extensions with a **Reload Window** button to reload the codespace. +5. When prompted by a dialog, select **Reload** to reload the window. This will ensure the latest version is being used. + +## Using GitHub Copilot Chat and agent mode + +To access GitHub Copilot Chat agent mode, you need to have the GitHub Copilot Chat extension installed in your IDE, which should already be the case if you are using a GitHub Codespace. + +> [!TIP] +> If you do not have the GitHub Copilot Chat extension installed, you can [install it from the Visual Studio Code Marketplace][copilot-chat-extension]. Or open the Extensions view in Visual Studio Code, search for **GitHub Copilot Chat**, and select **Install**. + +Once you have the extension installed, you may need to authenticate with your GitHub account to enable it. + +1. Return to your codespace. +2. If you don't already see Copilot Chat on the right side of your editor, select the **Copilot Chat** icon at the top of your codespace. +3. Type a message like "Hello world" in the Copilot Chat window and press enter. This should activate Copilot Chat. +4. Alternatively, if you are not authenticated you will be prompted to sign in to your GitHub account. Follow the instructions to authenticate. + + ![Example of Copilot Chat authentication prompt]((../images/)ex1-copilot-authentication.png) + +5. After authentication, you should see the Copilot Chat window appear. +6. Switch to agent mode by selecting the dropdown in the Copilot Chat window and selecting **Agent**. + + ![Example of switching to agent mode]((../images/)shared-agent-mode-dropdown.png) + +7. Set the model to **Claude Sonnet 4.5**. + + ![Example of selecting the Claude Sonnet 4.5 model]((../images/)ex1-select-model.png) + +> [!IMPORTANT] +> The authors of this workshop are not indicating a preference towards one model or another. When building this workshop, we used Claude Sonnet 4.5, and as such are including that in the instructions. The hope is the code suggestions you receive will be relatively consistent to ensure a good experience. However, because LLMs are probabilistic, you may notice the suggestions received differ from what is indicated in the workshop. This is perfectly normal and expected. + +8. The chat pane should update to indicate that you are now in agent mode. You should see a tools icon on the same line as the mode and model, which you utilized earlier, showing that you can configure tools for GitHub Copilot to use. + +Typically, the number of tools available will be set to 0 when setting up a new project, as you have not configured any MCP servers yet. But to help you get started, the project has a **.vscode/mcp.json** file with an example configuration for the [GitHub MCP server][github-mcp-server]. Let's go and explore that next. + +## Setting up the GitHub MCP server + +The **.vscode/mcp.json** file is used to configure the MCP servers that are available in this Visual Studio Code workspace. The MCP servers provide access to external tools and resources that GitHub Copilot can use to perform actions on your behalf. + +1. Open **.vscode/mcp.json** file in your repository. +2. You should see a JSON structure similar to the following: + + ```json + { + "servers": { + "github": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/" + } + } + } + ``` + +This configuration provides GitHub Copilot access to several additional tools so that it can interact with GitHub repositories, issues, pull requests, and more. This particular configuration uses the [remote GitHub MCP server][remote-github-mcp-server]. By using this approach, you don't need to worry about running the MCP server locally (and the associated management, like keeping it up to date), and you can authenticate to the remote server using OAuth 2.0 instead of a personal access token (PAT). + +The MCP server configuration is defined in the **servers** section of the **mcp.json** file. Each MCP server is defined by a unique name (in this case, github) and its type (in this case, **http**). When using local MCP servers, the type may be **stdio** and have a **command** and **args** field to specify how to start the MCP server. You can find out more about the configuration format in the [VS Code documentation][vscode-mcp-config]. In some configurations (not for the remote GitHub MCP server with OAuth), you may also see an **inputs** section. This defines any inputs (like sensitive tokens) that the MCP server may require. You can read more about the configuration properties in the [VS Code documentation][vscode-mcp-config] + +To utilize an MCP server it needs to be "started". This will allow GitHub Copilot to communicate with the server and perform the tasks you request. + +> [!NOTE] The exact authentication flow may vary a little bit. + +1. Inside VS Code, open **.vscode/mcp.json**. +2. To start the GitHub MCP server, select **Start** above the GitHub server. + + ![The start button above the GitHub MCP server entry]((../images/)ex1-start-mcp-server.png) + +3. You should see a popup asking you to authenticate to GitHub. + + ![A popup showing that the GitHub MCP server wants to authenticate to GitHub]((../images/)ex1-mcp-auth-popup.png) + +4. Select **Continue** on the user account that you're using for this lab. + + ![A popup showing the user account selection for GitHub authentication]((../images/)ex1-mcp-select-account.png) + +5. If the page appears, select **Authorize visual-studio-code** to allow the GitHub MCP server to login as your selected user account. Once complete, the page should say "You can now close the window.". + + ![A popup showing the authorization for visual-studio-code app]((../images/)ex1-mcp-authorize-vscode.png) + +6. After navigating back to the GitHub Codespace, you should see that the GitHub MCP server has started. You can check this in two places: + - The line in **.vscode/mcp.json** which previously said start should now present several options, and show a number of tools available. + - Select the tools icon in the Copilot Chat pane to see the tools available. Scroll down the list that appears at the top of the screen, and you should see a list of tools from the GitHub MCP server. + +That's it! You can now use Copilot Chat in agent mode to create issues, manage pull requests, and more. + +## Creating a backlog of tasks + +Now that you have set up the GitHub MCP server, you can use Copilot Agent mode to create a backlog of tasks for use in the rest of the lab. + +1. Return to the Copilot Chat pane. Ensure **Agent** is selected for the mode and **Claude Sonnet 4.5** is selected for the model. +2. Type or paste the following prompt to create the issues you'll be working on in the lab: + + ```markdown + In my GitHub repo, create GitHub issues for our Tailspin Toys backlog. Each issue should include: + - A clear title + - A brief description of the task and why it is important to the project + - A checkbox list of acceptance criteria + + From our recent planning meeting, the upcoming backlog includes the following tasks: + + 1. Allow users to filter games by category and publisher + 2. Update our repository coding standards (including rules about Python formatting and docstrings) in a custom instructions file + 3. Stretch Goal: Implement pagination on the game list page + ``` + +3. Press enter or select the **Send** button to send the prompt to Copilot. +4. GitHub Copilot should process the request and respond with a dialog box asking you to confirm the creation of the issues. + + ![Example of Copilot Chat dialog box asking for confirmation to run the create issue command]((../images/)ex1-create-issue-dialog.png) + +> [!IMPORTANT] +> Remember, AI can make mistakes, so make sure to review the issues before confirming. + +5. Select **see more** in **Run open new issue** box to see the details of the issue that will be created. +6. Ensure the details in the **owner** and **repo**, **title** and **body** of the issue look correct. You can make any desired edits by double clicking the body and updating the content with the correct information. +7. After reviewing the generated content, select **Continue** to create the issue. + + ![Example of the expanded dialog box showing the GitHub Issue that will be created]((../images/)ex1-create-issue-review.png) + +8. Repeat steps 4-6 for the remainder of the issues. Alternatively, if you are comfortable with Copilot automatically creating the issues you can select the down-arrow next to **Continue** and select **Allow in this session** to allow Copilot to create the issues for this session (the current chat). + + ![Example of allowing Copilot to automatically create issues]((../images/)ex1-create-issue-allow.png) + +> [!IMPORTANT] +> Ensure you are comfortable with Copilot automatically performing tasks on your behalf before you selecting **Allow in this session** or a similar option. + +9. In a separate browser tab, navigate to your GitHub repository and select the issues tab. +10. You should see a list of issues that have been created by Copilot. Each issue should include a clear title and a checkbox list of acceptance criteria. + +You should notice that the issues are fairly detailed. This is where you benefit from the power of Large Language Models (LLMs) and Model Context Protocol (MCP), as it has been able to create a clear initial issue description. + +![Example of issues created in GitHub]((../images/)ex1-github-issues-created.png) + +## Summary and next steps + +Congratulations, you have created issues on GitHub using Copilot Chat and MCP! + +To recap, in this exercise you: + +- used Model Context Protocol (MCP), which provides access to external tools and capabilities. +- set up the GitHub MCP server in your repository. +- used GitHub Copilot Chat agent mode to create issues in your repository. + +With the GitHub MCP server configured, you can now use GitHub Copilot Chat Agent Mode to perform additional actions on your behalf, like creating new repositories, managing pull requests, and searching for information across your repositories. + +You can now continue to the next exercise, where you will learn how to [provide Copilot guidance with custom instructions][next-lesson] to ensure code is generated following your organization's defined patterns and practices. + +### Optional exploration exercise – Set up the Microsoft Playwright MCP server + +If you are feeling adventurous, you can try installing and configuring another MCP server, such as the [Microsoft Playwright MCP server][playwright-mcp-server]. This will allow you to use GitHub Copilot Chat Agent Mode to perform browser automation tasks, such as navigating to web pages, filling out forms, and clicking buttons. + +You can find the instructions for installing and configuring the Playwright MCP server in the [Playwright MCP repository][playwright-mcp-server]. + +Notice that the setup process is similar to the GitHub MCP server, but you do not need to provide any credentials like the GitHub Personal Access Token. This is because the Playwright MCP server does not require authentication to access its capabilities. + +## Resources + +- [What the heck is MCP and why is everyone talking about it?][mcp-blog-post] +- [GitHub MCP Server][github-mcp-server] +- [Microsoft Playwright MCP Server][playwright-mcp-server] +- [GitHub MCP Registry][mcp-registry] +- [VS Code Extensions][vscode-extensions] +- [GitHub Copilot Chat Extension][copilot-chat-extension] + +--- + +| [← Custom instructions][previous-lesson] | [Next lesson: Agent mode β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ../shared/1-custom-instructions.md +[next-lesson]: ./3-agent-mode.md +[prereqs-lesson]: ../shared/0-prereqs.md +[mcp-blog-post]: https://github.blog/ai-and-ml/llms/what-the-heck-is-mcp-and-why-is-everyone-talking-about-it/ +[github-mcp-server]: https://github.com/github/github-mcp-server +[playwright-mcp-server]: https://github.com/microsoft/playwright-mcp +[mcp-registry]: https://github.com/mcp +[vscode-extensions]: https://code.visualstudio.com/docs/configure/extensions/extension-marketplace +[copilot-chat-extension]: https://marketplace.visualstudio.com/items?itemName=GitHub.copilot +[remote-github-mcp-server]: https://github.blog/changelog/2025-06-12-remote-github-mcp-server-is-now-available-in-public-preview/ +[vscode-mcp-config]: https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_configuration-format diff --git a/workshop-content/vscode/3-agent-mode.md b/workshop-content/vscode/3-agent-mode.md new file mode 100644 index 00000000..524eb8a6 --- /dev/null +++ b/workshop-content/vscode/3-agent-mode.md @@ -0,0 +1,224 @@ +# Exercise 3 - Adding new functionality with Copilot Agent Mode + +| [← MCP][previous-lesson] | [Next lesson: Coding agent β†’][next-lesson] | +|:--|--:| + +Even the simplest of updates to an application typically require updates to multiple files and operations to be performed like running tests. As a developer your flow typically involves tracking down all the necessary files, making the changes, running the tests, debugging, figuring out which file was missed, making another update... The list goes on and on. + +This is where Copilot Agent Mode comes into play. + +Copilot Agent Mode is built to act more autonomously in your IDE. It behaves in a similar fashion to a developer, starting by exploring the existing project structure, performing the necessary updates, running tasks like tests, and automatically fixing any discovered mistakes. Let's explore how you can use Agent Mode to introduce new functionality to your site. + +> [!NOTE] +> While the names are similar, agent mode and coding agent are built for two different types of experiences. Agent mode performs its tasks in your IDE, allowing for quick feedback cycles and interaction. Coding agent is designed as a peer programmer, working asynchronously like a member of the team, interacting with you via issues and pull requests. + +In this exercise, you will learn how: + +- Copilot Agent Mode can explore your project, identify relevant files, and make coordinated changes. +- GitHub Copilot Agent Mode can implement new features across both backend and frontend codebases. +- to review changes and tests generated by Copilot Agent Mode before merging into your codebase. + +## Scenario + +As the list of games grows, you want to allow users to filter by category. This will require updating both the API and UI, and updating the tests for the API. With the help of Copilot Agent Mode you'll work with your AI pair programmer to add the new feature! + +## Running the Tailspin Toys website + +Before you make any changes, let's explore the Tailspin Toys website to understand its current functionality. + +The website is a crowdfunding platform for board games with a developer theme. It allows users to list games and display details about them. The website has two main components: the front-end (written in Svelte) and the backend (written in Python). + +### Starting the website + +To make running the website easier, a script has been provided that will start both the front-end and back-end servers. You can run this script in your GitHub Codespace to start the website with the following instructions: + +1. Return to your codespace. You'll continue working in your current branch. +2. Open a new terminal window inside your codespace by selecting Ctl + \`. +3. Run the following script to start the website: + + ```bash + scripts/start-app.sh + ``` + + Once the script is running, you should see output indicating that both the front-end and back-end servers are running, similar to the below: + + ```bash + Server (Flask) running at: http://localhost:5100 + Client (Astro) server running at: http://localhost:4321 + ``` + +> [!NOTE] +> If a dialog box opens prompting you to open a browser window for `http://localhost:5100` close it by selecting the **x**. + +4. Open the website by using Ctrl-**Click** (or Cmd-**Click** on a Mac) on the client address `http://localhost:4321` in the terminal. + +> [!NOTE] +> When using a codespace, selecting a link for the localhost URL from the Codespace terminal will automatically redirect you to `https://-4321.app.github.dev/`. This is a private tunnel to your codespace, which is now hosting your web server! + +### Exploring the website + +Once the website is running, you can explore its functionality. The main features of the website include: + +- **Home Page**: Displays a list of board games with their titles, images, and descriptions. +- **Game Details Page**: When you select a game, you'll be brought to a details page with more information about the game, including its title, description, publisher and category. + +## Explore the backlog with Copilot + +The initial implementation of the website is functional, but we want to enhance it by adding new capabilities. Let's start off by reviewing the backlog. Ask GitHub Copilot to show you the backlog of items that we created in the previous exercise. + +1. Return to your codespace. +2. Open **Copilot Chat**. +3. Create a new chat session by selecting the **New Chat** button, which will remove any previous context. +4. Ensure **Agent** is selected from the list of modes. +5. Select **Claude Sonnet 4.5** from the list of available models. + +> [!IMPORTANT] +> The authors of this lab are not indicating a preference towards one model or another. When building this lab, we used Claude Sonnet 4.5, and as such are including that in the instructions. The hope is the code suggestions you receive will be relatively consistent to ensure a good experience. However, because LLMs are probabilistic, you may notice the suggestions received differ from what is indicated in the lab. This is perfectly normal and expected. + +6. Open the GitHub Copilot Chat, if it is not already open. +7. Switch to **Agent** mode, if you are not already in agent mode. +8. Select **Claude Sonnet 4.5** from the list of available models. + +> [!IMPORTANT] +> The authors of this lab are not indicating a preference towards one model or another. When building this lab, we used Claude Sonnet 4.5, and as such are including that in the instructions. The hope is the code suggestions you receive will be relatively consistent to ensure a good experience. However, because LLMs are probabilistic, you may notice the suggestions received differ from what is indicated in the lab. This is perfectly normal and expected. + +> [!NOTE] +> Because of the probabilistic nature of LLMs, Copilot may utilize a different MCP command, but should still be able to complete the task. + +8. Ask Copilot about the backlog of issues by sending the following prompt to Copilot: + + ```plaintext + Please show me the backlog of items from my GitHub repository. Help me prioritize them based on those which will be most useful to the user. + ``` +9. Select **Continue** to run the command to list all issues. +10. Review the generated list of issues. + +Notice how Copilot has even prioritized the items for you, based on the ones that it thinks will be most useful to the user. + +## Review instructions files + +Before kicking off the agent to generate the code, it's a good time to review the instructions file you'll use to provide Copilot context for its work. You're going to take advantage of the [user interface (UI)](../.github/instructions/ui.instructions.md) file, which contains context on how to approach adding functionality to the website. + +1. In your codespace, navigate to **.github/instructions/ui.instructions.md**. +2. Take note of the overall guidance on how to approach adding functionality. This includes: + - An overview of the architecture. + - Principles for component design, testability and accessibility. + - Links to specific instructions files for various file types, including: + - Astro + - Svelte + - Tailwind CSS + +> [!TIP] +> Instructions files allow you to reference both other instructions files and files in your project. The paths are relative to the location of the instructions file. This allows for reuse, breaking down complex instructions into smaller more manageable chunks, and providing examples and templates. + +## Implement the filtering functionality + +To implement filtering, no less than three separate updates will need to be made to the application: + +- A new endpoint added to the API +- A new set of tests for the new endpoint +- Updates to the UI to introduce the functionality + +In addition, the tests need to run (and pass) before you merge everything into your codebase. Copilot Agent Mode can perform these tasks for you! Let's add the functionality. + +1. You can continue in the current conversation with Copilot, or start a new one by selecting **New Chat**. +2. Select **Add Context**, **Instructions**, and **ui** as the instructions file. + + ![Screenshot showing an example of selecting the UI instructions file]((../images/)ex3-select-instructions-file.png) + +3. Ensure **Agent** mode is still selected. + + ![Screenshot of Copilot Chat mode selection with Agent highlighted](./(../images/)shared-agent-mode-dropdown.png) + +4. Ensure **Claude Sonnet 4.5** is still selected for the model. +5. Prompt Copilot to implement the functionality based on the issue you created earlier by using the following prompt: + + ```plaintext + Please update the site to include filtering by publisher and category based on the requirements from the related GitHub issue in the backlog. Ensure all tests are passing before completion. The server is already running, so you do not need to start it up. + ``` + +6. Watch as Copilot begins by exploring the project, locating the files associated with the desired functionality. You should see it finding both the API and UI definitions, as well as the tests. It then begins modifying the files and running the tests. + + ![Screenshot showing Copilot exploring the project files]((../images/)ex3-agent-mode-explores.png) + +> [!NOTE] +> You will notice that Copilot will perform several tasks, like exploring the project, modifying files, and running tests. It may take a few minutes depending on the complexity of the task and the codebase. During that process, you may notice **Keep** and **Undo** buttons appear in the code editor. When Copilot is finished, you will have a **Keep** or **Undo** for all of the changes, so you do not need to select them while work is in progress. + +7. As prompted by Copilot, select **Continue** to run the tests. + + ![Screenshot showing a dialog in the Copilot Chat pane asking the user to confirm they are happy to run tests]((../images/)ex3-agent-mode-run-tests.png) + +8. You may experience some pauses and even see some tests fail throughout the process. That's okay! Copilot works back and forth between code generation and tests until it completes the task and doesn't detect any errors. + + ![Screenshot showing a complete Chat session with Copilot Agent Mode]((../images/)ex3-agent-mode-proposed-changes.png) + +9. Explore the generated code for any potential issues. + +> [!IMPORTANT] +> Remember, it's always important to review the code that Copilot or any AI tools generate. + +10. Return to the browser with the website running. Explore the new functionality! +11. Once you've confirmed everything works and reviewed the code, select **Keep** in the Copilot Chat window. + +## Publish the branch and create a pull request + +With your changes created locally you're ready to create a pull request (PR) to allow for your team to review your suggested changes and work through your DevOps process. The first step in that process is to publish the branch. Let's take care of that first. + +1. Navigate to the **Source Control** panel in the Codespace and review the changes made by Copilot. +2. Stage the changes by selecting the **+** icon. +3. Generate a commit message using the **Sparkle** button. + + ![Screenshot of the Source Control panel showing the changes made]((../images/)ex3-source-control-changes.png) + +4. Select **Publish** to push the branch to your repository. + +## Create the pull request + +There are several ways to create a pull request, including through github.com and the GitHub command-line interface (CLI). But since you're already working with GitHub Copilot, let's let it create the PR for you! You can have it find the relevant issue and create the PR with an association to the located issue. + +1. Navigate to the Copilot Chat panel and select **New Chat** to start a new session. +2. Ask Copilot to create a PR for you: + + ```plaintext + Find the issue in the repo related to filtering by category and publisher. Create a new pull request for the current add-filters branch, and associate it with the correct issue. + ``` + +3. As needed, select **Continue** to allow Copilot to perform the tasks necessary to gather information and perform operations. +4. Notice how Copilot searches through the issues, finds the right one, and creates the PR. +5. Select the link generated by Copilot to review your pull request, but please **don't merge it yet**. + +## Summary and next steps + +Congratulations! In this exercise, we explored how to use GitHub Copilot Agent Mode to add new capabilities to the Tailspin Toys website. We learned how: + +- GitHub Copilot Agent Mode can implement new features across both backend and frontend codebases. +- Copilot Agent Mode can explore your project, identify relevant files, and make coordinated changes. +- to review changes and tests generated by Copilot Agent Mode before merging into your codebase. + +Now let's [return to our coding agent][next-lesson] to see how well it did with the issues we assigned to it. + +### Bonus exploration exercise – Implement paging + +As the list of games grows there will be a need for paging to be enabled. Using the skills you learned in this exercise, prompt Copilot to update the site to implement paging. Some considerations for the code include: + +- follow the existing best practices, including using the existing instructions files. +- consider how you want paging implemented, if you want to allow the user to select the page size or for it to be hard-coded. +- as you create the prompt ensure you provide Copilot with the necessary guidance to create the implementation as you desire. +- you may need to iterate with GitHub Copilot, asking for changes and providing context. This is the normal flow when working with Copilot! + +## Resources + +- [Coding agent 101][coding-agent-101] +- [Copilot ask, edit, and agent modes: What they do and when to use them][choose-mode] +- [Agent mode in VS Code][vs-code-agent-mode] + +--- + +| [← MCP][previous-lesson] | [Next lesson: Coding agent β†’][next-lesson] | +|:--|--:| + +[previous-lesson]: ./2-mcp.md +[next-lesson]: ./4-coding-agent.md +[coding-agent-101]: https://github.blog/ai-and-ml/github-copilot/agent-mode-101-all-about-github-copilots-powerful-mode/ +[choose-mode]: https://github.blog/ai-and-ml/github-copilot/copilot-ask-edit-and-agent-modes-what-they-do-and-when-to-use-them/ +[vs-code-agent-mode]: https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode diff --git a/workshop-content/vscode/4-coding-agent.md b/workshop-content/vscode/4-coding-agent.md new file mode 100644 index 00000000..2a3beaa0 --- /dev/null +++ b/workshop-content/vscode/4-coding-agent.md @@ -0,0 +1,205 @@ +# Exercise 4 - GitHub Copilot coding agent + +| [← Previous lesson: Copilot agent mode][previous-lesson] | [Next lesson: Reviewing coding agent β†’][next-lesson] | +|:--|--:| + +There are likely very few, if any, organizations who don't struggle with tech debt. This could be unresolved security issues, legacy code requiring updates, or feature requests which have languished on the backlog because there just wasn't the time to implement them. GitHub Copilot's coding agent is built to perform tasks such as updating code and adding functionality, all in an autonomous fashion. Once the agent completes its work, it generates a draft PR ready for a human developer to review. This allows offloading of tedious tasks and an acceleration of the development process, and frees developers to focus on larger picture items. + +You'll explore the following with Copilot coding agent: + +- customizing the environment for generating code. +- ensuring operations are performed securely. +- the importance of clearly scoped issues. +- assigning issues to Copilot. + +## Scenarios + +Tailspin Toys has some tech debt they'd like to address. The contractors initially hired to create the first version of the site left the documentation in an unideal state - and by that you'll notice it's completely lacking. As a first step, they'd like to see docstrings or the equivalent added to all functions in the application. + +Additionally, the design team is ready to get to work on building the UX for managing games. They don't need a full implementation yet, but they at least need some endpoints they can use for testing. Specifically, they need endpoints for the games API which will allow them to create, update and delete games. This is currently a blocker, but there are other issues which are of higher priority at the moment. + +These are both examples of tasks which can quickly find themselves deprioritized, and are great to assign to Copilot coding agent. Copilot coding agent can then work on them asynchronously, allowing the developer to focus on other tasks, then return to review Copilot's work and ensure everything is as expected. + +## Introducing GitHub Copilot coding agent + +[GitHub Copilot coding agent][coding-agent-overview] can perform tasks in the background, much in the same way a human developer would. And, just like with working with a human developer, this can be done in multiple ways, including [assigning a GitHub issue to Copilot][assign-issue]. Once assigned, Copilot will create a draft pull request to track its progress, setup an environment, and begin working on the task. You can dig into Copilot's session while it's still in flight or after its completed. Once its ready for you to review the proposed solution, it'll tag you in the pull request! + +## The importance of well-scoped instructions + +While it can often feel like it, there is no magic in GitHub Copilot. There are no magic solutions available, where you can with just a couple of sentences snap your fingers and let AI perform the entire task for you. In fact, even seemingly straight-forward operations can often have fair amount of complexity when you peel back the layers. + +As a result, you want to [be mindful about how you approach assigning tasks to Copilot coding agent][coding-agent-best-practices]. Working with Copilot as an AI pair programmer is typically the best approach. Approach tasks, big and small, following the same strategy you would without Copilot - work in stages, learn, experiment, and adapt accordingly. + +As always, the fundamentals of software development do not change with the addition of generative AI. + +## Setting up the dev environment for the Copilot coding agent + +Creating code, regardless of who's involved, typically requires a specific environment and some setup scripts to be run to ensure everything is in a good state. This holds true when assigning tasks to Copilot, which is performing tasks in a similar fashion to a SWE. + +Coding agent uses [GitHub Actions][github-actions] for its environment when doing its work. You can customize this environment by creating a [special setup workflow][setup-workflow], configured in the **.github/workflows/copilot-setup-steps.yml** file, to run before it gets to work. This enables it to have access to the required development tools and dependencies. This has been pre-configured ahead of the lab to help the lab flow and allow this learning opportunity. It makes sure that Copilot had access to Python, Node.JS, and the required dependencies for the client and server: + +```yaml +name: "Copilot Setup Steps" + +# Allows you to test the setup steps from your repository's "Actions" tab +on: workflow_dispatch + +jobs: + copilot-setup-steps: + runs-on: ubuntu-latest + # Set the permissions to the lowest permissions possible needed for *your steps*. Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v5 + + # Backend setup - Python + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + cache: "pip" + + - name: Install Python dependencies + run: ./scripts/setup-env.sh + + # Frontend setup - Node.js + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: "22" + cache: "npm" + cache-dependency-path: "./client/package.json" + + - name: Install JavaScript dependencies + working-directory: ./client + run: npm ci + + - name: Install Playwright + working-directory: ./client + run: npx playwright install +``` + +It looks like any other GitHub workflow file, but it has a few key points: + +- It contains a single job called **copilot-setup-steps**. This job is executed in GitHub Actions before Copilot starts working on the pull request. +- Notice the **workflow_dispatch** trigger, which allows you to run the workflow manually from the Actions tab of your repository. This is useful for testing that the workflow runs successfully instead of waiting for Copilot to run it. + +## Adding documentation + +While everyone understands the importance of documentation, most projects have either outdated information or lack it altogether. This is the type of tech debt which often goes unaddressed, slowing productivity and making it more difficult to maintain the codebase or bring new developers into the team. Fortunately, Copilot shines at creating documentation, and this is a perfect issue to assign to Copilot coding agent. It'll work in the background to generate the necessary documentation. In a future exercise you'll return to review its work. + +1. Navigate to your repository on github.com in a new browser tab. +2. Select the **Issues** tab. +3. Select **New issue** to open the new issue dialog. +4. Select **Blank issue** to create the new issue. +5. Set the **Title** to `Code lacks documentation`. +6. Set the **Description** to: + + ```plaintext + Our organization has a requirement that all functions have docstrings or the language equivalent. Unfortunately, recent updates haven't followed this standard. We need to update the existing code to ensure docstrings (or the equivalent) are included with every function or method. + ``` + +7. Select **Create** to create the issue. +8. On the right side, select **Assign to Copilot** to open the assignment dialog. + + ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + +9. Select **Assign**. + + ![Copilot assignment details]((../images/)ex4-assign-copilot-details.png) + +10. Select the **Pull Requests** tab. +11. Open the newly generated pull request (PR), which will be titled something similar to **[WIP]: Code lacks documentation**. If a new PR doesn't appear on the list, wait for a moment or two and refresh the browser window. +12. After a few minutes, you should see that Copilot has created a todo list. + +> [!NOTE] +> It make take several minutes for the todo list from Copilot to appear in the PR. Copilot is creating its environment (running the workflow highlighted previously), analyzing the project, and determining the best approach to tackling the problem. + +13. Review the list and the tasks it's going to complete. +14. Scroll down the pull request timeline, and you should see an update that Copilot has started working on the issue. +15. Select the **View session** button. + + ![Copilot session view]((../images/)ex4-view-session.png) + +> [!IMPORTANT] +> You may need to refresh the window to see the updated indicator. + +16. Notice that you can scroll through the live session, and how Copilot is solving the problem. That includes exploring the code and understanding the state, how Copilot pauses to think and decide on the appropriate plan and also creating code. + +This will likely take several minutes. One of the primary goals of Copilot coding agent is to allow it to perform tasks asynchronously, freeing us to focus on other tasks. We're going to take advantage of that very feature by both assigning another task to Copilot coding agent, then turning our attention to writing some code to add features to our application. + +## Create new endpoints to modify games + +As has been highlighted, one of the great advantages of GitHub Copilot coding agent is the ability to divide work, where you can focus on one set of tasks while it focuses on another. While creating the endpoints for modifying games for the design team might not necessarily take a long time, it's still time which could be used for other tasks. Let's assign it to Copilot coding agent! + +1. Return to your repository on github.com. +2. Select the **Issues** tab. +3. Select **New issue** to open the new issue dialogue. +4. Select **Blank issue** to use the blank template. +5. Set the **Title** to: `Add endpoints to create and edit games` +6. Set the **Description** to: + + ```markdown + We're going to be creating functionality in the future to allow for the submission (and editing) of games. For now we just want the endpoints so we can explore how we want to create the UX and do some acceptance testing. Our requirements are: + + - Add new endpoints to the Games API to support creating, updating and deleting games + - There should be appropriate error handling for all new endpoints + - There should be unit tests created for all new endpoints + - Before creating the PR, ensure all tests pass + ``` + +7. Select **Create** to create the issue. +8. On the right side, select **Assign to Copilot** to open the assignment dialog. + + ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + +9. Select **Assign**. + +Shortly after, you should see a set of πŸ‘€ on the first comment in the issue, indicating Copilot is on the job! + +![Copilot uses the eyes emoji to indicate it's working on the issue]((../images/)ex4-issue-eyes-emoji.png) + +9. Select **Assign** to assign the issue to Copilot coding agent. + +Copilot is now diligently working on your second request! Copilot coding agent works in a similar fashion to a SWE, so you don't need to actively monitor it, but instead review once it's completed. Let's turn your attention to writing code and adding other features. + +## Summary and next steps + +With coding agent working diligently in the background, you can now turn your attention to your next lesson, [creating and using custom agents][next-lesson]. [Copilot coding agent can also use MCP servers][coding-agent-mcp], and has custom instructions available to it, which you explored in earlier modules. + +## Summary and next steps + +This lesson explored [GitHub Copilot coding agent][copilot-agents], your AI peer programmer. With coding agent you can assign issues to Copilot to perform asynchronously. You can use Copilot to address tech debt, create new features, or aid in migrating code from one framework to another. + +You explored these concepts: + +- customizing the environment for generating code. +- ensuring operations are performed securely. +- the importance of clearly scoped issues. +- assigning issues to Copilot. + +With coding agent working diligently in the background, we can now turn our attention to our next lesson, [creating and using custom agents][next-lesson]. [Copilot coding agent can also use MCP servers][coding-agent-mcp], and has custom instructions available to it, which we explored in earlier modules. + +## Resources + +- [About Copilot coding agent][copilot-agents] +- [Assigning GitHub issues to Copilot][assign-issue] +- [Copilot coding agent setup workflow best practices][coding-agent-best-practices] + +--- + +| [← Previous lesson: Copilot agent mode][previous-lesson] | [Next lesson: Custom agents β†’][next-lesson] | +|:--|--:| + +[coding-agent-overview]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot#overview-of-copilot-coding-agent +[coding-agent-mcp]: https://docs.github.com/copilot/how-tos/agents/copilot-coding-agent/extending-copilot-coding-agent-with-mcp +[assign-issue]: https://docs.github.com/copilot/using-github-copilot/coding-agent/using-copilot-to-work-on-an-issue +[setup-workflow]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#pre-installing-dependencies-in-github-copilots-environment +[copilot-agents]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot +[coding-agent-best-practices]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks +[github-actions]: https://docs.github.com/actions +[next-lesson]: ./5-custom-agents.md +[previous-lesson]: ./3-agent-mode.md diff --git a/workshop-content/vscode/5-custom-agents.md b/workshop-content/vscode/5-custom-agents.md new file mode 100644 index 00000000..7d35afad --- /dev/null +++ b/workshop-content/vscode/5-custom-agents.md @@ -0,0 +1,94 @@ +# Exercise 5 - Custom agents + +| [← Previous lesson: GitHub Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | +|:--|--:| + +[Custom agents][custom-agents] in GitHub Copilot allow you to create specialized AI assistants tailored to specific tasks or domains within your development workflow. By defining agents through markdown files in the `.github/agents` folder of your repository, you can provide Copilot with focused instructions, best practices, coding patterns, and domain-specific knowledge that guide it to perform particular types of work more effectively. This allows teams to codify their expertise and standards into reusable agents. You might create an accessibility agent that ensures [WCAG][wcag] compliance, a security agent that follows secure coding practices, or a testing agent that maintains consistent test patternsβ€”enabling developers to leverage these specialized capabilities on-demand for faster, more consistent implementations. + +You'll explore the following with custom agents: + +- how to create a custom agent. +- assigning a task to a custom agent. + +## Scenario + +Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. + +Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. + +## Custom agents + +Custom agents are defined by markdown files in the **.github/agents** folder of your project. The markdown files will contain guidance for Copilot on how best to perform at task. + +## Reviewing the accessibility custom agent + +A custom agent has already been created for you for accessibility. Let's review the contents to understand how it will guide Copilot. + +1. Return to your codespace. +2. Open **.github/agents/accessibility.md**. +3. Note the header section with the name and description of the agent. + +> [!IMPORTANT] +> This section is required for custom agents. + +4. From there, scan and review the next sections which highlight: + - Core responsibilities when generating code for an accessible website. + - Best practices for accessibility. + - Code examples for HTML, CSS and JavaScript. + - A list of common pitfalls and mistakes. + +> [!NOTE] +> There is no "best markdown" for a custom agent. As with anything in AI, you will want to test and explore to determine what works best for your environments and scenarios. + +## Create and assign an issue + +Mission control is the central location for working with all agents for your environment. You can assign tasks to Copilot coding agent, monitor tasks, and even redirect and provide additional guidance. Let's start by assigning a task to create the high contrast mode to Copilot. + +1. Navigate to your repository. +2. Select the issues tab. +3. Select **New issue** to open the new issue dialog. +4. Select **Blank issue** to create the new issue. +5. Set the **Title** to `Add high contrast mode to website`. +6. Set the **Description** to: + + ```plaintext + We need a high contrast mode for the site. There should be a toggle for high contrast which the user can set. It should store the setting in local storage on the browser. + ``` + +7. Select **Create** to create the issue. +8. On the right side, select **Assign to Copilot** to open the assignment dialog. +9. Select **Accessibility agent** from the list of custom agents. + + ![Screenshot of coding agent assignment, with custom agent and accessibility highlighted](./(../images/)ex5-select-custom-agent.png) + +10. Select **Assign**. +11. Copilot gets to work on the task in the background! + +## Summary and next steps + +This lesson explored [custom agents][custom-agents] in GitHub Copilot, specialized AI assistants tailored to specific tasks and domains. With custom agents you can codify your team's expertise and standards into reusable agents that guide Copilot to perform particular types of work more effectively. + +You explored these concepts: + +- how to create a custom agent. +- assigning a task to a custom agent. + +With Copilot working on implementing the high contrast mode, we can now turn our attention to our next lesson, [using Copilot HQ to monitor and guide agent sessions][next-lesson]. Custom agents help ensure that Copilot follows your organization's best practices and domain-specific requirements, enabling faster and more consistent implementations across your team. + +## Resources + +- [Custom agents][custom-agents] +- [Preparing to use custom agents in your organization][org-custom-agents] +- [Preparing to use custom agents in your enterprise][enterprise-custom-agents] + +--- + +| [← Previous lesson: GitHub Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | +|:--|--:| + +[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents +[wcag]: https://www.w3.org/WAI/standards-guidelines/wcag/ +[org-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-organization/prepare-for-custom-agents +[enterprise-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/prepare-for-custom-agents +[next-lesson]: ./6-managing-agents.md +[previous-lesson]: ./4-coding-agent.md diff --git a/workshop-content/vscode/6-managing-agents.md b/workshop-content/vscode/6-managing-agents.md new file mode 100644 index 00000000..a0a619c7 --- /dev/null +++ b/workshop-content/vscode/6-managing-agents.md @@ -0,0 +1,92 @@ +# Exercise 6 - Monitoring and managing agents + +| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on Copilot's work β†’][next-lesson] | +|:--|--:| + +In the last couple of exercises you asked Copilot coding agent to take on three separate tasks focused on improving the user experience and adding functionality. While coding agent is built to operate asynchronously and autonomously, the ability to monitor these tasks is still important. + +There are numerous tools available to you to manage tasks assigned to coding agent, including [the agents page][agents-page] on GitHub.com. From this mission control you can see all agent tasks with open pull requests (PRs). You can explore the operations performed, and even steer an in-progress session to help guide it. + +In this lesson you will: + +- explore the agents page to monitor coding agent tasks. +- steer an in-flight session to request additional functionality. + +## Scenario + +After assigning the agent to create a high-contrast mode, the team realized it would be a good time to add a light mode as well. Since work was already being done to update the style of the site and add toggle functionality, it seemed logical to include this functionality. You want to steer the agent's work to ensure it adds a light mode as well as high contrast. + +## Review Copilot coding agent tasks + +Let's see the current status of all tasks assigned to Copilot coding agent. + +1. Navigate to agents page at [https://github.com/copilot/agents](https://github.com/copilot/agents). +2. Note the list of tasks, both on the main pane and on the left pane. You should see the list of the tasks you've assigned to Copilot, including: + - Updating documentation for your codebase. + - Generating APIs for modifying products. + - Adding a high contrast mode for the website. +3. Select one of the running tasks. Review the tasks which have been performed by Copilot. These can include: + - Checking out the code from the repository. + - Creating the environment for Copilot to work. + - Setting up MCP servers. + - Performing various steps to complete the assigned task. + +> [!NOTE] +> The exact steps listed will vary depending on the state of Copilot's work and the approach it took. + +4. Also note the pull request (PR) pane which appears on the right side. This allows you to see the PR and files changed for additional monitoring. + +## Steering coding agent + +Now that you've seen the tasks which are active, let's request Copilot include the light mode toggle while it works on the high-contrast mode. + +1. Select the session which refers to adding a high contrast mode. The exact title will vary depending on the name Copilot uses and the current state of work. + + ![Accessibility session in mission control]((../images/)ex6-accessibility-session.png) + +2. Watch the session for a few of minutes, until it indicates it's completed the setup and begun its work. You'll know this has happened when you start seeing messages similar to the ones below. +3. In the **Steer active session while Copilot is working** dialog, add the following prompt: + + ``` + While we are working on a high contrast mode, let's also add a light mode. There should be a switch for this mode as well where users can select their desired display mode. + ``` + + ![Screenshot of the coding agent task in the agents page with the steer active session while copilot is working dialogue highlighted](./(../images/)ex6-steer-coding-agent-task.png) + +4. Press Enter to send the prompt. +5. Notice how Copilot acknowledges the prompt and includes it in its flow. + +## Let Copilot do its work + +Just like before, Copilot will get to work on the updated task! It will incorporate the new request into its flow after it completes the particular step it's working on when you sent the message. + +As before, this will take several minutes, so it's a good time to pause and reflect on everything you've learned and explored thus far. + +## Summary and next steps + +This lesson explored the Copilot agents page, your central hub for monitoring and guiding GitHub Copilot coding agent tasks. With this mission control you can track all active and completed tasks, review the work being performed, and even redirect in-flight tasks to adjust scope or provide additional guidance. + +You explored these concepts: + +- explored Copilot HQ and the agents page to monitor coding agent tasks. +- redirected an in-flight session to request additional functionality. + +With Copilot completing its work on the accessibility features, we can now turn our attention to our next lesson, [iterating on the pull requests Copilot created][next-lesson]. Mission control provides visibility into agent work and enables dynamic collaboration with coding agents as they work on tasks. + +## Resources + +- [Copilot HQ agents page][agents-page] +- [Custom agents][custom-agents] + +--- + +| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on coding agent's work β†’][next-lesson] | +|:--|--:| + +[agents-page]: https://github.blog/changelog/2025-10-28-a-agents-page-to-assign-steer-and-track-copilot-coding-agent-tasks +[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents +[next-lesson]: +--- + +[next-lesson]: ./7-iterating.md +[previous-lesson]: ./5-custom-agents.md diff --git a/workshop-content/vscode/7-iterating.md b/workshop-content/vscode/7-iterating.md new file mode 100644 index 00000000..ab090dd1 --- /dev/null +++ b/workshop-content/vscode/7-iterating.md @@ -0,0 +1,173 @@ +# Exercise 7: Iterating on GitHub Copilot's work + +| [← Previous lesson: Mission control][previous-lesson] | +|:--| + +Throughout this lab you've assigned several issues to GitHub Copilot coding agent. You asked it to add documentation to your code, generate endpoints for the design team to iterate on, and implement accessibility features including high-contrast and light mode toggles. Let's explore the code changes it suggested and, if necessary, provide feedback to Copilot to improve its work. + +## Scenario + +As has been highlighted numerous times, the fundamentals of software design and DevOps do not change with the addition of generative AI. We always want to review the code generated, and work through our normal DevOps process. With that in mind, let's review the suggestions from GitHub Copilot for creating the documentation, new endpoints, and accessibility features before we turn on review for the rest of our team. + +## Security and GitHub Copilot coding agent + +Because Copilot coding agent performs its tasks asynchronously and without supervision, certain security constraints have been put in place to ensure everything remains safe. These include: + +- Copilot only has read access to your repository and write access **only** to the branch it will use for its code. +- Coding agent runs inside of GitHub Actions, where it will create a separate, ephemeral environment in which to work. +- Any GitHub Actions workflows require approval from a human before they can be run. +- [Access to external resources is limited by default][agent-firewall], including MCP servers. + +## Reviewing the generated documentation + +Let's start by exploring the first pull request (PR) generated by GitHub Copilot coding agent - adding documentation to your code. You'll perform this task by utilizing the standard PR interface in GitHub.com. + +> [!NOTE] +> When you explore the PR you may notice a warning about GitHub Copilot being blocked by a firewall. This **is expected**, as Copilot has limited access to external resources by default, including calls to external MCP servers. If you wish, you can [customize or disable the firewall for Copilot coding agent][agent-firewall]. + +1. Return to your repository on github.com. +2. Select **Pull Requests** to open the list of pull requests. +3. Open issue titled something similar to **Add missing documentation** or something more robust. + +> [!NOTE] +> If Copilot is still working on the task, the issue will contain the **[WIP]** flag. If so, wait for Copilot to complete the work. This may take a few minutes, so feel free to take a break, or reflect on everything you've learned so far. + +4. Once the pull request is ready, select the **Files changed** tab and review the changes. + + ![Files changed tab]((../images/)shared-pr-files-changed.png) + +5. Explore the newly updated code, which includes the newly created docstrings and other documentation. The exact changes will vary. +6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. +7. You should see an indicator that some workflows are waiting for approval. +8. Click on the **Approve and run workflows** button to allow the workflows to run. + + ![Approve and run workflows]((../images/)shared-approve-workflows.png) + +9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. + +## Requesting changes from GitHub Copilot + +Working with Copilot on a pull request is not just a one-way street. You can also tag Copilot in comments - like you would other members of your team - in the pull request, or inline comments of the code. Copilot will see these comments, and trigger another session to address them. Due to the non-deterministic results, we can't give prescriptive text of what to ask for. Some ideas of what to ask Copilot to update include: + +- Add comment headers to the top of each code file with a brief description of what they do. +- Add docstrings to TypeScript and Svelte files. +- Create a README in both the server and client folders with descriptions of the codebase of each. + +1. Add a comment requesting a change to the generated documentation, tagging **@copilot** like you would any user. Use one of the ideas above, or another suggestion for Copilot around documentation you'd like to see in the codebase. +2. Select **View Session** to watch Copilot perform its work. Notice how Copilot starts a new session to make the updates. +3. You can select **Back to pull request** to return to the pull request. + + ![Back to pull request]((../images/)ex7-back-to-pr.png) + +4. Once Copilot has completed the changes, you should see a new commit in the pull request. +5. Select the **Files changed** tab to review the changes. + +Feel free to continue iterating until you are happy. Once happy, you can convert the PR to ready from a draft, and merge it into the main branch. + +![Convert PR to ready]((../images/)ex7-ready-for-review.png) + +## Review the new endpoints + +Let's return to the PR Copilot generated for resolving our issue about adding endpoints to the games API for creating, updating and deleting games. + +1. Return to your repository in GitHub.com. +2. Select the **Pull Requests** tab. +3. Select the PR which has a title similar to **Add CRUD endpoints for games API** or something more robust. +4. Select the **Files changed** tab to review the code it generated. +5. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. +6. You should see an indicator that some workflows are waiting for approval. +7. Click on the **Approve and run workflows** button to allow the workflows to run. + + ![Approve and run workflows]((../images/)shared-approve-workflows.png) + +8. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. +9. **Optional:** You could even switch to this branch in your Codespace to perform a manual test of the new endpoints. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created, e.g. **copilot/fix-8**.): + + ```bash + git fetch origin + git checkout + ``` + +Copilot has created the new endpoints! Just as before, you can work iteratively with Copilot coding agent to request updates. For example, you might want to request Copilot centralizes the error handling to reduce duplication, or ensuring comments and docstrings are added (remember - this was assigned **before** you made the updates to your custom instructions!) Just like before, you can make these requests by adding a new comment on the **Conversation** tab, which Copilot will see and kickoff a new session. + +## Review the accessibility features + +Finally, let's review the accessibility features that were implemented using the custom accessibility agent. This PR should include both the high-contrast mode you assigned in Exercise 5, and the light mode that was requested in mission control in Exercise 6. + +1. Return to your repository in GitHub.com. +2. Select the **Pull Requests** tab. +3. Select the PR which has a title similar to **Add high contrast mode to website** or something more robust. + +> [!NOTE] +> If Copilot is still working on the task, the issue will contain the **[WIP]** flag. If so, wait for Copilot to complete the work. This may take a few minutes. + +4. Select the **Files changed** tab to review the code it generated. +5. Review the implementation, paying particular attention to: + - The toggle UI components for switching between modes + - The use of local storage to persist user preferences + - The CSS or styling changes for high-contrast and light modes + - The accessibility attributes (ARIA labels, keyboard navigation, etc.) + - Any JavaScript/TypeScript code that manages the mode switching + +6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. +7. You should see an indicator that some workflows are waiting for approval. +8. Click on the **Approve and run workflows** button to allow the workflows to run. + + ![Approve and run workflows]((../images/)shared-approve-workflows.png) + +9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. +10. **Optional:** You could switch to this branch in your Codespace to manually test the accessibility features. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created): + + ```bash + git fetch origin + git checkout + ``` + + Then start the application and test the high-contrast and light mode toggles in your browser to ensure they work as expected and persist across page reloads. + +Notice how the custom accessibility agent helped guide Copilot to implement these features following accessibility best practices. If you see any accessibility concerns or improvements, you can tag **@copilot** in a comment to request updates, just like you did with the previous PRs. + +## Optional Exercise - Explore the agent's capabilities with more issues + +Having access to a peer programmer who is able to explore our codebase and make changes asynchronously is powerful, allowing us to reach a first iteration across tasks quickly, allowing us to review and guide, or take over and continue coding in the editor. + +You have made great progress through the lab, and you're approaching the end. However, you're encouraged to create some additional issues in your GitHub repository and use Copilot to solve those. Some ideas include: + +- Create a backer interest form on the game details page +- Implement pagination on the game listing endpoint +- Add input validation and error handling to the Flask API +- Something else? What else might you consider? + +## Summary + +Congratulations! You completed the lab! You worked through several features available to you with GitHub Copilot, from the IDE to the repository. In particular you: + +- **Learned how to use GitHub Copilot and the Model Context Protocol (MCP) to streamline software development**. You set up the GitHub MCP server to enable Copilot to interact with your repository, created a detailed backlog using Copilot Agent Mode. +- **Explored how custom instructions and prompt files can guide Copilot to follow your project's coding standards.** You created a custom instructions file to provide context for Copilot, ensuring it generates code that adheres to your project's guidelines and used prompt files to provide guidance for repetitive tasks and established practices. +- **Used Copilot Agent Mode to implement new features, coordinate changes across backend and frontend code, and automate repetitive tasks.** You used GitHub Copilot to implement a new category and publisher filter for the game listing page, making changes across the client, backend, and the resulting tests. +- **Experienced Copilot as a peer programmer, being assigned issues and working collaboratively on pull requests.** You assigned Copilot to issues in your backlog, allowing it to create a pull request, build a plan, implement changes, and iterate further as you provided feedback. + +This is just the beginning, and we can't wait to see how you use Copilot to help you with your own projects. We hope you enjoyed the lab, and we look forward to seeing you in the next one! Happy coding! + +## Resources + +- [GitHub Copilot][github-copilot] +- [About Copilot agents][copilot-agents] +- [Assigning GitHub issues to Copilot][assign-issue] +- [Copilot coding agent setup workflow best practices][coding-agent-best-practices] +- [Configuring Copilot coding agent firewall][agent-firewall] + +--- + +| [← Previous lesson: Managing agents][previous-lesson] | +|:--| + +[github-copilot]: https://github.com/features/copilot +[coding-agent-overview]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot#overview-of-copilot-coding-agent +[assign-issue]: https://docs.github.com/copilot/using-github-copilot/coding-agent/using-copilot-to-work-on-an-issue +[setup-workflow]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#pre-installing-dependencies-in-github-copilots-environment +[copilot-agents]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot +[coding-agent-best-practices]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks +[agent-firewall]: https://docs.github.com/copilot/customizing-copilot/customizing-or-disabling-the-firewall-for-copilot-coding-agent + +[previous-lesson]: ./6-managing-agents.md diff --git a/workshop-content/vscode/README.md b/workshop-content/vscode/README.md new file mode 100644 index 00000000..8d96a6a1 --- /dev/null +++ b/workshop-content/vscode/README.md @@ -0,0 +1,41 @@ +# VS Code Learning Path + +Welcome to the **VS Code** learning path! This path focuses on GitHub Copilot features available directly within Visual Studio Code (and GitHub Codespaces), including Copilot Chat agent mode, MCP integration, and the coding agent for asynchronous tasks. + +## Path Overview + +| Exercise | Topic | Description | +|----------|-------|-------------| +| [0. Prerequisites][ex0] | Setup | Create your repository and codespace | +| [1. Custom Instructions][ex1] | Context | Learn how instruction files guide Copilot | +| [2. MCP with VS Code][ex2] | External Tools | Use MCP to interact with GitHub from your IDE | +| [3. Agent Mode][ex3] | Code Generation | Complete a site-wide update with agent mode | +| [4. Coding Agent][ex4] | Async Agent | Assign issues to Copilot coding agent | +| [5. Custom Agents][ex5] | Specialized Agents | Create and use custom agents | +| [6. Managing Agents][ex6] | Monitoring | Monitor and steer agent sessions | +| [7. Iterating][ex7] | Review | Iterate on Copilot's generated work | + +## Prerequisites + +Before attending this workshop, please ensure you have: + +- [ ] A GitHub account with an active **Copilot Pro, Pro+, Business, or Enterprise** subscription +- [ ] Access to GitHub Codespaces + +> [!NOTE] +> For exercises involving the Copilot coding agent (Exercises 4–7), you will need **Copilot Pro+, Business, or Enterprise** with coding agent enabled. + +## Get Started + +**[Start with Exercise 0: Prerequisites β†’][ex0]** + +--- + +[ex0]: ../shared/0-prereqs.md +[ex1]: ../shared/1-custom-instructions.md +[ex2]: ./2-mcp.md +[ex3]: ./3-agent-mode.md +[ex4]: ./4-coding-agent.md +[ex5]: ./5-custom-agents.md +[ex6]: ./6-managing-agents.md +[ex7]: ./7-iterating.md From 2c06d39b09ebb8ede8007a3c5e53cd33dd756f12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:10:37 +0000 Subject: [PATCH 03/29] chore: add image placeholder directories for path-specific assets Agent-Logs-Url: https://github.com/github-samples/agents-in-sdlc/sessions/fe23a7c4-299e-4150-9215-ce55e8e0682e Co-authored-by: GeekTrainer <6109729+GeekTrainer@users.noreply.github.com> --- workshop-content/cli/images/.gitkeep | 0 workshop-content/cloud/images/.gitkeep | 0 workshop-content/shared/images/.gitkeep | 0 workshop-content/vscode/images/.gitkeep | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 workshop-content/cli/images/.gitkeep create mode 100644 workshop-content/cloud/images/.gitkeep create mode 100644 workshop-content/shared/images/.gitkeep create mode 100644 workshop-content/vscode/images/.gitkeep diff --git a/workshop-content/cli/images/.gitkeep b/workshop-content/cli/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workshop-content/cloud/images/.gitkeep b/workshop-content/cloud/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workshop-content/shared/images/.gitkeep b/workshop-content/shared/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/workshop-content/vscode/images/.gitkeep b/workshop-content/vscode/images/.gitkeep new file mode 100644 index 00000000..e69de29b From af76cbbc9d1bb4870efd242bd498fa74fe2c74d1 Mon Sep 17 00:00:00 2001 From: Christopher Harrison Date: Thu, 30 Apr 2026 11:48:17 -0400 Subject: [PATCH 04/29] feat(docs): publish workshop site + dedup + content partials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds out the workshop-content/ directory as a publishable MkDocs site and removes long-standing duplication across the three learning paths. Site infrastructure (Phase A1) - mkdocs.yml at repo root with Material theme, dark/light toggle, nav tabs, search, code-copy, edit links, and PyMdown Snippets configured against partials/. - requirements-docs.txt pinning mkdocs-material, pymdown-extensions, and mkdocs-callouts (renders > [!NOTE]/[!IMPORTANT]/[!TIP] blocks as Material admonitions). - .github/workflows/pages.yml builds with mkdocs --strict on PRs (validation only) and on push to main (build + deploy via actions/deploy-pages). Explicit per-job permissions; pages concurrency group; pinned actions. - site/ and .venv-docs/ added to .gitignore. Content partials (Phase A2 β€” Tier 1) Five reusable snippets at repo-root partials/, replacing verbatim duplicates across paths via --8<-- includes: - sections/coding-agent-intro.md (vscode/4 + cloud/2) - sections/iterating-recap.md (vscode/7 + cloud/5) - sections/security-coding-agent.md (vscode/7 + cloud/5) - scenarios/tech-debt.md (vscode/4 + cloud/2) - scenarios/accessibility.md (cli/6 + shared/coding-agent/custom-agents) Partials follow inline-links-only / no-images / no-H1 rules documented in partials/README.md. Path-publishing fixes - Converted 3 outbound links pointing to .github/instructions/*.md to absolute github.com URLs (would otherwise break --strict). - Escaped placeholders in iterating files so they render as code spans, not stray HTML tags. - Escaping shared/1 link refs to absolute URLs preserves both raw GitHub readability and Pages strict build. Prior dedup + cleanup (also pending on this branch, included here) - shared/coding-agent/{custom-agents,managing-agents}.md as canonical bodies for the cross-path coding-agent flows. - Wrappers in vscode/{5,6} and cloud/{3,4} reduced to skeletons pointing to the shared module. - Multiple polish passes from earlier session work (cli warm-up, hands-on demos, /delegate optional section, etc.). Local validation: mkdocs build --strict passes with zero errors; all 5 partials resolve correctly in rendered output; GitHub alerts render as Material admonitions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/pages.yml | 74 +++++ .gitignore | 7 +- mkdocs.yml | 86 ++++++ partials/README.md | 30 +++ partials/scenarios/.gitkeep | 0 partials/scenarios/accessibility.md | 5 + partials/scenarios/tech-debt.md | 7 + partials/sections/.gitkeep | 0 partials/sections/coding-agent-intro.md | 3 + partials/sections/iterating-recap.md | 5 + partials/sections/security-coding-agent.md | 8 + requirements-docs.txt | 3 + workshop-content/0-prereqs.md | 47 ---- workshop-content/1-mcp.md | 230 ---------------- workshop-content/2-custom-instructions.md | 254 ------------------ .../3-copilot-agent-mode-vscode.md | 224 --------------- workshop-content/4-copilot-coding-agent.md | 205 -------------- workshop-content/7-iterating-copilot-work.md | 173 ------------ workshop-content/cli/2-install-copilot-cli.md | 23 +- workshop-content/cli/4-generating-code.md | 58 +++- workshop-content/cli/5-agent-skills.md | 2 +- workshop-content/cli/6-custom-agents.md | 6 +- workshop-content/cli/7-slash-commands.md | 36 ++- workshop-content/cli/8-review.md | 1 + workshop-content/cli/README.md | 2 +- workshop-content/cloud/2-coding-agent.md | 39 ++- workshop-content/cloud/3-custom-agents.md | 88 +----- workshop-content/cloud/4-managing-agents.md | 86 +----- workshop-content/cloud/5-iterating.md | 58 ++-- workshop-content/cloud/README.md | 7 +- workshop-content/images/cli-5-agent-skill.png | Bin 0 -> 15007 bytes .../images/cli-7-context-window.png | Bin 0 -> 123686 bytes .../shared/1-custom-instructions.md | 140 ++-------- .../coding-agent/custom-agents.md} | 31 +-- .../coding-agent/managing-agents.md} | 26 +- workshop-content/vscode/2-mcp.md | 120 ++++++++- workshop-content/vscode/3-agent-mode.md | 14 +- workshop-content/vscode/4-coding-agent.md | 22 +- workshop-content/vscode/5-custom-agents.md | 86 +----- workshop-content/vscode/6-managing-agents.md | 84 +----- workshop-content/vscode/7-iterating.md | 47 ++-- workshop-content/vscode/README.md | 2 +- 42 files changed, 601 insertions(+), 1738 deletions(-) create mode 100644 .github/workflows/pages.yml create mode 100644 mkdocs.yml create mode 100644 partials/README.md create mode 100644 partials/scenarios/.gitkeep create mode 100644 partials/scenarios/accessibility.md create mode 100644 partials/scenarios/tech-debt.md create mode 100644 partials/sections/.gitkeep create mode 100644 partials/sections/coding-agent-intro.md create mode 100644 partials/sections/iterating-recap.md create mode 100644 partials/sections/security-coding-agent.md create mode 100644 requirements-docs.txt delete mode 100644 workshop-content/0-prereqs.md delete mode 100644 workshop-content/1-mcp.md delete mode 100644 workshop-content/2-custom-instructions.md delete mode 100644 workshop-content/3-copilot-agent-mode-vscode.md delete mode 100644 workshop-content/4-copilot-coding-agent.md delete mode 100644 workshop-content/7-iterating-copilot-work.md create mode 100644 workshop-content/images/cli-5-agent-skill.png create mode 100644 workshop-content/images/cli-7-context-window.png rename workshop-content/{5-custom-agents.md => shared/coding-agent/custom-agents.md} (75%) rename workshop-content/{6-managing-agents.md => shared/coding-agent/managing-agents.md} (85%) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 00000000..394c9a31 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,74 @@ +name: Build and deploy workshop site + +# Builds the MkDocs site. Deploys to GitHub Pages on pushes to main. +# Pull requests run a build-only validation pass. + +on: + push: + branches: [main] + paths: + - 'workshop-content/**' + - 'partials/**' + - 'mkdocs.yml' + - 'requirements-docs.txt' + - '.github/workflows/pages.yml' + pull_request: + paths: + - 'workshop-content/**' + - 'partials/**' + - 'mkdocs.yml' + - 'requirements-docs.txt' + - '.github/workflows/pages.yml' + workflow_dispatch: + +# Single in-flight deploy per branch; do not cancel an active deploy mid-flight. +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + build: + name: Build site (mkdocs --strict) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: requirements-docs.txt + + - name: Install MkDocs and extensions + run: pip install -r requirements-docs.txt + + - name: Build site (strict) + run: mkdocs build --strict + + - name: Upload Pages artifact + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + name: Deploy to GitHub Pages + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 5ec07d29..7f23e315 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,9 @@ msbuild.err msbuild.wrn # SQLite -*.db \ No newline at end of file +*.db +# MkDocs build output +site/ + +# MkDocs docs venv (local docs builds) +.venv-docs/ diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..9ea98281 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,86 @@ +site_name: Agents in the SDLC Workshop +site_description: A hands-on workshop exploring GitHub Copilot agents across VS Code, the Copilot CLI, and the Copilot coding agent. +site_url: https://github-samples.github.io/agents-in-sdlc/ +repo_url: https://github.com/github-samples/agents-in-sdlc +edit_uri: edit/main/workshop-content/ +docs_dir: workshop-content + +theme: + name: material + palette: + - scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/weather-sunny + name: Switch to light mode + features: + - navigation.tabs + - navigation.sections + - navigation.indexes + - navigation.top + - content.code.copy + - content.action.edit + - search.highlight + - search.suggest + icon: + repo: fontawesome/brands/github + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - tables + - toc: + permalink: true + - pymdownx.details + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.snippets: + base_path: + - partials + check_paths: true + +plugins: + - search + - callouts + +# Files inside docs_dir that should not be published as standalone pages. +# (Currently empty; partials live OUTSIDE docs_dir at repo root /partials.) +not_in_nav: | + /shared/coding-agent/*.md + +nav: + - Home: README.md + - Prerequisites: shared/0-prereqs.md + - Custom instructions: shared/1-custom-instructions.md + - VS Code path: + - Overview: vscode/README.md + - 2. MCP with VS Code: vscode/2-mcp.md + - 3. Agent mode: vscode/3-agent-mode.md + - 4. Coding agent: vscode/4-coding-agent.md + - 5. Custom agents: vscode/5-custom-agents.md + - 6. Managing agents: vscode/6-managing-agents.md + - 7. Iterating: vscode/7-iterating.md + - Copilot CLI path: + - Overview: cli/README.md + - 2. Install Copilot CLI: cli/2-install-copilot-cli.md + - 3. MCP with CLI: cli/3-mcp.md + - 4. Generating code: cli/4-generating-code.md + - 5. Agent skills: cli/5-agent-skills.md + - 6. Custom agents: cli/6-custom-agents.md + - 7. Slash commands: cli/7-slash-commands.md + - 8. Review: cli/8-review.md + - Cloud agent path: + - Overview: cloud/README.md + - 2. Coding agent: cloud/2-coding-agent.md + - 3. Custom agents: cloud/3-custom-agents.md + - 4. Managing agents: cloud/4-managing-agents.md + - 5. Iterating: cloud/5-iterating.md diff --git a/partials/README.md b/partials/README.md new file mode 100644 index 00000000..0b756de7 --- /dev/null +++ b/partials/README.md @@ -0,0 +1,30 @@ +# Reusable Markdown partials for the workshop site + +This directory holds reusable content fragments included into the published workshop site +(`https://github-samples.github.io/agents-in-sdlc/`) via the +[PyMdown Snippets](https://facelessuser.github.io/pymdown-extensions/extensions/snippets/) extension. + +Snippets are referenced from pages in `workshop-content/` using: + +```markdown +--8<-- "sections/coding-agent-intro.md" +``` + +The `base_path` for snippet resolution is configured to this folder in `mkdocs.yml`. + +## Rules for partials + +- **Inline links only** β€” do NOT use reference-style links (`[text][ref]` plus `[ref]: url`). Reference + definitions become document-global once the snippet is included and can collide with the host page. +- **No image references** β€” keep images in the consuming page outside the include. Snippets are + inserted before Markdown conversion and have no stable source path. +- **No H1** β€” partials drop into the middle of a page; use H2 (`##`) at most. +- **One concept per partial** β€” easier to reuse, easier to update. + +## Layout + +``` +partials/ + scenarios/ # Repeatable scenario blocks (Tailspin-specific framing) + sections/ # Multi-paragraph sections that recur verbatim across paths +``` diff --git a/partials/scenarios/.gitkeep b/partials/scenarios/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/partials/scenarios/accessibility.md b/partials/scenarios/accessibility.md new file mode 100644 index 00000000..852dc20b --- /dev/null +++ b/partials/scenarios/accessibility.md @@ -0,0 +1,5 @@ +## Scenario + +Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. + +Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. diff --git a/partials/scenarios/tech-debt.md b/partials/scenarios/tech-debt.md new file mode 100644 index 00000000..001b12bb --- /dev/null +++ b/partials/scenarios/tech-debt.md @@ -0,0 +1,7 @@ +## Scenarios + +Tailspin Toys has some tech debt they'd like to address. The contractors initially hired to create the first version of the site left the documentation in an unideal state - and by that you'll notice it's completely lacking. As a first step, they'd like to see docstrings or the equivalent added to all functions in the application. + +Additionally, the design team is ready to get to work on building the UX for managing games. They don't need a full implementation yet, but they at least need some endpoints they can use for testing. Specifically, they need endpoints for the games API which will allow them to create, update and delete games. This is currently a blocker, but there are other issues which are of higher priority at the moment. + +These are both examples of tasks which can quickly find themselves deprioritized, and are great to assign to Copilot coding agent. Copilot coding agent can then work on them asynchronously, allowing the developer to focus on other tasks, then return to review Copilot's work and ensure everything is as expected. diff --git a/partials/sections/.gitkeep b/partials/sections/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/partials/sections/coding-agent-intro.md b/partials/sections/coding-agent-intro.md new file mode 100644 index 00000000..cb643a58 --- /dev/null +++ b/partials/sections/coding-agent-intro.md @@ -0,0 +1,3 @@ +## Introducing GitHub Copilot coding agent + +[GitHub Copilot coding agent](https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot#overview-of-copilot-coding-agent) can perform tasks in the background, much in the same way a human developer would. And, just like with working with a human developer, this can be done in multiple ways, including [assigning a GitHub issue to Copilot](https://docs.github.com/copilot/using-github-copilot/coding-agent/using-copilot-to-work-on-an-issue). Once assigned, Copilot will create a draft pull request to track its progress, setup an environment, and begin working on the task. You can dig into Copilot's session while it's still in flight or after its completed. Once its ready for you to review the proposed solution, it'll tag you in the pull request! diff --git a/partials/sections/iterating-recap.md b/partials/sections/iterating-recap.md new file mode 100644 index 00000000..bd770d49 --- /dev/null +++ b/partials/sections/iterating-recap.md @@ -0,0 +1,5 @@ +Throughout this lab you've assigned several issues to GitHub Copilot coding agent. You asked it to add documentation to your code, generate endpoints for the design team to iterate on, and implement accessibility features including high-contrast and light mode toggles. Let's explore the code changes it suggested and, if necessary, provide feedback to Copilot to improve its work. + +## Scenario + +As has been highlighted numerous times, the fundamentals of software design and DevOps do not change with the addition of generative AI. We always want to review the code generated, and work through our normal DevOps process. With that in mind, let's review the suggestions from GitHub Copilot for creating the documentation, new endpoints, and accessibility features before we turn on review for the rest of our team. diff --git a/partials/sections/security-coding-agent.md b/partials/sections/security-coding-agent.md new file mode 100644 index 00000000..94fb0ed3 --- /dev/null +++ b/partials/sections/security-coding-agent.md @@ -0,0 +1,8 @@ +## Security and GitHub Copilot coding agent + +Because Copilot coding agent performs its tasks asynchronously and without supervision, certain security constraints have been put in place to ensure everything remains safe. These include: + +- Copilot only has read access to your repository and write access **only** to the branch it will use for its code. +- Coding agent runs inside of GitHub Actions, where it will create a separate, ephemeral environment in which to work. +- Any GitHub Actions workflows require approval from a human before they can be run. +- [Access to external resources is limited by default](https://docs.github.com/copilot/customizing-copilot/customizing-or-disabling-the-firewall-for-copilot-coding-agent), including MCP servers. diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 00000000..090e61e0 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,3 @@ +mkdocs-material==9.5.* +pymdown-extensions==10.* +mkdocs-callouts==1.16.* diff --git a/workshop-content/0-prereqs.md b/workshop-content/0-prereqs.md deleted file mode 100644 index 0b76b958..00000000 --- a/workshop-content/0-prereqs.md +++ /dev/null @@ -1,47 +0,0 @@ -# Exercise 0: Prerequisites - -Before you get started on the lab, there's a few tasks you need to complete to get everything ready. You need to get a copy of the repository which includes the code, then spin up a [codespace][codespaces] to use to create your code. - -## Setting up the Lab Repository - -To create a copy of the repository for the code you'll create an instance from the [template][template-repository]. The new instance will contain all of the necessary files for the lab, and you'll use it as you work through the exercises. - -1. In a new browser window, navigate to the GitHub repository for this lab: `https://github.com/github-samples/agents-in-sdlc`. -2. Create your own copy of the repository by selecting the **Use this template** button on the lab repository page. Then select **Create a new repository**. - - ![Use this template button](images/ex0-use-template.png) - -3. If you are completing the workshop as part of an event being led by GitHub or Microsoft, follow the instructions provided by the mentors. Otherwise, you can create the new repository in an organization where you have access to Copilot coding agent and can assign issues to Copilot. - - ![Input the repository template settings](images/ex0-repository-settings.png) - -4. Make a note of the repository path you created (**organization-or-user-name/repository-name**), as you will be referring to this later in the lab. - -## Creating a codespace - -Next up, you'll be using a codespace to complete the lab exercises. [GitHub Codespaces][codespaces] are a cloud-based development environment that allows you to write, run, and debug code directly in your browser. It provides a fully-featured IDE with support for multiple programming languages, extensions, and tools. - -1. Navigate to your newly created repository. -2. Select the green **Code** button. - - ![Select the Code button](images/ex0-code-button.png) - -3. Select the **Codespaces** tab and select the **+** button to create a new Codespace. - - ![Create a new codespace](images/ex0-create-codespace.png) - -The creation of the codespace will take several minutes, although it's still far quicker than having to manually install all the services! That said, you can use this time to explore other features of GitHub Copilot, which we'll turn your attention to next! - -> [!IMPORTANT] -> You'll return to the codespace in a future exercise. For the time being, leave it open in a tab in your browser. - -## Summary - -Congratulations, you have created a copy of the lab repository! You also began the creation process of your codespace, which you'll use when you begin writing code. - -## Next step - -Let's explore how you can use Model Context Protocol (MCP) to interact with external services! You can do this by [setting up the backlog with Copilot agent mode and GitHub's MCP Server](./1-mcp.md). - -[codespaces]: https://github.com/features/codespaces -[template-repository]: https://docs.github.com/repositories/creating-and-managing-repositories/creating-a-template-repository diff --git a/workshop-content/1-mcp.md b/workshop-content/1-mcp.md deleted file mode 100644 index 44893e9e..00000000 --- a/workshop-content/1-mcp.md +++ /dev/null @@ -1,230 +0,0 @@ -# Exercise 1 - Setting up the backlog with Copilot agent mode and GitHub's MCP Server - -| [← Prerequisites][previous-lesson] | [Next lesson: Custom instructions β†’][next-lesson] | -|:--|--:| - -There's more to writing code than just writing code. Issues need to be filed, external services need to be called, and information needs to be gathered. Typically this involves interacting with external tools, which can break a developer's flow. Through the power of Model Context Protocol (MCP), you can access all of this functionality right from Copilot! - -## Scenario - -You are a part-time developer for Tailspin Toys - a crowdfunding platform for board games with a developer theme. You've been assigned various tasks to introduce new functionality to the website. Being a good team member, you want to file issues to track your work. To help future you, you've decided to enlist the help of Copilot. You will set up your backlog of work for the rest of the lab, using GitHub Copilot Chat agent mode and the GitHub Model Context Protocol (MCP) server to create the issues for you. - -In this exercise, you will: - -- use Model Context Protocol (MCP), which provides access to external tools and capabilities. -- set up the GitHub MCP server in your repository. -- use GitHub Copilot Chat agent mode to create issues in your repository. - -By the end of this exercise, you will have created a backlog of GitHub issues for use throughout the remainder of the lab. - -## What is agent mode and Model Context Protocol (MCP)? - -Agent mode in GitHub Copilot Chat transforms Copilot into an AI agent that can perform actions on your behalf. This mode allows you to interact with Copilot in a more dynamic way, enabling it to use tools and execute tasks, like running tests or terminal commands, reading problems from the editor, and using those insights to update your code. This allows for a more interactive and collaborative workflow, enabling you to leverage the capabilities of AI in your development process. - -[Model Context Protocol (MCP)][mcp-blog-post] provides AI agents with a way to communicate with external tools and services. By using MCP, AI agents can communicate with external tools and services in real-time. This allows them to access up-to-date information (using resources) and perform actions on your behalf (using tools). - -These tools and resources are accessed through an MCP server, which acts as a bridge between the AI agent and the external tools and services. The MCP server is responsible for managing the communication between the AI agent and the external tools (such as existing APIs or local tools like NPM packages). Each MCP server represents a different set of tools and resources that the AI agent can access. - -![Diagram showing the inner works of agent mode and how it interacts with context, LLM and tools - including tools contributed by MCP servers and VS Code extensions](images/ex1-mcp-diagram.png) - -A couple of popular existing MCP servers are: - -- **[GitHub MCP Server][github-mcp-server]**: This server provides access to a set of APIs for managing your GitHub repositories. It allows the AI agent to perform actions such as creating new repositories, updating existing ones, and managing issues and pull requests. -- **[Playwright MCP Server][playwright-mcp-server]**: This server provides browser automation capabilities using Playwright. It allows the AI agent to perform actions such as navigating to web pages, filling out forms, and clicking buttons. - -There are many other MCP servers available that provide access to different tools and resources. GitHub hosts an [MCP registry][mcp-registry] to enhance discoverability and contributions to the ecosystem. - -> [!IMPORTANT] -> With regard to security, treat MCP servers as you would any other dependency in your project. Before using an MCP server, carefully review its source code, verify the publisher, and consider the security implications. Only use MCP servers that you trust and be cautious about granting access to sensitive resources or operations. - -## Ensure your codespace is ready - -In a [prior exercise][prereqs-lesson] you launched the codespace you'll use for the remainder of the coding exercises in this lab. Let's put the final touches on it before you begin using it. - -The setup process for the codespace installed and setup many [VS Code extensions][vscode-extensions]. As with any software, updates may be needed. When your codespace is created you'll need to ensure everything is up-to-date. - -1. Return to the tab where you started your codespace. If you closed the tab, return to your repository, select **Code** > **Codespaces** and then the name of the codespace. -2. Select **Extensions** on the workbench on the left side of your codespace. - - ![Screenshot of the extensions window with multiple extensions showing either Update or Reload Window buttons](images/ex1-extensions-updates.png) - -3. Select **Update** on any extensions with an **Update** button. Repeat as necessary. -4. Select **Reload Window** on any extensions with a **Reload Window** button to reload the codespace. -5. When prompted by a dialog, select **Reload** to reload the window. This will ensure the latest version is being used. - -## Using GitHub Copilot Chat and agent mode - -To access GitHub Copilot Chat agent mode, you need to have the GitHub Copilot Chat extension installed in your IDE, which should already be the case if you are using a GitHub Codespace. - -> [!TIP] -> If you do not have the GitHub Copilot Chat extension installed, you can [install it from the Visual Studio Code Marketplace][copilot-chat-extension]. Or open the Extensions view in Visual Studio Code, search for **GitHub Copilot Chat**, and select **Install**. - -Once you have the extension installed, you may need to authenticate with your GitHub account to enable it. - -1. Return to your codespace. -2. If you don't already see Copilot Chat on the right side of your editor, select the **Copilot Chat** icon at the top of your codespace. -3. Type a message like "Hello world" in the Copilot Chat window and press enter. This should activate Copilot Chat. -4. Alternatively, if you are not authenticated you will be prompted to sign in to your GitHub account. Follow the instructions to authenticate. - - ![Example of Copilot Chat authentication prompt](images/ex1-copilot-authentication.png) - -5. After authentication, you should see the Copilot Chat window appear. -6. Switch to agent mode by selecting the dropdown in the Copilot Chat window and selecting **Agent**. - - ![Example of switching to agent mode](images/shared-agent-mode-dropdown.png) - -7. Set the model to **Claude Sonnet 4.5**. - - ![Example of selecting the Claude Sonnet 4.5 model](images/ex1-select-model.png) - -> [!IMPORTANT] -> The authors of this workshop are not indicating a preference towards one model or another. When building this workshop, we used Claude Sonnet 4.5, and as such are including that in the instructions. The hope is the code suggestions you receive will be relatively consistent to ensure a good experience. However, because LLMs are probabilistic, you may notice the suggestions received differ from what is indicated in the workshop. This is perfectly normal and expected. - -8. The chat pane should update to indicate that you are now in agent mode. You should see a tools icon on the same line as the mode and model, which you utilized earlier, showing that you can configure tools for GitHub Copilot to use. - -Typically, the number of tools available will be set to 0 when setting up a new project, as you have not configured any MCP servers yet. But to help you get started, the project has a **.vscode/mcp.json** file with an example configuration for the [GitHub MCP server][github-mcp-server]. Let's go and explore that next. - -## Setting up the GitHub MCP server - -The **.vscode/mcp.json** file is used to configure the MCP servers that are available in this Visual Studio Code workspace. The MCP servers provide access to external tools and resources that GitHub Copilot can use to perform actions on your behalf. - -1. Open **.vscode/mcp.json** file in your repository. -2. You should see a JSON structure similar to the following: - - ```json - { - "servers": { - "github": { - "type": "http", - "url": "https://api.githubcopilot.com/mcp/" - } - } - } - ``` - -This configuration provides GitHub Copilot access to several additional tools so that it can interact with GitHub repositories, issues, pull requests, and more. This particular configuration uses the [remote GitHub MCP server][remote-github-mcp-server]. By using this approach, you don't need to worry about running the MCP server locally (and the associated management, like keeping it up to date), and you can authenticate to the remote server using OAuth 2.0 instead of a personal access token (PAT). - -The MCP server configuration is defined in the **servers** section of the **mcp.json** file. Each MCP server is defined by a unique name (in this case, github) and its type (in this case, **http**). When using local MCP servers, the type may be **stdio** and have a **command** and **args** field to specify how to start the MCP server. You can find out more about the configuration format in the [VS Code documentation][vscode-mcp-config]. In some configurations (not for the remote GitHub MCP server with OAuth), you may also see an **inputs** section. This defines any inputs (like sensitive tokens) that the MCP server may require. You can read more about the configuration properties in the [VS Code documentation][vscode-mcp-config] - -To utilize an MCP server it needs to be "started". This will allow GitHub Copilot to communicate with the server and perform the tasks you request. - -> [!NOTE] The exact authentication flow may vary a little bit. - -1. Inside VS Code, open **.vscode/mcp.json**. -2. To start the GitHub MCP server, select **Start** above the GitHub server. - - ![The start button above the GitHub MCP server entry](images/ex1-start-mcp-server.png) - -3. You should see a popup asking you to authenticate to GitHub. - - ![A popup showing that the GitHub MCP server wants to authenticate to GitHub](images/ex1-mcp-auth-popup.png) - -4. Select **Continue** on the user account that you're using for this lab. - - ![A popup showing the user account selection for GitHub authentication](images/ex1-mcp-select-account.png) - -5. If the page appears, select **Authorize visual-studio-code** to allow the GitHub MCP server to login as your selected user account. Once complete, the page should say "You can now close the window.". - - ![A popup showing the authorization for visual-studio-code app](images/ex1-mcp-authorize-vscode.png) - -6. After navigating back to the GitHub Codespace, you should see that the GitHub MCP server has started. You can check this in two places: - - The line in **.vscode/mcp.json** which previously said start should now present several options, and show a number of tools available. - - Select the tools icon in the Copilot Chat pane to see the tools available. Scroll down the list that appears at the top of the screen, and you should see a list of tools from the GitHub MCP server. - -That's it! You can now use Copilot Chat in agent mode to create issues, manage pull requests, and more. - -## Creating a backlog of tasks - -Now that you have set up the GitHub MCP server, you can use Copilot Agent mode to create a backlog of tasks for use in the rest of the lab. - -1. Return to the Copilot Chat pane. Ensure **Agent** is selected for the mode and **Claude Sonnet 4.5** is selected for the model. -2. Type or paste the following prompt to create the issues you'll be working on in the lab: - - ```markdown - In my GitHub repo, create GitHub issues for our Tailspin Toys backlog. Each issue should include: - - A clear title - - A brief description of the task and why it is important to the project - - A checkbox list of acceptance criteria - - From our recent planning meeting, the upcoming backlog includes the following tasks: - - 1. Allow users to filter games by category and publisher - 2. Update our repository coding standards (including rules about Python formatting and docstrings) in a custom instructions file - 3. Stretch Goal: Implement pagination on the game list page - ``` - -3. Press enter or select the **Send** button to send the prompt to Copilot. -4. GitHub Copilot should process the request and respond with a dialog box asking you to confirm the creation of the issues. - - ![Example of Copilot Chat dialog box asking for confirmation to run the create issue command](images/ex1-create-issue-dialog.png) - -> [!IMPORTANT] -> Remember, AI can make mistakes, so make sure to review the issues before confirming. - -5. Select **see more** in **Run open new issue** box to see the details of the issue that will be created. -6. Ensure the details in the **owner** and **repo**, **title** and **body** of the issue look correct. You can make any desired edits by double clicking the body and updating the content with the correct information. -7. After reviewing the generated content, select **Continue** to create the issue. - - ![Example of the expanded dialog box showing the GitHub Issue that will be created](images/ex1-create-issue-review.png) - -8. Repeat steps 4-6 for the remainder of the issues. Alternatively, if you are comfortable with Copilot automatically creating the issues you can select the down-arrow next to **Continue** and select **Allow in this session** to allow Copilot to create the issues for this session (the current chat). - - ![Example of allowing Copilot to automatically create issues](images/ex1-create-issue-allow.png) - -> [!IMPORTANT] -> Ensure you are comfortable with Copilot automatically performing tasks on your behalf before you selecting **Allow in this session** or a similar option. - -9. In a separate browser tab, navigate to your GitHub repository and select the issues tab. -10. You should see a list of issues that have been created by Copilot. Each issue should include a clear title and a checkbox list of acceptance criteria. - -You should notice that the issues are fairly detailed. This is where you benefit from the power of Large Language Models (LLMs) and Model Context Protocol (MCP), as it has been able to create a clear initial issue description. - -![Example of issues created in GitHub](images/ex1-github-issues-created.png) - -## Summary and next steps - -Congratulations, you have created issues on GitHub using Copilot Chat and MCP! - -To recap, in this exercise you: - -- used Model Context Protocol (MCP), which provides access to external tools and capabilities. -- set up the GitHub MCP server in your repository. -- used GitHub Copilot Chat agent mode to create issues in your repository. - -With the GitHub MCP server configured, you can now use GitHub Copilot Chat Agent Mode to perform additional actions on your behalf, like creating new repositories, managing pull requests, and searching for information across your repositories. - -You can now continue to the next exercise, where you will learn how to [provide Copilot guidance with custom instructions][next-lesson] to ensure code is generated following your organization's defined patterns and practices. - -### Optional exploration exercise – Set up the Microsoft Playwright MCP server - -If you are feeling adventurous, you can try installing and configuring another MCP server, such as the [Microsoft Playwright MCP server][playwright-mcp-server]. This will allow you to use GitHub Copilot Chat Agent Mode to perform browser automation tasks, such as navigating to web pages, filling out forms, and clicking buttons. - -You can find the instructions for installing and configuring the Playwright MCP server in the [Playwright MCP repository][playwright-mcp-server]. - -Notice that the setup process is similar to the GitHub MCP server, but you do not need to provide any credentials like the GitHub Personal Access Token. This is because the Playwright MCP server does not require authentication to access its capabilities. - -## Resources - -- [What the heck is MCP and why is everyone talking about it?][mcp-blog-post] -- [GitHub MCP Server][github-mcp-server] -- [Microsoft Playwright MCP Server][playwright-mcp-server] -- [GitHub MCP Registry][mcp-registry] -- [VS Code Extensions][vscode-extensions] -- [GitHub Copilot Chat Extension][copilot-chat-extension] - ---- - -| [← Prerequisites][previous-lesson] | [Next lesson: Custom instructions β†’][next-lesson] | -|:--|--:| - -[previous-lesson]: ./0-prereqs.md -[next-lesson]: ./2-custom-instructions.md -[prereqs-lesson]: ./0-prereqs.md -[mcp-blog-post]: https://github.blog/ai-and-ml/llms/what-the-heck-is-mcp-and-why-is-everyone-talking-about-it/ -[github-mcp-server]: https://github.com/github/github-mcp-server -[playwright-mcp-server]: https://github.com/microsoft/playwright-mcp -[mcp-registry]: https://github.com/mcp -[vscode-extensions]: https://code.visualstudio.com/docs/configure/extensions/extension-marketplace -[copilot-chat-extension]: https://marketplace.visualstudio.com/items?itemName=GitHub.copilot -[remote-github-mcp-server]: https://github.blog/changelog/2025-06-12-remote-github-mcp-server-is-now-available-in-public-preview/ -[vscode-mcp-config]: https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_configuration-format diff --git a/workshop-content/2-custom-instructions.md b/workshop-content/2-custom-instructions.md deleted file mode 100644 index c3d5ad5d..00000000 --- a/workshop-content/2-custom-instructions.md +++ /dev/null @@ -1,254 +0,0 @@ -# Exercise 2 - Providing context to Copilot with instruction files - -| [← Previous lesson: Model Context Protocol (MCP)][previous-lesson] | [Next lesson: Copilot agent mode β†’][next-lesson] | -|:--|--:| - -Context is key across many aspects of life, and when working with generative AI. If you're performing a task which needs to be completed a particular way, or if a piece of background information is important, you want to ensure Copilot has access to that information. You can use [instruction files][instruction-files] to provide guidance so that Copilot not only understands what you want it to do but also how you want it to be done. - -In this exercise, you will learn how to: - -- provide Copilot with project-specific context, coding guidelines and documentation standards using [repository custom instructions][repository-custom-instructions] **.github/copilot-instructions.md**. -- provide path instruction files to guide Copilot for repetitive or templated tasks on specific types of files. -- implement both repository-wide instructions and task-specific instructions. - -> [!IMPORTANT] -> Note that the code generated may diverge from some of the standards you set. AI tools like Copilot are non-deterministic, and may not always provide the same result. The other files in the codebase do not contain docstrings or comment headers, which could lead Copilot in another direction. Consistency is key, so making sure that your code follows the established patterns is important. You can always follow-up in chat and ask Copilot to follow your coding standards, which will help guide it in the right direction. - -## Scenario - -As any good dev shop, Tailspin Toys has a set of guidelines and requirements for development practices. These include: - -- API always needs unit tests. -- UI should be in dark mode and have a modern feel. -- Documentation should be added to code in the form of docstrings. -- A block of comments should be added to the head of each file describing what the file does. - -Through the use of instruction files you'll ensure Copilot has the right information to perform the tasks in alignment with the practices highlighted. - -## Before you begin - -You're going to be making some code changes, so you should follow the usual practice of creating a new branch to work in. This will allow you to make changes without affecting the main branch until you're ready. - -1. Return to your codespace from the previous exercise. -2. Open a new terminal window inside your codespace by selecting Ctl+\`. -3. Create and switch to a new branch by running the following command in the terminal: - - ```bash - git checkout -b add-filters - ``` - -## Custom instructions - -Custom instructions allow you to provide context and preferences to Copilot chat, so that it can better understand your coding style and requirements. This is a powerful feature that can help you steer Copilot to get more relevant suggestions and code snippets. You can specify your preferred coding conventions, libraries, and even the types of comments you like to include in your code. You can create instructions for your entire repository, or for specific types of files for task-level context. - -There are two types of instructions files: - -- **.github/copilot-instructions.md**, a single instruction file sent to Copilot for **every** chat prompt for the repository. This file should contain project-level information, context which is relevant for most chat requests sent to Copilot. This could include the tech stack being used, an overview of what's being built and best practices, and other global guidance for Copilot. -- **\*.instructions.md** files can be created for specific tasks or file types. You can use **\*.instructions.md** files to provide guidelines for particular languages (like Python or TypeScript), or for tasks like creating a React component or a new set of unit tests. - -> [!NOTE] -> When working in your IDE, instructions files are only used for code generation in Copilot Chat, and not used for code completions or next edit suggestions. -> -> Copilot coding agent will utilize both repository level and \*.instructions with `applyTo` header matter when generating code. - -## Best practices for managing instructions files - -A full conversation about creating instructions files is beyond the scope of the workshop. However, the examples provided in the sample project provide a representative example of how to approach their management. At a high level: - -- Keep instructions in **copilot-instructions.md** focused on project-level guidance, such as a description of what's being built, the structure of the project, and global coding standards. -- Use **\*.instructions.md** files to provide specific instructions for file types (unit tests, React components, API endpoints), or for specific tasks. -- Use natural language in your instructions files. Keep guidance clear. Provide examples of how code should (and shouldn't) look. - -There isn't one specific way to create instructions files, just as there isn't one specific way to use AI. You will find through experimentation what works best for your project. The guidance provided here and the [resources](#resources) below should help you get started. - -> [!TIP] -> Every project using GitHub Copilot should have a robust collection of instructions files to provide context and best guide code generation. As you explore the instructions files in the project, you may notice there are ones for numerous types of files and tasks, including [UI updates](../.github/instructions/ui.instructions.md) and [Astro](../.github/instructions/astro.instructions.md). The investment made in instructions files will greatly enhance the quality of code suggestion from Copilot, ensuring it better matches the style and requirements your organization has. -> -> You can even have Copilot aid in generating instructions files by selecting the gear icon for **Configure Chat** in Copilot chat and selecting **Generate Agent Instructions**. -> -> ![Screenshot of option in GitHub Copilot chat with configure chat highlighted and generate agent instructions highlighted](./images/ex2-generate-instructions.png) - -## Use GitHub Copilot Chat before updating custom instructions - -To see the impact of custom instructions, you'll start by sending a prompt with the current version of the files. You'll then make some updates, send the same prompt again, and note the difference. - -1. Return to your codespace. -2. Close any open files in your codespace from the previous exercises. This will ensure Copilot has the context you want it to have. -3. Open `server/routes/publishers.py`, an empty file. -4. If **Copilot chat** is not already open, open it by selecting the Copilot icon towards the top of your codespace. -5. Create a new chat session by typing `/clear` into the chat window and selecting Enter (or return on a Mac). -6. Select **Ask** from the modes dropdown. - - ![Chat mode selection dialog with Ask mode highlighted](./images/ex2-select-chat-mode.png) - -7. Send the following prompt to create a new endpoint to return all publishers: - - ```plaintext - Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. - ``` - -8. Copilot explores the project to learn how best to implement the code, and generates a list of suggestions, which may include code for `publishers.py`, `app.py`, and tests to ensure the new code runs correctly. -9. Explore the code, noticing the generated code includes [type hints][python-type-hints] because, as you'll see, the custom instructions includes the directive to include them. -10. Notice the generated code **is missing** either a docstring or a comment header - or both! - -> [!IMPORTANT] -> As highlighted previously, GitHub Copilot and LLM tools are probabilistic, not deterministic. As a result, the exact code generated may vary, and there's even a chance it'll abide by your rules without you spelling it out! But to aid consistency in code you should always document anything you want to ensure Copilot should understand about how you want your code generated. - -## Add new repository standards to copilot-instructions.md - -As highlighted previously, `copilot-instructions.md` is designed to provide project-level information to Copilot. Let's ensure repository coding standards are documented to improve code suggestions from Copilot chat. - -1. Return to your codespace. -2. Open `.github/copilot-instructions.md`. -3. Explore the file, noting the brief description of the project and sections for **Code standards**, **Scripts** and **GitHub Actions Workflows**. These are applicable to any interactions you'd have with Copilot, are robust, and provide clear guidance on what you're doing and how you want to accomplish it. -4. Locate the **Code formatting requirements** section, which should be near line 27. Note how it contains a note to use type hints. That's why you saw those in the code generated previously. -5. Add the following lines of markdown right below the note about type hints to instruct Copilot to add comment headers to files and docstrings (which should be near line 27): - - ```markdown - - Every function should have docstrings or the language equivalent. - - Before imports or any code, add a comment block to the file that explains its purpose. - ``` - -6. Close **copilot-instructions.md**. -7. Select **New Chat** in Copilot chat to clear the buffer and start a new conversation. -8. Return to **server/routes/publishers.py** to ensure focus is set correctly. -9. Send the same prompt as before to create the endpoint. - - ```plaintext - Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. - ``` - -10. Notice how the newly generated code includes a comment header at the top of the file which resembles the following: - - ```python - """ - Publisher API routes for the Tailspin Toys Crowd Funding platform. - This module provides endpoints to retrieve publisher information. - """ - ``` - -11. Notice how the newly generated code includes a docstring inside the function which resembles the following: - - ```python - """ - Returns a list of all publishers with their id and name. - - Returns: - Response: JSON response containing an array of publisher objects - """ - ``` - -12. Notice the generated code now includes a docstring as well as a comment block at the top! -13. Also note how the existing code isn't updated, but of course you could ask Copilot to perform that operation if you so desired! -14. **Don't implement the suggested changes**, as you'll be doing that in the next section. - -> [!NOTE] -> If you accepted the changes, you can always select the **Undo** button towards the top right of the Copilot chat window. - -From this section, you explored how the custom instructions file has provided Copilot with the context it needs to generate code that follows the established guidelines. - -## Instructions for specific tasks or files - -Coding is often repetitive, with developers performing similar tasks on a regular basis. Copilot is wonderful for allowing you to offload the mundane, like adding endpoints, generating tests, or building a new component. But all code has a set of requirements, and often require a particular template or structure to be followed. **\*.instructions.md** files allow you to provide tailored guidance for these types of tasks and files. They can be added manually when using Copilot Chat, or can have an `applyTo:` tag added to the top of the file to have Copilot automatically use them for specific files. - -## Explore a task-specific custom instructions file - -You want to create a new endpoint to list all publishers, and to follow the same pattern used for the existing [games endpoints][games-endpoints], and to create tests which follow the same pattern as the existing [games endpoints tests][games-tests]. An instruction file has already been created; let's explore it and see the difference in code it generates. - -1. Open `.github/instructions/python-tests.instructions.md`. -2. Note the `applyTo:` section at the top, which contains a filter for all files in the `server/tests` directory which start with `test_` and have a `.py` extension. Whenever Copilot Chat interacts with a file which matches this pattern it will automatically use the guidance provided in this file. -3. Note the file contains guidance about how tests should be created, and how to utilize SQLite when testing database functionality. -4. Open `.github/instructions/flask-endpoint.instructions.md`. -5. Review the following entries inside the instruction file, which includes: - - - an overview of requirements, including that tests must be created, and endpoints are created in Flask using blueprints. - - a link to another the previously mentioned `python-tests.instructions.md` file. - - links to two existing files which follow the patterns you want - both the games blueprint and tests. Notice how these are setup as normal markdown links, allowing an instruction file to incorporate additional files for context. - -6. Return to `server/routes/publishers.py` to ensure focus is set correctly. -7. Return to Copilot Chat and select **New Chat** to start a new session. -8. Select **Edit** from the mode dropdown, which will allow Copilot to update multiple files. - - ![Copilot Chat mode selector with Edit chosen and highlighted](./images/ex2-select-edit-mode.png) - -> [!NOTE] -> If you have any issues running the tests in this part of the exercise, please undo your changes and retry from the above step using **Agent** mode instead. - -9. Select the **Add Context** button to open the context dialog -10. If prompted to allow the codespace to see text and images copied to the clipboard, select **Allow**. -11. Select **Instructions** from the dropdown at the top of your codespace. - -> [!TIP] -> If the list of options is long, you can type **instructions** to filter to the Instructions option then select **Instructions**. - -12. Select **flask-endpoint .github/instructions** to add the instruction file to the context. - - ![Screenshot showing the instruction file being added into Copilot Chat](images/ex2-add-instructions-file.png) - -13. Send the same prompt as before to generate the desired endpoint: - - ```plaintext - Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. - ``` - -> [!NOTE] -> While the up-arrow shortcut to resend a prior command is handy, it will reset any context you might add as well. If you added in the instructions file as context, then use the up arrow, it will remove the instructions file. For this particular step, make sure you copy/paste (or type) the command to avoid accidentally removing context. - -14. Note the **References** section and how it uses the **flask-endpoint.instructions.md** file to provide context. If you use instructions files with Copilot agent mode, you will notice that Copilot explores and reads the files referenced in the instructions file. - - ![Screenshot of the references section, showing the included instructions file](./images/ex2-copilot-instructions-references.png) - -15. Copilot generates the files. Notice how it generates updates across multiple files, like **publishers.py** and **test_publishers.py** - -> [!NOTE] -> Note that the code generated may diverge from some of the standards we set. AI tools like Copilot are non-deterministic, and may not always provide the same result. The other files in our codebase do not contain docstrings or comment headers, which could lead Copilot in another direction. Consistency is key, so making sure that your code follows the established patterns is important. You can always follow-up in chat and ask Copilot to follow your coding standards, which will help guide it in the right direction. - -16. After reviewing the code, select **Keep** in Copilot Chat to accept the changes. -17. Open a terminal window by selecting Ctl+\`. -18. Run the tests by running the script with the following command: - - ```sh - ./scripts/run-server-tests.sh - ``` - -19. Once the code is correct, and all tests pass, open the **Source Control** panel on the left of the Codespace and review the changes made by Copilot. -20. Stage the changes by selecting the **+** icon in the **Source Control** panel. -21. Generate a commit message using the **Sparkle** button. - - ![Screenshot of the Source Control panel showing the changes made](images/ex2-source-control-changes.png) - -22. Commit the changes to your repository by selecting **Commit**. - -## Summary and next steps - -Congratulations! You explored how to ensure Copilot has the right context to generate code following the practices your organization has set forth. This can be done at a repository level with the **.github/copilot-instructions.md** file, or on a task basis with instruction files. You explored how to: - -- provide Copilot with project-specific context, coding guidelines and documentation standards using custom instructions (.github/copilot-instructions.md). -- use instruction files to guide Copilot for repetitive or templated tasks. -- implement both repository-wide instructions and task-specific instructions. - -Next we'll use [agent mode to add functionality to the site][next-lesson]. - -## Resources - -- [Instruction files for GitHub Copilot customization][instruction-files] -- [5 tips for writing better custom instructions for Copilot][copilot-instructions-five-tips] -- [Best practices for creating custom instructions][instructions-best-practices] -- [Personal custom instructions for GitHub Copilot][personal-instructions] -- [Awesome Copilot - a collection of instructions files and other resources][awesome-copilot] - ---- - -| [← Previous lesson: Model Context Protocol (MCP)][previous-lesson] | [Next lesson: Copilot agent mode β†’][next-lesson] | -|:--|--:| - -[previous-lesson]: ./1-mcp.md -[next-lesson]: ./3-copilot-agent-mode-vscode.md -[instruction-files]: https://code.visualstudio.com/docs/copilot/copilot-customization -[python-type-hints]: https://docs.python.org/3/library/typing.html -[games-endpoints]: ../server/routes/games.py -[games-tests]: ../server/tests/test_games.py -[instructions-best-practices]: https://docs.github.com/enterprise-cloud@latest/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#adding-custom-instructions-to-your-repository -[personal-instructions]: https://docs.github.com/copilot/customizing-copilot/adding-personal-custom-instructions-for-github-copilot -[copilot-instructions-five-tips]: https://github.blog/ai-and-ml/github-copilot/5-tips-for-writing-better-custom-instructions-for-copilot/ -[awesome-copilot]: https://github.com/github/awesome-copilot diff --git a/workshop-content/3-copilot-agent-mode-vscode.md b/workshop-content/3-copilot-agent-mode-vscode.md deleted file mode 100644 index 42f18b3f..00000000 --- a/workshop-content/3-copilot-agent-mode-vscode.md +++ /dev/null @@ -1,224 +0,0 @@ -# Exercise 3 - Adding new functionality with Copilot Agent Mode - -| [← Previous lesson: Custom instructions][previous-lesson] | [Next lesson: GitHub Copilot coding agent β†’][next-lesson] | -|:--|--:| - -Even the simplest of updates to an application typically require updates to multiple files and operations to be performed like running tests. As a developer your flow typically involves tracking down all the necessary files, making the changes, running the tests, debugging, figuring out which file was missed, making another update... The list goes on and on. - -This is where Copilot Agent Mode comes into play. - -Copilot Agent Mode is built to act more autonomously in your IDE. It behaves in a similar fashion to a developer, starting by exploring the existing project structure, performing the necessary updates, running tasks like tests, and automatically fixing any discovered mistakes. Let's explore how you can use Agent Mode to introduce new functionality to your site. - -> [!NOTE] -> While the names are similar, agent mode and coding agent are built for two different types of experiences. Agent mode performs its tasks in your IDE, allowing for quick feedback cycles and interaction. Coding agent is designed as a peer programmer, working asynchronously like a member of the team, interacting with you via issues and pull requests. - -In this exercise, you will learn how: - -- Copilot Agent Mode can explore your project, identify relevant files, and make coordinated changes. -- GitHub Copilot Agent Mode can implement new features across both backend and frontend codebases. -- to review changes and tests generated by Copilot Agent Mode before merging into your codebase. - -## Scenario - -As the list of games grows, you want to allow users to filter by category. This will require updating both the API and UI, and updating the tests for the API. With the help of Copilot Agent Mode you'll work with your AI pair programmer to add the new feature! - -## Running the Tailspin Toys website - -Before you make any changes, let's explore the Tailspin Toys website to understand its current functionality. - -The website is a crowdfunding platform for board games with a developer theme. It allows users to list games and display details about them. The website has two main components: the front-end (written in Svelte) and the backend (written in Python). - -### Starting the website - -To make running the website easier, a script has been provided that will start both the front-end and back-end servers. You can run this script in your GitHub Codespace to start the website with the following instructions: - -1. Return to your codespace. You'll continue working in your current branch. -2. Open a new terminal window inside your codespace by selecting Ctl + \`. -3. Run the following script to start the website: - - ```bash - scripts/start-app.sh - ``` - - Once the script is running, you should see output indicating that both the front-end and back-end servers are running, similar to the below: - - ```bash - Server (Flask) running at: http://localhost:5100 - Client (Astro) server running at: http://localhost:4321 - ``` - -> [!NOTE] -> If a dialog box opens prompting you to open a browser window for `http://localhost:5100` close it by selecting the **x**. - -4. Open the website by using Ctrl-**Click** (or Cmd-**Click** on a Mac) on the client address `http://localhost:4321` in the terminal. - -> [!NOTE] -> When using a codespace, selecting a link for the localhost URL from the Codespace terminal will automatically redirect you to `https://-4321.app.github.dev/`. This is a private tunnel to your codespace, which is now hosting your web server! - -### Exploring the website - -Once the website is running, you can explore its functionality. The main features of the website include: - -- **Home Page**: Displays a list of board games with their titles, images, and descriptions. -- **Game Details Page**: When you select a game, you'll be brought to a details page with more information about the game, including its title, description, publisher and category. - -## Explore the backlog with Copilot - -The initial implementation of the website is functional, but we want to enhance it by adding new capabilities. Let's start off by reviewing the backlog. Ask GitHub Copilot to show you the backlog of items that we created in the previous exercise. - -1. Return to your codespace. -2. Open **Copilot Chat**. -3. Create a new chat session by selecting the **New Chat** button, which will remove any previous context. -4. Ensure **Agent** is selected from the list of modes. -5. Select **Claude Sonnet 4.5** from the list of available models. - -> [!IMPORTANT] -> The authors of this lab are not indicating a preference towards one model or another. When building this lab, we used Claude Sonnet 4.5, and as such are including that in the instructions. The hope is the code suggestions you receive will be relatively consistent to ensure a good experience. However, because LLMs are probabilistic, you may notice the suggestions received differ from what is indicated in the lab. This is perfectly normal and expected. - -6. Open the GitHub Copilot Chat, if it is not already open. -7. Switch to **Agent** mode, if you are not already in agent mode. -8. Select **Claude Sonnet 4.5** from the list of available models. - -> [!IMPORTANT] -> The authors of this lab are not indicating a preference towards one model or another. When building this lab, we used Claude Sonnet 4.5, and as such are including that in the instructions. The hope is the code suggestions you receive will be relatively consistent to ensure a good experience. However, because LLMs are probabilistic, you may notice the suggestions received differ from what is indicated in the lab. This is perfectly normal and expected. - -> [!NOTE] -> Because of the probabilistic nature of LLMs, Copilot may utilize a different MCP command, but should still be able to complete the task. - -8. Ask Copilot about the backlog of issues by sending the following prompt to Copilot: - - ```plaintext - Please show me the backlog of items from my GitHub repository. Help me prioritize them based on those which will be most useful to the user. - ``` -9. Select **Continue** to run the command to list all issues. -10. Review the generated list of issues. - -Notice how Copilot has even prioritized the items for you, based on the ones that it thinks will be most useful to the user. - -## Review instructions files - -Before kicking off the agent to generate the code, it's a good time to review the instructions file you'll use to provide Copilot context for its work. You're going to take advantage of the [user interface (UI)](../.github/instructions/ui.instructions.md) file, which contains context on how to approach adding functionality to the website. - -1. In your codespace, navigate to **.github/instructions/ui.instructions.md**. -2. Take note of the overall guidance on how to approach adding functionality. This includes: - - An overview of the architecture. - - Principles for component design, testability and accessibility. - - Links to specific instructions files for various file types, including: - - Astro - - Svelte - - Tailwind CSS - -> [!TIP] -> Instructions files allow you to reference both other instructions files and files in your project. The paths are relative to the location of the instructions file. This allows for reuse, breaking down complex instructions into smaller more manageable chunks, and providing examples and templates. - -## Implement the filtering functionality - -To implement filtering, no less than three separate updates will need to be made to the application: - -- A new endpoint added to the API -- A new set of tests for the new endpoint -- Updates to the UI to introduce the functionality - -In addition, the tests need to run (and pass) before you merge everything into your codebase. Copilot Agent Mode can perform these tasks for you! Let's add the functionality. - -1. You can continue in the current conversation with Copilot, or start a new one by selecting **New Chat**. -2. Select **Add Context**, **Instructions**, and **ui** as the instructions file. - - ![Screenshot showing an example of selecting the UI instructions file](images/ex3-select-instructions-file.png) - -3. Ensure **Agent** mode is still selected. - - ![Screenshot of Copilot Chat mode selection with Agent highlighted](./images/shared-agent-mode-dropdown.png) - -4. Ensure **Claude Sonnet 4.5** is still selected for the model. -5. Prompt Copilot to implement the functionality based on the issue you created earlier by using the following prompt: - - ```plaintext - Please update the site to include filtering by publisher and category based on the requirements from the related GitHub issue in the backlog. Ensure all tests are passing before completion. The server is already running, so you do not need to start it up. - ``` - -6. Watch as Copilot begins by exploring the project, locating the files associated with the desired functionality. You should see it finding both the API and UI definitions, as well as the tests. It then begins modifying the files and running the tests. - - ![Screenshot showing Copilot exploring the project files](images/ex3-agent-mode-explores.png) - -> [!NOTE] -> You will notice that Copilot will perform several tasks, like exploring the project, modifying files, and running tests. It may take a few minutes depending on the complexity of the task and the codebase. During that process, you may notice **Keep** and **Undo** buttons appear in the code editor. When Copilot is finished, you will have a **Keep** or **Undo** for all of the changes, so you do not need to select them while work is in progress. - -7. As prompted by Copilot, select **Continue** to run the tests. - - ![Screenshot showing a dialog in the Copilot Chat pane asking the user to confirm they are happy to run tests](images/ex3-agent-mode-run-tests.png) - -8. You may experience some pauses and even see some tests fail throughout the process. That's okay! Copilot works back and forth between code generation and tests until it completes the task and doesn't detect any errors. - - ![Screenshot showing a complete Chat session with Copilot Agent Mode](images/ex3-agent-mode-proposed-changes.png) - -9. Explore the generated code for any potential issues. - -> [!IMPORTANT] -> Remember, it's always important to review the code that Copilot or any AI tools generate. - -10. Return to the browser with the website running. Explore the new functionality! -11. Once you've confirmed everything works and reviewed the code, select **Keep** in the Copilot Chat window. - -## Publish the branch and create a pull request - -With your changes created locally you're ready to create a pull request (PR) to allow for your team to review your suggested changes and work through your DevOps process. The first step in that process is to publish the branch. Let's take care of that first. - -1. Navigate to the **Source Control** panel in the Codespace and review the changes made by Copilot. -2. Stage the changes by selecting the **+** icon. -3. Generate a commit message using the **Sparkle** button. - - ![Screenshot of the Source Control panel showing the changes made](images/ex3-source-control-changes.png) - -4. Select **Publish** to push the branch to your repository. - -## Create the pull request - -There are several ways to create a pull request, including through github.com and the GitHub command-line interface (CLI). But since you're already working with GitHub Copilot, let's let it create the PR for you! You can have it find the relevant issue and create the PR with an association to the located issue. - -1. Navigate to the Copilot Chat panel and select **New Chat** to start a new session. -2. Ask Copilot to create a PR for you: - - ```plaintext - Find the issue in the repo related to filtering by category and publisher. Create a new pull request for the current add-filters branch, and associate it with the correct issue. - ``` - -3. As needed, select **Continue** to allow Copilot to perform the tasks necessary to gather information and perform operations. -4. Notice how Copilot searches through the issues, finds the right one, and creates the PR. -5. Select the link generated by Copilot to review your pull request, but please **don't merge it yet**. - -## Summary and next steps - -Congratulations! In this exercise, we explored how to use GitHub Copilot Agent Mode to add new capabilities to the Tailspin Toys website. We learned how: - -- GitHub Copilot Agent Mode can implement new features across both backend and frontend codebases. -- Copilot Agent Mode can explore your project, identify relevant files, and make coordinated changes. -- to review changes and tests generated by Copilot Agent Mode before merging into your codebase. - -Now let's [return to our coding agent][next-lesson] to see how well it did with the issues we assigned to it. - -### Bonus exploration exercise – Implement paging - -As the list of games grows there will be a need for paging to be enabled. Using the skills you learned in this exercise, prompt Copilot to update the site to implement paging. Some considerations for the code include: - -- follow the existing best practices, including using the existing instructions files. -- consider how you want paging implemented, if you want to allow the user to select the page size or for it to be hard-coded. -- as you create the prompt ensure you provide Copilot with the necessary guidance to create the implementation as you desire. -- you may need to iterate with GitHub Copilot, asking for changes and providing context. This is the normal flow when working with Copilot! - -## Resources - -- [Coding agent 101][coding-agent-101] -- [Copilot ask, edit, and agent modes: What they do and when to use them][choose-mode] -- [Agent mode in VS Code][vs-code-agent-mode] - ---- - -| [← Previous lesson: Custom instructions][previous-lesson] | [Next lesson: GitHub Copilot coding agent β†’][next-lesson] | -|:--|--:| - -[previous-lesson]: ./2-custom-instructions.md -[next-lesson]: ./4-copilot-coding-agent.md -[coding-agent-101]: https://github.blog/ai-and-ml/github-copilot/agent-mode-101-all-about-github-copilots-powerful-mode/ -[choose-mode]: https://github.blog/ai-and-ml/github-copilot/copilot-ask-edit-and-agent-modes-what-they-do-and-when-to-use-them/ -[vs-code-agent-mode]: https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode diff --git a/workshop-content/4-copilot-coding-agent.md b/workshop-content/4-copilot-coding-agent.md deleted file mode 100644 index d8853f3a..00000000 --- a/workshop-content/4-copilot-coding-agent.md +++ /dev/null @@ -1,205 +0,0 @@ -# Exercise 4 - GitHub Copilot coding agent - -| [← Previous lesson: Copilot agent mode][previous-lesson] | [Next lesson: Reviewing coding agent β†’][next-lesson] | -|:--|--:| - -There are likely very few, if any, organizations who don't struggle with tech debt. This could be unresolved security issues, legacy code requiring updates, or feature requests which have languished on the backlog because there just wasn't the time to implement them. GitHub Copilot's coding agent is built to perform tasks such as updating code and adding functionality, all in an autonomous fashion. Once the agent completes its work, it generates a draft PR ready for a human developer to review. This allows offloading of tedious tasks and an acceleration of the development process, and frees developers to focus on larger picture items. - -You'll explore the following with Copilot coding agent: - -- customizing the environment for generating code. -- ensuring operations are performed securely. -- the importance of clearly scoped issues. -- assigning issues to Copilot. - -## Scenarios - -Tailspin Toys has some tech debt they'd like to address. The contractors initially hired to create the first version of the site left the documentation in an unideal state - and by that you'll notice it's completely lacking. As a first step, they'd like to see docstrings or the equivalent added to all functions in the application. - -Additionally, the design team is ready to get to work on building the UX for managing games. They don't need a full implementation yet, but they at least need some endpoints they can use for testing. Specifically, they need endpoints for the games API which will allow them to create, update and delete games. This is currently a blocker, but there are other issues which are of higher priority at the moment. - -These are both examples of tasks which can quickly find themselves deprioritized, and are great to assign to Copilot coding agent. Copilot coding agent can then work on them asynchronously, allowing the developer to focus on other tasks, then return to review Copilot's work and ensure everything is as expected. - -## Introducing GitHub Copilot coding agent - -[GitHub Copilot coding agent][coding-agent-overview] can perform tasks in the background, much in the same way a human developer would. And, just like with working with a human developer, this can be done in multiple ways, including [assigning a GitHub issue to Copilot][assign-issue]. Once assigned, Copilot will create a draft pull request to track its progress, setup an environment, and begin working on the task. You can dig into Copilot's session while it's still in flight or after its completed. Once its ready for you to review the proposed solution, it'll tag you in the pull request! - -## The importance of well-scoped instructions - -While it can often feel like it, there is no magic in GitHub Copilot. There are no magic solutions available, where you can with just a couple of sentences snap your fingers and let AI perform the entire task for you. In fact, even seemingly straight-forward operations can often have fair amount of complexity when you peel back the layers. - -As a result, you want to [be mindful about how you approach assigning tasks to Copilot coding agent][coding-agent-best-practices]. Working with Copilot as an AI pair programmer is typically the best approach. Approach tasks, big and small, following the same strategy you would without Copilot - work in stages, learn, experiment, and adapt accordingly. - -As always, the fundamentals of software development do not change with the addition of generative AI. - -## Setting up the dev environment for the Copilot coding agent - -Creating code, regardless of who's involved, typically requires a specific environment and some setup scripts to be run to ensure everything is in a good state. This holds true when assigning tasks to Copilot, which is performing tasks in a similar fashion to a SWE. - -Coding agent uses [GitHub Actions][github-actions] for its environment when doing its work. You can customize this environment by creating a [special setup workflow][setup-workflow], configured in the **.github/workflows/copilot-setup-steps.yml** file, to run before it gets to work. This enables it to have access to the required development tools and dependencies. This has been pre-configured ahead of the lab to help the lab flow and allow this learning opportunity. It makes sure that Copilot had access to Python, Node.JS, and the required dependencies for the client and server: - -```yaml -name: "Copilot Setup Steps" - -# Allows you to test the setup steps from your repository's "Actions" tab -on: workflow_dispatch - -jobs: - copilot-setup-steps: - runs-on: ubuntu-latest - # Set the permissions to the lowest permissions possible needed for *your steps*. Copilot will be given its own token for its operations. - permissions: - # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. - contents: read - steps: - - name: Checkout code - uses: actions/checkout@v5 - - # Backend setup - Python - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" - cache: "pip" - - - name: Install Python dependencies - run: ./scripts/setup-env.sh - - # Frontend setup - Node.js - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: "22" - cache: "npm" - cache-dependency-path: "./client/package.json" - - - name: Install JavaScript dependencies - working-directory: ./client - run: npm ci - - - name: Install Playwright - working-directory: ./client - run: npx playwright install -``` - -It looks like any other GitHub workflow file, but it has a few key points: - -- It contains a single job called **copilot-setup-steps**. This job is executed in GitHub Actions before Copilot starts working on the pull request. -- Notice the **workflow_dispatch** trigger, which allows you to run the workflow manually from the Actions tab of your repository. This is useful for testing that the workflow runs successfully instead of waiting for Copilot to run it. - -## Adding documentation - -While everyone understands the importance of documentation, most projects have either outdated information or lack it altogether. This is the type of tech debt which often goes unaddressed, slowing productivity and making it more difficult to maintain the codebase or bring new developers into the team. Fortunately, Copilot shines at creating documentation, and this is a perfect issue to assign to Copilot coding agent. It'll work in the background to generate the necessary documentation. In a future exercise you'll return to review its work. - -1. Navigate to your repository on github.com in a new browser tab. -2. Select the **Issues** tab. -3. Select **New issue** to open the new issue dialog. -4. Select **Blank issue** to create the new issue. -5. Set the **Title** to `Code lacks documentation`. -6. Set the **Description** to: - - ```plaintext - Our organization has a requirement that all functions have docstrings or the language equivalent. Unfortunately, recent updates haven't followed this standard. We need to update the existing code to ensure docstrings (or the equivalent) are included with every function or method. - ``` - -7. Select **Create** to create the issue. -8. On the right side, select **Assign to Copilot** to open the assignment dialog. - - ![Assigning Copilot to an issue](images/shared-assign-copilot.png) - -9. Select **Assign**. - - ![Copilot assignment details](images/ex4-assign-copilot-details.png) - -10. Select the **Pull Requests** tab. -11. Open the newly generated pull request (PR), which will be titled something similar to **[WIP]: Code lacks documentation**. If a new PR doesn't appear on the list, wait for a moment or two and refresh the browser window. -12. After a few minutes, you should see that Copilot has created a todo list. - -> [!NOTE] -> It make take several minutes for the todo list from Copilot to appear in the PR. Copilot is creating its environment (running the workflow highlighted previously), analyzing the project, and determining the best approach to tackling the problem. - -13. Review the list and the tasks it's going to complete. -14. Scroll down the pull request timeline, and you should see an update that Copilot has started working on the issue. -15. Select the **View session** button. - - ![Copilot session view](images/ex4-view-session.png) - -> [!IMPORTANT] -> You may need to refresh the window to see the updated indicator. - -16. Notice that you can scroll through the live session, and how Copilot is solving the problem. That includes exploring the code and understanding the state, how Copilot pauses to think and decide on the appropriate plan and also creating code. - -This will likely take several minutes. One of the primary goals of Copilot coding agent is to allow it to perform tasks asynchronously, freeing us to focus on other tasks. We're going to take advantage of that very feature by both assigning another task to Copilot coding agent, then turning our attention to writing some code to add features to our application. - -## Create new endpoints to modify games - -As has been highlighted, one of the great advantages of GitHub Copilot coding agent is the ability to divide work, where you can focus on one set of tasks while it focuses on another. While creating the endpoints for modifying games for the design team might not necessarily take a long time, it's still time which could be used for other tasks. Let's assign it to Copilot coding agent! - -1. Return to your repository on github.com. -2. Select the **Issues** tab. -3. Select **New issue** to open the new issue dialogue. -4. Select **Blank issue** to use the blank template. -5. Set the **Title** to: `Add endpoints to create and edit games` -6. Set the **Description** to: - - ```markdown - We're going to be creating functionality in the future to allow for the submission (and editing) of games. For now we just want the endpoints so we can explore how we want to create the UX and do some acceptance testing. Our requirements are: - - - Add new endpoints to the Games API to support creating, updating and deleting games - - There should be appropriate error handling for all new endpoints - - There should be unit tests created for all new endpoints - - Before creating the PR, ensure all tests pass - ``` - -7. Select **Create** to create the issue. -8. On the right side, select **Assign to Copilot** to open the assignment dialog. - - ![Assigning Copilot to an issue](images/shared-assign-copilot.png) - -9. Select **Assign**. - -Shortly after, you should see a set of πŸ‘€ on the first comment in the issue, indicating Copilot is on the job! - -![Copilot uses the eyes emoji to indicate it's working on the issue](images/ex4-issue-eyes-emoji.png) - -9. Select **Assign** to assign the issue to Copilot coding agent. - -Copilot is now diligently working on your second request! Copilot coding agent works in a similar fashion to a SWE, so you don't need to actively monitor it, but instead review once it's completed. Let's turn your attention to writing code and adding other features. - -## Summary and next steps - -With coding agent working diligently in the background, you can now turn your attention to your next lesson, [creating and using custom agents][next-lesson]. [Copilot coding agent can also use MCP servers][coding-agent-mcp], and has custom instructions available to it, which you explored in earlier modules. - -## Summary and next steps - -This lesson explored [GitHub Copilot coding agent][copilot-agents], your AI peer programmer. With coding agent you can assign issues to Copilot to perform asynchronously. You can use Copilot to address tech debt, create new features, or aid in migrating code from one framework to another. - -You explored these concepts: - -- customizing the environment for generating code. -- ensuring operations are performed securely. -- the importance of clearly scoped issues. -- assigning issues to Copilot. - -With coding agent working diligently in the background, we can now turn our attention to our next lesson, [creating and using custom agents][next-lesson]. [Copilot coding agent can also use MCP servers][coding-agent-mcp], and has custom instructions available to it, which we explored in earlier modules. - -## Resources - -- [About Copilot coding agent][copilot-agents] -- [Assigning GitHub issues to Copilot][assign-issue] -- [Copilot coding agent setup workflow best practices][coding-agent-best-practices] - ---- - -| [← Previous lesson: Copilot agent mode][previous-lesson] | [Next lesson: Custom agents β†’][next-lesson] | -|:--|--:| - -[coding-agent-overview]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot#overview-of-copilot-coding-agent -[coding-agent-mcp]: https://docs.github.com/copilot/how-tos/agents/copilot-coding-agent/extending-copilot-coding-agent-with-mcp -[assign-issue]: https://docs.github.com/copilot/using-github-copilot/coding-agent/using-copilot-to-work-on-an-issue -[setup-workflow]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#pre-installing-dependencies-in-github-copilots-environment -[copilot-agents]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot -[coding-agent-best-practices]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks -[github-actions]: https://docs.github.com/actions -[next-lesson]: ./5-custom-agents.md -[previous-lesson]: ./3-copilot-agent-mode-vscode.md diff --git a/workshop-content/7-iterating-copilot-work.md b/workshop-content/7-iterating-copilot-work.md deleted file mode 100644 index 96acd7d8..00000000 --- a/workshop-content/7-iterating-copilot-work.md +++ /dev/null @@ -1,173 +0,0 @@ -# Exercise 7: Iterating on GitHub Copilot's work - -| [← Previous lesson: Mission control][previous-lesson] | -|:--| - -Throughout this lab you've assigned several issues to GitHub Copilot coding agent. You asked it to add documentation to your code, generate endpoints for the design team to iterate on, and implement accessibility features including high-contrast and light mode toggles. Let's explore the code changes it suggested and, if necessary, provide feedback to Copilot to improve its work. - -## Scenario - -As has been highlighted numerous times, the fundamentals of software design and DevOps do not change with the addition of generative AI. We always want to review the code generated, and work through our normal DevOps process. With that in mind, let's review the suggestions from GitHub Copilot for creating the documentation, new endpoints, and accessibility features before we turn on review for the rest of our team. - -## Security and GitHub Copilot coding agent - -Because Copilot coding agent performs its tasks asynchronously and without supervision, certain security constraints have been put in place to ensure everything remains safe. These include: - -- Copilot only has read access to your repository and write access **only** to the branch it will use for its code. -- Coding agent runs inside of GitHub Actions, where it will create a separate, ephemeral environment in which to work. -- Any GitHub Actions workflows require approval from a human before they can be run. -- [Access to external resources is limited by default][agent-firewall], including MCP servers. - -## Reviewing the generated documentation - -Let's start by exploring the first pull request (PR) generated by GitHub Copilot coding agent - adding documentation to your code. You'll perform this task by utilizing the standard PR interface in GitHub.com. - -> [!NOTE] -> When you explore the PR you may notice a warning about GitHub Copilot being blocked by a firewall. This **is expected**, as Copilot has limited access to external resources by default, including calls to external MCP servers. If you wish, you can [customize or disable the firewall for Copilot coding agent][agent-firewall]. - -1. Return to your repository on github.com. -2. Select **Pull Requests** to open the list of pull requests. -3. Open issue titled something similar to **Add missing documentation** or something more robust. - -> [!NOTE] -> If Copilot is still working on the task, the issue will contain the **[WIP]** flag. If so, wait for Copilot to complete the work. This may take a few minutes, so feel free to take a break, or reflect on everything you've learned so far. - -4. Once the pull request is ready, select the **Files changed** tab and review the changes. - - ![Files changed tab](images/shared-pr-files-changed.png) - -5. Explore the newly updated code, which includes the newly created docstrings and other documentation. The exact changes will vary. -6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. -7. You should see an indicator that some workflows are waiting for approval. -8. Click on the **Approve and run workflows** button to allow the workflows to run. - - ![Approve and run workflows](images/shared-approve-workflows.png) - -9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. - -## Requesting changes from GitHub Copilot - -Working with Copilot on a pull request is not just a one-way street. You can also tag Copilot in comments - like you would other members of your team - in the pull request, or inline comments of the code. Copilot will see these comments, and trigger another session to address them. Due to the non-deterministic results, we can't give prescriptive text of what to ask for. Some ideas of what to ask Copilot to update include: - -- Add comment headers to the top of each code file with a brief description of what they do. -- Add docstrings to TypeScript and Svelte files. -- Create a README in both the server and client folders with descriptions of the codebase of each. - -1. Add a comment requesting a change to the generated documentation, tagging **@copilot** like you would any user. Use one of the ideas above, or another suggestion for Copilot around documentation you'd like to see in the codebase. -2. Select **View Session** to watch Copilot perform its work. Notice how Copilot starts a new session to make the updates. -3. You can select **Back to pull request** to return to the pull request. - - ![Back to pull request](images/ex7-back-to-pr.png) - -4. Once Copilot has completed the changes, you should see a new commit in the pull request. -5. Select the **Files changed** tab to review the changes. - -Feel free to continue iterating until you are happy. Once happy, you can convert the PR to ready from a draft, and merge it into the main branch. - -![Convert PR to ready](images/ex7-ready-for-review.png) - -## Review the new endpoints - -Let's return to the PR Copilot generated for resolving our issue about adding endpoints to the games API for creating, updating and deleting games. - -1. Return to your repository in GitHub.com. -2. Select the **Pull Requests** tab. -3. Select the PR which has a title similar to **Add CRUD endpoints for games API** or something more robust. -4. Select the **Files changed** tab to review the code it generated. -5. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. -6. You should see an indicator that some workflows are waiting for approval. -7. Click on the **Approve and run workflows** button to allow the workflows to run. - - ![Approve and run workflows](images/shared-approve-workflows.png) - -8. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. -9. **Optional:** You could even switch to this branch in your Codespace to perform a manual test of the new endpoints. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created, e.g. **copilot/fix-8**.): - - ```bash - git fetch origin - git checkout - ``` - -Copilot has created the new endpoints! Just as before, you can work iteratively with Copilot coding agent to request updates. For example, you might want to request Copilot centralizes the error handling to reduce duplication, or ensuring comments and docstrings are added (remember - this was assigned **before** you made the updates to your custom instructions!) Just like before, you can make these requests by adding a new comment on the **Conversation** tab, which Copilot will see and kickoff a new session. - -## Review the accessibility features - -Finally, let's review the accessibility features that were implemented using the custom accessibility agent. This PR should include both the high-contrast mode you assigned in Exercise 5, and the light mode that was requested in mission control in Exercise 6. - -1. Return to your repository in GitHub.com. -2. Select the **Pull Requests** tab. -3. Select the PR which has a title similar to **Add high contrast mode to website** or something more robust. - -> [!NOTE] -> If Copilot is still working on the task, the issue will contain the **[WIP]** flag. If so, wait for Copilot to complete the work. This may take a few minutes. - -4. Select the **Files changed** tab to review the code it generated. -5. Review the implementation, paying particular attention to: - - The toggle UI components for switching between modes - - The use of local storage to persist user preferences - - The CSS or styling changes for high-contrast and light modes - - The accessibility attributes (ARIA labels, keyboard navigation, etc.) - - Any JavaScript/TypeScript code that manages the mode switching - -6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. -7. You should see an indicator that some workflows are waiting for approval. -8. Click on the **Approve and run workflows** button to allow the workflows to run. - - ![Approve and run workflows](images/shared-approve-workflows.png) - -9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. -10. **Optional:** You could switch to this branch in your Codespace to manually test the accessibility features. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created): - - ```bash - git fetch origin - git checkout - ``` - - Then start the application and test the high-contrast and light mode toggles in your browser to ensure they work as expected and persist across page reloads. - -Notice how the custom accessibility agent helped guide Copilot to implement these features following accessibility best practices. If you see any accessibility concerns or improvements, you can tag **@copilot** in a comment to request updates, just like you did with the previous PRs. - -## Optional Exercise - Explore the agent's capabilities with more issues - -Having access to a peer programmer who is able to explore our codebase and make changes asynchronously is powerful, allowing us to reach a first iteration across tasks quickly, allowing us to review and guide, or take over and continue coding in the editor. - -You have made great progress through the lab, and you're approaching the end. However, you're encouraged to create some additional issues in your GitHub repository and use Copilot to solve those. Some ideas include: - -- Create a backer interest form on the game details page -- Implement pagination on the game listing endpoint -- Add input validation and error handling to the Flask API -- Something else? What else might you consider? - -## Summary - -Congratulations! You completed the lab! You worked through several features available to you with GitHub Copilot, from the IDE to the repository. In particular you: - -- **Learned how to use GitHub Copilot and the Model Context Protocol (MCP) to streamline software development**. You set up the GitHub MCP server to enable Copilot to interact with your repository, created a detailed backlog using Copilot Agent Mode. -- **Explored how custom instructions and prompt files can guide Copilot to follow your project's coding standards.** You created a custom instructions file to provide context for Copilot, ensuring it generates code that adheres to your project's guidelines and used prompt files to provide guidance for repetitive tasks and established practices. -- **Used Copilot Agent Mode to implement new features, coordinate changes across backend and frontend code, and automate repetitive tasks.** You used GitHub Copilot to implement a new category and publisher filter for the game listing page, making changes across the client, backend, and the resulting tests. -- **Experienced Copilot as a peer programmer, being assigned issues and working collaboratively on pull requests.** You assigned Copilot to issues in your backlog, allowing it to create a pull request, build a plan, implement changes, and iterate further as you provided feedback. - -This is just the beginning, and we can't wait to see how you use Copilot to help you with your own projects. We hope you enjoyed the lab, and we look forward to seeing you in the next one! Happy coding! - -## Resources - -- [GitHub Copilot][github-copilot] -- [About Copilot agents][copilot-agents] -- [Assigning GitHub issues to Copilot][assign-issue] -- [Copilot coding agent setup workflow best practices][coding-agent-best-practices] -- [Configuring Copilot coding agent firewall][agent-firewall] - ---- - -| [← Previous lesson: Managing agents][previous-lesson] | -|:--| - -[github-copilot]: https://github.com/features/copilot -[coding-agent-overview]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot#overview-of-copilot-coding-agent -[assign-issue]: https://docs.github.com/copilot/using-github-copilot/coding-agent/using-copilot-to-work-on-an-issue -[setup-workflow]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#pre-installing-dependencies-in-github-copilots-environment -[copilot-agents]: https://docs.github.com/copilot/using-github-copilot/coding-agent/about-assigning-tasks-to-copilot -[coding-agent-best-practices]: https://docs.github.com/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks -[agent-firewall]: https://docs.github.com/copilot/customizing-copilot/customizing-or-disabling-the-firewall-for-copilot-coding-agent - -[previous-lesson]: ./6-managing-agents.md diff --git a/workshop-content/cli/2-install-copilot-cli.md b/workshop-content/cli/2-install-copilot-cli.md index 8c734164..4284064f 100644 --- a/workshop-content/cli/2-install-copilot-cli.md +++ b/workshop-content/cli/2-install-copilot-cli.md @@ -72,34 +72,23 @@ On first launch, Copilot CLI will prompt you to authenticate with your GitHub ac > [!NOTE] > In a codespace, you may already be authenticated through your GitHub session. If Copilot CLI starts without prompting for authentication, you're good to go! -## Trust the directory +## Trust the directory and verify everything is working -When you first use Copilot CLI in a directory, it will ask you to confirm that you trust the files in that folder. This is a security feature to prevent Copilot from accidentally working with untrusted code. +Now that you're at the Copilot CLI prompt for the first time, let's trust this workshop repository and make sure Copilot CLI is properly installed and connected. -1. When prompted, you'll see three options: +1. When Copilot CLI asks you to confirm that you trust the files in this folder, you'll see three options: - **Yes, proceed**: Trust for this session only - **Yes, and remember this folder for future sessions**: Trust permanently - **No, exit (Esc)**: Don't allow file access 2. For this workshop, select **Yes, and remember this folder for future sessions** since you'll be working in this repository throughout. - -## Verify everything is working - -Let's make sure Copilot CLI is properly installed and connected. - -1. If you exited Copilot CLI, start it again: - - ```bash - copilot - ``` - -2. Ask Copilot a simple question to verify it's working: +3. Ask Copilot a simple question to verify it's working: ``` What files are in this project? ``` -3. Copilot should explore the repository and provide a summary of the project structure. -4. Try the `/help` command to see available slash commands: +4. Copilot should explore the repository and provide a summary of the project structure. +5. Try the `/help` command to see available slash commands: ``` /help diff --git a/workshop-content/cli/4-generating-code.md b/workshop-content/cli/4-generating-code.md index ec0fcfa3..9c8b4dc5 100644 --- a/workshop-content/cli/4-generating-code.md +++ b/workshop-content/cli/4-generating-code.md @@ -16,6 +16,45 @@ In this exercise, you will: By the end of this exercise, you will have added new functionality to the project. +## Warm-up: see custom instructions in action + +Before we dive into building filtering, let's see firsthand how the instruction files you explored in Exercise 1 shape Copilot CLI's output. + +1. Return to your codespace and open a fresh terminal window. Start Copilot CLI by issuing the following command: + + ```bash + copilot --allow-all-tools + ``` + + If Copilot CLI is already running, clear Copilot's context by sending the `/clear` command in the prompt. + +2. Open **server/routes/publishers.py**, which is currently an empty file, so you can see Copilot's output land there. You can use `code server/routes/publishers.py` if you're in a codespace, or whatever editor you prefer. +3. Send the following prompt to Copilot CLI: + + ``` + Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. + ``` + +4. Review the generated code. You should notice that it includes type hints because those are already in **.github/copilot-instructions.md**, but it's missing a docstring and comment header. +5. Open **.github/copilot-instructions.md**. In the **Code formatting requirements** section, right under the type-hints note, add the following guidance: + + ```markdown + - Every function should have docstrings or the language equivalent. + - Before imports or any code, add a comment block to the file that explains its purpose. + ``` + +6. Return to Copilot CLI, send `/clear`, and then send the same publishers prompt again. This time, the new output should include a comment header and a docstring. +7. Don't keep the warm-up changes. Undo them in your editor, or run the following command: + + ```bash + git checkout -- server/routes/publishers.py + ``` + + You'll do the real project work in the next section. + +> [!IMPORTANT] +> AI output is non-deterministic, so your generated code may vary slightly. Focus on the pattern: updating the instructions changes the shape of Copilot CLI's next response. + ## Utilize plan mode One of the best uses of AI is planning. Oftentimes you'll have a good concept of what you want to build, but just need to bounce some ideas off of something. AI tools can help you crystalize your thoughts by asking you follow up questions and working through different pitfalls or missing components. To support this process, Copilot CLI offers a plan mode. @@ -52,10 +91,21 @@ You'll start the process of creating the new functionality by utilizing plan mod All AI code needs to be reviewed before being merged into production. Let's take the time now to explore the files Copilot created and modified in implementing the new feature. -1. Hide the terminal window in your codespace by selecting Ctrl+\`. -2. Select **Source Control** in your codespace. -3. Note the files changed. You should see updates to files such as **games.py**, the Games API, and **test_games.py**, the tests for that API. You should also see new files created, such as Svelte components for the new filter functionality, and Playwright tests to validate the frontend. -4. Open the files and explore the changes. In particular, notice the comment sections which have been added. All of this comes from the instructions files you worked on previously in this workshop. +1. Review the changed files using whichever workflow fits your environment: + + - **Option A: Terminal**. If you're working from the terminal, you can use git directly: + + ```bash + git status + git diff + ``` + + Use `git status` to see the changed files and `git diff` to see the specific changes. Open any files you want to inspect with your editor of choice, such as `code ` if you're in a codespace, or `$EDITOR ` otherwise. + + - **Option B: VS Code GUI**. If you have VS Code open, hide the terminal window in your codespace by selecting Ctrl+\`, then select **Source Control** in your codespace. + +2. Note the files changed. You should see updates to files such as **games.py**, the Games API, and **test_games.py**, the tests for that API. You should also see new files created, such as Svelte components for the new filter functionality, and Playwright tests to validate the frontend. +3. Open the files and explore the changes. In particular, notice the comment sections which have been added. All of this comes from the instructions files you worked on previously in this workshop. ## Summary and next steps diff --git a/workshop-content/cli/5-agent-skills.md b/workshop-content/cli/5-agent-skills.md index f92674d2..95ba2d1a 100644 --- a/workshop-content/cli/5-agent-skills.md +++ b/workshop-content/cli/5-agent-skills.md @@ -78,7 +78,7 @@ As highlighted previously, skills are automatically invoked by Copilot CLI. As a 5. Copilot will acknowledge the request. After a few moments, you'll notice Copilot will indicate it's utilizing the **branches-commits-prs** skill. - ![Screenshot of the agent skill being called by Copilot CLI](https://raw.githubusercontent.com/GeekTrainer/tailspin-toys-workshop/main/workshop/images/5-agent-skill.png) + ![Screenshot of the agent skill being called by Copilot CLI](../images/cli-5-agent-skill.png) 6. Copilot will then follow the instructions in the skill. It will start by running the tests, then create a branch, commits, and eventually the PR. 7. Once the PR is created, return to your repository and open the PR. Note the sections follow the guidelines set forth in the skill, matching the requirements the team put forth. diff --git a/workshop-content/cli/6-custom-agents.md b/workshop-content/cli/6-custom-agents.md index 3452119c..722ee00f 100644 --- a/workshop-content/cli/6-custom-agents.md +++ b/workshop-content/cli/6-custom-agents.md @@ -10,11 +10,7 @@ You'll explore the following with custom agents: - how to create a custom agent. - using a custom agent in Copilot CLI. -## Scenario - -Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. - -Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. +--8<-- "scenarios/accessibility.md" In this exercise, you will: diff --git a/workshop-content/cli/7-slash-commands.md b/workshop-content/cli/7-slash-commands.md index c13c690e..6a65fcde 100644 --- a/workshop-content/cli/7-slash-commands.md +++ b/workshop-content/cli/7-slash-commands.md @@ -3,7 +3,7 @@ | [← Previous lesson: Custom Agents][previous-lesson] | [Next lesson: Review β†’][next-lesson] | |:--|--:| -Like any good CLI tool, GitHub Copilot CLI includes many slash commands to interact with it. These commands expose advanced functionality, "behind-the-scenes" information, or additional configuration options. You've already explored a couple with `/clear` to clear context and `/mcp` to register MCP servers. Let's explore a couple of other powerful ones, including `/context`, `/models` and `/share`. +Like any good CLI tool, GitHub Copilot CLI includes many slash commands to interact with it. These commands expose advanced functionality, "behind-the-scenes" information, or additional configuration options. You've already explored a couple with `/clear` to clear context and `/mcp` to register MCP servers. Let's explore a couple of other powerful ones, including `/context`, `/models`, `/share`, and `/delegate`. ## Scenario @@ -14,6 +14,7 @@ In this exercise you will use: - `/share` to create a GitHub gist to share your session with the team. - `/context` to see the context Copilot CLI is currently using. - `/models` to explore the list of available models and select a new one if you so desire. +- `/delegate` to optionally hand off a task to coding agent. This requires Copilot Pro+, Business, or Enterprise with coding agent enabled. ## Sharing a session @@ -49,7 +50,7 @@ When working on larger or more complex tasks you may bump into the maximum conte 4. In just a couple of moments, Copilot CLI will generate a visual representation of its current context: - ![Screenshot of context window from Copilot CLI](https://raw.githubusercontent.com/GeekTrainer/tailspin-toys-workshop/main/workshop/images/7-context-window.png) + ![Screenshot of context window from Copilot CLI](../images/cli-7-context-window.png) 5. Note the model displayed (which may be different than the one in the image), and the current percentage of tokens used. The rest of the information highlights: @@ -105,13 +106,42 @@ Different models have different strengths, and different developers have differe > [!IMPORTANT] > Model selection persists in Copilot CLI. +## Delegating to coding agent (optional) + +There are times when you want to keep working in your terminal but hand off a longer-running task to Copilot coding agent. The `/delegate` command sends the current Copilot CLI session to GitHub.com, where coding agent picks it up, works asynchronously, and opens a pull request when done. + +> [!NOTE] +> `/delegate` requires Copilot Pro+, Business, or Enterprise with coding agent enabled. If you don't have access, read through this section and skip the hands-on steps. + +1. Clear the current session first so accumulated workshop context isn't delegated: + + ``` + /clear + ``` + +2. Send a small, well-scoped prompt. For example, you could delegate the stretch-goal pagination from the backlog you created in Exercise 3: + + ``` + Implement pagination on the games list page. Add support for page and pageSize query parameters on the games API, update the frontend to render pagination controls, and add tests. + ``` + +3. Send the following slash command to hand the session to coding agent, and confirm the prompt you want to delegate: + + ``` + /delegate + ``` + +4. Open [Copilot agents](https://github.com/copilot/agents) in a browser to monitor progress. +5. You don't need to wait for the pull request to complete in this path; you can return to it later. If you want to dig deeper into managing asynchronous agent work, continue with the [Cloud path](../cloud/README.md). + ## Summary and next steps -Using slash commands in Copilot CLI allows you to configure it, share sessions, and get internal information about how Copilot's working. In this lesson you used: +Using slash commands in Copilot CLI allows you to configure it, share sessions, and get internal information about how Copilot's working. In this lesson you used or explored: - `/share` to create a GitHub gist to share your session with the team. - `/context` to see the context Copilot CLI is currently using. - `/models` to explore the list of available models and select a new one if you so desire. +- Learned about `/delegate` as an optional bridge to coding agent. There are of course more slash commands available, and more to explore with Copilot CLI! Let's close out our journey by [reviewing what we've learned][next-lesson] and some next steps to continue learning. diff --git a/workshop-content/cli/8-review.md b/workshop-content/cli/8-review.md index 83525a01..44afd3e2 100644 --- a/workshop-content/cli/8-review.md +++ b/workshop-content/cli/8-review.md @@ -9,6 +9,7 @@ Over the last several exercises, you explored some of the most common uses cases - using instructions files to guide code generation. - implementing skills to add tools to the Copilot CLI toolbox. - calling custom agents for advanced and more complex tasks. +- using slash commands to manage your session, and optionally bridging back to coding agent via `/delegate`. Let's talk about some slash commands, best practices, and next steps. diff --git a/workshop-content/cli/README.md b/workshop-content/cli/README.md index 7182ed4c..5ece4c20 100644 --- a/workshop-content/cli/README.md +++ b/workshop-content/cli/README.md @@ -13,7 +13,7 @@ Welcome to the **Copilot CLI** learning path! This path focuses on GitHub Copilo | [4. Generating Code][ex4] | Code Generation | Use plan mode and generate features | | [5. Agent Skills][ex5] | Skills | Enhance Copilot with specialized skills | | [6. Custom Agents][ex6] | Agents | Create and use custom agents | -| [7. Slash Commands][ex7] | CLI Features | Explore context, models, and sharing | +| [7. Slash Commands][ex7] | CLI Features | Explore context, models, sharing, and optional delegation to coding agent | | [8. Review][ex8] | Summary | Review key concepts and next steps | ## Prerequisites diff --git a/workshop-content/cloud/2-coding-agent.md b/workshop-content/cloud/2-coding-agent.md index 7d81ac31..b4578749 100644 --- a/workshop-content/cloud/2-coding-agent.md +++ b/workshop-content/cloud/2-coding-agent.md @@ -1,4 +1,4 @@ -# Exercise 4 - GitHub Copilot coding agent +# Exercise 2 - GitHub Copilot coding agent | [← Previous lesson: Custom instructions][previous-lesson] | [Next lesson: Custom agents β†’][next-lesson] | |:--|--:| @@ -12,17 +12,9 @@ You'll explore the following with Copilot coding agent: - the importance of clearly scoped issues. - assigning issues to Copilot. -## Scenarios +--8<-- "scenarios/tech-debt.md" -Tailspin Toys has some tech debt they'd like to address. The contractors initially hired to create the first version of the site left the documentation in an unideal state - and by that you'll notice it's completely lacking. As a first step, they'd like to see docstrings or the equivalent added to all functions in the application. - -Additionally, the design team is ready to get to work on building the UX for managing games. They don't need a full implementation yet, but they at least need some endpoints they can use for testing. Specifically, they need endpoints for the games API which will allow them to create, update and delete games. This is currently a blocker, but there are other issues which are of higher priority at the moment. - -These are both examples of tasks which can quickly find themselves deprioritized, and are great to assign to Copilot coding agent. Copilot coding agent can then work on them asynchronously, allowing the developer to focus on other tasks, then return to review Copilot's work and ensure everything is as expected. - -## Introducing GitHub Copilot coding agent - -[GitHub Copilot coding agent][coding-agent-overview] can perform tasks in the background, much in the same way a human developer would. And, just like with working with a human developer, this can be done in multiple ways, including [assigning a GitHub issue to Copilot][assign-issue]. Once assigned, Copilot will create a draft pull request to track its progress, setup an environment, and begin working on the task. You can dig into Copilot's session while it's still in flight or after its completed. Once its ready for you to review the proposed solution, it'll tag you in the pull request! +--8<-- "sections/coding-agent-intro.md" ## The importance of well-scoped instructions @@ -32,6 +24,17 @@ As a result, you want to [be mindful about how you approach assigning tasks to C As always, the fundamentals of software development do not change with the addition of generative AI. +## Custom instructions in this repository + +In Exercise 1, you explored what instruction files are and how they guide Copilot. Before assigning work to Copilot coding agent, take a quick read-only look at the instruction files already included in this repository so you can spot their effect later. + +Open the following files in the GitHub web UI for your repository, or in a codespace if you already have one running: + +- **.github/copilot-instructions.md** - Review the **Code standards** section, especially the requirement that Python code includes type hints. +- **.github/instructions/python-tests.instructions.md** - Notice the `applyTo` frontmatter, which scopes these instructions to `server/tests/test_*.py` files. + +When you assign the *Code lacks documentation* issue to coding agent in the next section, watch the resulting pull request for type hints, docstrings, and comment headers - these come from these instruction files. We'll call this out again when reviewing PRs in Exercise 5. + ## Setting up the dev environment for the Copilot coding agent Creating code, regardless of who's involved, typically requires a specific environment and some setup scripts to be run to ensure everything is in a good state. This holds true when assigning tasks to Copilot, which is performing tasks in a similar fashion to a SWE. @@ -105,11 +108,11 @@ While everyone understands the importance of documentation, most projects have e 7. Select **Create** to create the issue. 8. On the right side, select **Assign to Copilot** to open the assignment dialog. - ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + ![Assigning Copilot to an issue](../images/shared-assign-copilot.png) 9. Select **Assign**. - ![Copilot assignment details]((../images/)ex4-assign-copilot-details.png) + ![Copilot assignment details](../images/ex4-assign-copilot-details.png) 10. Select the **Pull Requests** tab. 11. Open the newly generated pull request (PR), which will be titled something similar to **[WIP]: Code lacks documentation**. If a new PR doesn't appear on the list, wait for a moment or two and refresh the browser window. @@ -122,7 +125,7 @@ While everyone understands the importance of documentation, most projects have e 14. Scroll down the pull request timeline, and you should see an update that Copilot has started working on the issue. 15. Select the **View session** button. - ![Copilot session view]((../images/)ex4-view-session.png) + ![Copilot session view](../images/ex4-view-session.png) > [!IMPORTANT] > You may need to refresh the window to see the updated indicator. @@ -154,13 +157,13 @@ As has been highlighted, one of the great advantages of GitHub Copilot coding ag 7. Select **Create** to create the issue. 8. On the right side, select **Assign to Copilot** to open the assignment dialog. - ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + ![Assigning Copilot to an issue](../images/shared-assign-copilot.png) 9. Select **Assign**. Shortly after, you should see a set of πŸ‘€ on the first comment in the issue, indicating Copilot is on the job! -![Copilot uses the eyes emoji to indicate it's working on the issue]((../images/)ex4-issue-eyes-emoji.png) +![Copilot uses the eyes emoji to indicate it's working on the issue](../images/ex4-issue-eyes-emoji.png) 9. Select **Assign** to assign the issue to Copilot coding agent. @@ -168,10 +171,6 @@ Copilot is now diligently working on your second request! Copilot coding agent w ## Summary and next steps -With coding agent working diligently in the background, you can now turn your attention to your next lesson, [creating and using custom agents][next-lesson]. [Copilot coding agent can also use MCP servers][coding-agent-mcp], and has custom instructions available to it, which you explored in earlier modules. - -## Summary and next steps - This lesson explored [GitHub Copilot coding agent][copilot-agents], your AI peer programmer. With coding agent you can assign issues to Copilot to perform asynchronously. You can use Copilot to address tech debt, create new features, or aid in migrating code from one framework to another. You explored these concepts: diff --git a/workshop-content/cloud/3-custom-agents.md b/workshop-content/cloud/3-custom-agents.md index 2b3f0dd2..068df591 100644 --- a/workshop-content/cloud/3-custom-agents.md +++ b/workshop-content/cloud/3-custom-agents.md @@ -1,94 +1,18 @@ -# Exercise 5 - Custom agents +# Exercise 3 - Custom agents -| [← Previous lesson: Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | -|:--|--:| - -[Custom agents][custom-agents] in GitHub Copilot allow you to create specialized AI assistants tailored to specific tasks or domains within your development workflow. By defining agents through markdown files in the `.github/agents` folder of your repository, you can provide Copilot with focused instructions, best practices, coding patterns, and domain-specific knowledge that guide it to perform particular types of work more effectively. This allows teams to codify their expertise and standards into reusable agents. You might create an accessibility agent that ensures [WCAG][wcag] compliance, a security agent that follows secure coding practices, or a testing agent that maintains consistent test patternsβ€”enabling developers to leverage these specialized capabilities on-demand for faster, more consistent implementations. - -You'll explore the following with custom agents: - -- how to create a custom agent. -- assigning a task to a custom agent. - -## Scenario - -Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. - -Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. - -## Custom agents - -Custom agents are defined by markdown files in the **.github/agents** folder of your project. The markdown files will contain guidance for Copilot on how best to perform at task. - -## Reviewing the accessibility custom agent - -A custom agent has already been created for you for accessibility. Let's review the contents to understand how it will guide Copilot. - -1. Return to your codespace. -2. Open **.github/agents/accessibility.md**. -3. Note the header section with the name and description of the agent. - -> [!IMPORTANT] -> This section is required for custom agents. - -4. From there, scan and review the next sections which highlight: - - Core responsibilities when generating code for an accessible website. - - Best practices for accessibility. - - Code examples for HTML, CSS and JavaScript. - - A list of common pitfalls and mistakes. - -> [!NOTE] -> There is no "best markdown" for a custom agent. As with anything in AI, you will want to test and explore to determine what works best for your environments and scenarios. - -## Create and assign an issue - -Mission control is the central location for working with all agents for your environment. You can assign tasks to Copilot coding agent, monitor tasks, and even redirect and provide additional guidance. Let's start by assigning a task to create the high contrast mode to Copilot. - -1. Navigate to your repository. -2. Select the issues tab. -3. Select **New issue** to open the new issue dialog. -4. Select **Blank issue** to create the new issue. -5. Set the **Title** to `Add high contrast mode to website`. -6. Set the **Description** to: - - ```plaintext - We need a high contrast mode for the site. There should be a toggle for high contrast which the user can set. It should store the setting in local storage on the browser. - ``` - -7. Select **Create** to create the issue. -8. On the right side, select **Assign to Copilot** to open the assignment dialog. -9. Select **Accessibility agent** from the list of custom agents. - - ![Screenshot of coding agent assignment, with custom agent and accessibility highlighted](./(../images/)ex5-select-custom-agent.png) - -10. Select **Assign**. -11. Copilot gets to work on the task in the background! - -## Summary and next steps - -This lesson explored [custom agents][custom-agents] in GitHub Copilot, specialized AI assistants tailored to specific tasks and domains. With custom agents you can codify your team's expertise and standards into reusable agents that guide Copilot to perform particular types of work more effectively. - -You explored these concepts: - -- how to create a custom agent. -- assigning a task to a custom agent. +| [← Previous lesson: Copilot coding agent][previous-lesson] | +|:--| -With Copilot working on implementing the high contrast mode, we can now turn our attention to our next lesson, [using Copilot HQ to monitor and guide agent sessions][next-lesson]. Custom agents help ensure that Copilot follows your organization's best practices and domain-specific requirements, enabling faster and more consistent implementations across your team. +You're in the **Cloud path**. The custom agents workflow happens entirely on github.com and is identical for both paths, so this exercise lives in a shared module. -## Resources +➑️ **Start the exercise:** [Custom agents module](../shared/coding-agent/custom-agents.md) -- [Custom agents][custom-agents] -- [Preparing to use custom agents in your organization][org-custom-agents] -- [Preparing to use custom agents in your enterprise][enterprise-custom-agents] +When you finish, return here and continue with the next lesson below. --- | [← Previous lesson: Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | |:--|--:| -[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents -[wcag]: https://www.w3.org/WAI/standards-guidelines/wcag/ -[org-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-organization/prepare-for-custom-agents -[enterprise-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/prepare-for-custom-agents [next-lesson]: ./4-managing-agents.md [previous-lesson]: ./2-coding-agent.md diff --git a/workshop-content/cloud/4-managing-agents.md b/workshop-content/cloud/4-managing-agents.md index a373477a..118f1a87 100644 --- a/workshop-content/cloud/4-managing-agents.md +++ b/workshop-content/cloud/4-managing-agents.md @@ -1,92 +1,18 @@ -# Exercise 6 - Monitoring and managing agents +# Exercise 4 - Monitoring and managing agents -| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on Copilot's work β†’][next-lesson] | -|:--|--:| - -In the last couple of exercises you asked Copilot coding agent to take on three separate tasks focused on improving the user experience and adding functionality. While coding agent is built to operate asynchronously and autonomously, the ability to monitor these tasks is still important. - -There are numerous tools available to you to manage tasks assigned to coding agent, including [the agents page][agents-page] on GitHub.com. From this mission control you can see all agent tasks with open pull requests (PRs). You can explore the operations performed, and even steer an in-progress session to help guide it. - -In this lesson you will: - -- explore the agents page to monitor coding agent tasks. -- steer an in-flight session to request additional functionality. - -## Scenario - -After assigning the agent to create a high-contrast mode, the team realized it would be a good time to add a light mode as well. Since work was already being done to update the style of the site and add toggle functionality, it seemed logical to include this functionality. You want to steer the agent's work to ensure it adds a light mode as well as high contrast. - -## Review Copilot coding agent tasks - -Let's see the current status of all tasks assigned to Copilot coding agent. - -1. Navigate to agents page at [https://github.com/copilot/agents](https://github.com/copilot/agents). -2. Note the list of tasks, both on the main pane and on the left pane. You should see the list of the tasks you've assigned to Copilot, including: - - Updating documentation for your codebase. - - Generating APIs for modifying products. - - Adding a high contrast mode for the website. -3. Select one of the running tasks. Review the tasks which have been performed by Copilot. These can include: - - Checking out the code from the repository. - - Creating the environment for Copilot to work. - - Setting up MCP servers. - - Performing various steps to complete the assigned task. - -> [!NOTE] -> The exact steps listed will vary depending on the state of Copilot's work and the approach it took. - -4. Also note the pull request (PR) pane which appears on the right side. This allows you to see the PR and files changed for additional monitoring. - -## Steering coding agent - -Now that you've seen the tasks which are active, let's request Copilot include the light mode toggle while it works on the high-contrast mode. - -1. Select the session which refers to adding a high contrast mode. The exact title will vary depending on the name Copilot uses and the current state of work. +| [← Previous lesson: Custom agents][previous-lesson] | +|:--| - ![Accessibility session in mission control]((../images/)ex6-accessibility-session.png) +You're in the **Cloud path**. The mission control workflow happens entirely on github.com and is identical for both paths, so this exercise lives in a shared module. -2. Watch the session for a few of minutes, until it indicates it's completed the setup and begun its work. You'll know this has happened when you start seeing messages similar to the ones below. -3. In the **Steer active session while Copilot is working** dialog, add the following prompt: +➑️ **Start the exercise:** [Monitoring and managing agents module](../shared/coding-agent/managing-agents.md) - ``` - While we are working on a high contrast mode, let's also add a light mode. There should be a switch for this mode as well where users can select their desired display mode. - ``` - - ![Screenshot of the coding agent task in the agents page with the steer active session while copilot is working dialogue highlighted](./(../images/)ex6-steer-coding-agent-task.png) - -4. Press Enter to send the prompt. -5. Notice how Copilot acknowledges the prompt and includes it in its flow. - -## Let Copilot do its work - -Just like before, Copilot will get to work on the updated task! It will incorporate the new request into its flow after it completes the particular step it's working on when you sent the message. - -As before, this will take several minutes, so it's a good time to pause and reflect on everything you've learned and explored thus far. - -## Summary and next steps - -This lesson explored the Copilot agents page, your central hub for monitoring and guiding GitHub Copilot coding agent tasks. With this mission control you can track all active and completed tasks, review the work being performed, and even redirect in-flight tasks to adjust scope or provide additional guidance. - -You explored these concepts: - -- explored Copilot HQ and the agents page to monitor coding agent tasks. -- redirected an in-flight session to request additional functionality. - -With Copilot completing its work on the accessibility features, we can now turn our attention to our next lesson, [iterating on the pull requests Copilot created][next-lesson]. Mission control provides visibility into agent work and enables dynamic collaboration with coding agents as they work on tasks. - -## Resources - -- [Copilot HQ agents page][agents-page] -- [Custom agents][custom-agents] +When you finish, return here and continue with the next lesson below. --- | [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on coding agent's work β†’][next-lesson] | |:--|--:| -[agents-page]: https://github.blog/changelog/2025-10-28-a-agents-page-to-assign-steer-and-track-copilot-coding-agent-tasks -[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents -[next-lesson]: ---- - [next-lesson]: ./5-iterating.md [previous-lesson]: ./3-custom-agents.md diff --git a/workshop-content/cloud/5-iterating.md b/workshop-content/cloud/5-iterating.md index 6032f812..5649ec36 100644 --- a/workshop-content/cloud/5-iterating.md +++ b/workshop-content/cloud/5-iterating.md @@ -1,22 +1,11 @@ -# Exercise 7: Iterating on GitHub Copilot's work +# Exercise 5: Iterating on GitHub Copilot's work | [← Previous lesson: Managing agents][previous-lesson] | |:--| -Throughout this lab you've assigned several issues to GitHub Copilot coding agent. You asked it to add documentation to your code, generate endpoints for the design team to iterate on, and implement accessibility features including high-contrast and light mode toggles. Let's explore the code changes it suggested and, if necessary, provide feedback to Copilot to improve its work. +--8<-- "sections/iterating-recap.md" -## Scenario - -As has been highlighted numerous times, the fundamentals of software design and DevOps do not change with the addition of generative AI. We always want to review the code generated, and work through our normal DevOps process. With that in mind, let's review the suggestions from GitHub Copilot for creating the documentation, new endpoints, and accessibility features before we turn on review for the rest of our team. - -## Security and GitHub Copilot coding agent - -Because Copilot coding agent performs its tasks asynchronously and without supervision, certain security constraints have been put in place to ensure everything remains safe. These include: - -- Copilot only has read access to your repository and write access **only** to the branch it will use for its code. -- Coding agent runs inside of GitHub Actions, where it will create a separate, ephemeral environment in which to work. -- Any GitHub Actions workflows require approval from a human before they can be run. -- [Access to external resources is limited by default][agent-firewall], including MCP servers. +--8<-- "sections/security-coding-agent.md" ## Reviewing the generated documentation @@ -34,14 +23,17 @@ Let's start by exploring the first pull request (PR) generated by GitHub Copilot 4. Once the pull request is ready, select the **Files changed** tab and review the changes. - ![Files changed tab]((../images/)shared-pr-files-changed.png) + ![Files changed tab](../images/shared-pr-files-changed.png) 5. Explore the newly updated code, which includes the newly created docstrings and other documentation. The exact changes will vary. + + As you scan the changes, look for type hints, docstrings, and comment headers. These come from the custom instruction files you reviewed at the start of Exercise 2. + 6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. 7. You should see an indicator that some workflows are waiting for approval. 8. Click on the **Approve and run workflows** button to allow the workflows to run. - ![Approve and run workflows]((../images/)shared-approve-workflows.png) + ![Approve and run workflows](../images/shared-approve-workflows.png) 9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. @@ -57,14 +49,14 @@ Working with Copilot on a pull request is not just a one-way street. You can als 2. Select **View Session** to watch Copilot perform its work. Notice how Copilot starts a new session to make the updates. 3. You can select **Back to pull request** to return to the pull request. - ![Back to pull request]((../images/)ex7-back-to-pr.png) + ![Back to pull request](../images/ex7-back-to-pr.png) 4. Once Copilot has completed the changes, you should see a new commit in the pull request. 5. Select the **Files changed** tab to review the changes. Feel free to continue iterating until you are happy. Once happy, you can convert the PR to ready from a draft, and merge it into the main branch. -![Convert PR to ready]((../images/)ex7-ready-for-review.png) +![Convert PR to ready](../images/ex7-ready-for-review.png) ## Review the new endpoints @@ -78,10 +70,10 @@ Let's return to the PR Copilot generated for resolving our issue about adding en 6. You should see an indicator that some workflows are waiting for approval. 7. Click on the **Approve and run workflows** button to allow the workflows to run. - ![Approve and run workflows]((../images/)shared-approve-workflows.png) + ![Approve and run workflows](../images/shared-approve-workflows.png) 8. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. -9. **Optional:** You could even switch to this branch in your Codespace to perform a manual test of the new endpoints. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created, e.g. **copilot/fix-8**.): +9. **Optional:** You could even switch to this branch in your Codespace to perform a manual test of the new endpoints. Navigate to your Codespace, open the terminal, and run the following commands (replace `` with the name of the branch Copilot created, e.g. **copilot/fix-8**.): ```bash git fetch origin @@ -113,10 +105,10 @@ Finally, let's review the accessibility features that were implemented using the 7. You should see an indicator that some workflows are waiting for approval. 8. Click on the **Approve and run workflows** button to allow the workflows to run. - ![Approve and run workflows]((../images/)shared-approve-workflows.png) + ![Approve and run workflows](../images/shared-approve-workflows.png) 9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. -10. **Optional:** You could switch to this branch in your Codespace to manually test the accessibility features. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created): +10. **Optional:** You could switch to this branch in your Codespace to manually test the accessibility features. Navigate to your Codespace, open the terminal, and run the following commands (replace `` with the name of the branch Copilot created): ```bash git fetch origin @@ -142,13 +134,27 @@ You have made great progress through the lab, and you're approaching the end. Ho Congratulations! You completed the lab! You worked through several features available to you with GitHub Copilot, from the IDE to the repository. In particular you: -- **Learned how to use GitHub Copilot and the Model Context Protocol (MCP) to streamline software development**. You set up the GitHub MCP server to enable Copilot to interact with your repository, created a detailed backlog using Copilot Agent Mode. -- **Explored how custom instructions and prompt files can guide Copilot to follow your project's coding standards.** You created a custom instructions file to provide context for Copilot, ensuring it generates code that adheres to your project's guidelines and used prompt files to provide guidance for repetitive tasks and established practices. -- **Used Copilot Agent Mode to implement new features, coordinate changes across backend and frontend code, and automate repetitive tasks.** You used GitHub Copilot to implement a new category and publisher filter for the game listing page, making changes across the client, backend, and the resulting tests. -- **Experienced Copilot as a peer programmer, being assigned issues and working collaboratively on pull requests.** You assigned Copilot to issues in your backlog, allowing it to create a pull request, build a plan, implement changes, and iterate further as you provided feedback. +- **Inspected the custom instruction files this repo ships with** so you could spot their effect in coding agent's output, connecting what you learned in Exercise 1 with the read-only inspection in Exercise 2. +- **Used Copilot coding agent to address tech debt and add features asynchronously.** You assigned the documentation issue and the CRUD endpoints issue, watching coding agent set up its environment, plan, and execute in the background. +- **Created and used a custom agent** for accessibility, applying domain-specific guidance to add high-contrast mode and light mode to the project. +- **Used the Copilot agents page as mission control to monitor and steer in-flight sessions**, redirecting the accessibility session mid-flight to also include light mode. +- **Reviewed and iterated on coding agent's pull requests**, tagging `@copilot` to request changes and approving workflows. This is just the beginning, and we can't wait to see how you use Copilot to help you with your own projects. We hope you enjoyed the lab, and we look forward to seeing you in the next one! Happy coding! +## Review and next steps + +You've completed the Cloud / Coding Agent path. If you'd like to keep exploring, the other paths complement what you practiced here: + +- πŸ–₯️ **[VS Code path](../vscode/README.md)** β€” explore Copilot Chat agent mode and MCP integration directly from your IDE. +- πŸ’» **[CLI path](../cli/README.md)** β€” work the same flows from your terminal with Copilot CLI: plan mode, agent skills, custom agents, and slash commands like `/delegate` to bridge back to the coding agent you used here. + +In your own repository, try these follow-up ideas: + +- Assign a refactoring or test-coverage issue to coding agent. +- Create a custom agent for another domain. +- Set up `copilot-setup-steps.yml` for a different stack. + ## Resources - [GitHub Copilot][github-copilot] diff --git a/workshop-content/cloud/README.md b/workshop-content/cloud/README.md index 1a0be1c5..7f4e7e23 100644 --- a/workshop-content/cloud/README.md +++ b/workshop-content/cloud/README.md @@ -11,7 +11,7 @@ Welcome to the **Cloud & Coding Agent** learning path! This path focuses on GitH | [2. Coding Agent][ex2] | Async Agent | Assign issues to Copilot coding agent | | [3. Custom Agents][ex3] | Specialized Agents | Create and use custom agents | | [4. Managing Agents][ex4] | Monitoring | Monitor and steer agent sessions | -| [5. Iterating][ex5] | Review | Iterate on Copilot's generated work | +| [5. Iterating][ex5] | Review | Review PRs, iterate on Copilot's work, and choose next steps | ## Prerequisites @@ -21,7 +21,10 @@ Before attending this workshop, please ensure you have: - [ ] Copilot coding agent enabled for your account or organization > [!NOTE] -> Copilot coding agent requires **Copilot Pro+, Business, or Enterprise**. Check with your administrator if you are unsure whether this feature is enabled. +> Requires **Copilot Pro+, Business, or Enterprise** with coding agent enabled. + +> [!NOTE] +> MCP isn't covered in this path. To explore using MCP servers with Copilot, see the [CLI path](../cli/README.md) or the [VS Code path](../vscode/README.md). ## Get Started diff --git a/workshop-content/images/cli-5-agent-skill.png b/workshop-content/images/cli-5-agent-skill.png new file mode 100644 index 0000000000000000000000000000000000000000..60f77eb661b6dcdbd9f159e91fd21226af7190d2 GIT binary patch literal 15007 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 z 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 literal 0 HcmV?d00001 diff --git a/workshop-content/shared/1-custom-instructions.md b/workshop-content/shared/1-custom-instructions.md index ce9eb872..80ac43da 100644 --- a/workshop-content/shared/1-custom-instructions.md +++ b/workshop-content/shared/1-custom-instructions.md @@ -7,12 +7,14 @@ Context is key across many aspects of life, and when working with generative AI. In this exercise, you will learn how to: -- provide Copilot with project-specific context, coding guidelines and documentation standards using [repository custom instructions][repository-custom-instructions] **.github/copilot-instructions.md**. -- provide path instruction files to guide Copilot for repetitive or templated tasks on specific types of files. -- implement both repository-wide instructions and task-specific instructions. +- describe how project-specific context, coding guidelines, and documentation standards are provided to Copilot using [repository custom instructions][repository-custom-instructions] (**.github/copilot-instructions.md**). +- identify path-scoped instruction files that guide Copilot for repetitive or templated tasks on specific types of files. +- locate and inspect the existing instruction files in this project so you can recognize them when you see them at work later. + +You'll see the impact of these instruction files in action in your chosen path's exercises β€” VS Code learners will run a live demonstration in Exercise 2; CLI learners in Exercise 4; Cloud learners will observe their effect in the documentation pull request generated by coding agent. > [!IMPORTANT] -> Note that the code generated may diverge from some of the standards you set. AI tools like Copilot are non-deterministic, and may not always provide the same result. The other files in the codebase do not contain docstrings or comment headers, which could lead Copilot in another direction. Consistency is key, so making sure that your code follows the established patterns is important. You can always follow-up in chat and ask Copilot to follow your coding standards, which will help guide it in the right direction. +> Note that the code generated may diverge from some of the standards you set. AI tools like Copilot are non-deterministic, and may not always provide the same result. The other files in the codebase do not contain docstrings or comment headers, which could lead Copilot in another direction. Consistency is key, so making sure that your code follows the established patterns is important. You can always follow up and ask Copilot to follow your coding standards, which will help guide it in the right direction. ## Scenario @@ -37,7 +39,7 @@ There are two types of instructions files: > [!NOTE] > When working in your IDE, instructions files are only used for code generation in Copilot Chat, and not used for code completions or next edit suggestions. > -> Copilot coding agent and Copilot CLI will utilize both repository level and `*.instructions` files with `applyTo` header matter when generating code. +> Copilot coding agent and Copilot CLI will utilize both repository-level and `*.instructions` files with `applyTo` header matter when generating code. ## Best practices for managing instructions files @@ -51,120 +53,38 @@ There isn't one specific way to create instructions files, just as there isn't o > [!TIP] > Every project using GitHub Copilot should have a robust collection of instructions files to provide context and best guide code generation. As you explore the instructions files in the project, you may notice there are ones for numerous types of files and tasks, including [UI updates][ui-instructions] and [Astro][astro-instructions]. The investment made in instructions files will greatly enhance the quality of code suggestion from Copilot, ensuring it better matches the style and requirements your organization has. -> -> You can even have Copilot aid in generating instructions files by selecting the gear icon for **Configure Chat** in Copilot chat and selecting **Generate Agent Instructions**. Or if using Copilot CLI, use the `/agent` command to engage an agent specialized for generating instruction files. > -> And, if you're looking for templates or a starting point for instructions files, you can explore [awesome-copilot][awesome-copilot], a repository full of instructions files, custom agents, and other resources to help you out! - -## Ensure your codespace is ready - -In a [prior exercise][prereqs-lesson] you launched the codespace you'll use for the remainder of the coding exercises in this lab. Let's put the final touches on it before you begin using it. - -The setup process for the codespace installed and set up many [VS Code extensions][vscode-extensions]. As with any software, updates may be needed. When your codespace is created you'll need to ensure everything is up-to-date. - -1. Return to the tab where you started your codespace. If you closed the tab, return to your repository, select **Code** > **Codespaces** and then the name of the codespace. -2. Select **Extensions** on the workbench on the left side of your codespace. - - ![Screenshot of the extensions window with multiple extensions showing either Update or Reload Window buttons](../images/ex1-extensions-updates.png) - -3. Select **Update** on any extensions with an **Update** button. Repeat as necessary. -4. Select **Reload Window** on any extensions with a **Reload Window** button to reload the codespace. -5. When prompted by a dialog, select **Reload** to reload the window. This will ensure the latest version is being used. - -## Explore the custom instructions files - -Let's start by exploring the instructions files created for this project. You'll notice there's one core **copilot-instructions.md** file, and a collection of **.instructions** files for various tasks. - -1. Return to your codespace. -2. Open **.github/copilot-instructions.md**. -3. Explore the file, noting the brief description of the project and sections for **Code standards**, **Scripts** and **GitHub Actions Workflows**. These are applicable to any interactions you'd have with Copilot, are robust, and provide clear guidance on what you're doing and how you want to accomplish it. -4. Open **.github/instructions**, and explore the files contained inside it. Note there are instructions for Astro files, Svelte files, the various tests, and others. -5. Open **.github/instructions/python-tests.instructions.md**. Make note of the `applyTo` section. This sets the path, relative to the root of the project, which determines which files the instructions apply to. In this case, any Python files in the **server/tests** folder with a name that starts with **test_** will match the slug. -6. Note the instructions specific to creating Python tests for this project. -7. Finally, open **.github/instructions/flask-endpoint.instructions.md**, and scroll to the bottom of the file. Note the links to other instructions files and existing files in the project. This allows you to both break down larger instruction sets into smaller, reusable files, and to point to examples Copilot should consider when generating code. Note these paths are relative to the instructions file rather than the root of the project. - -## Examine the impact of custom instructions - -To see the impact of custom instructions, you'll start by sending a prompt with the current version of the files, and see how Copilot pulls those files into context. Then you'll make some updates, send the same prompt again, and note the difference. - -1. Return to your codespace. -2. Close any files open in the codespace. -3. Open `server/routes/publishers.py`, an empty file. -4. If **Copilot chat** is not already open, open it by selecting the Copilot icon towards the top of your codespace. -5. Create a new chat session by typing `/clear` into the chat window and selecting Enter (or return on a Mac). -6. Select **Ask** from the modes dropdown. -7. Send the following prompt to create a new endpoint to return all publishers: - - ```plaintext - Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. - ``` - -8. Copilot explores the project to learn how best to implement the code, and generates a list of suggestions, which may include code for `publishers.py`, `app.py`, and tests to ensure the new code runs correctly. -9. Explore the code, noticing the generated code includes [type hints][python-type-hints] because, as you'll see, the custom instructions includes the directive to include them. -10. Notice the generated code **is missing** either a docstring or a comment header - or both! - -> [!IMPORTANT] -> As highlighted previously, GitHub Copilot and LLM tools are probabilistic, not deterministic. As a result, the exact code generated may vary, and there's even a chance it'll abide by your rules without you spelling it out! But to aid consistency in code you should always document anything you want to ensure Copilot should understand about how you want your code generated. - -## Add new repository standards to copilot-instructions.md - -As highlighted previously, `copilot-instructions.md` is designed to provide project-level information to Copilot. Let's ensure repository coding standards are documented to improve code suggestions from Copilot. - -1. Return to your codespace. -2. Open `.github/copilot-instructions.md`. -3. Locate the **Code formatting requirements** section. Note how it contains a note to use type hints. That's why you saw those in the code generated previously. -4. Add the following lines of markdown right below the note about type hints to instruct Copilot to add comment headers to files and docstrings: - - ```markdown - - Every function should have docstrings or the language equivalent. - - Before imports or any code, add a comment block to the file that explains its purpose. - ``` - -5. Close **copilot-instructions.md**. -6. Select **New Chat** in Copilot chat to clear the buffer and start a new conversation. -7. Return to **server/routes/publishers.py** to ensure focus is set correctly. -8. Send the same prompt as before to create the endpoint. - - ```plaintext - Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. - ``` - -9. Notice how the newly generated code includes a comment header at the top of the file which resembles the following: - - ```python - """ - Publisher API routes for the Tailspin Toys Crowd Funding platform. - This module provides endpoints to retrieve publisher information. - """ - ``` +> You can also have Copilot help generate instructions files for you. In VS Code, use **Configure Chat β†’ Generate Agent Instructions** in the Copilot Chat panel. In Copilot CLI, use the `/agent` command to engage an agent specialized in generating instruction files. +> +> If you're looking for templates or a starting point for instructions files, you can explore [awesome-copilot][awesome-copilot], a repository full of instructions files, custom agents, and other resources to help you out! -10. Notice how the newly generated code includes a docstring inside the function which resembles the following: +## Explore the custom instructions files in this project - ```python - """ - Returns a list of all publishers with their id and name. - - Returns: - Response: JSON response containing an array of publisher objects - """ - ``` +Let's get familiar with the instructions files this workshop's repository ships with. You'll notice there's one core **copilot-instructions.md** file, and a collection of **.instructions** files for various tasks. You can open these files using whatever tool fits your path β€” your codespace, a local editor, or the GitHub web UI on your repository. -11. Notice the generated code now includes a docstring as well as a comment block at the top! -12. Also note how the existing code isn't updated, but of course you could ask Copilot to perform that operation if you so desired! -13. **Don't implement the suggested changes**, as you'll be doing that in a later exercise. +1. Open **.github/copilot-instructions.md**. +2. Explore the file, noting the brief description of the project and sections for **Code standards**, **Scripts**, and **GitHub Actions Workflows**. These are applicable to any interactions you'd have with Copilot, are robust, and provide clear guidance on what you're doing and how you want to accomplish it. +3. Open the **.github/instructions** folder, and explore the files contained inside it. Note there are instructions for Astro files, Svelte files, the various tests, and others. +4. Open **.github/instructions/python-tests.instructions.md**. Make note of the `applyTo` section. This sets the path, relative to the root of the project, which determines which files the instructions apply to. In this case, any Python files in the **server/tests** folder with a name that starts with **test_** will match the slug. +5. Note the instructions specific to creating Python tests for this project. +6. Finally, open **.github/instructions/flask-endpoint.instructions.md**, and scroll to the bottom of the file. Note the links to other instructions files and existing files in the project. This allows you to both break down larger instruction sets into smaller, reusable files, and to point to examples Copilot should consider when generating code. Note these paths are relative to the instructions file rather than the root of the project. > [!NOTE] -> If you accepted the changes, you can always select the **Undo** button towards the top right of the Copilot chat window. +> The **Code formatting requirements** section in `copilot-instructions.md` already includes the rule about Python type hints. In your path's exercises, you'll add additional rules (about docstrings and comment headers) so you can see firsthand how editing this file changes Copilot's output. ## Summary and next steps Congratulations! You explored how to ensure Copilot has the right context to generate code following the practices your organization has set forth. This can be done at a repository level with the **.github/copilot-instructions.md** file, or on a task basis with instruction files. You explored how to: -- provide Copilot with project-specific context, coding guidelines and documentation standards using custom instructions (.github/copilot-instructions.md). -- use instruction files to guide Copilot for repetitive or templated tasks. -- implement both repository-wide instructions and task-specific instructions. +- describe how Copilot uses repository custom instructions and path-scoped instruction files. +- locate and read the existing instruction files in the project. +- recognize where additional rules can be added (and where you'll add some yourself in your path's exercises). + +Next, continue to the next exercise in your chosen path: -Next, continue to the exercise for your chosen path! +- πŸ–₯️ **VS Code path** β†’ [Exercise 2: MCP with VS Code](../vscode/2-mcp.md) (you'll see custom instructions in action there). +- πŸ’» **CLI path** β†’ [Exercise 2: Install Copilot CLI](../cli/2-install-copilot-cli.md) (you'll see custom instructions in action in Exercise 4). +- ☁️ **Cloud path** β†’ [Exercise 2: Coding Agent](../cloud/2-coding-agent.md) (you'll observe custom instructions in the documentation pull request). ## Resources @@ -183,11 +103,9 @@ Next, continue to the exercise for your chosen path! [next-lesson]: ../README.md [instruction-files]: https://code.visualstudio.com/docs/copilot/copilot-customization [repository-custom-instructions]: https://docs.github.com/copilot/how-tos/configure-custom-instructions/add-repository-instructions -[python-type-hints]: https://docs.python.org/3/library/typing.html [instructions-best-practices]: https://docs.github.com/enterprise-cloud@latest/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks#adding-custom-instructions-to-your-repository [personal-instructions]: https://docs.github.com/copilot/customizing-copilot/adding-personal-custom-instructions-for-github-copilot [copilot-instructions-five-tips]: https://github.blog/ai-and-ml/github-copilot/5-tips-for-writing-better-custom-instructions-for-copilot/ [awesome-copilot]: https://github.com/github/awesome-copilot -[ui-instructions]: ../../.github/instructions/ui.instructions.md -[astro-instructions]: ../../.github/instructions/astro.instructions.md -[vscode-extensions]: https://code.visualstudio.com/docs/configure/extensions/extension-marketplace +[ui-instructions]: https://github.com/github-samples/agents-in-sdlc/blob/main/.github/instructions/ui.instructions.md +[astro-instructions]: https://github.com/github-samples/agents-in-sdlc/blob/main/.github/instructions/astro.instructions.md diff --git a/workshop-content/5-custom-agents.md b/workshop-content/shared/coding-agent/custom-agents.md similarity index 75% rename from workshop-content/5-custom-agents.md rename to workshop-content/shared/coding-agent/custom-agents.md index edbc454d..8bc16e46 100644 --- a/workshop-content/5-custom-agents.md +++ b/workshop-content/shared/coding-agent/custom-agents.md @@ -1,7 +1,4 @@ -# Exercise 5 - Custom agents - -| [← Previous lesson: GitHub Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | -|:--|--:| +# Custom agents [Custom agents][custom-agents] in GitHub Copilot allow you to create specialized AI assistants tailored to specific tasks or domains within your development workflow. By defining agents through markdown files in the `.github/agents` folder of your repository, you can provide Copilot with focused instructions, best practices, coding patterns, and domain-specific knowledge that guide it to perform particular types of work more effectively. This allows teams to codify their expertise and standards into reusable agents. You might create an accessibility agent that ensures [WCAG][wcag] compliance, a security agent that follows secure coding practices, or a testing agent that maintains consistent test patternsβ€”enabling developers to leverage these specialized capabilities on-demand for faster, more consistent implementations. @@ -10,11 +7,7 @@ You'll explore the following with custom agents: - how to create a custom agent. - assigning a task to a custom agent. -## Scenario - -Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. - -Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. +--8<-- "scenarios/accessibility.md" ## Custom agents @@ -59,7 +52,7 @@ Mission control is the central location for working with all agents for your env 8. On the right side, select **Assign to Copilot** to open the assignment dialog. 9. Select **Accessibility agent** from the list of custom agents. - ![Screenshot of coding agent assignment, with custom agent and accessibility highlighted](./images/ex5-select-custom-agent.png) + ![Screenshot of coding agent assignment, with custom agent and accessibility highlighted](../../images/ex5-select-custom-agent.png) 10. Select **Assign**. 11. Copilot gets to work on the task in the background! @@ -73,7 +66,7 @@ You explored these concepts: - how to create a custom agent. - assigning a task to a custom agent. -With Copilot working on implementing the high contrast mode, we can now turn our attention to our next lesson, [using Copilot HQ to monitor and guide agent sessions][next-lesson]. Custom agents help ensure that Copilot follows your organization's best practices and domain-specific requirements, enabling faster and more consistent implementations across your team. +With Copilot working on implementing the high contrast mode, we can now turn our attention to **monitoring and guiding agent sessions**. Custom agents help ensure that Copilot follows your organization's best practices and domain-specific requirements, enabling faster and more consistent implementations across your team. ## Resources @@ -81,14 +74,16 @@ With Copilot working on implementing the high contrast mode, we can now turn our - [Preparing to use custom agents in your organization][org-custom-agents] - [Preparing to use custom agents in your enterprise][enterprise-custom-agents] ---- - -| [← Previous lesson: GitHub Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | -|:--|--:| - [custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents [wcag]: https://www.w3.org/WAI/standards-guidelines/wcag/ [org-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-organization/prepare-for-custom-agents [enterprise-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/prepare-for-custom-agents -[next-lesson]: ./6-managing-agents.md -[previous-lesson]: ./4-copilot-coding-agent.md + +--- + +## Return to your path + +Continue with the next exercise in the path you started: + +- πŸ–₯️ **VS Code path** β†’ [Exercise 6: Monitoring and managing agents](../../vscode/6-managing-agents.md) +- ☁️ **Cloud path** β†’ [Exercise 4: Monitoring and managing agents](../../cloud/4-managing-agents.md) diff --git a/workshop-content/6-managing-agents.md b/workshop-content/shared/coding-agent/managing-agents.md similarity index 85% rename from workshop-content/6-managing-agents.md rename to workshop-content/shared/coding-agent/managing-agents.md index 126351ab..e67046df 100644 --- a/workshop-content/6-managing-agents.md +++ b/workshop-content/shared/coding-agent/managing-agents.md @@ -1,7 +1,4 @@ -# Exercise 6 - Monitoring and managing agents - -| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on Copilot's work β†’][next-lesson] | -|:--|--:| +# Monitoring and managing agents In the last couple of exercises you asked Copilot coding agent to take on three separate tasks focused on improving the user experience and adding functionality. While coding agent is built to operate asynchronously and autonomously, the ability to monitor these tasks is still important. @@ -42,7 +39,7 @@ Now that you've seen the tasks which are active, let's request Copilot include t 1. Select the session which refers to adding a high contrast mode. The exact title will vary depending on the name Copilot uses and the current state of work. - ![Accessibility session in mission control](images/ex6-accessibility-session.png) + ![Accessibility session in mission control](../../images/ex6-accessibility-session.png) 2. Watch the session for a few of minutes, until it indicates it's completed the setup and begun its work. You'll know this has happened when you start seeing messages similar to the ones below. 3. In the **Steer active session while Copilot is working** dialog, add the following prompt: @@ -51,7 +48,7 @@ Now that you've seen the tasks which are active, let's request Copilot include t While we are working on a high contrast mode, let's also add a light mode. There should be a switch for this mode as well where users can select their desired display mode. ``` - ![Screenshot of the coding agent task in the agents page with the steer active session while copilot is working dialogue highlighted](./images/ex6-steer-coding-agent-task.png) + ![Screenshot of the coding agent task in the agents page with the steer active session while copilot is working dialogue highlighted](../../images/ex6-steer-coding-agent-task.png) 4. Press Enter to send the prompt. 5. Notice how Copilot acknowledges the prompt and includes it in its flow. @@ -71,22 +68,21 @@ You explored these concepts: - explored Copilot HQ and the agents page to monitor coding agent tasks. - redirected an in-flight session to request additional functionality. -With Copilot completing its work on the accessibility features, we can now turn our attention to our next lesson, [iterating on the pull requests Copilot created][next-lesson]. Mission control provides visibility into agent work and enables dynamic collaboration with coding agents as they work on tasks. +With Copilot completing its work on the accessibility features, we can now turn our attention to **iterating on the pull requests Copilot created**. Mission control provides visibility into agent work and enables dynamic collaboration with coding agents as they work on tasks. ## Resources - [Copilot HQ agents page][agents-page] - [Custom agents][custom-agents] ---- - -| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on coding agent's work β†’][next-lesson] | -|:--|--:| - [agents-page]: https://github.blog/changelog/2025-10-28-a-agents-page-to-assign-steer-and-track-copilot-coding-agent-tasks [custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents -[next-lesson]: + --- -[next-lesson]: ./7-iterating-copilot-work.md -[previous-lesson]: ./5-custom-agents.md +## Return to your path + +Continue with the next exercise in the path you started: + +- πŸ–₯️ **VS Code path** β†’ [Exercise 7: Iterating on Copilot's work](../../vscode/7-iterating.md) +- ☁️ **Cloud path** β†’ [Exercise 5: Iterating on Copilot's work](../../cloud/5-iterating.md) diff --git a/workshop-content/vscode/2-mcp.md b/workshop-content/vscode/2-mcp.md index 2704adc4..fe7a7aeb 100644 --- a/workshop-content/vscode/2-mcp.md +++ b/workshop-content/vscode/2-mcp.md @@ -1,10 +1,102 @@ -# Exercise 1 - Setting up the backlog with Copilot agent mode and GitHub's MCP Server +# Exercise 2 - Setting up the backlog with Copilot agent mode and GitHub's MCP Server | [← Custom instructions][previous-lesson] | [Next lesson: Agent mode β†’][next-lesson] | |:--|--:| There's more to writing code than just writing code. Issues need to be filed, external services need to be called, and information needs to be gathered. Typically this involves interacting with external tools, which can break a developer's flow. Through the power of Model Context Protocol (MCP), you can access all of this functionality right from Copilot! +## Custom instructions hands-on + +Before diving into MCP, let's put the concepts from Exercise 1 into practice in VS Code and see how custom instructions shape Copilot's suggestions. + +### Ensure your codespace is ready + +In a [prior exercise][prereqs-lesson] you launched the codespace you'll use for the remainder of the coding exercises in this lab. Let's put the final touches on it before you begin using it. + +The setup process for the codespace installed and set up many [VS Code extensions][vscode-extensions]. As with any software, updates may be needed. When your codespace is created you'll need to ensure everything is up-to-date. + +1. Return to the tab where you started your codespace. If you closed the tab, return to your repository, select **Code** > **Codespaces** and then the name of the codespace. +2. Select **Extensions** on the workbench on the left side of your codespace. + + ![Screenshot of the extensions window with multiple extensions showing either Update or Reload Window buttons](../images/ex1-extensions-updates.png) + +3. Select **Update** on any extensions with an **Update** button. Repeat as necessary. +4. Select **Reload Window** on any extensions with a **Reload Window** button to reload the codespace. +5. When prompted by a dialog, select **Reload** to reload the window. This will ensure the latest version is being used. + +### Examine the impact of custom instructions + +To see the impact of custom instructions, you'll start by sending a prompt with the current version of the files, and see how Copilot pulls those files into context. Then you'll make some updates, send the same prompt again, and note the difference. + +1. Return to your codespace. +2. Close any files open in the codespace. +3. Open `server/routes/publishers.py`, an empty file. +4. If **Copilot Chat** is not already open, open it by selecting the Copilot icon towards the top of your codespace. +5. Create a new chat session by typing `/clear` into the chat window and selecting Enter (or return on a Mac). +6. Select **Ask** from the modes dropdown. +7. Send the following prompt to create a new endpoint to return all publishers: + + ```plaintext + Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. + ``` + +8. Copilot explores the project to learn how best to implement the code, and generates a list of suggestions, which may include code for `publishers.py`, `app.py`, and tests to ensure the new code runs correctly. +9. Explore the code, noticing the generated code includes [type hints][python-type-hints] because, as you'll see, the custom instructions includes the directive to include them. +10. Notice the generated code **is missing** either a docstring or a comment header - or both! + +> [!IMPORTANT] +> As highlighted previously, GitHub Copilot and LLM tools are probabilistic, not deterministic. As a result, the exact code generated may vary, and there's even a chance it'll abide by your rules without you spelling it out! But to aid consistency in code you should always document anything you want to ensure Copilot should understand about how you want your code generated. + +### Add new repository standards to copilot-instructions.md + +As highlighted previously, `copilot-instructions.md` is designed to provide project-level information to Copilot. Let's ensure repository coding standards are documented to improve code suggestions from Copilot. + +1. Return to your codespace. +2. Open `.github/copilot-instructions.md`. +3. Locate the **Code formatting requirements** section. Note how it contains a note to use type hints. That's why you saw those in the code generated previously. +4. Add the following lines of markdown right below the note about type hints to instruct Copilot to add comment headers to files and docstrings: + + ```markdown + - Every function should have docstrings or the language equivalent. + - Before imports or any code, add a comment block to the file that explains its purpose. + ``` + +5. Close **copilot-instructions.md**. +6. Select **New Chat** in Copilot Chat to clear the buffer and start a new conversation. +7. Return to **server/routes/publishers.py** to ensure focus is set correctly. +8. Send the same prompt as before to create the endpoint. + + ```plaintext + Create a new endpoint to return a list of all publishers. It should return the name and id for all publishers. + ``` + +9. Notice how the newly generated code includes a comment header at the top of the file which resembles the following: + + ```python + """ + Publisher API routes for the Tailspin Toys Crowd Funding platform. + This module provides endpoints to retrieve publisher information. + """ + ``` + +10. Notice how the newly generated code includes a docstring inside the function which resembles the following: + + ```python + """ + Returns a list of all publishers with their id and name. + + Returns: + Response: JSON response containing an array of publisher objects + """ + ``` + +11. Notice the generated code now includes a docstring as well as a comment block at the top! +12. Also note how the existing code isn't updated, but of course you could ask Copilot to perform that operation if you so desired! +13. **Don't implement the suggested changes**, as you'll be doing that in a later exercise. + +> [!NOTE] +> If you accepted the changes, you can always select the **Undo** button towards the top right of the Copilot Chat window. + ## Scenario You are a part-time developer for Tailspin Toys - a crowdfunding platform for board games with a developer theme. You've been assigned various tasks to introduce new functionality to the website. Being a good team member, you want to file issues to track your work. To help future you, you've decided to enlist the help of Copilot. You will set up your backlog of work for the rest of the lab, using GitHub Copilot Chat agent mode and the GitHub Model Context Protocol (MCP) server to create the issues for you. @@ -25,7 +117,7 @@ Agent mode in GitHub Copilot Chat transforms Copilot into an AI agent that can p These tools and resources are accessed through an MCP server, which acts as a bridge between the AI agent and the external tools and services. The MCP server is responsible for managing the communication between the AI agent and the external tools (such as existing APIs or local tools like NPM packages). Each MCP server represents a different set of tools and resources that the AI agent can access. -![Diagram showing the inner works of agent mode and how it interacts with context, LLM and tools - including tools contributed by MCP servers and VS Code extensions]((../images/)ex1-mcp-diagram.png) +![Diagram showing the inner works of agent mode and how it interacts with context, LLM and tools - including tools contributed by MCP servers and VS Code extensions](../images/ex1-mcp-diagram.png) A couple of popular existing MCP servers are: @@ -46,7 +138,7 @@ The setup process for the codespace installed and setup many [VS Code extensions 1. Return to the tab where you started your codespace. If you closed the tab, return to your repository, select **Code** > **Codespaces** and then the name of the codespace. 2. Select **Extensions** on the workbench on the left side of your codespace. - ![Screenshot of the extensions window with multiple extensions showing either Update or Reload Window buttons]((../images/)ex1-extensions-updates.png) + ![Screenshot of the extensions window with multiple extensions showing either Update or Reload Window buttons](../images/ex1-extensions-updates.png) 3. Select **Update** on any extensions with an **Update** button. Repeat as necessary. 4. Select **Reload Window** on any extensions with a **Reload Window** button to reload the codespace. @@ -66,16 +158,16 @@ Once you have the extension installed, you may need to authenticate with your Gi 3. Type a message like "Hello world" in the Copilot Chat window and press enter. This should activate Copilot Chat. 4. Alternatively, if you are not authenticated you will be prompted to sign in to your GitHub account. Follow the instructions to authenticate. - ![Example of Copilot Chat authentication prompt]((../images/)ex1-copilot-authentication.png) + ![Example of Copilot Chat authentication prompt](../images/ex1-copilot-authentication.png) 5. After authentication, you should see the Copilot Chat window appear. 6. Switch to agent mode by selecting the dropdown in the Copilot Chat window and selecting **Agent**. - ![Example of switching to agent mode]((../images/)shared-agent-mode-dropdown.png) + ![Example of switching to agent mode](../images/shared-agent-mode-dropdown.png) 7. Set the model to **Claude Sonnet 4.5**. - ![Example of selecting the Claude Sonnet 4.5 model]((../images/)ex1-select-model.png) + ![Example of selecting the Claude Sonnet 4.5 model](../images/ex1-select-model.png) > [!IMPORTANT] > The authors of this workshop are not indicating a preference towards one model or another. When building this workshop, we used Claude Sonnet 4.5, and as such are including that in the instructions. The hope is the code suggestions you receive will be relatively consistent to ensure a good experience. However, because LLMs are probabilistic, you may notice the suggestions received differ from what is indicated in the workshop. This is perfectly normal and expected. @@ -113,19 +205,19 @@ To utilize an MCP server it needs to be "started". This will allow GitHub Copilo 1. Inside VS Code, open **.vscode/mcp.json**. 2. To start the GitHub MCP server, select **Start** above the GitHub server. - ![The start button above the GitHub MCP server entry]((../images/)ex1-start-mcp-server.png) + ![The start button above the GitHub MCP server entry](../images/ex1-start-mcp-server.png) 3. You should see a popup asking you to authenticate to GitHub. - ![A popup showing that the GitHub MCP server wants to authenticate to GitHub]((../images/)ex1-mcp-auth-popup.png) + ![A popup showing that the GitHub MCP server wants to authenticate to GitHub](../images/ex1-mcp-auth-popup.png) 4. Select **Continue** on the user account that you're using for this lab. - ![A popup showing the user account selection for GitHub authentication]((../images/)ex1-mcp-select-account.png) + ![A popup showing the user account selection for GitHub authentication](../images/ex1-mcp-select-account.png) 5. If the page appears, select **Authorize visual-studio-code** to allow the GitHub MCP server to login as your selected user account. Once complete, the page should say "You can now close the window.". - ![A popup showing the authorization for visual-studio-code app]((../images/)ex1-mcp-authorize-vscode.png) + ![A popup showing the authorization for visual-studio-code app](../images/ex1-mcp-authorize-vscode.png) 6. After navigating back to the GitHub Codespace, you should see that the GitHub MCP server has started. You can check this in two places: - The line in **.vscode/mcp.json** which previously said start should now present several options, and show a number of tools available. @@ -156,7 +248,7 @@ Now that you have set up the GitHub MCP server, you can use Copilot Agent mode t 3. Press enter or select the **Send** button to send the prompt to Copilot. 4. GitHub Copilot should process the request and respond with a dialog box asking you to confirm the creation of the issues. - ![Example of Copilot Chat dialog box asking for confirmation to run the create issue command]((../images/)ex1-create-issue-dialog.png) + ![Example of Copilot Chat dialog box asking for confirmation to run the create issue command](../images/ex1-create-issue-dialog.png) > [!IMPORTANT] > Remember, AI can make mistakes, so make sure to review the issues before confirming. @@ -165,11 +257,11 @@ Now that you have set up the GitHub MCP server, you can use Copilot Agent mode t 6. Ensure the details in the **owner** and **repo**, **title** and **body** of the issue look correct. You can make any desired edits by double clicking the body and updating the content with the correct information. 7. After reviewing the generated content, select **Continue** to create the issue. - ![Example of the expanded dialog box showing the GitHub Issue that will be created]((../images/)ex1-create-issue-review.png) + ![Example of the expanded dialog box showing the GitHub Issue that will be created](../images/ex1-create-issue-review.png) 8. Repeat steps 4-6 for the remainder of the issues. Alternatively, if you are comfortable with Copilot automatically creating the issues you can select the down-arrow next to **Continue** and select **Allow in this session** to allow Copilot to create the issues for this session (the current chat). - ![Example of allowing Copilot to automatically create issues]((../images/)ex1-create-issue-allow.png) + ![Example of allowing Copilot to automatically create issues](../images/ex1-create-issue-allow.png) > [!IMPORTANT] > Ensure you are comfortable with Copilot automatically performing tasks on your behalf before you selecting **Allow in this session** or a similar option. @@ -179,7 +271,7 @@ Now that you have set up the GitHub MCP server, you can use Copilot Agent mode t You should notice that the issues are fairly detailed. This is where you benefit from the power of Large Language Models (LLMs) and Model Context Protocol (MCP), as it has been able to create a clear initial issue description. -![Example of issues created in GitHub]((../images/)ex1-github-issues-created.png) +![Example of issues created in GitHub](../images/ex1-github-issues-created.png) ## Summary and next steps diff --git a/workshop-content/vscode/3-agent-mode.md b/workshop-content/vscode/3-agent-mode.md index 524eb8a6..5ced906c 100644 --- a/workshop-content/vscode/3-agent-mode.md +++ b/workshop-content/vscode/3-agent-mode.md @@ -97,7 +97,7 @@ Notice how Copilot has even prioritized the items for you, based on the ones tha ## Review instructions files -Before kicking off the agent to generate the code, it's a good time to review the instructions file you'll use to provide Copilot context for its work. You're going to take advantage of the [user interface (UI)](../.github/instructions/ui.instructions.md) file, which contains context on how to approach adding functionality to the website. +Before kicking off the agent to generate the code, it's a good time to review the instructions file you'll use to provide Copilot context for its work. You're going to take advantage of the [user interface (UI)](https://github.com/github-samples/agents-in-sdlc/blob/main/.github/instructions/ui.instructions.md) file, which contains context on how to approach adding functionality to the website. 1. In your codespace, navigate to **.github/instructions/ui.instructions.md**. 2. Take note of the overall guidance on how to approach adding functionality. This includes: @@ -124,11 +124,11 @@ In addition, the tests need to run (and pass) before you merge everything into y 1. You can continue in the current conversation with Copilot, or start a new one by selecting **New Chat**. 2. Select **Add Context**, **Instructions**, and **ui** as the instructions file. - ![Screenshot showing an example of selecting the UI instructions file]((../images/)ex3-select-instructions-file.png) + ![Screenshot showing an example of selecting the UI instructions file](../images/ex3-select-instructions-file.png) 3. Ensure **Agent** mode is still selected. - ![Screenshot of Copilot Chat mode selection with Agent highlighted](./(../images/)shared-agent-mode-dropdown.png) + ![Screenshot of Copilot Chat mode selection with Agent highlighted](../images/shared-agent-mode-dropdown.png) 4. Ensure **Claude Sonnet 4.5** is still selected for the model. 5. Prompt Copilot to implement the functionality based on the issue you created earlier by using the following prompt: @@ -139,18 +139,18 @@ In addition, the tests need to run (and pass) before you merge everything into y 6. Watch as Copilot begins by exploring the project, locating the files associated with the desired functionality. You should see it finding both the API and UI definitions, as well as the tests. It then begins modifying the files and running the tests. - ![Screenshot showing Copilot exploring the project files]((../images/)ex3-agent-mode-explores.png) + ![Screenshot showing Copilot exploring the project files](../images/ex3-agent-mode-explores.png) > [!NOTE] > You will notice that Copilot will perform several tasks, like exploring the project, modifying files, and running tests. It may take a few minutes depending on the complexity of the task and the codebase. During that process, you may notice **Keep** and **Undo** buttons appear in the code editor. When Copilot is finished, you will have a **Keep** or **Undo** for all of the changes, so you do not need to select them while work is in progress. 7. As prompted by Copilot, select **Continue** to run the tests. - ![Screenshot showing a dialog in the Copilot Chat pane asking the user to confirm they are happy to run tests]((../images/)ex3-agent-mode-run-tests.png) + ![Screenshot showing a dialog in the Copilot Chat pane asking the user to confirm they are happy to run tests](../images/ex3-agent-mode-run-tests.png) 8. You may experience some pauses and even see some tests fail throughout the process. That's okay! Copilot works back and forth between code generation and tests until it completes the task and doesn't detect any errors. - ![Screenshot showing a complete Chat session with Copilot Agent Mode]((../images/)ex3-agent-mode-proposed-changes.png) + ![Screenshot showing a complete Chat session with Copilot Agent Mode](../images/ex3-agent-mode-proposed-changes.png) 9. Explore the generated code for any potential issues. @@ -168,7 +168,7 @@ With your changes created locally you're ready to create a pull request (PR) to 2. Stage the changes by selecting the **+** icon. 3. Generate a commit message using the **Sparkle** button. - ![Screenshot of the Source Control panel showing the changes made]((../images/)ex3-source-control-changes.png) + ![Screenshot of the Source Control panel showing the changes made](../images/ex3-source-control-changes.png) 4. Select **Publish** to push the branch to your repository. diff --git a/workshop-content/vscode/4-coding-agent.md b/workshop-content/vscode/4-coding-agent.md index 2a3beaa0..aa4a9250 100644 --- a/workshop-content/vscode/4-coding-agent.md +++ b/workshop-content/vscode/4-coding-agent.md @@ -12,17 +12,9 @@ You'll explore the following with Copilot coding agent: - the importance of clearly scoped issues. - assigning issues to Copilot. -## Scenarios +--8<-- "scenarios/tech-debt.md" -Tailspin Toys has some tech debt they'd like to address. The contractors initially hired to create the first version of the site left the documentation in an unideal state - and by that you'll notice it's completely lacking. As a first step, they'd like to see docstrings or the equivalent added to all functions in the application. - -Additionally, the design team is ready to get to work on building the UX for managing games. They don't need a full implementation yet, but they at least need some endpoints they can use for testing. Specifically, they need endpoints for the games API which will allow them to create, update and delete games. This is currently a blocker, but there are other issues which are of higher priority at the moment. - -These are both examples of tasks which can quickly find themselves deprioritized, and are great to assign to Copilot coding agent. Copilot coding agent can then work on them asynchronously, allowing the developer to focus on other tasks, then return to review Copilot's work and ensure everything is as expected. - -## Introducing GitHub Copilot coding agent - -[GitHub Copilot coding agent][coding-agent-overview] can perform tasks in the background, much in the same way a human developer would. And, just like with working with a human developer, this can be done in multiple ways, including [assigning a GitHub issue to Copilot][assign-issue]. Once assigned, Copilot will create a draft pull request to track its progress, setup an environment, and begin working on the task. You can dig into Copilot's session while it's still in flight or after its completed. Once its ready for you to review the proposed solution, it'll tag you in the pull request! +--8<-- "sections/coding-agent-intro.md" ## The importance of well-scoped instructions @@ -105,11 +97,11 @@ While everyone understands the importance of documentation, most projects have e 7. Select **Create** to create the issue. 8. On the right side, select **Assign to Copilot** to open the assignment dialog. - ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + ![Assigning Copilot to an issue](../images/shared-assign-copilot.png) 9. Select **Assign**. - ![Copilot assignment details]((../images/)ex4-assign-copilot-details.png) + ![Copilot assignment details](../images/ex4-assign-copilot-details.png) 10. Select the **Pull Requests** tab. 11. Open the newly generated pull request (PR), which will be titled something similar to **[WIP]: Code lacks documentation**. If a new PR doesn't appear on the list, wait for a moment or two and refresh the browser window. @@ -122,7 +114,7 @@ While everyone understands the importance of documentation, most projects have e 14. Scroll down the pull request timeline, and you should see an update that Copilot has started working on the issue. 15. Select the **View session** button. - ![Copilot session view]((../images/)ex4-view-session.png) + ![Copilot session view](../images/ex4-view-session.png) > [!IMPORTANT] > You may need to refresh the window to see the updated indicator. @@ -154,13 +146,13 @@ As has been highlighted, one of the great advantages of GitHub Copilot coding ag 7. Select **Create** to create the issue. 8. On the right side, select **Assign to Copilot** to open the assignment dialog. - ![Assigning Copilot to an issue]((../images/)shared-assign-copilot.png) + ![Assigning Copilot to an issue](../images/shared-assign-copilot.png) 9. Select **Assign**. Shortly after, you should see a set of πŸ‘€ on the first comment in the issue, indicating Copilot is on the job! -![Copilot uses the eyes emoji to indicate it's working on the issue]((../images/)ex4-issue-eyes-emoji.png) +![Copilot uses the eyes emoji to indicate it's working on the issue](../images/ex4-issue-eyes-emoji.png) 9. Select **Assign** to assign the issue to Copilot coding agent. diff --git a/workshop-content/vscode/5-custom-agents.md b/workshop-content/vscode/5-custom-agents.md index 7d35afad..7cdb1766 100644 --- a/workshop-content/vscode/5-custom-agents.md +++ b/workshop-content/vscode/5-custom-agents.md @@ -1,94 +1,18 @@ # Exercise 5 - Custom agents -| [← Previous lesson: GitHub Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | -|:--|--:| - -[Custom agents][custom-agents] in GitHub Copilot allow you to create specialized AI assistants tailored to specific tasks or domains within your development workflow. By defining agents through markdown files in the `.github/agents` folder of your repository, you can provide Copilot with focused instructions, best practices, coding patterns, and domain-specific knowledge that guide it to perform particular types of work more effectively. This allows teams to codify their expertise and standards into reusable agents. You might create an accessibility agent that ensures [WCAG][wcag] compliance, a security agent that follows secure coding practices, or a testing agent that maintains consistent test patternsβ€”enabling developers to leverage these specialized capabilities on-demand for faster, more consistent implementations. - -You'll explore the following with custom agents: - -- how to create a custom agent. -- assigning a task to a custom agent. - -## Scenario - -Tailspin Toys is committed to ensuring their crowdfunding platform is accessible to all users, regardless of their visual abilities or preferences. Recent user feedback has highlighted that some users find the current dark theme difficult to read due to insufficient contrast between text and background colors. To address this accessibility concern, the design team has requested the implementation of a high-contrast mode that users can toggle on and off. - -Because accessibility is critical, you want to ensure this is implemented as quickly as possible. You're going to utilize a custom agent to generate the functionality. - -## Custom agents - -Custom agents are defined by markdown files in the **.github/agents** folder of your project. The markdown files will contain guidance for Copilot on how best to perform at task. - -## Reviewing the accessibility custom agent - -A custom agent has already been created for you for accessibility. Let's review the contents to understand how it will guide Copilot. - -1. Return to your codespace. -2. Open **.github/agents/accessibility.md**. -3. Note the header section with the name and description of the agent. - -> [!IMPORTANT] -> This section is required for custom agents. - -4. From there, scan and review the next sections which highlight: - - Core responsibilities when generating code for an accessible website. - - Best practices for accessibility. - - Code examples for HTML, CSS and JavaScript. - - A list of common pitfalls and mistakes. - -> [!NOTE] -> There is no "best markdown" for a custom agent. As with anything in AI, you will want to test and explore to determine what works best for your environments and scenarios. - -## Create and assign an issue - -Mission control is the central location for working with all agents for your environment. You can assign tasks to Copilot coding agent, monitor tasks, and even redirect and provide additional guidance. Let's start by assigning a task to create the high contrast mode to Copilot. - -1. Navigate to your repository. -2. Select the issues tab. -3. Select **New issue** to open the new issue dialog. -4. Select **Blank issue** to create the new issue. -5. Set the **Title** to `Add high contrast mode to website`. -6. Set the **Description** to: - - ```plaintext - We need a high contrast mode for the site. There should be a toggle for high contrast which the user can set. It should store the setting in local storage on the browser. - ``` - -7. Select **Create** to create the issue. -8. On the right side, select **Assign to Copilot** to open the assignment dialog. -9. Select **Accessibility agent** from the list of custom agents. - - ![Screenshot of coding agent assignment, with custom agent and accessibility highlighted](./(../images/)ex5-select-custom-agent.png) - -10. Select **Assign**. -11. Copilot gets to work on the task in the background! - -## Summary and next steps - -This lesson explored [custom agents][custom-agents] in GitHub Copilot, specialized AI assistants tailored to specific tasks and domains. With custom agents you can codify your team's expertise and standards into reusable agents that guide Copilot to perform particular types of work more effectively. - -You explored these concepts: - -- how to create a custom agent. -- assigning a task to a custom agent. +| [← Previous lesson: GitHub Copilot coding agent][previous-lesson] | +|:--| -With Copilot working on implementing the high contrast mode, we can now turn our attention to our next lesson, [using Copilot HQ to monitor and guide agent sessions][next-lesson]. Custom agents help ensure that Copilot follows your organization's best practices and domain-specific requirements, enabling faster and more consistent implementations across your team. +You're in the **VS Code path**. The custom agents workflow happens entirely on github.com and is identical for both paths, so this exercise lives in a shared module. -## Resources +➑️ **Start the exercise:** [Custom agents module](../shared/coding-agent/custom-agents.md) -- [Custom agents][custom-agents] -- [Preparing to use custom agents in your organization][org-custom-agents] -- [Preparing to use custom agents in your enterprise][enterprise-custom-agents] +When you finish, return here and continue with the next lesson below. --- | [← Previous lesson: GitHub Copilot coding agent][previous-lesson] | [Next lesson: Managing agents β†’][next-lesson] | |:--|--:| -[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents -[wcag]: https://www.w3.org/WAI/standards-guidelines/wcag/ -[org-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-organization/prepare-for-custom-agents -[enterprise-custom-agents]: https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/prepare-for-custom-agents [next-lesson]: ./6-managing-agents.md [previous-lesson]: ./4-coding-agent.md diff --git a/workshop-content/vscode/6-managing-agents.md b/workshop-content/vscode/6-managing-agents.md index a0a619c7..31efb991 100644 --- a/workshop-content/vscode/6-managing-agents.md +++ b/workshop-content/vscode/6-managing-agents.md @@ -1,92 +1,18 @@ # Exercise 6 - Monitoring and managing agents -| [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on Copilot's work β†’][next-lesson] | -|:--|--:| - -In the last couple of exercises you asked Copilot coding agent to take on three separate tasks focused on improving the user experience and adding functionality. While coding agent is built to operate asynchronously and autonomously, the ability to monitor these tasks is still important. - -There are numerous tools available to you to manage tasks assigned to coding agent, including [the agents page][agents-page] on GitHub.com. From this mission control you can see all agent tasks with open pull requests (PRs). You can explore the operations performed, and even steer an in-progress session to help guide it. - -In this lesson you will: - -- explore the agents page to monitor coding agent tasks. -- steer an in-flight session to request additional functionality. - -## Scenario - -After assigning the agent to create a high-contrast mode, the team realized it would be a good time to add a light mode as well. Since work was already being done to update the style of the site and add toggle functionality, it seemed logical to include this functionality. You want to steer the agent's work to ensure it adds a light mode as well as high contrast. - -## Review Copilot coding agent tasks - -Let's see the current status of all tasks assigned to Copilot coding agent. - -1. Navigate to agents page at [https://github.com/copilot/agents](https://github.com/copilot/agents). -2. Note the list of tasks, both on the main pane and on the left pane. You should see the list of the tasks you've assigned to Copilot, including: - - Updating documentation for your codebase. - - Generating APIs for modifying products. - - Adding a high contrast mode for the website. -3. Select one of the running tasks. Review the tasks which have been performed by Copilot. These can include: - - Checking out the code from the repository. - - Creating the environment for Copilot to work. - - Setting up MCP servers. - - Performing various steps to complete the assigned task. - -> [!NOTE] -> The exact steps listed will vary depending on the state of Copilot's work and the approach it took. - -4. Also note the pull request (PR) pane which appears on the right side. This allows you to see the PR and files changed for additional monitoring. - -## Steering coding agent - -Now that you've seen the tasks which are active, let's request Copilot include the light mode toggle while it works on the high-contrast mode. - -1. Select the session which refers to adding a high contrast mode. The exact title will vary depending on the name Copilot uses and the current state of work. +| [← Previous lesson: Custom agents][previous-lesson] | +|:--| - ![Accessibility session in mission control]((../images/)ex6-accessibility-session.png) +You're in the **VS Code path**. The mission control workflow happens entirely on github.com and is identical for both paths, so this exercise lives in a shared module. -2. Watch the session for a few of minutes, until it indicates it's completed the setup and begun its work. You'll know this has happened when you start seeing messages similar to the ones below. -3. In the **Steer active session while Copilot is working** dialog, add the following prompt: +➑️ **Start the exercise:** [Monitoring and managing agents module](../shared/coding-agent/managing-agents.md) - ``` - While we are working on a high contrast mode, let's also add a light mode. There should be a switch for this mode as well where users can select their desired display mode. - ``` - - ![Screenshot of the coding agent task in the agents page with the steer active session while copilot is working dialogue highlighted](./(../images/)ex6-steer-coding-agent-task.png) - -4. Press Enter to send the prompt. -5. Notice how Copilot acknowledges the prompt and includes it in its flow. - -## Let Copilot do its work - -Just like before, Copilot will get to work on the updated task! It will incorporate the new request into its flow after it completes the particular step it's working on when you sent the message. - -As before, this will take several minutes, so it's a good time to pause and reflect on everything you've learned and explored thus far. - -## Summary and next steps - -This lesson explored the Copilot agents page, your central hub for monitoring and guiding GitHub Copilot coding agent tasks. With this mission control you can track all active and completed tasks, review the work being performed, and even redirect in-flight tasks to adjust scope or provide additional guidance. - -You explored these concepts: - -- explored Copilot HQ and the agents page to monitor coding agent tasks. -- redirected an in-flight session to request additional functionality. - -With Copilot completing its work on the accessibility features, we can now turn our attention to our next lesson, [iterating on the pull requests Copilot created][next-lesson]. Mission control provides visibility into agent work and enables dynamic collaboration with coding agents as they work on tasks. - -## Resources - -- [Copilot HQ agents page][agents-page] -- [Custom agents][custom-agents] +When you finish, return here and continue with the next lesson below. --- | [← Previous lesson: Custom agents][previous-lesson] | [Next lesson: Iterating on coding agent's work β†’][next-lesson] | |:--|--:| -[agents-page]: https://github.blog/changelog/2025-10-28-a-agents-page-to-assign-steer-and-track-copilot-coding-agent-tasks -[custom-agents]: https://docs.github.com/copilot/concepts/agents/coding-agent/about-custom-agents -[next-lesson]: ---- - [next-lesson]: ./7-iterating.md [previous-lesson]: ./5-custom-agents.md diff --git a/workshop-content/vscode/7-iterating.md b/workshop-content/vscode/7-iterating.md index ab090dd1..7f576cf6 100644 --- a/workshop-content/vscode/7-iterating.md +++ b/workshop-content/vscode/7-iterating.md @@ -1,22 +1,11 @@ # Exercise 7: Iterating on GitHub Copilot's work -| [← Previous lesson: Mission control][previous-lesson] | +| [← Previous lesson: Monitoring and managing agents][previous-lesson] | |:--| -Throughout this lab you've assigned several issues to GitHub Copilot coding agent. You asked it to add documentation to your code, generate endpoints for the design team to iterate on, and implement accessibility features including high-contrast and light mode toggles. Let's explore the code changes it suggested and, if necessary, provide feedback to Copilot to improve its work. +--8<-- "sections/iterating-recap.md" -## Scenario - -As has been highlighted numerous times, the fundamentals of software design and DevOps do not change with the addition of generative AI. We always want to review the code generated, and work through our normal DevOps process. With that in mind, let's review the suggestions from GitHub Copilot for creating the documentation, new endpoints, and accessibility features before we turn on review for the rest of our team. - -## Security and GitHub Copilot coding agent - -Because Copilot coding agent performs its tasks asynchronously and without supervision, certain security constraints have been put in place to ensure everything remains safe. These include: - -- Copilot only has read access to your repository and write access **only** to the branch it will use for its code. -- Coding agent runs inside of GitHub Actions, where it will create a separate, ephemeral environment in which to work. -- Any GitHub Actions workflows require approval from a human before they can be run. -- [Access to external resources is limited by default][agent-firewall], including MCP servers. +--8<-- "sections/security-coding-agent.md" ## Reviewing the generated documentation @@ -34,14 +23,14 @@ Let's start by exploring the first pull request (PR) generated by GitHub Copilot 4. Once the pull request is ready, select the **Files changed** tab and review the changes. - ![Files changed tab]((../images/)shared-pr-files-changed.png) + ![Files changed tab](../images/shared-pr-files-changed.png) 5. Explore the newly updated code, which includes the newly created docstrings and other documentation. The exact changes will vary. 6. Once you've reviewed the updates and everything looks good, navigate back to the **Conversation** tab and scroll down. 7. You should see an indicator that some workflows are waiting for approval. 8. Click on the **Approve and run workflows** button to allow the workflows to run. - ![Approve and run workflows]((../images/)shared-approve-workflows.png) + ![Approve and run workflows](../images/shared-approve-workflows.png) 9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. @@ -57,14 +46,14 @@ Working with Copilot on a pull request is not just a one-way street. You can als 2. Select **View Session** to watch Copilot perform its work. Notice how Copilot starts a new session to make the updates. 3. You can select **Back to pull request** to return to the pull request. - ![Back to pull request]((../images/)ex7-back-to-pr.png) + ![Back to pull request](../images/ex7-back-to-pr.png) 4. Once Copilot has completed the changes, you should see a new commit in the pull request. 5. Select the **Files changed** tab to review the changes. Feel free to continue iterating until you are happy. Once happy, you can convert the PR to ready from a draft, and merge it into the main branch. -![Convert PR to ready]((../images/)ex7-ready-for-review.png) +![Convert PR to ready](../images/ex7-ready-for-review.png) ## Review the new endpoints @@ -78,10 +67,10 @@ Let's return to the PR Copilot generated for resolving our issue about adding en 6. You should see an indicator that some workflows are waiting for approval. 7. Click on the **Approve and run workflows** button to allow the workflows to run. - ![Approve and run workflows]((../images/)shared-approve-workflows.png) + ![Approve and run workflows](../images/shared-approve-workflows.png) 8. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. -9. **Optional:** You could even switch to this branch in your Codespace to perform a manual test of the new endpoints. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created, e.g. **copilot/fix-8**.): +9. **Optional:** You could even switch to this branch in your Codespace to perform a manual test of the new endpoints. Navigate to your Codespace, open the terminal, and run the following commands (replace `` with the name of the branch Copilot created, e.g. **copilot/fix-8**.): ```bash git fetch origin @@ -113,10 +102,10 @@ Finally, let's review the accessibility features that were implemented using the 7. You should see an indicator that some workflows are waiting for approval. 8. Click on the **Approve and run workflows** button to allow the workflows to run. - ![Approve and run workflows]((../images/)shared-approve-workflows.png) + ![Approve and run workflows](../images/shared-approve-workflows.png) 9. You should see the workflows get queued in the checks section of the pull request. All being well, you should see that the workflows pass for both the backend and frontend. This may take a few minutes to complete. -10. **Optional:** You could switch to this branch in your Codespace to manually test the accessibility features. Navigate to your Codespace, open the terminal, and run the following commands (replace **** with the name of the branch Copilot created): +10. **Optional:** You could switch to this branch in your Codespace to manually test the accessibility features. Navigate to your Codespace, open the terminal, and run the following commands (replace `` with the name of the branch Copilot created): ```bash git fetch origin @@ -149,6 +138,20 @@ Congratulations! You completed the lab! You worked through several features avai This is just the beginning, and we can't wait to see how you use Copilot to help you with your own projects. We hope you enjoyed the lab, and we look forward to seeing you in the next one! Happy coding! +## Review and next steps + +You've completed the VS Code path! If you'd like to expand your perspective on Copilot's agent capabilities, the other paths cover the same scenario through different surfaces: + +- πŸ’» **[CLI path](../cli/README.md)** β€” work the same flows from your terminal with Copilot CLI: plan mode, agent skills, custom agents, slash commands like `/share`, `/context`, and `/delegate` to bridge back to coding agent. +- ☁️ **[Cloud / Coding Agent path](../cloud/README.md)** β€” focus exclusively on assigning issues to coding agent, monitoring sessions through the agents page, and iterating asynchronously on pull requests. + +You can also continue building on what you started here. Some ideas: + +- Create a backer interest form on the game details page. +- Implement pagination on the game listing endpoint. +- Add input validation and error handling to the Flask API. +- Explore [awesome-copilot](https://github.com/github/awesome-copilot) for more instruction files, custom agents, and skills you can adapt to your own projects. + ## Resources - [GitHub Copilot][github-copilot] diff --git a/workshop-content/vscode/README.md b/workshop-content/vscode/README.md index 8d96a6a1..b811c9c8 100644 --- a/workshop-content/vscode/README.md +++ b/workshop-content/vscode/README.md @@ -13,7 +13,7 @@ Welcome to the **VS Code** learning path! This path focuses on GitHub Copilot fe | [4. Coding Agent][ex4] | Async Agent | Assign issues to Copilot coding agent | | [5. Custom Agents][ex5] | Specialized Agents | Create and use custom agents | | [6. Managing Agents][ex6] | Monitoring | Monitor and steer agent sessions | -| [7. Iterating][ex7] | Review | Iterate on Copilot's generated work | +| [7. Iterating][ex7] | Review | Review Copilot's work, iterate on PRs, and choose next steps | ## Prerequisites From 52ed813a320bb4b17d90468f7beed4a4174c3d00 Mon Sep 17 00:00:00 2001 From: Christopher Harrison Date: Fri, 8 May 2026 09:19:08 -0700 Subject: [PATCH 05/29] Migrate workshop docs from MkDocs to Astro + Starlight Replace the MkDocs Material build (and the opaque PyMdown --8<-- snippet syntax) with a new Astro + Starlight project under docs/. Authored content stays in workshop-content/ and is loaded via Astro's glob() loader. - New docs/ project with Astro 5 + Starlight 0.36, separate from client/ - Convert all Markdown to .mdx with YAML frontmatter - Replace --8<-- snippet includes with MDX partials in workshop-content/_partials/ and typed Astro components in docs/src/components/ (CalloutCloudAgentRequired, PrereqCopilotBase) - Convert GitHub-style alerts (> [!NOTE], etc.) to Starlight