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..345702fd 100644 --- a/.github/uvs.csv +++ b/.github/uvs.csv @@ -95,3 +95,45 @@ "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 diff --git a/.github/views.csv b/.github/views.csv index 432f3d1e..4f566887 100644 --- a/.github/views.csv +++ b/.github/views.csv @@ -94,3 +94,45 @@ "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 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/01-setup-and-first-steps/README.md b/01-setup-and-first-steps/README.md index 485002ab..ba53741b 100644 --- a/01-setup-and-first-steps/README.md +++ b/01-setup-and-first-steps/README.md @@ -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,7 @@ 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 | +| `/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). @@ -458,6 +462,8 @@ That's it for getting started! As you become comfortable, you can explore additi | `/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