diff --git a/.github/instructions/python-standards.instructions.md b/.github/instructions/python-standards.instructions.md new file mode 100644 index 00000000..b249c570 --- /dev/null +++ b/.github/instructions/python-standards.instructions.md @@ -0,0 +1,25 @@ +--- +applyTo: "**/*.py" +--- + +# Python Standards + +These rules apply automatically whenever Copilot works on a Python file in +this project. They never load for other file types, so a conversation about a +Dockerfile or some JSON stays clutter-free. + +## Style + +- Follow PEP 8 style conventions. +- Add type hints to every function signature. +- Prefer f-strings over `%` formatting or `str.format()`. + +## Error Handling + +- Catch specific exceptions; never use a bare `except:`. +- Validate inputs at function boundaries and fail with a clear message. + +## Testing + +- Put pytest tests in `samples/book-app-project/tests/` using `test_*.py` naming. +- Cover the happy path and edge cases (empty input, missing data). diff --git a/.github/scripts/copy-content-engine-files.js b/.github/scripts/copy-content-engine-files.js index 285458af..f55e358b 100644 --- a/.github/scripts/copy-content-engine-files.js +++ b/.github/scripts/copy-content-engine-files.js @@ -4,6 +4,9 @@ * * Usage: * npm run copy:content-engine + * + * Optional: + * CONTENT_ENGINE_DESTINATION_ROOT=/path/to/cse-content-engine/content/learning-pathways/copilot-cli-for-beginners npm run copy:content-engine */ const { @@ -12,13 +15,18 @@ const { mkdirSync, readdirSync, readFileSync, + rmSync, statSync, writeFileSync, } = require('fs'); const { dirname, join, relative, resolve } = require('path'); const sourceRoot = process.cwd(); -const destinationRoot = '/Users/danwahlin/Desktop/projects/cse-content-engine/content/learning-pathways/copilot-cli-for-beginners'; +const defaultDestinationRoot = resolve( + sourceRoot, + '../cse-content-engine/content/learning-pathways/copilot-cli-for-beginners', +); +const destinationRoot = resolve(process.env.CONTENT_ENGINE_DESTINATION_ROOT || defaultDestinationRoot); const destinationParent = dirname(destinationRoot); const contentEngineSchema = { $schema: 'http://json-schema.org/draft-07/schema#', @@ -95,6 +103,11 @@ function ensureSafeDestination() { } } +function resetDestination() { + rmSync(destinationRoot, { recursive: true, force: true }); + log(`Cleared ${destinationRoot}`); +} + function copyFile(sourcePath, destinationPath) { mkdirSync(dirname(destinationPath), { recursive: true }); @@ -333,6 +346,7 @@ function validateMarkdownFrontmatter() { } ensureSafeDestination(); +resetDestination(); copyCourseContent(); validateMarkdownFrontmatter(); validateMarkdownImagePaths(); diff --git a/.github/scripts/filter-co-op-review.js b/.github/scripts/filter-co-op-review.js new file mode 100644 index 00000000..2b36960b --- /dev/null +++ b/.github/scripts/filter-co-op-review.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +// Runs `co-op-review` and post-filters its findings so the workflow only fails +// on genuine errors. +// +// Why this exists: the `translate` command and the `co-op-review` command ship +// with *different* exclusion lists. `translate` skips `.github/**` (it is in +// Co-op Translator's EXCLUDED_DIRS), so those files are never translated. But +// `co-op-review` does NOT exclude `.github/**`, so it reports every one of those +// untranslated source files as a "Missing translated file" error and fails the +// build. Those errors are false positives for paths this repo intentionally +// leaves untranslated. The review CLI exposes no way to extend its exclusions, +// so we drop findings under the excluded paths here and fail only on the rest. + +const { spawnSync } = require("child_process"); + +// Source paths that are intentionally excluded from translation. Keep this in +// sync with the excludes in .github/workflows/co-op-translator.yml and the +// guidance in .github/workflows/translation-polisher.md. +const EXCLUDED_PREFIXES = [".github/", "samples/"]; + +const languages = process.argv + .slice(2) + .flatMap((value) => value.split(/\s+/)) + .map((value) => value.trim()) + .filter(Boolean); + +if (languages.length === 0) { + console.error("Usage: node .github/scripts/filter-co-op-review.js "); + process.exit(1); +} + +const result = spawnSync( + "co-op-review", + ["-l", languages.join(" "), "--format", "github"], + { encoding: "utf8" } +); + +if (result.error && result.error.code === "ENOENT") { + console.log("co-op-review is not available in the installed Co-op Translator package; skipping."); + process.exit(0); +} + +const report = result.stdout || ""; +const stderr = result.stderr || ""; + +// Always surface the full report in the job log (and summary when available). +if (report.trim()) { + console.log(report.trimEnd()); +} +if (process.env.GITHUB_STEP_SUMMARY && report.trim()) { + try { + require("fs").appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${report.trimEnd()}\n`); + } catch (err) { + console.error(`Could not write review report to step summary: ${err.message}`); + } +} + +const isExcluded = (filePath) => + EXCLUDED_PREFIXES.some((prefix) => filePath.startsWith(prefix)); + +const genuineErrors = []; +const ignoredErrors = []; + +for (const line of report.split("\n")) { + if (!line.trimStart().startsWith("|")) { + continue; + } + const cells = line.split("|").map((cell) => cell.trim()); + // cells[0] is the empty string before the leading pipe. + const severity = (cells[1] || "").toLowerCase(); + if (severity !== "error" && severity !== "warning") { + continue; // Skip the metrics table, header, and separator rows. + } + if (severity !== "error") { + continue; // Only errors fail the build; warnings are informational. + } + const filePath = (cells[4] || "").replace(/`/g, "").trim(); + if (isExcluded(filePath)) { + ignoredErrors.push(filePath); + } else { + genuineErrors.push({ filePath, message: cells[5] || "" }); + } +} + +if (ignoredErrors.length > 0) { + console.log( + `\nIgnored ${ignoredErrors.length} expected finding(s) for intentionally untranslated paths ` + + `(${EXCLUDED_PREFIXES.join(", ")}).` + ); +} + +if (genuineErrors.length > 0) { + console.error(`\nco-op-review found ${genuineErrors.length} genuine error(s):`); + for (const { filePath, message } of genuineErrors) { + console.error(` - ${filePath}: ${message}`); + } + if (stderr.trim()) { + console.error(stderr.trimEnd()); + } + process.exit(1); +} + +console.log("\nco-op-review passed (no genuine errors)."); +process.exit(0); diff --git a/.github/uvs.csv b/.github/uvs.csv index 8758710e..c7951850 100644 --- a/.github/uvs.csv +++ b/.github/uvs.csv @@ -95,3 +95,66 @@ "06/05",662 "06/06",291 "06/07",299 +"06/08",631 +"06/09",744 +"06/10",624 +"06/11",685 +"06/12",733 +"06/13",288 +"06/14",294 +"06/15",674 +"06/16",425 +"06/17",612 +"06/18",717 +"06/19",568 +"06/20",227 +"06/21",244 +"06/22",650 +"06/23",591 +"06/24",583 +"06/25",565 +"06/26",517 +"06/27",241 +"06/28",287 +"06/29",605 +"06/30",550 +"07/01",492 +"07/02",419 +"07/03",497 +"07/04",198 +"07/05",226 +"07/06",507 +"07/07",441 +"07/08",490 +"07/09",474 +"07/10",463 +"07/11",377 +"07/12",336 +"07/13",478 +"07/14",497 +"07/15",426 +"07/16",407 +"07/17",474 +"07/18",213 +"07/19",238 +"07/20",481 +"07/21",434 +"07/22",418 +"07/23",461 +"07/24",431 +"07/25",204 +"07/26",211 +"07/27",413 +"07/28",419 +"07/29",503 +"07/30",495 +"07/31",392 +"08/01",160 +"08/02",197 +"08/03",455 +"08/04",486 +"08/05",498 +"08/06",441 +"08/07",431 +"08/08",186 +"08/09",177 diff --git a/.github/views.csv b/.github/views.csv index 432f3d1e..986ee8f6 100644 --- a/.github/views.csv +++ b/.github/views.csv @@ -94,3 +94,66 @@ "06/05",1484 "06/06",665 "06/07",781 +"06/08",1249 +"06/09",1507 +"06/10",1229 +"06/11",1504 +"06/12",1691 +"06/13",693 +"06/14",684 +"06/15",1280 +"06/16",735 +"06/17",1235 +"06/18",1826 +"06/19",1319 +"06/20",589 +"06/21",552 +"06/22",1348 +"06/23",1152 +"06/24",1154 +"06/25",1230 +"06/26",1066 +"06/27",593 +"06/28",590 +"06/29",1184 +"06/30",1116 +"07/01",1035 +"07/02",1032 +"07/03",1199 +"07/04",495 +"07/05",794 +"07/06",2370 +"07/07",2880 +"07/08",4565 +"07/09",4512 +"07/10",904 +"07/11",850 +"07/12",788 +"07/13",981 +"07/14",980 +"07/15",924 +"07/16",822 +"07/17",1162 +"07/18",485 +"07/19",596 +"07/20",1041 +"07/21",881 +"07/22",878 +"07/23",958 +"07/24",1014 +"07/25",547 +"07/26",507 +"07/27",815 +"07/28",877 +"07/29",1088 +"07/30",1052 +"07/31",806 +"08/01",443 +"08/02",583 +"08/03",1003 +"08/04",956 +"08/05",993 +"08/06",1010 +"08/07",869 +"08/08",505 +"08/09",477 diff --git a/.github/workflows/co-op-translator.yml b/.github/workflows/co-op-translator.yml index edff8cc4..a06a9c38 100644 --- a/.github/workflows/co-op-translator.yml +++ b/.github/workflows/co-op-translator.yml @@ -14,7 +14,7 @@ on: - "!translations/**" - "!translated_images/**" - "!.github/**" - - "!samples/skills/**" + - "!samples/**" permissions: contents: write @@ -43,12 +43,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.12" @@ -87,32 +87,28 @@ jobs: run: | migrate-links -l "$TRANSLATION_LANGUAGES" -y node .github/scripts/fix-translated-markdown.js "$TRANSLATION_LANGUAGES" - if command -v co-op-review >/dev/null 2>&1; then - co-op-review -l "$TRANSLATION_LANGUAGES" --format github - else - echo "co-op-review is not available in the installed Co-op Translator package; skipping." - fi + node .github/scripts/filter-co-op-review.js "$TRANSLATION_LANGUAGES" - name: Remove excluded translations run: | for language in $TRANSLATION_LANGUAGES; do rm -rf \ "translations/$language/.github" \ - "translations/$language/samples/skills" \ + "translations/$language/samples" \ "translated_images/$language/.github" \ - "translated_images/$language/samples/skills" + "translated_images/$language/samples" done - name: Upload Co-op Translator logs if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: co-op-translator-logs path: logs/ if-no-files-found: ignore - name: Create pull request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.GH_AW_GITHUB_TOKEN }} commit-message: "Update translations via Co-op Translator" diff --git a/.github/workflows/sync-content-engine.yml b/.github/workflows/sync-content-engine.yml new file mode 100644 index 00000000..208f7d78 --- /dev/null +++ b/.github/workflows/sync-content-engine.yml @@ -0,0 +1,60 @@ +name: Sync content-engine copy + +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: sync-content-engine-copy + cancel-in-progress: true + +jobs: + sync-content: + name: Copy content and open content-engine PR + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout course repository + uses: actions/checkout@v4 + with: + path: copilot-cli-for-beginners + + - name: Checkout content-engine repository + uses: actions/checkout@v4 + with: + repository: github/cse-content-engine + token: ${{ secrets.GH_AW_GITHUB_TOKEN }} + path: cse-content-engine + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Copy course content into content-engine + working-directory: copilot-cli-for-beginners + env: + CONTENT_ENGINE_DESTINATION_ROOT: ${{ github.workspace }}/cse-content-engine/content/learning-pathways/copilot-cli-for-beginners + run: npm run copy:content-engine + + - name: Create content-engine pull request + uses: peter-evans/create-pull-request@v7 + with: + path: cse-content-engine + token: ${{ secrets.GH_AW_GITHUB_TOKEN }} + commit-message: "Update Copilot CLI for Beginners content" + title: "Update Copilot CLI for Beginners content" + body: | + This automated PR syncs the latest `github/copilot-cli-for-beginners` + content into the content-engine learning pathway. + + Source commit: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} + branch: update/copilot-cli-for-beginners-content + base: main + delete-branch: true diff --git a/.github/workflows/translation-polisher.md b/.github/workflows/translation-polisher.md index 64a3063c..7eff0d74 100644 --- a/.github/workflows/translation-polisher.md +++ b/.github/workflows/translation-polisher.md @@ -93,7 +93,7 @@ You may edit only translated Markdown files: Exclude these generated translation paths from review, grading, and edits: - `translations/*/.github/**` -- `translations/*/samples/skills/**` +- `translations/*/samples/**` Leave skill definition Markdown in English. @@ -107,11 +107,15 @@ Do not edit: ## Required process -1. Identify the target pull request number and head branch. +> **Shell tooling constraint:** Only the `gh`, `git`, and `node` commands are available in this environment. Do **not** call `python`, `python3`, `sed`, `awk`, or `grep` pipelines — they are not permitted and will fail with a permission error. Filter and process command output yourself instead of piping through unsupported tools. + +1. Identify the target pull request number and head branch, then list the files the pull request changed. Use only the available shell tools: + - List changed files with `git diff --name-only origin/main...HEAD` (the branch is checked out with full history) or `gh pr diff --name-only`. + - From that output, select only files matching `translations/**/*.md`, and ignore `translations/*/.github/**` and `translations/*/samples/**`. Do this selection yourself from the file list rather than piping through `grep`/`sed`. 2. Compare each changed translated Markdown file with its corresponding English source file. - Example: compare `translations/es/README.md` with `README.md`. - Example: compare `translations/es/03-development-workflows/README.md` with `03-development-workflows/README.md`. -3. Focus on files changed by the pull request, not every translated file in the repository. Ignore excluded translation paths under `translations/*/.github/**` and `translations/*/samples/skills/**`. +3. Focus on files changed by the pull request, not every translated file in the repository. Ignore excluded translation paths under `translations/*/.github/**` and `translations/*/samples/**`. 4. If the pull request body already has a managed `## Translation Quality Review` section, identify files with grades below A- and repair those files first. 5. Preserve Markdown structure exactly unless a link or heading fix is required. 6. Apply the shared quality rules and the language quality profile for each target language present in the pull request. diff --git a/00-quick-start/README.md b/00-quick-start/README.md index 3891c9a9..0a41e6a0 100644 --- a/00-quick-start/README.md +++ b/00-quick-start/README.md @@ -143,17 +143,20 @@ After trusting the folder, you can sign in with your GitHub account. > /login ``` -**What happens next:** +**What happens next (local terminal):** -1. Copilot CLI displays a one-time code (like `ABCD-1234`) -2. Your browser opens to GitHub's device authorization page. Sign in to GitHub if you haven't already. -3. Enter the code when prompted -4. Select "Authorize" to grant GitHub Copilot CLI access -5. Return to your terminal - you're now signed in! +1. Choose to sign into your GitHub.com account or an enterprise account. +2. Select `Sign in with your browser (recommended)` +3. Your browser opens automatically to GitHub's authorization page. Sign in to GitHub if you haven't already. +4. Select "Authorize" to grant GitHub Copilot CLI access. +5. Return to your terminal — you're now signed in! -Device Authorization Flow - showing the 5-step process from terminal login to signed-in confirmation - -*The device authorization flow: your terminal generates a code, you verify it in the browser, and Copilot CLI is authenticated.* +> 💡 **Remote or headless terminals**: If you're on a remote server or a terminal without a browser (such as SSH), Copilot CLI falls back to the **device code flow** instead. You'll see a one-time code like `ABCD-1234`. Visit [github.com/login/device](https://github.com/login/device) in a browser on another machine and enter the code to complete sign-in. To force a specific flow, use `copilot login --web-flow` to use the browser popup or `copilot login --device-code` to use the code-based flow. You can also pick interactively with `/login`. +> +> Device Authorization Flow - showing the 5-step process from terminal login to signed-in confirmation +> + +*The browser-based flow: your browser opens automatically and you authorize in one click. On remote/headless terminals, a device code is shown instead.* **Tip**: The sign-in persists across sessions. You only need to do this once unless your token expires or you explicitly sign out. @@ -267,7 +270,7 @@ copilot ### Browser doesn't open automatically -Manually visit [github.com/login/device](https://github.com/login/device) and enter the code shown in your terminal. +On remote or headless terminals, the device code flow is used instead. Your terminal will display a one-time code. Visit [github.com/login/device](https://github.com/login/device) and enter the code, then authorize access. ### Token expired diff --git a/01-setup-and-first-steps/README.md b/01-setup-and-first-steps/README.md index 485002ab..f7f80ba5 100644 --- a/01-setup-and-first-steps/README.md +++ b/01-setup-and-first-steps/README.md @@ -325,7 +325,7 @@ Step 4: Test the flow Proceed with implementation? [Y/n] ``` -**Key insight**: Plan mode lets you review and modify the approach before any code is written. Once a plan is complete, you can even tell Copilot CLI to save it to a file for later reference. For example, "Save this plan to `mark_as_read_plan.md`" would create a markdown file with the plan details. +**Key insight**: Plan mode lets you review and modify the approach before any code is written. While in plan mode, Copilot CLI is **read-only** and will not edit any files or run commands that change your workspace until you approve and move to implementation. This keeps you safely in the "thinking" stage until you're ready. Once a plan is complete, you can even tell Copilot CLI to save it to a file for later reference. For example, "Save this plan to `mark_as_read_plan.md`" would create a markdown file with the plan details. > 💡 **Want something more complex?** Try: `/plan Add search and filter capabilities to the book app`. Plan mode scales from simple features to full applications. @@ -383,11 +383,14 @@ These commands are great to learn initially as you're getting started with Copil | `/help` | Show all available commands | When you forget a command | | `/model` | Show or switch AI model | When you want to change the AI model | | `/plan` | Plan your work out before coding | For more complex features | +| `/refine` | Rewrite a rough, stream-of-consciousness prompt into a clear, focused one | When your prompt feels messy and you want better results | | `/research` | Deep research using GitHub and web sources | When you need to investigate a topic before coding | | `/exit` | End the session | When you're done | > 💡 **`/ask` vs regular chat**: Normally every message you send becomes part of the ongoing conversation and affects future responses. `/ask` is an "off the record" shortcut — perfect for quick one-off questions like `/ask What does YAML mean?` without polluting your session context. +> 💡 **`/refine` for better prompts**: Not sure if your prompt is clear enough? Type it out as it comes to mind, then run `/refine` to let Copilot rewrite it into a precise, well-structured prompt before sending. This is especially useful when you're new to AI tools and still learning how to write effective prompts. + > 💡 **Tab-completion**: When typing a slash command, press **Tab** to auto-complete the command name or cycle through available subcommands and arguments. This is especially handy when you can't remember the exact name of a command. That's it for getting started! As you become comfortable, you can explore additional commands. @@ -407,6 +410,8 @@ That's it for getting started! As you become comfortable, you can explore additi | `/env` | Show loaded environment details — what instructions, MCP servers, skills, agents, and plugins are active | | `/init` | Initialize Copilot instructions for your repository | | `/mcp` | Manage MCP server configuration | +| `/plugins` | Enable or disable plugins, instructions, agents, LSP servers, and hooks without restarting the session | +| `/settings` | Open an interactive dialog to browse and edit all user settings in one place | | `/skills` | Manage skills for enhanced capabilities | > 💡 Agents are covered in [Chapter 04](../04-agents-custom-instructions/README.md), skills are covered in [Chapter 05](../05-skills/README.md), and MCP servers are covered in [Chapter 06](../06-mcp-servers/README.md). @@ -436,6 +441,7 @@ That's it for getting started! As you become comfortable, you can explore additi |---------|--------------| | `/add-dir ` | Add a directory to allowed list | | `/allow-all [on\|off\|show]` | Auto-approve all permission prompts; use `on` to enable, `off` to disable, `show` to check current status | +| `/permissions` | Switch between approval modes (interactive, plan, autopilot) for controlling how much Copilot can do without asking | | `/yolo` | Quick alias for `/allow-all on` — auto-approves all permission prompts. | | `/cwd`, `/cd [directory]` | View or change working directory | | `/list-dirs` | Show all allowed directories | @@ -454,10 +460,12 @@ That's it for getting started! As you become comfortable, you can explore additi | `/new` | Ends the current session (saving it to history for search/resume) and starts a fresh conversation. | | `/resume` | Switch to a different session (optionally specify session ID or name) | | `/rename` | Rename the current session (omit the name to auto-generate one) | -| `/rewind` | Open a timeline picker to roll back to any earlier point in the conversation | +| `/rewind` | Open a timeline picker to roll back to any earlier point in the conversation; optionally restores the files Copilot changed (works without git) | | `/usage` | Display session usage metrics and statistics, including quota progress bars | | `/session` | Show session info and workspace summary; use `/session delete`, `/session delete `, or `/session delete-all` to remove sessions | | `/share` | Export session as a markdown file, GitHub gist, or self-contained HTML file | +| `/every ` | Schedule a prompt to run on a recurring interval (e.g., `/every 1h summarize new commits`). Use natural language for the interval. `/loop` is an alias for `/every`. | +| `/after